1 /*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2016 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_sysvideo.h"
30 #include "../SDL_pixels_c.h"
31
32 #include "SDL_x11video.h"
33 #include "SDL_x11framebuffer.h"
34 #include "SDL_x11shape.h"
35 #include "SDL_x11touch.h"
36 #include "SDL_x11xinput2.h"
37
38 #if SDL_VIDEO_OPENGL_EGL
39 #include "SDL_x11opengles.h"
40 #endif
41
42 #ifdef X_HAVE_UTF8_STRING
43 #include <locale.h>
44 #endif
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 (data->display) {
114 X11_XCloseDisplay(data->display);
115 }
116 SDL_free(data->windowlist);
117 SDL_free(device->driverdata);
118 SDL_free(device);
119
120 SDL_X11_UnloadSymbols();
121 }
122
123 /* An error handler to reset the vidmode and then call the default handler. */
124 static SDL_bool safety_net_triggered = SDL_FALSE;
125 static int (*orig_x11_errhandler) (Display *, XErrorEvent *) = NULL;
126 static int
X11_SafetyNetErrHandler(Display * d,XErrorEvent * e)127 X11_SafetyNetErrHandler(Display * d, XErrorEvent * e)
128 {
129 SDL_VideoDevice *device = NULL;
130 /* if we trigger an error in our error handler, don't try again. */
131 if (!safety_net_triggered) {
132 safety_net_triggered = SDL_TRUE;
133 device = SDL_GetVideoDevice();
134 if (device != NULL) {
135 int i;
136 for (i = 0; i < device->num_displays; i++) {
137 SDL_VideoDisplay *display = &device->displays[i];
138 if (SDL_memcmp(&display->current_mode, &display->desktop_mode,
139 sizeof (SDL_DisplayMode)) != 0) {
140 X11_SetDisplayMode(device, display, &display->desktop_mode);
141 }
142 }
143 }
144 }
145
146 if (orig_x11_errhandler != NULL) {
147 return orig_x11_errhandler(d, e); /* probably terminate. */
148 }
149
150 return 0;
151 }
152
153 static SDL_VideoDevice *
X11_CreateDevice(int devindex)154 X11_CreateDevice(int devindex)
155 {
156 SDL_VideoDevice *device;
157 SDL_VideoData *data;
158 const char *display = NULL; /* Use the DISPLAY environment variable */
159
160 if (!SDL_X11_LoadSymbols()) {
161 return NULL;
162 }
163
164 /* Need for threading gl calls. This is also required for the proprietary
165 nVidia driver to be threaded. */
166 X11_XInitThreads();
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 /* FIXME: Do we need this?
185 if ( (SDL_strncmp(X11_XDisplayName(display), ":", 1) == 0) ||
186 (SDL_strncmp(X11_XDisplayName(display), "unix:", 5) == 0) ) {
187 local_X11 = 1;
188 } else {
189 local_X11 = 0;
190 }
191 */
192 data->display = X11_XOpenDisplay(display);
193 #if defined(__osf__) && defined(SDL_VIDEO_DRIVER_X11_DYNAMIC)
194 /* On Tru64 if linking without -lX11, it fails and you get following message.
195 * Xlib: connection to ":0.0" refused by server
196 * Xlib: XDM authorization key matches an existing client!
197 *
198 * It succeeds if retrying 1 second later
199 * or if running xhost +localhost on shell.
200 */
201 if (data->display == NULL) {
202 SDL_Delay(1000);
203 data->display = X11_XOpenDisplay(display);
204 }
205 #endif
206 if (data->display == NULL) {
207 SDL_free(device->driverdata);
208 SDL_free(device);
209 SDL_SetError("Couldn't open X11 display");
210 return NULL;
211 }
212 #ifdef X11_DEBUG
213 X11_XSynchronize(data->display, True);
214 #endif
215
216 /* Hook up an X11 error handler to recover the desktop resolution. */
217 safety_net_triggered = SDL_FALSE;
218 orig_x11_errhandler = X11_XSetErrorHandler(X11_SafetyNetErrHandler);
219
220 /* Set the function pointers */
221 device->VideoInit = X11_VideoInit;
222 device->VideoQuit = X11_VideoQuit;
223 device->GetDisplayModes = X11_GetDisplayModes;
224 device->GetDisplayBounds = X11_GetDisplayBounds;
225 device->GetDisplayUsableBounds = X11_GetDisplayUsableBounds;
226 device->GetDisplayDPI = X11_GetDisplayDPI;
227 device->SetDisplayMode = X11_SetDisplayMode;
228 device->SuspendScreenSaver = X11_SuspendScreenSaver;
229 device->PumpEvents = X11_PumpEvents;
230
231 device->CreateWindow = X11_CreateWindow;
232 device->CreateWindowFrom = X11_CreateWindowFrom;
233 device->SetWindowTitle = X11_SetWindowTitle;
234 device->SetWindowIcon = X11_SetWindowIcon;
235 device->SetWindowPosition = X11_SetWindowPosition;
236 device->SetWindowSize = X11_SetWindowSize;
237 device->SetWindowMinimumSize = X11_SetWindowMinimumSize;
238 device->SetWindowMaximumSize = X11_SetWindowMaximumSize;
239 device->GetWindowBordersSize = X11_GetWindowBordersSize;
240 device->SetWindowOpacity = X11_SetWindowOpacity;
241 device->SetWindowModalFor = X11_SetWindowModalFor;
242 device->SetWindowInputFocus = X11_SetWindowInputFocus;
243 device->ShowWindow = X11_ShowWindow;
244 device->HideWindow = X11_HideWindow;
245 device->RaiseWindow = X11_RaiseWindow;
246 device->MaximizeWindow = X11_MaximizeWindow;
247 device->MinimizeWindow = X11_MinimizeWindow;
248 device->RestoreWindow = X11_RestoreWindow;
249 device->SetWindowBordered = X11_SetWindowBordered;
250 device->SetWindowResizable = X11_SetWindowResizable;
251 device->SetWindowFullscreen = X11_SetWindowFullscreen;
252 device->SetWindowGammaRamp = X11_SetWindowGammaRamp;
253 device->SetWindowGrab = X11_SetWindowGrab;
254 device->DestroyWindow = X11_DestroyWindow;
255 device->CreateWindowFramebuffer = X11_CreateWindowFramebuffer;
256 device->UpdateWindowFramebuffer = X11_UpdateWindowFramebuffer;
257 device->DestroyWindowFramebuffer = X11_DestroyWindowFramebuffer;
258 device->GetWindowWMInfo = X11_GetWindowWMInfo;
259 device->SetWindowHitTest = X11_SetWindowHitTest;
260
261 device->shape_driver.CreateShaper = X11_CreateShaper;
262 device->shape_driver.SetWindowShape = X11_SetWindowShape;
263 device->shape_driver.ResizeWindowShape = X11_ResizeWindowShape;
264
265 #if SDL_VIDEO_OPENGL_GLX
266 device->GL_LoadLibrary = X11_GL_LoadLibrary;
267 device->GL_GetProcAddress = X11_GL_GetProcAddress;
268 device->GL_UnloadLibrary = X11_GL_UnloadLibrary;
269 device->GL_CreateContext = X11_GL_CreateContext;
270 device->GL_MakeCurrent = X11_GL_MakeCurrent;
271 device->GL_SetSwapInterval = X11_GL_SetSwapInterval;
272 device->GL_GetSwapInterval = X11_GL_GetSwapInterval;
273 device->GL_SwapWindow = X11_GL_SwapWindow;
274 device->GL_DeleteContext = X11_GL_DeleteContext;
275 #elif SDL_VIDEO_OPENGL_EGL
276 device->GL_LoadLibrary = X11_GLES_LoadLibrary;
277 device->GL_GetProcAddress = X11_GLES_GetProcAddress;
278 device->GL_UnloadLibrary = X11_GLES_UnloadLibrary;
279 device->GL_CreateContext = X11_GLES_CreateContext;
280 device->GL_MakeCurrent = X11_GLES_MakeCurrent;
281 device->GL_SetSwapInterval = X11_GLES_SetSwapInterval;
282 device->GL_GetSwapInterval = X11_GLES_GetSwapInterval;
283 device->GL_SwapWindow = X11_GLES_SwapWindow;
284 device->GL_DeleteContext = X11_GLES_DeleteContext;
285 #endif
286
287 device->SetClipboardText = X11_SetClipboardText;
288 device->GetClipboardText = X11_GetClipboardText;
289 device->HasClipboardText = X11_HasClipboardText;
290 device->StartTextInput = X11_StartTextInput;
291 device->StopTextInput = X11_StopTextInput;
292 device->SetTextInputRect = X11_SetTextInputRect;
293
294 device->free = X11_DeleteDevice;
295
296 return device;
297 }
298
299 VideoBootStrap X11_bootstrap = {
300 "x11", "SDL X11 video driver",
301 X11_Available, X11_CreateDevice
302 };
303
304 static int (*handler) (Display *, XErrorEvent *) = NULL;
305 static int
X11_CheckWindowManagerErrorHandler(Display * d,XErrorEvent * e)306 X11_CheckWindowManagerErrorHandler(Display * d, XErrorEvent * e)
307 {
308 if (e->error_code == BadWindow) {
309 return (0);
310 } else {
311 return (handler(d, e));
312 }
313 }
314
315 static void
X11_CheckWindowManager(_THIS)316 X11_CheckWindowManager(_THIS)
317 {
318 SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
319 Display *display = data->display;
320 Atom _NET_SUPPORTING_WM_CHECK;
321 int status, real_format;
322 Atom real_type;
323 unsigned long items_read = 0, items_left = 0;
324 unsigned char *propdata = NULL;
325 Window wm_window = 0;
326 #ifdef DEBUG_WINDOW_MANAGER
327 char *wm_name;
328 #endif
329
330 /* Set up a handler to gracefully catch errors */
331 X11_XSync(display, False);
332 handler = X11_XSetErrorHandler(X11_CheckWindowManagerErrorHandler);
333
334 _NET_SUPPORTING_WM_CHECK = X11_XInternAtom(display, "_NET_SUPPORTING_WM_CHECK", False);
335 status = X11_XGetWindowProperty(display, DefaultRootWindow(display), _NET_SUPPORTING_WM_CHECK, 0L, 1L, False, XA_WINDOW, &real_type, &real_format, &items_read, &items_left, &propdata);
336 if (status == Success) {
337 if (items_read) {
338 wm_window = ((Window*)propdata)[0];
339 }
340 if (propdata) {
341 X11_XFree(propdata);
342 propdata = NULL;
343 }
344 }
345
346 if (wm_window) {
347 status = X11_XGetWindowProperty(display, wm_window, _NET_SUPPORTING_WM_CHECK, 0L, 1L, False, XA_WINDOW, &real_type, &real_format, &items_read, &items_left, &propdata);
348 if (status != Success || !items_read || wm_window != ((Window*)propdata)[0]) {
349 wm_window = None;
350 }
351 if (status == Success && propdata) {
352 X11_XFree(propdata);
353 propdata = NULL;
354 }
355 }
356
357 /* Reset the error handler, we're done checking */
358 X11_XSync(display, False);
359 X11_XSetErrorHandler(handler);
360
361 if (!wm_window) {
362 #ifdef DEBUG_WINDOW_MANAGER
363 printf("Couldn't get _NET_SUPPORTING_WM_CHECK property\n");
364 #endif
365 return;
366 }
367 data->net_wm = SDL_TRUE;
368
369 #ifdef DEBUG_WINDOW_MANAGER
370 wm_name = X11_GetWindowTitle(_this, wm_window);
371 printf("Window manager: %s\n", wm_name);
372 SDL_free(wm_name);
373 #endif
374 }
375
376
377 int
X11_VideoInit(_THIS)378 X11_VideoInit(_THIS)
379 {
380 SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
381
382 /* Get the window class name, usually the name of the application */
383 data->classname = get_classname();
384
385 /* Get the process PID to be associated to the window */
386 data->pid = getpid();
387
388 /* I have no idea how random this actually is, or has to be. */
389 data->window_group = (XID) (((size_t) data->pid) ^ ((size_t) _this));
390
391 /* Open a connection to the X input manager */
392 #ifdef X_HAVE_UTF8_STRING
393 if (SDL_X11_HAVE_UTF8) {
394 /* Set the locale, and call XSetLocaleModifiers before XOpenIM so that
395 Compose keys will work correctly. */
396 char *prev_locale = setlocale(LC_ALL, NULL);
397 char *prev_xmods = X11_XSetLocaleModifiers(NULL);
398 const char *new_xmods = "";
399 #if defined(HAVE_IBUS_IBUS_H) || defined(HAVE_FCITX_FRONTEND_H)
400 const char *env_xmods = SDL_getenv("XMODIFIERS");
401 #endif
402 SDL_bool has_dbus_ime_support = SDL_FALSE;
403
404 if (prev_locale) {
405 prev_locale = SDL_strdup(prev_locale);
406 }
407
408 if (prev_xmods) {
409 prev_xmods = SDL_strdup(prev_xmods);
410 }
411
412 /* IBus resends some key events that were filtered by XFilterEvents
413 when it is used via XIM which causes issues. Prevent this by forcing
414 @im=none if XMODIFIERS contains @im=ibus. IBus can still be used via
415 the DBus implementation, which also has support for pre-editing. */
416 #ifdef HAVE_IBUS_IBUS_H
417 if (env_xmods && SDL_strstr(env_xmods, "@im=ibus") != NULL) {
418 has_dbus_ime_support = SDL_TRUE;
419 }
420 #endif
421 #ifdef HAVE_FCITX_FRONTEND_H
422 if (env_xmods && SDL_strstr(env_xmods, "@im=fcitx") != NULL) {
423 has_dbus_ime_support = SDL_TRUE;
424 }
425 #endif
426 if (has_dbus_ime_support) {
427 new_xmods = "@im=none";
428 }
429
430 setlocale(LC_ALL, "");
431 X11_XSetLocaleModifiers(new_xmods);
432
433 data->im = X11_XOpenIM(data->display, NULL, data->classname, data->classname);
434
435 /* Reset the locale + X locale modifiers back to how they were,
436 locale first because the X locale modifiers depend on it. */
437 setlocale(LC_ALL, prev_locale);
438 X11_XSetLocaleModifiers(prev_xmods);
439
440 if (prev_locale) {
441 SDL_free(prev_locale);
442 }
443
444 if (prev_xmods) {
445 SDL_free(prev_xmods);
446 }
447 }
448 #endif
449
450 /* Look up some useful Atoms */
451 #define GET_ATOM(X) data->X = X11_XInternAtom(data->display, #X, False)
452 GET_ATOM(WM_PROTOCOLS);
453 GET_ATOM(WM_DELETE_WINDOW);
454 GET_ATOM(WM_TAKE_FOCUS);
455 GET_ATOM(_NET_WM_STATE);
456 GET_ATOM(_NET_WM_STATE_HIDDEN);
457 GET_ATOM(_NET_WM_STATE_FOCUSED);
458 GET_ATOM(_NET_WM_STATE_MAXIMIZED_VERT);
459 GET_ATOM(_NET_WM_STATE_MAXIMIZED_HORZ);
460 GET_ATOM(_NET_WM_STATE_FULLSCREEN);
461 GET_ATOM(_NET_WM_STATE_ABOVE);
462 GET_ATOM(_NET_WM_STATE_SKIP_TASKBAR);
463 GET_ATOM(_NET_WM_STATE_SKIP_PAGER);
464 GET_ATOM(_NET_WM_ALLOWED_ACTIONS);
465 GET_ATOM(_NET_WM_ACTION_FULLSCREEN);
466 GET_ATOM(_NET_WM_NAME);
467 GET_ATOM(_NET_WM_ICON_NAME);
468 GET_ATOM(_NET_WM_ICON);
469 GET_ATOM(_NET_WM_PING);
470 GET_ATOM(_NET_WM_WINDOW_OPACITY);
471 GET_ATOM(_NET_WM_USER_TIME);
472 GET_ATOM(_NET_ACTIVE_WINDOW);
473 GET_ATOM(_NET_FRAME_EXTENTS);
474 GET_ATOM(UTF8_STRING);
475 GET_ATOM(PRIMARY);
476 GET_ATOM(XdndEnter);
477 GET_ATOM(XdndPosition);
478 GET_ATOM(XdndStatus);
479 GET_ATOM(XdndTypeList);
480 GET_ATOM(XdndActionCopy);
481 GET_ATOM(XdndDrop);
482 GET_ATOM(XdndFinished);
483 GET_ATOM(XdndSelection);
484 GET_ATOM(XKLAVIER_STATE);
485
486 /* Detect the window manager */
487 X11_CheckWindowManager(_this);
488
489 if (X11_InitModes(_this) < 0) {
490 return -1;
491 }
492
493 X11_InitXinput2(_this);
494
495 if (X11_InitKeyboard(_this) != 0) {
496 return -1;
497 }
498 X11_InitMouse(_this);
499
500 X11_InitTouch(_this);
501
502 #if SDL_USE_LIBDBUS
503 SDL_DBus_Init();
504 #endif
505
506 return 0;
507 }
508
509 void
X11_VideoQuit(_THIS)510 X11_VideoQuit(_THIS)
511 {
512 SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
513
514 SDL_free(data->classname);
515 #ifdef X_HAVE_UTF8_STRING
516 if (data->im) {
517 X11_XCloseIM(data->im);
518 }
519 #endif
520
521 X11_QuitModes(_this);
522 X11_QuitKeyboard(_this);
523 X11_QuitMouse(_this);
524 X11_QuitTouch(_this);
525
526 #if SDL_USE_LIBDBUS
527 SDL_DBus_Quit();
528 #endif
529 }
530
531 SDL_bool
X11_UseDirectColorVisuals(void)532 X11_UseDirectColorVisuals(void)
533 {
534 return SDL_getenv("SDL_VIDEO_X11_NODIRECTCOLOR") ? SDL_FALSE : SDL_TRUE;
535 }
536
537 #endif /* SDL_VIDEO_DRIVER_X11 */
538
539 /* vim: set ts=4 sw=4 expandtab: */
540