1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2021 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 "SDL_video.h"
27#include "SDL_mouse.h"
28#include "SDL_stdinc.h"
29#include "../../events/SDL_events_c.h"
30
31#include "SDL_waylandvideo.h"
32#include "SDL_waylandevents_c.h"
33#include "SDL_waylandwindow.h"
34#include "SDL_waylandopengles.h"
35#include "SDL_waylandmouse.h"
36#include "SDL_waylandkeyboard.h"
37#include "SDL_waylandtouch.h"
38#include "SDL_waylandclipboard.h"
39#include "SDL_waylandvulkan.h"
40
41#include <sys/types.h>
42#include <unistd.h>
43#include <fcntl.h>
44#include <xkbcommon/xkbcommon.h>
45
46#include "SDL_waylanddyn.h"
47#include <wayland-util.h>
48
49#include "xdg-shell-client-protocol.h"
50#include "xdg-shell-unstable-v6-client-protocol.h"
51#include "xdg-decoration-unstable-v1-client-protocol.h"
52#include "keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h"
53#include "idle-inhibit-unstable-v1-client-protocol.h"
54
55#define WAYLANDVID_DRIVER_NAME "wayland"
56
57/* Initialization/Query functions */
58static int
59Wayland_VideoInit(_THIS);
60
61static void
62Wayland_GetDisplayModes(_THIS, SDL_VideoDisplay *sdl_display);
63static int
64Wayland_SetDisplayMode(_THIS, SDL_VideoDisplay *display, SDL_DisplayMode *mode);
65
66static void
67Wayland_VideoQuit(_THIS);
68
69/* Find out what class name we should use
70 * Based on src/video/x11/SDL_x11video.c */
71static char *
72get_classname()
73{
74/* !!! FIXME: this is probably wrong, albeit harmless in many common cases. From protocol spec:
75 "The surface class identifies the general class of applications
76 to which the surface belongs. A common convention is to use the
77 file name (or the full path if it is a non-standard location) of
78 the application's .desktop file as the class." */
79
80 char *spot;
81#if defined(__LINUX__) || defined(__FREEBSD__)
82 char procfile[1024];
83 char linkfile[1024];
84 int linksize;
85#endif
86
87 /* First allow environment variable override */
88 spot = SDL_getenv("SDL_VIDEO_WAYLAND_WMCLASS");
89 if (spot) {
90 return SDL_strdup(spot);
91 } else {
92 /* Fallback to the "old" envvar */
93 spot = SDL_getenv("SDL_VIDEO_X11_WMCLASS");
94 if (spot) {
95 return SDL_strdup(spot);
96 }
97 }
98
99 /* Next look at the application's executable name */
100#if defined(__LINUX__) || defined(__FREEBSD__)
101#if defined(__LINUX__)
102 SDL_snprintf(procfile, SDL_arraysize(procfile), "/proc/%d/exe", getpid());
103#elif defined(__FREEBSD__)
104 SDL_snprintf(procfile, SDL_arraysize(procfile), "/proc/%d/file",
105 getpid());
106#else
107#error Where can we find the executable name?
108#endif
109 linksize = readlink(procfile, linkfile, sizeof(linkfile) - 1);
110 if (linksize > 0) {
111 linkfile[linksize] = '\0';
112 spot = SDL_strrchr(linkfile, '/');
113 if (spot) {
114 return SDL_strdup(spot + 1);
115 } else {
116 return SDL_strdup(linkfile);
117 }
118 }
119#endif /* __LINUX__ || __FREEBSD__ */
120
121 /* Finally use the default we've used forever */
122 return SDL_strdup("SDL_App");
123}
124
125static void
126Wayland_DeleteDevice(SDL_VideoDevice *device)
127{
128 SDL_VideoData *data = (SDL_VideoData *)device->driverdata;
129 if (data->display) {
130 WAYLAND_wl_display_flush(data->display);
131 WAYLAND_wl_display_disconnect(data->display);
132 }
133 SDL_free(data);
134 SDL_free(device);
135 SDL_WAYLAND_UnloadSymbols();
136}
137
138static SDL_VideoDevice *
139Wayland_CreateDevice(int devindex)
140{
141 SDL_VideoDevice *device;
142 SDL_VideoData *data;
143 struct wl_display *display;
144
145 if (!SDL_WAYLAND_LoadSymbols()) {
146 return NULL;
147 }
148
149 display = WAYLAND_wl_display_connect(NULL);
150 if (display == NULL) {
151 SDL_WAYLAND_UnloadSymbols();
152 return NULL;
153 }
154
155 data = SDL_calloc(1, sizeof(*data));
156 if (data == NULL) {
157 WAYLAND_wl_display_disconnect(display);
158 SDL_WAYLAND_UnloadSymbols();
159 SDL_OutOfMemory();
160 return NULL;
161 }
162
163 data->display = display;
164
165 /* Initialize all variables that we clean on shutdown */
166 device = SDL_calloc(1, sizeof(SDL_VideoDevice));
167 if (!device) {
168 SDL_free(data);
169 WAYLAND_wl_display_disconnect(display);
170 SDL_WAYLAND_UnloadSymbols();
171 SDL_OutOfMemory();
172 return NULL;
173 }
174
175 device->driverdata = data;
176
177 /* Set the function pointers */
178 device->VideoInit = Wayland_VideoInit;
179 device->VideoQuit = Wayland_VideoQuit;
180 device->SetDisplayMode = Wayland_SetDisplayMode;
181 device->GetDisplayModes = Wayland_GetDisplayModes;
182 device->GetWindowWMInfo = Wayland_GetWindowWMInfo;
183 device->SuspendScreenSaver = Wayland_SuspendScreenSaver;
184
185 device->PumpEvents = Wayland_PumpEvents;
186
187 device->GL_SwapWindow = Wayland_GLES_SwapWindow;
188 device->GL_GetSwapInterval = Wayland_GLES_GetSwapInterval;
189 device->GL_SetSwapInterval = Wayland_GLES_SetSwapInterval;
190 device->GL_GetDrawableSize = Wayland_GLES_GetDrawableSize;
191 device->GL_MakeCurrent = Wayland_GLES_MakeCurrent;
192 device->GL_CreateContext = Wayland_GLES_CreateContext;
193 device->GL_LoadLibrary = Wayland_GLES_LoadLibrary;
194 device->GL_UnloadLibrary = Wayland_GLES_UnloadLibrary;
195 device->GL_GetProcAddress = Wayland_GLES_GetProcAddress;
196 device->GL_DeleteContext = Wayland_GLES_DeleteContext;
197
198 device->CreateSDLWindow = Wayland_CreateWindow;
199 device->ShowWindow = Wayland_ShowWindow;
200 device->SetWindowFullscreen = Wayland_SetWindowFullscreen;
201 device->MaximizeWindow = Wayland_MaximizeWindow;
202 device->MinimizeWindow = Wayland_MinimizeWindow;
203 device->SetWindowMouseGrab = Wayland_SetWindowMouseGrab;
204 device->SetWindowKeyboardGrab = Wayland_SetWindowKeyboardGrab;
205 device->RestoreWindow = Wayland_RestoreWindow;
206 device->SetWindowBordered = Wayland_SetWindowBordered;
207 device->SetWindowResizable = Wayland_SetWindowResizable;
208 device->SetWindowSize = Wayland_SetWindowSize;
209 device->SetWindowMinimumSize = Wayland_SetWindowMinimumSize;
210 device->SetWindowMaximumSize = Wayland_SetWindowMaximumSize;
211 device->SetWindowTitle = Wayland_SetWindowTitle;
212 device->DestroyWindow = Wayland_DestroyWindow;
213 device->SetWindowHitTest = Wayland_SetWindowHitTest;
214
215 device->SetClipboardText = Wayland_SetClipboardText;
216 device->GetClipboardText = Wayland_GetClipboardText;
217 device->HasClipboardText = Wayland_HasClipboardText;
218 device->StartTextInput = Wayland_StartTextInput;
219 device->StopTextInput = Wayland_StopTextInput;
220 device->SetTextInputRect = Wayland_SetTextInputRect;
221
222#if SDL_VIDEO_VULKAN
223 device->Vulkan_LoadLibrary = Wayland_Vulkan_LoadLibrary;
224 device->Vulkan_UnloadLibrary = Wayland_Vulkan_UnloadLibrary;
225 device->Vulkan_GetInstanceExtensions = Wayland_Vulkan_GetInstanceExtensions;
226 device->Vulkan_CreateSurface = Wayland_Vulkan_CreateSurface;
227 device->Vulkan_GetDrawableSize = Wayland_Vulkan_GetDrawableSize;
228#endif
229
230 device->free = Wayland_DeleteDevice;
231
232 return device;
233}
234
235VideoBootStrap Wayland_bootstrap = {
236 WAYLANDVID_DRIVER_NAME, "SDL Wayland video driver",
237 Wayland_CreateDevice
238};
239
240static void
241display_handle_geometry(void *data,
242 struct wl_output *output,
243 int x, int y,
244 int physical_width,
245 int physical_height,
246 int subpixel,
247 const char *make,
248 const char *model,
249 int transform)
250
251{
252 SDL_WaylandOutputData *driverdata = data;
253
254 driverdata->placeholder.name = SDL_strdup(model);
255 driverdata->transform = transform;
256}
257
258static void
259display_handle_mode(void *data,
260 struct wl_output *output,
261 uint32_t flags,
262 int width,
263 int height,
264 int refresh)
265{
266 SDL_WaylandOutputData* driverdata = data;
267 SDL_DisplayMode mode;
268
269 if (flags & WL_OUTPUT_MODE_CURRENT) {
270 driverdata->width = width;
271 driverdata->height = height;
272 driverdata->refresh = refresh;
273 }
274
275 /* Note that the width/height are NOT multiplied by scale_factor!
276 * This is intentional and is designed to get the unscaled modes, which is
277 * important for high-DPI games intending to use the display mode as the
278 * target drawable size. The scaled desktop mode will be added at the end
279 * when display_handle_done is called (see below).
280 */
281 SDL_zero(mode);
282 mode.format = SDL_PIXELFORMAT_RGB888;
283 if (driverdata->transform & WL_OUTPUT_TRANSFORM_90) {
284 mode.w = height;
285 mode.h = width;
286 } else {
287 mode.w = width;
288 mode.h = height;
289 }
290 mode.refresh_rate = refresh / 1000; /* mHz to Hz */
291 mode.driverdata = driverdata->output;
292 SDL_AddDisplayMode(&driverdata->placeholder, &mode);
293}
294
295static void
296display_handle_done(void *data,
297 struct wl_output *output)
298{
299 SDL_WaylandOutputData* driverdata = data;
300 SDL_DisplayMode mode;
301
302 if (driverdata->done)
303 return;
304
305 driverdata->done = SDL_TRUE;
306
307 SDL_zero(mode);
308 mode.format = SDL_PIXELFORMAT_RGB888;
309 if (driverdata->transform & WL_OUTPUT_TRANSFORM_90) {
310 mode.w = driverdata->height / driverdata->scale_factor;
311 mode.h = driverdata->width / driverdata->scale_factor;
312 } else {
313 mode.w = driverdata->width / driverdata->scale_factor;
314 mode.h = driverdata->height / driverdata->scale_factor;
315 }
316 mode.refresh_rate = driverdata->refresh / 1000; /* mHz to Hz */
317 mode.driverdata = driverdata->output;
318 SDL_AddDisplayMode(&driverdata->placeholder, &mode);
319 driverdata->placeholder.current_mode = mode;
320 driverdata->placeholder.desktop_mode = mode;
321
322 driverdata->placeholder.driverdata = driverdata;
323 SDL_AddVideoDisplay(&driverdata->placeholder, SDL_FALSE);
324 SDL_zero(driverdata->placeholder);
325}
326
327static void
328display_handle_scale(void *data,
329 struct wl_output *output,
330 int32_t factor)
331{
332 SDL_WaylandOutputData *driverdata = data;
333 driverdata->scale_factor = factor;
334}
335
336static const struct wl_output_listener output_listener = {
337 display_handle_geometry,
338 display_handle_mode,
339 display_handle_done,
340 display_handle_scale
341};
342
343static void
344Wayland_add_display(SDL_VideoData *d, uint32_t id)
345{
346 struct wl_output *output;
347 SDL_WaylandOutputData *data;
348
349 output = wl_registry_bind(d->registry, id, &wl_output_interface, 2);
350 if (!output) {
351 SDL_SetError("Failed to retrieve output.");
352 return;
353 }
354 data = SDL_malloc(sizeof *data);
355 SDL_zerop(data);
356 data->output = output;
357 data->scale_factor = 1.0;
358
359 wl_output_add_listener(output, &output_listener, data);
360}
361
362#ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH
363static void
364windowmanager_hints(void *data, struct qt_windowmanager *qt_windowmanager,
365 int32_t show_is_fullscreen)
366{
367}
368
369static void
370windowmanager_quit(void *data, struct qt_windowmanager *qt_windowmanager)
371{
372 SDL_SendQuit();
373}
374
375static const struct qt_windowmanager_listener windowmanager_listener = {
376 windowmanager_hints,
377 windowmanager_quit,
378};
379#endif /* SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH */
380
381
382static void
383handle_ping_zxdg_shell(void *data, struct zxdg_shell_v6 *zxdg, uint32_t serial)
384{
385 zxdg_shell_v6_pong(zxdg, serial);
386}
387
388static const struct zxdg_shell_v6_listener shell_listener_zxdg = {
389 handle_ping_zxdg_shell
390};
391
392
393static void
394handle_ping_xdg_wm_base(void *data, struct xdg_wm_base *xdg, uint32_t serial)
395{
396 xdg_wm_base_pong(xdg, serial);
397}
398
399static const struct xdg_wm_base_listener shell_listener_xdg = {
400 handle_ping_xdg_wm_base
401};
402
403
404static void
405display_handle_global(void *data, struct wl_registry *registry, uint32_t id,
406 const char *interface, uint32_t version)
407{
408 SDL_VideoData *d = data;
409
410 /*printf("WAYLAND INTERFACE: %s\n", interface);*/
411
412 if (strcmp(interface, "wl_compositor") == 0) {
413 d->compositor = wl_registry_bind(d->registry, id, &wl_compositor_interface, SDL_min(3, version));
414 } else if (strcmp(interface, "wl_output") == 0) {
415 Wayland_add_display(d, id);
416 } else if (strcmp(interface, "wl_seat") == 0) {
417 Wayland_display_add_input(d, id, version);
418 } else if (strcmp(interface, "xdg_wm_base") == 0) {
419 d->shell.xdg = wl_registry_bind(d->registry, id, &xdg_wm_base_interface, 1);
420 xdg_wm_base_add_listener(d->shell.xdg, &shell_listener_xdg, NULL);
421 } else if (strcmp(interface, "zxdg_shell_v6") == 0) {
422 d->shell.zxdg = wl_registry_bind(d->registry, id, &zxdg_shell_v6_interface, 1);
423 zxdg_shell_v6_add_listener(d->shell.zxdg, &shell_listener_zxdg, NULL);
424 } else if (strcmp(interface, "wl_shell") == 0) {
425 d->shell.wl = wl_registry_bind(d->registry, id, &wl_shell_interface, 1);
426 } else if (strcmp(interface, "wl_shm") == 0) {
427 d->shm = wl_registry_bind(registry, id, &wl_shm_interface, 1);
428 d->cursor_theme = WAYLAND_wl_cursor_theme_load(NULL, 32, d->shm);
429 } else if (strcmp(interface, "zwp_relative_pointer_manager_v1") == 0) {
430 Wayland_display_add_relative_pointer_manager(d, id);
431 } else if (strcmp(interface, "zwp_pointer_constraints_v1") == 0) {
432 Wayland_display_add_pointer_constraints(d, id);
433 } else if (strcmp(interface, "zwp_keyboard_shortcuts_inhibit_manager_v1") == 0) {
434 d->key_inhibitor_manager = wl_registry_bind(d->registry, id, &zwp_keyboard_shortcuts_inhibit_manager_v1_interface, 1);
435 } else if (strcmp(interface, "zwp_idle_inhibit_manager_v1") == 0) {
436 d->idle_inhibit_manager = wl_registry_bind(d->registry, id, &zwp_idle_inhibit_manager_v1_interface, 1);
437 } else if (strcmp(interface, "wl_data_device_manager") == 0) {
438 Wayland_add_data_device_manager(d, id, version);
439 } else if (strcmp(interface, "zxdg_decoration_manager_v1") == 0) {
440 d->decoration_manager = wl_registry_bind(d->registry, id, &zxdg_decoration_manager_v1_interface, 1);
441
442#ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH
443 } else if (strcmp(interface, "qt_touch_extension") == 0) {
444 Wayland_touch_create(d, id);
445 } else if (strcmp(interface, "qt_surface_extension") == 0) {
446 d->surface_extension = wl_registry_bind(registry, id,
447 &qt_surface_extension_interface, 1);
448 } else if (strcmp(interface, "qt_windowmanager") == 0) {
449 d->windowmanager = wl_registry_bind(registry, id,
450 &qt_windowmanager_interface, 1);
451 qt_windowmanager_add_listener(d->windowmanager, &windowmanager_listener, d);
452#endif /* SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH */
453 }
454}
455
456static void
457display_remove_global(void *data, struct wl_registry *registry, uint32_t id) {}
458
459static const struct wl_registry_listener registry_listener = {
460 display_handle_global,
461 display_remove_global
462};
463
464int
465Wayland_VideoInit(_THIS)
466{
467 SDL_VideoData *data = (SDL_VideoData*)_this->driverdata;
468
469 data->xkb_context = WAYLAND_xkb_context_new(0);
470 if (!data->xkb_context) {
471 return SDL_SetError("Failed to create XKB context");
472 }
473
474 data->registry = wl_display_get_registry(data->display);
475 if (data->registry == NULL) {
476 return SDL_SetError("Failed to get the Wayland registry");
477 }
478
479 wl_registry_add_listener(data->registry, &registry_listener, data);
480
481 // First roundtrip to receive all registry objects.
482 WAYLAND_wl_display_roundtrip(data->display);
483
484 // Second roundtrip to receive all output events.
485 WAYLAND_wl_display_roundtrip(data->display);
486
487 Wayland_InitMouse();
488
489 /* Get the surface class name, usually the name of the application */
490 data->classname = get_classname();
491
492 WAYLAND_wl_display_flush(data->display);
493
494 Wayland_InitKeyboard(_this);
495
496#if SDL_USE_LIBDBUS
497 SDL_DBus_Init();
498#endif
499
500 return 0;
501}
502
503static void
504Wayland_GetDisplayModes(_THIS, SDL_VideoDisplay *sdl_display)
505{
506 // Nothing to do here, everything was already done in the wl_output
507 // callbacks.
508}
509
510static int
511Wayland_SetDisplayMode(_THIS, SDL_VideoDisplay *display, SDL_DisplayMode *mode)
512{
513 return SDL_Unsupported();
514}
515
516void
517Wayland_VideoQuit(_THIS)
518{
519 SDL_VideoData *data = _this->driverdata;
520 int i, j;
521
522 Wayland_FiniMouse ();
523
524 for (i = 0; i < _this->num_displays; ++i) {
525 SDL_VideoDisplay *display = &_this->displays[i];
526
527 wl_output_destroy(((SDL_WaylandOutputData*)display->driverdata)->output);
528 SDL_free(display->driverdata);
529 display->driverdata = NULL;
530
531 for (j = display->num_display_modes; j--;) {
532 display->display_modes[j].driverdata = NULL;
533 }
534 display->desktop_mode.driverdata = NULL;
535 }
536
537 Wayland_display_destroy_input(data);
538 Wayland_display_destroy_pointer_constraints(data);
539 Wayland_display_destroy_relative_pointer_manager(data);
540
541 if (data->idle_inhibit_manager)
542 zwp_idle_inhibit_manager_v1_destroy(data->idle_inhibit_manager);
543
544 if (data->key_inhibitor_manager)
545 zwp_keyboard_shortcuts_inhibit_manager_v1_destroy(data->key_inhibitor_manager);
546
547 if (data->xkb_context) {
548 WAYLAND_xkb_context_unref(data->xkb_context);
549 data->xkb_context = NULL;
550 }
551#ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH
552 if (data->windowmanager)
553 qt_windowmanager_destroy(data->windowmanager);
554
555 if (data->surface_extension)
556 qt_surface_extension_destroy(data->surface_extension);
557
558 Wayland_touch_destroy(data);
559#endif /* SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH */
560
561 if (data->data_device_manager)
562 wl_data_device_manager_destroy(data->data_device_manager);
563
564 if (data->shm)
565 wl_shm_destroy(data->shm);
566
567 if (data->cursor_theme)
568 WAYLAND_wl_cursor_theme_destroy(data->cursor_theme);
569
570 if (data->shell.wl)
571 wl_shell_destroy(data->shell.wl);
572
573 if (data->shell.xdg)
574 xdg_wm_base_destroy(data->shell.xdg);
575
576 if (data->shell.zxdg)
577 zxdg_shell_v6_destroy(data->shell.zxdg);
578
579 if (data->decoration_manager)
580 zxdg_decoration_manager_v1_destroy(data->decoration_manager);
581
582 if (data->compositor)
583 wl_compositor_destroy(data->compositor);
584
585 if (data->registry)
586 wl_registry_destroy(data->registry);
587
588 Wayland_QuitKeyboard(_this);
589
590 SDL_free(data->classname);
591}
592
593#endif /* SDL_VIDEO_DRIVER_WAYLAND */
594
595/* vi: set ts=4 sw=4 expandtab: */
596