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#include "../../SDL_internal.h"
22
23#if SDL_VIDEO_DRIVER_X11
24
25#include <unistd.h> /* For getpid() and readlink() */
26
27#include "SDL_video.h"
28#include "SDL_mouse.h"
29#include "SDL_timer.h"
30#include "SDL_hints.h"
31#include "../SDL_sysvideo.h"
32#include "../SDL_pixels_c.h"
33
34#include "SDL_x11video.h"
35#include "SDL_x11framebuffer.h"
36#include "SDL_x11shape.h"
37#include "SDL_x11touch.h"
38#include "SDL_x11xinput2.h"
39
40#if SDL_VIDEO_OPENGL_EGL
41#include "SDL_x11opengles.h"
42#endif
43
44#include "SDL_x11vulkan.h"
45
46/* Initialization/Query functions */
47static int X11_VideoInit(_THIS);
48static void X11_VideoQuit(_THIS);
49
50/* Find out what class name we should use */
51static char *
52get_classname()
53{
54 char *spot;
55#if defined(__LINUX__) || defined(__FREEBSD__)
56 char procfile[1024];
57 char linkfile[1024];
58 int linksize;
59#endif
60
61 /* First allow environment variable override */
62 spot = SDL_getenv("SDL_VIDEO_X11_WMCLASS");
63 if (spot) {
64 return SDL_strdup(spot);
65 }
66
67 /* Next look at the application's executable name */
68#if defined(__LINUX__) || defined(__FREEBSD__)
69#if defined(__LINUX__)
70 SDL_snprintf(procfile, SDL_arraysize(procfile), "/proc/%d/exe", getpid());
71#elif defined(__FREEBSD__)
72 SDL_snprintf(procfile, SDL_arraysize(procfile), "/proc/%d/file",
73 getpid());
74#else
75#error Where can we find the executable name?
76#endif
77 linksize = readlink(procfile, linkfile, sizeof(linkfile) - 1);
78 if (linksize > 0) {
79 linkfile[linksize] = '\0';
80 spot = SDL_strrchr(linkfile, '/');
81 if (spot) {
82 return SDL_strdup(spot + 1);
83 } else {
84 return SDL_strdup(linkfile);
85 }
86 }
87#endif /* __LINUX__ || __FREEBSD__ */
88
89 /* Finally use the default we've used forever */
90 return SDL_strdup("SDL_App");
91}
92
93/* X11 driver bootstrap functions */
94
95static int (*orig_x11_errhandler) (Display *, XErrorEvent *) = NULL;
96
97static void
98X11_DeleteDevice(SDL_VideoDevice * device)
99{
100 SDL_VideoData *data = (SDL_VideoData *) device->driverdata;
101 if (device->vulkan_config.loader_handle) {
102 device->Vulkan_UnloadLibrary(device);
103 }
104 if (data->display) {
105 X11_XSetErrorHandler(orig_x11_errhandler);
106 X11_XCloseDisplay(data->display);
107 }
108 SDL_free(data->windowlist);
109 SDL_free(device->driverdata);
110 SDL_free(device);
111
112 SDL_X11_UnloadSymbols();
113}
114
115/* An error handler to reset the vidmode and then call the default handler. */
116static SDL_bool safety_net_triggered = SDL_FALSE;
117static int
118X11_SafetyNetErrHandler(Display * d, XErrorEvent * e)
119{
120 SDL_VideoDevice *device = NULL;
121 /* if we trigger an error in our error handler, don't try again. */
122 if (!safety_net_triggered) {
123 safety_net_triggered = SDL_TRUE;
124 device = SDL_GetVideoDevice();
125 if (device != NULL) {
126 int i;
127 for (i = 0; i < device->num_displays; i++) {
128 SDL_VideoDisplay *display = &device->displays[i];
129 if (SDL_memcmp(&display->current_mode, &display->desktop_mode,
130 sizeof (SDL_DisplayMode)) != 0) {
131 X11_SetDisplayMode(device, display, &display->desktop_mode);
132 }
133 }
134 }
135 }
136
137 if (orig_x11_errhandler != NULL) {
138 return orig_x11_errhandler(d, e); /* probably terminate. */
139 }
140
141 return 0;
142}
143
144static SDL_VideoDevice *
145X11_CreateDevice(int devindex)
146{
147 SDL_VideoDevice *device;
148 SDL_VideoData *data;
149 const char *display = NULL; /* Use the DISPLAY environment variable */
150 Display *x11_display = NULL;
151
152 if (!SDL_X11_LoadSymbols()) {
153 return NULL;
154 }
155
156 /* Need for threading gl calls. This is also required for the proprietary
157 nVidia driver to be threaded. */
158 X11_XInitThreads();
159
160 /* Open the display first to be sure that X11 is available */
161 x11_display = X11_XOpenDisplay(display);
162
163 if (!x11_display) {
164 SDL_X11_UnloadSymbols();
165 return NULL;
166 }
167
168 /* Initialize all variables that we clean on shutdown */
169 device = (SDL_VideoDevice *) SDL_calloc(1, sizeof(SDL_VideoDevice));
170 if (!device) {
171 SDL_OutOfMemory();
172 return NULL;
173 }
174 data = (struct SDL_VideoData *) SDL_calloc(1, sizeof(SDL_VideoData));
175 if (!data) {
176 SDL_free(device);
177 SDL_OutOfMemory();
178 return NULL;
179 }
180 device->driverdata = data;
181
182 data->global_mouse_changed = SDL_TRUE;
183
184 data->display = x11_display;
185#ifdef X11_DEBUG
186 X11_XSynchronize(data->display, True);
187#endif
188
189 /* Hook up an X11 error handler to recover the desktop resolution. */
190 safety_net_triggered = SDL_FALSE;
191 orig_x11_errhandler = X11_XSetErrorHandler(X11_SafetyNetErrHandler);
192
193 /* Set the function pointers */
194 device->VideoInit = X11_VideoInit;
195 device->VideoQuit = X11_VideoQuit;
196 device->ResetTouch = X11_ResetTouch;
197 device->GetDisplayModes = X11_GetDisplayModes;
198 device->GetDisplayBounds = X11_GetDisplayBounds;
199 device->GetDisplayUsableBounds = X11_GetDisplayUsableBounds;
200 device->GetDisplayDPI = X11_GetDisplayDPI;
201 device->SetDisplayMode = X11_SetDisplayMode;
202 device->SuspendScreenSaver = X11_SuspendScreenSaver;
203 device->PumpEvents = X11_PumpEvents;
204
205 device->CreateSDLWindow = X11_CreateWindow;
206 device->CreateSDLWindowFrom = X11_CreateWindowFrom;
207 device->SetWindowTitle = X11_SetWindowTitle;
208 device->SetWindowIcon = X11_SetWindowIcon;
209 device->SetWindowPosition = X11_SetWindowPosition;
210 device->SetWindowSize = X11_SetWindowSize;
211 device->SetWindowMinimumSize = X11_SetWindowMinimumSize;
212 device->SetWindowMaximumSize = X11_SetWindowMaximumSize;
213 device->GetWindowBordersSize = X11_GetWindowBordersSize;
214 device->SetWindowOpacity = X11_SetWindowOpacity;
215 device->SetWindowModalFor = X11_SetWindowModalFor;
216 device->SetWindowInputFocus = X11_SetWindowInputFocus;
217 device->ShowWindow = X11_ShowWindow;
218 device->HideWindow = X11_HideWindow;
219 device->RaiseWindow = X11_RaiseWindow;
220 device->MaximizeWindow = X11_MaximizeWindow;
221 device->MinimizeWindow = X11_MinimizeWindow;
222 device->RestoreWindow = X11_RestoreWindow;
223 device->SetWindowBordered = X11_SetWindowBordered;
224 device->SetWindowResizable = X11_SetWindowResizable;
225 device->SetWindowFullscreen = X11_SetWindowFullscreen;
226 device->SetWindowGammaRamp = X11_SetWindowGammaRamp;
227 device->SetWindowMouseGrab = X11_SetWindowMouseGrab;
228 device->SetWindowKeyboardGrab = X11_SetWindowKeyboardGrab;
229 device->DestroyWindow = X11_DestroyWindow;
230 device->CreateWindowFramebuffer = X11_CreateWindowFramebuffer;
231 device->UpdateWindowFramebuffer = X11_UpdateWindowFramebuffer;
232 device->DestroyWindowFramebuffer = X11_DestroyWindowFramebuffer;
233 device->GetWindowWMInfo = X11_GetWindowWMInfo;
234 device->SetWindowHitTest = X11_SetWindowHitTest;
235 device->AcceptDragAndDrop = X11_AcceptDragAndDrop;
236
237 device->shape_driver.CreateShaper = X11_CreateShaper;
238 device->shape_driver.SetWindowShape = X11_SetWindowShape;
239 device->shape_driver.ResizeWindowShape = X11_ResizeWindowShape;
240
241#if SDL_VIDEO_OPENGL_GLX
242 device->GL_LoadLibrary = X11_GL_LoadLibrary;
243 device->GL_GetProcAddress = X11_GL_GetProcAddress;
244 device->GL_UnloadLibrary = X11_GL_UnloadLibrary;
245 device->GL_CreateContext = X11_GL_CreateContext;
246 device->GL_MakeCurrent = X11_GL_MakeCurrent;
247 device->GL_SetSwapInterval = X11_GL_SetSwapInterval;
248 device->GL_GetSwapInterval = X11_GL_GetSwapInterval;
249 device->GL_SwapWindow = X11_GL_SwapWindow;
250 device->GL_DeleteContext = X11_GL_DeleteContext;
251#endif
252#if SDL_VIDEO_OPENGL_EGL
253#if SDL_VIDEO_OPENGL_GLX
254 if (SDL_GetHintBoolean(SDL_HINT_VIDEO_X11_FORCE_EGL, SDL_FALSE)) {
255#endif
256 device->GL_LoadLibrary = X11_GLES_LoadLibrary;
257 device->GL_GetProcAddress = X11_GLES_GetProcAddress;
258 device->GL_UnloadLibrary = X11_GLES_UnloadLibrary;
259 device->GL_CreateContext = X11_GLES_CreateContext;
260 device->GL_MakeCurrent = X11_GLES_MakeCurrent;
261 device->GL_SetSwapInterval = X11_GLES_SetSwapInterval;
262 device->GL_GetSwapInterval = X11_GLES_GetSwapInterval;
263 device->GL_SwapWindow = X11_GLES_SwapWindow;
264 device->GL_DeleteContext = X11_GLES_DeleteContext;
265#if SDL_VIDEO_OPENGL_GLX
266 }
267#endif
268#endif
269
270 device->SetClipboardText = X11_SetClipboardText;
271 device->GetClipboardText = X11_GetClipboardText;
272 device->HasClipboardText = X11_HasClipboardText;
273 device->StartTextInput = X11_StartTextInput;
274 device->StopTextInput = X11_StopTextInput;
275 device->SetTextInputRect = X11_SetTextInputRect;
276
277 device->free = X11_DeleteDevice;
278
279#if SDL_VIDEO_VULKAN
280 device->Vulkan_LoadLibrary = X11_Vulkan_LoadLibrary;
281 device->Vulkan_UnloadLibrary = X11_Vulkan_UnloadLibrary;
282 device->Vulkan_GetInstanceExtensions = X11_Vulkan_GetInstanceExtensions;
283 device->Vulkan_CreateSurface = X11_Vulkan_CreateSurface;
284#endif
285
286 return device;
287}
288
289VideoBootStrap X11_bootstrap = {
290 "x11", "SDL X11 video driver",
291 X11_CreateDevice
292};
293
294static int (*handler) (Display *, XErrorEvent *) = NULL;
295static int
296X11_CheckWindowManagerErrorHandler(Display * d, XErrorEvent * e)
297{
298 if (e->error_code == BadWindow) {
299 return (0);
300 } else {
301 return (handler(d, e));
302 }
303}
304
305static void
306X11_CheckWindowManager(_THIS)
307{
308 SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
309 Display *display = data->display;
310 Atom _NET_SUPPORTING_WM_CHECK;
311 int status, real_format;
312 Atom real_type;
313 unsigned long items_read = 0, items_left = 0;
314 unsigned char *propdata = NULL;
315 Window wm_window = 0;
316#ifdef DEBUG_WINDOW_MANAGER
317 char *wm_name;
318#endif
319
320 /* Set up a handler to gracefully catch errors */
321 X11_XSync(display, False);
322 handler = X11_XSetErrorHandler(X11_CheckWindowManagerErrorHandler);
323
324 _NET_SUPPORTING_WM_CHECK = X11_XInternAtom(display, "_NET_SUPPORTING_WM_CHECK", False);
325 status = X11_XGetWindowProperty(display, DefaultRootWindow(display), _NET_SUPPORTING_WM_CHECK, 0L, 1L, False, XA_WINDOW, &real_type, &real_format, &items_read, &items_left, &propdata);
326 if (status == Success) {
327 if (items_read) {
328 wm_window = ((Window*)propdata)[0];
329 }
330 if (propdata) {
331 X11_XFree(propdata);
332 propdata = NULL;
333 }
334 }
335
336 if (wm_window) {
337 status = X11_XGetWindowProperty(display, wm_window, _NET_SUPPORTING_WM_CHECK, 0L, 1L, False, XA_WINDOW, &real_type, &real_format, &items_read, &items_left, &propdata);
338 if (status != Success || !items_read || wm_window != ((Window*)propdata)[0]) {
339 wm_window = None;
340 }
341 if (status == Success && propdata) {
342 X11_XFree(propdata);
343 propdata = NULL;
344 }
345 }
346
347 /* Reset the error handler, we're done checking */
348 X11_XSync(display, False);
349 X11_XSetErrorHandler(handler);
350
351 if (!wm_window) {
352#ifdef DEBUG_WINDOW_MANAGER
353 printf("Couldn't get _NET_SUPPORTING_WM_CHECK property\n");
354#endif
355 return;
356 }
357 data->net_wm = SDL_TRUE;
358
359#ifdef DEBUG_WINDOW_MANAGER
360 wm_name = X11_GetWindowTitle(_this, wm_window);
361 printf("Window manager: %s\n", wm_name);
362 SDL_free(wm_name);
363#endif
364}
365
366
367int
368X11_VideoInit(_THIS)
369{
370 SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
371
372 /* Get the window class name, usually the name of the application */
373 data->classname = get_classname();
374
375 /* Get the process PID to be associated to the window */
376 data->pid = getpid();
377
378 /* I have no idea how random this actually is, or has to be. */
379 data->window_group = (XID) (((size_t) data->pid) ^ ((size_t) _this));
380
381 /* Look up some useful Atoms */
382#define GET_ATOM(X) data->X = X11_XInternAtom(data->display, #X, False)
383 GET_ATOM(WM_PROTOCOLS);
384 GET_ATOM(WM_DELETE_WINDOW);
385 GET_ATOM(WM_TAKE_FOCUS);
386 GET_ATOM(_NET_WM_STATE);
387 GET_ATOM(_NET_WM_STATE_HIDDEN);
388 GET_ATOM(_NET_WM_STATE_FOCUSED);
389 GET_ATOM(_NET_WM_STATE_MAXIMIZED_VERT);
390 GET_ATOM(_NET_WM_STATE_MAXIMIZED_HORZ);
391 GET_ATOM(_NET_WM_STATE_FULLSCREEN);
392 GET_ATOM(_NET_WM_STATE_ABOVE);
393 GET_ATOM(_NET_WM_STATE_SKIP_TASKBAR);
394 GET_ATOM(_NET_WM_STATE_SKIP_PAGER);
395 GET_ATOM(_NET_WM_ALLOWED_ACTIONS);
396 GET_ATOM(_NET_WM_ACTION_FULLSCREEN);
397 GET_ATOM(_NET_WM_NAME);
398 GET_ATOM(_NET_WM_ICON_NAME);
399 GET_ATOM(_NET_WM_ICON);
400 GET_ATOM(_NET_WM_PING);
401 GET_ATOM(_NET_WM_WINDOW_OPACITY);
402 GET_ATOM(_NET_WM_USER_TIME);
403 GET_ATOM(_NET_ACTIVE_WINDOW);
404 GET_ATOM(_NET_FRAME_EXTENTS);
405 GET_ATOM(UTF8_STRING);
406 GET_ATOM(PRIMARY);
407 GET_ATOM(XdndEnter);
408 GET_ATOM(XdndPosition);
409 GET_ATOM(XdndStatus);
410 GET_ATOM(XdndTypeList);
411 GET_ATOM(XdndActionCopy);
412 GET_ATOM(XdndDrop);
413 GET_ATOM(XdndFinished);
414 GET_ATOM(XdndSelection);
415 GET_ATOM(XKLAVIER_STATE);
416
417 /* Detect the window manager */
418 X11_CheckWindowManager(_this);
419
420 if (X11_InitModes(_this) < 0) {
421 return -1;
422 }
423
424 X11_InitXinput2(_this);
425
426 if (X11_InitKeyboard(_this) != 0) {
427 return -1;
428 }
429 X11_InitMouse(_this);
430
431 X11_InitTouch(_this);
432
433#if SDL_USE_LIBDBUS
434 SDL_DBus_Init();
435#endif
436
437 return 0;
438}
439
440void
441X11_VideoQuit(_THIS)
442{
443 SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
444
445 if (data->clipboard_window) {
446 X11_XDestroyWindow(data->display, data->clipboard_window);
447 }
448
449 SDL_free(data->classname);
450#ifdef X_HAVE_UTF8_STRING
451 if (data->im) {
452 X11_XCloseIM(data->im);
453 }
454#endif
455
456 X11_QuitModes(_this);
457 X11_QuitKeyboard(_this);
458 X11_QuitMouse(_this);
459 X11_QuitTouch(_this);
460}
461
462SDL_bool
463X11_UseDirectColorVisuals(void)
464{
465 return SDL_getenv("SDL_VIDEO_X11_NODIRECTCOLOR") ? SDL_FALSE : SDL_TRUE;
466}
467
468#endif /* SDL_VIDEO_DRIVER_X11 */
469
470/* vim: set ts=4 sw=4 expandtab: */
471