1 /*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2020 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 #include "../../SDL_internal.h"
22
23 #if SDL_VIDEO_DRIVER_RPI
24
25 #include "SDL_assert.h"
26 #include "SDL_surface.h"
27 #include "SDL_hints.h"
28
29 #include "SDL_rpivideo.h"
30 #include "SDL_rpimouse.h"
31
32 #include "../SDL_sysvideo.h"
33 #include "../../events/SDL_mouse_c.h"
34 #include "../../events/default_cursor.h"
35
36 /* Copied from vc_vchi_dispmanx.h which is bugged and tries to include a non existing file */
37 /* Attributes changes flag mask */
38 #define ELEMENT_CHANGE_LAYER (1<<0)
39 #define ELEMENT_CHANGE_OPACITY (1<<1)
40 #define ELEMENT_CHANGE_DEST_RECT (1<<2)
41 #define ELEMENT_CHANGE_SRC_RECT (1<<3)
42 #define ELEMENT_CHANGE_MASK_RESOURCE (1<<4)
43 #define ELEMENT_CHANGE_TRANSFORM (1<<5)
44 /* End copied from vc_vchi_dispmanx.h */
45
46 static SDL_Cursor *RPI_CreateDefaultCursor(void);
47 static SDL_Cursor *RPI_CreateCursor(SDL_Surface * surface, int hot_x, int hot_y);
48 static int RPI_ShowCursor(SDL_Cursor * cursor);
49 static void RPI_MoveCursor(SDL_Cursor * cursor);
50 static void RPI_FreeCursor(SDL_Cursor * cursor);
51 static void RPI_WarpMouse(SDL_Window * window, int x, int y);
52 static int RPI_WarpMouseGlobal(int x, int y);
53
54 static SDL_Cursor *global_cursor;
55
56 static SDL_Cursor *
RPI_CreateDefaultCursor(void)57 RPI_CreateDefaultCursor(void)
58 {
59 return SDL_CreateCursor(default_cdata, default_cmask, DEFAULT_CWIDTH, DEFAULT_CHEIGHT, DEFAULT_CHOTX, DEFAULT_CHOTY);
60 }
61
62 /* Create a cursor from a surface */
63 static SDL_Cursor *
RPI_CreateCursor(SDL_Surface * surface,int hot_x,int hot_y)64 RPI_CreateCursor(SDL_Surface * surface, int hot_x, int hot_y)
65 {
66 RPI_CursorData *curdata;
67 SDL_Cursor *cursor;
68 int ret;
69 VC_RECT_T dst_rect;
70 Uint32 dummy;
71
72 SDL_assert(surface->format->format == SDL_PIXELFORMAT_ARGB8888);
73 SDL_assert(surface->pitch == surface->w * 4);
74
75 cursor = (SDL_Cursor *) SDL_calloc(1, sizeof(*cursor));
76 if (cursor == NULL) {
77 SDL_OutOfMemory();
78 return NULL;
79 }
80 curdata = (RPI_CursorData *) SDL_calloc(1, sizeof(*curdata));
81 if (curdata == NULL) {
82 SDL_OutOfMemory();
83 SDL_free(cursor);
84 return NULL;
85 }
86
87 curdata->hot_x = hot_x;
88 curdata->hot_y = hot_y;
89 curdata->w = surface->w;
90 curdata->h = surface->h;
91
92 /* This usage is inspired by Wayland/Weston RPI code, how they figured this out is anyone's guess */
93 curdata->resource = vc_dispmanx_resource_create(VC_IMAGE_ARGB8888, surface->w | (surface->pitch << 16), surface->h | (surface->h << 16), &dummy);
94 SDL_assert(curdata->resource);
95 vc_dispmanx_rect_set(&dst_rect, 0, 0, curdata->w, curdata->h);
96 /* A note from Weston:
97 * vc_dispmanx_resource_write_data() ignores ifmt,
98 * rect.x, rect.width, and uses stride only for computing
99 * the size of the transfer as rect.height * stride.
100 * Therefore we can only write rows starting at x=0.
101 */
102 ret = vc_dispmanx_resource_write_data(curdata->resource, VC_IMAGE_ARGB8888, surface->pitch, surface->pixels, &dst_rect);
103 SDL_assert (ret == DISPMANX_SUCCESS);
104
105 cursor->driverdata = curdata;
106
107 return cursor;
108
109 }
110
111 /* Show the specified cursor, or hide if cursor is NULL */
112 static int
RPI_ShowCursor(SDL_Cursor * cursor)113 RPI_ShowCursor(SDL_Cursor * cursor)
114 {
115 int ret;
116 DISPMANX_UPDATE_HANDLE_T update;
117 RPI_CursorData *curdata;
118 VC_RECT_T src_rect, dst_rect;
119 SDL_Mouse *mouse;
120 SDL_VideoDisplay *display;
121 SDL_DisplayData *data;
122 VC_DISPMANX_ALPHA_T alpha = { DISPMANX_FLAGS_ALPHA_FROM_SOURCE /* flags */ , 255 /*opacity 0->255*/, 0 /* mask */ };
123 uint32_t layer = SDL_RPI_MOUSELAYER;
124 const char *env;
125
126 mouse = SDL_GetMouse();
127 if (mouse == NULL) {
128 return -1;
129 }
130
131 if (cursor != global_cursor) {
132 if (global_cursor != NULL) {
133 curdata = (RPI_CursorData *) global_cursor->driverdata;
134 if (curdata && curdata->element > DISPMANX_NO_HANDLE) {
135 update = vc_dispmanx_update_start(0);
136 SDL_assert(update);
137 ret = vc_dispmanx_element_remove(update, curdata->element);
138 SDL_assert(ret == DISPMANX_SUCCESS);
139 ret = vc_dispmanx_update_submit_sync(update);
140 SDL_assert(ret == DISPMANX_SUCCESS);
141 curdata->element = DISPMANX_NO_HANDLE;
142 }
143 }
144 global_cursor = cursor;
145 }
146
147 if (cursor == NULL) {
148 return 0;
149 }
150
151 curdata = (RPI_CursorData *) cursor->driverdata;
152 if (curdata == NULL) {
153 return -1;
154 }
155
156 if (mouse->focus == NULL) {
157 return -1;
158 }
159
160 display = SDL_GetDisplayForWindow(mouse->focus);
161 if (display == NULL) {
162 return -1;
163 }
164
165 data = (SDL_DisplayData*) display->driverdata;
166 if (data == NULL) {
167 return -1;
168 }
169
170 if (curdata->element == DISPMANX_NO_HANDLE) {
171 vc_dispmanx_rect_set(&src_rect, 0, 0, curdata->w << 16, curdata->h << 16);
172 vc_dispmanx_rect_set(&dst_rect, mouse->x - curdata->hot_x, mouse->y - curdata->hot_y, curdata->w, curdata->h);
173
174 update = vc_dispmanx_update_start(0);
175 SDL_assert(update);
176
177 env = SDL_GetHint(SDL_HINT_RPI_VIDEO_LAYER);
178 if (env) {
179 layer = SDL_atoi(env) + 1;
180 }
181
182 curdata->element = vc_dispmanx_element_add(update,
183 data->dispman_display,
184 layer,
185 &dst_rect,
186 curdata->resource,
187 &src_rect,
188 DISPMANX_PROTECTION_NONE,
189 &alpha,
190 DISPMANX_NO_HANDLE, // clamp
191 DISPMANX_NO_ROTATE);
192 SDL_assert(curdata->element > DISPMANX_NO_HANDLE);
193 ret = vc_dispmanx_update_submit_sync(update);
194 SDL_assert(ret == DISPMANX_SUCCESS);
195 }
196
197 return 0;
198 }
199
200 /* Free a window manager cursor */
201 static void
RPI_FreeCursor(SDL_Cursor * cursor)202 RPI_FreeCursor(SDL_Cursor * cursor)
203 {
204 int ret;
205 DISPMANX_UPDATE_HANDLE_T update;
206 RPI_CursorData *curdata;
207
208 if (cursor != NULL) {
209 curdata = (RPI_CursorData *) cursor->driverdata;
210
211 if (curdata != NULL) {
212 if (curdata->element != DISPMANX_NO_HANDLE) {
213 update = vc_dispmanx_update_start(0);
214 SDL_assert(update);
215 ret = vc_dispmanx_element_remove(update, curdata->element);
216 SDL_assert(ret == DISPMANX_SUCCESS);
217 ret = vc_dispmanx_update_submit_sync(update);
218 SDL_assert(ret == DISPMANX_SUCCESS);
219 }
220
221 if (curdata->resource != DISPMANX_NO_HANDLE) {
222 ret = vc_dispmanx_resource_delete(curdata->resource);
223 SDL_assert(ret == DISPMANX_SUCCESS);
224 }
225
226 SDL_free(cursor->driverdata);
227 }
228 SDL_free(cursor);
229 if (cursor == global_cursor) {
230 global_cursor = NULL;
231 }
232 }
233 }
234
235 /* Warp the mouse to (x,y) */
236 static void
RPI_WarpMouse(SDL_Window * window,int x,int y)237 RPI_WarpMouse(SDL_Window * window, int x, int y)
238 {
239 RPI_WarpMouseGlobal(x, y);
240 }
241
242 /* Warp the mouse to (x,y) */
243 static int
RPI_WarpMouseGlobal(int x,int y)244 RPI_WarpMouseGlobal(int x, int y)
245 {
246 RPI_CursorData *curdata;
247 DISPMANX_UPDATE_HANDLE_T update;
248 int ret;
249 VC_RECT_T dst_rect;
250 VC_RECT_T src_rect;
251 SDL_Mouse *mouse = SDL_GetMouse();
252
253 if (mouse == NULL || mouse->cur_cursor == NULL || mouse->cur_cursor->driverdata == NULL) {
254 return 0;
255 }
256
257 /* Update internal mouse position. */
258 SDL_SendMouseMotion(mouse->focus, mouse->mouseID, 0, x, y);
259
260 curdata = (RPI_CursorData *) mouse->cur_cursor->driverdata;
261 if (curdata->element == DISPMANX_NO_HANDLE) {
262 return 0;
263 }
264
265 update = vc_dispmanx_update_start(0);
266 if (!update) {
267 return 0;
268 }
269
270 src_rect.x = 0;
271 src_rect.y = 0;
272 src_rect.width = curdata->w << 16;
273 src_rect.height = curdata->h << 16;
274 dst_rect.x = x - curdata->hot_x;
275 dst_rect.y = y - curdata->hot_y;
276 dst_rect.width = curdata->w;
277 dst_rect.height = curdata->h;
278
279 ret = vc_dispmanx_element_change_attributes(
280 update,
281 curdata->element,
282 0,
283 0,
284 0,
285 &dst_rect,
286 &src_rect,
287 DISPMANX_NO_HANDLE,
288 DISPMANX_NO_ROTATE);
289 if (ret != DISPMANX_SUCCESS) {
290 return SDL_SetError("vc_dispmanx_element_change_attributes() failed");
291 }
292
293 /* Submit asynchronously, otherwise the peformance suffers a lot */
294 ret = vc_dispmanx_update_submit(update, 0, NULL);
295 if (ret != DISPMANX_SUCCESS) {
296 return SDL_SetError("vc_dispmanx_update_submit() failed");
297 }
298 return 0;
299 }
300
301 /* Warp the mouse to (x,y) */
302 static int
RPI_WarpMouseGlobalGraphicOnly(int x,int y)303 RPI_WarpMouseGlobalGraphicOnly(int x, int y)
304 {
305 RPI_CursorData *curdata;
306 DISPMANX_UPDATE_HANDLE_T update;
307 int ret;
308 VC_RECT_T dst_rect;
309 VC_RECT_T src_rect;
310 SDL_Mouse *mouse = SDL_GetMouse();
311
312 if (mouse == NULL || mouse->cur_cursor == NULL || mouse->cur_cursor->driverdata == NULL) {
313 return 0;
314 }
315
316 curdata = (RPI_CursorData *) mouse->cur_cursor->driverdata;
317 if (curdata->element == DISPMANX_NO_HANDLE) {
318 return 0;
319 }
320
321 update = vc_dispmanx_update_start(0);
322 if (!update) {
323 return 0;
324 }
325
326 src_rect.x = 0;
327 src_rect.y = 0;
328 src_rect.width = curdata->w << 16;
329 src_rect.height = curdata->h << 16;
330 dst_rect.x = x - curdata->hot_x;
331 dst_rect.y = y - curdata->hot_y;
332 dst_rect.width = curdata->w;
333 dst_rect.height = curdata->h;
334
335 ret = vc_dispmanx_element_change_attributes(
336 update,
337 curdata->element,
338 0,
339 0,
340 0,
341 &dst_rect,
342 &src_rect,
343 DISPMANX_NO_HANDLE,
344 DISPMANX_NO_ROTATE);
345 if (ret != DISPMANX_SUCCESS) {
346 return SDL_SetError("vc_dispmanx_element_change_attributes() failed");
347 }
348
349 /* Submit asynchronously, otherwise the peformance suffers a lot */
350 ret = vc_dispmanx_update_submit(update, 0, NULL);
351 if (ret != DISPMANX_SUCCESS) {
352 return SDL_SetError("vc_dispmanx_update_submit() failed");
353 }
354 return 0;
355 }
356
357 void
RPI_InitMouse(_THIS)358 RPI_InitMouse(_THIS)
359 {
360 /* FIXME: Using UDEV it should be possible to scan all mice
361 * but there's no point in doing so as there's no multimice support...yet!
362 */
363 SDL_Mouse *mouse = SDL_GetMouse();
364
365 mouse->CreateCursor = RPI_CreateCursor;
366 mouse->ShowCursor = RPI_ShowCursor;
367 mouse->MoveCursor = RPI_MoveCursor;
368 mouse->FreeCursor = RPI_FreeCursor;
369 mouse->WarpMouse = RPI_WarpMouse;
370 mouse->WarpMouseGlobal = RPI_WarpMouseGlobal;
371
372 SDL_SetDefaultCursor(RPI_CreateDefaultCursor());
373 }
374
375 void
RPI_QuitMouse(_THIS)376 RPI_QuitMouse(_THIS)
377 {
378 }
379
380 /* This is called when a mouse motion event occurs */
381 static void
RPI_MoveCursor(SDL_Cursor * cursor)382 RPI_MoveCursor(SDL_Cursor * cursor)
383 {
384 SDL_Mouse *mouse = SDL_GetMouse();
385 /* We must NOT call SDL_SendMouseMotion() on the next call or we will enter recursivity,
386 * so we create a version of WarpMouseGlobal without it. */
387 RPI_WarpMouseGlobalGraphicOnly(mouse->x, mouse->y);
388 }
389
390 #endif /* SDL_VIDEO_DRIVER_RPI */
391
392 /* vi: set ts=4 sw=4 expandtab: */
393