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  // Fall back to internal input method.
282  XSetLocaleModifiers("@im=none");
283  _im = XOpenIM(_display, nullptr, nullptr, nullptr);
284  if (_im == (XIM)nullptr) {
285  x11display_cat.warning()
286  << "Couldn't open input method.\n";
287  }
288  }
289 
290  // What styles does the current input method support?
291  /*
292  XIMStyles *im_supported_styles;
293  XGetIMValues(_im, XNQueryInputStyle, &im_supported_styles, NULL);
294 
295  for (int i = 0; i < im_supported_styles->count_styles; i++) {
296  XIMStyle style = im_supported_styles->supported_styles[i];
297  cerr << "style " << i << ". " << hex << style << dec << "\n";
298  }
299 
300  XFree(im_supported_styles);
301  */
302 
303  // Get some X atom numbers.
304  _wm_delete_window = XInternAtom(_display, "WM_DELETE_WINDOW", false);
305  _net_wm_pid = XInternAtom(_display, "_NET_WM_PID", false);
306  _net_wm_window_type = XInternAtom(_display, "_NET_WM_WINDOW_TYPE", false);
307  _net_wm_window_type_splash = XInternAtom(_display, "_NET_WM_WINDOW_TYPE_SPLASH", false);
308  _net_wm_window_type_fullscreen = XInternAtom(_display, "_NET_WM_WINDOW_TYPE_FULLSCREEN", false);
309  _net_wm_state = XInternAtom(_display, "_NET_WM_STATE", false);
310  _net_wm_state_fullscreen = XInternAtom(_display, "_NET_WM_STATE_FULLSCREEN", false);
311  _net_wm_state_above = XInternAtom(_display, "_NET_WM_STATE_ABOVE", false);
312  _net_wm_state_below = XInternAtom(_display, "_NET_WM_STATE_BELOW", false);
313  _net_wm_state_add = XInternAtom(_display, "_NET_WM_STATE_ADD", false);
314  _net_wm_state_remove = XInternAtom(_display, "_NET_WM_STATE_REMOVE", false);
315  _net_wm_bypass_compositor = XInternAtom(_display, "_NET_WM_BYPASS_COMPOSITOR", false);
316 }
317 
318 /**
319  *
320  */
321 x11GraphicsPipe::
322 ~x11GraphicsPipe() {
323  release_hidden_cursor();
324  if (_im) {
325  XCloseIM(_im);
326  }
327  if (_display) {
328  XCloseDisplay(_display);
329  }
330 }
331 
332 /**
333  * Returns an XRRScreenResources object, or null if RandR 1.2 is not supported.
334  */
335 std::unique_ptr<XRRScreenResources, pfn_XRRFreeScreenResources> x11GraphicsPipe::
336 get_screen_resources() const {
337  XRRScreenResources *res = nullptr;
338 
339  if (_have_xrandr && _XRRGetScreenResourcesCurrent != nullptr) {
340  res = _XRRGetScreenResourcesCurrent(_display, _root);
341  }
342 
343  return std::unique_ptr<XRRScreenResources, pfn_XRRFreeScreenResources>(res, _XRRFreeScreenResources);
344 }
345 
346 /**
347  * Returns an XRRCrtcInfo object, or null if RandR 1.2 is not supported.
348  */
349 std::unique_ptr<XRRCrtcInfo, pfn_XRRFreeCrtcInfo> x11GraphicsPipe::
350 get_crtc_info(XRRScreenResources *res, RRCrtc crtc) const {
351  XRRCrtcInfo *info = nullptr;
352 
353  if (_have_xrandr && _XRRGetCrtcInfo != nullptr) {
354  info = _XRRGetCrtcInfo(_display, res, crtc);
355  }
356 
357  return std::unique_ptr<XRRCrtcInfo, pfn_XRRFreeCrtcInfo>(info, _XRRFreeCrtcInfo);
358 }
359 
360 /**
361  * Finds a CRTC for going fullscreen to, at the given origin. The new CRTC
362  * is returned, along with its x, y, width and height.
363  *
364  * If the required RandR extension is not supported, a value of None will be
365  * returned, but x, y, width and height will still be populated.
366  */
368 find_fullscreen_crtc(const LPoint2i &point,
369  int &x, int &y, int &width, int &height) {
370  x = 0;
371  y = 0;
372  width = DisplayWidth(_display, _screen);
373  height = DisplayHeight(_display, _screen);
374 
375  if (auto res = get_screen_resources()) {
376  for (int i = 0; i < res->ncrtc; ++i) {
377  RRCrtc crtc = res->crtcs[i];
378  if (auto info = get_crtc_info(res.get(), crtc)) {
379  if (point[0] >= info->x && point[0] < info->x + (int)info->width &&
380  point[1] >= info->y && point[1] < info->y + (int)info->height) {
381 
382  x = info->x;
383  y = info->y;
384  width = (int)info->width;
385  height = (int)info->height;
386  return crtc;
387  }
388  }
389  }
390  }
391 
392  return None;
393 }
394 
395 /**
396  * Returns an indication of the thread in which this GraphicsPipe requires its
397  * window processing to be performed: typically either the app thread (e.g.
398  * X) or the draw thread (Windows).
399  */
400 GraphicsPipe::PreferredWindowThread
402  // Actually, since we're creating the graphics context in open_window() now,
403  // it appears we need to ensure the open_window() call is performed in the
404  // draw thread for now, even though X wants all of its calls to be single-
405  // threaded.
406 
407  // This means that all X windows may have to be handled by the same draw
408  // thread, which we didn't intend (though the global _x_mutex may allow them
409  // to be technically served by different threads, even though the actual X
410  // calls will be serialized). There might be a better way.
411 
412  return PWT_draw;
413 }
414 
415 /**
416  * Called once to make an invisible Cursor for return from
417  * get_hidden_cursor().
418  */
419 void x11GraphicsPipe::
420 make_hidden_cursor() {
421  nassertv(_hidden_cursor == None);
422 
423  unsigned int x_size, y_size;
424  XQueryBestCursor(_display, _root, 1, 1, &x_size, &y_size);
425 
426  Pixmap empty = XCreatePixmap(_display, _root, x_size, y_size, 1);
427 
428  XColor black;
429  memset(&black, 0, sizeof(black));
430 
431  _hidden_cursor = XCreatePixmapCursor(_display, empty, empty,
432  &black, &black, x_size, y_size);
433  XFreePixmap(_display, empty);
434 }
435 
436 /**
437  * Called once to release the invisible cursor created by
438  * make_hidden_cursor().
439  */
440 void x11GraphicsPipe::
441 release_hidden_cursor() {
442  if (_hidden_cursor != None) {
443  XFreeCursor(_display, _hidden_cursor);
444  _hidden_cursor = None;
445  }
446 }
447 
448 /**
449  * Installs new Xlib error handler functions if this is the first time this
450  * function has been called. These error handler functions will attempt to
451  * reduce Xlib's annoying tendency to shut down the client at the first error.
452  * Unfortunately, it is difficult to play nice with the client if it has
453  * already installed its own error handlers.
454  */
455 void x11GraphicsPipe::
456 install_error_handlers() {
457  if (_error_handlers_installed) {
458  return;
459  }
460 
461  _prev_error_handler = (ErrorHandlerFunc *)XSetErrorHandler(error_handler);
462  _prev_io_error_handler = (IOErrorHandlerFunc *)XSetIOErrorHandler(io_error_handler);
463  _error_handlers_installed = true;
464 }
465 
466 /**
467  * This function is installed as the error handler for a non-fatal Xlib error.
468  */
469 int x11GraphicsPipe::
470 error_handler(X11_Display *display, XErrorEvent *error) {
471  ++_x_error_count;
472 
473  static const int msg_len = 80;
474  char msg[msg_len];
475  XGetErrorText(display, error->error_code, msg, msg_len);
476 
477  if (!_x_error_messages_enabled) {
478  if (x11display_cat.is_debug()) {
479  x11display_cat.debug()
480  << msg << "\n";
481  }
482  return 0;
483  }
484 
485  x11display_cat.error()
486  << msg << "\n";
487 
488  if (x_error_abort) {
489  abort();
490  }
491 
492  // We return to allow the application to continue running, unlike the
493  // default X error handler which exits.
494  return 0;
495 }
496 
497 /**
498  * This function is installed as the error handler for a fatal Xlib error.
499  */
500 int x11GraphicsPipe::
501 io_error_handler(X11_Display *display) {
502  x11display_cat.fatal()
503  << "X fatal error on display " << (void *)display << "\n";
504 
505  // Unfortunately, we can't continue from this function, even if we promise
506  // never to use X again. We're supposed to terminate without returning, and
507  // if we do return, the caller will exit anyway. Sigh. Very poor design on
508  // X's part.
509  return 0;
510 }
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:401
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:336
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:350
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:368
x11GraphicsWindow.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
LightReMutex
A lightweight reentrant mutex.
Definition: lightReMutex.h:32
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