1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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_shown 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 KMSDRM_gbm_bo_destroy(dispdata->cursor_bo);
71 dispdata->cursor_bo = NULL;
72 dispdata->cursor_bo_drm_fd = -1;
73 }
74}
75
76/* Given a display's internal, create the cursor BO for it.
77 To be called from KMSDRM_CreateWindow(), as that's where we
78 build a window and assign a display to it. */
79bool KMSDRM_CreateCursorBO(SDL_VideoDisplay *display)
80{
81
82 SDL_VideoDevice *dev = SDL_GetVideoDevice();
83 SDL_VideoData *viddata = dev->internal;
84 SDL_DisplayData *dispdata = display->internal;
85
86 if (!KMSDRM_gbm_device_is_format_supported(viddata->gbm_dev,
87 GBM_FORMAT_ARGB8888,
88 GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE)) {
89 return SDL_SetError("Unsupported pixel format for cursor");
90 }
91
92 if (KMSDRM_drmGetCap(viddata->drm_fd,
93 DRM_CAP_CURSOR_WIDTH, &dispdata->cursor_w) ||
94 KMSDRM_drmGetCap(viddata->drm_fd, DRM_CAP_CURSOR_HEIGHT,
95 &dispdata->cursor_h)) {
96 return SDL_SetError("Could not get the recommended GBM cursor size");
97 }
98
99 if (dispdata->cursor_w == 0 || dispdata->cursor_h == 0) {
100 return SDL_SetError("Could not get an usable GBM cursor size");
101 }
102
103 dispdata->cursor_bo = KMSDRM_gbm_bo_create(viddata->gbm_dev,
104 dispdata->cursor_w, dispdata->cursor_h,
105 GBM_FORMAT_ARGB8888, GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE | GBM_BO_USE_LINEAR);
106
107 if (!dispdata->cursor_bo) {
108 return SDL_SetError("Could not create GBM cursor BO");
109 }
110
111 dispdata->cursor_bo_drm_fd = viddata->drm_fd;
112 return true;
113}
114
115// Remove a cursor buffer from a display's DRM cursor BO.
116static bool KMSDRM_RemoveCursorFromBO(SDL_VideoDisplay *display)
117{
118 bool result = true;
119
120 SDL_DisplayData *dispdata = display->internal;
121 SDL_VideoDevice *video_device = SDL_GetVideoDevice();
122 SDL_VideoData *viddata = video_device->internal;
123
124 const int rc = KMSDRM_drmModeSetCursor(viddata->drm_fd, dispdata->crtc->crtc_id, 0, 0, 0);
125 if (rc < 0) {
126 result = SDL_SetError("drmModeSetCursor() failed: %s", strerror(-rc));
127 }
128 return result;
129}
130
131// Dump a cursor buffer to a display's DRM cursor BO.
132static bool KMSDRM_DumpCursorToBO(SDL_VideoDisplay *display, SDL_Cursor *cursor)
133{
134 SDL_DisplayData *dispdata = display->internal;
135 SDL_CursorData *curdata = cursor->internal;
136 SDL_VideoDevice *video_device = SDL_GetVideoDevice();
137 SDL_VideoData *viddata = video_device->internal;
138
139 uint32_t bo_handle;
140 size_t bo_stride;
141 size_t bufsize;
142 uint8_t *ready_buffer = NULL;
143 uint8_t *src_row;
144
145 int i, rc;
146 bool result = true;
147
148 if (!curdata || !dispdata->cursor_bo) {
149 return SDL_SetError("Cursor or display not initialized properly.");
150 }
151
152 /* Prepare a buffer we can dump to our GBM BO (different
153 size, alpha premultiplication...) */
154 bo_stride = KMSDRM_gbm_bo_get_stride(dispdata->cursor_bo);
155 bufsize = bo_stride * dispdata->cursor_h;
156
157 ready_buffer = (uint8_t *)SDL_calloc(1, bufsize);
158
159 if (!ready_buffer) {
160 result = false;
161 goto cleanup;
162 }
163
164 // Copy from the cursor buffer to a buffer that we can dump to the GBM BO.
165 for (i = 0; i < curdata->h; i++) {
166 src_row = &((uint8_t *)curdata->buffer)[i * curdata->w * 4];
167 SDL_memcpy(ready_buffer + (i * bo_stride), src_row, (size_t)4 * curdata->w);
168 }
169
170 // Dump the cursor buffer to our GBM BO.
171 if (KMSDRM_gbm_bo_write(dispdata->cursor_bo, ready_buffer, bufsize)) {
172 result = SDL_SetError("Could not write to GBM cursor BO");
173 goto cleanup;
174 }
175
176 // Put the GBM BO buffer on screen using the DRM interface.
177 bo_handle = KMSDRM_gbm_bo_get_handle(dispdata->cursor_bo).u32;
178 if (curdata->hot_x == 0 && curdata->hot_y == 0) {
179 rc = KMSDRM_drmModeSetCursor(viddata->drm_fd, dispdata->crtc->crtc_id,
180 bo_handle, dispdata->cursor_w, dispdata->cursor_h);
181 } else {
182 rc = KMSDRM_drmModeSetCursor2(viddata->drm_fd, dispdata->crtc->crtc_id,
183 bo_handle, dispdata->cursor_w, dispdata->cursor_h, curdata->hot_x, curdata->hot_y);
184 }
185 if (rc < 0) {
186 result = SDL_SetError("Failed to set DRM cursor: %s", strerror(-rc));
187 goto cleanup;
188 }
189
190cleanup:
191
192 if (ready_buffer) {
193 SDL_free(ready_buffer);
194 }
195 return result;
196}
197
198// This is only for freeing the SDL_cursor.
199static void KMSDRM_FreeCursor(SDL_Cursor *cursor)
200{
201 SDL_CursorData *curdata;
202
203 // Even if the cursor is not ours, free it.
204 if (cursor) {
205 curdata = cursor->internal;
206 // Free cursor buffer
207 if (curdata->buffer) {
208 SDL_free(curdata->buffer);
209 curdata->buffer = NULL;
210 }
211 // Free cursor itself
212 if (cursor->internal) {
213 SDL_free(cursor->internal);
214 }
215 SDL_free(cursor);
216 }
217}
218
219/* This simply gets the cursor soft-buffer ready.
220 We don't copy it to a GBO BO until ShowCursor() because the cusor GBM BO (living
221 in dispata) is destroyed and recreated when we recreate windows, etc. */
222static SDL_Cursor *KMSDRM_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y)
223{
224 SDL_CursorData *curdata;
225 SDL_Cursor *cursor, *result;
226
227 curdata = NULL;
228 result = NULL;
229
230 cursor = (SDL_Cursor *)SDL_calloc(1, sizeof(*cursor));
231 if (!cursor) {
232 goto cleanup;
233 }
234 curdata = (SDL_CursorData *)SDL_calloc(1, sizeof(*curdata));
235 if (!curdata) {
236 goto cleanup;
237 }
238
239 // hox_x and hot_y are the coordinates of the "tip of the cursor" from it's base.
240 curdata->hot_x = hot_x;
241 curdata->hot_y = hot_y;
242 curdata->w = surface->w;
243 curdata->h = surface->h;
244 curdata->buffer = NULL;
245
246 /* Configure the cursor buffer info.
247 This buffer has the original size of the cursor surface we are given. */
248 curdata->buffer_pitch = surface->w;
249 curdata->buffer_size = (size_t)surface->w * surface->h * 4;
250 curdata->buffer = (uint32_t *)SDL_malloc(curdata->buffer_size);
251
252 if (!curdata->buffer) {
253 goto cleanup;
254 }
255
256 /* All code below assumes ARGB8888 format for the cursor surface,
257 like other backends do. Also, the GBM BO pixels have to be
258 alpha-premultiplied, but the SDL surface we receive has
259 straight-alpha pixels, so we always have to convert. */
260 SDL_PremultiplyAlpha(surface->w, surface->h,
261 surface->format, surface->pixels, surface->pitch,
262 SDL_PIXELFORMAT_ARGB8888, curdata->buffer, surface->w * 4, true);
263
264 cursor->internal = curdata;
265
266 result = cursor;
267
268cleanup:
269 if (!result) {
270 if (curdata) {
271 if (curdata->buffer) {
272 SDL_free(curdata->buffer);
273 }
274 SDL_free(curdata);
275 }
276 if (cursor) {
277 SDL_free(cursor);
278 }
279 }
280
281 return result;
282}
283
284// Show the specified cursor, or hide if cursor is NULL or has no focus.
285static bool KMSDRM_ShowCursor(SDL_Cursor *cursor)
286{
287 SDL_VideoDisplay *display;
288 SDL_Window *window;
289 SDL_Mouse *mouse = SDL_GetMouse();
290
291 int i;
292 bool result = true;
293
294 // Get the mouse focused window, if any.
295 window = mouse->focus;
296
297 if (!window || !cursor) {
298 /* If no window is focused by mouse or cursor is NULL,
299 since we have no window (no mouse->focus) and hence
300 we have no display, we simply hide mouse on all displays.
301 This happens on video quit, where we get here after
302 the mouse focus has been unset, yet SDL wants to
303 restore the system default cursor (makes no sense here). */
304 SDL_DisplayID *displays = SDL_GetDisplays(NULL);
305 if (displays) {
306 // Iterate on the displays, hiding the cursor.
307 for (i = 0; i < displays[i]; i++) {
308 display = SDL_GetVideoDisplay(displays[i]);
309 result = KMSDRM_RemoveCursorFromBO(display);
310 }
311 SDL_free(displays);
312 }
313 } else {
314 display = SDL_GetVideoDisplayForWindow(window);
315 if (display) {
316 if (cursor) {
317 /* Dump the cursor to the display DRM cursor BO so it becomes visible
318 on that display. */
319 result = KMSDRM_DumpCursorToBO(display, cursor);
320 } else {
321 // Hide the cursor on that display.
322 result = KMSDRM_RemoveCursorFromBO(display);
323 }
324 }
325 }
326
327 return result;
328}
329
330static bool KMSDRM_WarpMouseGlobal(float x, float y)
331{
332 SDL_Mouse *mouse = SDL_GetMouse();
333
334 if (mouse && mouse->cur_cursor && mouse->focus) {
335 SDL_Window *window = mouse->focus;
336 SDL_DisplayData *dispdata = SDL_GetDisplayDriverDataForWindow(window);
337
338 // Update internal mouse position.
339 SDL_SendMouseMotion(0, mouse->focus, SDL_GLOBAL_MOUSE_ID, false, x, y);
340
341 // And now update the cursor graphic position on screen.
342 if (dispdata->cursor_bo) {
343 const int rc = KMSDRM_drmModeMoveCursor(dispdata->cursor_bo_drm_fd, dispdata->crtc->crtc_id, (int)x, (int)y);
344 if (rc < 0) {
345 return SDL_SetError("drmModeMoveCursor() failed: %s", strerror(-rc));
346 }
347 return true;
348 } else {
349 return SDL_SetError("Cursor not initialized properly.");
350 }
351 } else {
352 return SDL_SetError("No mouse or current cursor.");
353 }
354}
355
356static bool KMSDRM_WarpMouse(SDL_Window *window, float x, float y)
357{
358 // Only one global/fullscreen window is supported
359 return KMSDRM_WarpMouseGlobal(x, y);
360}
361
362void KMSDRM_InitMouse(SDL_VideoDevice *_this, SDL_VideoDisplay *display)
363{
364 SDL_Mouse *mouse = SDL_GetMouse();
365 SDL_DisplayData *dispdata = display->internal;
366
367 mouse->CreateCursor = KMSDRM_CreateCursor;
368 mouse->ShowCursor = KMSDRM_ShowCursor;
369 mouse->MoveCursor = KMSDRM_MoveCursor;
370 mouse->FreeCursor = KMSDRM_FreeCursor;
371 mouse->WarpMouse = KMSDRM_WarpMouse;
372 mouse->WarpMouseGlobal = KMSDRM_WarpMouseGlobal;
373
374 /* Only create the default cursor for this display if we haven't done so before,
375 we don't want several cursors to be created for the same display. */
376 if (!dispdata->default_cursor_init) {
377 SDL_SetDefaultCursor(KMSDRM_CreateDefaultCursor());
378 dispdata->default_cursor_init = true;
379 }
380}
381
382void KMSDRM_QuitMouse(SDL_VideoDevice *_this)
383{
384 // TODO: ?
385}
386
387// This is called when a mouse motion event occurs
388static bool KMSDRM_MoveCursor(SDL_Cursor *cursor)
389{
390 SDL_Mouse *mouse = SDL_GetMouse();
391
392 /* We must NOT call SDL_SendMouseMotion() here or we will enter recursivity!
393 That's why we move the cursor graphic ONLY. */
394 if (mouse && mouse->cur_cursor && mouse->focus) {
395 SDL_Window *window = mouse->focus;
396 SDL_DisplayData *dispdata = SDL_GetDisplayDriverDataForWindow(window);
397
398 if (!dispdata->cursor_bo) {
399 return SDL_SetError("Cursor not initialized properly.");
400 }
401
402 const int rc = KMSDRM_drmModeMoveCursor(dispdata->cursor_bo_drm_fd, dispdata->crtc->crtc_id, (int)mouse->x, (int)mouse->y);
403 if (rc < 0) {
404 return SDL_SetError("drmModeMoveCursor() failed: %s", strerror(-rc));
405 }
406 }
407 return true;
408}
409
410#endif // SDL_VIDEO_DRIVER_KMSDRM
411