diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c index 46c8877d8c..b69116a07e 100644 --- a/src/video/wayland/SDL_waylandevents.c +++ b/src/video/wayland/SDL_waylandevents.c @@ -373,8 +373,15 @@ void Wayland_DisplayInitPointerGestureManager(SDL_VideoData *display) static void Wayland_SeatCreateCursorShape(SDL_WaylandSeat *seat) { if (seat->display->cursor_shape_manager) { - if (seat->pointer.wl_pointer && !seat->pointer.cursor_shape) { - seat->pointer.cursor_shape = wp_cursor_shape_manager_v1_get_pointer(seat->display->cursor_shape_manager, seat->pointer.wl_pointer); + if (seat->pointer.wl_pointer && !seat->pointer.cursor_state.cursor_shape) { + seat->pointer.cursor_state.cursor_shape = wp_cursor_shape_manager_v1_get_pointer(seat->display->cursor_shape_manager, seat->pointer.wl_pointer); + } + + SDL_WaylandPenTool *tool; + wl_list_for_each(tool, &seat->tablet.tool_list, link) { + if (!tool->cursor_state.cursor_shape) { + tool->cursor_state.cursor_shape = wp_cursor_shape_manager_v1_get_tablet_tool_v2(seat->display->cursor_shape_manager, tool->wltool); + } } } } @@ -770,7 +777,7 @@ static void pointer_dispatch_absolute_motion(SDL_WaylandSeat *seat) if (rc != window_data->hit_test_result) { window_data->hit_test_result = rc; - Wayland_SeatUpdateCursor(seat); + Wayland_SeatUpdatePointerCursor(seat); } } } @@ -852,7 +859,7 @@ static void pointer_handle_enter(void *data, struct wl_pointer *pointer, * window that already has focus, as the focus change sequence * won't be run. */ - Wayland_SeatUpdateCursor(seat); + Wayland_SeatUpdatePointerCursor(seat); } } @@ -892,7 +899,7 @@ static void pointer_handle_leave(void *data, struct wl_pointer *pointer, } Wayland_SeatUpdatePointerGrab(seat); - Wayland_SeatUpdateCursor(seat); + Wayland_SeatUpdatePointerCursor(seat); } static bool Wayland_ProcessHitTest(SDL_WaylandSeat *seat, Uint32 serial) @@ -1269,7 +1276,7 @@ static void pointer_handle_frame(void *data, struct wl_pointer *pointer) * window that already has focus, as the focus change sequence * won't be run. */ - Wayland_SeatUpdateCursor(seat); + Wayland_SeatUpdatePointerCursor(seat); } } @@ -2354,7 +2361,7 @@ static const struct wl_keyboard_listener keyboard_listener = { static void Wayland_SeatDestroyPointer(SDL_WaylandSeat *seat, bool send_event) { - Wayland_SeatDestroyCursorFrameCallback(seat); + Wayland_CursorStateRelease(&seat->pointer.cursor_state); // End any active gestures. if (seat->pointer.gesture_focus) { @@ -2388,18 +2395,6 @@ static void Wayland_SeatDestroyPointer(SDL_WaylandSeat *seat, bool send_event) zwp_pointer_gesture_pinch_v1_destroy(seat->pointer.gesture_pinch); } - if (seat->pointer.cursor_state.surface) { - wl_surface_destroy(seat->pointer.cursor_state.surface); - } - - if (seat->pointer.cursor_state.viewport) { - wp_viewport_destroy(seat->pointer.cursor_state.viewport); - } - - if (seat->pointer.cursor_shape) { - wp_cursor_shape_device_v1_destroy(seat->pointer.cursor_shape); - } - if (seat->pointer.wl_pointer) { if (wl_pointer_get_version(seat->pointer.wl_pointer) >= WL_POINTER_RELEASE_SINCE_VERSION) { wl_pointer_release(seat->pointer.wl_pointer); @@ -3274,23 +3269,6 @@ void Wayland_DisplayCreateTextInputManager(SDL_VideoData *d, uint32_t id) } // Pen/Tablet support... - -typedef struct SDL_WaylandPenTool // a stylus, etc, on a tablet. -{ - SDL_PenID instance_id; - SDL_PenInfo info; - SDL_Window *tool_focus; - struct zwp_tablet_tool_v2 *wltool; - float x; - float y; - bool frame_motion_set; - float frame_axes[SDL_PEN_AXIS_COUNT]; - Uint32 frame_axes_set; - int frame_pen_down; - int frame_buttons[3]; - struct wl_list link; -} SDL_WaylandPenTool; - static void tablet_tool_handle_type(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t type) { SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data; @@ -3342,7 +3320,9 @@ static void tablet_tool_handle_removed(void *data, struct zwp_tablet_tool_v2 *to if (sdltool->instance_id) { SDL_RemovePenDevice(0, sdltool->instance_id); } - zwp_tablet_tool_v2_destroy(tool); + + Wayland_CursorStateRelease(&sdltool->cursor_state); + zwp_tablet_tool_v2_destroy(sdltool->wltool); WAYLAND_wl_list_remove(&sdltool->link); SDL_free(sdltool); } @@ -3351,79 +3331,70 @@ static void tablet_tool_handle_proximity_in(void *data, struct zwp_tablet_tool_v { SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data; SDL_WindowData *windowdata = surface ? Wayland_GetWindowDataForOwnedSurface(surface) : NULL; - sdltool->tool_focus = windowdata ? windowdata->sdlwindow : NULL; + sdltool->focus = windowdata; + sdltool->proximity_serial = serial; + sdltool->frame.have_proximity_in = true; - SDL_assert(sdltool->instance_id == 0); // shouldn't be added at this point. - if (sdltool->info.subtype != SDL_PEN_TYPE_UNKNOWN) { // don't tell SDL about it if we don't know its role. - sdltool->instance_id = SDL_AddPenDevice(0, NULL, &sdltool->info, sdltool); - } - - // According to the docs, this should be followed by a motion event, where we'll send our SDL events. + // According to the docs, this should be followed by a frame event, where we'll send our SDL events. } static void tablet_tool_handle_proximity_out(void *data, struct zwp_tablet_tool_v2 *tool) { SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data; - sdltool->tool_focus = NULL; - - if (sdltool->instance_id) { - SDL_RemovePenDevice(0, sdltool->instance_id); - sdltool->instance_id = 0; - } + sdltool->frame.have_proximity_out = true; } static void tablet_tool_handle_down(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t serial) { SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data; - sdltool->frame_pen_down = 1; + sdltool->frame.tool_state = WAYLAND_TABLET_TOOL_STATE_DOWN; } static void tablet_tool_handle_up(void *data, struct zwp_tablet_tool_v2 *tool) { SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data; - sdltool->frame_pen_down = 0; + sdltool->frame.tool_state = WAYLAND_TABLET_TOOL_STATE_UP; } static void tablet_tool_handle_motion(void *data, struct zwp_tablet_tool_v2 *tool, wl_fixed_t sx_w, wl_fixed_t sy_w) { SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data; - SDL_Window *window = sdltool->tool_focus; - if (window) { - const SDL_WindowData *windowdata = window->internal; - sdltool->x = (float)(wl_fixed_to_double(sx_w) * windowdata->pointer_scale.x); - sdltool->y = (float)(wl_fixed_to_double(sy_w) * windowdata->pointer_scale.y); - sdltool->frame_motion_set = true; + SDL_WindowData *windowdata = sdltool->focus; + if (windowdata) { + sdltool->frame.x = (float)(wl_fixed_to_double(sx_w) * windowdata->pointer_scale.x); + sdltool->frame.y = (float)(wl_fixed_to_double(sy_w) * windowdata->pointer_scale.y); + sdltool->frame.have_motion = true; } } static void tablet_tool_handle_pressure(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t pressure) { SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data; - sdltool->frame_axes[SDL_PEN_AXIS_PRESSURE] = ((float) pressure) / 65535.0f; - sdltool->frame_axes_set |= (1u << SDL_PEN_AXIS_PRESSURE); + sdltool->frame.axes[SDL_PEN_AXIS_PRESSURE] = ((float) pressure) / 65535.0f; + sdltool->frame.axes_set |= (1u << SDL_PEN_AXIS_PRESSURE); if (pressure) { - sdltool->frame_axes[SDL_PEN_AXIS_DISTANCE] = 0.0f; - sdltool->frame_axes_set |= (1u << SDL_PEN_AXIS_DISTANCE); + sdltool->frame.axes[SDL_PEN_AXIS_DISTANCE] = 0.0f; + sdltool->frame.axes_set |= (1u << SDL_PEN_AXIS_DISTANCE); } } static void tablet_tool_handle_distance(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t distance) { SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data; - sdltool->frame_axes[SDL_PEN_AXIS_DISTANCE] = ((float) distance) / 65535.0f; - sdltool->frame_axes_set |= (1u << SDL_PEN_AXIS_DISTANCE); + sdltool->frame.axes[SDL_PEN_AXIS_DISTANCE] = ((float) distance) / 65535.0f; + sdltool->frame.axes_set |= (1u << SDL_PEN_AXIS_DISTANCE); if (distance) { - sdltool->frame_axes[SDL_PEN_AXIS_PRESSURE] = 0.0f; - sdltool->frame_axes_set |= (1u << SDL_PEN_AXIS_PRESSURE); + sdltool->frame.axes[SDL_PEN_AXIS_PRESSURE] = 0.0f; + sdltool->frame.axes_set |= (1u << SDL_PEN_AXIS_PRESSURE); } } static void tablet_tool_handle_tilt(void *data, struct zwp_tablet_tool_v2 *tool, wl_fixed_t xtilt, wl_fixed_t ytilt) { SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data; - sdltool->frame_axes[SDL_PEN_AXIS_XTILT] = (float)(wl_fixed_to_double(xtilt)); - sdltool->frame_axes[SDL_PEN_AXIS_YTILT] = (float)(wl_fixed_to_double(ytilt)); - sdltool->frame_axes_set |= (1u << SDL_PEN_AXIS_XTILT) | (1u << SDL_PEN_AXIS_YTILT); + sdltool->frame.axes[SDL_PEN_AXIS_XTILT] = (float)(wl_fixed_to_double(xtilt)); + sdltool->frame.axes[SDL_PEN_AXIS_YTILT] = (float)(wl_fixed_to_double(ytilt)); + sdltool->frame.axes_set |= (1u << SDL_PEN_AXIS_XTILT) | (1u << SDL_PEN_AXIS_YTILT); } static void tablet_tool_handle_button(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t serial, uint32_t button, uint32_t state) @@ -3446,23 +3417,23 @@ static void tablet_tool_handle_button(void *data, struct zwp_tablet_tool_v2 *too return; // don't care about this button, I guess. } - SDL_assert((sdlbutton >= 1) && (sdlbutton <= SDL_arraysize(sdltool->frame_buttons))); - sdltool->frame_buttons[sdlbutton-1] = (state == ZWP_TABLET_PAD_V2_BUTTON_STATE_PRESSED) ? 1 : 0; + SDL_assert((sdlbutton >= 1) && (sdlbutton <= SDL_arraysize(sdltool->frame.buttons))); + sdltool->frame.buttons[sdlbutton-1] = (state == ZWP_TABLET_PAD_V2_BUTTON_STATE_PRESSED) ? 1 : 0; } static void tablet_tool_handle_rotation(void *data, struct zwp_tablet_tool_v2 *tool, wl_fixed_t degrees) { SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data; const float rotation = (float)(wl_fixed_to_double(degrees)); - sdltool->frame_axes[SDL_PEN_AXIS_ROTATION] = (rotation > 180.0f) ? (rotation - 360.0f) : rotation; // map to -180.0f ... 179.0f range - sdltool->frame_axes_set |= (1u << SDL_PEN_AXIS_ROTATION); + sdltool->frame.axes[SDL_PEN_AXIS_ROTATION] = (rotation > 180.0f) ? (rotation - 360.0f) : rotation; // map to -180.0f ... 179.0f range + sdltool->frame.axes_set |= (1u << SDL_PEN_AXIS_ROTATION); } static void tablet_tool_handle_slider(void *data, struct zwp_tablet_tool_v2 *tool, int32_t position) { SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data; - sdltool->frame_axes[SDL_PEN_AXIS_SLIDER] = position / 65535.f; - sdltool->frame_axes_set |= (1u << SDL_PEN_AXIS_SLIDER); + sdltool->frame.axes[SDL_PEN_AXIS_SLIDER] = position / 65535.f; + sdltool->frame.axes_set |= (1u << SDL_PEN_AXIS_SLIDER); } static void tablet_tool_handle_wheel(void *data, struct zwp_tablet_tool_v2 *tool, int32_t degrees, int32_t clicks) @@ -3474,51 +3445,66 @@ static void tablet_tool_handle_frame(void *data, struct zwp_tablet_tool_v2 *tool { SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data; - if (!sdltool->instance_id) { + const Uint64 timestamp = Wayland_AdjustEventTimestampBase(Wayland_EventTimestampMSToNS(time)); + SDL_Window *window = sdltool->focus ? sdltool->focus->sdlwindow : NULL; + + if (sdltool->frame.have_proximity_in) { + SDL_assert(sdltool->instance_id == 0); // shouldn't be added at this point. + if (sdltool->info.subtype != SDL_PEN_TYPE_UNKNOWN) { // don't tell SDL about it if we don't know its role. + sdltool->instance_id = SDL_AddPenDevice(timestamp, NULL, &sdltool->info, sdltool); + Wayland_TabletToolUpdateCursor(sdltool); + } + } + + const SDL_PenID instance_id = sdltool->instance_id; + + if (!instance_id) { return; // Not a pen we report on. } - const Uint64 timestamp = Wayland_AdjustEventTimestampBase(Wayland_EventTimestampMSToNS(time)); - const SDL_PenID instance_id = sdltool->instance_id; - SDL_Window *window = sdltool->tool_focus; + // !!! FIXME: Should hit testing be done if pens generate pointer motion? // I don't know if this is necessary (or makes sense), but send motion before pen downs, but after pen ups, so you don't get unexpected lines drawn. - if (sdltool->frame_motion_set && (sdltool->frame_pen_down != -1)) { - if (sdltool->frame_pen_down) { - SDL_SendPenMotion(timestamp, instance_id, window, sdltool->x, sdltool->y); + if (sdltool->frame.have_motion && sdltool->frame.tool_state) { + if (sdltool->frame.tool_state == WAYLAND_TABLET_TOOL_STATE_UP) { + SDL_SendPenMotion(timestamp, instance_id, window, sdltool->frame.x, sdltool->frame.y); SDL_SendPenTouch(timestamp, instance_id, window, false, true); // !!! FIXME: how do we know what tip is in use? } else { SDL_SendPenTouch(timestamp, instance_id, window, false, false); // !!! FIXME: how do we know what tip is in use? - SDL_SendPenMotion(timestamp, instance_id, window, sdltool->x, sdltool->y); + SDL_SendPenMotion(timestamp, instance_id, window, sdltool->frame.x, sdltool->frame.y); } } else { - if (sdltool->frame_pen_down != -1) { - SDL_SendPenTouch(timestamp, instance_id, window, false, (sdltool->frame_pen_down != 0)); // !!! FIXME: how do we know what tip is in use? + if (sdltool->frame.tool_state) { + SDL_SendPenTouch(timestamp, instance_id, window, false, sdltool->frame.tool_state == WAYLAND_TABLET_TOOL_STATE_DOWN); // !!! FIXME: how do we know what tip is in use? } - if (sdltool->frame_motion_set) { - SDL_SendPenMotion(timestamp, instance_id, window, sdltool->x, sdltool->y); + if (sdltool->frame.have_motion) { + SDL_SendPenMotion(timestamp, instance_id, window, sdltool->frame.x, sdltool->frame.y); } } for (SDL_PenAxis i = 0; i < SDL_PEN_AXIS_COUNT; i++) { - if (sdltool->frame_axes_set & (1u << i)) { - SDL_SendPenAxis(timestamp, instance_id, window, i, sdltool->frame_axes[i]); + if (sdltool->frame.axes_set & (1u << i)) { + SDL_SendPenAxis(timestamp, instance_id, window, i, sdltool->frame.axes[i]); } } - for (int i = 0; i < SDL_arraysize(sdltool->frame_buttons); i++) { - const int state = sdltool->frame_buttons[i]; - if (state != -1) { - SDL_SendPenButton(timestamp, instance_id, window, (Uint8)(i + 1), (state != 0)); - sdltool->frame_buttons[i] = -1; + for (int i = 0; i < SDL_arraysize(sdltool->frame.buttons); i++) { + const int state = sdltool->frame.buttons[i]; + if (state) { + SDL_SendPenButton(timestamp, instance_id, window, (Uint8)(i + 1), state == WAYLAND_TABLET_TOOL_BUTTON_DOWN); } } - // reset for next frame. - sdltool->frame_pen_down = -1; - sdltool->frame_motion_set = false; - sdltool->frame_axes_set = 0; + if (sdltool->frame.have_proximity_out) { + sdltool->focus = NULL; + Wayland_TabletToolUpdateCursor(sdltool); + SDL_RemovePenDevice(timestamp, sdltool->instance_id); + sdltool->instance_id = 0; + } + + // Reset for the next frame. + SDL_zero(sdltool->frame); } static const struct zwp_tablet_tool_v2_listener tablet_tool_listener = { @@ -3558,10 +3544,11 @@ static void tablet_seat_handle_tool_added(void *data, struct zwp_tablet_seat_v2 sdltool->wltool = tool; sdltool->info.max_tilt = -1.0f; sdltool->info.num_buttons = -1; - sdltool->frame_pen_down = -1; - for (int i = 0; i < SDL_arraysize(sdltool->frame_buttons); i++) { - sdltool->frame_buttons[i] = -1; + + if (seat->display->cursor_shape_manager) { + sdltool->cursor_state.cursor_shape = wp_cursor_shape_manager_v1_get_tablet_tool_v2(seat->display->cursor_shape_manager, tool); } + WAYLAND_wl_list_insert(&seat->tablet.tool_list, &sdltool->link); // this will send a bunch of zwp_tablet_tool_v2 events right up front to tell @@ -3598,6 +3585,8 @@ void Wayland_DisplayInitTabletManager(SDL_VideoData *display) static void Wayland_remove_all_pens_callback(SDL_PenID instance_id, void *handle, void *userdata) { SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) handle; + + Wayland_CursorStateRelease(&sdltool->cursor_state); zwp_tablet_tool_v2_destroy(sdltool->wltool); SDL_free(sdltool); } @@ -3605,10 +3594,10 @@ static void Wayland_remove_all_pens_callback(SDL_PenID instance_id, void *handle static void Wayland_SeatDestroyTablet(SDL_WaylandSeat *seat, bool send_events) { if (send_events) { - SDL_WaylandPenTool *pen, *temp; - wl_list_for_each_safe (pen, temp, &seat->tablet.tool_list, link) { + SDL_WaylandPenTool *tool, *temp; + wl_list_for_each_safe (tool, temp, &seat->tablet.tool_list, link) { // Remove all tools for this seat, sending PROXIMITY_OUT events. - tablet_tool_handle_removed(pen, pen->wltool); + tablet_tool_handle_removed(tool, tool->wltool); } } else { // Shutting down, just delete everything. @@ -3675,8 +3664,13 @@ void Wayland_DisplayRemoveWindowReferencesFromSeats(SDL_VideoData *display, SDL_ SDL_WaylandPenTool *tool; wl_list_for_each (tool, &seat->tablet.tool_list, link) { - if (tool->tool_focus == window->sdlwindow) { - tablet_tool_handle_proximity_out(tool, tool->wltool); + if (tool->focus == window) { + tool->focus = NULL; + Wayland_TabletToolUpdateCursor(tool); + if (tool->instance_id) { + SDL_RemovePenDevice(0, tool->instance_id); + tool->instance_id = 0; + } } } } @@ -3811,7 +3805,7 @@ void Wayland_SeatUpdatePointerGrab(SDL_WaylandSeat *seat) seat->pointer.locked_pointer = NULL; // Update the cursor after destroying a relative move lock. - Wayland_SeatUpdateCursor(seat); + Wayland_SeatUpdatePointerCursor(seat); } if (seat->pointer.wl_pointer) { @@ -3831,7 +3825,7 @@ void Wayland_SeatUpdatePointerGrab(SDL_WaylandSeat *seat) zwp_locked_pointer_v1_add_listener(seat->pointer.locked_pointer, &locked_pointer_listener, seat); // Ensure that the relative pointer is hidden, if required. - Wayland_SeatUpdateCursor(seat); + Wayland_SeatUpdatePointerCursor(seat); } // Locked the cursor for relative mode, nothing more to do. diff --git a/src/video/wayland/SDL_waylandevents_c.h b/src/video/wayland/SDL_waylandevents_c.h index a178d129f4..57d58368f2 100644 --- a/src/video/wayland/SDL_waylandevents_c.h +++ b/src/video/wayland/SDL_waylandevents_c.h @@ -56,6 +56,66 @@ typedef struct char text[8]; } SDL_WaylandKeyboardRepeat; +typedef struct SDL_WaylandCursorState +{ + SDL_CursorData *current_cursor; + struct wp_cursor_shape_device_v1 *cursor_shape; + struct wl_surface *surface; + struct wp_viewport *viewport; + + double scale; + + // Pointer to the internal data for system cursors. + void *system_cursor_handle; + + // The cursor animation thread lock must be held when modifying this. + struct wl_callback *frame_callback; + + Uint64 last_frame_callback_time_ms; + Uint32 current_frame_time_ms; + int current_frame; + SDL_HitTestResult hit_test_result; +} SDL_WaylandCursorState; + +typedef struct SDL_WaylandPenTool // a stylus, etc, on a tablet. +{ + SDL_PenID instance_id; + SDL_PenInfo info; + SDL_WindowData *focus; + struct zwp_tablet_tool_v2 *wltool; + Uint32 proximity_serial; + + struct + { + float x; + float y; + + float axes[SDL_PEN_AXIS_COUNT]; + Uint32 axes_set; + + enum + { + WAYLAND_TABLET_TOOL_BUTTON_NONE = 0, + WAYLAND_TABLET_TOOL_BUTTON_DOWN, + WAYLAND_TABLET_TOOL_BUTTON_UP + } buttons[3]; + + enum + { + WAYLAND_TABLET_TOOL_STATE_NONE = 0, + WAYLAND_TABLET_TOOL_STATE_DOWN, + WAYLAND_TABLET_TOOL_STATE_UP + } tool_state; + + bool have_motion; + bool have_proximity_in; + bool have_proximity_out; + } frame; + + SDL_WaylandCursorState cursor_state; + struct wl_list link; +} SDL_WaylandPenTool; + typedef struct SDL_WaylandSeat { SDL_VideoData *display; @@ -120,7 +180,6 @@ typedef struct SDL_WaylandSeat struct wl_pointer *wl_pointer; struct zwp_relative_pointer_v1 *relative_pointer; struct zwp_input_timestamps_v1 *timestamps; - struct wp_cursor_shape_device_v1 *cursor_shape; struct zwp_locked_pointer_v1 *locked_pointer; struct zwp_confined_pointer_v1 *confined_pointer; struct zwp_pointer_gesture_pinch_v1 *gesture_pinch; @@ -176,22 +235,7 @@ typedef struct SDL_WaylandSeat Uint64 timestamp_ns; } pending_frame; - // Cursor state - struct - { - struct wl_surface *surface; - struct wp_viewport *viewport; - - // Animation state for cursors - void *cursor_handle; - - // The cursor animation thread lock must be held when modifying this. - struct wl_callback *frame_callback; - - Uint64 last_frame_callback_time_ms; - Uint32 current_frame_time_ms; - int current_frame; - } cursor_state; + SDL_WaylandCursorState cursor_state; } pointer; struct diff --git a/src/video/wayland/SDL_waylandmouse.c b/src/video/wayland/SDL_waylandmouse.c index bae09f9ff6..348e92295c 100644 --- a/src/video/wayland/SDL_waylandmouse.c +++ b/src/video/wayland/SDL_waylandmouse.c @@ -42,6 +42,7 @@ #include "pointer-constraints-unstable-v1-client-protocol.h" #include "viewporter-client-protocol.h" #include "pointer-warp-v1-client-protocol.h" +#include "tablet-v2-client-protocol.h" #include "../../SDL_hints_c.h" @@ -296,48 +297,40 @@ static void Wayland_DBusFinishCursorProperties(void) #endif -static CustomCursorImage *Wayland_GetScaledCustomCursorImage(SDL_CursorData *data, int frame_index, double scale) +static struct wl_buffer *Wayland_CursorStateGetFrame(SDL_WaylandCursorState *state, int frame_index) { - const int offset = data->cursor_data.custom.images_per_frame * frame_index; - - /* Find the closest image. Images that are larger than the - * desired size are preferred over images that are smaller. - */ - CustomCursorImage *closest = NULL; - int desired_w = (int)SDL_round(data->cursor_data.custom.width * scale); - int desired_h = (int)SDL_round(data->cursor_data.custom.height * scale); - int desired_size = desired_w * desired_h; - int closest_distance = -1; - int closest_size = -1; - for (int i = 0; i < data->cursor_data.custom.images_per_frame && closest_distance && data->cursor_data.custom.images[offset + i].buffer; ++i) { - CustomCursorImage *candidate = &data->cursor_data.custom.images[offset + i]; - int size = candidate->width * candidate->height; - int delta_w = candidate->width - desired_w; - int delta_h = candidate->height - desired_h; - int distance = (delta_w * delta_w) + (delta_h * delta_h); - if (closest_distance < 0 || distance < closest_distance || - (size > desired_size && closest_size < desired_size)) { - closest = candidate; - closest_distance = distance; - closest_size = size; - } - } - - return closest; -} - -static struct wl_buffer *Wayland_SeatGetCursorFrame(SDL_WaylandSeat *seat, int frame_index) -{ - SDL_CursorData *data = seat->pointer.current_cursor; + SDL_CursorData *data = state->current_cursor; if (data) { if (!data->is_system_cursor) { - const double scale = seat->pointer.focus ? seat->pointer.focus->scale_factor : 1.0; - CustomCursorImage *image = Wayland_GetScaledCustomCursorImage(data, frame_index, scale); + const int offset = data->cursor_data.custom.images_per_frame * frame_index; - return image ? image->buffer : NULL; + /* Find the closest image. Images that are larger than the + * desired size are preferred over images that are smaller. + */ + CustomCursorImage *closest = NULL; + int desired_w = (int)SDL_round(data->cursor_data.custom.width * state->scale); + int desired_h = (int)SDL_round(data->cursor_data.custom.height * state->scale); + int desired_size = desired_w * desired_h; + int closest_distance = -1; + int closest_size = -1; + for (int i = 0; i < data->cursor_data.custom.images_per_frame && closest_distance && data->cursor_data.custom.images[offset + i].buffer; ++i) { + CustomCursorImage *candidate = &data->cursor_data.custom.images[offset + i]; + int size = candidate->width * candidate->height; + int delta_w = candidate->width - desired_w; + int delta_h = candidate->height - desired_h; + int distance = (delta_w * delta_w) + (delta_h * delta_h); + if (closest_distance < 0 || distance < closest_distance || + (size > desired_size && closest_size < desired_size)) { + closest = candidate; + closest_distance = distance; + closest_size = size; + } + } + + return closest ? closest->buffer : NULL; } else { - return ((Wayland_CachedSystemCursor *)(seat->pointer.cursor_state.cursor_handle))->buffers[frame_index]; + return ((Wayland_CachedSystemCursor *)(state->system_cursor_handle))->buffers[frame_index]; } } @@ -509,23 +502,23 @@ static const struct wl_callback_listener cursor_frame_listener = { static void cursor_frame_done(void *data, struct wl_callback *cb, uint32_t time) { - SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data; - if (!seat->pointer.current_cursor) { + SDL_WaylandCursorState *state = (SDL_WaylandCursorState *)data; + if (!state->current_cursor) { return; } - Uint32 *frames = seat->pointer.current_cursor->frame_durations_ms; - SDL_CursorData *c = seat->pointer.current_cursor; + Uint32 *frames = state->current_cursor->frame_durations_ms; + SDL_CursorData *c = state->current_cursor; const Uint64 now = SDL_GetTicks(); - const Uint32 elapsed = (now - seat->pointer.cursor_state.last_frame_callback_time_ms) % c->total_duration_ms; + const Uint32 elapsed = (now - state->last_frame_callback_time_ms) % c->total_duration_ms; Uint32 advance = 0; - int next = seat->pointer.cursor_state.current_frame; + int next = state->current_frame; - seat->pointer.cursor_state.current_frame_time_ms += elapsed; + state->current_frame_time_ms += elapsed; // Calculate the next frame based on the elapsed duration. - for (Uint32 t = frames[next]; t <= seat->pointer.cursor_state.current_frame_time_ms; t += frames[next]) { + for (Uint32 t = frames[next]; t <= state->current_frame_time_ms; t += frames[next]) { next = (next + 1) % c->num_frames; advance = t; @@ -536,52 +529,52 @@ static void cursor_frame_done(void *data, struct wl_callback *cb, uint32_t time) } wl_callback_destroy(cb); - seat->pointer.cursor_state.frame_callback = NULL; + state->frame_callback = NULL; // Don't queue another callback if this frame time is infinite. if (frames[next]) { - seat->pointer.cursor_state.frame_callback = wl_surface_frame(seat->pointer.cursor_state.surface); - wl_callback_add_listener(seat->pointer.cursor_state.frame_callback, &cursor_frame_listener, data); + state->frame_callback = wl_surface_frame(state->surface); + wl_callback_add_listener(state->frame_callback, &cursor_frame_listener, data); } - seat->pointer.cursor_state.current_frame_time_ms -= advance; - seat->pointer.cursor_state.last_frame_callback_time_ms = now; - seat->pointer.cursor_state.current_frame = next; + state->current_frame_time_ms -= advance; + state->last_frame_callback_time_ms = now; + state->current_frame = next; - struct wl_buffer *buffer = Wayland_SeatGetCursorFrame(seat, next); - wl_surface_attach(seat->pointer.cursor_state.surface, buffer, 0, 0); + struct wl_buffer *buffer = Wayland_CursorStateGetFrame(state, next); + wl_surface_attach(state->surface, buffer, 0, 0); - if (wl_surface_get_version(seat->pointer.cursor_state.surface) >= WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION) { - wl_surface_damage_buffer(seat->pointer.cursor_state.surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32); + if (wl_surface_get_version(state->surface) >= WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION) { + wl_surface_damage_buffer(state->surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32); } else { - wl_surface_damage(seat->pointer.cursor_state.surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32); + wl_surface_damage(state->surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32); } - wl_surface_commit(seat->pointer.cursor_state.surface); + wl_surface_commit(state->surface); } -void Wayland_SeatSetCursorFrameCallback(SDL_WaylandSeat *seat) +void Wayland_CursorStateSetFrameCallback(SDL_WaylandCursorState *state, void *userdata) { if (cursor_thread_context.lock) { SDL_LockMutex(cursor_thread_context.lock); } - seat->pointer.cursor_state.frame_callback = wl_surface_frame(seat->pointer.cursor_state.surface); - wl_callback_add_listener(seat->pointer.cursor_state.frame_callback, &cursor_frame_listener, seat); + state->frame_callback = wl_surface_frame(state->surface); + wl_callback_add_listener(state->frame_callback, &cursor_frame_listener, userdata); if (cursor_thread_context.lock) { SDL_UnlockMutex(cursor_thread_context.lock); } } -void Wayland_SeatDestroyCursorFrameCallback(SDL_WaylandSeat *seat) +void Wayland_CursorStateDestroyFrameCallback(SDL_WaylandCursorState *state) { if (cursor_thread_context.lock) { SDL_LockMutex(cursor_thread_context.lock); } - if (seat->pointer.cursor_state.frame_callback) { - wl_callback_destroy(seat->pointer.cursor_state.frame_callback); - seat->pointer.cursor_state.frame_callback = NULL; + if (state->frame_callback) { + wl_callback_destroy(state->frame_callback); + state->frame_callback = NULL; } if (cursor_thread_context.lock) { @@ -589,21 +582,39 @@ void Wayland_SeatDestroyCursorFrameCallback(SDL_WaylandSeat *seat) } } -static void Wayland_SeatResetCursorAnimation(SDL_WaylandSeat *seat, bool lock) +static void Wayland_CursorStateResetAnimation(SDL_WaylandCursorState *state, bool lock) { if (lock && cursor_thread_context.lock) { SDL_LockMutex(cursor_thread_context.lock); } - seat->pointer.cursor_state.last_frame_callback_time_ms = SDL_GetTicks(); - seat->pointer.cursor_state.current_frame_time_ms = 0; - seat->pointer.cursor_state.current_frame = 0; + state->last_frame_callback_time_ms = SDL_GetTicks(); + state->current_frame_time_ms = 0; + state->current_frame = 0; if (lock && cursor_thread_context.lock) { SDL_UnlockMutex(cursor_thread_context.lock); } } +void Wayland_CursorStateRelease(SDL_WaylandCursorState *state) +{ + Wayland_CursorStateDestroyFrameCallback(state); + if (state->cursor_shape) { + wp_cursor_shape_device_v1_destroy(state->cursor_shape); + } + if (state->viewport) { + wp_viewport_destroy(state->viewport); + } + if (state->surface) { + wl_surface_attach(state->surface, NULL, 0, 0); + wl_surface_commit(state->surface); + wl_surface_destroy(state->surface); + } + + SDL_zerop(state); +} + static Wayland_CachedSystemCursor *Wayland_CacheSystemCursor(SDL_CursorData *cdata, struct wl_cursor *cursor, int size) { Wayland_CachedSystemCursor *cache = NULL; @@ -633,13 +644,13 @@ static Wayland_CachedSystemCursor *Wayland_CacheSystemCursor(SDL_CursorData *cda return cache; } -static bool Wayland_GetSystemCursor(SDL_CursorData *cdata, SDL_WaylandSeat *seat, int *scale, int *dst_size, int *hot_x, int *hot_y) +static bool Wayland_GetSystemCursor(SDL_CursorData *cdata, SDL_WaylandCursorState *state, int *dst_size, int *hot_x, int *hot_y) { - SDL_VideoData *vdata = seat->display; + SDL_VideoData *vdata = SDL_GetVideoDevice()->internal; struct wl_cursor_theme *theme = NULL; const char *css_name = "default"; const char *fallback_name = NULL; - double scale_factor = 1.0; + double scale_factor = state->scale; int theme_size = dbus_cursor_size; // Fallback envvar if the DBus properties don't exist @@ -652,13 +663,8 @@ static bool Wayland_GetSystemCursor(SDL_CursorData *cdata, SDL_WaylandSeat *seat if (theme_size <= 0) { theme_size = 24; } - // First, find the appropriate theme based on the current scale... - SDL_Window *focus = SDL_GetMouse()->focus; - if (focus) { - // TODO: Use the fractional scale once GNOME supports viewports on cursor surfaces. - scale_factor = SDL_ceil(focus->internal->scale_factor); - } + // First, find the appropriate theme based on the current scale... const int scaled_size = (int)SDL_lround(theme_size * scale_factor); for (int i = 0; i < vdata->num_cursor_themes; ++i) { if (vdata->cursor_themes[i].size == scaled_size) { @@ -707,7 +713,7 @@ static bool Wayland_GetSystemCursor(SDL_CursorData *cdata, SDL_WaylandSeat *seat // ... Set the cursor data, finally. cdata->num_frames = cursor->image_count; Wayland_CachedSystemCursor *c = Wayland_CacheSystemCursor(cdata, cursor, theme_size); - seat->pointer.cursor_state.cursor_handle = c; + state->system_cursor_handle = c; if (cursor->image_count > 1 && !cdata->frame_durations_ms) { cdata->total_duration_ms = 0; @@ -719,32 +725,9 @@ static bool Wayland_GetSystemCursor(SDL_CursorData *cdata, SDL_WaylandSeat *seat } } - *scale = SDL_ceil(scale_factor) == scale_factor ? (int)scale_factor : 0; - - if (scaled_size != cursor->images[0]->width) { - /* If the cursor size isn't an exact match for the target size, use a viewport - * to avoid a possible "Buffer size is not divisible by scale" protocol error. - * - * If viewports are unavailable, find an integer scale that works. - */ - if (vdata->viewporter) { - // A scale of 0 indicates that a viewport set to the destination size should be used. - *scale = 0; - } else { - for (; *scale > 1; --*scale) { - if (cursor->images[0]->width % *scale == 0) { - break; - } - } - // Set the scale factor to the new value for the hotspot calculations. - scale_factor = *scale; - } - } - - *dst_size = (int)SDL_lround(cursor->images[0]->width / scale_factor); - - *hot_x = (int)SDL_lround(cursor->images[0]->hotspot_x / scale_factor); - *hot_y = (int)SDL_lround(cursor->images[0]->hotspot_y / scale_factor); + *dst_size = SDL_lround(cursor->images[0]->width / state->scale); + *hot_x = SDL_lround(cursor->images[0]->hotspot_x / state->scale); + *hot_y = SDL_lround(cursor->images[0]->hotspot_y / state->scale); return true; } @@ -852,9 +835,8 @@ static SDL_Cursor *Wayland_CreateAnimatedCursor(SDL_CursorFrameInfo *frames, int return cursor; failed: - Wayland_ReleaseSHMPool(data->cursor_data.custom.shmPool); - if (data) { + Wayland_ReleaseSHMPool(data->cursor_data.custom.shmPool); SDL_free(data->frame_durations_ms); for (int i = 0; i < data->cursor_data.custom.images_per_frame * frame_count; ++i) { if (data->cursor_data.custom.images[i].buffer) { @@ -922,7 +904,7 @@ static void Wayland_FreeCursorData(SDL_CursorData *d) wl_list_for_each (seat, &video_data->seat_list, link) { if (seat->pointer.current_cursor == d) { - Wayland_SeatDestroyCursorFrameCallback(seat); + Wayland_CursorStateDestroyFrameCallback(&seat->pointer.cursor_state); if (seat->pointer.cursor_state.surface) { wl_surface_attach(seat->pointer.cursor_state.surface, NULL, 0, 0); @@ -967,7 +949,7 @@ static void Wayland_FreeCursor(SDL_Cursor *cursor) SDL_free(cursor); } -static void Wayland_SetSystemCursorShape(SDL_WaylandSeat *seat, SDL_SystemCursor id) +static enum wp_cursor_shape_device_v1_shape Wayland_GetSystemCursorShape(SDL_SystemCursor id) { Uint32 shape; @@ -1037,126 +1019,147 @@ static void Wayland_SetSystemCursorShape(SDL_WaylandSeat *seat, SDL_SystemCursor shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT; } - wp_cursor_shape_device_v1_set_shape(seat->pointer.cursor_shape, seat->pointer.enter_serial, shape); + return shape; } -static void Wayland_SeatSetCursor(SDL_WaylandSeat *seat, SDL_Cursor *cursor) +typedef struct Wayland_PointerObject { - if (seat->pointer.wl_pointer) { - SDL_CursorData *cursor_data = cursor ? cursor->internal : NULL; - int scale = 0; - int dst_width = 0; - int dst_height = 0; - int hot_x; - int hot_y; + union + { + struct wl_pointer *wl_pointer; + struct zwp_tablet_tool_v2 *wl_tool; + }; - // Stop the frame callback for old animated cursors. - if (cursor_data != seat->pointer.current_cursor) { - Wayland_SeatDestroyCursorFrameCallback(seat); + bool is_pointer; +} Wayland_PointerObject; + +static void Wayland_CursorStateSetCursor(SDL_WaylandCursorState *state, const Wayland_PointerObject *obj, SDL_WindowData *focus, Uint32 serial, SDL_Cursor *cursor) +{ + SDL_VideoData *viddata = SDL_GetVideoDevice()->internal; + SDL_CursorData *cursor_data = cursor ? cursor->internal : NULL; + int dst_width = 0; + int dst_height = 0; + int hot_x; + int hot_y; + + // Stop the frame callback for old animated cursors. + if (cursor_data != state->current_cursor) { + Wayland_CursorStateDestroyFrameCallback(state); + } + + if (cursor) { + if (cursor_data == state->current_cursor) { + // Restart the animation sequence if the cursor didn't change. + if (cursor_data->num_frames > 1) { + Wayland_CursorStateResetAnimation(state, true); + } + + return; } - if (cursor) { - if (cursor_data == seat->pointer.current_cursor) { - // Restart the animation sequence if the cursor didn't change. - if (cursor_data->num_frames > 1) { - Wayland_SeatResetCursorAnimation(seat, true); + if (cursor_data->is_system_cursor) { + // If the cursor shape protocol is supported, the compositor will draw nicely scaled cursors for us, so nothing more to do. + if (state->cursor_shape) { + // Don't need the surface or viewport if using the cursor shape protocol. + if (state->surface) { + wl_surface_attach(state->surface, NULL, 0, 0); + wl_surface_commit(state->surface); + + if (obj->is_pointer) { + wl_pointer_set_cursor(obj->wl_pointer, serial, NULL, 0, 0); + } else { + zwp_tablet_tool_v2_set_cursor(obj->wl_tool, serial, NULL, 0, 0); + } + + if (state->viewport) { + wp_viewport_destroy(state->viewport); + state->viewport = NULL; + } + + wl_surface_destroy(state->surface); + state->surface = NULL; } + const enum wp_cursor_shape_device_v1_shape shape = Wayland_GetSystemCursorShape(cursor_data->cursor_data.system.id); + wp_cursor_shape_device_v1_set_shape(state->cursor_shape, serial, shape); + state->current_cursor = cursor_data; + return; } - if (cursor_data->is_system_cursor) { - // If the cursor shape protocol is supported, the compositor will draw nicely scaled cursors for us, so nothing more to do. - if (seat->pointer.cursor_shape) { - // Don't need the surface or viewport if using the cursor shape protocol. - if (seat->pointer.cursor_state.surface) { - wl_pointer_set_cursor(seat->pointer.wl_pointer, seat->pointer.enter_serial, NULL, 0, 0); - wl_surface_destroy(seat->pointer.cursor_state.surface); - seat->pointer.cursor_state.surface = NULL; - } - if (seat->pointer.cursor_state.viewport) { - wp_viewport_destroy(seat->pointer.cursor_state.viewport); - seat->pointer.cursor_state.viewport = NULL; - } - - Wayland_SetSystemCursorShape(seat, cursor_data->cursor_data.system.id); - seat->pointer.current_cursor = cursor_data; - - return; - } - - if (!Wayland_GetSystemCursor(cursor_data, seat, &scale, &dst_width, &hot_x, &hot_y)) { - return; - } - - dst_height = dst_width; - } else { - dst_width = cursor_data->cursor_data.custom.width; - dst_height = cursor_data->cursor_data.custom.height; - hot_x = cursor_data->cursor_data.custom.hot_x; - hot_y = cursor_data->cursor_data.custom.hot_y; - - // If viewports aren't available, figure out the integer scale. - if (!seat->display->viewporter) { - scale = 1; - - double image_scale = seat->pointer.focus ? seat->pointer.focus->scale_factor : 1.0; - CustomCursorImage *image = Wayland_GetScaledCustomCursorImage(cursor_data, 0, image_scale); - if (image) { - image_scale = (double)image->width / (double)cursor_data->cursor_data.custom.width; - scale= SDL_lround(image_scale); - } - } + // If viewports aren't available, the scale is always 1.0. + state->scale = viddata->viewporter && focus ? focus->scale_factor : 1.0; + if (!Wayland_GetSystemCursor(cursor_data, state, &dst_width, &hot_x, &hot_y)) { + return; } - seat->pointer.current_cursor = cursor_data; - - if (!seat->pointer.cursor_state.surface) { - if (cursor_thread_context.compositor_wrapper) { - seat->pointer.cursor_state.surface = wl_compositor_create_surface((struct wl_compositor *)cursor_thread_context.compositor_wrapper); - } else { - seat->pointer.cursor_state.surface = wl_compositor_create_surface(seat->display->compositor); - } - } - - struct wl_buffer *buffer = Wayland_SeatGetCursorFrame(seat, 0); - wl_surface_attach(seat->pointer.cursor_state.surface, buffer, 0, 0); - - // A scale value of 0 indicates that a viewport with the returned destination size should be used. - if (!scale) { - if (!seat->pointer.cursor_state.viewport) { - seat->pointer.cursor_state.viewport = wp_viewporter_get_viewport(seat->display->viewporter, seat->pointer.cursor_state.surface); - } - wl_surface_set_buffer_scale(seat->pointer.cursor_state.surface, 1); - wp_viewport_set_source(seat->pointer.cursor_state.viewport, wl_fixed_from_int(-1), wl_fixed_from_int(-1), wl_fixed_from_int(-1), wl_fixed_from_int(-1)); - wp_viewport_set_destination(seat->pointer.cursor_state.viewport, dst_width, dst_height); - } else { - if (seat->pointer.cursor_state.viewport) { - wp_viewport_destroy(seat->pointer.cursor_state.viewport); - seat->pointer.cursor_state.viewport = NULL; - } - wl_surface_set_buffer_scale(seat->pointer.cursor_state.surface, scale); - } - - wl_pointer_set_cursor(seat->pointer.wl_pointer, seat->pointer.enter_serial, seat->pointer.cursor_state.surface, hot_x, hot_y); - - if (wl_surface_get_version(seat->pointer.cursor_state.surface) >= WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION) { - wl_surface_damage_buffer(seat->pointer.cursor_state.surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32); - } else { - wl_surface_damage(seat->pointer.cursor_state.surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32); - } - - // If more than one frame is available, create a frame callback to run the animation. - if (cursor_data->num_frames > 1) { - Wayland_SeatResetCursorAnimation(seat, false); - Wayland_SeatSetCursorFrameCallback(seat); - } - - wl_surface_commit(seat->pointer.cursor_state.surface); + dst_height = dst_width; } else { - Wayland_SeatDestroyCursorFrameCallback(seat); - seat->pointer.current_cursor = NULL; - wl_pointer_set_cursor(seat->pointer.wl_pointer, seat->pointer.enter_serial, NULL, 0, 0); + // If viewports aren't available, the scale is always 1.0. + state->scale = viddata->viewporter && focus ? focus->scale_factor : 1.0; + dst_width = cursor_data->cursor_data.custom.width; + dst_height = cursor_data->cursor_data.custom.height; + hot_x = cursor_data->cursor_data.custom.hot_x; + hot_y = cursor_data->cursor_data.custom.hot_y; + } + + state->current_cursor = cursor_data; + + if (!state->surface) { + if (cursor_thread_context.compositor_wrapper) { + state->surface = wl_compositor_create_surface((struct wl_compositor *)cursor_thread_context.compositor_wrapper); + } else { + state->surface = wl_compositor_create_surface(viddata->compositor); + } + } + + struct wl_buffer *buffer = Wayland_CursorStateGetFrame(state, 0); + wl_surface_attach(state->surface, buffer, 0, 0); + + if (state->scale != 1.0) { + if (!state->viewport) { + state->viewport = wp_viewporter_get_viewport(viddata->viewporter, state->surface); + } + + wp_viewport_set_source(state->viewport, wl_fixed_from_int(-1), wl_fixed_from_int(-1), wl_fixed_from_int(-1), wl_fixed_from_int(-1)); + wp_viewport_set_destination(state->viewport, dst_width, dst_height); + } else if (state->viewport) { + wp_viewport_destroy(state->viewport); + state->viewport = NULL; + } + + if (obj->is_pointer) { + wl_pointer_set_cursor(obj->wl_pointer, serial, state->surface, hot_x, hot_y); + } else { + zwp_tablet_tool_v2_set_cursor(obj->wl_tool, serial, state->surface, hot_x, hot_y); + } + + if (wl_surface_get_version(state->surface) >= WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION) { + wl_surface_damage_buffer(state->surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32); + } else { + wl_surface_damage(state->surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32); + } + + // If more than one frame is available, create a frame callback to run the animation. + if (cursor_data->num_frames > 1) { + Wayland_CursorStateResetAnimation(state, false); + Wayland_CursorStateSetFrameCallback(state, state); + } + + wl_surface_commit(state->surface); + } else { + Wayland_CursorStateDestroyFrameCallback(state); + state->current_cursor = NULL; + + if (state->surface) { + wl_surface_attach(state->surface, NULL, 0, 0); + wl_surface_commit(state->surface); + } + + if (obj->is_pointer) { + wl_pointer_set_cursor(obj->wl_pointer, serial, NULL, 0, 0); + } else { + zwp_tablet_tool_v2_set_cursor(obj->wl_tool, serial, NULL, 0, 0); } } } @@ -1167,12 +1170,30 @@ static bool Wayland_ShowCursor(SDL_Cursor *cursor) SDL_VideoData *d = vd->internal; SDL_Mouse *mouse = SDL_GetMouse(); SDL_WaylandSeat *seat; + Wayland_PointerObject obj; wl_list_for_each (seat, &d->seat_list, link) { + obj.wl_pointer = seat->pointer.wl_pointer; + obj.is_pointer = true; if (mouse->focus && mouse->focus->internal == seat->pointer.focus) { - Wayland_SeatSetCursor(seat, cursor); + Wayland_CursorStateSetCursor(&seat->pointer.cursor_state, &obj, seat->pointer.focus, seat->pointer.enter_serial, cursor); } else if (!seat->pointer.focus) { - Wayland_SeatSetCursor(seat, NULL); + Wayland_CursorStateSetCursor(&seat->pointer.cursor_state, &obj, seat->pointer.focus, seat->pointer.enter_serial, NULL); + } + + SDL_WaylandPenTool *tool; + wl_list_for_each(tool, &seat->tablet.tool_list, link) { + obj.wl_tool = tool->wltool; + obj.is_pointer = false; + + /* The current cursor is explicitly set on tablet tools, as there may be no pointer device, or + * the pointer may not have focus, which would instead cause the default cursor to be set. + */ + if (tool->focus && (!mouse->focus || mouse->focus->internal == tool->focus)) { + Wayland_CursorStateSetCursor(&tool->cursor_state, &obj, tool->focus, tool->proximity_serial, mouse->cur_cursor); + } else if (!tool->focus) { + Wayland_CursorStateSetCursor(&tool->cursor_state, &obj, tool->focus, tool->proximity_serial, NULL); + } } } @@ -1484,29 +1505,57 @@ void Wayland_FiniMouse(SDL_VideoData *data) } } -void Wayland_SeatUpdateCursor(SDL_WaylandSeat *seat) +void Wayland_SeatUpdatePointerCursor(SDL_WaylandSeat *seat) { SDL_Mouse *mouse = SDL_GetMouse(); SDL_WindowData *pointer_focus = seat->pointer.focus; + const Wayland_PointerObject obj = { + .wl_pointer = seat->pointer.wl_pointer, + .is_pointer = true + }; if (pointer_focus && mouse->cursor_visible) { if (!seat->pointer.relative_pointer || !mouse->relative_mode_hide_cursor) { const SDL_HitTestResult rc = pointer_focus->hit_test_result; if (seat->pointer.relative_pointer || rc == SDL_HITTEST_NORMAL || rc == SDL_HITTEST_DRAGGABLE) { - Wayland_SeatSetCursor(seat, mouse->cur_cursor); + Wayland_CursorStateSetCursor(&seat->pointer.cursor_state, &obj, pointer_focus, seat->pointer.enter_serial, mouse->cur_cursor); } else { - Wayland_SeatSetCursor(seat, sys_cursors[rc]); + Wayland_CursorStateSetCursor(&seat->pointer.cursor_state, &obj, pointer_focus, seat->pointer.enter_serial, sys_cursors[rc]); } } else { // Hide the cursor in relative mode, unless requested otherwise by the hint. - Wayland_SeatSetCursor(seat, NULL); + Wayland_CursorStateSetCursor(&seat->pointer.cursor_state, &obj, pointer_focus, seat->pointer.enter_serial, NULL); } } else { /* The spec states "The cursor actually changes only if the input device focus is one of the * requesting client's surfaces", so just clear the cursor if the seat has no pointer focus. */ - Wayland_SeatSetCursor(seat, NULL); + Wayland_CursorStateSetCursor(&seat->pointer.cursor_state, &obj, pointer_focus, seat->pointer.enter_serial, NULL); + } +} + +void Wayland_TabletToolUpdateCursor(SDL_WaylandPenTool *tool) +{ + SDL_Mouse *mouse = SDL_GetMouse(); + SDL_WindowData *tool_focus = tool->focus; + const Wayland_PointerObject obj = { + .wl_tool = tool->wltool, + .is_pointer = false + }; + + if (tool_focus && mouse->cursor_visible) { + // Relative mode is only relevant if the tool sends pointer events. + const bool relative = mouse->pen_mouse_events && (tool_focus->sdlwindow->flags & SDL_WINDOW_MOUSE_RELATIVE_MODE); + + if (!relative || !mouse->relative_mode_hide_cursor) { + Wayland_CursorStateSetCursor(&tool->cursor_state, &obj, tool_focus, tool->proximity_serial, mouse->cur_cursor); + } else { + // Hide the cursor in relative mode, unless requested otherwise by the hint. + Wayland_CursorStateSetCursor(&tool->cursor_state, &obj, tool_focus, tool->proximity_serial, NULL); + } + } else { + Wayland_CursorStateSetCursor(&tool->cursor_state, &obj, tool_focus, tool->proximity_serial, NULL); } } diff --git a/src/video/wayland/SDL_waylandmouse.h b/src/video/wayland/SDL_waylandmouse.h index 537b4bd33f..e2f04c57ce 100644 --- a/src/video/wayland/SDL_waylandmouse.h +++ b/src/video/wayland/SDL_waylandmouse.h @@ -26,10 +26,12 @@ extern void Wayland_InitMouse(SDL_VideoData *data); extern void Wayland_FiniMouse(SDL_VideoData *data); -extern void Wayland_SeatUpdateCursor(SDL_WaylandSeat *seat); +extern void Wayland_SeatUpdatePointerCursor(SDL_WaylandSeat *seat); +extern void Wayland_TabletToolUpdateCursor(SDL_WaylandPenTool *tool); extern void Wayland_SeatWarpMouse(SDL_WaylandSeat *seat, SDL_WindowData *window, float x, float y); -extern void Wayland_SeatSetCursorFrameCallback(SDL_WaylandSeat *seat); -extern void Wayland_SeatDestroyCursorFrameCallback(SDL_WaylandSeat *seat); +extern void Wayland_CursorStateSetFrameCallback(SDL_WaylandCursorState *state, void *userdata); +extern void Wayland_CursorStateDestroyFrameCallback(SDL_WaylandCursorState *state); +extern void Wayland_CursorStateRelease(SDL_WaylandCursorState *state); #if 0 // TODO RECONNECT: See waylandvideo.c for more information! extern void Wayland_RecreateCursors(void); #endif // 0