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; relative mouse mode 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_name = XInternAtom(_display, "_NET_WM_NAME", false);
344 _net_wm_pid = XInternAtom(_display, "_NET_WM_PID", false);
345 _net_wm_window_type = XInternAtom(_display, "_NET_WM_WINDOW_TYPE", false);
346 _net_wm_window_type_splash = XInternAtom(_display, "_NET_WM_WINDOW_TYPE_SPLASH", false);
347 _net_wm_window_type_fullscreen = XInternAtom(_display, "_NET_WM_WINDOW_TYPE_FULLSCREEN", false);
348 _net_wm_state = XInternAtom(_display, "_NET_WM_STATE", false);
349 _net_wm_state_fullscreen = XInternAtom(_display, "_NET_WM_STATE_FULLSCREEN", false);
350 _net_wm_state_above = XInternAtom(_display, "_NET_WM_STATE_ABOVE", false);
351 _net_wm_state_below = XInternAtom(_display, "_NET_WM_STATE_BELOW", false);
352 _net_wm_state_add = XInternAtom(_display, "_NET_WM_STATE_ADD", false);
353 _net_wm_state_remove = XInternAtom(_display, "_NET_WM_STATE_REMOVE", false);
354 _net_wm_bypass_compositor = XInternAtom(_display, "_NET_WM_BYPASS_COMPOSITOR", false);
355}
356
357/**
358 *
359 */
360x11GraphicsPipe::
361~x11GraphicsPipe() {
362 release_hidden_cursor();
363 if (_im) {
364 XCloseIM(_im);
365 }
366 if (_display) {
367 XCloseDisplay(_display);
368 }
369}
370
371/**
372 * Returns an XRRScreenResources object, or null if RandR 1.2 is not supported.
373 */
374std::unique_ptr<XRRScreenResources, pfn_XRRFreeScreenResources> x11GraphicsPipe::
375get_screen_resources() const {
376 XRRScreenResources *res = nullptr;
377
378 if (_have_xrandr && _XRRGetScreenResourcesCurrent != nullptr) {
379 res = _XRRGetScreenResourcesCurrent(_display, _root);
380 }
381
382 return std::unique_ptr<XRRScreenResources, pfn_XRRFreeScreenResources>(res, _XRRFreeScreenResources);
383}
384
385/**
386 * Returns an XRRCrtcInfo object, or null if RandR 1.2 is not supported.
387 */
388std::unique_ptr<XRRCrtcInfo, pfn_XRRFreeCrtcInfo> x11GraphicsPipe::
389get_crtc_info(XRRScreenResources *res, RRCrtc crtc) const {
390 XRRCrtcInfo *info = nullptr;
391
392 if (_have_xrandr && _XRRGetCrtcInfo != nullptr) {
393 info = _XRRGetCrtcInfo(_display, res, crtc);
394 }
395
396 return std::unique_ptr<XRRCrtcInfo, pfn_XRRFreeCrtcInfo>(info, _XRRFreeCrtcInfo);
397}
398
399/**
400 * Finds a CRTC for going fullscreen to, at the given origin. The new CRTC
401 * is returned, along with its x, y, width and height.
402 *
403 * If the required RandR extension is not supported, a value of None will be
404 * returned, but x, y, width and height will still be populated.
405 */
407find_fullscreen_crtc(const LPoint2i &point,
408 int &x, int &y, int &width, int &height) {
409 x = 0;
410 y = 0;
411 width = DisplayWidth(_display, _screen);
412 height = DisplayHeight(_display, _screen);
413
414 if (auto res = get_screen_resources()) {
415 for (int i = 0; i < res->ncrtc; ++i) {
416 RRCrtc crtc = res->crtcs[i];
417 if (auto info = get_crtc_info(res.get(), crtc)) {
418 if (point[0] >= info->x && point[0] < info->x + (int)info->width &&
419 point[1] >= info->y && point[1] < info->y + (int)info->height) {
420
421 x = info->x;
422 y = info->y;
423 width = (int)info->width;
424 height = (int)info->height;
425 return crtc;
426 }
427 }
428 }
429 }
430
431 return None;
432}
433
434/**
435 * Returns an indication of the thread in which this GraphicsPipe requires its
436 * window processing to be performed: typically either the app thread (e.g.
437 * X) or the draw thread (Windows).
438 */
439GraphicsPipe::PreferredWindowThread
441 // Actually, since we're creating the graphics context in open_window() now,
442 // it appears we need to ensure the open_window() call is performed in the
443 // draw thread for now, even though X wants all of its calls to be single-
444 // threaded.
445
446 // This means that all X windows may have to be handled by the same draw
447 // thread, which we didn't intend (though the global _x_mutex may allow them
448 // to be technically served by different threads, even though the actual X
449 // calls will be serialized). There might be a better way.
450
451 return PWT_draw;
452}
453
454/**
455 * Called once to make an invisible Cursor for return from
456 * get_hidden_cursor().
457 */
458void x11GraphicsPipe::
459make_hidden_cursor() {
460 nassertv(_hidden_cursor == None);
461
462 unsigned int x_size, y_size;
463 XQueryBestCursor(_display, _root, 1, 1, &x_size, &y_size);
464
465 Pixmap empty = XCreatePixmap(_display, _root, x_size, y_size, 1);
466
467 XColor black;
468 memset(&black, 0, sizeof(black));
469
470 _hidden_cursor = XCreatePixmapCursor(_display, empty, empty,
471 &black, &black, x_size, y_size);
472 XFreePixmap(_display, empty);
473}
474
475/**
476 * Called once to release the invisible cursor created by
477 * make_hidden_cursor().
478 */
479void x11GraphicsPipe::
480release_hidden_cursor() {
481 if (_hidden_cursor != None) {
482 XFreeCursor(_display, _hidden_cursor);
483 _hidden_cursor = None;
484 }
485}
486
487/**
488 * Installs new Xlib error handler functions if this is the first time this
489 * function has been called. These error handler functions will attempt to
490 * reduce Xlib's annoying tendency to shut down the client at the first error.
491 * Unfortunately, it is difficult to play nice with the client if it has
492 * already installed its own error handlers.
493 */
494void x11GraphicsPipe::
495install_error_handlers() {
496 if (_error_handlers_installed) {
497 return;
498 }
499
500 _prev_error_handler = (ErrorHandlerFunc *)XSetErrorHandler(error_handler);
501 _prev_io_error_handler = (IOErrorHandlerFunc *)XSetIOErrorHandler(io_error_handler);
502 _error_handlers_installed = true;
503}
504
505/**
506 * This function is installed as the error handler for a non-fatal Xlib error.
507 */
508int x11GraphicsPipe::
509error_handler(X11_Display *display, XErrorEvent *error) {
510 ++_x_error_count;
511
512 static const int msg_len = 80;
513 char msg[msg_len];
514 XGetErrorText(display, error->error_code, msg, msg_len);
515
516 if (!_x_error_messages_enabled) {
517 if (x11display_cat.is_debug()) {
518 x11display_cat.debug()
519 << msg << "\n";
520 }
521 return 0;
522 }
523
524 x11display_cat.error()
525 << msg << "\n";
526
527 if (x_error_abort) {
528 abort();
529 }
530
531 // We return to allow the application to continue running, unlike the
532 // default X error handler which exits.
533 return 0;
534}
535
536/**
537 * This function is installed as the error handler for a fatal Xlib error.
538 */
539int x11GraphicsPipe::
540io_error_handler(X11_Display *display) {
541 x11display_cat.fatal()
542 << "X fatal error on display " << (void *)display << "\n";
543
544 // Unfortunately, we can't continue from this function, even if we promise
545 // never to use X again. We're supposed to terminate without returning, and
546 // if we do return, the caller will exit anyway. Sigh. Very poor design on
547 // X's part.
548 return 0;
549}
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.