wayland: Set tablet cursors separately from pointer cursors

Some compositors don't implicitly use the pointer cursor when the tablet cursor is not set, and the presence of a tablet doesn't necessarily guarantee pointer capability. Set the cursor for tablet tools independently of pointer cursors.

This required refactoring of cursor state handling, as well as some tablet related structures.
This commit is contained in:
Frank Praznik
2025-10-29 12:34:55 -04:00
parent ca569bb837
commit 6a510d6174
4 changed files with 437 additions and 348 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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