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