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