Compare commits

...

9 Commits

Author SHA1 Message Date
Felipe Jorge
865655ae09
Merge 52243c2ff4 into 768e81a0eb 2025-08-14 17:06:50 +03:00
Jan Hendrik Farr
768e81a0eb Wayland: Fix key repeat halting
Key repeat shoud only be halted when the repeating key
is released, not when another key is released.
2025-08-14 15:35:04 +02:00
Camilla Löwy
161fb1b6f6 Wayland: Fix fallback decoration scroll events
The fallback decorations would emit scroll events as if scrolling had
occurred over the content area of the window.
2025-08-12 17:11:27 +02:00
Camilla Löwy
645a35a38e Wayland: Cleanup 2025-08-12 17:11:27 +02:00
Camilla Löwy
7523b0e6bd Wayland: Move fallback decoration pointer logic
Decluttered the wl_pointer handlers by moving the bulk of fallback
decoration related logic to separate functions.
2025-08-12 17:11:26 +02:00
Camilla Löwy
5190a30d8a Wayland: Move fallback decoration struct member
The cursorPreviousName member was only used for the fallback decorations
but was not grouped with other related members.
2025-08-12 17:11:26 +02:00
Camilla Löwy
ddbb8e0f2c Wayland: Fix fallback decoration cursor position
If fallback decorations were in use, pointer motion over a decoration
surface would cause glfwGetCursorPos to provide incorrect cursor
positions.

The cursor position is now only updated when the pointer is over the
content area of the window, similar to libdecor and XDG decorations.
2025-08-12 17:11:24 +02:00
Camilla Löwy
5245180c56 Formatting 2025-08-12 17:10:43 +02:00
Felipe Jorge
52243c2ff4 Win32: Update GUID for SDL newer format 2025-07-15 17:41:32 -03:00
7 changed files with 258 additions and 193 deletions

View File

@ -68,6 +68,7 @@ video tutorials.
- Jan Ekström - Jan Ekström
- Siavash Eliasi - Siavash Eliasi
- er-azh - er-azh
- Jan Hendrik Farr
- Ahmad Fatoum - Ahmad Fatoum
- Nikita Fediuchin - Nikita Fediuchin
- Felipe Ferreira - Felipe Ferreira

View File

