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"
17 #include "frameBufferProperties.h"
18 #include "displayInformation.h"
19 #include "pstrtod.h"
20 
21 #include <dlfcn.h>
22 
23 TypeHandle x11GraphicsPipe::_type_handle;
24 
25 bool x11GraphicsPipe::_error_handlers_installed = false;
26 x11GraphicsPipe::ErrorHandlerFunc *x11GraphicsPipe::_prev_error_handler;
27 x11GraphicsPipe::IOErrorHandlerFunc *x11GraphicsPipe::_prev_io_error_handler;
28 bool x11GraphicsPipe::_x_error_messages_enabled = true;
29 int x11GraphicsPipe::_x_error_count = 0;
30 
31 LightReMutex x11GraphicsPipe::_x_mutex;
32 
33 /**
34  *
35  */
36 x11GraphicsPipe::
37 x11GraphicsPipe(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  */
345 x11GraphicsPipe::
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  */
359 std::unique_ptr<XRRScreenResources, pfn_XRRFreeScreenResources> x11GraphicsPipe::
360 get_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  */
373 std::unique_ptr<XRRCrtcInfo, pfn_XRRFreeCrtcInfo> x11GraphicsPipe::
374 get_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  */
392 find_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  */
424 GraphicsPipe::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  */
443 void x11GraphicsPipe::
444 make_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  */
464 void x11GraphicsPipe::
465 release_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  */
479 void x11GraphicsPipe::
480 install_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  */
493 int x11GraphicsPipe::
494 error_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  */
524 int x11GraphicsPipe::
525 io_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.