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 
22 #include "../../SDL_internal.h"
23 
24 #if SDL_VIDEO_DRIVER_WAYLAND
25 
26 #include <sys/types.h>
27 #include <sys/mman.h>
28 #include <fcntl.h>
29 #include <unistd.h>
30 #include <stdlib.h>
31 #include <limits.h>
32 
33 #include "../SDL_sysvideo.h"
34 
35 #include "SDL_mouse.h"
36 #include "../../events/SDL_mouse_c.h"
37 #include "SDL_waylandvideo.h"
38 #include "SDL_waylandevents_c.h"
39 
40 #include "SDL_waylanddyn.h"
41 #include "wayland-cursor.h"
42 
43 #include "SDL_assert.h"
44 
45 
46 typedef struct {
47     struct wl_buffer   *buffer;
48     struct wl_surface  *surface;
49 
50     int                hot_x, hot_y;
51     int                w, h;
52 
53     /* Either a preloaded cursor, or one we created ourselves */
54     struct wl_cursor   *cursor;
55     void               *shm_data;
56 } Wayland_CursorData;
57 
58 static int
wayland_create_tmp_file(off_t size)59 wayland_create_tmp_file(off_t size)
60 {
61     static const char template[] = "/sdl-shared-XXXXXX";
62     char *xdg_path;
63     char tmp_path[PATH_MAX];
64     int fd;
65 
66     xdg_path = SDL_getenv("XDG_RUNTIME_DIR");
67     if (!xdg_path) {
68         return -1;
69     }
70 
71     SDL_strlcpy(tmp_path, xdg_path, PATH_MAX);
72     SDL_strlcat(tmp_path, template, PATH_MAX);
73 
74     fd = mkostemp(tmp_path, O_CLOEXEC);
75     if (fd < 0)
76         return -1;
77 
78     if (ftruncate(fd, size) < 0) {
79         close(fd);
80         return -1;
81     }
82 
83     return fd;
84 }
85 
86 static void
mouse_buffer_release(void * data,struct wl_buffer * buffer)87 mouse_buffer_release(void *data, struct wl_buffer *buffer)
88 {
89 }
90 
91 static const struct wl_buffer_listener mouse_buffer_listener = {
92     mouse_buffer_release
93 };
94 
95 static int
create_buffer_from_shm(Wayland_CursorData * d,int width,int height,uint32_t format)96 create_buffer_from_shm(Wayland_CursorData *d,
97                        int width,
98                        int height,
99                        uint32_t format)
100 {
101     SDL_VideoDevice *vd = SDL_GetVideoDevice();
102     SDL_VideoData *data = (SDL_VideoData *) vd->driverdata;
103     struct wl_shm_pool *shm_pool;
104 
105     int stride = width * 4;
106     int size = stride * height;
107 
108     int shm_fd;
109 
110     shm_fd = wayland_create_tmp_file(size);
111     if (shm_fd < 0)
112     {
113         return SDL_SetError("Creating mouse cursor buffer failed.");
114     }
115 
116     d->shm_data = mmap(NULL,
117                        size,
118                        PROT_READ | PROT_WRITE,
119                        MAP_SHARED,
120                        shm_fd,
121                        0);
122     if (d->shm_data == MAP_FAILED) {
123         d->shm_data = NULL;
124         close (shm_fd);
125         return SDL_SetError("mmap() failed.");
126     }
127 
128     SDL_assert(d->shm_data != NULL);
129 
130     shm_pool = wl_shm_create_pool(data->shm, shm_fd, size);
131     d->buffer = wl_shm_pool_create_buffer(shm_pool,
132                                           0,
133                                           width,
134                                           height,
135                                           stride,
136                                           format);
137     wl_buffer_add_listener(d->buffer,
138                            &mouse_buffer_listener,
139                            d);
140 
141     wl_shm_pool_destroy (shm_pool);
142     close (shm_fd);
143 
144     return 0;
145 }
146 
147 static SDL_Cursor *
Wayland_CreateCursor(SDL_Surface * surface,int hot_x,int hot_y)148 Wayland_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y)
149 {
150     SDL_Cursor *cursor;
151 
152     cursor = calloc(1, sizeof (*cursor));
153     if (cursor) {
154         SDL_VideoDevice *vd = SDL_GetVideoDevice ();
155         SDL_VideoData *wd = (SDL_VideoData *) vd->driverdata;
156         Wayland_CursorData *data = calloc (1, sizeof (Wayland_CursorData));
157         if (!data) {
158             SDL_OutOfMemory();
159             free(cursor);
160             return NULL;
161         }
162         cursor->driverdata = (void *) data;
163 
164         /* Assume ARGB8888 */
165         SDL_assert(surface->format->format == SDL_PIXELFORMAT_ARGB8888);
166         SDL_assert(surface->pitch == surface->w * 4);
167 
168         /* Allocate shared memory buffer for this cursor */
169         if (create_buffer_from_shm (data,
170                                     surface->w,
171                                     surface->h,
172                                     WL_SHM_FORMAT_ARGB8888) < 0)
173         {
174             free (cursor->driverdata);
175             free (cursor);
176             return NULL;
177         }
178 
179         SDL_memcpy(data->shm_data,
180                    surface->pixels,
181                    surface->h * surface->pitch);
182 
183         data->surface = wl_compositor_create_surface(wd->compositor);
184         wl_surface_set_user_data(data->surface, NULL);
185 
186         data->hot_x = hot_x;
187         data->hot_y = hot_y;
188         data->w = surface->w;
189         data->h = surface->h;
190     } else {
191         SDL_OutOfMemory();
192     }
193 
194     return cursor;
195 }
196 
197 static SDL_Cursor *
CreateCursorFromWlCursor(SDL_VideoData * d,struct wl_cursor * wlcursor)198 CreateCursorFromWlCursor(SDL_VideoData *d, struct wl_cursor *wlcursor)
199 {
200     SDL_Cursor *cursor;
201 
202     cursor = calloc(1, sizeof (*cursor));
203     if (cursor) {
204         Wayland_CursorData *data = calloc (1, sizeof (Wayland_CursorData));
205         if (!data) {
206             SDL_OutOfMemory();
207             free(cursor);
208             return NULL;
209         }
210         cursor->driverdata = (void *) data;
211 
212         data->buffer = WAYLAND_wl_cursor_image_get_buffer(wlcursor->images[0]);
213         data->surface = wl_compositor_create_surface(d->compositor);
214         wl_surface_set_user_data(data->surface, NULL);
215         data->hot_x = wlcursor->images[0]->hotspot_x;
216         data->hot_y = wlcursor->images[0]->hotspot_y;
217         data->w = wlcursor->images[0]->width;
218         data->h = wlcursor->images[0]->height;
219         data->cursor= wlcursor;
220     } else {
221         SDL_OutOfMemory ();
222     }
223 
224     return cursor;
225 }
226 
227 static SDL_Cursor *
Wayland_CreateDefaultCursor()228 Wayland_CreateDefaultCursor()
229 {
230     SDL_VideoDevice *device = SDL_GetVideoDevice();
231     SDL_VideoData *data = device->driverdata;
232 
233     return CreateCursorFromWlCursor (data,
234                                      WAYLAND_wl_cursor_theme_get_cursor(data->cursor_theme,
235                                                                 "left_ptr"));
236 }
237 
238 static SDL_Cursor *
Wayland_CreateSystemCursor(SDL_SystemCursor id)239 Wayland_CreateSystemCursor(SDL_SystemCursor id)
240 {
241     SDL_VideoDevice *vd = SDL_GetVideoDevice();
242     SDL_VideoData *d = vd->driverdata;
243 
244     struct wl_cursor *cursor = NULL;
245 
246     switch(id)
247     {
248     default:
249         SDL_assert(0);
250         return NULL;
251     case SDL_SYSTEM_CURSOR_ARROW:
252         cursor = WAYLAND_wl_cursor_theme_get_cursor(d->cursor_theme, "left_ptr");
253         break;
254     case SDL_SYSTEM_CURSOR_IBEAM:
255         cursor = WAYLAND_wl_cursor_theme_get_cursor(d->cursor_theme, "xterm");
256         break;
257     case SDL_SYSTEM_CURSOR_WAIT:
258         cursor = WAYLAND_wl_cursor_theme_get_cursor(d->cursor_theme, "watch");
259         break;
260     case SDL_SYSTEM_CURSOR_CROSSHAIR:
261         cursor = WAYLAND_wl_cursor_theme_get_cursor(d->cursor_theme, "hand1");
262         break;
263     case SDL_SYSTEM_CURSOR_WAITARROW:
264         cursor = WAYLAND_wl_cursor_theme_get_cursor(d->cursor_theme, "watch");
265         break;
266     case SDL_SYSTEM_CURSOR_SIZENWSE:
267         cursor = WAYLAND_wl_cursor_theme_get_cursor(d->cursor_theme, "hand1");
268         break;
269     case SDL_SYSTEM_CURSOR_SIZENESW:
270         cursor = WAYLAND_wl_cursor_theme_get_cursor(d->cursor_theme, "hand1");
271         break;
272     case SDL_SYSTEM_CURSOR_SIZEWE:
273         cursor = WAYLAND_wl_cursor_theme_get_cursor(d->cursor_theme, "hand1");
274         break;
275     case SDL_SYSTEM_CURSOR_SIZENS:
276         cursor = WAYLAND_wl_cursor_theme_get_cursor(d->cursor_theme, "hand1");
277         break;
278     case SDL_SYSTEM_CURSOR_SIZEALL:
279         cursor = WAYLAND_wl_cursor_theme_get_cursor(d->cursor_theme, "hand1");
280         break;
281     case SDL_SYSTEM_CURSOR_NO:
282         cursor = WAYLAND_wl_cursor_theme_get_cursor(d->cursor_theme, "xterm");
283         break;
284     case SDL_SYSTEM_CURSOR_HAND:
285         cursor = WAYLAND_wl_cursor_theme_get_cursor(d->cursor_theme, "hand1");
286         break;
287     }
288 
289     return CreateCursorFromWlCursor(d, cursor);
290 }
291 
292 static void
Wayland_FreeCursor(SDL_Cursor * cursor)293 Wayland_FreeCursor(SDL_Cursor *cursor)
294 {
295     Wayland_CursorData *d;
296 
297     if (!cursor)
298         return;
299 
300     d = cursor->driverdata;
301 
302     /* Probably not a cursor we own */
303     if (!d)
304         return;
305 
306     if (d->buffer && !d->cursor)
307         wl_buffer_destroy(d->buffer);
308 
309     if (d->surface)
310         wl_surface_destroy(d->surface);
311 
312     /* Not sure what's meant to happen to shm_data */
313     free (cursor->driverdata);
314     SDL_free(cursor);
315 }
316 
317 static int
Wayland_ShowCursor(SDL_Cursor * cursor)318 Wayland_ShowCursor(SDL_Cursor *cursor)
319 {
320     SDL_VideoDevice *vd = SDL_GetVideoDevice();
321     SDL_VideoData *d = vd->driverdata;
322 
323     struct wl_pointer *pointer = d->pointer;
324 
325     if (!pointer)
326         return -1;
327 
328     if (cursor)
329     {
330         Wayland_CursorData *data = cursor->driverdata;
331 
332         wl_pointer_set_cursor (pointer, 0,
333                                data->surface,
334                                data->hot_x,
335                                data->hot_y);
336         wl_surface_attach(data->surface, data->buffer, 0, 0);
337         wl_surface_damage(data->surface, 0, 0, data->w, data->h);
338         wl_surface_commit(data->surface);
339     }
340     else
341     {
342         wl_pointer_set_cursor (pointer, 0,
343                                NULL,
344                                0,
345                                0);
346     }
347 
348     return 0;
349 }
350 
351 static void
Wayland_WarpMouse(SDL_Window * window,int x,int y)352 Wayland_WarpMouse(SDL_Window *window, int x, int y)
353 {
354     SDL_Unsupported();
355 }
356 
357 static int
Wayland_WarpMouseGlobal(int x,int y)358 Wayland_WarpMouseGlobal(int x, int y)
359 {
360     return SDL_Unsupported();
361 }
362 
363 static int
Wayland_SetRelativeMouseMode(SDL_bool enabled)364 Wayland_SetRelativeMouseMode(SDL_bool enabled)
365 {
366     SDL_VideoDevice *vd = SDL_GetVideoDevice();
367     SDL_VideoData *data = (SDL_VideoData *) vd->driverdata;
368 
369     if (enabled)
370         return Wayland_input_lock_pointer(data->input);
371     else
372         return Wayland_input_unlock_pointer(data->input);
373 }
374 
375 void
Wayland_InitMouse(void)376 Wayland_InitMouse(void)
377 {
378     SDL_Mouse *mouse = SDL_GetMouse();
379 
380     mouse->CreateCursor = Wayland_CreateCursor;
381     mouse->CreateSystemCursor = Wayland_CreateSystemCursor;
382     mouse->ShowCursor = Wayland_ShowCursor;
383     mouse->FreeCursor = Wayland_FreeCursor;
384     mouse->WarpMouse = Wayland_WarpMouse;
385     mouse->WarpMouseGlobal = Wayland_WarpMouseGlobal;
386     mouse->SetRelativeMouseMode = Wayland_SetRelativeMouseMode;
387 
388     SDL_SetDefaultCursor(Wayland_CreateDefaultCursor());
389 }
390 
391 void
Wayland_FiniMouse(void)392 Wayland_FiniMouse(void)
393 {
394     /* This effectively assumes that nobody else
395      * touches SDL_Mouse which is effectively
396      * a singleton */
397 }
398 #endif  /* SDL_VIDEO_DRIVER_WAYLAND */
399