Atlas - SDL_waylandmouse.c

Home / ext / SDL / src / video / wayland Lines: 1 | Size: 58943 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2026 Sam Lantinga <[email protected]> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20*/ 21 22#include "SDL_internal.h" 23 24#ifdef SDL_VIDEO_DRIVER_WAYLAND 25 26#include <errno.h> 27 28#include "../SDL_sysvideo.h" 29#include "../SDL_video_c.h" 30 31#include "../../core/unix/SDL_poll.h" 32#include "../../events/SDL_mouse_c.h" 33#include "SDL_waylandvideo.h" 34#include "../SDL_pixels_c.h" 35#include "SDL_waylandevents_c.h" 36 37#include "wayland-cursor.h" 38#include "SDL_waylandmouse.h" 39#include "SDL_waylandshmbuffer.h" 40 41#include "cursor-shape-v1-client-protocol.h" 42#include "pointer-constraints-unstable-v1-client-protocol.h" 43#include "viewporter-client-protocol.h" 44#include "pointer-warp-v1-client-protocol.h" 45#include "tablet-v2-client-protocol.h" 46 47#include "../../SDL_hints_c.h" 48 49static SDL_Cursor *sys_cursors[SDL_HITTEST_RESIZE_LEFT + 1]; 50 51static bool Wayland_SetRelativeMouseMode(bool enabled); 52 53typedef struct 54{ 55 int width; 56 int height; 57 struct wl_buffer *buffer; 58} CustomCursorImage; 59 60typedef struct 61{ 62 // The base dimensions of the cursor. 63 int width; 64 int height; 65 int hot_x; 66 int hot_y; 67 68 int images_per_frame; 69 CustomCursorImage images[]; 70} Wayland_CustomCursor; 71 72typedef struct 73{ 74 int size; 75 struct wl_list node; 76 struct wl_buffer *buffers[]; 77} Wayland_CachedSystemCursor; 78 79typedef struct 80{ 81 SDL_SystemCursor id; 82 struct wl_list cursor_buffer_cache; 83} Wayland_SystemCursor; 84 85struct SDL_CursorData 86{ 87 // Cursor animation data. 88 Uint32 *frame_durations_ms; 89 Uint32 total_duration_ms; 90 int num_frames; 91 bool is_system_cursor; 92 93 union 94 { 95 Wayland_CustomCursor custom; 96 Wayland_SystemCursor system; 97 } cursor_data; 98}; 99 100static int dbus_cursor_size; 101static char *dbus_cursor_theme; 102 103static void Wayland_FreeCursorThemes(SDL_VideoData *vdata) 104{ 105 for (int i = 0; i < vdata->num_cursor_themes; i += 1) { 106 WAYLAND_wl_cursor_theme_destroy(vdata->cursor_themes[i].theme); 107 } 108 vdata->num_cursor_themes = 0; 109 SDL_free(vdata->cursor_themes); 110 vdata->cursor_themes = NULL; 111} 112 113#ifdef SDL_USE_LIBDBUS 114 115#include "../../core/linux/SDL_dbus.h" 116 117#define CURSOR_NODE "org.freedesktop.portal.Desktop" 118#define CURSOR_PATH "/org/freedesktop/portal/desktop" 119#define CURSOR_INTERFACE "org.freedesktop.portal.Settings" 120#define CURSOR_NAMESPACE "org.gnome.desktop.interface" 121#define CURSOR_SIGNAL_NAME "SettingChanged" 122#define CURSOR_SIZE_KEY "cursor-size" 123#define CURSOR_THEME_KEY "cursor-theme" 124 125static DBusMessage *Wayland_ReadDBusProperty(SDL_DBusContext *dbus, const char *key) 126{ 127 static const char *iface = "org.gnome.desktop.interface"; 128 129 DBusMessage *reply = NULL; 130 DBusMessage *msg = dbus->message_new_method_call(CURSOR_NODE, 131 CURSOR_PATH, 132 CURSOR_INTERFACE, 133 "Read"); // Method 134 135 if (msg) { 136 if (dbus->message_append_args(msg, DBUS_TYPE_STRING, &iface, DBUS_TYPE_STRING, &key, DBUS_TYPE_INVALID)) { 137 reply = dbus->connection_send_with_reply_and_block(dbus->session_conn, msg, DBUS_TIMEOUT_USE_DEFAULT, NULL); 138 } 139 dbus->message_unref(msg); 140 } 141 142 return reply; 143} 144 145static bool Wayland_ParseDBusReply(SDL_DBusContext *dbus, DBusMessage *reply, int type, void *value) 146{ 147 DBusMessageIter iter[3]; 148 149 dbus->message_iter_init(reply, &iter[0]); 150 if (dbus->message_iter_get_arg_type(&iter[0]) != DBUS_TYPE_VARIANT) { 151 return false; 152 } 153 154 dbus->message_iter_recurse(&iter[0], &iter[1]); 155 if (dbus->message_iter_get_arg_type(&iter[1]) != DBUS_TYPE_VARIANT) { 156 return false; 157 } 158 159 dbus->message_iter_recurse(&iter[1], &iter[2]); 160 if (dbus->message_iter_get_arg_type(&iter[2]) != type) { 161 return false; 162 } 163 164 dbus->message_iter_get_basic(&iter[2], value); 165 166 return true; 167} 168 169static DBusHandlerResult Wayland_DBusCursorMessageFilter(DBusConnection *conn, DBusMessage *msg, void *data) 170{ 171 SDL_DBusContext *dbus = SDL_DBus_GetContext(); 172 SDL_VideoData *vdata = (SDL_VideoData *)data; 173 174 if (dbus->message_is_signal(msg, CURSOR_INTERFACE, CURSOR_SIGNAL_NAME)) { 175 DBusMessageIter signal_iter, variant_iter; 176 const char *namespace, *key; 177 178 dbus->message_iter_init(msg, &signal_iter); 179 // Check if the parameters are what we expect 180 if (dbus->message_iter_get_arg_type(&signal_iter) != DBUS_TYPE_STRING) { 181 goto not_our_signal; 182 } 183 dbus->message_iter_get_basic(&signal_iter, &namespace); 184 if (SDL_strcmp(CURSOR_NAMESPACE, namespace) != 0) { 185 goto not_our_signal; 186 } 187 if (!dbus->message_iter_next(&signal_iter)) { 188 goto not_our_signal; 189 } 190 if (dbus->message_iter_get_arg_type(&signal_iter) != DBUS_TYPE_STRING) { 191 goto not_our_signal; 192 } 193 dbus->message_iter_get_basic(&signal_iter, &key); 194 if (SDL_strcmp(CURSOR_SIZE_KEY, key) == 0) { 195 int new_cursor_size; 196 197 if (!dbus->message_iter_next(&signal_iter)) { 198 goto not_our_signal; 199 } 200 if (dbus->message_iter_get_arg_type(&signal_iter) != DBUS_TYPE_VARIANT) { 201 goto not_our_signal; 202 } 203 dbus->message_iter_recurse(&signal_iter, &variant_iter); 204 if (dbus->message_iter_get_arg_type(&variant_iter) != DBUS_TYPE_INT32) { 205 goto not_our_signal; 206 } 207 dbus->message_iter_get_basic(&variant_iter, &new_cursor_size); 208 209 if (dbus_cursor_size != new_cursor_size) { 210 dbus_cursor_size = new_cursor_size; 211 SDL_RedrawCursor(); // Force cursor update 212 } 213 } else if (SDL_strcmp(CURSOR_THEME_KEY, key) == 0) { 214 const char *new_cursor_theme = NULL; 215 216 if (!dbus->message_iter_next(&signal_iter)) { 217 goto not_our_signal; 218 } 219 if (dbus->message_iter_get_arg_type(&signal_iter) != DBUS_TYPE_VARIANT) { 220 goto not_our_signal; 221 } 222 dbus->message_iter_recurse(&signal_iter, &variant_iter); 223 if (dbus->message_iter_get_arg_type(&variant_iter) != DBUS_TYPE_STRING) { 224 goto not_our_signal; 225 } 226 dbus->message_iter_get_basic(&variant_iter, &new_cursor_theme); 227 228 if (!dbus_cursor_theme || !new_cursor_theme || SDL_strcmp(dbus_cursor_theme, new_cursor_theme) != 0) { 229 SDL_free(dbus_cursor_theme); 230 if (new_cursor_theme) { 231 dbus_cursor_theme = SDL_strdup(new_cursor_theme); 232 } else { 233 dbus_cursor_theme = NULL; 234 } 235 236 // Purge the current cached themes and force a cursor refresh. 237 Wayland_FreeCursorThemes(vdata); 238 SDL_RedrawCursor(); 239 } 240 } else { 241 goto not_our_signal; 242 } 243 244 return DBUS_HANDLER_RESULT_HANDLED; 245 } 246 247not_our_signal: 248 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; 249} 250 251static void Wayland_DBusInitCursorProperties(SDL_VideoData *vdata) 252{ 253 DBusMessage *reply; 254 SDL_DBusContext *dbus = SDL_DBus_GetContext(); 255 bool add_filter = false; 256 257 if (!dbus) { 258 return; 259 } 260 261 if ((reply = Wayland_ReadDBusProperty(dbus, CURSOR_SIZE_KEY))) { 262 if (Wayland_ParseDBusReply(dbus, reply, DBUS_TYPE_INT32, &dbus_cursor_size)) { 263 add_filter = true; 264 } 265 dbus->message_unref(reply); 266 } 267 268 if ((reply = Wayland_ReadDBusProperty(dbus, CURSOR_THEME_KEY))) { 269 const char *temp = NULL; 270 if (Wayland_ParseDBusReply(dbus, reply, DBUS_TYPE_STRING, &temp)) { 271 add_filter = true; 272 273 if (temp) { 274 dbus_cursor_theme = SDL_strdup(temp); 275 } 276 } 277 dbus->message_unref(reply); 278 } 279 280 // Only add the filter if at least one of the settings we want is present. 281 if (add_filter) { 282 dbus->bus_add_match(dbus->session_conn, 283 "type='signal', interface='" CURSOR_INTERFACE "'," 284 "member='" CURSOR_SIGNAL_NAME "', arg0='" CURSOR_NAMESPACE "'", 285 NULL); 286 dbus->connection_add_filter(dbus->session_conn, &Wayland_DBusCursorMessageFilter, vdata, NULL); 287 dbus->connection_flush(dbus->session_conn); 288 } 289} 290 291static void Wayland_DBusFinishCursorProperties(void) 292{ 293 SDL_free(dbus_cursor_theme); 294 dbus_cursor_theme = NULL; 295} 296 297#endif 298 299static struct wl_buffer *Wayland_CursorStateGetFrame(SDL_WaylandCursorState *state, int frame_index) 300{ 301 SDL_CursorData *data = state->current_cursor; 302 303 if (data) { 304 if (!data->is_system_cursor) { 305 const int offset = data->cursor_data.custom.images_per_frame * frame_index; 306 307 /* Find the closest image. Images that are larger than the 308 * desired size are preferred over images that are smaller. 309 */ 310 CustomCursorImage *closest = NULL; 311 const int target_area = SDL_lround(data->cursor_data.custom.width * data->cursor_data.custom.height * state->scale); 312 int closest_area = 0; 313 for (int i = 0; i < data->cursor_data.custom.images_per_frame && closest_area < target_area && data->cursor_data.custom.images[offset + i].buffer; ++i) { 314 closest = &data->cursor_data.custom.images[offset + i]; 315 closest_area = closest->width * closest->height; 316 } 317 318 return closest ? closest->buffer : NULL; 319 } else { 320 return ((Wayland_CachedSystemCursor *)(state->system_cursor_handle))->buffers[frame_index]; 321 } 322 } 323 324 return NULL; 325} 326 327static struct CursorThreadContext 328{ 329 SDL_Thread *thread; 330 struct wl_event_queue *queue; 331 struct wl_proxy *compositor_wrapper; 332 SDL_Mutex *lock; 333 bool should_exit; 334} cursor_thread_context; 335 336static void handle_cursor_thread_exit(void *data, struct wl_callback *wl_callback, uint32_t callback_data) 337{ 338 wl_callback_destroy(wl_callback); 339 cursor_thread_context.should_exit = true; 340} 341 342static const struct wl_callback_listener cursor_thread_exit_listener = { 343 handle_cursor_thread_exit 344}; 345 346static int SDLCALL Wayland_CursorThreadFunc(void *data) 347{ 348 struct wl_display *display = data; 349 const int display_fd = WAYLAND_wl_display_get_fd(display); 350 int ret; 351 352 /* The lock must be held whenever dispatching to avoid a race condition when setting 353 * or destroying cursor frame callbacks, as adding the callback followed by setting 354 * the listener is not an atomic operation, and the callback proxy must not be 355 * destroyed while in the callback handler. 356 * 357 * Any error other than EAGAIN is fatal and causes the thread to exit. 358 */ 359 while (!cursor_thread_context.should_exit) { 360 if (WAYLAND_wl_display_prepare_read_queue(display, cursor_thread_context.queue) == 0) { 361 Sint64 timeoutNS = -1; 362 363 ret = WAYLAND_wl_display_flush(display); 364 365 if (ret < 0) { 366 if (errno == EAGAIN) { 367 // If the flush failed with EAGAIN, don't block as not to inhibit other threads from reading events. 368 timeoutNS = SDL_MS_TO_NS(1); 369 } else { 370 WAYLAND_wl_display_cancel_read(display); 371 return -1; 372 } 373 } 374 375 // Wait for a read/write operation to become possible. 376 ret = SDL_IOReady(display_fd, SDL_IOR_READ, timeoutNS); 377 378 if (ret <= 0) { 379 WAYLAND_wl_display_cancel_read(display); 380 if (ret < 0) { 381 return -1; 382 } 383 384 // Nothing to read, and woke to flush; try again. 385 continue; 386 } 387 388 ret = WAYLAND_wl_display_read_events(display); 389 if (ret == -1) { 390 return -1; 391 } 392 } 393 394 SDL_LockMutex(cursor_thread_context.lock); 395 ret = WAYLAND_wl_display_dispatch_queue_pending(display, cursor_thread_context.queue); 396 SDL_UnlockMutex(cursor_thread_context.lock); 397 398 if (ret < 0) { 399 return -1; 400 } 401 } 402 403 return 0; 404} 405 406static bool Wayland_StartCursorThread(SDL_VideoData *data) 407{ 408 if (!cursor_thread_context.thread) { 409 cursor_thread_context.queue = Wayland_DisplayCreateQueue(data->display, "SDL Cursor Surface Queue"); 410 if (!cursor_thread_context.queue) { 411 goto cleanup; 412 } 413 414 cursor_thread_context.compositor_wrapper = WAYLAND_wl_proxy_create_wrapper(data->compositor); 415 if (!cursor_thread_context.compositor_wrapper) { 416 goto cleanup; 417 } 418 WAYLAND_wl_proxy_set_queue(cursor_thread_context.compositor_wrapper, cursor_thread_context.queue); 419 420 cursor_thread_context.lock = SDL_CreateMutex(); 421 if (!cursor_thread_context.lock) { 422 goto cleanup; 423 } 424 425 cursor_thread_context.thread = SDL_CreateThread(Wayland_CursorThreadFunc, "wl_cursor_surface", data->display); 426 if (!cursor_thread_context.thread) { 427 goto cleanup; 428 } 429 430 return true; 431 } 432 433cleanup: 434 if (cursor_thread_context.lock) { 435 SDL_DestroyMutex(cursor_thread_context.lock); 436 } 437 438 if (cursor_thread_context.compositor_wrapper) { 439 WAYLAND_wl_proxy_wrapper_destroy(cursor_thread_context.compositor_wrapper); 440 } 441 442 if (cursor_thread_context.queue) { 443 WAYLAND_wl_event_queue_destroy(cursor_thread_context.queue); 444 } 445 446 SDL_zero(cursor_thread_context); 447 448 return false; 449} 450 451static void Wayland_DestroyCursorThread(SDL_VideoData *data) 452{ 453 if (cursor_thread_context.thread) { 454 // Dispatch the exit event to unblock the animation thread and signal it to exit. 455 struct wl_proxy *display_wrapper = WAYLAND_wl_proxy_create_wrapper(data->display); 456 WAYLAND_wl_proxy_set_queue(display_wrapper, cursor_thread_context.queue); 457 458 SDL_LockMutex(cursor_thread_context.lock); 459 struct wl_callback *cb = wl_display_sync((struct wl_display *)display_wrapper); 460 wl_callback_add_listener(cb, &cursor_thread_exit_listener, NULL); 461 SDL_UnlockMutex(cursor_thread_context.lock); 462 463 WAYLAND_wl_proxy_wrapper_destroy(display_wrapper); 464 465 int ret = WAYLAND_wl_display_flush(data->display); 466 while (ret == -1 && errno == EAGAIN) { 467 // Shutting down the thread requires a successful flush. 468 ret = SDL_IOReady(WAYLAND_wl_display_get_fd(data->display), SDL_IOR_WRITE, -1); 469 if (ret >= 0) { 470 ret = WAYLAND_wl_display_flush(data->display); 471 } 472 } 473 474 // Avoid a warning if the flush failed due to a broken connection. 475 if (ret < 0) { 476 wl_callback_destroy(cb); 477 } 478 479 // Wait for the thread to return; it will exit automatically on a broken connection. 480 SDL_WaitThread(cursor_thread_context.thread, NULL); 481 482 WAYLAND_wl_proxy_wrapper_destroy(cursor_thread_context.compositor_wrapper); 483 WAYLAND_wl_event_queue_destroy(cursor_thread_context.queue); 484 SDL_DestroyMutex(cursor_thread_context.lock); 485 SDL_zero(cursor_thread_context); 486 } 487} 488 489static void cursor_frame_done(void *data, struct wl_callback *cb, uint32_t time); 490static const struct wl_callback_listener cursor_frame_listener = { 491 cursor_frame_done 492}; 493 494static void cursor_frame_done(void *data, struct wl_callback *cb, uint32_t time) 495{ 496 SDL_WaylandCursorState *state = (SDL_WaylandCursorState *)data; 497 if (!state->current_cursor) { 498 return; 499 } 500 501 Uint32 *frames = state->current_cursor->frame_durations_ms; 502 SDL_CursorData *c = state->current_cursor; 503 504 const Uint64 now = SDL_GetTicks(); 505 const Uint32 elapsed = (now - state->last_frame_callback_time_ms) % c->total_duration_ms; 506 Uint32 advance = 0; 507 int next = state->current_frame; 508 509 state->current_frame_time_ms += elapsed; 510 511 // Calculate the next frame based on the elapsed duration. 512 for (Uint32 t = frames[next]; t <= state->current_frame_time_ms; t += frames[next]) { 513 next = (next + 1) % c->num_frames; 514 advance = t; 515 516 // Make sure we don't end up in an infinite loop if a cursor has frame durations of 0. 517 if (!frames[next]) { 518 break; 519 } 520 } 521 522 wl_callback_destroy(cb); 523 state->frame_callback = NULL; 524 525 // Don't queue another callback if this frame time is infinite. 526 if (frames[next]) { 527 state->frame_callback = wl_surface_frame(state->surface); 528 wl_callback_add_listener(state->frame_callback, &cursor_frame_listener, data); 529 } 530 531 state->current_frame_time_ms -= advance; 532 state->last_frame_callback_time_ms = now; 533 state->current_frame = next; 534 535 struct wl_buffer *buffer = Wayland_CursorStateGetFrame(state, next); 536 wl_surface_attach(state->surface, buffer, 0, 0); 537 538 if (wl_surface_get_version(state->surface) >= WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION) { 539 wl_surface_damage_buffer(state->surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32); 540 } else { 541 wl_surface_damage(state->surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32); 542 } 543 wl_surface_commit(state->surface); 544} 545 546void Wayland_CursorStateSetFrameCallback(SDL_WaylandCursorState *state, void *userdata) 547{ 548 SDL_LockMutex(cursor_thread_context.lock); 549 550 state->frame_callback = wl_surface_frame(state->surface); 551 wl_callback_add_listener(state->frame_callback, &cursor_frame_listener, userdata); 552 553 SDL_UnlockMutex(cursor_thread_context.lock); 554} 555 556void Wayland_CursorStateDestroyFrameCallback(SDL_WaylandCursorState *state) 557{ 558 SDL_LockMutex(cursor_thread_context.lock); 559 560 if (state->frame_callback) { 561 wl_callback_destroy(state->frame_callback); 562 state->frame_callback = NULL; 563 } 564 565 SDL_UnlockMutex(cursor_thread_context.lock); 566} 567 568static void Wayland_CursorStateResetAnimation(SDL_WaylandCursorState *state, bool lock) 569{ 570 if (lock) { 571 SDL_LockMutex(cursor_thread_context.lock); 572 } 573 574 state->last_frame_callback_time_ms = SDL_GetTicks(); 575 state->current_frame_time_ms = 0; 576 state->current_frame = 0; 577 578 if (lock) { 579 SDL_UnlockMutex(cursor_thread_context.lock); 580 } 581} 582 583void Wayland_CursorStateRelease(SDL_WaylandCursorState *state) 584{ 585 Wayland_CursorStateDestroyFrameCallback(state); 586 if (state->cursor_shape) { 587 wp_cursor_shape_device_v1_destroy(state->cursor_shape); 588 } 589 if (state->viewport) { 590 wp_viewport_destroy(state->viewport); 591 } 592 if (state->surface) { 593 wl_surface_attach(state->surface, NULL, 0, 0); 594 wl_surface_commit(state->surface); 595 wl_surface_destroy(state->surface); 596 } 597 598 SDL_zerop(state); 599} 600 601static Wayland_CachedSystemCursor *Wayland_CacheSystemCursor(SDL_CursorData *cdata, struct wl_cursor *cursor, int size) 602{ 603 Wayland_CachedSystemCursor *cache = NULL; 604 605 // Is this cursor already cached at the target scale? 606 if (!WAYLAND_wl_list_empty(&cdata->cursor_data.system.cursor_buffer_cache)) { 607 Wayland_CachedSystemCursor *c = NULL; 608 wl_list_for_each (c, &cdata->cursor_data.system.cursor_buffer_cache, node) { 609 if (c->size == size) { 610 cache = c; 611 break; 612 } 613 } 614 } 615 616 if (!cache) { 617 cache = SDL_calloc(1, sizeof(Wayland_CachedSystemCursor) + (sizeof(struct wl_buffer *) * cdata->num_frames)); 618 619 cache->size = size; 620 for (int i = 0; i < cdata->num_frames; ++i) { 621 cache->buffers[i] = WAYLAND_wl_cursor_image_get_buffer(cursor->images[i]); 622 } 623 624 WAYLAND_wl_list_insert(&cdata->cursor_data.system.cursor_buffer_cache, &cache->node); 625 } 626 627 return cache; 628} 629 630static bool Wayland_GetSystemCursor(SDL_CursorData *cdata, SDL_WaylandCursorState *state, int *dst_size, int *hot_x, int *hot_y) 631{ 632 SDL_VideoData *vdata = SDL_GetVideoDevice()->internal; 633 struct wl_cursor_theme *theme = NULL; 634 const char *css_name = "default"; 635 const char *fallback_name = NULL; 636 double scale_factor = state->scale; 637 int theme_size = dbus_cursor_size; 638 639 // Fallback envvar if the DBus properties don't exist 640 if (theme_size <= 0) { 641 const char *xcursor_size = SDL_getenv("XCURSOR_SIZE"); 642 if (xcursor_size) { 643 theme_size = SDL_atoi(xcursor_size); 644 } 645 } 646 if (theme_size <= 0) { 647 theme_size = 24; 648 } 649 650 // First, find the appropriate theme based on the current scale... 651 const int scaled_size = (int)SDL_lround(theme_size * scale_factor); 652 for (int i = 0; i < vdata->num_cursor_themes; ++i) { 653 if (vdata->cursor_themes[i].size == scaled_size) { 654 theme = vdata->cursor_themes[i].theme; 655 break; 656 } 657 } 658 if (!theme) { 659 const char *xcursor_theme = dbus_cursor_theme; 660 661 SDL_WaylandCursorTheme *new_cursor_themes = SDL_realloc(vdata->cursor_themes, 662 sizeof(SDL_WaylandCursorTheme) * (vdata->num_cursor_themes + 1)); 663 if (!new_cursor_themes) { 664 return false; 665 } 666 vdata->cursor_themes = new_cursor_themes; 667 668 // Fallback envvar if the DBus properties don't exist 669 if (!xcursor_theme) { 670 xcursor_theme = SDL_getenv("XCURSOR_THEME"); 671 } 672 673 theme = WAYLAND_wl_cursor_theme_load(xcursor_theme, scaled_size, vdata->shm); 674 vdata->cursor_themes[vdata->num_cursor_themes].size = scaled_size; 675 vdata->cursor_themes[vdata->num_cursor_themes++].theme = theme; 676 } 677 678 css_name = SDL_GetCSSCursorName(cdata->cursor_data.system.id, &fallback_name); 679 struct wl_cursor *cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, css_name); 680 if (!cursor && fallback_name) { 681 cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, fallback_name); 682 } 683 684 // Fallback to the default cursor if the chosen one wasn't found 685 if (!cursor) { 686 cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "default"); 687 } 688 // Try the old X11 name as a last resort 689 if (!cursor) { 690 cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "left_ptr"); 691 } 692 if (!cursor) { 693 return false; 694 } 695 696 // ... Set the cursor data, finally. 697 cdata->num_frames = cursor->image_count; 698 Wayland_CachedSystemCursor *c = Wayland_CacheSystemCursor(cdata, cursor, theme_size); 699 state->system_cursor_handle = c; 700 701 if (cursor->image_count > 1 && !cdata->frame_durations_ms) { 702 cdata->total_duration_ms = 0; 703 cdata->frame_durations_ms = SDL_calloc(cursor->image_count, sizeof(Uint32)); 704 705 for (int i = 0; i < cursor->image_count; ++i) { 706 cdata->frame_durations_ms[i] = cursor->images[i]->delay; 707 cdata->total_duration_ms += cursor->images[i]->delay; 708 } 709 } 710 711 *dst_size = SDL_lround(cursor->images[0]->width / state->scale); 712 *hot_x = SDL_lround(cursor->images[0]->hotspot_x / state->scale); 713 *hot_y = SDL_lround(cursor->images[0]->hotspot_y / state->scale); 714 715 return true; 716} 717 718static int surface_sort_callback(const void *a, const void *b) 719{ 720 SDL_Surface *s1 = (SDL_Surface *)a; 721 SDL_Surface *s2 = (SDL_Surface *)b; 722 723 return (s1->w * s1->h) <= (s2->w * s2->h) ? -1 : 1; 724} 725 726static SDL_Cursor *Wayland_CreateAnimatedCursor(SDL_CursorFrameInfo *frames, int frame_count, int hot_x, int hot_y) 727{ 728 SDL_Cursor *cursor = SDL_calloc(1, sizeof(*cursor)); 729 if (!cursor) { 730 return NULL; 731 } 732 733 SDL_CursorData *data = NULL; 734 Wayland_SHMPool *shm_pool = NULL; 735 int pool_size = 0; 736 int max_images = 0; 737 bool is_stack = false; 738 struct SurfaceArray 739 { 740 SDL_Surface **surfaces; 741 int count; 742 } *surfaces = SDL_small_alloc(struct SurfaceArray, frame_count, &is_stack); 743 if (!surfaces) { 744 goto failed; 745 } 746 SDL_memset(surfaces, 0, sizeof(struct SurfaceArray) * frame_count); 747 748 // Calculate the total allocation size. 749 for (int i = 0; i < frame_count; ++i) { 750 surfaces[i].surfaces = SDL_GetSurfaceImages(frames[i].surface, &surfaces[i].count); 751 if (!surfaces[i].surfaces) { 752 goto failed; 753 } 754 755 SDL_qsort(surfaces[i].surfaces, surfaces[i].count, sizeof(SDL_Surface *), surface_sort_callback); 756 max_images = SDL_max(max_images, surfaces[i].count); 757 for (int j = 0; j < surfaces[i].count; ++j) { 758 pool_size += surfaces[i].surfaces[j]->w * surfaces[i].surfaces[j]->h * 4; 759 } 760 } 761 762 data = SDL_calloc(1, sizeof(*data) + (sizeof(CustomCursorImage) * max_images * frame_count)); 763 if (!data) { 764 goto failed; 765 } 766 767 data->frame_durations_ms = SDL_calloc(frame_count, sizeof(Uint32)); 768 if (!data->frame_durations_ms) { 769 goto failed; 770 } 771 772 shm_pool = Wayland_AllocSHMPool(pool_size); 773 if (!shm_pool) { 774 goto failed; 775 } 776 777 cursor->internal = data; 778 data->cursor_data.custom.width = frames[0].surface->w; 779 data->cursor_data.custom.height = frames[0].surface->h; 780 data->cursor_data.custom.hot_x = hot_x; 781 data->cursor_data.custom.hot_y = hot_y; 782 data->cursor_data.custom.images_per_frame = max_images; 783 data->num_frames = frame_count; 784 785 for (int i = 0; i < frame_count; ++i) { 786 data->frame_durations_ms[i] = frames[i].duration; 787 if (data->total_duration_ms < SDL_MAX_UINT32) { 788 if (data->frame_durations_ms[i] > 0) { 789 data->total_duration_ms += data->frame_durations_ms[i]; 790 } else { 791 data->total_duration_ms = SDL_MAX_UINT32; 792 } 793 } 794 795 const int offset = i * max_images; 796 for (int j = 0; j < surfaces[i].count; ++j) { 797 SDL_Surface *surface = surfaces[i].surfaces[j]; 798 799 // Convert the surface format, if required. 800 if (surface->format != SDL_PIXELFORMAT_ARGB8888) { 801 surface = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_ARGB8888); 802 if (!surface) { 803 goto failed; 804 } 805 } 806 807 data->cursor_data.custom.images[offset + j].width = surface->w; 808 data->cursor_data.custom.images[offset + j].height = surface->h; 809 810 void *buf_data; 811 data->cursor_data.custom.images[offset + j].buffer = Wayland_AllocBufferFromPool(shm_pool, surface->w, surface->h, &buf_data); 812 // Wayland requires premultiplied alpha for its surfaces. 813 SDL_PremultiplyAlpha(surface->w, surface->h, 814 surface->format, surface->pixels, surface->pitch, 815 SDL_PIXELFORMAT_ARGB8888, buf_data, surface->w * 4, true); 816 817 if (surface != surfaces[i].surfaces[j]) { 818 SDL_DestroySurface(surface); 819 } 820 } 821 822 // Free the memory returned by SDL_GetSurfaceImages(). 823 SDL_free(surfaces[i].surfaces); 824 } 825 826 SDL_small_free(surfaces, is_stack); 827 Wayland_ReleaseSHMPool(shm_pool); 828 829 return cursor; 830 831failed: 832 Wayland_ReleaseSHMPool(shm_pool); 833 if (data) { 834 SDL_free(data->frame_durations_ms); 835 for (int i = 0; i < data->cursor_data.custom.images_per_frame * frame_count; ++i) { 836 if (data->cursor_data.custom.images[i].buffer) { 837 wl_buffer_destroy(data->cursor_data.custom.images[i].buffer); 838 } 839 } 840 } 841 842 SDL_free(data); 843 844 if (surfaces) { 845 for (int i = 0; i < frame_count; ++i) { 846 SDL_free(surfaces[i].surfaces); 847 } 848 SDL_small_free(surfaces, is_stack); 849 } 850 851 SDL_free(cursor); 852 853 return NULL; 854} 855 856static SDL_Cursor *Wayland_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y) 857{ 858 SDL_CursorFrameInfo frame = { 859 surface, 0 860 }; 861 862 return Wayland_CreateAnimatedCursor(&frame, 1, hot_x, hot_y); 863} 864 865static SDL_Cursor *Wayland_CreateSystemCursor(SDL_SystemCursor id) 866{ 867 SDL_Cursor *cursor = SDL_calloc(1, sizeof(*cursor)); 868 869 if (cursor) { 870 SDL_CursorData *cdata = SDL_calloc(1, sizeof(*cdata)); 871 if (!cdata) { 872 SDL_free(cursor); 873 return NULL; 874 } 875 cursor->internal = cdata; 876 877 WAYLAND_wl_list_init(&cdata->cursor_data.system.cursor_buffer_cache); 878 cdata->cursor_data.system.id = id; 879 cdata->is_system_cursor = true; 880 } 881 882 return cursor; 883} 884 885static SDL_Cursor *Wayland_CreateDefaultCursor(void) 886{ 887 SDL_SystemCursor id = SDL_GetDefaultSystemCursor(); 888 return Wayland_CreateSystemCursor(id); 889} 890 891static void Wayland_FreeCursorData(SDL_CursorData *d) 892{ 893 SDL_VideoDevice *video_device = SDL_GetVideoDevice(); 894 SDL_VideoData *video_data = video_device->internal; 895 SDL_WaylandSeat *seat; 896 897 // Stop any frame callbacks and detach buffers associated with the cursor being destroyed. 898 wl_list_for_each (seat, &video_data->seat_list, link) 899 { 900 if (seat->pointer.cursor_state.current_cursor == d) { 901 Wayland_CursorStateDestroyFrameCallback(&seat->pointer.cursor_state); 902 903 // Custom cursor buffers are about to be destroyed, so ensure they are detached. 904 if (!d->is_system_cursor && seat->pointer.cursor_state.surface) { 905 wl_surface_attach(seat->pointer.cursor_state.surface, NULL, 0, 0); 906 } 907 908 seat->pointer.cursor_state.current_cursor = NULL; 909 } 910 911 SDL_WaylandPenTool *tool; 912 wl_list_for_each (tool, &seat->tablet.tool_list, link) { 913 if (tool->cursor_state.current_cursor == d) { 914 Wayland_CursorStateDestroyFrameCallback(&tool->cursor_state); 915 916 // Custom cursor buffers are about to be destroyed, so ensure they are detached. 917 if (!d->is_system_cursor && tool->cursor_state.surface) { 918 wl_surface_attach(seat->pointer.cursor_state.surface, NULL, 0, 0); 919 } 920 921 tool->cursor_state.current_cursor = NULL; 922 } 923 } 924 } 925 926 if (d->is_system_cursor) { 927 Wayland_CachedSystemCursor *c, *temp; 928 wl_list_for_each_safe(c, temp, &d->cursor_data.system.cursor_buffer_cache, node) { 929 SDL_free(c); 930 } 931 } else { 932 for (int i = 0; i < d->num_frames * d->cursor_data.custom.images_per_frame; ++i) { 933 if (d->cursor_data.custom.images[i].buffer) { 934 wl_buffer_destroy(d->cursor_data.custom.images[i].buffer); 935 } 936 } 937 } 938 939 SDL_free(d->frame_durations_ms); 940} 941 942static void Wayland_FreeCursor(SDL_Cursor *cursor) 943{ 944 if (!cursor) { 945 return; 946 } 947 948 // Probably not a cursor we own 949 if (!cursor->internal) { 950 return; 951 } 952 953 Wayland_FreeCursorData(cursor->internal); 954 955 SDL_free(cursor->internal); 956 SDL_free(cursor); 957} 958 959static enum wp_cursor_shape_device_v1_shape Wayland_GetSystemCursorShape(SDL_SystemCursor id) 960{ 961 Uint32 shape; 962 963 switch (id) { 964 case SDL_SYSTEM_CURSOR_DEFAULT: 965 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT; 966 break; 967 case SDL_SYSTEM_CURSOR_TEXT: 968 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT; 969 break; 970 case SDL_SYSTEM_CURSOR_WAIT: 971 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_WAIT; 972 break; 973 case SDL_SYSTEM_CURSOR_CROSSHAIR: 974 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR; 975 break; 976 case SDL_SYSTEM_CURSOR_PROGRESS: 977 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_PROGRESS; 978 break; 979 case SDL_SYSTEM_CURSOR_NWSE_RESIZE: 980 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NWSE_RESIZE; 981 break; 982 case SDL_SYSTEM_CURSOR_NESW_RESIZE: 983 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NESW_RESIZE; 984 break; 985 case SDL_SYSTEM_CURSOR_EW_RESIZE: 986 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_EW_RESIZE; 987 break; 988 case SDL_SYSTEM_CURSOR_NS_RESIZE: 989 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NS_RESIZE; 990 break; 991 case SDL_SYSTEM_CURSOR_MOVE: 992 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_SCROLL; 993 break; 994 case SDL_SYSTEM_CURSOR_NOT_ALLOWED: 995 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NOT_ALLOWED; 996 break; 997 case SDL_SYSTEM_CURSOR_POINTER: 998 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER; 999 break; 1000 case SDL_SYSTEM_CURSOR_NW_RESIZE: 1001 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NW_RESIZE; 1002 break; 1003 case SDL_SYSTEM_CURSOR_N_RESIZE: 1004 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_N_RESIZE; 1005 break; 1006 case SDL_SYSTEM_CURSOR_NE_RESIZE: 1007 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NE_RESIZE; 1008 break; 1009 case SDL_SYSTEM_CURSOR_E_RESIZE: 1010 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_E_RESIZE; 1011 break; 1012 case SDL_SYSTEM_CURSOR_SE_RESIZE: 1013 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SE_RESIZE; 1014 break; 1015 case SDL_SYSTEM_CURSOR_S_RESIZE: 1016 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_S_RESIZE; 1017 break; 1018 case SDL_SYSTEM_CURSOR_SW_RESIZE: 1019 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SW_RESIZE; 1020 break; 1021 case SDL_SYSTEM_CURSOR_W_RESIZE: 1022 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_W_RESIZE; 1023 break; 1024 default: 1025 SDL_assert(0); // Should never be here... 1026 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT; 1027 } 1028 1029 return shape; 1030} 1031 1032typedef struct Wayland_PointerObject 1033{ 1034 union 1035 { 1036 struct wl_pointer *wl_pointer; 1037 struct zwp_tablet_tool_v2 *wl_tool; 1038 }; 1039 1040 bool is_pointer; 1041} Wayland_PointerObject; 1042 1043static void Wayland_CursorStateSetCursor(SDL_WaylandCursorState *state, const Wayland_PointerObject *obj, SDL_WindowData *focus, Uint32 serial, SDL_Cursor *cursor) 1044{ 1045 SDL_VideoData *viddata = SDL_GetVideoDevice()->internal; 1046 SDL_CursorData *cursor_data = cursor ? cursor->internal : NULL; 1047 int dst_width = 0; 1048 int dst_height = 0; 1049 int hot_x; 1050 int hot_y; 1051 1052 // Stop the frame callback for old animated cursors. 1053 if (cursor_data != state->current_cursor) { 1054 Wayland_CursorStateDestroyFrameCallback(state); 1055 } 1056 1057 if (cursor) { 1058 if (cursor_data == state->current_cursor && state->current_frame >= 0) { 1059 // Restart the animation sequence if the cursor didn't change. 1060 if (cursor_data->num_frames > 1) { 1061 Wayland_CursorStateResetAnimation(state, true); 1062 } 1063 1064 return; 1065 } 1066 1067 if (cursor_data->is_system_cursor) { 1068 // If the cursor shape protocol is supported, the compositor will draw nicely scaled cursors for us, so nothing more to do. 1069 if (state->cursor_shape) { 1070 // Don't need the surface or viewport if using the cursor shape protocol. 1071 if (state->surface) { 1072 wl_surface_attach(state->surface, NULL, 0, 0); 1073 wl_surface_commit(state->surface); 1074 1075 if (obj->is_pointer) { 1076 wl_pointer_set_cursor(obj->wl_pointer, serial, NULL, 0, 0); 1077 } else { 1078 zwp_tablet_tool_v2_set_cursor(obj->wl_tool, serial, NULL, 0, 0); 1079 } 1080 1081 if (state->viewport) { 1082 wp_viewport_destroy(state->viewport); 1083 state->viewport = NULL; 1084 } 1085 1086 wl_surface_destroy(state->surface); 1087 state->surface = NULL; 1088 } 1089 1090 const enum wp_cursor_shape_device_v1_shape shape = Wayland_GetSystemCursorShape(cursor_data->cursor_data.system.id); 1091 wp_cursor_shape_device_v1_set_shape(state->cursor_shape, serial, shape); 1092 state->current_cursor = cursor_data; 1093 state->current_frame = 0; 1094 1095 return; 1096 } 1097 1098 // If viewports aren't available, the scale is always 1.0. 1099 state->scale = viddata->viewporter && focus ? focus->scale_factor : 1.0; 1100 if (!Wayland_GetSystemCursor(cursor_data, state, &dst_width, &hot_x, &hot_y)) { 1101 return; 1102 } 1103 1104 dst_height = dst_width; 1105 } else { 1106 /* If viewports aren't available, the scale is always 1.0. 1107 * 1108 * If the pointer scale values are 1.0, the preferred backing buffer scale is the window scale. 1109 * 1110 * If the pointer is scaled, the dimensions are scaled by the pointer scale, so custom cursors will be scaled 1111 * relative to the viewport size. 1112 */ 1113 if (!focus || (focus->pointer_scale.x == 1.0 && focus->pointer_scale.y == 1.0)) { 1114 state->scale = viddata->viewporter && focus ? focus->scale_factor : 1.0; 1115 dst_width = cursor_data->cursor_data.custom.width; 1116 dst_height = cursor_data->cursor_data.custom.height; 1117 hot_x = cursor_data->cursor_data.custom.hot_x; 1118 hot_y = cursor_data->cursor_data.custom.hot_y; 1119 } else { 1120 // The preferred buffer scale is the inverse of the pointer scale. 1121 state->scale = 1.0 / SDL_min(focus->pointer_scale.x, focus->pointer_scale.y); 1122 dst_width = SDL_max((int)SDL_lround((double)cursor_data->cursor_data.custom.width / focus->pointer_scale.x), 1); 1123 dst_height = SDL_max((int)SDL_lround((double)cursor_data->cursor_data.custom.height / focus->pointer_scale.y), 1); 1124 hot_x = (int)SDL_lround((double)cursor_data->cursor_data.custom.hot_x / focus->pointer_scale.x); 1125 hot_y = (int)SDL_lround((double)cursor_data->cursor_data.custom.hot_y / focus->pointer_scale.y); 1126 } 1127 1128 } 1129 1130 state->current_cursor = cursor_data; 1131 1132 if (!state->surface) { 1133 if (cursor_thread_context.compositor_wrapper) { 1134 state->surface = wl_compositor_create_surface((struct wl_compositor *)cursor_thread_context.compositor_wrapper); 1135 } else { 1136 state->surface = wl_compositor_create_surface(viddata->compositor); 1137 } 1138 } 1139 1140 struct wl_buffer *buffer = Wayland_CursorStateGetFrame(state, 0); 1141 wl_surface_attach(state->surface, buffer, 0, 0); 1142 state->current_frame = 0; 1143 1144 if (state->scale != 1.0) { 1145 if (!state->viewport) { 1146 state->viewport = wp_viewporter_get_viewport(viddata->viewporter, state->surface); 1147 } 1148 1149 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)); 1150 wp_viewport_set_destination(state->viewport, dst_width, dst_height); 1151 } else if (state->viewport) { 1152 wp_viewport_destroy(state->viewport); 1153 state->viewport = NULL; 1154 } 1155 1156 if (obj->is_pointer) { 1157 wl_pointer_set_cursor(obj->wl_pointer, serial, state->surface, hot_x, hot_y); 1158 } else { 1159 zwp_tablet_tool_v2_set_cursor(obj->wl_tool, serial, state->surface, hot_x, hot_y); 1160 } 1161 1162 if (wl_surface_get_version(state->surface) >= WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION) { 1163 wl_surface_damage_buffer(state->surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32); 1164 } else { 1165 wl_surface_damage(state->surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32); 1166 } 1167 1168 // If more than one frame is available, create a frame callback to run the animation. 1169 if (cursor_data->num_frames > 1) { 1170 Wayland_CursorStateResetAnimation(state, false); 1171 Wayland_CursorStateSetFrameCallback(state, state); 1172 } 1173 1174 wl_surface_commit(state->surface); 1175 } else { 1176 Wayland_CursorStateDestroyFrameCallback(state); 1177 state->current_cursor = NULL; 1178 1179 if (state->surface) { 1180 wl_surface_attach(state->surface, NULL, 0, 0); 1181 wl_surface_commit(state->surface); 1182 } 1183 1184 if (obj->is_pointer) { 1185 wl_pointer_set_cursor(obj->wl_pointer, serial, NULL, 0, 0); 1186 } else { 1187 zwp_tablet_tool_v2_set_cursor(obj->wl_tool, serial, NULL, 0, 0); 1188 } 1189 } 1190} 1191 1192static void Wayland_CursorStateResetCursor(SDL_WaylandCursorState *state) 1193{ 1194 // Stop the frame callback and set the reset status. 1195 Wayland_CursorStateDestroyFrameCallback(state); 1196 state->current_frame = -1; 1197} 1198 1199void Wayland_DisplayUpdatePointerFocusedScale(SDL_WindowData *updated_window) 1200{ 1201 SDL_VideoData *viddata = updated_window->waylandData; 1202 SDL_WaylandSeat *seat; 1203 const double new_scale = SDL_min(updated_window->pointer_scale.x, updated_window->pointer_scale.y); 1204 1205 wl_list_for_each (seat, &viddata->seat_list, link) { 1206 if (seat->pointer.focus == updated_window) { 1207 SDL_WaylandCursorState *state = &seat->pointer.cursor_state; 1208 if (state->current_cursor && !state->current_cursor->is_system_cursor && state->scale != new_scale) { 1209 Wayland_CursorStateResetCursor(state); 1210 Wayland_SeatUpdatePointerCursor(seat); 1211 } 1212 } 1213 1214 SDL_WaylandPenTool *tool; 1215 wl_list_for_each (tool, &seat->tablet.tool_list, link) { 1216 if (tool->focus == updated_window) { 1217 SDL_WaylandCursorState *state = &tool->cursor_state; 1218 if (state->current_cursor && !state->current_cursor->is_system_cursor && state->scale != new_scale) { 1219 Wayland_CursorStateResetCursor(&tool->cursor_state); 1220 Wayland_TabletToolUpdateCursor(tool); 1221 } 1222 } 1223 } 1224 } 1225} 1226 1227static bool Wayland_ShowCursor(SDL_Cursor *cursor) 1228{ 1229 SDL_VideoDevice *vd = SDL_GetVideoDevice(); 1230 SDL_VideoData *d = vd->internal; 1231 SDL_Mouse *mouse = SDL_GetMouse(); 1232 SDL_WaylandSeat *seat; 1233 Wayland_PointerObject obj; 1234 1235 wl_list_for_each (seat, &d->seat_list, link) { 1236 if (seat->pointer.wl_pointer) { 1237 obj.wl_pointer = seat->pointer.wl_pointer; 1238 obj.is_pointer = true; 1239 if (mouse->focus && mouse->focus->internal == seat->pointer.focus) { 1240 Wayland_CursorStateSetCursor(&seat->pointer.cursor_state, &obj, seat->pointer.focus, seat->pointer.enter_serial, cursor); 1241 } else if (!seat->pointer.focus) { 1242 Wayland_CursorStateResetCursor(&seat->pointer.cursor_state); 1243 } 1244 } 1245 1246 SDL_WaylandPenTool *tool; 1247 wl_list_for_each(tool, &seat->tablet.tool_list, link) { 1248 obj.wl_tool = tool->wltool; 1249 obj.is_pointer = false; 1250 1251 /* The current cursor is explicitly set on tablet tools, as there may be no pointer device, or 1252 * the pointer may not have focus, which would instead cause the default cursor to be set. 1253 */ 1254 if (tool->focus && (!mouse->focus || mouse->focus->internal == tool->focus)) { 1255 Wayland_CursorStateSetCursor(&tool->cursor_state, &obj, tool->focus, tool->proximity_serial, mouse->cur_cursor); 1256 } else if (!tool->focus) { 1257 Wayland_CursorStateResetCursor(&tool->cursor_state); 1258 } 1259 } 1260 } 1261 1262 return true; 1263} 1264 1265void Wayland_SeatWarpMouse(SDL_WaylandSeat *seat, SDL_WindowData *window, float x, float y) 1266{ 1267 SDL_VideoDevice *vd = SDL_GetVideoDevice(); 1268 SDL_VideoData *d = vd->internal; 1269 1270 if (seat->pointer.wl_pointer) { 1271 if (d->wp_pointer_warp_v1) { 1272 // It's a protocol error to warp the pointer outside of the surface, so clamp the position. 1273 const wl_fixed_t f_x = wl_fixed_from_double(SDL_clamp(x / window->pointer_scale.x, 0, window->current.logical_width)); 1274 const wl_fixed_t f_y = wl_fixed_from_double(SDL_clamp(y / window->pointer_scale.y, 0, window->current.logical_height)); 1275 wp_pointer_warp_v1_warp_pointer(d->wp_pointer_warp_v1, window->surface, seat->pointer.wl_pointer, f_x, f_y, seat->pointer.enter_serial); 1276 } else { 1277 bool update_grabs = false; 1278 1279 // Pointers can only have one confinement type active on a surface at one time. 1280 if (seat->pointer.confined_pointer) { 1281 zwp_confined_pointer_v1_destroy(seat->pointer.confined_pointer); 1282 seat->pointer.confined_pointer = NULL; 1283 update_grabs = true; 1284 } 1285 if (seat->pointer.locked_pointer) { 1286 zwp_locked_pointer_v1_destroy(seat->pointer.locked_pointer); 1287 seat->pointer.locked_pointer = NULL; 1288 update_grabs = true; 1289 } 1290 1291 /* The pointer confinement protocol allows setting a hint to warp the pointer, 1292 * but only when the pointer is locked. 1293 * 1294 * Lock the pointer, set the position hint, unlock, and hope for the best. 1295 */ 1296 struct zwp_locked_pointer_v1 *warp_lock = 1297 zwp_pointer_constraints_v1_lock_pointer(d->pointer_constraints, window->surface, 1298 seat->pointer.wl_pointer, NULL, 1299 ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT); 1300 1301 const wl_fixed_t f_x = wl_fixed_from_double(x / window->pointer_scale.x); 1302 const wl_fixed_t f_y = wl_fixed_from_double(y / window->pointer_scale.y); 1303 zwp_locked_pointer_v1_set_cursor_position_hint(warp_lock, f_x, f_y); 1304 wl_surface_commit(window->surface); 1305 1306 zwp_locked_pointer_v1_destroy(warp_lock); 1307 1308 if (update_grabs) { 1309 Wayland_SeatUpdatePointerGrab(seat); 1310 } 1311 } 1312 1313 /* NOTE: There is a pending warp event under discussion that should replace this when available. 1314 * https://gitlab.freedesktop.org/wayland/wayland/-/merge_requests/340 1315 */ 1316 SDL_SendMouseMotion(0, window->sdlwindow, seat->pointer.sdl_id, false, x, y); 1317 } 1318} 1319 1320static bool Wayland_WarpMouseRelative(SDL_Window *window, float x, float y) 1321{ 1322 SDL_VideoDevice *vd = SDL_GetVideoDevice(); 1323 SDL_VideoData *d = vd->internal; 1324 SDL_WindowData *wind = window->internal; 1325 SDL_WaylandSeat *seat; 1326 1327 if (d->wp_pointer_warp_v1 || d->pointer_constraints) { 1328 wl_list_for_each (seat, &d->seat_list, link) { 1329 if (wind == seat->pointer.focus) { 1330 Wayland_SeatWarpMouse(seat, wind, x, y); 1331 } 1332 } 1333 } else { 1334 return SDL_SetError("wayland: mouse warp failed; compositor lacks support for the required wp_pointer_warp_v1 or zwp_pointer_confinement_v1 protocol"); 1335 } 1336 1337 return true; 1338} 1339 1340static bool Wayland_WarpMouseGlobal(float x, float y) 1341{ 1342 SDL_VideoDevice *vd = SDL_GetVideoDevice(); 1343 SDL_VideoData *d = vd->internal; 1344 SDL_WaylandSeat *seat; 1345 1346 if (d->wp_pointer_warp_v1 || d->pointer_constraints) { 1347 wl_list_for_each (seat, &d->seat_list, link) { 1348 SDL_WindowData *wind = seat->pointer.focus ? seat->pointer.focus : seat->keyboard.focus; 1349 1350 // If the client wants the coordinates warped to within a focused window, just convert the coordinates to relative. 1351 if (wind) { 1352 SDL_Window *window = wind->sdlwindow; 1353 1354 int abs_x, abs_y; 1355 SDL_RelativeToGlobalForWindow(window, window->x, window->y, &abs_x, &abs_y); 1356 1357 const SDL_FPoint p = { x, y }; 1358 const SDL_FRect r = { abs_x, abs_y, window->w, window->h }; 1359 1360 // Try to warp the cursor if the point is within the seat's focused window. 1361 if (SDL_PointInRectFloat(&p, &r)) { 1362 Wayland_SeatWarpMouse(seat, wind, p.x - abs_x, p.y - abs_y); 1363 } 1364 } 1365 } 1366 } else { 1367 return SDL_SetError("wayland: mouse warp failed; compositor lacks support for the required wp_pointer_warp_v1 or zwp_pointer_confinement_v1 protocol"); 1368 } 1369 1370 return true; 1371} 1372 1373static bool Wayland_SetRelativeMouseMode(bool enabled) 1374{ 1375 SDL_VideoDevice *vd = SDL_GetVideoDevice(); 1376 SDL_VideoData *data = vd->internal; 1377 1378 // Relative mode requires both the relative motion and pointer confinement protocols. 1379 if (!data->relative_pointer_manager) { 1380 return SDL_SetError("Failed to enable relative mode: compositor lacks support for the required zwp_relative_pointer_manager_v1 protocol"); 1381 } 1382 if (!data->pointer_constraints) { 1383 return SDL_SetError("Failed to enable relative mode: compositor lacks support for the required zwp_pointer_constraints_v1 protocol"); 1384 } 1385 1386 // Windows have a relative mode flag, so just update the grabs on a state change. 1387 Wayland_DisplayUpdatePointerGrabs(data, NULL); 1388 return true; 1389} 1390 1391/* Wayland doesn't support getting the true global cursor position, but it can 1392 * be faked well enough for what most applications use it for: querying the 1393 * global cursor coordinates and transforming them to the window-relative 1394 * coordinates manually. 1395 * 1396 * The global position is derived by taking the cursor position relative to the 1397 * toplevel window, and offsetting it by the origin of the output the window is 1398 * currently considered to be on. The cursor position and button state when the 1399 * cursor is outside an application window are unknown, but this gives 'correct' 1400 * coordinates when the window has focus, which is good enough for most 1401 * applications. 1402 */ 1403static SDL_MouseButtonFlags SDLCALL Wayland_GetGlobalMouseState(float *x, float *y) 1404{ 1405 const SDL_Mouse *mouse = SDL_GetMouse(); 1406 SDL_MouseButtonFlags result = 0; 1407 1408 // If there is no window with mouse focus, we have no idea what the actual position or button state is. 1409 if (mouse->focus) { 1410 SDL_VideoData *video_data = SDL_GetVideoDevice()->internal; 1411 SDL_WaylandSeat *seat; 1412 int off_x, off_y; 1413 SDL_RelativeToGlobalForWindow(mouse->focus, mouse->focus->x, mouse->focus->y, &off_x, &off_y); 1414 *x = mouse->x + off_x; 1415 *y = mouse->y + off_y; 1416 1417 // Query the buttons from the seats directly, as this may be called from within a hit test handler. 1418 wl_list_for_each (seat, &video_data->seat_list, link) { 1419 result |= seat->pointer.buttons_pressed; 1420 } 1421 } else { 1422 *x = 0.f; 1423 *y = 0.f; 1424 } 1425 1426 return result; 1427} 1428 1429#if 0 // TODO RECONNECT: See waylandvideo.c for more information! 1430static void Wayland_RecreateCursor(SDL_Cursor *cursor, SDL_VideoData *vdata) 1431{ 1432 SDL_CursorData *cdata = cursor->internal; 1433 1434 // Probably not a cursor we own 1435 if (cdata == NULL) { 1436 return; 1437 } 1438 1439 Wayland_FreeCursorData(cdata); 1440 1441 // We're not currently freeing this, so... yolo? 1442 if (cdata->shm_data != NULL) { 1443 void *old_data_pointer = cdata->shm_data; 1444 int stride = cdata->w * 4; 1445 1446 create_buffer_from_shm(cdata, cdata->w, cdata->h, WL_SHM_FORMAT_ARGB8888); 1447 1448 SDL_memcpy(cdata->shm_data, old_data_pointer, stride * cdata->h); 1449 } 1450 cdata->surface = wl_compositor_create_surface(vdata->compositor); 1451 wl_surface_set_user_data(cdata->surface, NULL); 1452} 1453 1454void Wayland_RecreateCursors(void) 1455{ 1456 SDL_Cursor *cursor; 1457 SDL_Mouse *mouse = SDL_GetMouse(); 1458 SDL_VideoData *vdata = SDL_GetVideoDevice()->internal; 1459 1460 if (vdata && vdata->cursor_themes) { 1461 SDL_free(vdata->cursor_themes); 1462 vdata->cursor_themes = NULL; 1463 vdata->num_cursor_themes = 0; 1464 } 1465 1466 if (mouse == NULL) { 1467 return; 1468 } 1469 1470 for (cursor = mouse->cursors; cursor != NULL; cursor = cursor->next) { 1471 Wayland_RecreateCursor(cursor, vdata); 1472 } 1473 if (mouse->def_cursor) { 1474 Wayland_RecreateCursor(mouse->def_cursor, vdata); 1475 } 1476 if (mouse->cur_cursor) { 1477 Wayland_RecreateCursor(mouse->cur_cursor, vdata); 1478 if (mouse->cursor_visible) { 1479 Wayland_ShowCursor(mouse->cur_cursor); 1480 } 1481 } 1482} 1483#endif // 0 1484 1485void Wayland_InitMouse(SDL_VideoData *data) 1486{ 1487 SDL_Mouse *mouse = SDL_GetMouse(); 1488 1489 mouse->CreateCursor = Wayland_CreateCursor; 1490 mouse->CreateAnimatedCursor = Wayland_CreateAnimatedCursor; 1491 mouse->CreateSystemCursor = Wayland_CreateSystemCursor; 1492 mouse->ShowCursor = Wayland_ShowCursor; 1493 mouse->FreeCursor = Wayland_FreeCursor; 1494 mouse->WarpMouse = Wayland_WarpMouseRelative; 1495 mouse->WarpMouseGlobal = Wayland_WarpMouseGlobal; 1496 mouse->SetRelativeMouseMode = Wayland_SetRelativeMouseMode; 1497 mouse->GetGlobalMouseState = Wayland_GetGlobalMouseState; 1498 1499 if (!Wayland_StartCursorThread(data)) { 1500 SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "wayland: Failed to start cursor animation event thread"); 1501 } 1502 1503 SDL_HitTestResult r = SDL_HITTEST_NORMAL; 1504 while (r <= SDL_HITTEST_RESIZE_LEFT) { 1505 switch (r) { 1506 case SDL_HITTEST_NORMAL: 1507 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_DEFAULT); 1508 break; 1509 case SDL_HITTEST_DRAGGABLE: 1510 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_DEFAULT); 1511 break; 1512 case SDL_HITTEST_RESIZE_TOPLEFT: 1513 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_NW_RESIZE); 1514 break; 1515 case SDL_HITTEST_RESIZE_TOP: 1516 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_N_RESIZE); 1517 break; 1518 case SDL_HITTEST_RESIZE_TOPRIGHT: 1519 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_NE_RESIZE); 1520 break; 1521 case SDL_HITTEST_RESIZE_RIGHT: 1522 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_E_RESIZE); 1523 break; 1524 case SDL_HITTEST_RESIZE_BOTTOMRIGHT: 1525 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_SE_RESIZE); 1526 break; 1527 case SDL_HITTEST_RESIZE_BOTTOM: 1528 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_S_RESIZE); 1529 break; 1530 case SDL_HITTEST_RESIZE_BOTTOMLEFT: 1531 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_SW_RESIZE); 1532 break; 1533 case SDL_HITTEST_RESIZE_LEFT: 1534 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_W_RESIZE); 1535 break; 1536 } 1537 r++; 1538 } 1539 1540#ifdef SDL_USE_LIBDBUS 1541 SDL_VideoDevice *vd = SDL_GetVideoDevice(); 1542 SDL_VideoData *d = vd->internal; 1543 1544 /* The D-Bus cursor properties are only needed when manually loading themes and system cursors. 1545 * If the cursor shape protocol is present, the compositor will handle it internally. 1546 */ 1547 if (!d->cursor_shape_manager) { 1548 Wayland_DBusInitCursorProperties(d); 1549 } 1550#endif 1551 1552 SDL_SetDefaultCursor(Wayland_CreateDefaultCursor()); 1553} 1554 1555void Wayland_FiniMouse(SDL_VideoData *data) 1556{ 1557 for (int i = 0; i < SDL_arraysize(sys_cursors); i++) { 1558 Wayland_FreeCursor(sys_cursors[i]); 1559 sys_cursors[i] = NULL; 1560 } 1561 1562 Wayland_DestroyCursorThread(data); 1563 Wayland_FreeCursorThemes(data); 1564 1565#ifdef SDL_USE_LIBDBUS 1566 Wayland_DBusFinishCursorProperties(); 1567#endif 1568} 1569 1570void Wayland_SeatResetCursor(SDL_WaylandSeat *seat) 1571{ 1572 Wayland_CursorStateResetCursor(&seat->pointer.cursor_state); 1573} 1574 1575void Wayland_SeatSetDefaultCursor(SDL_WaylandSeat *seat) 1576{ 1577 SDL_Mouse *mouse = SDL_GetMouse(); 1578 SDL_WindowData *pointer_focus = seat->pointer.focus; 1579 const Wayland_PointerObject obj = { 1580 .wl_pointer = seat->pointer.wl_pointer, 1581 .is_pointer = true 1582 }; 1583 1584 Wayland_CursorStateSetCursor(&seat->pointer.cursor_state, &obj, pointer_focus, seat->pointer.enter_serial, mouse->def_cursor); 1585} 1586 1587void Wayland_SeatUpdatePointerCursor(SDL_WaylandSeat *seat) 1588{ 1589 SDL_Mouse *mouse = SDL_GetMouse(); 1590 SDL_WindowData *pointer_focus = seat->pointer.focus; 1591 const Wayland_PointerObject obj = { 1592 .wl_pointer = seat->pointer.wl_pointer, 1593 .is_pointer = true 1594 }; 1595 1596 if (pointer_focus) { 1597 if (mouse->cursor_visible) { 1598 if (!seat->pointer.relative_pointer || !mouse->relative_mode_hide_cursor) { 1599 const SDL_HitTestResult rc = pointer_focus->hit_test_result; 1600 1601 if (seat->pointer.relative_pointer || rc == SDL_HITTEST_NORMAL || rc == SDL_HITTEST_DRAGGABLE) { 1602 Wayland_CursorStateSetCursor(&seat->pointer.cursor_state, &obj, pointer_focus, seat->pointer.enter_serial, mouse->cur_cursor); 1603 } else { 1604 Wayland_CursorStateSetCursor(&seat->pointer.cursor_state, &obj, pointer_focus, seat->pointer.enter_serial, sys_cursors[rc]); 1605 } 1606 } else { 1607 // Hide the cursor in relative mode, unless requested otherwise by the hint. 1608 Wayland_CursorStateSetCursor(&seat->pointer.cursor_state, &obj, pointer_focus, seat->pointer.enter_serial, NULL); 1609 } 1610 } else { 1611 Wayland_CursorStateSetCursor(&seat->pointer.cursor_state, &obj, pointer_focus, seat->pointer.enter_serial, NULL); 1612 } 1613 } else { 1614 Wayland_CursorStateResetCursor(&seat->pointer.cursor_state); 1615 } 1616} 1617 1618void Wayland_TabletToolUpdateCursor(SDL_WaylandPenTool *tool) 1619{ 1620 SDL_Mouse *mouse = SDL_GetMouse(); 1621 SDL_WindowData *tool_focus = tool->focus; 1622 const Wayland_PointerObject obj = { 1623 .wl_tool = tool->wltool, 1624 .is_pointer = false 1625 }; 1626 1627 if (tool_focus) { 1628 if (mouse->cursor_visible) { 1629 // Relative mode is only relevant if the tool sends pointer events. 1630 const bool relative = mouse->pen_mouse_events && (tool_focus->sdlwindow->flags & SDL_WINDOW_MOUSE_RELATIVE_MODE); 1631 1632 if (!relative || !mouse->relative_mode_hide_cursor) { 1633 Wayland_CursorStateSetCursor(&tool->cursor_state, &obj, tool_focus, tool->proximity_serial, mouse->cur_cursor); 1634 } else { 1635 // Hide the cursor in relative mode, unless requested otherwise by the hint. 1636 Wayland_CursorStateSetCursor(&tool->cursor_state, &obj, tool_focus, tool->proximity_serial, NULL); 1637 } 1638 } else { 1639 Wayland_CursorStateSetCursor(&tool->cursor_state, &obj, tool_focus, tool->proximity_serial, NULL); 1640 } 1641 } else { 1642 Wayland_CursorStateResetCursor(&tool->cursor_state); 1643 } 1644} 1645 1646#endif // SDL_VIDEO_DRIVER_WAYLAND 1647
[FILE END]
(C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.