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