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