@ -135,6 +135,10 @@ information on what to include when reporting a bug.
- [Wayland] Bugfix: Reset key repeat timer when window destroyed (#2741,#2727) - [Wayland] Bugfix: Reset key repeat timer when window destroyed (#2741,#2727)
- [Wayland] Bugfix: Memory would leak if reading a data offer failed midway - [Wayland] Bugfix: Memory would leak if reading a data offer failed midway
- [Wayland] Bugfix: Keyboard leave event handler now processes key repeats (#2736) - [Wayland] Bugfix: Keyboard leave event handler now processes key repeats (#2736)
- [Wayland] Bugfix: Retrieved cursor position would be incorrect when hovering over
fallback decorations
- [Wayland] Bugfix: Fallback decorations would report scroll events
- [Wayland] Bugfix: Keyboard repeat events halted when any key is released (#2568)
- [X11] Bugfix: Running without a WM could trigger an assert (#2593,#2601,#2631) - [X11] Bugfix: Running without a WM could trigger an assert (#2593,#2601,#2631)
- [Null] Added Vulkan 'window' surface creation via `VK_EXT_headless_surface` - [Null] Added Vulkan 'window' surface creation via `VK_EXT_headless_surface`
- [Null] Added EGL context creation on Mesa via `EGL_MESA_platform_surfaceless` - [Null] Added EGL context creation on Mesa via `EGL_MESA_platform_surfaceless`

View File

@ -126,6 +126,9 @@ static GLFWbool loadLibraries(void)
{ {
_glfw.win32.xinput.GetCapabilities = (PFN_XInputGetCapabilities) _glfw.win32.xinput.GetCapabilities = (PFN_XInputGetCapabilities)
_glfwPlatformGetModuleSymbol(_glfw.win32.xinput.instance, "XInputGetCapabilities"); _glfwPlatformGetModuleSymbol(_glfw.win32.xinput.instance, "XInputGetCapabilities");
// 108 is the ordinal for _XInputGetCapabilitiesEx, which additionally returns VID/PID of the controller.
_glfw.win32.xinput.GetCapabilitiesEx = (PFN_XInputGetCapabilitiesEx)
_glfwPlatformGetModuleSymbol(_glfw.win32.xinput.instance, (LPCSTR)108);
_glfw.win32.xinput.GetState = (PFN_XInputGetState) _glfw.win32.xinput.GetState = (PFN_XInputGetState)
_glfwPlatformGetModuleSymbol(_glfw.win32.xinput.instance, "XInputGetState"); _glfwPlatformGetModuleSymbol(_glfw.win32.xinput.instance, "XInputGetState");

View File

@ -519,12 +519,33 @@ void _glfwDetectJoystickConnectionWin32(void)
if (jid <= GLFW_JOYSTICK_LAST) if (jid <= GLFW_JOYSTICK_LAST)
continue; continue;
if (XInputGetCapabilities(index, 0, &xic) != ERROR_SUCCESS) if (XInputGetCapabilities(index, XINPUT_FLAG_GAMEPAD, &xic) != ERROR_SUCCESS)
continue; continue;
// Generate a joystick GUID that matches the SDL 2.0.5+ one GLFW_XINPUT_CAPABILITIES_EX xic_ex;
sprintf(guid, "78696e707574%02x000000000000000000", if (!XInputGetCapabilitiesEx || XInputGetCapabilitiesEx(1, index, 0, &xic_ex) != ERROR_SUCCESS)
xic.SubType & 0xff); {
// use a generic VID/PID representing an XInput controller
xic_ex.VendorId = USB_VENDOR_MICROSOFT;
xic_ex.ProductId = USB_PRODUCT_XBOX360_XUSB_CONTROLLER;
xic_ex.VersionNumber = 0;
}
else
{
// fixup for Wireless Xbox 360 Controller
if (xic_ex.ProductId == 0 && xic_ex.Capabilities.Flags & XINPUT_CAPS_WIRELESS)
{
xic_ex.VendorId = USB_VENDOR_MICROSOFT;
xic_ex.ProductId = USB_PRODUCT_XBOX360_XUSB_CONTROLLER;
}
}
// Generate a joystick GUID that matches the SDL one
// SDL_xinputjoystick.c:207
uint8_t* vid = (uint8_t*)&xic_ex.VendorId;
uint8_t* pid = (uint8_t*)&xic_ex.ProductId;
uint8_t* ver = (uint8_t*)&xic_ex.VersionNumber;
sprintf(guid, "03000000%02x%02x0000%02x%02x0000%02x%02x0000", vid[0], vid[1], pid[0], pid[1], ver[0], ver[1]);
js = _glfwAllocJoystick(getDeviceDescription(&xic), guid, 6, 10, 1); js = _glfwAllocJoystick(getDeviceDescription(&xic), guid, 6, 10, 1);
if (!js) if (!js)

View File

@ -216,11 +216,26 @@ typedef enum
#define ERROR_INVALID_PROFILE_ARB 0x2096 #define ERROR_INVALID_PROFILE_ARB 0x2096
#define ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB 0x2054 #define ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB 0x2054
// this struct might not defined in XInput headers
typedef struct
{
XINPUT_CAPABILITIES Capabilities;
WORD VendorId;
WORD ProductId;
WORD VersionNumber;
WORD unk1;
DWORD unk2;
} GLFW_XINPUT_CAPABILITIES_EX;
// xinput.dll function pointer typedefs // xinput.dll function pointer typedefs
typedef DWORD (WINAPI * PFN_XInputGetCapabilities)(DWORD,DWORD,XINPUT_CAPABILITIES*); typedef DWORD (WINAPI * PFN_XInputGetCapabilities)(DWORD,DWORD,XINPUT_CAPABILITIES*);
typedef DWORD (WINAPI * PFN_XInputGetCapabilitiesEx)(DWORD,DWORD,DWORD,GLFW_XINPUT_CAPABILITIES_EX*);
typedef DWORD (WINAPI * PFN_XInputGetState)(DWORD,XINPUT_STATE*); typedef DWORD (WINAPI * PFN_XInputGetState)(DWORD,XINPUT_STATE*);
#define XInputGetCapabilities _glfw.win32.xinput.GetCapabilities #define XInputGetCapabilities _glfw.win32.xinput.GetCapabilities
#define XInputGetCapabilitiesEx _glfw.win32.xinput.GetCapabilitiesEx
#define XInputGetState _glfw.win32.xinput.GetState #define XInputGetState _glfw.win32.xinput.GetState
#define USB_VENDOR_MICROSOFT 0x045e
#define USB_PRODUCT_XBOX360_XUSB_CONTROLLER 0x02a1 // XUSB driver software PID
// dinput8.dll function pointer typedefs // dinput8.dll function pointer typedefs
typedef HRESULT (WINAPI * PFN_DirectInput8Create)(HINSTANCE,DWORD,REFIID,LPVOID*,LPUNKNOWN); typedef HRESULT (WINAPI * PFN_DirectInput8Create)(HINSTANCE,DWORD,REFIID,LPVOID*,LPUNKNOWN);
@ -412,6 +427,7 @@ typedef struct _GLFWlibraryWin32
struct { struct {
HINSTANCE instance; HINSTANCE instance;
PFN_XInputGetCapabilities GetCapabilities; PFN_XInputGetCapabilities GetCapabilities;
PFN_XInputGetCapabilitiesEx GetCapabilitiesEx;
PFN_XInputGetState GetState; PFN_XInputGetState GetState;
} xinput; } xinput;

View File

@ -413,6 +413,8 @@ typedef struct _GLFWwindowWayland
struct wl_buffer* buffer; struct wl_buffer* buffer;
_GLFWfallbackEdgeWayland top, left, right, bottom; _GLFWfallbackEdgeWayland top, left, right, bottom;
struct wl_surface* focus; struct wl_surface* focus;
wl_fixed_t pointerX, pointerY;
const char* cursorName;
} fallback; } fallback;
} _GLFWwindowWayland; } _GLFWwindowWayland;
@ -454,7 +456,6 @@ typedef struct _GLFWlibraryWayland
struct wl_cursor_theme* cursorTheme; struct wl_cursor_theme* cursorTheme;
struct wl_cursor_theme* cursorThemeHiDPI; struct wl_cursor_theme* cursorThemeHiDPI;
struct wl_surface* cursorSurface; struct wl_surface* cursorSurface;
const char* cursorPreviousName;
int cursorTimerfd; int cursorTimerfd;
uint32_t serial; uint32_t serial;
uint32_t pointerEnterSerial; uint32_t pointerEnterSerial;

View File

@ -275,6 +275,146 @@ static void destroyFallbackDecorations(_GLFWwindow* window)
destroyFallbackEdge(&window->wl.fallback.bottom); destroyFallbackEdge(&window->wl.fallback.bottom);
} }
static void updateFallbackDecorationCursor(_GLFWwindow* window,
wl_fixed_t sx,
wl_fixed_t sy)
{
window->wl.fallback.pointerX = sx;
window->wl.fallback.pointerY = sy;
const double xpos = wl_fixed_to_double(sx);
const double ypos = wl_fixed_to_double(sy);
const char* cursorName = "left_ptr";
if (window->resizable)
{
if (window->wl.fallback.focus == window->wl.fallback.top.surface)
{
if (ypos < GLFW_BORDER_SIZE)
cursorName = "n-resize";
}
else if (window->wl.fallback.focus == window->wl.fallback.left.surface)
{
if (ypos < GLFW_BORDER_SIZE)
cursorName = "nw-resize";
else
cursorName = "w-resize";
}
else if (window->wl.fallback.focus == window->wl.fallback.right.surface)
{
if (ypos < GLFW_BORDER_SIZE)
cursorName = "ne-resize";
else
cursorName = "e-resize";
}
else if (window->wl.fallback.focus == window->wl.fallback.bottom.surface)
{
if (xpos < GLFW_BORDER_SIZE)
cursorName = "sw-resize";
else if (xpos > window->wl.width + GLFW_BORDER_SIZE)
cursorName = "se-resize";
else
cursorName = "s-resize";
}
}
if (window->wl.fallback.cursorName != cursorName)
{
struct wl_surface* surface = _glfw.wl.cursorSurface;
struct wl_cursor_theme* theme = _glfw.wl.cursorTheme;
int scale = 1;
if (window->wl.bufferScale > 1 && _glfw.wl.cursorThemeHiDPI)
{
// We only support up to scale=2 for now, since libwayland-cursor
// requires us to load a different theme for each size.
scale = 2;
theme = _glfw.wl.cursorThemeHiDPI;
}
struct wl_cursor* cursor = wl_cursor_theme_get_cursor(theme, cursorName);
if (!cursor)
return;
// TODO: handle animated cursors too.
struct wl_cursor_image* image = cursor->images[0];
if (!image)
return;
struct wl_buffer* buffer = wl_cursor_image_get_buffer(image);
if (!buffer)
return;
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerEnterSerial,
surface,
image->hotspot_x / scale,
image->hotspot_y / scale);
wl_surface_set_buffer_scale(surface, scale);
wl_surface_attach(surface, buffer, 0, 0);
wl_surface_damage(surface, 0, 0, image->width, image->height);
wl_surface_commit(surface);
window->wl.fallback.cursorName = cursorName;
}
}
static void handleFallbackDecorationButton(_GLFWwindow* window,
uint32_t serial,
uint32_t button)
{
const double xpos = wl_fixed_to_double(window->wl.fallback.pointerX);
const double ypos = wl_fixed_to_double(window->wl.fallback.pointerY);
if (button == BTN_LEFT)
{
uint32_t edges = XDG_TOPLEVEL_RESIZE_EDGE_NONE;
if (window->wl.fallback.focus == window->wl.fallback.top.surface)
{
if (ypos < GLFW_BORDER_SIZE)
edges = XDG_TOPLEVEL_RESIZE_EDGE_TOP;
else
xdg_toplevel_move(window->wl.xdg.toplevel, _glfw.wl.seat, serial);
}
else if (window->wl.fallback.focus == window->wl.fallback.left.surface)
{
if (ypos < GLFW_BORDER_SIZE)
edges = XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT;
else
edges = XDG_TOPLEVEL_RESIZE_EDGE_LEFT;
}
else if (window->wl.fallback.focus == window->wl.fallback.right.surface)
{
if (ypos < GLFW_BORDER_SIZE)
edges = XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT;
else
edges = XDG_TOPLEVEL_RESIZE_EDGE_RIGHT;
}
else if (window->wl.fallback.focus == window->wl.fallback.bottom.surface)
{
if (xpos < GLFW_BORDER_SIZE)
edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT;
else if (xpos > window->wl.width + GLFW_BORDER_SIZE)
edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT;
else
edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM;
}
if (edges != XDG_TOPLEVEL_RESIZE_EDGE_NONE)
xdg_toplevel_resize(window->wl.xdg.toplevel, _glfw.wl.seat, serial, edges);
}
else if (button == BTN_RIGHT)
{
if (window->wl.xdg.toplevel)
{
xdg_toplevel_show_window_menu(window->wl.xdg.toplevel,
_glfw.wl.seat, serial,
window->wl.cursorPosX,
window->wl.cursorPosY);
}
}
}
static void xdgDecorationHandleConfigure(void* userData, static void xdgDecorationHandleConfigure(void* userData,
struct zxdg_toplevel_decoration_v1* decoration, struct zxdg_toplevel_decoration_v1* decoration,
uint32_t mode) uint32_t mode)
@ -1417,7 +1557,6 @@ static void pointerHandleLeave(void* userData,
_glfw.wl.serial = serial; _glfw.wl.serial = serial;
_glfw.wl.pointerFocus = NULL; _glfw.wl.pointerFocus = NULL;
_glfw.wl.cursorPreviousName = NULL;
if (window->wl.hovered) if (window->wl.hovered)
{ {
@ -1427,7 +1566,10 @@ static void pointerHandleLeave(void* userData,
else else
{ {
if (window->wl.fallback.decorations) if (window->wl.fallback.decorations)
{
window->wl.fallback.focus = NULL; window->wl.fallback.focus = NULL;
window->wl.fallback.cursorName = NULL;
}
} }
} }
@ -1444,92 +1586,16 @@ static void pointerHandleMotion(void* userData,
if (window->cursorMode == GLFW_CURSOR_DISABLED) if (window->cursorMode == GLFW_CURSOR_DISABLED)
return; return;
const double xpos = wl_fixed_to_double(sx);
const double ypos = wl_fixed_to_double(sy);
window->wl.cursorPosX = xpos;
window->wl.cursorPosY = ypos;
if (window->wl.hovered) if (window->wl.hovered)
{ {
_glfw.wl.cursorPreviousName = NULL; window->wl.cursorPosX = wl_fixed_to_double(sx);
_glfwInputCursorPos(window, xpos, ypos); window->wl.cursorPosY = wl_fixed_to_double(sy);
return; _glfwInputCursorPos(window, window->wl.cursorPosX, window->wl.cursorPosY);
} }
else
{
if (window->wl.fallback.decorations) if (window->wl.fallback.decorations)
{ updateFallbackDecorationCursor(window, sx, sy);
const char* cursorName = "left_ptr";
if (window->resizable)
{
if (window->wl.fallback.focus == window->wl.fallback.top.surface)
{
if (ypos < GLFW_BORDER_SIZE)
cursorName = "n-resize";
}
else if (window->wl.fallback.focus == window->wl.fallback.left.surface)
{
if (ypos < GLFW_BORDER_SIZE)
cursorName = "nw-resize";
else
cursorName = "w-resize";
}
else if (window->wl.fallback.focus == window->wl.fallback.right.surface)
{
if (ypos < GLFW_BORDER_SIZE)
cursorName = "ne-resize";
else
cursorName = "e-resize";
}
else if (window->wl.fallback.focus == window->wl.fallback.bottom.surface)
{
if (xpos < GLFW_BORDER_SIZE)
cursorName = "sw-resize";
else if (xpos > window->wl.width + GLFW_BORDER_SIZE)
cursorName = "se-resize";
else
cursorName = "s-resize";
}
}
if (_glfw.wl.cursorPreviousName != cursorName)
{
struct wl_surface* surface = _glfw.wl.cursorSurface;
struct wl_cursor_theme* theme = _glfw.wl.cursorTheme;
int scale = 1;
if (window->wl.bufferScale > 1 && _glfw.wl.cursorThemeHiDPI)
{
// We only support up to scale=2 for now, since libwayland-cursor
// requires us to load a different theme for each size.
scale = 2;
theme = _glfw.wl.cursorThemeHiDPI;
}
struct wl_cursor* cursor = wl_cursor_theme_get_cursor(theme, cursorName);
if (!cursor)
return;
// TODO: handle animated cursors too.
struct wl_cursor_image* image = cursor->images[0];
if (!image)
return;
struct wl_buffer* buffer = wl_cursor_image_get_buffer(image);
if (!buffer)
return;
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerEnterSerial,
surface,
image->hotspot_x / scale,
image->hotspot_y / scale);
wl_surface_set_buffer_scale(surface, scale);
wl_surface_attach(surface, buffer, 0, 0);
wl_surface_damage(surface, 0, 0, image->width, image->height);
wl_surface_commit(surface);
_glfw.wl.cursorPreviousName = cursorName;
}
} }
} }
@ -1552,62 +1618,11 @@ static void pointerHandleButton(void* userData,
button - BTN_LEFT, button - BTN_LEFT,
state == WL_POINTER_BUTTON_STATE_PRESSED, state == WL_POINTER_BUTTON_STATE_PRESSED,
_glfw.wl.xkb.modifiers); _glfw.wl.xkb.modifiers);
return;
} }
else
{
if (window->wl.fallback.decorations) if (window->wl.fallback.decorations)
{ handleFallbackDecorationButton(window, serial, button);
if (button == BTN_LEFT)
{
uint32_t edges = XDG_TOPLEVEL_RESIZE_EDGE_NONE;
if (window->wl.fallback.focus == window->wl.fallback.top.surface)
{
if (window->wl.cursorPosY < GLFW_BORDER_SIZE)
edges = XDG_TOPLEVEL_RESIZE_EDGE_TOP;
else
xdg_toplevel_move(window->wl.xdg.toplevel, _glfw.wl.seat, serial);
}
else if (window->wl.fallback.focus == window->wl.fallback.left.surface)
{
if (window->wl.cursorPosY < GLFW_BORDER_SIZE)
edges = XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT;
else
edges = XDG_TOPLEVEL_RESIZE_EDGE_LEFT;
}
else if (window->wl.fallback.focus == window->wl.fallback.right.surface)
{
if (window->wl.cursorPosY < GLFW_BORDER_SIZE)
edges = XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT;
else
edges = XDG_TOPLEVEL_RESIZE_EDGE_RIGHT;
}
else if (window->wl.fallback.focus == window->wl.fallback.bottom.surface)
{
if (window->wl.cursorPosX < GLFW_BORDER_SIZE)
edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT;
else if (window->wl.cursorPosX > window->wl.width + GLFW_BORDER_SIZE)
edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT;
else
edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM;
}
if (edges != XDG_TOPLEVEL_RESIZE_EDGE_NONE)
{
xdg_toplevel_resize(window->wl.xdg.toplevel, _glfw.wl.seat,
serial, edges);
}
}
else if (button == BTN_RIGHT)
{
if (window->wl.xdg.toplevel)
{
xdg_toplevel_show_window_menu(window->wl.xdg.toplevel,
_glfw.wl.seat, serial,
window->wl.cursorPosX,
window->wl.cursorPosY);
}
}
} }
} }
@ -1621,12 +1636,15 @@ static void pointerHandleAxis(void* userData,
if (!window) if (!window)
return; return;
if (window->wl.hovered)
{
// NOTE: 10 units of motion per mouse wheel step seems to be a common ratio // NOTE: 10 units of motion per mouse wheel step seems to be a common ratio
if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL)
_glfwInputScroll(window, -wl_fixed_to_double(value) / 10.0, 0.0); _glfwInputScroll(window, -wl_fixed_to_double(value) / 10.0, 0.0);
else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL)
_glfwInputScroll(window, 0.0, -wl_fixed_to_double(value) / 10.0); _glfwInputScroll(window, 0.0, -wl_fixed_to_double(value) / 10.0);
} }
}
static const struct wl_pointer_listener pointerListener = static const struct wl_pointer_listener pointerListener =
{ {
@ -1820,10 +1838,11 @@ static void keyboardHandleKey(void* userData,
timer.it_value.tv_sec = _glfw.wl.keyRepeatDelay / 1000; timer.it_value.tv_sec = _glfw.wl.keyRepeatDelay / 1000;
timer.it_value.tv_nsec = (_glfw.wl.keyRepeatDelay % 1000) * 1000000; timer.it_value.tv_nsec = (_glfw.wl.keyRepeatDelay % 1000) * 1000000;
}
}
timerfd_settime(_glfw.wl.keyRepeatTimerfd, 0, &timer, NULL); timerfd_settime(_glfw.wl.keyRepeatTimerfd, 0, &timer, NULL);
}
} else if (scancode == _glfw.wl.keyRepeatScancode) {
timerfd_settime(_glfw.wl.keyRepeatTimerfd, 0, &timer, NULL);
}
_glfwInputKey(window, key, scancode, action, _glfw.wl.xkb.modifiers); _glfwInputKey(window, key, scancode, action, _glfw.wl.xkb.modifiers);