Panda3D
x11GraphicsPipe.cxx
Go to the documentation of this file.
1/**
2 * PANDA 3D SOFTWARE
3 * Copyright (c) Carnegie Mellon University. All rights reserved.
4 *
5 * All use of this software is subject to the terms of the revised BSD
6 * license. You should have received a copy of this license along
7 * with this source code in a file named "LICENSE."
8 *
9 * @file x11GraphicsPipe.cxx
10 * @author rdb
11 * @date 2009-07-07
12 */
13
14#include "x11GraphicsPipe.h"
15#include "x11GraphicsWindow.h"
16#include "config_x11display.h"
18#include "displayInformation.h"
19#include "pstrtod.h"
20
21#include <dlfcn.h>
22
23TypeHandle x11GraphicsPipe::_type_handle;
24
25bool x11GraphicsPipe::_error_handlers_installed = false;
26x11GraphicsPipe::ErrorHandlerFunc *x11GraphicsPipe::_prev_error_handler;
27x11GraphicsPipe::IOErrorHandlerFunc *x11GraphicsPipe::_prev_io_error_handler;
28bool x11GraphicsPipe::_x_error_messages_enabled = true;
29int x11GraphicsPipe::_x_error_count = 0;
30
31LightReMutex x11GraphicsPipe::_x_mutex;
32
33/**
34 *
35 */
36x11GraphicsPipe::
37x11GraphicsPipe(const std::string &display) :
38 _have_xrandr(false),
39 _xcursor_size(-1),
40 _XF86DGADirectVideo(nullptr) {
41
42 std::string display_spec = display;
43 if (display_spec.empty()) {
44 display_spec = display_cfg;
45 }
46 if (display_spec.empty()) {
47 display_spec = ExecutionEnvironment::get_environment_variable("DISPLAY");
48 }
49 if (display_spec.empty()) {
50 display_spec = ":0.0";
51 }
52
53 // The X docs say we should do this to get international character support
54 // from the keyboard.
55 setlocale(LC_ALL, "");
56
57 // But it's important that we use the "C" locale for numeric formatting,
58 // since all of the internal Panda code assumes this--we need a decimal
59 // point to mean a decimal point.
60 setlocale(LC_NUMERIC, "C");
61
62 _is_valid = false;
63 _supported_types = OT_window | OT_buffer | OT_texture_buffer;
64 _display = nullptr;
65 _screen = 0;
66 _root = (X11_Window)nullptr;
67 _im = (XIM)nullptr;
68 _hidden_cursor = None;
69
70 // According to the documentation, we should call this before making any
71 // other Xlib calls if we wish to use the Xlib locking system.
72 if (x_init_threads) {
73 XInitThreads();
74 }
75
76 install_error_handlers();
77
78 _display = XOpenDisplay(display_spec.c_str());
79 if (!_display) {
80 x11display_cat.error()
81 << "Could not open display \"" << display_spec << "\".\n";
82 _is_valid = false;
83 _screen = None;
84 _root = None;
85 _display_width = 0;
86 _display_height = 0;
87 return;
88 }
89
90 if (!XSupportsLocale()) {
91 x11display_cat.warning()
92 << "X does not support locale " << setlocale(LC_ALL, nullptr) << "\n";
93 }
94 XSetLocaleModifiers("");
95
96 _screen = DefaultScreen(_display);
97 _root = RootWindow(_display, _screen);
98 _display_width = DisplayWidth(_display, _screen);
99 _display_height = DisplayHeight(_display, _screen);
100 _is_valid = true;
101
102 // Dynamically load the xf86dga extension.
103 void *xf86dga = dlopen("libXxf86dga.so.1", RTLD_NOW | RTLD_LOCAL);
104 if (xf86dga != nullptr) {
105 pfn_XF86DGAQueryVersion _XF86DGAQueryVersion = (pfn_XF86DGAQueryVersion)dlsym(xf86dga, "XF86DGAQueryVersion");
106 _XF86DGADirectVideo = (pfn_XF86DGADirectVideo)dlsym(xf86dga, "XF86DGADirectVideo");
107
108 int major_ver, minor_ver;
109 if (_XF86DGAQueryVersion == nullptr || _XF86DGADirectVideo == nullptr) {
110 x11display_cat.warning()
111 << "libXxf86dga.so.1 does not provide required functions; relative mouse mode will not work.\n";
112
113 } else if (!_XF86DGAQueryVersion(_display, &major_ver, &minor_ver)) {
114 _XF86DGADirectVideo = nullptr;
115 }
116 } else {
117 _XF86DGADirectVideo = nullptr;
118 if (x11display_cat.is_debug()) {
119 x11display_cat.debug()
120 << "cannot dlopen libXxf86dga.so.1; cursor changing will not work.\n";
121 }
122 }
123
124 // Dynamically load the XCursor extension.
125 void *xcursor = dlopen("libXcursor.so.1", RTLD_NOW | RTLD_LOCAL);
126 if (xcursor != nullptr) {
127 pfn_XcursorGetDefaultSize _XcursorGetDefaultSize = (pfn_XcursorGetDefaultSize)dlsym(xcursor, "XcursorGetDefaultSize");
128 _XcursorXcFileLoadImages = (pfn_XcursorXcFileLoadImages)dlsym(xcursor, "XcursorXcFileLoadImages");
129 _XcursorImagesLoadCursor = (pfn_XcursorImagesLoadCursor)dlsym(xcursor, "XcursorImagesLoadCursor");
130 _XcursorImagesDestroy = (pfn_XcursorImagesDestroy)dlsym(xcursor, "XcursorImagesDestroy");
131 _XcursorImageCreate = (pfn_XcursorImageCreate)dlsym(xcursor, "XcursorImageCreate");
132 _XcursorImageLoadCursor = (pfn_XcursorImageLoadCursor)dlsym(xcursor, "XcursorImageLoadCursor");
133 _XcursorImageDestroy = (pfn_XcursorImageDestroy)dlsym(xcursor, "XcursorImageDestroy");
134
135 if (_XcursorGetDefaultSize == nullptr || _XcursorXcFileLoadImages == nullptr ||
136 _XcursorImagesLoadCursor == nullptr || _XcursorImagesDestroy == nullptr ||
137 _XcursorImageCreate == nullptr || _XcursorImageLoadCursor == nullptr ||
138 _XcursorImageDestroy == nullptr) {
139 _xcursor_size = -1;
140 x11display_cat.warning()
141 << "libXcursor.so.1 does not provide required functions; cursor changing will not work.\n";
142
143 } else if (x_cursor_size.get_value() >= 0) {
144 _xcursor_size = x_cursor_size;
145 } else {
146 _xcursor_size = _XcursorGetDefaultSize(_display);
147 }
148 } else {
149 _xcursor_size = -1;
150 if (x11display_cat.is_debug()) {
151 x11display_cat.debug()
152 << "cannot dlopen libXcursor.so.1; cursor changing will not work.\n";
153 }
154 }
155
156 // Dynamically load the XRandr extension.
157 void *xrandr = dlopen("libXrandr.so.2", RTLD_NOW | RTLD_LOCAL);
158 if (xrandr != nullptr) {
159 pfn_XRRQueryExtension _XRRQueryExtension = (pfn_XRRQueryExtension)dlsym(xrandr, "XRRQueryExtension");
160 pfn_XRRQueryVersion _XRRQueryVersion = (pfn_XRRQueryVersion)dlsym(xrandr, "XRRQueryVersion");
161
162 _XRRSizes = (pfn_XRRSizes)dlsym(xrandr, "XRRSizes");
163 _XRRRates = (pfn_XRRRates)dlsym(xrandr, "XRRRates");
164 _XRRGetScreenInfo = (pfn_XRRGetScreenInfo)dlsym(xrandr, "XRRGetScreenInfo");
165 _XRRConfigCurrentConfiguration = (pfn_XRRConfigCurrentConfiguration)dlsym(xrandr, "XRRConfigCurrentConfiguration");
166 _XRRSetScreenConfig = (pfn_XRRSetScreenConfig)dlsym(xrandr, "XRRSetScreenConfig");
167
168 int event, error, major, minor;
169 if (_XRRQueryExtension == nullptr || _XRRSizes == nullptr || _XRRRates == nullptr ||
170 _XRRGetScreenInfo == nullptr || _XRRConfigCurrentConfiguration == nullptr ||
171 _XRRSetScreenConfig == nullptr || _XRRQueryVersion == nullptr) {
172 _have_xrandr = false;
173 x11display_cat.warning()
174 << "libXrandr.so.2 does not provide required functions; resolution setting will not work.\n";
175 }
176 else if (_XRRQueryExtension(_display, &event, &error) &&
177 _XRRQueryVersion(_display, &major, &minor)) {
178 _have_xrandr = true;
179 if (x11display_cat.is_debug()) {
180 x11display_cat.debug()
181 << "Found RandR extension " << major << "." << minor << "\n";
182 }
183
184 if (major > 1 || (major == 1 && minor >= 2)) {
185 if (major > 1 || (major == 1 && minor >= 3)) {
186 _XRRGetScreenResourcesCurrent = (pfn_XRRGetScreenResources)
187 dlsym(xrandr, "XRRGetScreenResourcesCurrent");
188 } else {
189 // Fall back to this slower version.
190 _XRRGetScreenResourcesCurrent = (pfn_XRRGetScreenResources)
191 dlsym(xrandr, "XRRGetScreenResources");
192 }
193
194 _XRRFreeScreenResources = (pfn_XRRFreeScreenResources)dlsym(xrandr, "XRRFreeScreenResources");
195 _XRRGetCrtcInfo = (pfn_XRRGetCrtcInfo)dlsym(xrandr, "XRRGetCrtcInfo");
196 _XRRFreeCrtcInfo = (pfn_XRRFreeCrtcInfo)dlsym(xrandr, "XRRFreeCrtcInfo");
197 } else {
198 _XRRGetScreenResourcesCurrent = nullptr;
199 _XRRFreeScreenResources = nullptr;
200 _XRRGetCrtcInfo = nullptr;
201 _XRRFreeCrtcInfo = nullptr;
202 }
203 } else {
204 _have_xrandr = false;
205 if (x11display_cat.is_debug()) {
206 x11display_cat.debug()
207 << "RandR extension not supported; resolution setting will not work.\n";
208 }
209 }
210 } else {
211 _have_xrandr = false;
212 if (x11display_cat.is_debug()) {
213 x11display_cat.debug()
214 << "cannot dlopen libXrandr.so.2; resolution setting will not work.\n";
215 }
216 }
217
218 // Use Xrandr to fill in the supported resolution list.
219 if (_have_xrandr) {
220 // If we have XRRGetScreenResources, we prefer that. It seems to be more
221 // reliable than XRRSizes in multi-monitor set-ups.
222 if (auto res = get_screen_resources()) {
223 if (x11display_cat.is_debug()) {
224 x11display_cat.debug()
225 << "Using XRRScreenResources to obtain display modes\n";
226 }
227 _display_information->_total_display_modes = res->nmode;
228 _display_information->_display_mode_array = new DisplayMode[res->nmode];
229 for (int i = 0; i < res->nmode; ++i) {
230 XRRModeInfo &mode = res->modes[i];
231
232 DisplayMode *dm = _display_information->_display_mode_array + i;
233 dm->width = mode.width;
234 dm->height = mode.height;
235 dm->bits_per_pixel = -1;
236 dm->fullscreen_only = false;
237
238 if (mode.hTotal && mode.vTotal) {
239 dm->refresh_rate = (double)mode.dotClock /
240 ((double)mode.hTotal * (double)mode.vTotal);
241 } else {
242 dm->refresh_rate = 0;
243 }
244 }
245 } else {
246 if (x11display_cat.is_debug()) {
247 x11display_cat.debug()
248 << "Using XRRSizes and XRRRates to obtain display modes\n";
249 }
250
251 int num_sizes, num_rates;
252 XRRScreenSize *xrrs;
253 xrrs = _XRRSizes(_display, 0, &num_sizes);
254 _display_information->_total_display_modes = 0;
255 for (int i = 0; i < num_sizes; ++i) {
256 _XRRRates(_display, 0, i, &num_rates);
257 _display_information->_total_display_modes += num_rates;
258 }
259
260 short *rates;
261 short counter = 0;
262 _display_information->_display_mode_array = new DisplayMode[_display_information->_total_display_modes];
263 for (int i = 0; i < num_sizes; ++i) {
264 int num_rates;
265 rates = _XRRRates(_display, 0, i, &num_rates);
266 for (int j = 0; j < num_rates; ++j) {
267 DisplayMode* dm = _display_information->_display_mode_array + counter;
268 dm->width = xrrs[i].width;
269 dm->height = xrrs[i].height;
270 dm->refresh_rate = rates[j];
271 dm->bits_per_pixel = -1;
272 dm->fullscreen_only = false;
273 ++counter;
274 }
275 }
276 }
277 }
278
279 // Connect to an input method for supporting international text entry.
280 _im = XOpenIM(_display, nullptr, nullptr, nullptr);
281 if (_im == (XIM)nullptr) {
282 // Fall back to internal input method.
283 XSetLocaleModifiers("@im=none");
284 _im = XOpenIM(_display, nullptr, nullptr, nullptr);
285 if (_im == (XIM)nullptr) {
286 x11display_cat.warning()
287 << "Couldn't open input method.\n";
288 }
289 }
290
291 // What styles does the current input method support?
292 /*
293 XIMStyles *im_supported_styles;
294 XGetIMValues(_im, XNQueryInputStyle, &im_supported_styles, NULL);
295
296 for (int i = 0; i < im_supported_styles->count_styles; i++) {
297 XIMStyle style = im_supported_styles->supported_styles[i];
298 cerr << "style " << i << ". " << hex << style << dec << "\n";
299 }
300
301 XFree(im_supported_styles);
302 */
303
304 const char *dpi = XGetDefault(_display, "Xft", "dpi");
305 if (dpi != nullptr) {
306 char *endptr = nullptr;
307 double result = pstrtod(dpi, &endptr);
308 if (result != 0 && !cnan(result) && endptr[0] == '\0') {
309 result /= 96;
310 set_detected_display_zoom(result);
311
312 if (x11display_cat.is_debug()) {
313 x11display_cat.debug()
314 << "Determined display zoom to be " << result
315 << " based on specified Xft.dpi " << dpi << "\n";
316 }
317 } else {
318 x11display_cat.warning()
319 << "Unable to determine display zoom because Xft.dpi is invalid: "
320 << dpi << "\n";
321 }
322 } else if (x11display_cat.is_debug()) {
323 x11display_cat.debug()
324 << "Unable to determine display zoom because Xft.dpi was not set.\n";
325 }
326
327 // Get some X atom numbers.
328 _wm_delete_window = XInternAtom(_display, "WM_DELETE_WINDOW", false);
329 _net_wm_pid = XInternAtom(_display, "_NET_WM_PID", false);
330 _net_wm_window_type = XInternAtom(_display, "_NET_WM_WINDOW_TYPE", false);
331 _net_wm_window_type_splash = XInternAtom(_display, "_NET_WM_WINDOW_TYPE_SPLASH", false);
332 _net_wm_window_type_fullscreen = XInternAtom(_display, "_NET_WM_WINDOW_TYPE_FULLSCREEN", false);
333 _net_wm_state = XInternAtom(_display, "_NET_WM_STATE", false);
334 _net_wm_state_fullscreen = XInternAtom(_display, "_NET_WM_STATE_FULLSCREEN", false);
335 _net_wm_state_above = XInternAtom(_display, "_NET_WM_STATE_ABOVE", false);
336 _net_wm_state_below = XInternAtom(_display, "_NET_WM_STATE_BELOW", false);
337 _net_wm_state_add = XInternAtom(_display, "_NET_WM_STATE_ADD", false);
338 _net_wm_state_remove = XInternAtom(_display, "_NET_WM_STATE_REMOVE", false);
339 _net_wm_bypass_compositor = XInternAtom(_display, "_NET_WM_BYPASS_COMPOSITOR", false);
340}
341
342/**
343 *
344 */
345x11GraphicsPipe::
346~x11GraphicsPipe() {
347 release_hidden_cursor();
348 if (_im) {
349 XCloseIM(_im);
350 }
351 if (_display) {
352 XCloseDisplay(_display);
353 }
354}
355
356/**
357 * Returns an XRRScreenResources object, or null if RandR 1.2 is not supported.
358 */
359std::unique_ptr<XRRScreenResources, pfn_XRRFreeScreenResources> x11GraphicsPipe::
360get_screen_resources() const {
361 XRRScreenResources *res = nullptr;
362
363 if (_have_xrandr && _XRRGetScreenResourcesCurrent != nullptr) {
364 res = _XRRGetScreenResourcesCurrent(_display, _root);
365 }
366
367 return std::unique_ptr<XRRScreenResources, pfn_XRRFreeScreenResources>(res, _XRRFreeScreenResources);
368}
369
370/**
371 * Returns an XRRCrtcInfo object, or null if RandR 1.2 is not supported.
372 */
373std::unique_ptr<XRRCrtcInfo, pfn_XRRFreeCrtcInfo> x11GraphicsPipe::
374get_crtc_info(XRRScreenResources *res, RRCrtc crtc) const {
375 XRRCrtcInfo *info = nullptr;
376
377 if (_have_xrandr && _XRRGetCrtcInfo != nullptr) {
378 info = _XRRGetCrtcInfo(_display, res, crtc);
379 }
380
381 return std::unique_ptr<XRRCrtcInfo, pfn_XRRFreeCrtcInfo>(info, _XRRFreeCrtcInfo);
382}
383
384/**
385 * Finds a CRTC for going fullscreen to, at the given origin. The new CRTC
386 * is returned, along with its x, y, width and height.
387 *
388 * If the required RandR extension is not supported, a value of None will be
389 * returned, but x, y, width and height will still be populated.
390 */
392find_fullscreen_crtc(const LPoint2i &point,
393 int &x, int &y, int &width, int &height) {
394 x = 0;
395 y = 0;
396 width = DisplayWidth(_display, _screen);
397 height = DisplayHeight(_display, _screen);
398
399 if (auto res = get_screen_resources()) {
400 for (int i = 0; i < res->ncrtc; ++i) {
401 RRCrtc crtc = res->crtcs[i];
402 if (auto info = get_crtc_info(res.get(), crtc)) {
403 if (point[0] >= info->x && point[0] < info->x + (int)info->width &&
404 point[1] >= info->y && point[1] < info->y + (int)info->height) {
405
406 x = info->x;
407 y = info->y;
408 width = (int)info->width;
409 height = (int)info->height;
410 return crtc;
411 }
412 }
413 }
414 }
415
416 return None;
417}
418
419/**
420 * Returns an indication of the thread in which this GraphicsPipe requires its
421 * window processing to be performed: typically either the app thread (e.g.
422 * X) or the draw thread (Windows).
423 */
424GraphicsPipe::PreferredWindowThread
426 // Actually, since we're creating the graphics context in open_window() now,
427 // it appears we need to ensure the open_window() call is performed in the
428 // draw thread for now, even though X wants all of its calls to be single-
429 // threaded.
430
431 // This means that all X windows may have to be handled by the same draw
432 // thread, which we didn't intend (though the global _x_mutex may allow them
433 // to be technically served by different threads, even though the actual X
434 // calls will be serialized). There might be a better way.
435
436 return PWT_draw;
437}
438
439/**
440 * Called once to make an invisible Cursor for return from
441 * get_hidden_cursor().
442 */
443void x11GraphicsPipe::
444make_hidden_cursor() {
445 nassertv(_hidden_cursor == None);
446
447 unsigned int x_size, y_size;
448 XQueryBestCursor(_display, _root, 1, 1, &x_size, &y_size);
449
450 Pixmap empty = XCreatePixmap(_display, _root, x_size, y_size, 1);
451
452 XColor black;
453 memset(&black, 0, sizeof(black));
454
455 _hidden_cursor = XCreatePixmapCursor(_display, empty, empty,
456 &black, &black, x_size, y_size);
457 XFreePixmap(_display, empty);
458}
459
460/**
461 * Called once to release the invisible cursor created by
462 * make_hidden_cursor().
463 */
464void x11GraphicsPipe::
465release_hidden_cursor() {
466 if (_hidden_cursor != None) {
467 XFreeCursor(_display, _hidden_cursor);
468 _hidden_cursor = None;
469 }
470}
471
472/**
473 * Installs new Xlib error handler functions if this is the first time this
474 * function has been called. These error handler functions will attempt to
475 * reduce Xlib's annoying tendency to shut down the client at the first error.
476 * Unfortunately, it is difficult to play nice with the client if it has
477 * already installed its own error handlers.
478 */
479void x11GraphicsPipe::
480install_error_handlers() {
481 if (_error_handlers_installed) {
482 return;
483 }
484
485 _prev_error_handler = (ErrorHandlerFunc *)XSetErrorHandler(error_handler);
486 _prev_io_error_handler = (IOErrorHandlerFunc *)XSetIOErrorHandler(io_error_handler);
487 _error_handlers_installed = true;
488}
489
490/**
491 * This function is installed as the error handler for a non-fatal Xlib error.
492 */
493int x11GraphicsPipe::
494error_handler(X11_Display *display, XErrorEvent *error) {
495 ++_x_error_count;
496
497 static const int msg_len = 80;
498 char msg[msg_len];
499 XGetErrorText(display, error->error_code, msg, msg_len);
500
501 if (!_x_error_messages_enabled) {
502 if (x11display_cat.is_debug()) {
503 x11display_cat.debug()
504 << msg << "\n";
505 }
506 return 0;
507 }
508
509 x11display_cat.error()
510 << msg << "\n";
511
512 if (x_error_abort) {
513 abort();
514 }
515
516 // We return to allow the application to continue running, unlike the
517 // default X error handler which exits.
518 return 0;
519}
520
521/**
522 * This function is installed as the error handler for a fatal Xlib error.
523 */
524int x11GraphicsPipe::
525io_error_handler(X11_Display *display) {
526 x11display_cat.fatal()
527 << "X fatal error on display " << (void *)display << "\n";
528
529 // Unfortunately, we can't continue from this function, even if we promise
530 // never to use X again. We're supposed to terminate without returning, and
531 // if we do return, the caller will exit anyway. Sigh. Very poor design on
532 // X's part.
533 return 0;
534}
get_value
Returns the variable's value.
get_environment_variable
Returns the definition of the indicated environment variable, or the empty string if the variable is ...
A lightweight reentrant mutex.
Definition: lightReMutex.h:32
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
std::unique_ptr< XRRScreenResources, pfn_XRRFreeScreenResources > get_screen_resources() const
Returns an XRRScreenResources object, or null if RandR 1.2 is not supported.
std::unique_ptr< XRRCrtcInfo, pfn_XRRFreeCrtcInfo > get_crtc_info(XRRScreenResources *res, RRCrtc crtc) const
Returns an XRRCrtcInfo object, or null if RandR 1.2 is not supported.
RRCrtc find_fullscreen_crtc(const LPoint2i &point, int &x, int &y, int &width, int &height)
Finds a CRTC for going fullscreen to, at the given origin.
virtual PreferredWindowThread get_preferred_window_thread() const
Returns an indication of the thread in which this GraphicsPipe requires its window processing to be p...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
double pstrtod(const char *nptr, char **endptr)
This function re-implements strtod, to avoid the problems that occur when the LC_NUMERIC locale gets ...
Definition: pstrtod.cxx:31
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.