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#include "SDL_internal.h"
22
23#ifdef SDL_VIDEO_DRIVER_X11
24
25#include <unistd.h> // For getpid() and readlink()
26
27#include "../../core/linux/SDL_system_theme.h"
28#include "../../events/SDL_keyboard_c.h"
29#include "../../events/SDL_mouse_c.h"
30#include "../SDL_pixels_c.h"
31#include "../SDL_sysvideo.h"
32
33#include "SDL_x11framebuffer.h"
34#include "SDL_x11pen.h"
35#include "SDL_x11touch.h"
36#include "SDL_x11video.h"
37#include "SDL_x11xfixes.h"
38#include "SDL_x11xinput2.h"
39#include "SDL_x11messagebox.h"
40#include "SDL_x11shape.h"
41#include "SDL_x11xsync.h"
42#include "SDL_x11xtest.h"
43
44#ifdef SDL_VIDEO_OPENGL_EGL
45#include "SDL_x11opengles.h"
46#endif
47
48// Initialization/Query functions
49static bool X11_VideoInit(SDL_VideoDevice *_this);
50static void X11_VideoQuit(SDL_VideoDevice *_this);
51
52// X11 driver bootstrap functions
53
54static void X11_DeleteDevice(SDL_VideoDevice *device)
55{
56 SDL_VideoData *data = device->internal;
57 if (device->vulkan_config.loader_handle) {
58 device->Vulkan_UnloadLibrary(device);
59 }
60 if (data->display) {
61 X11_XCloseDisplay(data->display);
62 }
63 if (data->request_display) {
64 X11_XCloseDisplay(data->request_display);
65 }
66 SDL_free(data->windowlist);
67 if (device->wakeup_lock) {
68 SDL_DestroyMutex(device->wakeup_lock);
69 }
70 SDL_free(device->internal);
71 SDL_free(device);
72
73 SDL_X11_UnloadSymbols();
74}
75
76static bool X11_IsXWayland(Display *d)
77{
78 int opcode, event, error;
79 return X11_XQueryExtension(d, "XWAYLAND", &opcode, &event, &error) == True;
80}
81
82static bool X11_CheckCurrentDesktop(const char *name)
83{
84 SDL_Environment *env = SDL_GetEnvironment();
85
86 const char *desktopVar = SDL_GetEnvironmentVariable(env, "DESKTOP_SESSION");
87 if (desktopVar && SDL_strcasecmp(desktopVar, name) == 0) {
88 return true;
89 }
90
91 desktopVar = SDL_GetEnvironmentVariable(env, "XDG_CURRENT_DESKTOP");
92 if (desktopVar && SDL_strcasestr(desktopVar, name)) {
93 return true;
94 }
95
96 return false;
97}
98
99static SDL_VideoDevice *X11_CreateDevice(void)
100{
101 SDL_VideoDevice *device;
102 SDL_VideoData *data;
103 const char *display = NULL; // Use the DISPLAY environment variable
104 Display *x11_display = NULL;
105
106 if (!SDL_X11_LoadSymbols()) {
107 return NULL;
108 }
109
110 /* Need for threading gl calls. This is also required for the proprietary
111 nVidia driver to be threaded. */
112 X11_XInitThreads();
113
114 // Open the display first to be sure that X11 is available
115 x11_display = X11_XOpenDisplay(display);
116
117 if (!x11_display) {
118 SDL_X11_UnloadSymbols();
119 return NULL;
120 }
121
122 // Initialize all variables that we clean on shutdown
123 device = (SDL_VideoDevice *)SDL_calloc(1, sizeof(SDL_VideoDevice));
124 if (!device) {
125 return NULL;
126 }
127 data = (struct SDL_VideoData *)SDL_calloc(1, sizeof(SDL_VideoData));
128 if (!data) {
129 SDL_free(device);
130 return NULL;
131 }
132 device->internal = data;
133
134 data->global_mouse_changed = true;
135
136#ifdef SDL_VIDEO_DRIVER_X11_XFIXES
137 data->active_cursor_confined_window = NULL;
138#endif // SDL_VIDEO_DRIVER_X11_XFIXES
139
140 data->display = x11_display;
141 data->request_display = X11_XOpenDisplay(display);
142 if (!data->request_display) {
143 X11_XCloseDisplay(data->display);
144 SDL_free(device->internal);
145 SDL_free(device);
146 SDL_X11_UnloadSymbols();
147 return NULL;
148 }
149
150 device->wakeup_lock = SDL_CreateMutex();
151
152#ifdef X11_DEBUG
153 X11_XSynchronize(data->display, True);
154#endif
155
156 /* Steam Deck will have an on-screen keyboard, so check their environment
157 * variable so we can make use of SDL_StartTextInput.
158 */
159 data->is_steam_deck = SDL_GetHintBoolean("SteamDeck", false);
160
161 // Set the function pointers
162 device->VideoInit = X11_VideoInit;
163 device->VideoQuit = X11_VideoQuit;
164 device->ResetTouch = X11_ResetTouch;
165 device->GetDisplayModes = X11_GetDisplayModes;
166 device->GetDisplayBounds = X11_GetDisplayBounds;
167 device->GetDisplayUsableBounds = X11_GetDisplayUsableBounds;
168 device->GetWindowICCProfile = X11_GetWindowICCProfile;
169 device->SetDisplayMode = X11_SetDisplayMode;
170 device->SuspendScreenSaver = X11_SuspendScreenSaver;
171 device->PumpEvents = X11_PumpEvents;
172 device->WaitEventTimeout = X11_WaitEventTimeout;
173 device->SendWakeupEvent = X11_SendWakeupEvent;
174
175 device->CreateSDLWindow = X11_CreateWindow;
176 device->SetWindowTitle = X11_SetWindowTitle;
177 device->SetWindowIcon = X11_SetWindowIcon;
178 device->SetWindowPosition = X11_SetWindowPosition;
179 device->SetWindowSize = X11_SetWindowSize;
180 device->SetWindowMinimumSize = X11_SetWindowMinimumSize;
181 device->SetWindowMaximumSize = X11_SetWindowMaximumSize;
182 device->SetWindowAspectRatio = X11_SetWindowAspectRatio;
183 device->GetWindowBordersSize = X11_GetWindowBordersSize;
184 device->SetWindowOpacity = X11_SetWindowOpacity;
185 device->SetWindowParent = X11_SetWindowParent;
186 device->SetWindowModal = X11_SetWindowModal;
187 device->ShowWindow = X11_ShowWindow;
188 device->HideWindow = X11_HideWindow;
189 device->RaiseWindow = X11_RaiseWindow;
190 device->MaximizeWindow = X11_MaximizeWindow;
191 device->MinimizeWindow = X11_MinimizeWindow;
192 device->RestoreWindow = X11_RestoreWindow;
193 device->SetWindowBordered = X11_SetWindowBordered;
194 device->SetWindowResizable = X11_SetWindowResizable;
195 device->SetWindowAlwaysOnTop = X11_SetWindowAlwaysOnTop;
196 device->SetWindowFullscreen = X11_SetWindowFullscreen;
197 device->SetWindowMouseGrab = X11_SetWindowMouseGrab;
198 device->SetWindowKeyboardGrab = X11_SetWindowKeyboardGrab;
199 device->DestroyWindow = X11_DestroyWindow;
200 device->CreateWindowFramebuffer = X11_CreateWindowFramebuffer;
201 device->UpdateWindowFramebuffer = X11_UpdateWindowFramebuffer;
202 device->DestroyWindowFramebuffer = X11_DestroyWindowFramebuffer;
203 device->SetWindowHitTest = X11_SetWindowHitTest;
204 device->AcceptDragAndDrop = X11_AcceptDragAndDrop;
205 device->UpdateWindowShape = X11_UpdateWindowShape;
206 device->FlashWindow = X11_FlashWindow;
207 device->ShowWindowSystemMenu = X11_ShowWindowSystemMenu;
208 device->SetWindowFocusable = X11_SetWindowFocusable;
209 device->SyncWindow = X11_SyncWindow;
210
211#ifdef SDL_VIDEO_DRIVER_X11_XFIXES
212 device->SetWindowMouseRect = X11_SetWindowMouseRect;
213#endif // SDL_VIDEO_DRIVER_X11_XFIXES
214
215#ifdef SDL_VIDEO_OPENGL_GLX
216 device->GL_LoadLibrary = X11_GL_LoadLibrary;
217 device->GL_GetProcAddress = X11_GL_GetProcAddress;
218 device->GL_UnloadLibrary = X11_GL_UnloadLibrary;
219 device->GL_CreateContext = X11_GL_CreateContext;
220 device->GL_MakeCurrent = X11_GL_MakeCurrent;
221 device->GL_SetSwapInterval = X11_GL_SetSwapInterval;
222 device->GL_GetSwapInterval = X11_GL_GetSwapInterval;
223 device->GL_SwapWindow = X11_GL_SwapWindow;
224 device->GL_DestroyContext = X11_GL_DestroyContext;
225 device->GL_GetEGLSurface = NULL;
226#endif
227#ifdef SDL_VIDEO_OPENGL_EGL
228#ifdef SDL_VIDEO_OPENGL_GLX
229 if (SDL_GetHintBoolean(SDL_HINT_VIDEO_FORCE_EGL, false)) {
230#endif
231 device->GL_LoadLibrary = X11_GLES_LoadLibrary;
232 device->GL_GetProcAddress = X11_GLES_GetProcAddress;
233 device->GL_UnloadLibrary = X11_GLES_UnloadLibrary;
234 device->GL_CreateContext = X11_GLES_CreateContext;
235 device->GL_MakeCurrent = X11_GLES_MakeCurrent;
236 device->GL_SetSwapInterval = X11_GLES_SetSwapInterval;
237 device->GL_GetSwapInterval = X11_GLES_GetSwapInterval;
238 device->GL_SwapWindow = X11_GLES_SwapWindow;
239 device->GL_DestroyContext = X11_GLES_DestroyContext;
240 device->GL_GetEGLSurface = X11_GLES_GetEGLSurface;
241#ifdef SDL_VIDEO_OPENGL_GLX
242 }
243#endif
244#endif
245
246 device->GetTextMimeTypes = X11_GetTextMimeTypes;
247 device->SetClipboardData = X11_SetClipboardData;
248 device->GetClipboardData = X11_GetClipboardData;
249 device->HasClipboardData = X11_HasClipboardData;
250 device->SetPrimarySelectionText = X11_SetPrimarySelectionText;
251 device->GetPrimarySelectionText = X11_GetPrimarySelectionText;
252 device->HasPrimarySelectionText = X11_HasPrimarySelectionText;
253 device->StartTextInput = X11_StartTextInput;
254 device->StopTextInput = X11_StopTextInput;
255 device->UpdateTextInputArea = X11_UpdateTextInputArea;
256 device->HasScreenKeyboardSupport = X11_HasScreenKeyboardSupport;
257 device->ShowScreenKeyboard = X11_ShowScreenKeyboard;
258 device->HideScreenKeyboard = X11_HideScreenKeyboard;
259 device->IsScreenKeyboardShown = X11_IsScreenKeyboardShown;
260
261 device->free = X11_DeleteDevice;
262
263#ifdef SDL_VIDEO_VULKAN
264 device->Vulkan_LoadLibrary = X11_Vulkan_LoadLibrary;
265 device->Vulkan_UnloadLibrary = X11_Vulkan_UnloadLibrary;
266 device->Vulkan_GetInstanceExtensions = X11_Vulkan_GetInstanceExtensions;
267 device->Vulkan_CreateSurface = X11_Vulkan_CreateSurface;
268 device->Vulkan_DestroySurface = X11_Vulkan_DestroySurface;
269 device->Vulkan_GetPresentationSupport = X11_Vulkan_GetPresentationSupport;
270#endif
271
272#ifdef SDL_USE_LIBDBUS
273 if (SDL_SystemTheme_Init())
274 device->system_theme = SDL_SystemTheme_Get();
275#endif
276
277 device->device_caps = VIDEO_DEVICE_CAPS_HAS_POPUP_WINDOW_SUPPORT;
278
279 /* Openbox doesn't send the new window dimensions when entering fullscreen, so the events must be synthesized.
280 * This is otherwise not wanted, as it can break fullscreen window positioning on multi-monitor configurations.
281 */
282 if (!X11_CheckCurrentDesktop("openbox")) {
283 device->device_caps |= VIDEO_DEVICE_CAPS_SENDS_DISPLAY_CHANGES;
284 }
285
286 data->is_xwayland = X11_IsXWayland(x11_display);
287 if (data->is_xwayland) {
288 device->device_caps |= VIDEO_DEVICE_CAPS_MODE_SWITCHING_EMULATED |
289 VIDEO_DEVICE_CAPS_DISABLE_MOUSE_WARP_ON_FULLSCREEN_TRANSITIONS;
290 }
291
292 return device;
293}
294
295VideoBootStrap X11_bootstrap = {
296 "x11", "SDL X11 video driver",
297 X11_CreateDevice,
298 X11_ShowMessageBox,
299 false
300};
301
302static int (*handler)(Display *, XErrorEvent *) = NULL;
303static int X11_CheckWindowManagerErrorHandler(Display *d, XErrorEvent *e)
304{
305 if (e->error_code == BadWindow) {
306 return 0;
307 } else {
308 return handler(d, e);
309 }
310}
311
312static void X11_CheckWindowManager(SDL_VideoDevice *_this)
313{
314 SDL_VideoData *data = _this->internal;
315 Display *display = data->display;
316 Atom _NET_SUPPORTING_WM_CHECK;
317 int status, real_format;
318 Atom real_type;
319 unsigned long items_read = 0, items_left = 0;
320 unsigned char *propdata = NULL;
321 Window wm_window = 0;
322#ifdef DEBUG_WINDOW_MANAGER
323 char *wm_name;
324#endif
325
326 // Set up a handler to gracefully catch errors
327 X11_XSync(display, False);
328 handler = X11_XSetErrorHandler(X11_CheckWindowManagerErrorHandler);
329
330 _NET_SUPPORTING_WM_CHECK = X11_XInternAtom(display, "_NET_SUPPORTING_WM_CHECK", False);
331 status = X11_XGetWindowProperty(display, DefaultRootWindow(display), _NET_SUPPORTING_WM_CHECK, 0L, 1L, False, XA_WINDOW, &real_type, &real_format, &items_read, &items_left, &propdata);
332 if (status == Success) {
333 if (items_read) {
334 wm_window = ((Window *)propdata)[0];
335 }
336 if (propdata) {
337 X11_XFree(propdata);
338 propdata = NULL;
339 }
340 }
341
342 if (wm_window) {
343 status = X11_XGetWindowProperty(display, wm_window, _NET_SUPPORTING_WM_CHECK, 0L, 1L, False, XA_WINDOW, &real_type, &real_format, &items_read, &items_left, &propdata);
344 if (status != Success || !items_read || wm_window != ((Window *)propdata)[0]) {
345 wm_window = None;
346 }
347 if (status == Success && propdata) {
348 X11_XFree(propdata);
349 propdata = NULL;
350 }
351 }
352
353 // Reset the error handler, we're done checking
354 X11_XSync(display, False);
355 X11_XSetErrorHandler(handler);
356
357 if (!wm_window) {
358#ifdef DEBUG_WINDOW_MANAGER
359 printf("Couldn't get _NET_SUPPORTING_WM_CHECK property\n");
360#endif
361 return;
362 }
363 data->net_wm = true;
364
365#ifdef DEBUG_WINDOW_MANAGER
366 wm_name = X11_GetWindowTitle(_this, wm_window);
367 printf("Window manager: %s\n", wm_name);
368 SDL_free(wm_name);
369#endif
370}
371
372static bool X11_VideoInit(SDL_VideoDevice *_this)
373{
374 SDL_VideoData *data = _this->internal;
375
376 // Get the process PID to be associated to the window
377 data->pid = getpid();
378
379 // I have no idea how random this actually is, or has to be.
380 data->window_group = (XID)(((size_t)data->pid) ^ ((size_t)_this));
381
382 // Look up some useful Atoms
383#define GET_ATOM(X) data->atoms.X = X11_XInternAtom(data->display, #X, False)
384 GET_ATOM(WM_PROTOCOLS);
385 GET_ATOM(WM_DELETE_WINDOW);
386 GET_ATOM(WM_TAKE_FOCUS);
387 GET_ATOM(WM_NAME);
388 GET_ATOM(WM_TRANSIENT_FOR);
389 GET_ATOM(_NET_WM_STATE);
390 GET_ATOM(_NET_WM_STATE_HIDDEN);
391 GET_ATOM(_NET_WM_STATE_FOCUSED);
392 GET_ATOM(_NET_WM_STATE_MAXIMIZED_VERT);
393 GET_ATOM(_NET_WM_STATE_MAXIMIZED_HORZ);
394 GET_ATOM(_NET_WM_STATE_FULLSCREEN);
395 GET_ATOM(_NET_WM_STATE_ABOVE);
396 GET_ATOM(_NET_WM_STATE_SKIP_TASKBAR);
397 GET_ATOM(_NET_WM_STATE_SKIP_PAGER);
398 GET_ATOM(_NET_WM_MOVERESIZE);
399 GET_ATOM(_NET_WM_STATE_MODAL);
400 GET_ATOM(_NET_WM_ALLOWED_ACTIONS);
401 GET_ATOM(_NET_WM_ACTION_FULLSCREEN);
402 GET_ATOM(_NET_WM_NAME);
403 GET_ATOM(_NET_WM_ICON_NAME);
404 GET_ATOM(_NET_WM_ICON);
405 GET_ATOM(_NET_WM_PING);
406 GET_ATOM(_NET_WM_SYNC_REQUEST);
407 GET_ATOM(_NET_WM_SYNC_REQUEST_COUNTER);
408 GET_ATOM(_NET_WM_WINDOW_OPACITY);
409 GET_ATOM(_NET_WM_USER_TIME);
410 GET_ATOM(_NET_ACTIVE_WINDOW);
411 GET_ATOM(_NET_FRAME_EXTENTS);
412 GET_ATOM(_SDL_WAKEUP);
413 GET_ATOM(UTF8_STRING);
414 GET_ATOM(PRIMARY);
415 GET_ATOM(CLIPBOARD);
416 GET_ATOM(INCR);
417 GET_ATOM(SDL_SELECTION);
418 GET_ATOM(TARGETS);
419 GET_ATOM(SDL_FORMATS);
420 GET_ATOM(XdndAware);
421 GET_ATOM(XdndEnter);
422 GET_ATOM(XdndLeave);
423 GET_ATOM(XdndPosition);
424 GET_ATOM(XdndStatus);
425 GET_ATOM(XdndTypeList);
426 GET_ATOM(XdndActionCopy);
427 GET_ATOM(XdndDrop);
428 GET_ATOM(XdndFinished);
429 GET_ATOM(XdndSelection);
430 GET_ATOM(XKLAVIER_STATE);
431
432 // Detect the window manager
433 X11_CheckWindowManager(_this);
434
435 if (!X11_InitModes(_this)) {
436 return false;
437 }
438
439 if (!X11_InitXinput2(_this)) {
440 // Assume a mouse and keyboard are attached
441 SDL_AddKeyboard(SDL_DEFAULT_KEYBOARD_ID, NULL, false);
442 SDL_AddMouse(SDL_DEFAULT_MOUSE_ID, NULL, false);
443 }
444
445#ifdef SDL_VIDEO_DRIVER_X11_XFIXES
446 X11_InitXfixes(_this);
447#endif
448
449 X11_InitXsettings(_this);
450
451#ifdef SDL_VIDEO_DRIVER_X11_XSYNC
452 X11_InitXsync(_this);
453#endif
454
455#ifdef SDL_VIDEO_DRIVER_X11_XTEST
456 X11_InitXTest(_this);
457#endif
458
459#ifndef X_HAVE_UTF8_STRING
460#warning X server does not support UTF8_STRING, a feature introduced in 2000! This is likely to become a hard error in a future libSDL3.
461#endif
462
463 if (!X11_InitKeyboard(_this)) {
464 return false;
465 }
466 X11_InitMouse(_this);
467
468 X11_InitTouch(_this);
469
470 X11_InitPen(_this);
471
472 return true;
473}
474
475void X11_VideoQuit(SDL_VideoDevice *_this)
476{
477 SDL_VideoData *data = _this->internal;
478
479 if (data->clipboard_window) {
480 X11_XDestroyWindow(data->display, data->clipboard_window);
481 }
482
483 if (data->xsettings_window) {
484 X11_XDestroyWindow(data->display, data->xsettings_window);
485 }
486
487#ifdef X_HAVE_UTF8_STRING
488 if (data->im) {
489 X11_XCloseIM(data->im);
490 }
491#endif
492
493 X11_QuitModes(_this);
494 X11_QuitKeyboard(_this);
495 X11_QuitMouse(_this);
496 X11_QuitTouch(_this);
497 X11_QuitPen(_this);
498 X11_QuitClipboard(_this);
499 X11_QuitXsettings(_this);
500}
501
502bool X11_UseDirectColorVisuals(void)
503{
504 if (SDL_GetHintBoolean(SDL_HINT_VIDEO_X11_NODIRECTCOLOR, false)) {
505 return false;
506 }
507 return true;
508}
509
510#endif // SDL_VIDEO_DRIVER_X11
511