internal/glfw, interna/cglfw, internal/goglfw: add MousePassthrough

Work in progress

Updates #2511
This commit is contained in:
Hajime Hoshi 2023-09-18 14:52:25 +09:00
parent 777c575638
commit c8d38f7f25
14 changed files with 217 additions and 38 deletions

View File

@ -917,6 +917,9 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
return GLFW_FALSE; return GLFW_FALSE;
} }
if (wndconfig->mousePassthrough)
_glfwPlatformSetWindowMousePassthrough(window, GLFW_TRUE);
if (window->monitor) if (window->monitor)
{ {
_glfwPlatformShowWindow(window); _glfwPlatformShowWindow(window);
@ -1435,6 +1438,13 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled)
} // autoreleasepool } // autoreleasepool
} }
void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, GLFWbool enabled)
{
@autoreleasepool {
[window->ns.object setIgnoresMouseEvents:enabled];
}
}
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window) float _glfwPlatformGetWindowOpacity(_GLFWwindow* window)
{ {
@autoreleasepool { @autoreleasepool {

View File

@ -721,6 +721,13 @@ extern "C" {
*/ */
#define GLFW_FOCUS_ON_SHOW 0x0002000C #define GLFW_FOCUS_ON_SHOW 0x0002000C
/*! @brief Mouse input transparency window hint and attribute
*
* Mouse input transparency [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. /*! @brief Framebuffer bit depth hint.
* *
* Framebuffer bit depth [hint](@ref GLFW_RED_BITS). * Framebuffer bit depth [hint](@ref GLFW_RED_BITS).

View File

@ -169,6 +169,7 @@ struct _GLFWwndconfig
GLFWbool maximized; GLFWbool maximized;
GLFWbool centerCursor; GLFWbool centerCursor;
GLFWbool focusOnShow; GLFWbool focusOnShow;
GLFWbool mousePassthrough;
GLFWbool scaleToMonitor; GLFWbool scaleToMonitor;
struct { struct {
GLFWbool retina; GLFWbool retina;
@ -276,6 +277,7 @@ struct _GLFWwindow
GLFWbool autoIconify; GLFWbool autoIconify;
GLFWbool floating; GLFWbool floating;
GLFWbool focusOnShow; GLFWbool focusOnShow;
GLFWbool mousePassthrough;
GLFWbool shouldClose; GLFWbool shouldClose;
void* userPointer; void* userPointer;
GLFWbool doublebuffer; GLFWbool doublebuffer;
@ -504,6 +506,7 @@ void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled);
void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, GLFWbool enabled); void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, GLFWbool enabled);
void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled); void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled);
void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity); void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity);
void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, GLFWbool enabled);
void _glfwPlatformPollEvents(void); void _glfwPlatformPollEvents(void);
void _glfwPlatformWaitEvents(void); void _glfwPlatformWaitEvents(void);

View File

@ -175,13 +175,14 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height,
window->videoMode.blueBits = fbconfig.blueBits; window->videoMode.blueBits = fbconfig.blueBits;
window->videoMode.refreshRate = _glfw.hints.refreshRate; window->videoMode.refreshRate = _glfw.hints.refreshRate;
window->monitor = (_GLFWmonitor*) monitor; window->monitor = (_GLFWmonitor*) monitor;
window->resizable = wndconfig.resizable; window->resizable = wndconfig.resizable;
window->decorated = wndconfig.decorated; window->decorated = wndconfig.decorated;
window->autoIconify = wndconfig.autoIconify; window->autoIconify = wndconfig.autoIconify;
window->floating = wndconfig.floating; window->floating = wndconfig.floating;
window->focusOnShow = wndconfig.focusOnShow; window->focusOnShow = wndconfig.focusOnShow;
window->cursorMode = GLFW_CURSOR_NORMAL; window->mousePassthrough = wndconfig.mousePassthrough;
window->cursorMode = GLFW_CURSOR_NORMAL;
window->doublebuffer = fbconfig.doublebuffer; window->doublebuffer = fbconfig.doublebuffer;
@ -331,6 +332,9 @@ GLFWAPI void glfwWindowHint(int hint, int value)
case GLFW_FOCUS_ON_SHOW: case GLFW_FOCUS_ON_SHOW:
_glfw.hints.window.focusOnShow = value ? GLFW_TRUE : GLFW_FALSE; _glfw.hints.window.focusOnShow = value ? GLFW_TRUE : GLFW_FALSE;
return; return;
case GLFW_MOUSE_PASSTHROUGH:
_glfw.hints.window.mousePassthrough = value ? GLFW_TRUE : GLFW_FALSE;
return;
case GLFW_CLIENT_API: case GLFW_CLIENT_API:
_glfw.hints.context.client = value; _glfw.hints.context.client = value;
return; return;
@ -796,6 +800,8 @@ GLFWAPI int glfwGetWindowAttrib(GLFWwindow* handle, int attrib)
return _glfwPlatformWindowHovered(window); return _glfwPlatformWindowHovered(window);
case GLFW_FOCUS_ON_SHOW: case GLFW_FOCUS_ON_SHOW:
return window->focusOnShow; return window->focusOnShow;
case GLFW_MOUSE_PASSTHROUGH:
return window->mousePassthrough;
case GLFW_TRANSPARENT_FRAMEBUFFER: case GLFW_TRANSPARENT_FRAMEBUFFER:
return _glfwPlatformFramebufferTransparent(window); return _glfwPlatformFramebufferTransparent(window);
case GLFW_RESIZABLE: case GLFW_RESIZABLE:
@ -874,6 +880,11 @@ GLFWAPI void glfwSetWindowAttrib(GLFWwindow* handle, int attrib, int value)
} }
else if (attrib == GLFW_FOCUS_ON_SHOW) else if (attrib == GLFW_FOCUS_ON_SHOW)
window->focusOnShow = value; window->focusOnShow = value;
else if (attrib == GLFW_MOUSE_PASSTHROUGH)
{
window->mousePassthrough = value;
_glfwPlatformSetWindowMousePassthrough(window, value);
}
else else
_glfwInputError(GLFW_INVALID_ENUM, "Invalid window attribute 0x%08X", attrib); _glfwInputError(GLFW_INVALID_ENUM, "Invalid window attribute 0x%08X", attrib);
} }

View File

@ -820,6 +820,37 @@ static GLFWbool initExtensions(void)
} }
} }
#if defined(__CYGWIN__)
_glfw.x11.xshape.handle = _glfw_dlopen("libXext-6.so");
#elif defined(__OpenBSD__) || defined(__NetBSD__)
_glfw.x11.xshape.handle = _glfw_dlopen("libXext.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");
_glfw.x11.xshape.ShapeCombineMask = (PFN_XShapeCombineMask)
_glfw_dlsym(_glfw.x11.xshape.handle, "XShapeCombineMask");
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 // Update the key code LUT
// FIXME: We should listen to XkbMapNotify events to track changes to // FIXME: We should listen to XkbMapNotify events to track changes to
// the keyboard mapping. // the keyboard mapping.

View File

@ -27,6 +27,9 @@
// The XInput extension provides raw mouse motion input // The XInput extension provides raw mouse motion input
#include <X11/extensions/XInput2.h> #include <X11/extensions/XInput2.h>
// The Shape extension provides custom window shapes
#include <X11/extensions/shape.h>
typedef XRRCrtcGamma* (* PFN_XRRAllocGamma)(int); typedef XRRCrtcGamma* (* PFN_XRRAllocGamma)(int);
typedef void (* PFN_XRRFreeCrtcInfo)(XRRCrtcInfo*); typedef void (* PFN_XRRFreeCrtcInfo)(XRRCrtcInfo*);
typedef void (* PFN_XRRFreeGamma)(XRRCrtcGamma*); typedef void (* PFN_XRRFreeGamma)(XRRCrtcGamma*);
@ -109,6 +112,16 @@ typedef XRenderPictFormat* (* PFN_XRenderFindVisualFormat)(Display*,Visual const
#define XRenderQueryVersion _glfw.x11.xrender.QueryVersion #define XRenderQueryVersion _glfw.x11.xrender.QueryVersion
#define XRenderFindVisualFormat _glfw.x11.xrender.FindVisualFormat #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);
typedef void (* PFN_XShapeCombineMask)(Display*,Window,int,int,int,Pixmap,int);
#define XShapeQueryExtension _glfw.x11.xshape.QueryExtension
#define XShapeQueryVersion _glfw.x11.xshape.QueryVersion
#define XShapeCombineRegion _glfw.x11.xshape.ShapeCombineRegion
#define XShapeCombineMask _glfw.x11.xshape.ShapeCombineMask
#include "posix_thread.h" #include "posix_thread.h"
#include "posix_time_linbsd.h" #include "posix_time_linbsd.h"
#include "xkb_unicode_linbsd.h" #include "xkb_unicode_linbsd.h"
@ -365,6 +378,19 @@ typedef struct _GLFWlibraryX11
PFN_XRenderQueryVersion QueryVersion; PFN_XRenderQueryVersion QueryVersion;
PFN_XRenderFindVisualFormat FindVisualFormat; PFN_XRenderFindVisualFormat FindVisualFormat;
} xrender; } xrender;
struct {
GLFWbool available;
void* handle;
int major;
int minor;
int eventBase;
int errorBase;
PFN_XShapeQueryExtension QueryExtension;
PFN_XShapeCombineRegion ShapeCombineRegion;
PFN_XShapeQueryVersion QueryVersion;
PFN_XShapeCombineMask ShapeCombineMask;
} xshape;
} _GLFWlibraryX11; } _GLFWlibraryX11;
// X11-specific per-monitor data // X11-specific per-monitor data

View File

@ -2056,6 +2056,9 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
return GLFW_FALSE; return GLFW_FALSE;
} }
if (wndconfig->mousePassthrough)
_glfwPlatformSetWindowMousePassthrough(window, GLFW_TRUE);
if (window->monitor) if (window->monitor)
{ {
_glfwPlatformShowWindow(window); _glfwPlatformShowWindow(window);
@ -2741,6 +2744,25 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled)
XFlush(_glfw.x11.display); XFlush(_glfw.x11.display);
} }
void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, GLFWbool enabled)
{
if (!_glfw.x11.xshape.available)
return;
if (enabled)
{
Region region = XCreateRegion();
XShapeCombineRegion(_glfw.x11.display, window->x11.handle,
ShapeInput, 0, 0, region, ShapeSet);
XDestroyRegion(region);
}
else
{
XShapeCombineMask(_glfw.x11.display, window->x11.handle,
ShapeInput, 0, 0, None, ShapeSet);
}
}
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window) float _glfwPlatformGetWindowOpacity(_GLFWwindow* window)
{ {
float opacity = 1.f; float opacity = 1.f;

View File

@ -70,6 +70,7 @@ const (
FocusOnShow = Hint(0x0002000C) FocusOnShow = Hint(0x0002000C)
Iconified = Hint(0x00020002) Iconified = Hint(0x00020002)
Maximized = Hint(0x00020008) Maximized = Hint(0x00020008)
MousePassthrough = Hint(0x0002000D)
OpenGLForwardCompatible = Hint(0x00022006) OpenGLForwardCompatible = Hint(0x00022006)
OpenGLProfile = Hint(0x00022008) OpenGLProfile = Hint(0x00022008)
Resizable = Hint(0x00020003) Resizable = Hint(0x00020003)

View File

@ -333,6 +333,7 @@ const (
_WS_EX_LAYERED = 0x00080000 _WS_EX_LAYERED = 0x00080000
_WS_EX_OVERLAPPEDWINDOW = _WS_EX_WINDOWEDGE | _WS_EX_CLIENTEDGE _WS_EX_OVERLAPPEDWINDOW = _WS_EX_WINDOWEDGE | _WS_EX_CLIENTEDGE
_WS_EX_TOPMOST = 0x00000008 _WS_EX_TOPMOST = 0x00000008
_WS_EX_TRANSPARENT = 0x00000020
_WS_EX_WINDOWEDGE = 0x00000100 _WS_EX_WINDOWEDGE = 0x00000100
_WS_MAXIMIZE = 0x01000000 _WS_MAXIMIZE = 0x01000000
_WS_MAXIMIZEBOX = 0x00010000 _WS_MAXIMIZEBOX = 0x00010000

View File

@ -155,6 +155,10 @@ func (w *Window) platformSetWindowFloating(enabled bool) error {
panic("goglfw: Window.platformSetWindowFloating is not implemented yet") panic("goglfw: Window.platformSetWindowFloating is not implemented yet")
} }
func (w *Window) platformSetWindowMousePassthrough(enabled bool) error {
panic("goglfw: Window.platformSetWindowMousePassthrough is not implemented yet")
}
func (w *Window) platformGetWindowOpacity() (float32, error) { func (w *Window) platformGetWindowOpacity() (float32, error) {
// cocoa_window.m:L1462 // cocoa_window.m:L1462
panic("goglfw: Window.platformGetWindowOpacity is not implemented yet") panic("goglfw: Window.platformGetWindowOpacity is not implemented yet")

View File

@ -58,6 +58,7 @@ const (
TransparentFramebuffer Hint = 0x0002000A TransparentFramebuffer Hint = 0x0002000A
Hovered Hint = 0x0002000B Hovered Hint = 0x0002000B
FocusOnShow Hint = 0x0002000C FocusOnShow Hint = 0x0002000C
MousePassthrough Hint = 0x0002000D
RedBits Hint = 0x00021001 RedBits Hint = 0x00021001
GreenBits Hint = 0x00021002 GreenBits Hint = 0x00021002

View File

@ -23,19 +23,20 @@ type initconfig struct {
} }
type wndconfig struct { type wndconfig struct {
width int width int
height int height int
title string title string
resizable bool resizable bool
visible bool visible bool
decorated bool decorated bool
focused bool focused bool
autoIconify bool autoIconify bool
floating bool floating bool
maximized bool maximized bool
centerCursor bool centerCursor bool
focusOnShow bool focusOnShow bool
scaleToMonitor bool mousePassthrough bool
scaleToMonitor bool
} }
type ctxconfig struct { type ctxconfig struct {
@ -118,17 +119,18 @@ type (
) )
type Window struct { type Window struct {
resizable bool resizable bool
decorated bool decorated bool
autoIconify bool autoIconify bool
floating bool floating bool
focusOnShow bool focusOnShow bool
shouldClose bool mousePassthrough bool
userPointer unsafe.Pointer shouldClose bool
doublebuffer bool userPointer unsafe.Pointer
videoMode VidMode doublebuffer bool
monitor *Monitor videoMode VidMode
cursor *Cursor monitor *Monitor
cursor *Cursor
minwidth int minwidth int
minheight int minheight int

View File

@ -1409,6 +1409,12 @@ func (w *Window) platformCreateWindow(wndconfig *wndconfig, ctxconfig *ctxconfig
} }
} }
if wndconfig.mousePassthrough {
if err := w.platformSetWindowMousePassthrough(true); err != nil {
return err
}
}
if w.monitor != nil { if w.monitor != nil {
w.platformShowWindow() w.platformShowWindow()
if err := w.platformFocusWindow(); err != nil { if err := w.platformFocusWindow(); err != nil {
@ -1972,6 +1978,49 @@ func (w *Window) platformSetWindowFloating(enabled bool) error {
return _SetWindowPos(w.platform.handle, after, 0, 0, 0, 0, _SWP_NOACTIVATE|_SWP_NOMOVE|_SWP_NOSIZE) return _SetWindowPos(w.platform.handle, after, 0, 0, 0, 0, _SWP_NOACTIVATE|_SWP_NOMOVE|_SWP_NOSIZE)
} }
func (w *Window) platformSetWindowMousePassthrough(enabled bool) error {
exStyle, err := _GetWindowLongW(w.platform.handle, _GWL_EXSTYLE)
if err != nil {
return err
}
var key _COLORREF
var alpha byte
var flags uint32
if exStyle&_WS_EX_LAYERED != 0 {
var err error
key, alpha, flags, err = _GetLayeredWindowAttributes(w.platform.handle)
if err != nil {
return err
}
}
if enabled {
exStyle |= _WS_EX_TRANSPARENT | _WS_EX_LAYERED
} else {
exStyle &^= _WS_EX_TRANSPARENT
// NOTE: Window opacity also needs the layered window style so do not
// remove it if the window is alpha blended
if exStyle&_WS_EX_LAYERED != 0 {
if flags&_LWA_ALPHA == 0 {
exStyle &^= _WS_EX_LAYERED
}
}
}
if _, err := _SetWindowLongW(w.platform.handle, _GWL_EXSTYLE, exStyle); err != nil {
return err
}
if enabled {
if err := _SetLayeredWindowAttributes(w.platform.handle, key, alpha, flags); err != nil {
return err
}
}
return nil
}
func (w *Window) platformGetWindowOpacity() (float32, error) { func (w *Window) platformGetWindowOpacity() (float32, error) {
style, err := _GetWindowLongW(w.platform.handle, _GWL_EXSTYLE) style, err := _GetWindowLongW(w.platform.handle, _GWL_EXSTYLE)
if err != nil { if err != nil {

View File

@ -120,13 +120,14 @@ func CreateWindow(width, height int, title string, monitor *Monitor, share *Wind
RefreshRate: _glfw.hints.refreshRate, RefreshRate: _glfw.hints.refreshRate,
}, },
monitor: monitor, monitor: monitor,
resizable: wndconfig.resizable, resizable: wndconfig.resizable,
decorated: wndconfig.decorated, decorated: wndconfig.decorated,
autoIconify: wndconfig.autoIconify, autoIconify: wndconfig.autoIconify,
floating: wndconfig.floating, floating: wndconfig.floating,
focusOnShow: wndconfig.focusOnShow, focusOnShow: wndconfig.focusOnShow,
cursorMode: CursorNormal, mousePassthrough: wndconfig.mousePassthrough,
cursorMode: CursorNormal,
doublebuffer: fbconfig.doublebuffer, doublebuffer: fbconfig.doublebuffer,
@ -252,6 +253,8 @@ func WindowHint(hint Hint, value int) error {
_glfw.hints.window.centerCursor = intToBool(value) _glfw.hints.window.centerCursor = intToBool(value)
case FocusOnShow: case FocusOnShow:
_glfw.hints.window.focusOnShow = intToBool(value) _glfw.hints.window.focusOnShow = intToBool(value)
case MousePassthrough:
_glfw.hints.window.mousePassthrough = intToBool(value)
case ClientAPI: case ClientAPI:
_glfw.hints.context.client = value _glfw.hints.context.client = value
case ContextCreationAPI: case ContextCreationAPI:
@ -597,6 +600,8 @@ func (w *Window) GetAttrib(attrib Hint) (int, error) {
return boolToInt(b), nil return boolToInt(b), nil
case FocusOnShow: case FocusOnShow:
return boolToInt(w.focusOnShow), nil return boolToInt(w.focusOnShow), nil
case MousePassthrough:
return boolToInt(w.mousePassthrough), nil
case TransparentFramebuffer: case TransparentFramebuffer:
return boolToInt(w.platformFramebufferTransparent()), nil return boolToInt(w.platformFramebufferTransparent()), nil
case Resizable: case Resizable:
@ -681,6 +686,12 @@ func (w *Window) SetAttrib(attrib Hint, value int) error {
case FocusOnShow: case FocusOnShow:
w.focusOnShow = bValue w.focusOnShow = bValue
return nil return nil
case MousePassthrough:
w.mousePassthrough = bValue
if err := w.platformSetWindowMousePassthrough(bValue); err != nil {
return err
}
return nil
default: default:
return fmt.Errorf("goglfw: invalid window attribute 0x%08X: %w", attrib, InvalidEnum) return fmt.Errorf("goglfw: invalid window attribute 0x%08X: %w", attrib, InvalidEnum)
} }