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