Wayland: Handle complex pointer event frames
From the spec: > The wl_pointer.enter and wl_pointer.leave events are logical events > generated by the compositor and not the hardware. These events are > also grouped by a wl_pointer.frame. When a pointer moves from one > surface to another, a compositor should group the wl_pointer.leave > event within the same wl_pointer.frame. However, a client must not > rely on wl_pointer.leave and wl_pointer.enter being in the same > wl_pointer.frame. Compositor-specific policies may require the > wl_pointer.leave and wl_pointer.enter event being split across > multiple wl_pointer.frame groups. From my understanding™ this means that a compositor SHOULD group leave/enter events together. Is this common? From my testing... Not really. Notably, (only?) KDE does this. Our pointer frame event assumed that we would be working with the currently pointed window but since all events must be logically grouped together I think it can really only group "normal" events related to the *leave* event. Now, whenever there's a pointer focus change, we send everything to the old window, if it exists, otherwise the currently pointed one. This approach seems to handle complex event frames with both leave and enter events properly now, with good results on all compositors. This patch also and makes it harder to get to a null check when the window simply does not exists (the error was meant only for existing but invalid windows), along with an helper method to aid in this.
This commit is contained in:
@@ -1916,7 +1916,6 @@ void WaylandThread::_wl_pointer_on_leave(void *data, struct wl_pointer *wl_point
|
||||
DisplayServerEnums::WindowID id = pd.pointed_id;
|
||||
|
||||
pd.pointed_id = DisplayServerEnums::INVALID_WINDOW_ID;
|
||||
pd.pressed_button_mask.clear();
|
||||
|
||||
DEBUG_LOG_WAYLAND_THREAD(vformat("Pointer left window %d.", id));
|
||||
|
||||
@@ -2025,14 +2024,19 @@ void WaylandThread::_wl_pointer_on_frame(void *data, struct wl_pointer *wl_point
|
||||
PointerData &old_pd = ss->pointer_data;
|
||||
PointerData &pd = ss->pointer_data_buffer;
|
||||
|
||||
WindowState *ws = nullptr;
|
||||
|
||||
if (pd.pointed_id != old_pd.pointed_id) {
|
||||
if (old_pd.pointed_id != DisplayServerEnums::INVALID_WINDOW_ID) {
|
||||
pd.pressed_button_mask.clear();
|
||||
|
||||
Ref<WindowEventMessage> msg;
|
||||
msg.instantiate();
|
||||
msg->id = old_pd.pointed_id;
|
||||
msg->event = DisplayServerEnums::WINDOW_EVENT_MOUSE_EXIT;
|
||||
|
||||
wayland_thread->push_message(msg);
|
||||
|
||||
DEBUG_LOG_WAYLAND_THREAD(vformat("Notifying mouse exit to window %d", msg->id));
|
||||
}
|
||||
|
||||
if (pd.pointed_id != DisplayServerEnums::INVALID_WINDOW_ID) {
|
||||
@@ -2040,24 +2044,32 @@ void WaylandThread::_wl_pointer_on_frame(void *data, struct wl_pointer *wl_point
|
||||
msg.instantiate();
|
||||
msg->id = pd.pointed_id;
|
||||
msg->event = DisplayServerEnums::WINDOW_EVENT_MOUSE_ENTER;
|
||||
DEBUG_LOG_WAYLAND_THREAD(vformat("Notifying mouse enter to window %d", msg->id));
|
||||
|
||||
wayland_thread->push_message(msg);
|
||||
}
|
||||
|
||||
// According to the spec, compositors SHOULD emit both leave and enter events
|
||||
// in one frame. Given that the frame event groups logically related events,
|
||||
// it makes sense that all other events outside `enter` are related to the OLD
|
||||
// surface. Additionally, some compositors (e.g. sway) emit other events
|
||||
// alongside a leave event in one single frame, further confirming this
|
||||
// behavior.
|
||||
if (wayland_thread->window_exists(old_pd.pointed_id)) {
|
||||
ws = wayland_thread->window_get_state(old_pd.pointed_id);
|
||||
if (ws == nullptr) {
|
||||
// Not ERR_FAIL_* as we still want to fall through.
|
||||
ERR_PRINT("Invalid window userdata.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WindowState *ws = nullptr;
|
||||
|
||||
// NOTE: At least on sway, with wl_pointer version 5 or greater,
|
||||
// wl_pointer::leave might be emitted with other events (like
|
||||
// wl_pointer::button) within the same wl_pointer::frame. Because of this, we
|
||||
// need to account for when the currently pointed window might be invalid
|
||||
// (third-party or even none) and fall back to the old one.
|
||||
if (pd.pointed_id != DisplayServerEnums::INVALID_WINDOW_ID) {
|
||||
ws = ss->wayland_thread->window_get_state(pd.pointed_id);
|
||||
ERR_FAIL_NULL(ws);
|
||||
} else if (old_pd.pointed_id != DisplayServerEnums::INVALID_WINDOW_ID) {
|
||||
ws = ss->wayland_thread->window_get_state(old_pd.pointed_id);
|
||||
ERR_FAIL_NULL(ws);
|
||||
if (ws == nullptr && wayland_thread->window_exists(pd.pointed_id)) {
|
||||
ws = wayland_thread->window_get_state(pd.pointed_id);
|
||||
if (ws == nullptr) {
|
||||
// Not ERR_FAIL_* as we still want to fall through.
|
||||
ERR_PRINT("Invalid window userdata.");
|
||||
}
|
||||
}
|
||||
|
||||
if (ws == nullptr) {
|
||||
@@ -4313,6 +4325,14 @@ void WaylandThread::window_destroy(DisplayServerEnums::WindowID p_window_id) {
|
||||
windows.erase(p_window_id);
|
||||
}
|
||||
|
||||
bool WaylandThread::window_exists(DisplayServerEnums::WindowID p_window_id) {
|
||||
if (p_window_id == DisplayServerEnums::INVALID_WINDOW_ID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return windows.has(p_window_id);
|
||||
}
|
||||
|
||||
struct wl_surface *WaylandThread::window_get_wl_surface(DisplayServerEnums::WindowID p_window_id) const {
|
||||
const WindowState *ws = windows.getptr(p_window_id);
|
||||
if (ws) {
|
||||
|
||||
@@ -1231,6 +1231,10 @@ public:
|
||||
void window_create_popup(DisplayServerEnums::WindowID p_window_id, DisplayServerEnums::WindowID p_parent_id, Rect2i p_rect);
|
||||
void window_destroy(DisplayServerEnums::WindowID p_window_Id);
|
||||
|
||||
// Checks if a window exists for this ID (NOT if its data is valid). Useful to
|
||||
// detect deleted windows.
|
||||
bool window_exists(DisplayServerEnums::WindowID p_window_id);
|
||||
|
||||
void window_set_parent(DisplayServerEnums::WindowID p_window_id, DisplayServerEnums::WindowID p_parent_id);
|
||||
|
||||
struct wl_surface *window_get_wl_surface(DisplayServerEnums::WindowID p_window_id) const;
|
||||
|
||||
Reference in New Issue
Block a user