Atlas - SDL_waylandmouse.c
Home / ext / SDL / src / video / wayland Lines: 1 | Size: 54958 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 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.current_cursor == d) { 901 Wayland_CursorStateDestroyFrameCallback(&seat->pointer.cursor_state); 902 903 if (seat->pointer.cursor_state.surface) { 904 wl_surface_attach(seat->pointer.cursor_state.surface, NULL, 0, 0); 905 } 906 907 seat->pointer.current_cursor = NULL; 908 } 909 } 910 911 if (d->is_system_cursor) { 912 Wayland_CachedSystemCursor *c, *temp; 913 wl_list_for_each_safe(c, temp, &d->cursor_data.system.cursor_buffer_cache, node) { 914 SDL_free(c); 915 } 916 } else { 917 for (int i = 0; i < d->num_frames * d->cursor_data.custom.images_per_frame; ++i) { 918 if (d->cursor_data.custom.images[i].buffer) { 919 wl_buffer_destroy(d->cursor_data.custom.images[i].buffer); 920 } 921 } 922 } 923 924 SDL_free(d->frame_durations_ms); 925} 926 927static void Wayland_FreeCursor(SDL_Cursor *cursor) 928{ 929 if (!cursor) { 930 return; 931 } 932 933 // Probably not a cursor we own 934 if (!cursor->internal) { 935 return; 936 } 937 938 Wayland_FreeCursorData(cursor->internal); 939 940 SDL_free(cursor->internal); 941 SDL_free(cursor); 942} 943 944static enum wp_cursor_shape_device_v1_shape Wayland_GetSystemCursorShape(SDL_SystemCursor id) 945{ 946 Uint32 shape; 947 948 switch (id) { 949 case SDL_SYSTEM_CURSOR_DEFAULT: 950 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT; 951 break; 952 case SDL_SYSTEM_CURSOR_TEXT: 953 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT; 954 break; 955 case SDL_SYSTEM_CURSOR_WAIT: 956 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_WAIT; 957 break; 958 case SDL_SYSTEM_CURSOR_CROSSHAIR: 959 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR; 960 break; 961 case SDL_SYSTEM_CURSOR_PROGRESS: 962 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_PROGRESS; 963 break; 964 case SDL_SYSTEM_CURSOR_NWSE_RESIZE: 965 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NWSE_RESIZE; 966 break; 967 case SDL_SYSTEM_CURSOR_NESW_RESIZE: 968 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NESW_RESIZE; 969 break; 970 case SDL_SYSTEM_CURSOR_EW_RESIZE: 971 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_EW_RESIZE; 972 break; 973 case SDL_SYSTEM_CURSOR_NS_RESIZE: 974 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NS_RESIZE; 975 break; 976 case SDL_SYSTEM_CURSOR_MOVE: 977 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_SCROLL; 978 break; 979 case SDL_SYSTEM_CURSOR_NOT_ALLOWED: 980 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NOT_ALLOWED; 981 break; 982 case SDL_SYSTEM_CURSOR_POINTER: 983 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER; 984 break; 985 case SDL_SYSTEM_CURSOR_NW_RESIZE: 986 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NW_RESIZE; 987 break; 988 case SDL_SYSTEM_CURSOR_N_RESIZE: 989 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_N_RESIZE; 990 break; 991 case SDL_SYSTEM_CURSOR_NE_RESIZE: 992 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NE_RESIZE; 993 break; 994 case SDL_SYSTEM_CURSOR_E_RESIZE: 995 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_E_RESIZE; 996 break; 997 case SDL_SYSTEM_CURSOR_SE_RESIZE: 998 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SE_RESIZE; 999 break; 1000 case SDL_SYSTEM_CURSOR_S_RESIZE: 1001 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_S_RESIZE; 1002 break; 1003 case SDL_SYSTEM_CURSOR_SW_RESIZE: 1004 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SW_RESIZE; 1005 break; 1006 case SDL_SYSTEM_CURSOR_W_RESIZE: 1007 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_W_RESIZE; 1008 break; 1009 default: 1010 SDL_assert(0); // Should never be here... 1011 shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT; 1012 } 1013 1014 return shape; 1015} 1016 1017typedef struct Wayland_PointerObject 1018{ 1019 union 1020 { 1021 struct wl_pointer *wl_pointer; 1022 struct zwp_tablet_tool_v2 *wl_tool; 1023 }; 1024 1025 bool is_pointer; 1026} Wayland_PointerObject; 1027 1028static void Wayland_CursorStateSetCursor(SDL_WaylandCursorState *state, const Wayland_PointerObject *obj, SDL_WindowData *focus, Uint32 serial, SDL_Cursor *cursor) 1029{ 1030 SDL_VideoData *viddata = SDL_GetVideoDevice()->internal; 1031 SDL_CursorData *cursor_data = cursor ? cursor->internal : NULL; 1032 int dst_width = 0; 1033 int dst_height = 0; 1034 int hot_x; 1035 int hot_y; 1036 1037 // Stop the frame callback for old animated cursors. 1038 if (cursor_data != state->current_cursor) { 1039 Wayland_CursorStateDestroyFrameCallback(state); 1040 } 1041 1042 if (cursor) { 1043 if (cursor_data == state->current_cursor) { 1044 // Restart the animation sequence if the cursor didn't change. 1045 if (cursor_data->num_frames > 1) { 1046 Wayland_CursorStateResetAnimation(state, true); 1047 } 1048 1049 return; 1050 } 1051 1052 if (cursor_data->is_system_cursor) { 1053 // If the cursor shape protocol is supported, the compositor will draw nicely scaled cursors for us, so nothing more to do. 1054 if (state->cursor_shape) { 1055 // Don't need the surface or viewport if using the cursor shape protocol. 1056 if (state->surface) { 1057 wl_surface_attach(state->surface, NULL, 0, 0); 1058 wl_surface_commit(state->surface); 1059 1060 if (obj->is_pointer) { 1061 wl_pointer_set_cursor(obj->wl_pointer, serial, NULL, 0, 0); 1062 } else { 1063 zwp_tablet_tool_v2_set_cursor(obj->wl_tool, serial, NULL, 0, 0); 1064 } 1065 1066 if (state->viewport) { 1067 wp_viewport_destroy(state->viewport); 1068 state->viewport = NULL; 1069 } 1070 1071 wl_surface_destroy(state->surface); 1072 state->surface = NULL; 1073 } 1074 1075 const enum wp_cursor_shape_device_v1_shape shape = Wayland_GetSystemCursorShape(cursor_data->cursor_data.system.id); 1076 wp_cursor_shape_device_v1_set_shape(state->cursor_shape, serial, shape); 1077 state->current_cursor = cursor_data; 1078 1079 return; 1080 } 1081 1082 // If viewports aren't available, the scale is always 1.0. 1083 state->scale = viddata->viewporter && focus ? focus->scale_factor : 1.0; 1084 if (!Wayland_GetSystemCursor(cursor_data, state, &dst_width, &hot_x, &hot_y)) { 1085 return; 1086 } 1087 1088 dst_height = dst_width; 1089 } else { 1090 // If viewports aren't available, the scale is always 1.0. 1091 state->scale = viddata->viewporter && focus ? focus->scale_factor : 1.0; 1092 dst_width = cursor_data->cursor_data.custom.width; 1093 dst_height = cursor_data->cursor_data.custom.height; 1094 hot_x = cursor_data->cursor_data.custom.hot_x; 1095 hot_y = cursor_data->cursor_data.custom.hot_y; 1096 } 1097 1098 state->current_cursor = cursor_data; 1099 1100 if (!state->surface) { 1101 if (cursor_thread_context.compositor_wrapper) { 1102 state->surface = wl_compositor_create_surface((struct wl_compositor *)cursor_thread_context.compositor_wrapper); 1103 } else { 1104 state->surface = wl_compositor_create_surface(viddata->compositor); 1105 } 1106 } 1107 1108 struct wl_buffer *buffer = Wayland_CursorStateGetFrame(state, 0); 1109 wl_surface_attach(state->surface, buffer, 0, 0); 1110 1111 if (state->scale != 1.0) { 1112 if (!state->viewport) { 1113 state->viewport = wp_viewporter_get_viewport(viddata->viewporter, state->surface); 1114 } 1115 1116 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)); 1117 wp_viewport_set_destination(state->viewport, dst_width, dst_height); 1118 } else if (state->viewport) { 1119 wp_viewport_destroy(state->viewport); 1120 state->viewport = NULL; 1121 } 1122 1123 if (obj->is_pointer) { 1124 wl_pointer_set_cursor(obj->wl_pointer, serial, state->surface, hot_x, hot_y); 1125 } else { 1126 zwp_tablet_tool_v2_set_cursor(obj->wl_tool, serial, state->surface, hot_x, hot_y); 1127 } 1128 1129 if (wl_surface_get_version(state->surface) >= WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION) { 1130 wl_surface_damage_buffer(state->surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32); 1131 } else { 1132 wl_surface_damage(state->surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32); 1133 } 1134 1135 // If more than one frame is available, create a frame callback to run the animation. 1136 if (cursor_data->num_frames > 1) { 1137 Wayland_CursorStateResetAnimation(state, false); 1138 Wayland_CursorStateSetFrameCallback(state, state); 1139 } 1140 1141 wl_surface_commit(state->surface); 1142 } else { 1143 Wayland_CursorStateDestroyFrameCallback(state); 1144 state->current_cursor = NULL; 1145 1146 if (state->surface) { 1147 wl_surface_attach(state->surface, NULL, 0, 0); 1148 wl_surface_commit(state->surface); 1149 } 1150 1151 if (obj->is_pointer) { 1152 wl_pointer_set_cursor(obj->wl_pointer, serial, NULL, 0, 0); 1153 } else { 1154 zwp_tablet_tool_v2_set_cursor(obj->wl_tool, serial, NULL, 0, 0); 1155 } 1156 } 1157} 1158 1159static bool Wayland_ShowCursor(SDL_Cursor *cursor) 1160{ 1161 SDL_VideoDevice *vd = SDL_GetVideoDevice(); 1162 SDL_VideoData *d = vd->internal; 1163 SDL_Mouse *mouse = SDL_GetMouse(); 1164 SDL_WaylandSeat *seat; 1165 Wayland_PointerObject obj; 1166 1167 wl_list_for_each (seat, &d->seat_list, link) { 1168 obj.wl_pointer = seat->pointer.wl_pointer; 1169 obj.is_pointer = true; 1170 if (mouse->focus && mouse->focus->internal == seat->pointer.focus) { 1171 Wayland_CursorStateSetCursor(&seat->pointer.cursor_state, &obj, seat->pointer.focus, seat->pointer.enter_serial, cursor); 1172 } else if (!seat->pointer.focus) { 1173 Wayland_CursorStateSetCursor(&seat->pointer.cursor_state, &obj, seat->pointer.focus, seat->pointer.enter_serial, NULL); 1174 } 1175 1176 SDL_WaylandPenTool *tool; 1177 wl_list_for_each(tool, &seat->tablet.tool_list, link) { 1178 obj.wl_tool = tool->wltool; 1179 obj.is_pointer = false; 1180 1181 /* The current cursor is explicitly set on tablet tools, as there may be no pointer device, or 1182 * the pointer may not have focus, which would instead cause the default cursor to be set. 1183 */ 1184 if (tool->focus && (!mouse->focus || mouse->focus->internal == tool->focus)) { 1185 Wayland_CursorStateSetCursor(&tool->cursor_state, &obj, tool->focus, tool->proximity_serial, mouse->cur_cursor); 1186 } else if (!tool->focus) { 1187 Wayland_CursorStateSetCursor(&tool->cursor_state, &obj, tool->focus, tool->proximity_serial, NULL); 1188 } 1189 } 1190 } 1191 1192 return true; 1193} 1194 1195void Wayland_SeatWarpMouse(SDL_WaylandSeat *seat, SDL_WindowData *window, float x, float y) 1196{ 1197 SDL_VideoDevice *vd = SDL_GetVideoDevice(); 1198 SDL_VideoData *d = vd->internal; 1199 1200 if (seat->pointer.wl_pointer) { 1201 if (d->wp_pointer_warp_v1) { 1202 // It's a protocol error to warp the pointer outside of the surface, so clamp the position. 1203 const wl_fixed_t f_x = wl_fixed_from_double(SDL_clamp(x / window->pointer_scale.x, 0, window->current.logical_width)); 1204 const wl_fixed_t f_y = wl_fixed_from_double(SDL_clamp(y / window->pointer_scale.y, 0, window->current.logical_height)); 1205 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); 1206 } else { 1207 bool update_grabs = false; 1208 1209 // Pointers can only have one confinement type active on a surface at one time. 1210 if (seat->pointer.confined_pointer) { 1211 zwp_confined_pointer_v1_destroy(seat->pointer.confined_pointer); 1212 seat->pointer.confined_pointer = NULL; 1213 update_grabs = true; 1214 } 1215 if (seat->pointer.locked_pointer) { 1216 zwp_locked_pointer_v1_destroy(seat->pointer.locked_pointer); 1217 seat->pointer.locked_pointer = NULL; 1218 update_grabs = true; 1219 } 1220 1221 /* The pointer confinement protocol allows setting a hint to warp the pointer, 1222 * but only when the pointer is locked. 1223 * 1224 * Lock the pointer, set the position hint, unlock, and hope for the best. 1225 */ 1226 struct zwp_locked_pointer_v1 *warp_lock = 1227 zwp_pointer_constraints_v1_lock_pointer(d->pointer_constraints, window->surface, 1228 seat->pointer.wl_pointer, NULL, 1229 ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT); 1230 1231 const wl_fixed_t f_x = wl_fixed_from_double(x / window->pointer_scale.x); 1232 const wl_fixed_t f_y = wl_fixed_from_double(y / window->pointer_scale.y); 1233 zwp_locked_pointer_v1_set_cursor_position_hint(warp_lock, f_x, f_y); 1234 wl_surface_commit(window->surface); 1235 1236 zwp_locked_pointer_v1_destroy(warp_lock); 1237 1238 if (update_grabs) { 1239 Wayland_SeatUpdatePointerGrab(seat); 1240 } 1241 1242 /* NOTE: There is a pending warp event under discussion that should replace this when available. 1243 * https://gitlab.freedesktop.org/wayland/wayland/-/merge_requests/340 1244 */ 1245 SDL_SendMouseMotion(0, window->sdlwindow, seat->pointer.sdl_id, false, x, y); 1246 } 1247 } 1248} 1249 1250static bool Wayland_WarpMouseRelative(SDL_Window *window, float x, float y) 1251{ 1252 SDL_VideoDevice *vd = SDL_GetVideoDevice(); 1253 SDL_VideoData *d = vd->internal; 1254 SDL_WindowData *wind = window->internal; 1255 SDL_WaylandSeat *seat; 1256 1257 if (d->wp_pointer_warp_v1 || d->pointer_constraints) { 1258 wl_list_for_each (seat, &d->seat_list, link) { 1259 if (wind == seat->pointer.focus) { 1260 Wayland_SeatWarpMouse(seat, wind, x, y); 1261 } 1262 } 1263 } else { 1264 return SDL_SetError("wayland: mouse warp failed; compositor lacks support for the required wp_pointer_warp_v1 or zwp_pointer_confinement_v1 protocol"); 1265 } 1266 1267 return true; 1268} 1269 1270static bool Wayland_WarpMouseGlobal(float x, float y) 1271{ 1272 SDL_VideoDevice *vd = SDL_GetVideoDevice(); 1273 SDL_VideoData *d = vd->internal; 1274 SDL_WaylandSeat *seat; 1275 1276 if (d->wp_pointer_warp_v1 || d->pointer_constraints) { 1277 wl_list_for_each (seat, &d->seat_list, link) { 1278 SDL_WindowData *wind = seat->pointer.focus ? seat->pointer.focus : seat->keyboard.focus; 1279 1280 // If the client wants the coordinates warped to within a focused window, just convert the coordinates to relative. 1281 if (wind) { 1282 SDL_Window *window = wind->sdlwindow; 1283 1284 int abs_x, abs_y; 1285 SDL_RelativeToGlobalForWindow(window, window->x, window->y, &abs_x, &abs_y); 1286 1287 const SDL_FPoint p = { x, y }; 1288 const SDL_FRect r = { abs_x, abs_y, window->w, window->h }; 1289 1290 // Try to warp the cursor if the point is within the seat's focused window. 1291 if (SDL_PointInRectFloat(&p, &r)) { 1292 Wayland_SeatWarpMouse(seat, wind, p.x - abs_x, p.y - abs_y); 1293 } 1294 } 1295 } 1296 } else { 1297 return SDL_SetError("wayland: mouse warp failed; compositor lacks support for the required wp_pointer_warp_v1 or zwp_pointer_confinement_v1 protocol"); 1298 } 1299 1300 return true; 1301} 1302 1303static bool Wayland_SetRelativeMouseMode(bool enabled) 1304{ 1305 SDL_VideoDevice *vd = SDL_GetVideoDevice(); 1306 SDL_VideoData *data = vd->internal; 1307 1308 // Relative mode requires both the relative motion and pointer confinement protocols. 1309 if (!data->relative_pointer_manager) { 1310 return SDL_SetError("Failed to enable relative mode: compositor lacks support for the required zwp_relative_pointer_manager_v1 protocol"); 1311 } 1312 if (!data->pointer_constraints) { 1313 return SDL_SetError("Failed to enable relative mode: compositor lacks support for the required zwp_pointer_constraints_v1 protocol"); 1314 } 1315 1316 // Windows have a relative mode flag, so just update the grabs on a state change. 1317 Wayland_DisplayUpdatePointerGrabs(data, NULL); 1318 return true; 1319} 1320 1321/* Wayland doesn't support getting the true global cursor position, but it can 1322 * be faked well enough for what most applications use it for: querying the 1323 * global cursor coordinates and transforming them to the window-relative 1324 * coordinates manually. 1325 * 1326 * The global position is derived by taking the cursor position relative to the 1327 * toplevel window, and offsetting it by the origin of the output the window is 1328 * currently considered to be on. The cursor position and button state when the 1329 * cursor is outside an application window are unknown, but this gives 'correct' 1330 * coordinates when the window has focus, which is good enough for most 1331 * applications. 1332 */ 1333static SDL_MouseButtonFlags SDLCALL Wayland_GetGlobalMouseState(float *x, float *y) 1334{ 1335 const SDL_Mouse *mouse = SDL_GetMouse(); 1336 SDL_MouseButtonFlags result = 0; 1337 1338 // If there is no window with mouse focus, we have no idea what the actual position or button state is. 1339 if (mouse->focus) { 1340 SDL_VideoData *video_data = SDL_GetVideoDevice()->internal; 1341 SDL_WaylandSeat *seat; 1342 int off_x, off_y; 1343 SDL_RelativeToGlobalForWindow(mouse->focus, mouse->focus->x, mouse->focus->y, &off_x, &off_y); 1344 *x = mouse->x + off_x; 1345 *y = mouse->y + off_y; 1346 1347 // Query the buttons from the seats directly, as this may be called from within a hit test handler. 1348 wl_list_for_each (seat, &video_data->seat_list, link) { 1349 result |= seat->pointer.buttons_pressed; 1350 } 1351 } else { 1352 *x = 0.f; 1353 *y = 0.f; 1354 } 1355 1356 return result; 1357} 1358 1359#if 0 // TODO RECONNECT: See waylandvideo.c for more information! 1360static void Wayland_RecreateCursor(SDL_Cursor *cursor, SDL_VideoData *vdata) 1361{ 1362 SDL_CursorData *cdata = cursor->internal; 1363 1364 // Probably not a cursor we own 1365 if (cdata == NULL) { 1366 return; 1367 } 1368 1369 Wayland_FreeCursorData(cdata); 1370 1371 // We're not currently freeing this, so... yolo? 1372 if (cdata->shm_data != NULL) { 1373 void *old_data_pointer = cdata->shm_data; 1374 int stride = cdata->w * 4; 1375 1376 create_buffer_from_shm(cdata, cdata->w, cdata->h, WL_SHM_FORMAT_ARGB8888); 1377 1378 SDL_memcpy(cdata->shm_data, old_data_pointer, stride * cdata->h); 1379 } 1380 cdata->surface = wl_compositor_create_surface(vdata->compositor); 1381 wl_surface_set_user_data(cdata->surface, NULL); 1382} 1383 1384void Wayland_RecreateCursors(void) 1385{ 1386 SDL_Cursor *cursor; 1387 SDL_Mouse *mouse = SDL_GetMouse(); 1388 SDL_VideoData *vdata = SDL_GetVideoDevice()->internal; 1389 1390 if (vdata && vdata->cursor_themes) { 1391 SDL_free(vdata->cursor_themes); 1392 vdata->cursor_themes = NULL; 1393 vdata->num_cursor_themes = 0; 1394 } 1395 1396 if (mouse == NULL) { 1397 return; 1398 } 1399 1400 for (cursor = mouse->cursors; cursor != NULL; cursor = cursor->next) { 1401 Wayland_RecreateCursor(cursor, vdata); 1402 } 1403 if (mouse->def_cursor) { 1404 Wayland_RecreateCursor(mouse->def_cursor, vdata); 1405 } 1406 if (mouse->cur_cursor) { 1407 Wayland_RecreateCursor(mouse->cur_cursor, vdata); 1408 if (mouse->cursor_visible) { 1409 Wayland_ShowCursor(mouse->cur_cursor); 1410 } 1411 } 1412} 1413#endif // 0 1414 1415void Wayland_InitMouse(SDL_VideoData *data) 1416{ 1417 SDL_Mouse *mouse = SDL_GetMouse(); 1418 1419 mouse->CreateCursor = Wayland_CreateCursor; 1420 mouse->CreateAnimatedCursor = Wayland_CreateAnimatedCursor; 1421 mouse->CreateSystemCursor = Wayland_CreateSystemCursor; 1422 mouse->ShowCursor = Wayland_ShowCursor; 1423 mouse->FreeCursor = Wayland_FreeCursor; 1424 mouse->WarpMouse = Wayland_WarpMouseRelative; 1425 mouse->WarpMouseGlobal = Wayland_WarpMouseGlobal; 1426 mouse->SetRelativeMouseMode = Wayland_SetRelativeMouseMode; 1427 mouse->GetGlobalMouseState = Wayland_GetGlobalMouseState; 1428 1429 if (!Wayland_StartCursorThread(data)) { 1430 SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "wayland: Failed to start cursor animation event thread"); 1431 } 1432 1433 SDL_HitTestResult r = SDL_HITTEST_NORMAL; 1434 while (r <= SDL_HITTEST_RESIZE_LEFT) { 1435 switch (r) { 1436 case SDL_HITTEST_NORMAL: 1437 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_DEFAULT); 1438 break; 1439 case SDL_HITTEST_DRAGGABLE: 1440 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_DEFAULT); 1441 break; 1442 case SDL_HITTEST_RESIZE_TOPLEFT: 1443 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_NW_RESIZE); 1444 break; 1445 case SDL_HITTEST_RESIZE_TOP: 1446 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_N_RESIZE); 1447 break; 1448 case SDL_HITTEST_RESIZE_TOPRIGHT: 1449 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_NE_RESIZE); 1450 break; 1451 case SDL_HITTEST_RESIZE_RIGHT: 1452 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_E_RESIZE); 1453 break; 1454 case SDL_HITTEST_RESIZE_BOTTOMRIGHT: 1455 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_SE_RESIZE); 1456 break; 1457 case SDL_HITTEST_RESIZE_BOTTOM: 1458 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_S_RESIZE); 1459 break; 1460 case SDL_HITTEST_RESIZE_BOTTOMLEFT: 1461 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_SW_RESIZE); 1462 break; 1463 case SDL_HITTEST_RESIZE_LEFT: 1464 sys_cursors[r] = Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_W_RESIZE); 1465 break; 1466 } 1467 r++; 1468 } 1469 1470#ifdef SDL_USE_LIBDBUS 1471 SDL_VideoDevice *vd = SDL_GetVideoDevice(); 1472 SDL_VideoData *d = vd->internal; 1473 1474 /* The D-Bus cursor properties are only needed when manually loading themes and system cursors. 1475 * If the cursor shape protocol is present, the compositor will handle it internally. 1476 */ 1477 if (!d->cursor_shape_manager) { 1478 Wayland_DBusInitCursorProperties(d); 1479 } 1480#endif 1481 1482 SDL_SetDefaultCursor(Wayland_CreateDefaultCursor()); 1483} 1484 1485void Wayland_FiniMouse(SDL_VideoData *data) 1486{ 1487 for (int i = 0; i < SDL_arraysize(sys_cursors); i++) { 1488 Wayland_FreeCursor(sys_cursors[i]); 1489 sys_cursors[i] = NULL; 1490 } 1491 1492 Wayland_DestroyCursorThread(data); 1493 Wayland_FreeCursorThemes(data); 1494 1495#ifdef SDL_USE_LIBDBUS 1496 Wayland_DBusFinishCursorProperties(); 1497#endif 1498} 1499 1500void Wayland_SeatUpdatePointerCursor(SDL_WaylandSeat *seat) 1501{ 1502 SDL_Mouse *mouse = SDL_GetMouse(); 1503 SDL_WindowData *pointer_focus = seat->pointer.focus; 1504 const Wayland_PointerObject obj = { 1505 .wl_pointer = seat->pointer.wl_pointer, 1506 .is_pointer = true 1507 }; 1508 1509 if (pointer_focus && mouse->cursor_visible) { 1510 if (!seat->pointer.relative_pointer || !mouse->relative_mode_hide_cursor) { 1511 const SDL_HitTestResult rc = pointer_focus->hit_test_result; 1512 1513 if (seat->pointer.relative_pointer || rc == SDL_HITTEST_NORMAL || rc == SDL_HITTEST_DRAGGABLE) { 1514 Wayland_CursorStateSetCursor(&seat->pointer.cursor_state, &obj, pointer_focus, seat->pointer.enter_serial, mouse->cur_cursor); 1515 } else { 1516 Wayland_CursorStateSetCursor(&seat->pointer.cursor_state, &obj, pointer_focus, seat->pointer.enter_serial, sys_cursors[rc]); 1517 } 1518 } else { 1519 // Hide the cursor in relative mode, unless requested otherwise by the hint. 1520 Wayland_CursorStateSetCursor(&seat->pointer.cursor_state, &obj, pointer_focus, seat->pointer.enter_serial, NULL); 1521 } 1522 } else { 1523 /* The spec states "The cursor actually changes only if the input device focus is one of the 1524 * requesting client's surfaces", so just clear the cursor if the seat has no pointer focus. 1525 */ 1526 Wayland_CursorStateSetCursor(&seat->pointer.cursor_state, &obj, pointer_focus, seat->pointer.enter_serial, NULL); 1527 } 1528} 1529 1530void Wayland_TabletToolUpdateCursor(SDL_WaylandPenTool *tool) 1531{ 1532 SDL_Mouse *mouse = SDL_GetMouse(); 1533 SDL_WindowData *tool_focus = tool->focus; 1534 const Wayland_PointerObject obj = { 1535 .wl_tool = tool->wltool, 1536 .is_pointer = false 1537 }; 1538 1539 if (tool_focus && mouse->cursor_visible) { 1540 // Relative mode is only relevant if the tool sends pointer events. 1541 const bool relative = mouse->pen_mouse_events && (tool_focus->sdlwindow->flags & SDL_WINDOW_MOUSE_RELATIVE_MODE); 1542 1543 if (!relative || !mouse->relative_mode_hide_cursor) { 1544 Wayland_CursorStateSetCursor(&tool->cursor_state, &obj, tool_focus, tool->proximity_serial, mouse->cur_cursor); 1545 } else { 1546 // Hide the cursor in relative mode, unless requested otherwise by the hint. 1547 Wayland_CursorStateSetCursor(&tool->cursor_state, &obj, tool_focus, tool->proximity_serial, NULL); 1548 } 1549 } else { 1550 Wayland_CursorStateSetCursor(&tool->cursor_state, &obj, tool_focus, tool->proximity_serial, NULL); 1551 } 1552} 1553 1554#endif // SDL_VIDEO_DRIVER_WAYLAND 1555[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.