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_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 */
47 static int X11_VideoInit(_THIS);
48 static void X11_VideoQuit(_THIS);
49
50 /* Find out what class name we should use */
51 static char *
get_classname()52 get_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
95 static int
X11_Available(void)96 X11_Available(void)
97 {
98 Display *display = NULL;
99 if (SDL_X11_LoadSymbols()) {
100 display = X11_XOpenDisplay(NULL);
101 if (display != NULL) {
102 X11_XCloseDisplay(display);
103 }
104 SDL_X11_UnloadSymbols();
105 }
106 return (display != NULL);
107 }
108
109 static void
X11_DeleteDevice(SDL_VideoDevice * device)110 X11_DeleteDevice(SDL_VideoDevice * device)
111 {
112 SDL_VideoData *data = (SDL_VideoData *) device->driverdata;
113 if (device->vulkan_config.loader_handle) {
114 device->Vulkan_UnloadLibrary(device);
115 }
116 if (data->display) {
117 X11_XCloseDisplay(data->display);
118 }
119 SDL_free(data->windowlist);
120 SDL_free(device->driverdata);
121 SDL_free(device);
122
123 SDL_X11_UnloadSymbols();
124 }
125
126 /* An error handler to reset the vidmode and then call the default handler. */
127 static SDL_bool safety_net_triggered = SDL_FALSE;
128 static int (*orig_x11_errhandler) (Display *, XErrorEvent *) = NULL;
129 static int
X11_SafetyNetErrHandler(Display * d,XErrorEvent * e)130 X11_SafetyNetErrHandler(Display * d, XErrorEvent * e)
131 {
132 SDL_VideoDevice *device = NULL;
133 /* if we trigger an error in our error handler, don't try again. */
134 if (!safety_net_triggered) {
135 safety_net_triggered = SDL_TRUE;
136 device = SDL_GetVideoDevice();
137 if (device != NULL) {
138 int i;
139 for (i = 0; i < device->num_displays; i++) {
140 SDL_VideoDisplay *display = &device->displays[i];
141 if (SDL_memcmp(&display->current_mode, &display->desktop_mode,
142 sizeof (SDL_DisplayMode)) != 0) {
143 X11_SetDisplayMode(device, display, &display->desktop_mode);
144 }
145 }
146 }
147 }
148
149 if (orig_x11_errhandler != NULL) {
150 return orig_x11_errhandler(d, e); /* probably terminate. */
151 }
152
153 return 0;
154 }
155
156 static SDL_VideoDevice *
X11_CreateDevice(int devindex)157 X11_CreateDevice(int devindex)
158 {
159 SDL_VideoDevice *device;
160 SDL_VideoData *data;
161 const char *display = NULL; /* Use the DISPLAY environment variable */
162
163 if (!SDL_X11_LoadSymbols()) {
164 return NULL;
165 }
166
167 /* Need for threading gl calls. This is also required for the proprietary
168 nVidia driver to be threaded. */
169 X11_XInitThreads();
170
171 /* Initialize all variables that we clean on shutdown */
172 device = (SDL_VideoDevice *) SDL_calloc(1, sizeof(SDL_VideoDevice));
173 if (!device) {
174 SDL_OutOfMemory();
175 return NULL;
176 }
177 data = (struct SDL_VideoData *) SDL_calloc(1, sizeof(SDL_VideoData));
178 if (!data) {
179 SDL_free(device);
180 SDL_OutOfMemory();
181 return NULL;
182 }
183 device->driverdata = data;
184
185 data->global_mouse_changed = SDL_TRUE;
186
187 /* FIXME: Do we need this?
188 if ( (SDL_strncmp(X11_XDisplayName(display), ":", 1) == 0) ||
189 (SDL_strncmp(X11_XDisplayName(display), "unix:", 5) == 0) ) {
190 local_X11 = 1;
191 } else {
192 local_X11 = 0;
193 }
194 */
195 data->display = X11_XOpenDisplay(display);
196 #ifdef SDL_VIDEO_DRIVER_X11_DYNAMIC
197 /* On some systems if linking without -lX11, it fails and you get following message.
198 * Xlib: connection to ":0.0" refused by server
199 * Xlib: XDM authorization key matches an existing client!
200 *
201 * It succeeds if retrying 1 second later
202 * or if running xhost +localhost on shell.
203 */
204 if (data->display == NULL) {
205 SDL_Delay(1000);
206 data->display = X11_XOpenDisplay(display);
207 }
208 #endif
209 if (data->display == NULL) {
210 SDL_free(device->driverdata);
211 SDL_free(device);
212 SDL_SetError("Couldn't open X11 display");
213 return NULL;
214 }
215 #ifdef X11_DEBUG
216 X11_XSynchronize(data->display, True);
217 #endif
218
219 /* Hook up an X11 error handler to recover the desktop resolution. */
220 safety_net_triggered = SDL_FALSE;
221 orig_x11_errhandler = X11_XSetErrorHandler(X11_SafetyNetErrHandler);
222
223 /* Set the function pointers */
224 device->VideoInit = X11_VideoInit;
225 device->VideoQuit = X11_VideoQuit;
226 device->ResetTouch = X11_ResetTouch;
227 device->GetDisplayModes = X11_GetDisplayModes;
228 device->GetDisplayBounds = X11_GetDisplayBounds;
229 device->GetDisplayUsableBounds = X11_GetDisplayUsableBounds;
230 device->GetDisplayDPI = X11_GetDisplayDPI;
231 device->SetDisplayMode = X11_SetDisplayMode;
232 device->SuspendScreenSaver = X11_SuspendScreenSaver;
233 device->PumpEvents = X11_PumpEvents;
234
235 device->CreateSDLWindow = X11_CreateWindow;
236 device->CreateSDLWindowFrom = X11_CreateWindowFrom;
237 device->SetWindowTitle = X11_SetWindowTitle;
238 device->SetWindowIcon = X11_SetWindowIcon;
239 device->SetWindowPosition = X11_SetWindowPosition;
240 device->SetWindowSize = X11_SetWindowSize;
241 device->SetWindowMinimumSize = X11_SetWindowMinimumSize;
242 device->SetWindowMaximumSize = X11_SetWindowMaximumSize;
243 device->GetWindowBordersSize = X11_GetWindowBordersSize;
244 device->SetWindowOpacity = X11_SetWindowOpacity;
245 device->SetWindowModalFor = X11_SetWindowModalFor;
246 device->SetWindowInputFocus = X11_SetWindowInputFocus;
247 device->ShowWindow = X11_ShowWindow;
248 device->HideWindow = X11_HideWindow;
249 device->RaiseWindow = X11_RaiseWindow;
250 device->MaximizeWindow = X11_MaximizeWindow;
251 device->MinimizeWindow = X11_MinimizeWindow;
252 device->RestoreWindow = X11_RestoreWindow;
253 device->SetWindowBordered = X11_SetWindowBordered;
254 device->SetWindowResizable = X11_SetWindowResizable;
255 device->SetWindowFullscreen = X11_SetWindowFullscreen;
256 device->SetWindowGammaRamp = X11_SetWindowGammaRamp;
257 device->SetWindowGrab = X11_SetWindowGrab;
258 device->DestroyWindow = X11_DestroyWindow;
259 device->CreateWindowFramebuffer = X11_CreateWindowFramebuffer;
260 device->UpdateWindowFramebuffer = X11_UpdateWindowFramebuffer;
261 device->DestroyWindowFramebuffer = X11_DestroyWindowFramebuffer;
262 device->GetWindowWMInfo = X11_GetWindowWMInfo;
263 device->SetWindowHitTest = X11_SetWindowHitTest;
264 device->AcceptDragAndDrop = X11_AcceptDragAndDrop;
265
266 device->shape_driver.CreateShaper = X11_CreateShaper;
267 device->shape_driver.SetWindowShape = X11_SetWindowShape;
268 device->shape_driver.ResizeWindowShape = X11_ResizeWindowShape;
269
270 #if SDL_VIDEO_OPENGL_GLX
271 device->GL_LoadLibrary = X11_GL_LoadLibrary;
272 device->GL_GetProcAddress = X11_GL_GetProcAddress;
273 device->GL_UnloadLibrary = X11_GL_UnloadLibrary;
274 device->GL_CreateContext = X11_GL_CreateContext;
275 device->GL_MakeCurrent = X11_GL_MakeCurrent;
276 device->GL_SetSwapInterval = X11_GL_SetSwapInterval;
277 device->GL_GetSwapInterval = X11_GL_GetSwapInterval;
278 device->GL_SwapWindow = X11_GL_SwapWindow;
279 device->GL_DeleteContext = X11_GL_DeleteContext;
280 #endif
281 #if SDL_VIDEO_OPENGL_EGL
282 #if SDL_VIDEO_OPENGL_GLX
283 if (SDL_GetHintBoolean(SDL_HINT_VIDEO_X11_FORCE_EGL, SDL_FALSE)) {
284 #endif
285 device->GL_LoadLibrary = X11_GLES_LoadLibrary;
286 device->GL_GetProcAddress = X11_GLES_GetProcAddress;
287 device->GL_UnloadLibrary = X11_GLES_UnloadLibrary;
288 device->GL_CreateContext = X11_GLES_CreateContext;
289 device->GL_MakeCurrent = X11_GLES_MakeCurrent;
290 device->GL_SetSwapInterval = X11_GLES_SetSwapInterval;
291 device->GL_GetSwapInterval = X11_GLES_GetSwapInterval;
292 device->GL_SwapWindow = X11_GLES_SwapWindow;
293 device->GL_DeleteContext = X11_GLES_DeleteContext;
294 #if SDL_VIDEO_OPENGL_GLX
295 }
296 #endif
297 #endif
298
299 device->SetClipboardText = X11_SetClipboardText;
300 device->GetClipboardText = X11_GetClipboardText;
301 device->HasClipboardText = X11_HasClipboardText;
302 device->StartTextInput = X11_StartTextInput;
303 device->StopTextInput = X11_StopTextInput;
304 device->SetTextInputRect = X11_SetTextInputRect;
305
306 device->free = X11_DeleteDevice;
307
308 #if SDL_VIDEO_VULKAN
309 device->Vulkan_LoadLibrary = X11_Vulkan_LoadLibrary;
310 device->Vulkan_UnloadLibrary = X11_Vulkan_UnloadLibrary;
311 device->Vulkan_GetInstanceExtensions = X11_Vulkan_GetInstanceExtensions;
312 device->Vulkan_CreateSurface = X11_Vulkan_CreateSurface;
313 #endif
314
315 return device;
316 }
317
318 VideoBootStrap X11_bootstrap = {
319 "x11", "SDL X11 video driver",
320 X11_Available, X11_CreateDevice
321 };
322
323 static int (*handler) (Display *, XErrorEvent *) = NULL;
324 static int
X11_CheckWindowManagerErrorHandler(Display * d,XErrorEvent * e)325 X11_CheckWindowManagerErrorHandler(Display * d, XErrorEvent * e)
326 {
327 if (e->error_code == BadWindow) {
328 return (0);
329 } else {
330 return (handler(d, e));
331 }
332 }
333
334 static void
X11_CheckWindowManager(_THIS)335 X11_CheckWindowManager(_THIS)
336 {
337 SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
338 Display *display = data->display;
339 Atom _NET_SUPPORTING_WM_CHECK;
340 int status, real_format;
341 Atom real_type;
342 unsigned long items_read = 0, items_left = 0;
343 unsigned char *propdata = NULL;
344 Window wm_window = 0;
345 #ifdef DEBUG_WINDOW_MANAGER
346 char *wm_name;
347 #endif
348
349 /* Set up a handler to gracefully catch errors */
350 X11_XSync(display, False);
351 handler = X11_XSetErrorHandler(X11_CheckWindowManagerErrorHandler);
352
353 _NET_SUPPORTING_WM_CHECK = X11_XInternAtom(display, "_NET_SUPPORTING_WM_CHECK", False);
354 status = X11_XGetWindowProperty(display, DefaultRootWindow(display), _NET_SUPPORTING_WM_CHECK, 0L, 1L, False, XA_WINDOW, &real_type, &real_format, &items_read, &items_left, &propdata);
355 if (status == Success) {
356 if (items_read) {
357 wm_window = ((Window*)propdata)[0];
358 }
359 if (propdata) {
360 X11_XFree(propdata);
361 propdata = NULL;
362 }
363 }
364
365 if (wm_window) {
366 status = X11_XGetWindowProperty(display, wm_window, _NET_SUPPORTING_WM_CHECK, 0L, 1L, False, XA_WINDOW, &real_type, &real_format, &items_read, &items_left, &propdata);
367 if (status != Success || !items_read || wm_window != ((Window*)propdata)[0]) {
368 wm_window = None;
369 }
370 if (status == Success && propdata) {
371 X11_XFree(propdata);
372 propdata = NULL;
373 }
374 }
375
376 /* Reset the error handler, we're done checking */
377 X11_XSync(display, False);
378 X11_XSetErrorHandler(handler);
379
380 if (!wm_window) {
381 #ifdef DEBUG_WINDOW_MANAGER
382 printf("Couldn't get _NET_SUPPORTING_WM_CHECK property\n");
383 #endif
384 return;
385 }
386 data->net_wm = SDL_TRUE;
387
388 #ifdef DEBUG_WINDOW_MANAGER
389 wm_name = X11_GetWindowTitle(_this, wm_window);
390 printf("Window manager: %s\n", wm_name);
391 SDL_free(wm_name);
392 #endif
393 }
394
395
396 int
X11_VideoInit(_THIS)397 X11_VideoInit(_THIS)
398 {
399 SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
400
401 /* Get the window class name, usually the name of the application */
402 data->classname = get_classname();
403
404 /* Get the process PID to be associated to the window */
405 data->pid = getpid();
406
407 /* I have no idea how random this actually is, or has to be. */
408 data->window_group = (XID) (((size_t) data->pid) ^ ((size_t) _this));
409
410 /* Look up some useful Atoms */
411 #define GET_ATOM(X) data->X = X11_XInternAtom(data->display, #X, False)
412 GET_ATOM(WM_PROTOCOLS);
413 GET_ATOM(WM_DELETE_WINDOW);
414 GET_ATOM(WM_TAKE_FOCUS);
415 GET_ATOM(_NET_WM_STATE);
416 GET_ATOM(_NET_WM_STATE_HIDDEN);
417 GET_ATOM(_NET_WM_STATE_FOCUSED);
418 GET_ATOM(_NET_WM_STATE_MAXIMIZED_VERT);
419 GET_ATOM(_NET_WM_STATE_MAXIMIZED_HORZ);
420 GET_ATOM(_NET_WM_STATE_FULLSCREEN);
421 GET_ATOM(_NET_WM_STATE_ABOVE);
422 GET_ATOM(_NET_WM_STATE_SKIP_TASKBAR);
423 GET_ATOM(_NET_WM_STATE_SKIP_PAGER);
424 GET_ATOM(_NET_WM_ALLOWED_ACTIONS);
425 GET_ATOM(_NET_WM_ACTION_FULLSCREEN);
426 GET_ATOM(_NET_WM_NAME);
427 GET_ATOM(_NET_WM_ICON_NAME);
428 GET_ATOM(_NET_WM_ICON);
429 GET_ATOM(_NET_WM_PING);
430 GET_ATOM(_NET_WM_WINDOW_OPACITY);
431 GET_ATOM(_NET_WM_USER_TIME);
432 GET_ATOM(_NET_ACTIVE_WINDOW);
433 GET_ATOM(_NET_FRAME_EXTENTS);
434 GET_ATOM(UTF8_STRING);
435 GET_ATOM(PRIMARY);
436 GET_ATOM(XdndEnter);
437 GET_ATOM(XdndPosition);
438 GET_ATOM(XdndStatus);
439 GET_ATOM(XdndTypeList);
440 GET_ATOM(XdndActionCopy);
441 GET_ATOM(XdndDrop);
442 GET_ATOM(XdndFinished);
443 GET_ATOM(XdndSelection);
444 GET_ATOM(XKLAVIER_STATE);
445
446 /* Detect the window manager */
447 X11_CheckWindowManager(_this);
448
449 if (X11_InitModes(_this) < 0) {
450 return -1;
451 }
452
453 X11_InitXinput2(_this);
454
455 if (X11_InitKeyboard(_this) != 0) {
456 return -1;
457 }
458 X11_InitMouse(_this);
459
460 X11_InitTouch(_this);
461
462 #if SDL_USE_LIBDBUS
463 SDL_DBus_Init();
464 #endif
465
466 return 0;
467 }
468
469 void
X11_VideoQuit(_THIS)470 X11_VideoQuit(_THIS)
471 {
472 SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
473
474 if (data->clipboard_window) {
475 X11_XDestroyWindow(data->display, data->clipboard_window);
476 }
477
478 SDL_free(data->classname);
479 #ifdef X_HAVE_UTF8_STRING
480 if (data->im) {
481 X11_XCloseIM(data->im);
482 }
483 #endif
484
485 X11_QuitModes(_this);
486 X11_QuitKeyboard(_this);
487 X11_QuitMouse(_this);
488 X11_QuitTouch(_this);
489
490 /* !!! FIXME: other subsystems use D-Bus, so we shouldn't quit it here;
491 have SDL.c do this at a higher level, or add refcounting. */
492 #if SDL_USE_LIBDBUS
493 SDL_DBus_Quit();
494 #endif
495 }
496
497 SDL_bool
X11_UseDirectColorVisuals(void)498 X11_UseDirectColorVisuals(void)
499 {
500 return SDL_getenv("SDL_VIDEO_X11_NODIRECTCOLOR") ? SDL_FALSE : SDL_TRUE;
501 }
502
503 #endif /* SDL_VIDEO_DRIVER_X11 */
504
505 /* vim: set ts=4 sw=4 expandtab: */
506