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 | |
35 | static SDL_Cursor *KMSDRM_CreateDefaultCursor(void); |
36 | static SDL_Cursor *KMSDRM_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y); |
37 | static bool KMSDRM_ShowCursor(SDL_Cursor *cursor); |
38 | static bool KMSDRM_MoveCursor(SDL_Cursor *cursor); |
39 | static 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 | |
56 | static 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. */ |
64 | void 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. */ |
79 | bool 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. |
116 | static 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. |
132 | static 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 | |
190 | cleanup: |
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. |
199 | static 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. */ |
222 | static 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 | |
268 | cleanup: |
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. |
285 | static 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 | |
330 | static 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 | |
356 | static 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 | |
362 | void 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 | |
382 | void KMSDRM_QuitMouse(SDL_VideoDevice *_this) |
383 | { |
384 | // TODO: ? |
385 | } |
386 | |
387 | // This is called when a mouse motion event occurs |
388 | static 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 | |