From 2402f35e6c67c62a932fd16c6541f28c6dd27374 Mon Sep 17 00:00:00 2001 From: catbot Date: Fri, 12 Jun 2026 15:16:57 +0000 Subject: [PATCH] fix(wayland): guard null focusedWindow in pointer handlers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sway delivers pointer events in surprising orders around input-device hot-plug — observed: a second wl_pointer.leave after focus was already cleared, which crashed in PointerListenerHandleLeave (null deref), and motion/button would crash the same way if an enter never matched one of our surfaces. Early-return instead. Found while end-to-end-testing the scroll chain for #32 with a zwlr_virtual_pointer_v1 injector. Co-Authored-By: Claude Opus 4.8 --- implementations/Crafter.Graphics-Device.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/implementations/Crafter.Graphics-Device.cpp b/implementations/Crafter.Graphics-Device.cpp index b5c283c..016242a 100644 --- a/implementations/Crafter.Graphics-Device.cpp +++ b/implementations/Crafter.Graphics-Device.cpp @@ -223,6 +223,11 @@ void Device::handle_global_remove(void* data, wl_registry* registry, uint32_t na } void Device::pointer_handle_button(void* data, wl_pointer* pointer, std::uint32_t serial, std::uint32_t time, std::uint32_t button, std::uint32_t state) { + // Defensive: compositors can deliver pointer events without (or after) + // a matching enter — observed on sway during input-device hot-plug. + if (focusedWindow == nullptr) { + return; + } if (button == BTN_LEFT) { if(state == WL_POINTER_BUTTON_STATE_PRESSED) { Device::focusedWindow->mouseLeftHeld = true; @@ -243,6 +248,9 @@ void Device::pointer_handle_button(void* data, wl_pointer* pointer, std::uint32_ } void Device::PointerListenerHandleMotion(void* data, wl_pointer* wl_pointer, std::uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) { + if (focusedWindow == nullptr) { + return; + } Vector pos(wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y)); //Device::focusedWindow->lastMousePos = Device::focusedWindow->currentMousePos; Device::focusedWindow->currentMousePos = pos * Device::focusedWindow->scale; @@ -267,8 +275,12 @@ void Device::PointerListenerHandleEnter(void* data, wl_pointer* wl_pointer, std: } void Device::PointerListenerHandleLeave(void* data, wl_pointer*, std::uint32_t, wl_surface*) { - Device::focusedWindow->onMouseLeave.Invoke(); - focusedWindow = nullptr; + // Sway has been observed to send a second leave after focus is already + // gone (input-device hot-plug) — don't crash on it. + if (focusedWindow != nullptr) { + focusedWindow->onMouseLeave.Invoke(); + focusedWindow = nullptr; + } // Drop any sub-detent scroll remainder so it can't leak into whatever // window the pointer enters next. scrollDetentRemainder = 0.0;