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