Panda3D
Loading...
Searching...
No Matches
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
228 // Query current configuration, we just grab the first CRTC for now,
229 // since we don't have a way to represent multiple monitors.
230 RRMode current_mode_id = 0;
231 if (res->ncrtc > 0) {
232 if (auto info = get_crtc_info(res.get(), res->crtcs[0])) {
233 current_mode_id = info->mode;
234 }
235 }
236
237 _display_information->_total_display_modes = res->nmode;
238 _display_information->_display_mode_array = new DisplayMode[res->nmode];
239 for (int i = 0; i < res->nmode; ++i) {
240 XRRModeInfo &mode = res->modes[i];
241
242 if (mode.id == current_mode_id) {
243 _display_information->_current_display_mode_index = i;
244 }
245
246 DisplayMode *dm = _display_information->_display_mode_array + i;
247 dm->width = mode.width;
248 dm->height = mode.height;
249 dm->bits_per_pixel = -1;
250 dm->fullscreen_only = false;
251
252 if (mode.hTotal && mode.vTotal) {
253 dm->refresh_rate = (double)mode.dotClock /
254 ((double)mode.hTotal * (double)mode.vTotal);
255 } else {
256 dm->refresh_rate = 0;
257 }
258 }
259 } else {
260 if (x11display_cat.is_debug()) {
261 x11display_cat.debug()
262 << "Using XRRSizes and XRRRates to obtain display modes\n";
263 }
264
265 int num_sizes, num_rates;
266 XRRScreenSize *xrrs;
267 xrrs = _XRRSizes(_display, 0, &num_sizes);
268 _display_information->_total_display_modes = 0;
269 for (int i = 0; i < num_sizes; ++i) {
270 _XRRRates(_display, 0, i, &num_rates);
271 _display_information->_total_display_modes += num_rates;
272 }
273
274 short *rates;
275 short counter = 0;
276 _display_information->_display_mode_array = new DisplayMode[_display_information->_total_display_modes];
277 for (int i = 0; i < num_sizes; ++i) {
278 int num_rates;
279 rates = _XRRRates(_display, 0, i, &num_rates);
280 for (int j = 0; j < num_rates; ++j) {
281 DisplayMode* dm = _display_information->_display_mode_array + counter;
282 dm->width = xrrs[i].width;
283 dm->height = xrrs[i].height;
284 dm->refresh_rate = rates[j];
285 dm->bits_per_pixel = -1;
286 dm->fullscreen_only = false;
287 ++counter;
288 }
289 }
290 }
291 }
292
293 // Connect to an input method for supporting international text entry.
294 _im = XOpenIM(_display, nullptr, nullptr, nullptr);
295 if (_im == (XIM)nullptr) {
296 // Fall back to internal input method.
297 XSetLocaleModifiers("@im=none");
298 _im = XOpenIM(_display, nullptr, nullptr, nullptr);
299 if (_im == (XIM)nullptr) {
300 x11display_cat.warning()
301 << "Couldn't open input method.\n";
302 }
303 }
304
305 // What styles does the current input method support?
306 /*
307 XIMStyles *im_supported_styles;
308 XGetIMValues(_im, XNQueryInputStyle, &im_supported_styles, NULL);
309
310 for (int i = 0; i < im_supported_styles->count_styles; i++) {
311 XIMStyle style = im_supported_styles->supported_styles[i];
312 cerr << "style " << i << ". " << hex << style << dec << "\n";
313 }
314
315 XFree(im_supported_styles);
316 */
317
318 const char *dpi = XGetDefault(_display, "Xft", "dpi");
319 if (dpi != nullptr) {
320 char *endptr = nullptr;
321 double result = pstrtod(dpi, &endptr);
322 if (result != 0 && !cnan(result) && endptr[0] == '\0') {
323 result /= 96;
324 set_detected_display_zoom(result);
325
326 if (x11display_cat.is_debug()) {
327 x11display_cat.debug()
328 << "Determined display zoom to be " << result
329 << " based on specified Xft.dpi " << dpi << "\n";
330 }
331 } else {
332 x11display_cat.warning()
333 << "Unable to determine display zoom because Xft.dpi is invalid: "
334 << dpi << "\n";
335 }
336 } else if (x11display_cat.is_debug()) {
337 x11display_cat.debug()
338 << "Unable to determine display zoom because Xft.dpi was not set.\n";
339 }
340
341 // Get some X atom numbers.
342 _wm_delete_window = XInternAtom(_display, "WM_DELETE_WINDOW", false);
343 _net_wm_pid = XInternAtom(_display, "_NET_WM_PID", false);
344 _net_wm_window_type = XInternAtom(_display, "_NET_WM_WINDOW_TYPE", false);
345 _net_wm_window_type_splash = XInternAtom(_display, "_NET_WM_WINDOW_TYPE_SPLASH", false);
346 _net_wm_window_type_fullscreen = XInternAtom(_display, "_NET_WM_WINDOW_TYPE_FULLSCREEN", false);
347 _net_wm_state = XInternAtom(_display, "_NET_WM_STATE", false);
348 _net_wm_state_fullscreen = XInternAtom(_display, "_NET_WM_STATE_FULLSCREEN", false);
349 _net_wm_state_above = XInternAtom(_display, "_NET_WM_STATE_ABOVE", false);
350 _net_wm_state_below = XInternAtom(_display, "_NET_WM_STATE_BELOW", false);
351 _net_wm_state_add = XInternAtom(_display, "_NET_WM_STATE_ADD", false);
352 _net_wm_state_remove = XInternAtom(_display, "_NET_WM_STATE_REMOVE", false);
353 _net_wm_bypass_compositor = XInternAtom(_display, "_NET_WM_BYPASS_COMPOSITOR", false);
354}
355
356/**
357 *
358 */
359x11GraphicsPipe::
360~x11GraphicsPipe() {
361 release_hidden_cursor();
362 if (_im) {
363 XCloseIM(_im);
364 }
365 if (_display) {
366 XCloseDisplay(_display);
367 }
368}
369
370/**
371 * Returns an XRRScreenResources object, or null if RandR 1.2 is not supported.
372 */
373std::unique_ptr<XRRScreenResources, pfn_XRRFreeScreenResources> x11GraphicsPipe::
374get_screen_resources() const {
375 XRRScreenResources *res = nullptr;
376
377 if (_have_xrandr && _XRRGetScreenResourcesCurrent != nullptr) {
378 res = _XRRGetScreenResourcesCurrent(_display, _root);
379 }
380
381 return std::unique_ptr<XRRScreenResources, pfn_XRRFreeScreenResources>(res, _XRRFreeScreenResources);
382}
383
384/**
385 * Returns an XRRCrtcInfo object, or null if RandR 1.2 is not supported.
386 */
387std::unique_ptr<XRRCrtcInfo, pfn_XRRFreeCrtcInfo> x11GraphicsPipe::
388get_crtc_info(XRRScreenResources *res, RRCrtc crtc) const {
389 XRRCrtcInfo *info = nullptr;
390
391 if (_have_xrandr && _XRRGetCrtcInfo != nullptr) {
392 info = _XRRGetCrtcInfo(_display, res, crtc);
393 }
394
395 return std::unique_ptr<XRRCrtcInfo, pfn_XRRFreeCrtcInfo>(info, _XRRFreeCrtcInfo);
396}
397
398/**
399 * Finds a CRTC for going fullscreen to, at the given origin. The new CRTC
400 * is returned, along with its x, y, width and height.
401 *
402 * If the required RandR extension is not supported, a value of None will be
403 * returned, but x, y, width and height will still be populated.
404 */
406find_fullscreen_crtc(const LPoint2i &point,
407 int &x, int &y, int &width, int &height) {
408 x = 0;
409 y = 0;
410 width = DisplayWidth(_display, _screen);
411 height = DisplayHeight(_display, _screen);
412
413 if (auto res = get_screen_resources()) {
414 for (int i = 0; i < res->ncrtc; ++i) {
415 RRCrtc crtc = res->crtcs[i];
416 if (auto info = get_crtc_info(res.get(), crtc)) {
417 if (point[0] >= info->x && point[0] < info->x + (int)info->width &&
418 point[1] >= info->y && point[1] < info->y + (int)info->height) {
419
420 x = info->x;
421 y = info->y;
422 width = (int)info->width;
423 height = (int)info->height;
424 return crtc;
425 }
426 }
427 }
428 }
429
430 return None;
431}
432
433/**
434 * Returns an indication of the thread in which this GraphicsPipe requires its
435 * window processing to be performed: typically either the app thread (e.g.
436 * X) or the draw thread (Windows).
437 */
438GraphicsPipe::PreferredWindowThread
440 // Actually, since we're creating the graphics context in open_window() now,
441 // it appears we need to ensure the open_window() call is performed in the
442 // draw thread for now, even though X wants all of its calls to be single-
443 // threaded.
444
445 // This means that all X windows may have to be handled by the same draw
446 // thread, which we didn't intend (though the global _x_mutex may allow them
447 // to be technically served by different threads, even though the actual X
448 // calls will be serialized). There might be a better way.
449
450 return PWT_draw;
451}
452
453/**
454 * Called once to make an invisible Cursor for return from
455 * get_hidden_cursor().
456 */
457void x11GraphicsPipe::
458make_hidden_cursor() {
459 nassertv(_hidden_cursor == None);
460
461 unsigned int x_size, y_size;
462 XQueryBestCursor(_display, _root, 1, 1, &x_size, &y_size);
463
464 Pixmap empty = XCreatePixmap(_display, _root, x_size, y_size, 1);
465
466 XColor black;
467 memset(&black, 0, sizeof(black));
468
469 _hidden_cursor = XCreatePixmapCursor(_display, empty, empty,
470 &black, &black, x_size, y_size);
471 XFreePixmap(_display, empty);
472}
473
474/**
475 * Called once to release the invisible cursor created by
476 * make_hidden_cursor().
477 */
478void x11GraphicsPipe::
479release_hidden_cursor() {
480 if (_hidden_cursor != None) {
481 XFreeCursor(_display, _hidden_cursor);
482 _hidden_cursor = None;
483 }
484}
485
486/**
487 * Installs new Xlib error handler functions if this is the first time this
488 * function has been called. These error handler functions will attempt to
489 * reduce Xlib's annoying tendency to shut down the client at the first error.
490 * Unfortunately, it is difficult to play nice with the client if it has
491 * already installed its own error handlers.
492 */
493void x11GraphicsPipe::
494install_error_handlers() {
495 if (_error_handlers_installed) {
496 return;
497 }
498
499 _prev_error_handler = (ErrorHandlerFunc *)XSetErrorHandler(error_handler);
500 _prev_io_error_handler = (IOErrorHandlerFunc *)XSetIOErrorHandler(io_error_handler);
501 _error_handlers_installed = true;
502}
503
504/**
505 * This function is installed as the error handler for a non-fatal Xlib error.
506 */
507int x11GraphicsPipe::
508error_handler(X11_Display *display, XErrorEvent *error) {
509 ++_x_error_count;
510
511 static const int msg_len = 80;
512 char msg[msg_len];
513 XGetErrorText(display, error->error_code, msg, msg_len);
514
515 if (!_x_error_messages_enabled) {
516 if (x11display_cat.is_debug()) {
517 x11display_cat.debug()
518 << msg << "\n";
519 }
520 return 0;
521 }
522
523 x11display_cat.error()
524 << msg << "\n";
525
526 if (x_error_abort) {
527 abort();
528 }
529
530 // We return to allow the application to continue running, unlike the
531 // default X error handler which exits.
532 return 0;
533}
534
535/**
536 * This function is installed as the error handler for a fatal Xlib error.
537 */
538int x11GraphicsPipe::
539io_error_handler(X11_Display *display) {
540 x11display_cat.fatal()
541 << "X fatal error on display " << (void *)display << "\n";
542
543 // Unfortunately, we can't continue from this function, even if we promise
544 // never to use X again. We're supposed to terminate without returning, and
545 // if we do return, the caller will exit anyway. Sigh. Very poor design on
546 // X's part.
547 return 0;
548}
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.
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.