Atlas - SDL_waylandmouse.c
Home / ext / SDL / src / video / wayland Lines: 1 | Size: 58169 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 * The dimensions are scaled by the pointer scale, so custom cursors will be scaled relative to the window size. 1108 */ 1109 state->scale = viddata->viewporter && focus ? SDL_min(focus->pointer_scale.x, focus->pointer_scale.y) : 1.0; 1110 dst_width = SDL_max((int)SDL_lround((double)cursor_data->cursor_data.custom.width / state->scale), 1); 1111 dst_height = SDL_max((int)SDL_lround((double)cursor_data->cursor_data.custom.height / state->scale), 1); 1112 hot_x = (int)SDL_lround((double)cursor_data->cursor_data.custom.hot_x / state->scale); 1113 hot_y = (int)SDL_lround((double)cursor_data->cursor_data.custom.hot_y / state->scale); 1114 } 1115 1116 state->current_cursor = cursor_data; 1117 1118 if (!state->surface) { 1119 if (cursor_thread_context.compositor_wrapper) { 1120 state->surface = wl_compositor_create_surface((struct wl_compositor *)cursor_thread_context.compositor_wrapper); 1121 } else { 1122 state->surface = wl_compositor_create_surface(viddata->compositor); 1123 } 1124 } 1125 1126 struct wl_buffer *buffer = Wayland_CursorStateGetFrame(state, 0); 1127 wl_surface_attach(state->surface, buffer, 0, 0); 1128 state->current_frame = 0; 1129 1130 if (state->scale != 1.0) { 1131 if (!state->viewport) { 1132 state->viewport = wp_viewporter_get_viewport(viddata->viewporter, state->surface); 1133 } 1134 1135 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)); 1136 wp_viewport_set_destination(state->viewport, dst_width, dst_height); 1137 } else if (state->viewport) { 1138 wp_viewport_destroy(state->viewport); 1139 state->viewport = NULL; 1140 } 1141 1142 if (obj->is_pointer) { 1143 wl_pointer_set_cursor(obj->wl_pointer, serial, state->surface, hot_x, hot_y); 1144 } else { 1145 zwp_tablet_tool_v2_set_cursor(obj->wl_tool, serial, state->surface, hot_x, hot_y); 1146 } 1147 1148 if (wl_surface_get_version(state->surface) >= WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION) { 1149 wl_surface_damage_buffer(state->surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32); 1150 } else { 1151 wl_surface_damage(state->surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32); 1152 } 1153 1154 // If more than one frame is available, create a frame callback to run the animation. 1155 if (cursor_data->num_frames > 1) { 1156 Wayland_CursorStateResetAnimation(state, false); 1157 Wayland_CursorStateSetFrameCallback(state, state); 1158 } 1159 1160 wl_surface_commit(state->surface); 1161 } else { 1162 Wayland_CursorStateDestroyFrameCallback(state); 1163 state->current_cursor = NULL; 1164 1165 if (state->surface) { 1166 wl_surface_attach(state->surface, NULL, 0, 0); 1167 wl_surface_commit(state->surface); 1168 } 1169 1170 if (obj->is_pointer) { 1171 wl_pointer_set_cursor(obj->wl_pointer, serial, NULL, 0, 0); 1172 } else { 1173 zwp_tablet_tool_v2_set_cursor(obj->wl_tool, serial, NULL, 0, 0); 1174 } 1175 } 1176} 1177 1178static void Wayland_CursorStateResetCursor(SDL_WaylandCursorState *state) 1179{ 1180 // Stop the frame callback and set the reset status. 1181 Wayland_CursorStateDestroyFrameCallback(state); 1182 state->current_frame = -1; 1183} 1184 1185void Wayland_DisplayUpdatePointerFocusedScale(SDL_WindowData *updated_window) 1186{ 1187 SDL_VideoData *viddata = updated_window->waylandData; 1188 SDL_WaylandSeat *seat; 1189 const double new_scale = SDL_min(updated_window->pointer_scale.x, updated_window->pointer_scale.y); 1190 1191 wl_list_for_each (seat, &viddata->seat_list, link) { 1192 if (seat->pointer.focus == updated_window) { 1193 SDL_WaylandCursorState *state = &seat->pointer.cursor_state; 1194 if (state->current_cursor && !state->current_cursor->is_system_cursor && state->scale != new_scale) { 1195 Wayland_CursorStateResetCursor(state); 1196 Wayland_SeatUpdatePointerCursor(seat); 1197 } 1198 } 1199 1200 SDL_WaylandPenTool *tool; 1201 wl_list_for_each (tool, &seat->tablet.tool_list, link) { 1202 if (tool->focus == updated_window) { 1203 SDL_WaylandCursorState *state = &tool->cursor_state; 1204 if (state->current_cursor && !state->current_cursor->is_system_cursor && state->scale != new_scale) { 1205 Wayland_CursorStateResetCursor(&tool->cursor_state); 1206 Wayland_TabletToolUpdateCursor(tool); 1207 } 1208 } 1209 } 1210 } 1211} 1212 1213static bool Wayland_ShowCursor(SDL_Cursor *cursor) 1214{ 1215 SDL_VideoDevice *vd = SDL_GetVideoDevice(); 1216 SDL_VideoData *d = vd->internal; 1217 SDL_Mouse *mouse = SDL_GetMouse(); 1218 SDL_WaylandSeat *seat; 1219 Wayland_PointerObject obj; 1220 1221 wl_list_for_each (seat, &d->seat_list, link) { 1222 if (seat->pointer.wl_pointer) { 1223 obj.wl_pointer = seat->pointer.wl_pointer; 1224 obj.is_pointer = true; 1225 if (mouse->focus && mouse->focus->internal == seat->pointer.focus) { 1226 Wayland_CursorStateSetCursor(&seat->pointer.cursor_state, &obj, seat->pointer.focus, seat->pointer.enter_serial, cursor); 1227 } else if (!seat->pointer.focus) { 1228 Wayland_CursorStateResetCursor(&seat->pointer.cursor_state); 1229 } 1230 } 1231 1232 SDL_WaylandPenTool *tool; 1233 wl_list_for_each(tool, &seat->tablet.tool_list, link) { 1234 obj.wl_tool = tool->wltool; 1235 obj.is_pointer = false; 1236 1237 /* The current cursor is explicitly set on tablet tools, as there may be no pointer device, or 1238 * the pointer may not have focus, which would instead cause the default cursor to be set. 1239 */ 1240 if (tool->focus && (!mouse->focus || mouse->focus->internal == tool->focus)) { 1241 Wayland_CursorStateSetCursor(&tool->cursor_state, &obj, tool->focus, tool->proximity_serial, mouse->cur_cursor); 1242 } else if (!tool->focus) { 1243 Wayland_CursorStateResetCursor(&tool->cursor_state); 1244 } 1245 } 1246 } 1247 1248 return true; 1249} 1250 1251void Wayland_SeatWarpMouse(SDL_WaylandSeat *seat, SDL_WindowData *window, float x, float y) 1252{ 1253 SDL_VideoDevice *vd = SDL_GetVideoDevice(); 1254 SDL_VideoData *d = vd->internal; 1255 1256 if (seat->pointer.wl_pointer) { 1257 if (d->wp_pointer_warp_v1) { 1258 // It's a protocol error to warp the pointer outside of the surface, so clamp the position. 1259 const wl_fixed_t f_x = wl_fixed_from_double(SDL_clamp(x / window->pointer_scale.x, 0, window->current.logical_width)); 1260 const wl_fixed_t f_y = wl_fixed_from_double(SDL_clamp(y / window->pointer_scale.y, 0, window->current.logical_height)); 1261 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); 1262 } else { 1263 bool update_grabs = false; 1264 1265 // Pointers can only have one confinement type active on a surface at one time. 1266 if (seat->pointer.confined_pointer) { 1267 zwp_confined_pointer_v1_destroy(seat->pointer.confined_pointer); 1268 seat->pointer.confined_pointer = NULL; 1269 update_grabs = true; 1270 } 1271 if (seat->pointer.locked_pointer) { 1272 zwp_locked_pointer_v1_destroy(seat->pointer.locked_pointer); 1273 seat->pointer.locked_pointer = NULL; 1274 update_grabs = true; 1275 } 1276 1277 /* The pointer confinement protocol allows setting a hint to warp the pointer, 1278 * but only when the pointer is locked. 1279 * 1280 * Lock the pointer, set the position hint, unlock, and hope for the best. 1281 */ 1282 struct zwp_locked_pointer_v1 *warp_lock = 1283 zwp_pointer_constraints_v1_lock_pointer(d->pointer_constraints, window->surface, 1284 seat->pointer.wl_pointer, NULL, 1285 ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT); 1286 1287 const wl_fixed_t f_x = wl_fixed_from_double(x / window->pointer_scale.x); 1288 const wl_fixed_t f_y = wl_fixed_from_double(y / window->pointer_scale.y); 1289 zwp_locked_pointer_v1_set_cursor_position_hint(warp_lock, f_x, f_y); 1290 wl_surface_commit(window->surface); 1291 1292 zwp_locked_pointer_v1_destroy(warp_lock); 1293 1294 if (update_grabs) { 1295 Wayland_SeatUpdatePointerGrab(seat); 1296 } 1297 } 1298 1299 /* NOTE: There is a pending warp event under discussion that should replace this when available. 1300 * https://gitlab.freedesktop.org/wayland/wayland/-/merge_requests/340 1301 */ 1302 SDL_SendMouseMotion(0, window->sdlwindow, seat->pointer.sdl_id, false, x, y); 1303 } 1304} 1305 1306static bool Wayland_WarpMouseRelative(SDL_Window *window, float x, float y) 1307{ 1308 SDL_VideoDevice *vd = SDL_GetVideoDevice(); 1309 SDL_VideoData *d = vd->internal; 1310 SDL_WindowData *wind = window->internal; 1311 SDL_WaylandSeat *seat; 1312 1313 if (d->wp_pointer_warp_v1 || d->pointer_constraints) { 1314 wl_list_for_each (seat, &d->seat_list, link) { 1315 if (wind == seat->pointer.focus) { 1316 Wayland_SeatWarpMouse(seat, wind, x, y); 1317 } 1318 } 1319 } else { 1320 return SDL_SetError("wayland: mouse warp failed; compositor lacks support for the required wp_pointer_warp_v1 or zwp_pointer_confinement_v1 protocol"); 1321 } 1322 1323 return true; 1324} 1325 1326static bool Wayland_WarpMouseGlobal(float x, float y) 1327{ 1328 SDL_VideoDevice *vd = SDL_GetVideoDevice(); 1329 SDL_VideoData *d = vd->internal; 1330 SDL_WaylandSeat *seat; 1331 1332 if (d->wp_pointer_warp_v1 || d->pointer_constraints) { 1333 wl_list_for_each (seat, &d->seat_list, link) { 1334 SDL_WindowData *wind = seat->pointer.focus ? seat->pointer.focus : seat->keyboard.focus; 1335 1336 // If the client wants the coordinates warped to within a focused window, just convert the coordinates to relative. 1337 if (wind) { 1338 SDL_Window *window = wind->sdlwindow; 1339 1340 int abs_x, abs_y; 1341 SDL_RelativeToGlobalForWindow(window, window->x, window->y, &abs_x, &abs_y); 1342 1343 const SDL_FPoint p = { x, y }; 1344 const SDL_FRect r = { abs_x, abs_y, window->w, window->h }; 1345 1346 // Try to warp the cursor if the point is within the seat's focused window. 1347 if (SDL_PointInRectFloat(&p, &r)) { 1348 Wayland_SeatWarpMouse(seat, wind, p.x - abs_x, p.y - abs_y); 1349 } 1350 } 1351 } 1352 } else { 1353 return SDL_SetError("wayland: mouse warp failed; compositor lacks support for the required wp_pointer_warp_v1 or zwp_pointer_confinement_v1 protocol"); 1354 } 1355 1356 return true; 1357} 1358 1359static bool Wayland_SetRelativeMouseMode(bool enabled) 1360{ 1361 SDL_VideoDevice *vd = SDL_GetVideoDevice(); 1362 SDL_VideoData *data = vd->internal; 1363 1364 // Relative mode requires both the relative motion and pointer confinement protocols. 1365 if (!data->relative_pointer_manager) { 1366 return SDL_SetError("Failed to enable relative mode: compositor lacks support for the required zwp_relative_pointer_manager_v1 protocol"); 1367 } 1368 if (!data->pointer_constraints) { 1369 return SDL_SetError("Failed to enable relative mode: compositor lacks support for the required zwp_pointer_constraints_v1 protocol"); 1370 } 1371 1372 // Windows have a relative mode flag, so just update the grabs on a state change. 1373 Wayland_DisplayUpdatePointerGrabs(data, NULL); 1374 return true; 1375} 1376 1377/* Wayland doesn't support getting the true global cursor position, but it can 1378 * be faked well enough for what most applications use it for: querying the 1379 * global cursor coordinates and transforming them to the window-relative 1380 * coordinates manually. 1381 * 1382 * The global position is derived by taking the cursor position relative to the 1383 * toplevel window, and offsetting it by the origin of the output the window is 1384 * currently considered to be on. The cursor position and button state when the 1385 * cursor is outside an application window are unknown, but this gives 'correct' 1386 * coordinates when the window has focus, which is good enough for most 1387 * applications. 1388 */ 1389static SDL_MouseButtonFlags SDLCALL Wayland_GetGlobalMouseState(float *x, float *y) 1390{ 1391 const SDL_Mouse *mouse = SDL_GetMouse(); 1392 SDL_MouseButtonFlags result = 0; 1393 1394 // If there is no window with mouse focus, we have no idea what the actual position or button state is. 1395 if (mouse->focus) { 1396 SDL_VideoData *video_data = SDL_GetVideoDevice()->internal; 1397 SDL_WaylandSeat *seat; 1398 int off_x, off_y; 1399 SDL_RelativeToGlobalForWindow(mouse->focus, mouse->focus->x, mouse->focus->y, &off_x, &off_y); 1400 *x = mouse->x + off_x; 1401 *y = mouse->y + off_y; 1402 1403 // Query the buttons from the seats directly, as this may be called from within a hit test handler. 1404 wl_list_for_each (seat, &video_data->seat_list, link) { 1405 result |= seat->pointer.buttons_pressed; 1406 } 1407 } else { 1408 *x = 0.f; 1409 *y = 0.f; 1410 } 1411 1412 return result; 1413} 1414 1415#if 0 // TODO RECONNECT: See waylandvideo.c for more information! 1416static void Wayland_RecreateCursor(SDL_Cursor *cursor, SDL_VideoData *vdata) 1417{ 1418 SDL_CursorData *cdata = cursor->internal; 1419 1420 // Probably not a cursor we own 1421 if (cdata == NULL) { 1422 return; 1423 } 1424 1425 Wayland_FreeCursorData(cdata); 1426 1427 // We're not currently freeing this, so... yolo? 1428 if (cdata->shm_data != NULL) { 1429 void *old_data_pointer = cdata->shm_data; 1430 int stride = cdata->w * 4; 1431 1432 create_buffer_from_shm(cdata, cdata->w, cdata->h, WL_SHM_FORMAT_ARGB8888); 1433 1434 SDL_memcpy(cdata->shm_data, old_data_pointer, stride * cdata->h); 1435 } 1436 cdata->surface = wl_compositor_create_surface(vdata->compositor); 1437 wl_surface_set_user_data(cdata->surface, NULL); 1438} 1439 1440void Wayland_RecreateCursors(void) 1441{ 1442 SDL_Cursor *cursor; 1443 SDL_Mouse *mouse = SDL_GetMouse(); 1444 SDL_VideoData *vdata = SDL_GetVideoDevice()->internal; 1445 1446 if (vdata && vdata->cursor_themes) { 1447 SDL_free(vdata->cursor_themes); 1448 vdata->cursor_themes = NULL; 1449 vdata->num_cursor_themes = 0; 1450 } 1451 1452 if (mouse == NULL) { 1453 return; 1454 } 1455 1456 for (cursor = mouse->cursors; cursor != NULL; cursor = cursor->next) { 1457 Wayland_RecreateCursor(cursor, vdata); 1458 } 1459 if (mouse->def_cursor) { 1460 Wayland_RecreateCursor(mouse->def_cursor, vdata); 1461 } 1462 if (mouse->cur_cursor) { 1463 Wayland_RecreateCursor(mouse->cur_cursor, vdata); 1464 if (mouse->cursor_visible) { 1465 Wayland_ShowCursor(mouse->cur_cursor); 1466 } 1467 } 1468} 1469#endif // 0 1470 1471void Wayland_InitMouse(SDL_VideoData *data) 1472{ 1473 SDL_Mouse *mouse = SDL_GetMouse(); 1474 1475 mouse->CreateCursor = Wayland_CreateCursor; 1476 mouse->CreateAnimatedCursor = Wayland_CreateAnimatedCursor; 1477 mouse->CreateSystemCursor = Wayland_CreateSystemCursor; 1478 mouse->ShowCursor = Wayland_ShowCursor; 1479 mouse->FreeCursor = Wayland_FreeCursor; 1480 mouse->WarpMouse = Wayland_WarpMouseRelative; 1481 mouse->WarpMouseGlobal = Wayland_WarpMouseGlobal; 1482 mouse->SetRelativeMouseMode = Wayland_SetRelativeMouseMode; 1483 mouse->GetGlobalMouseState = Wayland_GetGlobalMouseState; 1484 1485 if (!Wayland_StartCursorThread(data)) { 1486 SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "wayland: Failed to start cursor animation event thread"); 1487 } 1488 1489 SDL_HitTestResult r = SDL_HITTEST_NORMAL; 1490 while (r <= SDL_HITTEST_RESIZE_LEFT) { 1491 switch (r) { 1492 case SDL_HITTEST_NORMAL: 1493 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_DEFAULT); 1494 break; 1495 case SDL_HITTEST_DRAGGABLE: 1496 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_DEFAULT); 1497 break; 1498 case SDL_HITTEST_RESIZE_TOPLEFT: 1499 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_NW_RESIZE); 1500 break; 1501 case SDL_HITTEST_RESIZE_TOP: 1502 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_N_RESIZE); 1503 break; 1504 case SDL_HITTEST_RESIZE_TOPRIGHT: 1505 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_NE_RESIZE); 1506 break; 1507 case SDL_HITTEST_RESIZE_RIGHT: 1508 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_E_RESIZE); 1509 break; 1510 case SDL_HITTEST_RESIZE_BOTTOMRIGHT: 1511 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_SE_RESIZE); 1512 break; 1513 case SDL_HITTEST_RESIZE_BOTTOM: 1514 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_S_RESIZE); 1515 break; 1516 case SDL_HITTEST_RESIZE_BOTTOMLEFT: 1517 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_SW_RESIZE); 1518 break; 1519 case SDL_HITTEST_RESIZE_LEFT: 1520 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_W_RESIZE); 1521 break; 1522 } 1523 r++; 1524 } 1525 1526#ifdef SDL_USE_LIBDBUS 1527 SDL_VideoDevice *vd = SDL_GetVideoDevice(); 1528 SDL_VideoData *d = vd->internal; 1529 1530 /* The D-Bus cursor properties are only needed when manually loading themes and system cursors. 1531 * If the cursor shape protocol is present, the compositor will handle it internally. 1532 */ 1533 if (!d->cursor_shape_manager) { 1534 Wayland_DBusInitCursorProperties(d); 1535 } 1536#endif 1537 1538 SDL_SetDefaultCursor(Wayland_CreateDefaultCursor()); 1539} 1540 1541void Wayland_FiniMouse(SDL_VideoData *data) 1542{ 1543 for (int i = 0; i < SDL_arraysize(sys_cursors); i++) { 1544 Wayland_FreeCursor(sys_cursors[i]); 1545 sys_cursors[i] = NULL; 1546 } 1547 1548 Wayland_DestroyCursorThread(data); 1549 Wayland_FreeCursorThemes(data); 1550 1551#ifdef SDL_USE_LIBDBUS 1552 Wayland_DBusFinishCursorProperties(); 1553#endif 1554} 1555 1556void Wayland_SeatResetCursor(SDL_WaylandSeat *seat) 1557{ 1558 Wayland_CursorStateResetCursor(&seat->pointer.cursor_state); 1559} 1560 1561void Wayland_SeatSetDefaultCursor(SDL_WaylandSeat *seat) 1562{ 1563 SDL_Mouse *mouse = SDL_GetMouse(); 1564 SDL_WindowData *pointer_focus = seat->pointer.focus; 1565 const Wayland_PointerObject obj = { 1566 .wl_pointer = seat->pointer.wl_pointer, 1567 .is_pointer = true 1568 }; 1569 1570 Wayland_CursorStateSetCursor(&seat->pointer.cursor_state, &obj, pointer_focus, seat->pointer.enter_serial, mouse->def_cursor); 1571} 1572 1573void Wayland_SeatUpdatePointerCursor(SDL_WaylandSeat *seat) 1574{ 1575 SDL_Mouse *mouse = SDL_GetMouse(); 1576 SDL_WindowData *pointer_focus = seat->pointer.focus; 1577 const Wayland_PointerObject obj = { 1578 .wl_pointer = seat->pointer.wl_pointer, 1579 .is_pointer = true 1580 }; 1581 1582 if (pointer_focus) { 1583 if (mouse->cursor_visible) { 1584 if (!seat->pointer.relative_pointer || !mouse->relative_mode_hide_cursor) { 1585 const SDL_HitTestResult rc = pointer_focus->hit_test_result; 1586 1587 if (seat->pointer.relative_pointer || rc == SDL_HITTEST_NORMAL || rc == SDL_HITTEST_DRAGGABLE) { 1588 Wayland_CursorStateSetCursor(&seat->pointer.cursor_state, &obj, pointer_focus, seat->pointer.enter_serial, mouse->cur_cursor); 1589 } else { 1590 Wayland_CursorStateSetCursor(&seat->pointer.cursor_state, &obj, pointer_focus, seat->pointer.enter_serial, sys_cursors[rc]); 1591 } 1592 } else { 1593 // Hide the cursor in relative mode, unless requested otherwise by the hint. 1594 Wayland_CursorStateSetCursor(&seat->pointer.cursor_state, &obj, pointer_focus, seat->pointer.enter_serial, NULL); 1595 } 1596 } else { 1597 Wayland_CursorStateSetCursor(&seat->pointer.cursor_state, &obj, pointer_focus, seat->pointer.enter_serial, NULL); 1598 } 1599 } else { 1600 Wayland_CursorStateResetCursor(&seat->pointer.cursor_state); 1601 } 1602} 1603 1604void Wayland_TabletToolUpdateCursor(SDL_WaylandPenTool *tool) 1605{ 1606 SDL_Mouse *mouse = SDL_GetMouse(); 1607 SDL_WindowData *tool_focus = tool->focus; 1608 const Wayland_PointerObject obj = { 1609 .wl_tool = tool->wltool, 1610 .is_pointer = false 1611 }; 1612 1613 if (tool_focus) { 1614 if (mouse->cursor_visible) { 1615 // Relative mode is only relevant if the tool sends pointer events. 1616 const bool relative = mouse->pen_mouse_events && (tool_focus->sdlwindow->flags & SDL_WINDOW_MOUSE_RELATIVE_MODE); 1617 1618 if (!relative || !mouse->relative_mode_hide_cursor) { 1619 Wayland_CursorStateSetCursor(&tool->cursor_state, &obj, tool_focus, tool->proximity_serial, mouse->cur_cursor); 1620 } else { 1621 // Hide the cursor in relative mode, unless requested otherwise by the hint. 1622 Wayland_CursorStateSetCursor(&tool->cursor_state, &obj, tool_focus, tool->proximity_serial, NULL); 1623 } 1624 } else { 1625 Wayland_CursorStateSetCursor(&tool->cursor_state, &obj, tool_focus, tool->proximity_serial, NULL); 1626 } 1627 } else { 1628 Wayland_CursorStateResetCursor(&tool->cursor_state); 1629 } 1630} 1631 1632#endif // SDL_VIDEO_DRIVER_WAYLAND 1633[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.