Atlas - SDL_kmsdrmmouse.c

Home / ext / SDL / src / video / kmsdrm Lines: 1 | Size: 18663 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_KMSDRM 25 26#include "SDL_kmsdrmvideo.h" 27#include "SDL_kmsdrmmouse.h" 28#include "SDL_kmsdrmdyn.h" 29 30#include "../../events/SDL_mouse_c.h" 31#include "../../events/default_cursor.h" 32 33#include "../SDL_pixels_c.h" 34 35static SDL_Cursor *KMSDRM_CreateDefaultCursor(void); 36static SDL_Cursor *KMSDRM_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y); 37static bool KMSDRM_ShowCursor(SDL_Cursor *cursor); 38static bool KMSDRM_MoveCursor(SDL_Cursor *cursor); 39static void KMSDRM_FreeCursor(SDL_Cursor *cursor); 40 41/**************************************************************************************/ 42// BEFORE CODING ANYTHING MOUSE/CURSOR RELATED, REMEMBER THIS. 43// How does SDL manage cursors internally? First, mouse =! cursor. The mouse can have 44// many cursors in mouse->cursors. 45// -SDL tells us to create a cursor with KMSDRM_CreateCursor(). It can create many 46// cursosr with this, not only one. 47// -SDL stores those cursors in a cursors array, in mouse->cursors. 48// -Whenever it wants (or the programmer wants) takes a cursor from that array 49// and shows it on screen with KMSDRM_ShowCursor(). 50// KMSDRM_ShowCursor() simply shows or hides the cursor it receives: it does NOT 51// mind if it's mouse->cur_cursor, etc. 52// -If KMSDRM_ShowCursor() returns successfully, that cursor becomes 53// mouse->cur_cursor and mouse->cursor_visible is 1. 54/**************************************************************************************/ 55 56static SDL_Cursor *KMSDRM_CreateDefaultCursor(void) 57{ 58 return SDL_CreateCursor(default_cdata, default_cmask, DEFAULT_CWIDTH, DEFAULT_CHEIGHT, DEFAULT_CHOTX, DEFAULT_CHOTY); 59} 60 61/* Given a display's internal, destroy the cursor BO for it. 62 To be called from KMSDRM_DestroyWindow(), as that's where we 63 destroy the internal for the window's display. */ 64void KMSDRM_DestroyCursorBO(SDL_VideoDevice *_this, SDL_VideoDisplay *display) 65{ 66 SDL_DisplayData *dispdata = display->internal; 67 68 // Destroy the curso GBM BO. 69 if (dispdata->cursor_bo) { 70 SDL_VideoData *viddata = (SDL_VideoData *) _this->internal; 71 if (viddata->is_atomic) { 72 if (dispdata->cursor_plane) { 73 // Unset the the cursor BO from the cursor plane. 74 KMSDRM_PlaneInfo info; 75 SDL_zero(info); 76 info.plane = dispdata->cursor_plane; 77 drm_atomic_set_plane_props(dispdata, &info); 78 // Wait until the cursor is unset from the cursor plane before destroying it's BO. 79 if (drm_atomic_commit(_this, dispdata, true, false)) { 80 SDL_SetError("Failed atomic commit in KMSDRM_DenitMouse."); 81 } 82 // Free the cursor plane, on which the cursor was being shown. 83 free_plane(&dispdata->cursor_plane); 84 } 85 } 86 87 KMSDRM_gbm_bo_destroy(dispdata->cursor_bo); 88 dispdata->cursor_bo = NULL; 89 dispdata->cursor_bo_drm_fd = -1; 90 } 91} 92 93/* Given a display's internal, create the cursor BO for it. 94 To be called from KMSDRM_CreateWindow(), as that's where we 95 build a window and assign a display to it. */ 96bool KMSDRM_CreateCursorBO(SDL_VideoDisplay *display) 97{ 98 SDL_VideoDevice *dev = SDL_GetVideoDevice(); 99 SDL_VideoData *viddata = dev->internal; 100 SDL_DisplayData *dispdata = display->internal; 101 102 if (viddata->is_atomic) { 103 setup_plane(dev, dispdata, &dispdata->cursor_plane, DRM_PLANE_TYPE_CURSOR); 104 } 105 106 if (!KMSDRM_gbm_device_is_format_supported(viddata->gbm_dev, 107 GBM_FORMAT_ARGB8888, 108 GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE)) { 109 return SDL_SetError("Unsupported pixel format for cursor"); 110 } 111 112 if (KMSDRM_drmGetCap(viddata->drm_fd, 113 DRM_CAP_CURSOR_WIDTH, &dispdata->cursor_w) || 114 KMSDRM_drmGetCap(viddata->drm_fd, DRM_CAP_CURSOR_HEIGHT, 115 &dispdata->cursor_h)) { 116 return SDL_SetError("Could not get the recommended GBM cursor size"); 117 } 118 119 if (dispdata->cursor_w == 0 || dispdata->cursor_h == 0) { 120 return SDL_SetError("Could not get an usable GBM cursor size"); 121 } 122 123 dispdata->cursor_bo = KMSDRM_gbm_bo_create(viddata->gbm_dev, 124 dispdata->cursor_w, dispdata->cursor_h, 125 GBM_FORMAT_ARGB8888, GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE | GBM_BO_USE_LINEAR); 126 127 if (!dispdata->cursor_bo) { 128 return SDL_SetError("Could not create GBM cursor BO"); 129 } 130 131 dispdata->cursor_bo_drm_fd = viddata->drm_fd; 132 return true; 133} 134 135// Remove a cursor buffer from a display's DRM cursor BO. 136static bool KMSDRM_RemoveCursorFromBO(SDL_VideoDisplay *display) 137{ 138 bool result = true; 139 140 SDL_DisplayData *dispdata = display->internal; 141 SDL_VideoDevice *video_device = SDL_GetVideoDevice(); 142 SDL_VideoData *viddata = video_device->internal; 143 144 if (viddata->is_atomic) { 145 if (dispdata->cursor_plane) { 146 KMSDRM_PlaneInfo info; 147 SDL_zero(info); 148 info.plane = dispdata->cursor_plane; 149 // The rest of the members are zeroed, so this takes away the cursor from the cursor plane. 150 drm_atomic_set_plane_props(dispdata, &info); 151 if (drm_atomic_commit(video_device, dispdata, true, false)) { 152 result = SDL_SetError("Failed atomic commit in KMSDRM_ShowCursor."); 153 } 154 } 155 } else { 156 const int rc = KMSDRM_drmModeSetCursor(viddata->drm_fd, dispdata->crtc.crtc->crtc_id, 0, 0, 0); 157 if (rc < 0) { 158 result = SDL_SetError("drmModeSetCursor() failed: %s", strerror(-rc)); 159 } 160 } 161 162 return result; 163} 164 165// Dump a cursor buffer to a display's DRM cursor BO. 166static bool KMSDRM_DumpCursorToBO(SDL_VideoDisplay *display, SDL_Mouse *mouse, SDL_Cursor *cursor) 167{ 168 SDL_DisplayData *dispdata = display->internal; 169 SDL_CursorData *curdata = cursor->internal; 170 SDL_VideoDevice *video_device = SDL_GetVideoDevice(); 171 SDL_VideoData *viddata = video_device->internal; 172 173 uint32_t bo_handle; 174 size_t bo_stride; 175 size_t bufsize; 176 uint8_t *ready_buffer = NULL; 177 uint8_t *src_row; 178 179 int i, rc; 180 bool result = true; 181 182 if (!curdata || !dispdata->cursor_bo) { 183 return SDL_SetError("Cursor or display not initialized properly."); 184 } 185 186 /* Prepare a buffer we can dump to our GBM BO (different 187 size, alpha premultiplication...) */ 188 bo_stride = KMSDRM_gbm_bo_get_stride(dispdata->cursor_bo); 189 bufsize = bo_stride * dispdata->cursor_h; 190 191 ready_buffer = (uint8_t *)SDL_calloc(1, bufsize); 192 193 if (!ready_buffer) { 194 result = false; 195 goto cleanup; 196 } 197 198 // Copy from the cursor buffer to a buffer that we can dump to the GBM BO. 199 for (i = 0; i < curdata->h; i++) { 200 src_row = &((uint8_t *)curdata->buffer)[i * curdata->w * 4]; 201 SDL_memcpy(ready_buffer + (i * bo_stride), src_row, (size_t)4 * curdata->w); 202 } 203 204 // Dump the cursor buffer to our GBM BO. 205 if (KMSDRM_gbm_bo_write(dispdata->cursor_bo, ready_buffer, bufsize)) { 206 result = SDL_SetError("Could not write to GBM cursor BO"); 207 goto cleanup; 208 } 209 210 if (viddata->is_atomic) { 211 // Get the fb_id for the GBM BO so we can show it on the cursor plane. 212 KMSDRM_FBInfo *fb = KMSDRM_FBFromBO(video_device, dispdata->cursor_bo); 213 KMSDRM_PlaneInfo info; 214 215 // Show the GBM BO buffer on the cursor plane. 216 SDL_zero(info); 217 info.plane = dispdata->cursor_plane; 218 info.crtc_id = dispdata->crtc.crtc->crtc_id; 219 info.fb_id = fb->fb_id; 220 info.src_w = dispdata->cursor_w; 221 info.src_h = dispdata->cursor_h; 222 info.crtc_x = ((int32_t) SDL_roundf(mouse->x)) - curdata->hot_x; 223 info.crtc_y = ((int32_t) SDL_roundf(mouse->y)) - curdata->hot_y; 224 info.crtc_w = curdata->w; 225 info.crtc_h = curdata->h; 226 drm_atomic_set_plane_props(dispdata, &info); 227 if (drm_atomic_commit(video_device, dispdata, true, false)) { 228 result = SDL_SetError("Failed atomic commit in KMSDRM_ShowCursor."); 229 goto cleanup; 230 } 231 } else { 232 // Put the GBM BO buffer on screen using the DRM interface. 233 bo_handle = KMSDRM_gbm_bo_get_handle(dispdata->cursor_bo).u32; 234 if (curdata->hot_x == 0 && curdata->hot_y == 0) { 235 rc = KMSDRM_drmModeSetCursor(viddata->drm_fd, dispdata->crtc.crtc->crtc_id, bo_handle, dispdata->cursor_w, dispdata->cursor_h); 236 } else { 237 rc = KMSDRM_drmModeSetCursor2(viddata->drm_fd, dispdata->crtc.crtc->crtc_id, bo_handle, dispdata->cursor_w, dispdata->cursor_h, curdata->hot_x, curdata->hot_y); 238 } 239 if (rc < 0) { 240 result = SDL_SetError("Failed to set DRM cursor: %s", strerror(-rc)); 241 goto cleanup; 242 } 243 } 244 245cleanup: 246 SDL_free(ready_buffer); 247 return result; 248} 249 250// This is only for freeing the SDL_cursor. 251static void KMSDRM_FreeCursor(SDL_Cursor *cursor) 252{ 253 SDL_CursorData *curdata; 254 255 // Even if the cursor is not ours, free it. 256 if (cursor) { 257 curdata = cursor->internal; 258 // Free cursor buffer 259 if (curdata->buffer) { 260 SDL_free(curdata->buffer); 261 curdata->buffer = NULL; 262 } 263 // Free cursor itself 264 SDL_free(cursor->internal); 265 SDL_free(cursor); 266 } 267} 268 269/* This simply gets the cursor soft-buffer ready. 270 We don't copy it to a GBO BO until ShowCursor() because the cusor GBM BO (living 271 in dispata) is destroyed and recreated when we recreate windows, etc. */ 272static SDL_Cursor *KMSDRM_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y) 273{ 274 SDL_CursorData *curdata; 275 SDL_Cursor *cursor, *result; 276 277 curdata = NULL; 278 result = NULL; 279 280 cursor = (SDL_Cursor *)SDL_calloc(1, sizeof(*cursor)); 281 if (!cursor) { 282 goto cleanup; 283 } 284 curdata = (SDL_CursorData *)SDL_calloc(1, sizeof(*curdata)); 285 if (!curdata) { 286 goto cleanup; 287 } 288 289 // hox_x and hot_y are the coordinates of the "tip of the cursor" from it's base. 290 curdata->hot_x = hot_x; 291 curdata->hot_y = hot_y; 292 curdata->w = surface->w; 293 curdata->h = surface->h; 294 curdata->buffer = NULL; 295 296 /* Configure the cursor buffer info. 297 This buffer has the original size of the cursor surface we are given. */ 298 curdata->buffer_pitch = surface->w; 299 curdata->buffer_size = (size_t)surface->w * surface->h * 4; 300 curdata->buffer = (uint32_t *)SDL_malloc(curdata->buffer_size); 301 302 if (!curdata->buffer) { 303 goto cleanup; 304 } 305 306 /* All code below assumes ARGB8888 format for the cursor surface, 307 like other backends do. Also, the GBM BO pixels have to be 308 alpha-premultiplied, but the SDL surface we receive has 309 straight-alpha pixels, so we always have to convert. */ 310 SDL_PremultiplyAlpha(surface->w, surface->h, 311 surface->format, surface->pixels, surface->pitch, 312 SDL_PIXELFORMAT_ARGB8888, curdata->buffer, surface->w * 4, true); 313 314 cursor->internal = curdata; 315 316 result = cursor; 317 318cleanup: 319 if (!result) { 320 if (curdata) { 321 SDL_free(curdata->buffer); 322 SDL_free(curdata); 323 } 324 SDL_free(cursor); 325 } 326 327 return result; 328} 329 330// Show the specified cursor, or hide if cursor is NULL or has no focus. 331static bool KMSDRM_ShowCursor(SDL_Cursor *cursor) 332{ 333 SDL_VideoDisplay *display; 334 SDL_Window *window; 335 SDL_Mouse *mouse = SDL_GetMouse(); 336 337 int i; 338 bool result = true; 339 340 // Get the mouse focused window, if any. 341 window = mouse->focus; 342 343 if (!window || !cursor) { 344 /* If no window is focused by mouse or cursor is NULL, 345 since we have no window (no mouse->focus) and hence 346 we have no display, we simply hide mouse on all displays. 347 This happens on video quit, where we get here after 348 the mouse focus has been unset, yet SDL wants to 349 restore the system default cursor (makes no sense here). */ 350 SDL_DisplayID *displays = SDL_GetDisplays(NULL); 351 if (displays) { 352 // Iterate on the displays, hiding the cursor. 353 for (i = 0; i < displays[i]; i++) { 354 display = SDL_GetVideoDisplay(displays[i]); 355 result = KMSDRM_RemoveCursorFromBO(display); 356 } 357 SDL_free(displays); 358 } 359 } else { 360 display = SDL_GetVideoDisplayForWindow(window); 361 if (display) { 362 if (cursor) { 363 /* Dump the cursor to the display DRM cursor BO so it becomes visible 364 on that display. */ 365 result = KMSDRM_DumpCursorToBO(display, mouse, cursor); 366 } else { 367 // Hide the cursor on that display. 368 result = KMSDRM_RemoveCursorFromBO(display); 369 } 370 } 371 } 372 373 return result; 374} 375 376static void drm_atomic_movecursor(SDL_DisplayData *dispdata, const SDL_CursorData *curdata, uint16_t x, uint16_t y) 377{ 378 if (dispdata->cursor_plane) { // We can't move a non-existing cursor, but that's ok. 379 // Do we have a set of changes already in the making? If not, allocate a new one. 380 if (!dispdata->atomic_req) { 381 dispdata->atomic_req = KMSDRM_drmModeAtomicAlloc(); 382 } 383 add_plane_property(dispdata->atomic_req, dispdata->cursor_plane, "CRTC_X", x - curdata->hot_x); 384 add_plane_property(dispdata->atomic_req, dispdata->cursor_plane, "CRTC_Y", y - curdata->hot_y); 385 } 386} 387 388static bool KMSDRM_WarpMouseGlobal(float x, float y) 389{ 390 SDL_Mouse *mouse = SDL_GetMouse(); 391 392 if (mouse && mouse->cur_cursor && mouse->focus) { 393 SDL_Window *window = mouse->focus; 394 SDL_DisplayData *dispdata = SDL_GetDisplayDriverDataForWindow(window); 395 396 // Update internal mouse position. 397 SDL_SendMouseMotion(0, mouse->focus, SDL_GLOBAL_MOUSE_ID, false, x, y); 398 399 // And now update the cursor graphic position on screen. 400 if (dispdata->cursor_bo) { 401 SDL_VideoDevice *dev = SDL_GetVideoDevice(); 402 SDL_VideoData *viddata = dev->internal; 403 if (viddata->is_atomic) { 404 const SDL_CursorData *curdata = (const SDL_CursorData *) mouse->cur_cursor->internal; 405 drm_atomic_movecursor(dispdata, curdata, (uint16_t) (int) x, (uint16_t) (int) y); 406 } else { 407 const int rc = KMSDRM_drmModeMoveCursor(dispdata->cursor_bo_drm_fd, dispdata->crtc.crtc->crtc_id, (int)x, (int)y); 408 if (rc < 0) { 409 return SDL_SetError("drmModeMoveCursor() failed: %s", strerror(-rc)); 410 } 411 } 412 } else { 413 return SDL_SetError("Cursor not initialized properly."); 414 } 415 } else { 416 return SDL_SetError("No mouse or current cursor."); 417 } 418 419 return true; 420} 421 422static bool KMSDRM_WarpMouse(SDL_Window *window, float x, float y) 423{ 424 // Only one global/fullscreen window is supported 425 return KMSDRM_WarpMouseGlobal(x, y); 426} 427 428void KMSDRM_InitMouse(SDL_VideoDevice *_this, SDL_VideoDisplay *display) 429{ 430 SDL_Mouse *mouse = SDL_GetMouse(); 431 SDL_DisplayData *dispdata = display->internal; 432 433 mouse->CreateCursor = KMSDRM_CreateCursor; 434 mouse->ShowCursor = KMSDRM_ShowCursor; 435 mouse->MoveCursor = KMSDRM_MoveCursor; 436 mouse->FreeCursor = KMSDRM_FreeCursor; 437 mouse->WarpMouse = KMSDRM_WarpMouse; 438 mouse->WarpMouseGlobal = KMSDRM_WarpMouseGlobal; 439 440 /* Only create the default cursor for this display if we haven't done so before, 441 we don't want several cursors to be created for the same display. */ 442 if (!dispdata->default_cursor_init) { 443 SDL_SetDefaultCursor(KMSDRM_CreateDefaultCursor()); 444 dispdata->default_cursor_init = true; 445 } 446} 447 448void KMSDRM_QuitMouse(SDL_VideoDevice *_this) 449{ 450 // TODO: ? 451} 452 453// This is called when a mouse motion event occurs 454static bool KMSDRM_MoveCursor(SDL_Cursor *cursor) 455{ 456 SDL_Mouse *mouse = SDL_GetMouse(); 457 458 /* We must NOT call SDL_SendMouseMotion() here or we will enter recursivity! 459 That's why we move the cursor graphic ONLY. */ 460 if (mouse && mouse->cur_cursor && mouse->focus) { 461 SDL_Window *window = mouse->focus; 462 SDL_DisplayData *dispdata = SDL_GetDisplayDriverDataForWindow(window); 463 SDL_VideoDevice *dev = SDL_GetVideoDevice(); 464 SDL_VideoData *viddata = dev->internal; 465 466 if (!dispdata->cursor_bo) { 467 return SDL_SetError("Cursor not initialized properly."); 468 } 469 470 if (viddata->is_atomic) { 471 /* !!! FIXME: Some programs expect cursor movement even while they don't do SwapWindow() calls, 472 and since we ride on the atomic_commit() in SwapWindow() for cursor movement, 473 cursor won't move in these situations. We could do an atomic_commit() here 474 for each cursor movement request, but it cripples the movement to 30FPS, 475 so a future solution is needed. SDLPoP "QUIT?" menu is an example of this 476 situation. */ 477 const SDL_CursorData *curdata = (const SDL_CursorData *) mouse->cur_cursor->internal; 478 drm_atomic_movecursor(dispdata, curdata, (uint16_t) (int) mouse->x, (uint16_t) (int) mouse->y); 479 } else { 480 const int rc = KMSDRM_drmModeMoveCursor(dispdata->cursor_bo_drm_fd, dispdata->crtc.crtc->crtc_id, (int)mouse->x, (int)mouse->y); 481 if (rc < 0) { 482 return SDL_SetError("drmModeMoveCursor() failed: %s", strerror(-rc)); 483 } 484 } 485 } 486 return true; 487} 488 489#endif // SDL_VIDEO_DRIVER_KMSDRM 490
[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.