Panda3D
x11GraphicsWindow.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 x11GraphicsWindow.cxx
10  * @author rdb
11  * @date 2009-07-07
12  */
13 
14 #include "x11GraphicsWindow.h"
15 #include "config_x11display.h"
16 #include "x11GraphicsPipe.h"
17 
18 #include "graphicsPipe.h"
19 #include "keyboardButton.h"
20 #include "mouseButton.h"
21 #include "buttonMap.h"
22 #include "clockObject.h"
23 #include "pStatTimer.h"
24 #include "textEncoder.h"
25 #include "throw_event.h"
26 #include "lightReMutexHolder.h"
27 #include "nativeWindowHandle.h"
28 #include "virtualFileSystem.h"
29 #include "get_x11.h"
30 #include "pnmImage.h"
31 #include "pnmFileTypeRegistry.h"
32 #include "evdevInputDevice.h"
33 
34 #include <sys/time.h>
35 #include <fcntl.h>
36 
37 using std::istream;
38 using std::ostringstream;
39 using std::string;
40 
41 struct _XcursorFile {
42  void *closure;
43  int (*read)(XcursorFile *, unsigned char *, int);
44  int (*write)(XcursorFile *, unsigned char *, int);
45  int (*seek)(XcursorFile *, long, int);
46 };
47 
48 typedef struct _XcursorImage {
49  unsigned int version;
50  unsigned int size;
51  unsigned int width;
52  unsigned int height;
53  unsigned int xhot;
54  unsigned int yhot;
55  unsigned int delay;
56  unsigned int *pixels;
57 } XcursorImage;
58 
59 static int xcursor_read(XcursorFile *file, unsigned char *buf, int len) {
60  istream* str = (istream*) file->closure;
61  str->read((char*) buf, len);
62  return str->gcount();
63 }
64 
65 static int xcursor_write(XcursorFile *file, unsigned char *buf, int len) {
66  // Not implemented, we don't need it.
67  nassertr_always(false, 0);
68  return 0;
69 }
70 
71 static int xcursor_seek(XcursorFile *file, long offset, int whence) {
72  istream* str = (istream*) file->closure;
73  switch (whence) {
74  case SEEK_SET:
75  str->seekg(offset, istream::beg);
76  break;
77  case SEEK_CUR:
78  str->seekg(offset, istream::cur);
79  break;
80  case SEEK_END:
81  str->seekg(offset, istream::end);
82  }
83 
84  return str->tellg();
85 }
86 
87 TypeHandle x11GraphicsWindow::_type_handle;
88 
89 /**
90  *
91  */
92 x11GraphicsWindow::
93 x11GraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe,
94  const string &name,
95  const FrameBufferProperties &fb_prop,
96  const WindowProperties &win_prop,
97  int flags,
99  GraphicsOutput *host) :
100  GraphicsWindow(engine, pipe, name, fb_prop, win_prop, flags, gsg, host)
101 {
102  x11GraphicsPipe *x11_pipe;
103  DCAST_INTO_V(x11_pipe, _pipe);
104  _display = x11_pipe->get_display();
105  _screen = x11_pipe->get_screen();
106  _xwindow = (X11_Window)nullptr;
107  _ic = (XIC)nullptr;
108  _visual_info = nullptr;
109  _orig_size_id = -1;
110 
111  if (x11_pipe->_have_xrandr) {
112  // We may still need these functions after the pipe is already destroyed,
113  // so we copy them into the x11GraphicsWindow.
114  _XRRGetScreenInfo = x11_pipe->_XRRGetScreenInfo;
115  _XRRSetScreenConfig = x11_pipe->_XRRSetScreenConfig;
116  }
117 
118  _awaiting_configure = false;
119  _dga_mouse_enabled = false;
120  _override_redirect = False;
121  _wm_delete_window = x11_pipe->_wm_delete_window;
122 
123  PT(GraphicsWindowInputDevice) device = GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard_mouse");
124  add_input_device(device);
125  _input = device;
126 }
127 
128 /**
129  *
130  */
131 x11GraphicsWindow::
132 ~x11GraphicsWindow() {
133  if (!_cursor_filenames.empty()) {
134  LightReMutexHolder holder(x11GraphicsPipe::_x_mutex);
135  for (auto item : _cursor_filenames) {
136  XFreeCursor(_display, item.second);
137  }
138  }
139 }
140 
141 /**
142  * Returns the MouseData associated with the nth input device's pointer. This
143  * is deprecated; use get_pointer_device().get_pointer() instead, or for raw
144  * mice, use the InputDeviceManager interface.
145  */
147 get_pointer(int device) const {
148  MouseData result;
149  {
150  LightMutexHolder holder(_input_lock);
151  nassertr(device >= 0 && device < (int)_input_devices.size(), MouseData());
152 
153  result = ((const GraphicsWindowInputDevice *)_input_devices[device].p())->get_pointer();
154 
155  // We recheck this immediately to get the most up-to-date value, but we
156  // won't bother waiting for the lock if we can't.
157  if (device == 0 && !_dga_mouse_enabled && result._in_window &&
158  x11GraphicsPipe::_x_mutex.try_lock()) {
159  XEvent event;
160  if (_xwindow != None &&
161  XQueryPointer(_display, _xwindow, &event.xbutton.root,
162  &event.xbutton.window, &event.xbutton.x_root, &event.xbutton.y_root,
163  &event.xbutton.x, &event.xbutton.y, &event.xbutton.state)) {
164  double time = ClockObject::get_global_clock()->get_real_time();
165  result._xpos = event.xbutton.x;
166  result._ypos = event.xbutton.y;
167  ((GraphicsWindowInputDevice *)_input_devices[0].p())->set_pointer_in_window(result._xpos, result._ypos, time);
168  }
169  x11GraphicsPipe::_x_mutex.release();
170  }
171  }
172  return result;
173 }
174 
175 /**
176  * Forces the pointer to the indicated position within the window, if
177  * possible.
178  *
179  * Returns true if successful, false on failure. This may fail if the mouse
180  * is not currently within the window, or if the API doesn't support this
181  * operation.
182  */
184 move_pointer(int device, int x, int y) {
185  // Note: this is not thread-safe; it should be called only from App.
186  // Probably not an issue.
187  if (device == 0) {
188  // Move the system mouse pointer.
189  PointerData md = _input->get_pointer();
190  if (!_properties.get_foreground() || !md.get_in_window()) {
191  // If the window doesn't have input focus, or the mouse isn't currently
192  // within the window, forget it.
193  return false;
194  }
195 
196  if (!md.get_in_window() || md.get_x() != x || md.get_y() != y) {
197  if (!_dga_mouse_enabled) {
198  LightReMutexHolder holder(x11GraphicsPipe::_x_mutex);
199  XWarpPointer(_display, None, _xwindow, 0, 0, 0, 0, x, y);
200  }
201  _input->set_pointer_in_window(x, y);
202  }
203  return true;
204  } else {
205  // Can't move a raw mouse.
206  return false;
207  }
208 }
209 
210 /**
211  * Clears the entire framebuffer before rendering, according to the settings
212  * of get_color_clear_active() and get_depth_clear_active() (inherited from
213  * DrawableRegion).
214  *
215  * This function is called only within the draw thread.
216  */
218 clear(Thread *current_thread) {
219  if (is_any_clear_active()) {
220  // Evidently the NVIDIA driver may call glXCreateNewContext inside
221  // prepare_display_region, so we need to hold the X11 lock.
222  LightReMutexHolder holder(x11GraphicsPipe::_x_mutex);
223  GraphicsOutput::clear(current_thread);
224  }
225 }
226 
227 /**
228  * This function will be called within the draw thread before beginning
229  * rendering for a given frame. It should do whatever setup is required, and
230  * return true if the frame should be rendered, or false if it should be
231  * skipped.
232  */
234 begin_frame(FrameMode mode, Thread *current_thread) {
235  PStatTimer timer(_make_current_pcollector, current_thread);
236 
237  begin_frame_spam(mode);
238  if (_gsg == nullptr) {
239  return false;
240  }
241  if (_awaiting_configure) {
242  // Don't attempt to draw while we have just reconfigured the window and we
243  // haven't got the notification back yet.
244  return false;
245  }
246 
247  // Reset the GSG state if this is the first time it has been used. (We
248  // can't just call reset() when we construct the GSG, because reset()
249  // requires having a current context.)
250  _gsg->reset_if_new();
251 
252  if (mode == FM_render) {
253  // begin_render_texture();
254  clear_cube_map_selection();
255  }
256 
257  _gsg->set_current_properties(&get_fb_properties());
258  return _gsg->begin_frame(current_thread);
259 }
260 
261 /**
262  * This function will be called within the draw thread after rendering is
263  * completed for a given frame. It should do whatever finalization is
264  * required.
265  */
267 end_frame(FrameMode mode, Thread *current_thread) {
268  end_frame_spam(mode);
269  nassertv(_gsg != nullptr);
270 
271  if (mode == FM_render) {
272  // end_render_texture();
273  copy_to_textures();
274  }
275 
276  _gsg->end_frame(current_thread);
277 
278  if (mode == FM_render) {
279  trigger_flip();
280  clear_cube_map_selection();
281  }
282 }
283 
284 /**
285  * Do whatever processing is necessary to ensure that the window responds to
286  * user events. Also, honor any requests recently made via
287  * request_properties()
288  *
289  * This function is called only within the window thread.
290  */
293  LightReMutexHolder holder(x11GraphicsPipe::_x_mutex);
294 
296 
297  if (_xwindow == (X11_Window)0) {
298  return;
299  }
300 
301  XEvent event;
302  XKeyEvent keyrelease_event;
303  bool got_keyrelease_event = false;
304 
305  XConfigureEvent configure_event;
306  bool got_configure_event = false;
307 
308  WindowProperties properties;
309  bool changed_properties = false;
310 
311  while (XCheckIfEvent(_display, &event, check_event, (char *)this)) {
312  if (XFilterEvent(&event, None)) {
313  continue;
314  }
315 
316  if (got_keyrelease_event) {
317  // If a keyrelease event is immediately followed by a matching keypress
318  // event, that's just key repeat and we should treat the two events
319  // accordingly. It would be nice if X provided a way to differentiate
320  // between keyrepeat and explicit keypresses more generally.
321  got_keyrelease_event = false;
322 
323  if (event.type == KeyPress &&
324  event.xkey.keycode == keyrelease_event.keycode &&
325  (event.xkey.time - keyrelease_event.time <= 1)) {
326  // In particular, we only generate down messages for the repeated
327  // keys, not down-and-up messages.
328  handle_keystroke(event.xkey);
329 
330  // We thought about not generating the keypress event, but we need
331  // that repeat for backspace. Rethink later.
332  handle_keypress(event.xkey);
333  continue;
334 
335  } else {
336  // This keyrelease event is not immediately followed by a matching
337  // keypress event, so it's a genuine release.
338  handle_keyrelease(keyrelease_event);
339  }
340  }
341 
342  ButtonHandle button;
343 
344  switch (event.type) {
345  case ReparentNotify:
346  break;
347 
348  case ConfigureNotify:
349  // When resizing or moving the window, multiple ConfigureNotify events
350  // may be sent in rapid succession. We only respond to the last one.
351  configure_event = event.xconfigure;
352  got_configure_event = true;
353  break;
354 
355  case ButtonPress:
356  // This refers to the mouse buttons.
357  button = get_mouse_button(event.xbutton);
358  if (!_dga_mouse_enabled) {
359  _input->set_pointer_in_window(event.xbutton.x, event.xbutton.y);
360  }
361  _input->button_down(button);
362  break;
363 
364  case ButtonRelease:
365  button = get_mouse_button(event.xbutton);
366  if (!_dga_mouse_enabled) {
367  _input->set_pointer_in_window(event.xbutton.x, event.xbutton.y);
368  }
369  _input->button_up(button);
370  break;
371 
372  case MotionNotify:
373  if (_dga_mouse_enabled) {
374  PointerData md = _input->get_pointer();
375  _input->set_pointer_in_window(md.get_x() + event.xmotion.x_root, md.get_y() + event.xmotion.y_root);
376  } else {
377  _input->set_pointer_in_window(event.xmotion.x, event.xmotion.y);
378  }
379  break;
380 
381  case KeyPress:
382  handle_keystroke(event.xkey);
383  handle_keypress(event.xkey);
384  break;
385 
386  case KeyRelease:
387  // The KeyRelease can't be processed immediately, because we have to
388  // check first if it's immediately followed by a matching KeyPress
389  // event.
390  keyrelease_event = event.xkey;
391  got_keyrelease_event = true;
392  break;
393 
394  case EnterNotify:
395  if (_dga_mouse_enabled) {
396  PointerData md = _input->get_pointer();
397  _input->set_pointer_in_window(md.get_x(), md.get_y());
398  } else {
399  _input->set_pointer_in_window(event.xcrossing.x, event.xcrossing.y);
400  }
401  break;
402 
403  case LeaveNotify:
404  _input->set_pointer_out_of_window();
405  break;
406 
407  case FocusIn:
408  properties.set_foreground(true);
409  changed_properties = true;
410  break;
411 
412  case FocusOut:
413  _input->focus_lost();
414  properties.set_foreground(false);
415  changed_properties = true;
416  break;
417 
418  case UnmapNotify:
419  properties.set_minimized(true);
420  changed_properties = true;
421  break;
422 
423  case MapNotify:
424  properties.set_minimized(false);
425  changed_properties = true;
426 
427  // Auto-focus the window when it is mapped.
428  XSetInputFocus(_display, _xwindow, RevertToPointerRoot, CurrentTime);
429  break;
430 
431  case ClientMessage:
432  if ((Atom)(event.xclient.data.l[0]) == _wm_delete_window) {
433  // This is a message from the window manager indicating that the user
434  // has requested to close the window.
435  string close_request_event = get_close_request_event();
436  if (!close_request_event.empty()) {
437  // In this case, the app has indicated a desire to intercept the
438  // request and process it directly.
439  throw_event(close_request_event);
440 
441  } else {
442  // In this case, the default case, the app does not intend to
443  // service the request, so we do by closing the window.
444 
445  // TODO: don't release the gsg in the window thread.
446  close_window();
447  properties.set_open(false);
448  system_changed_properties(properties);
449  }
450  }
451  break;
452 
453  case DestroyNotify:
454  // Apparently, we never get a DestroyNotify on a toplevel window.
455  // Instead, we rely on hints from the window manager (see above).
456  x11display_cat.info()
457  << "DestroyNotify\n";
458  break;
459 
460  default:
461  x11display_cat.warning()
462  << "unhandled X event type " << event.type << "\n";
463  }
464  }
465 
466  if (got_configure_event) {
467  // Now handle the last configure event we found.
468  _awaiting_configure = false;
469 
470  // Is this the inner corner or the outer corner? The Xlib docs say it
471  // should be the outer corner, but it appears to be the inner corner on my
472  // own implementation, which is inconsistent with XConfigureWindow.
473  // (Panda really wants to work with the inner corner, anyway, but that
474  // means we need to fix XConfigureWindow too.)
475  properties.set_origin(configure_event.x, configure_event.y);
476  properties.set_size(configure_event.width, configure_event.height);
477 
478  if (_properties.get_fixed_size()) {
479  // If the window properties indicate a fixed size only, undo any attempt
480  // by the user to change them. In X, there doesn't appear to be a way
481  // to universally disallow this directly (although we do set the
482  // min_size and max_size to the same value, which seems to work for most
483  // window managers.)
484  if (configure_event.width != _fixed_size.get_x() ||
485  configure_event.height != _fixed_size.get_y()) {
486  XWindowChanges changes;
487  changes.width = _fixed_size.get_x();
488  changes.height = _fixed_size.get_y();
489  int value_mask = (CWWidth | CWHeight);
490  XConfigureWindow(_display, _xwindow, value_mask, &changes);
491  }
492  }
493 
494  // If the window was reconfigured, we may need to re-confine the mouse
495  // pointer. See GitHub bug #280.
496  if (_properties.get_mouse_mode() == WindowProperties::M_confined) {
497  X11_Cursor cursor = None;
498  if (_properties.get_cursor_hidden()) {
499  x11GraphicsPipe *x11_pipe;
500  DCAST_INTO_V(x11_pipe, _pipe);
501  cursor = x11_pipe->get_hidden_cursor();
502  }
503 
504  XGrabPointer(_display, _xwindow, True, 0, GrabModeAsync, GrabModeAsync,
505  _xwindow, cursor, CurrentTime);
506  }
507 
508  changed_properties = true;
509  }
510 
511  if (properties.has_foreground() && (
512  _properties.get_mouse_mode() == WindowProperties::M_confined ||
513  _dga_mouse_enabled)) {
514  x11GraphicsPipe *x11_pipe;
515  DCAST_INTO_V(x11_pipe, _pipe);
516 
517  // Focus has changed, let's let go of the pointer if we've grabbed or re-grab it if needed
518  if (properties.get_foreground()) {
519  // Window is going to the foreground, re-grab the pointer
520  X11_Cursor cursor = None;
521  if (_properties.get_cursor_hidden()) {
522  cursor = x11_pipe->get_hidden_cursor();
523  }
524 
525  XGrabPointer(_display, _xwindow, True, 0, GrabModeAsync, GrabModeAsync,
526  _xwindow, cursor, CurrentTime);
527  if (_dga_mouse_enabled) {
528  x11_pipe->enable_relative_mouse();
529  }
530  }
531  else {
532  // window is leaving the foreground, ungrab the pointer
533  if (_dga_mouse_enabled) {
534  x11_pipe->disable_relative_mouse();
535  } else if (_properties.get_mouse_mode() == WindowProperties::M_confined) {
536  XUngrabPointer(_display, CurrentTime);
537  }
538  }
539  }
540 
541  if (changed_properties) {
542  system_changed_properties(properties);
543  }
544 
545  if (got_keyrelease_event) {
546  // This keyrelease event is not immediately followed by a matching
547  // keypress event, so it's a genuine release.
548  handle_keyrelease(keyrelease_event);
549  }
550 }
551 
552 /**
553  * Applies the requested set of properties to the window, if possible, for
554  * instance to request a change in size or minimization status.
555  *
556  * The window properties are applied immediately, rather than waiting until
557  * the next frame. This implies that this method may *only* be called from
558  * within the window thread.
559  *
560  * The return value is true if the properties are set, false if they are
561  * ignored. This is mainly useful for derived classes to implement extensions
562  * to this function.
563  */
566  if (_pipe == nullptr) {
567  // If the pipe is null, we're probably closing down.
569  return;
570  }
571 
572  x11GraphicsPipe *x11_pipe;
573  DCAST_INTO_V(x11_pipe, _pipe);
574 
575  LightReMutexHolder holder(x11GraphicsPipe::_x_mutex);
576 
577  // We're either going into or out of fullscreen, or are in fullscreen and
578  // are changing the resolution.
579  bool is_fullscreen = _properties.has_fullscreen() && _properties.get_fullscreen();
580  bool want_fullscreen = properties.has_fullscreen() ? properties.get_fullscreen() : is_fullscreen;
581 
582  if (want_fullscreen && properties.has_origin()) {
583  // If we're fullscreen, reject changes to the origin.
584  properties.clear_origin();
585  }
586 
587  if (is_fullscreen != want_fullscreen || (is_fullscreen && properties.has_size())) {
588  if (want_fullscreen) {
589  // OK, first figure out which CRTC the window is on. It may be on more
590  // than one, actually, so grab a point in the center in order to figure
591  // out which one it's more-or-less mostly on.
592  LPoint2i center(0, 0);
593  if (_properties.has_origin()) {
594  center = _properties.get_origin();
595  if (_properties.has_size()) {
596  center += _properties.get_size() / 2;
597  }
598  }
599  int x, y, width, height;
600  x11_pipe->find_fullscreen_crtc(center, x, y, width, height);
601 
602  // Which size should we go fullscreen in?
603  int reqsizex, reqsizey;
604  if (properties.has_size()) {
605  reqsizex = properties.get_x_size();
606  reqsizey = properties.get_y_size();
607  } else if (_properties.has_size()) {
608  reqsizex = _properties.get_x_size();
609  reqsizey = _properties.get_y_size();
610  } else {
611  reqsizex = width;
612  reqsizey = height;
613  }
614 
615  // Are we passing in pipe.display_width/height? This is actually the
616  // size of the virtual desktop, which may not be a real resolution, so
617  // if that is passed in, we have to assume that the user means to just
618  // fullscreen without changing the screen resolution.
619  if ((reqsizex == x11_pipe->get_display_width() &&
620  reqsizey == x11_pipe->get_display_height())
621  || (width == reqsizex && height == reqsizey)
622  || !x11_pipe->_have_xrandr) {
623 
624  // Cover the current CRTC.
625  properties.set_origin(x, y);
626  properties.set_size(width, height);
627 
628  if (x11display_cat.is_debug()) {
629  x11display_cat.debug()
630  << "Setting window to fullscreen on CRTC "
631  << width << "x" << height << "+" << x << "+" << y << "\n";
632  }
633  } else {
634  // We may need to change the screen resolution. The code below is
635  // suboptimal; in the future, we probably want to only touch the CRTC
636  // that the window is on.
637  XRRScreenConfiguration *conf = _XRRGetScreenInfo(_display, _xwindow ? _xwindow : x11_pipe->get_root());
638  SizeID old_size_id = x11_pipe->_XRRConfigCurrentConfiguration(conf, &_orig_rotation);
639  SizeID new_size_id = (SizeID) -1;
640  int num_sizes = 0;
641 
642  XRRScreenSize *xrrs;
643  xrrs = x11_pipe->_XRRSizes(_display, 0, &num_sizes);
644  for (int i = 0; i < num_sizes; ++i) {
645  if (xrrs[i].width == reqsizex &&
646  xrrs[i].height == reqsizey) {
647  new_size_id = i;
648  }
649  }
650  if (new_size_id == (SizeID) -1) {
651  x11display_cat.error()
652  << "Videocard has no supported display resolutions at specified res ("
653  << reqsizex << " x " << reqsizey << ")\n";
654 
655  // Just go fullscreen at native resolution, then.
656  properties.set_origin(x, y);
657  properties.set_size(width, height);
658  } else {
659  if (x11display_cat.is_debug()) {
660  x11display_cat.debug()
661  << "Switching to fullscreen with resolution "
662  << reqsizex << "x" << reqsizey << "\n";
663  }
664 
665  if (new_size_id != old_size_id) {
666  _XRRSetScreenConfig(_display, conf, x11_pipe->get_root(), new_size_id, _orig_rotation, CurrentTime);
667  if (_orig_size_id == (SizeID) -1) {
668  // Remember the original resolution so we can switch back to it.
669  _orig_size_id = old_size_id;
670  }
671 
672  // Since the above changes the entire screen configuration, we
673  // have to set the origin to 0, 0.
674  properties.set_origin(0, 0);
675  }
676  }
677  }
678  } else {
679  // Change the resolution back to what it was. Don't remove the SizeID
680  // typecast!
681  if (_orig_size_id != (SizeID) -1) {
682  XRRScreenConfiguration *conf = _XRRGetScreenInfo(_display, x11_pipe->get_root());
683  _XRRSetScreenConfig(_display, conf, x11_pipe->get_root(), _orig_size_id, _orig_rotation, CurrentTime);
684  _orig_size_id = (SizeID) -1;
685  }
686  // Set the origin back to what it was
687  if (!properties.has_origin() && _properties.has_origin()) {
688  properties.set_origin(_properties.get_x_origin(), _properties.get_y_origin());
689  }
690  }
691  }
692 
693  if (properties.has_origin()) {
694  // A coordinate of -2 means to center the window on screen.
695  if (properties.get_x_origin() == -2 || properties.get_y_origin() == -2) {
696  int x_origin = properties.get_x_origin();
697  int y_origin = properties.get_y_origin();
698  if (properties.has_size()) {
699  if (x_origin == -2) {
700  x_origin = 0.5 * (x11_pipe->get_display_width() - properties.get_x_size());
701  }
702  if (y_origin == -2) {
703  y_origin = 0.5 * (x11_pipe->get_display_height() - properties.get_y_size());
704  }
705  } else {
706  if (x_origin == -2) {
707  x_origin = 0.5 * (x11_pipe->get_display_width() - _properties.get_x_size());
708  }
709  if (y_origin == -2) {
710  y_origin = 0.5 * (x11_pipe->get_display_height() - _properties.get_y_size());
711  }
712  }
713  properties.set_origin(x_origin, y_origin);
714  }
715  }
716 
718  if (!properties.is_any_specified()) {
719  // The base class has already handled this case.
720  return;
721  }
722 
723  // The window is already open; we are limited to what we can change on the
724  // fly.
725 
726  // We'll pass some property requests on as a window manager hint.
727  set_wm_properties(properties, true);
728 
729  // The window title may be changed by issuing another hint request. Assume
730  // this will be honored.
731  if (properties.has_title()) {
732  _properties.set_title(properties.get_title());
733  properties.clear_title();
734  }
735 
736  // Same for fullscreen.
737  if (properties.has_fullscreen()) {
738  _properties.set_fullscreen(properties.get_fullscreen());
739  properties.clear_fullscreen();
740  }
741 
742  // The size and position of an already-open window are changed via explicit
743  // X calls. These may still get intercepted by the window manager. Rather
744  // than changing _properties immediately, we'll wait for the ConfigureNotify
745  // message to come back.
746  XWindowChanges changes;
747  int value_mask = 0;
748 
749  if (_properties.get_fullscreen()) {
750  changes.x = 0;
751  changes.y = 0;
752  value_mask |= CWX | CWY;
753  properties.clear_origin();
754 
755  } else if (properties.has_origin()) {
756  changes.x = properties.get_x_origin();
757  changes.y = properties.get_y_origin();
758  if (changes.x != -1) value_mask |= CWX;
759  if (changes.y != -1) value_mask |= CWY;
760  properties.clear_origin();
761  }
762 
763  // This, too. But we can't currently change out of fixed_size mode.
764  if (properties.has_fixed_size() && properties.get_fixed_size()) {
765  _properties.set_fixed_size(properties.get_fixed_size());
766  properties.clear_fixed_size();
767  _fixed_size = _properties.get_size();
768  }
769 
770  if (properties.has_size()) {
771  changes.width = properties.get_x_size();
772  changes.height = properties.get_y_size();
773  value_mask |= (CWWidth | CWHeight);
774 
775  if (_properties.get_fixed_size()) {
776  _fixed_size = properties.get_size();
777  }
778  properties.clear_size();
779  }
780 
781  if (properties.has_z_order()) {
782  // We'll send the classic stacking request through the standard interface,
783  // for users of primitive window managers; but we'll also send it as a
784  // window manager hint, for users of modern window managers.
785  _properties.set_z_order(properties.get_z_order());
786  switch (properties.get_z_order()) {
787  case WindowProperties::Z_bottom:
788  changes.stack_mode = Below;
789  break;
790 
791  case WindowProperties::Z_normal:
792  changes.stack_mode = TopIf;
793  break;
794 
795  case WindowProperties::Z_top:
796  changes.stack_mode = Above;
797  break;
798  }
799 
800  value_mask |= (CWStackMode);
801  properties.clear_z_order();
802  }
803 
804  // We hide the cursor by setting it to an invisible pixmap. We can also
805  // load a custom cursor from a file.
806  if (properties.has_cursor_hidden() || properties.has_cursor_filename()) {
807  if (properties.has_cursor_hidden()) {
808  _properties.set_cursor_hidden(properties.get_cursor_hidden());
809  properties.clear_cursor_hidden();
810  }
811  Filename cursor_filename;
812  if (properties.has_cursor_filename()) {
813  cursor_filename = properties.get_cursor_filename();
814  _properties.set_cursor_filename(cursor_filename);
815  properties.clear_cursor_filename();
816  }
817  Filename filename = properties.get_cursor_filename();
818  _properties.set_cursor_filename(filename);
819 
820  if (_properties.get_cursor_hidden()) {
821  XDefineCursor(_display, _xwindow, x11_pipe->get_hidden_cursor());
822 
823  } else if (!cursor_filename.empty()) {
824  // Note that if the cursor fails to load, cursor will be None
825  X11_Cursor cursor = get_cursor(cursor_filename);
826  XDefineCursor(_display, _xwindow, cursor);
827 
828  } else {
829  XDefineCursor(_display, _xwindow, None);
830  }
831 
832  // Regrab the mouse if we changed the cursor, otherwise it won't update.
833  if (!properties.has_mouse_mode() &&
834  _properties.get_mouse_mode() != WindowProperties::M_absolute) {
835  properties.set_mouse_mode(_properties.get_mouse_mode());
836  }
837  }
838 
839  if (properties.has_foreground()) {
840  if (properties.get_foreground()) {
841  XSetInputFocus(_display, _xwindow, RevertToPointerRoot, CurrentTime);
842  } else {
843  XSetInputFocus(_display, PointerRoot, RevertToPointerRoot, CurrentTime);
844  }
845  properties.clear_foreground();
846  }
847 
848  if (properties.has_mouse_mode()) {
849  switch (properties.get_mouse_mode()) {
850  case WindowProperties::M_absolute:
851  XUngrabPointer(_display, CurrentTime);
852  if (_dga_mouse_enabled) {
853  x11_pipe->disable_relative_mouse();
854  _dga_mouse_enabled = false;
855  }
856  _properties.set_mouse_mode(WindowProperties::M_absolute);
857  properties.clear_mouse_mode();
858  break;
859 
860  case WindowProperties::M_relative:
861  if (!_dga_mouse_enabled) {
862  if (x11_pipe->supports_relative_mouse()) {
863  X11_Cursor cursor = None;
864  if (_properties.get_cursor_hidden()) {
865  x11GraphicsPipe *x11_pipe;
866  DCAST_INTO_V(x11_pipe, _pipe);
867  cursor = x11_pipe->get_hidden_cursor();
868  }
869 
870  if (XGrabPointer(_display, _xwindow, True, 0, GrabModeAsync,
871  GrabModeAsync, _xwindow, cursor, CurrentTime) != GrabSuccess) {
872  x11display_cat.error() << "Failed to grab pointer!\n";
873  } else {
874  x11_pipe->enable_relative_mouse();
875 
876  _properties.set_mouse_mode(WindowProperties::M_relative);
877  properties.clear_mouse_mode();
878  _dga_mouse_enabled = true;
879 
880  // Get the real mouse position, so we can addsubtract our relative
881  // coordinates later.
882  XEvent event;
883  XQueryPointer(_display, _xwindow, &event.xbutton.root,
884  &event.xbutton.window, &event.xbutton.x_root, &event.xbutton.y_root,
885  &event.xbutton.x, &event.xbutton.y, &event.xbutton.state);
886  _input->set_pointer_in_window(event.xbutton.x, event.xbutton.y);
887  }
888  } else {
889  x11display_cat.info()
890  << "XF86DGA extension not available, cannot enable relative mouse mode\n";
891  _dga_mouse_enabled = false;
892  }
893  }
894  break;
895 
896  case WindowProperties::M_confined:
897  {
898  x11GraphicsPipe *x11_pipe;
899  DCAST_INTO_V(x11_pipe, _pipe);
900 
901  if (_dga_mouse_enabled) {
902  x11_pipe->disable_relative_mouse();
903  _dga_mouse_enabled = false;
904  }
905  X11_Cursor cursor = None;
906  if (_properties.get_cursor_hidden()) {
907  cursor = x11_pipe->get_hidden_cursor();
908  }
909 
910  if (XGrabPointer(_display, _xwindow, True, 0, GrabModeAsync,
911  GrabModeAsync, _xwindow, cursor, CurrentTime) != GrabSuccess) {
912  x11display_cat.error() << "Failed to grab pointer!\n";
913  } else {
914  _properties.set_mouse_mode(WindowProperties::M_confined);
915  properties.clear_mouse_mode();
916  }
917  }
918  break;
919  }
920  }
921 
922  if (value_mask != 0) {
923  // We must call this after changing the WM properties, otherwise we may
924  // get misleading ConfigureNotify events in the wrong order.
925  XReconfigureWMWindow(_display, _xwindow, _screen, value_mask, &changes);
926 
927  // Don't draw anything until this is done reconfiguring.
928  _awaiting_configure = true;
929  }
930 }
931 
932 /**
933  * Overridden from GraphicsWindow.
934  */
935 void x11GraphicsWindow::
936 mouse_mode_absolute() {
937  // unused: remove in 1.10!
938 }
939 
940 /**
941  * Overridden from GraphicsWindow.
942  */
943 void x11GraphicsWindow::
944 mouse_mode_relative() {
945  // unused: remove in 1.10!
946 }
947 
948 /**
949  * Closes the window right now. Called from the window thread.
950  */
951 void x11GraphicsWindow::
952 close_window() {
953  if (_gsg != nullptr) {
954  _gsg.clear();
955  }
956 
957  LightReMutexHolder holder(x11GraphicsPipe::_x_mutex);
958  if (_ic != (XIC)nullptr) {
959  XDestroyIC(_ic);
960  _ic = (XIC)nullptr;
961  }
962 
963  if (_xwindow != (X11_Window)nullptr) {
964  XDestroyWindow(_display, _xwindow);
965  _xwindow = (X11_Window)nullptr;
966 
967  // This may be necessary if we just closed the last X window in an
968  // application, so the server hears the close request.
969  XFlush(_display);
970  }
971 
972  // Change the resolution back to what it was. Don't remove the SizeID
973  // typecast!
974  if (_orig_size_id != (SizeID) -1) {
975  X11_Window root;
976  if (_pipe != nullptr) {
977  x11GraphicsPipe *x11_pipe;
978  DCAST_INTO_V(x11_pipe, _pipe);
979  root = x11_pipe->get_root();
980  } else {
981  // Oops. Looks like the pipe was destroyed before the window gets
982  // closed. Oh well, let's get the root window by ourselves.
983  root = RootWindow(_display, _screen);
984  }
985  XRRScreenConfiguration *conf = _XRRGetScreenInfo(_display, root);
986  _XRRSetScreenConfig(_display, conf, root, _orig_size_id, _orig_rotation, CurrentTime);
987  _orig_size_id = -1;
988  }
989 
990  GraphicsWindow::close_window();
991 }
992 
993 /**
994  * Opens the window right now. Called from the window thread. Returns true
995  * if the window is successfully opened, or false if there was a problem.
996  */
997 bool x11GraphicsWindow::
998 open_window() {
999  if (_visual_info == nullptr) {
1000  // No X visual for this fbconfig; how can we open the window?
1001  x11display_cat.error()
1002  << "No X visual: cannot open window.\n";
1003  return false;
1004  }
1005 
1006  x11GraphicsPipe *x11_pipe;
1007  DCAST_INTO_R(x11_pipe, _pipe, false);
1008 
1009  if (!_properties.has_origin()) {
1010  _properties.set_origin(0, 0);
1011  }
1012  if (!_properties.has_size()) {
1013  _properties.set_size(100, 100);
1014  }
1015 
1016  // Make sure we are not making X11 calls from other threads.
1017  LightReMutexHolder holder(x11GraphicsPipe::_x_mutex);
1018 
1019  X11_Window parent_window = x11_pipe->get_root();
1020  WindowHandle *window_handle = _properties.get_parent_window();
1021  if (window_handle != nullptr) {
1022  x11display_cat.info()
1023  << "Got parent_window " << *window_handle << "\n";
1024  WindowHandle::OSHandle *os_handle = window_handle->get_os_handle();
1025  if (os_handle != nullptr) {
1026  x11display_cat.info()
1027  << "os_handle type " << os_handle->get_type() << "\n";
1028 
1029  if (os_handle->is_of_type(NativeWindowHandle::X11Handle::get_class_type())) {
1030  NativeWindowHandle::X11Handle *x11_handle = DCAST(NativeWindowHandle::X11Handle, os_handle);
1031  parent_window = x11_handle->get_handle();
1032  } else if (os_handle->is_of_type(NativeWindowHandle::IntHandle::get_class_type())) {
1033  NativeWindowHandle::IntHandle *int_handle = DCAST(NativeWindowHandle::IntHandle, os_handle);
1034  parent_window = (X11_Window)int_handle->get_handle();
1035  }
1036  }
1037  }
1038  _parent_window_handle = window_handle;
1039 
1040  _event_mask =
1041  ButtonPressMask | ButtonReleaseMask |
1042  KeyPressMask | KeyReleaseMask |
1043  EnterWindowMask | LeaveWindowMask |
1044  PointerMotionMask |
1045  FocusChangeMask | StructureNotifyMask;
1046 
1047  // Initialize window attributes
1048  XSetWindowAttributes wa;
1049  wa.background_pixel = XBlackPixel(_display, _screen);
1050  wa.border_pixel = 0;
1051  wa.colormap = _colormap;
1052  wa.event_mask = _event_mask;
1053  wa.override_redirect = _override_redirect;
1054 
1055  unsigned long attrib_mask =
1056  CWBackPixel | CWBorderPixel | CWColormap | CWEventMask | CWOverrideRedirect;
1057 
1058  _xwindow = XCreateWindow
1059  (_display, parent_window,
1060  _properties.get_x_origin(), _properties.get_y_origin(),
1061  _properties.get_x_size(), _properties.get_y_size(),
1062  0, _visual_info->depth, InputOutput,
1063  _visual_info->visual, attrib_mask, &wa);
1064 
1065  if (_xwindow == (X11_Window)0) {
1066  x11display_cat.error()
1067  << "failed to create X window.\n";
1068  return false;
1069  }
1070 
1071  if (_properties.get_fixed_size()) {
1072  _fixed_size = _properties.get_size();
1073  }
1074 
1075  set_wm_properties(_properties, false);
1076 
1077  // We don't specify any fancy properties of the XIC. It would be nicer if
1078  // we could support fancy IM's that want preedit callbacks, etc., but that
1079  // can wait until we have an X server that actually supports these to test
1080  // it on.
1081  XIM im = x11_pipe->get_im();
1082  _ic = nullptr;
1083  if (im) {
1084  _ic = XCreateIC
1085  (im,
1086  XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
1087  nullptr);
1088  if (_ic == (XIC)nullptr) {
1089  x11display_cat.warning()
1090  << "Couldn't create input context.\n";
1091  }
1092  }
1093 
1094  if (_properties.get_cursor_hidden()) {
1095  XDefineCursor(_display, _xwindow, x11_pipe->get_hidden_cursor());
1096 
1097  } else if (_properties.has_cursor_filename() && !_properties.get_cursor_filename().empty()) {
1098  // Note that if the cursor fails to load, cursor will be None
1099  X11_Cursor cursor = get_cursor(_properties.get_cursor_filename());
1100  XDefineCursor(_display, _xwindow, cursor);
1101  }
1102 
1103  XMapWindow(_display, _xwindow);
1104 
1105  if (_properties.get_raw_mice()) {
1106  open_raw_mice();
1107  } else {
1108  if (x11display_cat.is_debug()) {
1109  x11display_cat.debug()
1110  << "Raw mice not requested.\n";
1111  }
1112  }
1113 
1114  // Create a WindowHandle for ourselves
1115  _window_handle = NativeWindowHandle::make_x11(_xwindow);
1116 
1117  // And tell our parent window that we're now its child.
1118  if (_parent_window_handle != nullptr) {
1119  _parent_window_handle->attach_child(_window_handle);
1120  }
1121 
1122  return true;
1123 }
1124 
1125 /**
1126  * Asks the window manager to set the appropriate properties. In X, these
1127  * properties cannot be specified directly by the application; they must be
1128  * requested via the window manager, which may or may not choose to honor the
1129  * request.
1130  *
1131  * If already_mapped is true, the window has already been mapped (manifested)
1132  * on the display. This means we may need to use a different action in some
1133  * cases.
1134  *
1135  * Assumes the X11 lock is held.
1136  */
1137 void x11GraphicsWindow::
1138 set_wm_properties(const WindowProperties &properties, bool already_mapped) {
1139  x11GraphicsPipe *x11_pipe;
1140  DCAST_INTO_V(x11_pipe, _pipe);
1141 
1142  // Name the window if there is a name
1143  XTextProperty window_name;
1144  XTextProperty *window_name_p = nullptr;
1145  if (properties.has_title()) {
1146  const char *name = properties.get_title().c_str();
1147  if (XStringListToTextProperty((char **)&name, 1, &window_name) != 0) {
1148  window_name_p = &window_name;
1149  }
1150  }
1151 
1152  // The size hints request a window of a particular size andor a particular
1153  // placement onscreen.
1154  XSizeHints *size_hints_p = nullptr;
1155  if (properties.has_origin() || properties.has_size()) {
1156  size_hints_p = XAllocSizeHints();
1157  if (size_hints_p != nullptr) {
1158  if (properties.has_origin()) {
1159  size_hints_p->x = properties.get_x_origin();
1160  size_hints_p->y = properties.get_y_origin();
1161  size_hints_p->flags |= USPosition;
1162  }
1163  LVecBase2i size = _properties.get_size();
1164  if (properties.has_size()) {
1165  size = properties.get_size();
1166  size_hints_p->width = size.get_x();
1167  size_hints_p->height = size.get_y();
1168  size_hints_p->flags |= USSize;
1169  }
1170  if (properties.get_fixed_size()) {
1171  size_hints_p->min_width = size.get_x();
1172  size_hints_p->min_height = size.get_y();
1173  size_hints_p->max_width = size.get_x();
1174  size_hints_p->max_height = size.get_y();
1175  size_hints_p->flags |= (PMinSize | PMaxSize);
1176  }
1177  }
1178  }
1179 
1180  // The window manager hints include requests to the window manager other
1181  // than those specific to window geometry.
1182  XWMHints *wm_hints_p = nullptr;
1183  wm_hints_p = XAllocWMHints();
1184  if (wm_hints_p != nullptr) {
1185  if (properties.has_minimized() && properties.get_minimized()) {
1186  wm_hints_p->initial_state = IconicState;
1187  } else {
1188  wm_hints_p->initial_state = NormalState;
1189  }
1190  wm_hints_p->flags = StateHint;
1191  }
1192 
1193  // Two competing window manager interfaces have evolved. One of them allows
1194  // to set certain properties as a "type"; the other one as a "state". We'll
1195  // try to honor both.
1196  static const int max_type_data = 32;
1197  int32_t type_data[max_type_data];
1198  int next_type_data = 0;
1199 
1200  static const int max_state_data = 32;
1201  int32_t state_data[max_state_data];
1202  int next_state_data = 0;
1203 
1204  static const int max_set_data = 32;
1205  class SetAction {
1206  public:
1207  inline SetAction() { }
1208  inline SetAction(Atom state, Atom action) : _state(state), _action(action) { }
1209  Atom _state;
1210  Atom _action;
1211  };
1212  SetAction set_data[max_set_data];
1213  int next_set_data = 0;
1214 
1215  if (properties.has_fullscreen()) {
1216  if (properties.get_fullscreen()) {
1217  // For a "fullscreen" request, we pass this through, hoping the window
1218  // manager will support EWMH.
1219  type_data[next_type_data++] = x11_pipe->_net_wm_window_type_fullscreen;
1220 
1221  // We also request it as a state.
1222  state_data[next_state_data++] = x11_pipe->_net_wm_state_fullscreen;
1223  // Don't ask me why this has to be 10 and not _net_wm_state_add. It
1224  // doesn't seem to work otherwise.
1225  set_data[next_set_data++] = SetAction(x11_pipe->_net_wm_state_fullscreen, 1);
1226 
1227  } else {
1228  set_data[next_set_data++] = SetAction(x11_pipe->_net_wm_state_fullscreen, 0);
1229  }
1230  }
1231 
1232  // If we asked for a window without a border, there's no excellent way to
1233  // arrange that. For users whose window managers follow the EWMH
1234  // specification, we can ask for a "splash" screen, which is usually
1235  // undecorated. It's not exactly right, but the spec doesn't give us an
1236  // exactly-right option.
1237 
1238  // For other users, we'll totally punt and just set the window's Class to
1239  // "Undecorated", and let the user configure hisher window manager not to
1240  // put a border around windows of this class.
1241  XClassHint *class_hints_p = nullptr;
1242  if (!x_wm_class.empty()) {
1243  // Unless the user wanted to use his own WM_CLASS, of course.
1244  class_hints_p = XAllocClassHint();
1245  class_hints_p->res_class = (char*) x_wm_class.c_str();
1246  if (!x_wm_class_name.empty()) {
1247  class_hints_p->res_name = (char*) x_wm_class_name.c_str();
1248  }
1249 
1250  } else if (properties.get_undecorated() || properties.get_fullscreen()) {
1251  class_hints_p = XAllocClassHint();
1252  class_hints_p->res_class = (char*) "Undecorated";
1253  }
1254 
1255  if (properties.get_undecorated() && !properties.get_fullscreen()) {
1256  type_data[next_type_data++] = x11_pipe->_net_wm_window_type_splash;
1257  }
1258 
1259  if (properties.has_z_order()) {
1260  switch (properties.get_z_order()) {
1261  case WindowProperties::Z_bottom:
1262  state_data[next_state_data++] = x11_pipe->_net_wm_state_below;
1263  set_data[next_set_data++] = SetAction(x11_pipe->_net_wm_state_below,
1264  x11_pipe->_net_wm_state_add);
1265  set_data[next_set_data++] = SetAction(x11_pipe->_net_wm_state_above,
1266  x11_pipe->_net_wm_state_remove);
1267  break;
1268 
1269  case WindowProperties::Z_normal:
1270  set_data[next_set_data++] = SetAction(x11_pipe->_net_wm_state_below,
1271  x11_pipe->_net_wm_state_remove);
1272  set_data[next_set_data++] = SetAction(x11_pipe->_net_wm_state_above,
1273  x11_pipe->_net_wm_state_remove);
1274  break;
1275 
1276  case WindowProperties::Z_top:
1277  state_data[next_state_data++] = x11_pipe->_net_wm_state_above;
1278  set_data[next_set_data++] = SetAction(x11_pipe->_net_wm_state_below,
1279  x11_pipe->_net_wm_state_remove);
1280  set_data[next_set_data++] = SetAction(x11_pipe->_net_wm_state_above,
1281  x11_pipe->_net_wm_state_add);
1282  break;
1283  }
1284  }
1285 
1286  nassertv(next_type_data < max_type_data);
1287  nassertv(next_state_data < max_state_data);
1288  nassertv(next_set_data < max_set_data);
1289 
1290  // Add the process ID as a convenience for other applications.
1291  int32_t pid = getpid();
1292  XChangeProperty(_display, _xwindow, x11_pipe->_net_wm_pid,
1293  XA_CARDINAL, 32, PropModeReplace,
1294  (unsigned char *)&pid, 1);
1295 
1296  // Disable compositing effects in fullscreen mode.
1297  if (properties.has_fullscreen()) {
1298  int32_t compositor = properties.get_fullscreen() ? 1 : 0;
1299  XChangeProperty(_display, _xwindow, x11_pipe->_net_wm_bypass_compositor,
1300  XA_CARDINAL, 32, PropModeReplace,
1301  (unsigned char *)&compositor, 1);
1302  }
1303 
1304  XChangeProperty(_display, _xwindow, x11_pipe->_net_wm_window_type,
1305  XA_ATOM, 32, PropModeReplace,
1306  (unsigned char *)type_data, next_type_data);
1307 
1308  // Request the state properties all at once.
1309  XChangeProperty(_display, _xwindow, x11_pipe->_net_wm_state,
1310  XA_ATOM, 32, PropModeReplace,
1311  (unsigned char *)state_data, next_state_data);
1312 
1313  if (already_mapped) {
1314  // We have to request state changes differently when the window has been
1315  // mapped. To do this, we need to send a client message to the root
1316  // window for each change.
1317 
1318  x11GraphicsPipe *x11_pipe;
1319  DCAST_INTO_V(x11_pipe, _pipe);
1320 
1321  for (int i = 0; i < next_set_data; ++i) {
1322  XClientMessageEvent event;
1323  memset(&event, 0, sizeof(event));
1324  event.type = ClientMessage;
1325  event.send_event = True;
1326  event.display = _display;
1327  event.window = _xwindow;
1328  event.message_type = x11_pipe->_net_wm_state;
1329  event.format = 32;
1330  event.data.l[0] = set_data[i]._action;
1331  event.data.l[1] = set_data[i]._state;
1332  event.data.l[2] = 0;
1333  event.data.l[3] = 1;
1334 
1335  XSendEvent(_display, x11_pipe->get_root(), True, SubstructureNotifyMask | SubstructureRedirectMask, (XEvent *)&event);
1336  }
1337  }
1338 
1339  XSetWMProperties(_display, _xwindow, window_name_p, window_name_p,
1340  nullptr, 0, size_hints_p, wm_hints_p, class_hints_p);
1341 
1342  if (size_hints_p != nullptr) {
1343  XFree(size_hints_p);
1344  }
1345  if (wm_hints_p != nullptr) {
1346  XFree(wm_hints_p);
1347  }
1348  if (class_hints_p != nullptr) {
1349  XFree(class_hints_p);
1350  }
1351 
1352  // Also, indicate to the window manager that we'd like to get a chance to
1353  // close our windows cleanly, rather than being rudely disconnected from the
1354  // X server if the user requests a window close.
1355  Atom protocols[] = {
1356  _wm_delete_window,
1357  };
1358 
1359  XSetWMProtocols(_display, _xwindow, protocols,
1360  sizeof(protocols) / sizeof(Atom));
1361 }
1362 
1363 /**
1364  * Allocates a colormap appropriate to the visual and stores in in the
1365  * _colormap method.
1366  */
1367 void x11GraphicsWindow::
1368 setup_colormap(XVisualInfo *visual) {
1369  x11GraphicsPipe *x11_pipe;
1370  DCAST_INTO_V(x11_pipe, _pipe);
1371  X11_Window root_window = x11_pipe->get_root();
1372 
1373  _colormap = XCreateColormap(_display, root_window,
1374  visual->visual, AllocNone);
1375 }
1376 
1377 /**
1378  * Adds raw mice to the _input_devices list.
1379  * @deprecated obtain raw devices via the device manager instead.
1380  */
1381 void x11GraphicsWindow::
1382 open_raw_mice() {
1383 #ifdef PHAVE_LINUX_INPUT_H
1384  bool any_present = false;
1385  bool any_mice = false;
1386 
1387  for (int i=0; i<64; i++) {
1388  ostringstream fnb;
1389  fnb << "/dev/input/event" << i;
1390  string fn = fnb.str();
1391  int fd = open(fn.c_str(), O_RDONLY | O_NONBLOCK, 0);
1392  if (fd >= 0) {
1393  EvdevInputDevice *device = new EvdevInputDevice(nullptr, fd);
1394  nassertd(device != NULL) continue;
1395 
1396  if (device->has_pointer()) {
1397  add_input_device(device);
1398 
1399  x11display_cat.info()
1400  << "Raw mouse " << _input_devices.size()
1401  << " detected: " << device->get_name() << "\n";
1402 
1403  any_mice = true;
1404  any_present = true;
1405  }
1406  } else {
1407  if (errno == ENOENT || errno == ENOTDIR) {
1408  break;
1409  } else {
1410  any_present = true;
1411  x11display_cat.error()
1412  << "Opening raw mice: " << strerror(errno) << " " << fn << "\n";
1413  }
1414  }
1415  }
1416 
1417  if (any_mice) {
1418  _properties.set_raw_mice(true);
1419 
1420  } else if (!any_present) {
1421  x11display_cat.error() <<
1422  "Opening raw mice: files not found: /dev/input/event*\n";
1423 
1424  } else {
1425  x11display_cat.error() <<
1426  "Opening raw mice: no mouse devices detected in /dev/input/event*\n";
1427  }
1428 #else
1429  x11display_cat.error() <<
1430  "Opening raw mice: panda not compiled with raw mouse support.\n";
1431 #endif
1432 }
1433 
1434 /**
1435  * Generates a keystroke corresponding to the indicated X KeyPress event.
1436  */
1437 void x11GraphicsWindow::
1438 handle_keystroke(XKeyEvent &event) {
1439  if (!_dga_mouse_enabled) {
1440  _input->set_pointer_in_window(event.x, event.y);
1441  }
1442 
1443  if (_ic) {
1444  // First, get the keystroke as a wide-character sequence.
1445  static const int buffer_size = 256;
1446  wchar_t buffer[buffer_size];
1447  Status status;
1448  int len = XwcLookupString(_ic, &event, buffer, buffer_size, nullptr,
1449  &status);
1450  if (status == XBufferOverflow) {
1451  x11display_cat.error()
1452  << "Overflowed input buffer.\n";
1453  }
1454 
1455  // Now each of the returned wide characters represents a keystroke.
1456  for (int i = 0; i < len; i++) {
1457  _input->keystroke(buffer[i]);
1458  }
1459 
1460  } else {
1461  // Without an input context, just get the ascii keypress.
1462  ButtonHandle button = get_button(event, true);
1463  if (button.has_ascii_equivalent()) {
1464  _input->keystroke(button.get_ascii_equivalent());
1465  }
1466  }
1467 }
1468 
1469 /**
1470  * Generates a keypress corresponding to the indicated X KeyPress event.
1471  */
1472 void x11GraphicsWindow::
1473 handle_keypress(XKeyEvent &event) {
1474  if (!_dga_mouse_enabled) {
1475  _input->set_pointer_in_window(event.x, event.y);
1476  }
1477 
1478  // Now get the raw unshifted button.
1479  ButtonHandle button = get_button(event, false);
1480  if (button != ButtonHandle::none()) {
1481  if (button == KeyboardButton::lcontrol() || button == KeyboardButton::rcontrol()) {
1482  _input->button_down(KeyboardButton::control());
1483  }
1484  if (button == KeyboardButton::lshift() || button == KeyboardButton::rshift()) {
1485  _input->button_down(KeyboardButton::shift());
1486  }
1487  if (button == KeyboardButton::lalt() || button == KeyboardButton::ralt()) {
1488  _input->button_down(KeyboardButton::alt());
1489  }
1490  if (button == KeyboardButton::lmeta() || button == KeyboardButton::rmeta()) {
1491  _input->button_down(KeyboardButton::meta());
1492  }
1493  _input->button_down(button);
1494  }
1495 
1496  if (event.keycode >= 9 && event.keycode <= 135) {
1497  ButtonHandle raw_button = map_raw_button(event.keycode);
1498  if (raw_button != ButtonHandle::none()) {
1499  _input->raw_button_down(raw_button);
1500  }
1501  }
1502 }
1503 
1504 /**
1505  * Generates a keyrelease corresponding to the indicated X KeyRelease event.
1506  */
1507 void x11GraphicsWindow::
1508 handle_keyrelease(XKeyEvent &event) {
1509  if (!_dga_mouse_enabled) {
1510  _input->set_pointer_in_window(event.x, event.y);
1511  }
1512 
1513  // Now get the raw unshifted button.
1514  ButtonHandle button = get_button(event, false);
1515  if (button != ButtonHandle::none()) {
1516  if (button == KeyboardButton::lcontrol() || button == KeyboardButton::rcontrol()) {
1517  _input->button_up(KeyboardButton::control());
1518  }
1519  if (button == KeyboardButton::lshift() || button == KeyboardButton::rshift()) {
1520  _input->button_up(KeyboardButton::shift());
1521  }
1522  if (button == KeyboardButton::lalt() || button == KeyboardButton::ralt()) {
1523  _input->button_up(KeyboardButton::alt());
1524  }
1525  if (button == KeyboardButton::lmeta() || button == KeyboardButton::rmeta()) {
1526  _input->button_up(KeyboardButton::meta());
1527  }
1528  _input->button_up(button);
1529  }
1530 
1531  if (event.keycode >= 9 && event.keycode <= 135) {
1532  ButtonHandle raw_button = map_raw_button(event.keycode);
1533  if (raw_button != ButtonHandle::none()) {
1534  _input->raw_button_up(raw_button);
1535  }
1536  }
1537 }
1538 
1539 /**
1540  * Returns the Panda ButtonHandle corresponding to the keyboard button
1541  * indicated by the given key event.
1542  */
1543 ButtonHandle x11GraphicsWindow::
1544 get_button(XKeyEvent &key_event, bool allow_shift) {
1545  KeySym key = XLookupKeysym(&key_event, 0);
1546 
1547  if ((key_event.state & Mod2Mask) != 0) {
1548  // Mod2Mask corresponds to NumLock being in effect. In this case, we want
1549  // to get the alternate keysym associated with any keypad keys. Weird
1550  // system.
1551  KeySym k2;
1552  ButtonHandle button;
1553  switch (key) {
1554  case XK_KP_Space:
1555  case XK_KP_Tab:
1556  case XK_KP_Enter:
1557  case XK_KP_F1:
1558  case XK_KP_F2:
1559  case XK_KP_F3:
1560  case XK_KP_F4:
1561  case XK_KP_Equal:
1562  case XK_KP_Multiply:
1563  case XK_KP_Add:
1564  case XK_KP_Separator:
1565  case XK_KP_Subtract:
1566  case XK_KP_Divide:
1567  case XK_KP_Left:
1568  case XK_KP_Up:
1569  case XK_KP_Right:
1570  case XK_KP_Down:
1571  case XK_KP_Begin:
1572  case XK_KP_Prior:
1573  case XK_KP_Next:
1574  case XK_KP_Home:
1575  case XK_KP_End:
1576  case XK_KP_Insert:
1577  case XK_KP_Delete:
1578  case XK_KP_0:
1579  case XK_KP_1:
1580  case XK_KP_2:
1581  case XK_KP_3:
1582  case XK_KP_4:
1583  case XK_KP_5:
1584  case XK_KP_6:
1585  case XK_KP_7:
1586  case XK_KP_8:
1587  case XK_KP_9:
1588  k2 = XLookupKeysym(&key_event, 1);
1589  button = map_button(k2);
1590  if (button != ButtonHandle::none()) {
1591  return button;
1592  }
1593  // If that didn't produce a button we know, just fall through and handle
1594  // the normal, un-numlocked key.
1595  break;
1596 
1597  default:
1598  break;
1599  }
1600  }
1601 
1602  if (allow_shift) {
1603  // If shift is held down, get the shifted keysym.
1604  if ((key_event.state & ShiftMask) != 0) {
1605  KeySym k2 = XLookupKeysym(&key_event, 1);
1606  ButtonHandle button = map_button(k2);
1607  if (button != ButtonHandle::none()) {
1608  return button;
1609  }
1610  }
1611 
1612  // If caps lock is down, shift lowercase letters to uppercase. We can do
1613  // this in just the ASCII set, because we handle international keyboards
1614  // elsewhere (via an input context).
1615  if ((key_event.state & (ShiftMask | LockMask)) != 0) {
1616  if (key >= XK_a && key <= XK_z) {
1617  key += (XK_A - XK_a);
1618  }
1619  }
1620  }
1621 
1622  return map_button(key);
1623 }
1624 
1625 /**
1626  * Maps from a single X keysym to Panda's ButtonHandle. Called by
1627  * get_button(), above.
1628  */
1629 ButtonHandle x11GraphicsWindow::
1630 map_button(KeySym key) const {
1631  switch (key) {
1632  case NoSymbol:
1633  return ButtonHandle::none();
1634  case XK_BackSpace:
1635  return KeyboardButton::backspace();
1636  case XK_Tab:
1637  case XK_KP_Tab:
1638  return KeyboardButton::tab();
1639  case XK_Return:
1640  case XK_KP_Enter:
1641  return KeyboardButton::enter();
1642  case XK_Escape:
1643  return KeyboardButton::escape();
1644  case XK_KP_Space:
1645  case XK_space:
1646  return KeyboardButton::space();
1647  case XK_exclam:
1648  return KeyboardButton::ascii_key('!');
1649  case XK_quotedbl:
1650  return KeyboardButton::ascii_key('"');
1651  case XK_numbersign:
1652  return KeyboardButton::ascii_key('#');
1653  case XK_dollar:
1654  return KeyboardButton::ascii_key('$');
1655  case XK_percent:
1656  return KeyboardButton::ascii_key('%');
1657  case XK_ampersand:
1658  return KeyboardButton::ascii_key('&');
1659  case XK_apostrophe: // == XK_quoteright
1660  case XK_dead_acute: // on int'l keyboards
1661  return KeyboardButton::ascii_key('\'');
1662  case XK_parenleft:
1663  return KeyboardButton::ascii_key('(');
1664  case XK_parenright:
1665  return KeyboardButton::ascii_key(')');
1666  case XK_asterisk:
1667  case XK_KP_Multiply:
1668  return KeyboardButton::ascii_key('*');
1669  case XK_plus:
1670  case XK_KP_Add:
1671  return KeyboardButton::ascii_key('+');
1672  case XK_comma:
1673  case XK_KP_Separator:
1674  return KeyboardButton::ascii_key(',');
1675  case XK_minus:
1676  case XK_KP_Subtract:
1677  return KeyboardButton::ascii_key('-');
1678  case XK_period:
1679  case XK_KP_Decimal:
1680  return KeyboardButton::ascii_key('.');
1681  case XK_slash:
1682  case XK_KP_Divide:
1683  return KeyboardButton::ascii_key('/');
1684  case XK_0:
1685  case XK_KP_0:
1686  return KeyboardButton::ascii_key('0');
1687  case XK_1:
1688  case XK_KP_1:
1689  return KeyboardButton::ascii_key('1');
1690  case XK_2:
1691  case XK_KP_2:
1692  return KeyboardButton::ascii_key('2');
1693  case XK_3:
1694  case XK_KP_3:
1695  return KeyboardButton::ascii_key('3');
1696  case XK_4:
1697  case XK_KP_4:
1698  return KeyboardButton::ascii_key('4');
1699  case XK_5:
1700  case XK_KP_5:
1701  return KeyboardButton::ascii_key('5');
1702  case XK_6:
1703  case XK_KP_6:
1704  return KeyboardButton::ascii_key('6');
1705  case XK_7:
1706  case XK_KP_7:
1707  return KeyboardButton::ascii_key('7');
1708  case XK_8:
1709  case XK_KP_8:
1710  return KeyboardButton::ascii_key('8');
1711  case XK_9:
1712  case XK_KP_9:
1713  return KeyboardButton::ascii_key('9');
1714  case XK_colon:
1715  return KeyboardButton::ascii_key(':');
1716  case XK_semicolon:
1717  return KeyboardButton::ascii_key(';');
1718  case XK_less:
1719  return KeyboardButton::ascii_key('<');
1720  case XK_equal:
1721  case XK_KP_Equal:
1722  return KeyboardButton::ascii_key('=');
1723  case XK_greater:
1724  return KeyboardButton::ascii_key('>');
1725  case XK_question:
1726  return KeyboardButton::ascii_key('?');
1727  case XK_at:
1728  return KeyboardButton::ascii_key('@');
1729  case XK_A:
1730  return KeyboardButton::ascii_key('A');
1731  case XK_B:
1732  return KeyboardButton::ascii_key('B');
1733  case XK_C:
1734  return KeyboardButton::ascii_key('C');
1735  case XK_D:
1736  return KeyboardButton::ascii_key('D');
1737  case XK_E:
1738  return KeyboardButton::ascii_key('E');
1739  case XK_F:
1740  return KeyboardButton::ascii_key('F');
1741  case XK_G:
1742  return KeyboardButton::ascii_key('G');
1743  case XK_H:
1744  return KeyboardButton::ascii_key('H');
1745  case XK_I:
1746  return KeyboardButton::ascii_key('I');
1747  case XK_J:
1748  return KeyboardButton::ascii_key('J');
1749  case XK_K:
1750  return KeyboardButton::ascii_key('K');
1751  case XK_L:
1752  return KeyboardButton::ascii_key('L');
1753  case XK_M:
1754  return KeyboardButton::ascii_key('M');
1755  case XK_N:
1756  return KeyboardButton::ascii_key('N');
1757  case XK_O:
1758  return KeyboardButton::ascii_key('O');
1759  case XK_P:
1760  return KeyboardButton::ascii_key('P');
1761  case XK_Q:
1762  return KeyboardButton::ascii_key('Q');
1763  case XK_R:
1764  return KeyboardButton::ascii_key('R');
1765  case XK_S:
1766  return KeyboardButton::ascii_key('S');
1767  case XK_T:
1768  return KeyboardButton::ascii_key('T');
1769  case XK_U:
1770  return KeyboardButton::ascii_key('U');
1771  case XK_V:
1772  return KeyboardButton::ascii_key('V');
1773  case XK_W:
1774  return KeyboardButton::ascii_key('W');
1775  case XK_X:
1776  return KeyboardButton::ascii_key('X');
1777  case XK_Y:
1778  return KeyboardButton::ascii_key('Y');
1779  case XK_Z:
1780  return KeyboardButton::ascii_key('Z');
1781  case XK_bracketleft:
1782  return KeyboardButton::ascii_key('[');
1783  case XK_backslash:
1784  return KeyboardButton::ascii_key('\\');
1785  case XK_bracketright:
1786  return KeyboardButton::ascii_key(']');
1787  case XK_asciicircum:
1788  return KeyboardButton::ascii_key('^');
1789  case XK_underscore:
1790  return KeyboardButton::ascii_key('_');
1791  case XK_grave: // == XK_quoteleft
1792  case XK_dead_grave: // on int'l keyboards
1793  return KeyboardButton::ascii_key('`');
1794  case XK_a:
1795  return KeyboardButton::ascii_key('a');
1796  case XK_b:
1797  return KeyboardButton::ascii_key('b');
1798  case XK_c:
1799  return KeyboardButton::ascii_key('c');
1800  case XK_d:
1801  return KeyboardButton::ascii_key('d');
1802  case XK_e:
1803  return KeyboardButton::ascii_key('e');
1804  case XK_f:
1805  return KeyboardButton::ascii_key('f');
1806  case XK_g:
1807  return KeyboardButton::ascii_key('g');
1808  case XK_h:
1809  return KeyboardButton::ascii_key('h');
1810  case XK_i:
1811  return KeyboardButton::ascii_key('i');
1812  case XK_j:
1813  return KeyboardButton::ascii_key('j');
1814  case XK_k:
1815  return KeyboardButton::ascii_key('k');
1816  case XK_l:
1817  return KeyboardButton::ascii_key('l');
1818  case XK_m:
1819  return KeyboardButton::ascii_key('m');
1820  case XK_n:
1821  return KeyboardButton::ascii_key('n');
1822  case XK_o:
1823  return KeyboardButton::ascii_key('o');
1824  case XK_p:
1825  return KeyboardButton::ascii_key('p');
1826  case XK_q:
1827  return KeyboardButton::ascii_key('q');
1828  case XK_r:
1829  return KeyboardButton::ascii_key('r');
1830  case XK_s:
1831  return KeyboardButton::ascii_key('s');
1832  case XK_t:
1833  return KeyboardButton::ascii_key('t');
1834  case XK_u:
1835  return KeyboardButton::ascii_key('u');
1836  case XK_v:
1837  return KeyboardButton::ascii_key('v');
1838  case XK_w:
1839  return KeyboardButton::ascii_key('w');
1840  case XK_x:
1841  return KeyboardButton::ascii_key('x');
1842  case XK_y:
1843  return KeyboardButton::ascii_key('y');
1844  case XK_z:
1845  return KeyboardButton::ascii_key('z');
1846  case XK_braceleft:
1847  return KeyboardButton::ascii_key('{');
1848  case XK_bar:
1849  return KeyboardButton::ascii_key('|');
1850  case XK_braceright:
1851  return KeyboardButton::ascii_key('}');
1852  case XK_asciitilde:
1853  return KeyboardButton::ascii_key('~');
1854  case XK_F1:
1855  case XK_KP_F1:
1856  return KeyboardButton::f1();
1857  case XK_F2:
1858  case XK_KP_F2:
1859  return KeyboardButton::f2();
1860  case XK_F3:
1861  case XK_KP_F3:
1862  return KeyboardButton::f3();
1863  case XK_F4:
1864  case XK_KP_F4:
1865  return KeyboardButton::f4();
1866  case XK_F5:
1867  return KeyboardButton::f5();
1868  case XK_F6:
1869  return KeyboardButton::f6();
1870  case XK_F7:
1871  return KeyboardButton::f7();
1872  case XK_F8:
1873  return KeyboardButton::f8();
1874  case XK_F9:
1875  return KeyboardButton::f9();
1876  case XK_F10:
1877  return KeyboardButton::f10();
1878  case XK_F11:
1879  return KeyboardButton::f11();
1880  case XK_F12:
1881  return KeyboardButton::f12();
1882  case XK_KP_Left:
1883  case XK_Left:
1884  return KeyboardButton::left();
1885  case XK_KP_Up:
1886  case XK_Up:
1887  return KeyboardButton::up();
1888  case XK_KP_Right:
1889  case XK_Right:
1890  return KeyboardButton::right();
1891  case XK_KP_Down:
1892  case XK_Down:
1893  return KeyboardButton::down();
1894  case XK_KP_Prior:
1895  case XK_Prior:
1896  return KeyboardButton::page_up();
1897  case XK_KP_Next:
1898  case XK_Next:
1899  return KeyboardButton::page_down();
1900  case XK_KP_Home:
1901  case XK_Home:
1902  return KeyboardButton::home();
1903  case XK_KP_End:
1904  case XK_End:
1905  return KeyboardButton::end();
1906  case XK_KP_Insert:
1907  case XK_Insert:
1908  return KeyboardButton::insert();
1909  case XK_KP_Delete:
1910  case XK_Delete:
1911  return KeyboardButton::del();
1912  case XK_Num_Lock:
1913  return KeyboardButton::num_lock();
1914  case XK_Scroll_Lock:
1915  return KeyboardButton::scroll_lock();
1916  case XK_Print:
1917  return KeyboardButton::print_screen();
1918  case XK_Pause:
1919  return KeyboardButton::pause();
1920  case XK_Menu:
1921  return KeyboardButton::menu();
1922  case XK_Shift_L:
1923  return KeyboardButton::lshift();
1924  case XK_Shift_R:
1925  return KeyboardButton::rshift();
1926  case XK_Control_L:
1927  return KeyboardButton::lcontrol();
1928  case XK_Control_R:
1929  return KeyboardButton::rcontrol();
1930  case XK_Alt_L:
1931  return KeyboardButton::lalt();
1932  case XK_Alt_R:
1933  return KeyboardButton::ralt();
1934  case XK_Meta_L:
1935  case XK_Super_L:
1936  return KeyboardButton::lmeta();
1937  case XK_Meta_R:
1938  case XK_Super_R:
1939  return KeyboardButton::rmeta();
1940  case XK_Caps_Lock:
1941  return KeyboardButton::caps_lock();
1942  case XK_Shift_Lock:
1943  return KeyboardButton::shift_lock();
1944  }
1945  if (x11display_cat.is_debug()) {
1946  x11display_cat.debug()
1947  << "Unrecognized keysym 0x" << std::hex << key << std::dec << "\n";
1948  }
1949  return ButtonHandle::none();
1950 }
1951 
1952 /**
1953  * Maps from a single X keycode to Panda's ButtonHandle.
1954  */
1955 ButtonHandle x11GraphicsWindow::
1956 map_raw_button(KeyCode key) const {
1957 #ifdef PHAVE_LINUX_INPUT_H
1958  // Most X11 servers are configured to use the evdev driver, which
1959  // adds 8 to the underlying evdev keycodes (not sure why).
1960  // In any case, this means we can use the same mapping as our raw
1961  // input code, which uses evdev directly.
1962  int index = key - 8;
1963  if (index >= 0) {
1964  return EvdevInputDevice::map_button(index);
1965  }
1966 #endif
1967  return ButtonHandle::none();
1968 }
1969 
1970 /**
1971  * Returns the Panda ButtonHandle corresponding to the mouse button indicated
1972  * by the given button event.
1973  */
1974 ButtonHandle x11GraphicsWindow::
1975 get_mouse_button(XButtonEvent &button_event) {
1976  int index = button_event.button;
1977  if (index == x_wheel_up_button) {
1978  return MouseButton::wheel_up();
1979  } else if (index == x_wheel_down_button) {
1980  return MouseButton::wheel_down();
1981  } else if (index == x_wheel_left_button) {
1982  return MouseButton::wheel_left();
1983  } else if (index == x_wheel_right_button) {
1984  return MouseButton::wheel_right();
1985  } else {
1986  return MouseButton::button(index - 1);
1987  }
1988 }
1989 
1990 /**
1991  * Returns a ButtonMap containing the association between raw buttons and
1992  * virtual buttons.
1993  */
1994 ButtonMap *x11GraphicsWindow::
1995 get_keyboard_map() const {
1996  // NB. This could be improved by using the Xkb API. XkbDescPtr desc =
1997  // XkbGetMap(_display, XkbAllMapComponentsMask, XkbUseCoreKbd);
1998  ButtonMap *map = new ButtonMap;
1999 
2000  LightReMutexHolder holder(x11GraphicsPipe::_x_mutex);
2001 
2002  for (int k = 9; k <= 135; ++k) {
2003  ButtonHandle raw_button = map_raw_button(k);
2004  if (raw_button == ButtonHandle::none()) {
2005  continue;
2006  }
2007 
2008  KeySym sym = XkbKeycodeToKeysym(_display, k, 0, 0);
2009  ButtonHandle button = map_button(sym);
2010  if (button == ButtonHandle::none()) {
2011  continue;
2012  }
2013 
2014  map->map_button(raw_button, button);
2015  }
2016 
2017  return map;
2018 }
2019 
2020 /**
2021  * This function is used as a predicate to XCheckIfEvent() to determine if the
2022  * indicated queued X event is relevant and should be returned to this window.
2023  */
2024 Bool x11GraphicsWindow::
2025 check_event(X11_Display *display, XEvent *event, char *arg) {
2026  const x11GraphicsWindow *self = (x11GraphicsWindow *)arg;
2027 
2028  // We accept any event that is sent to our window.
2029  return (event->xany.window == self->_xwindow);
2030 }
2031 
2032 /**
2033  * Loads and returns a Cursor corresponding to the indicated filename. If the
2034  * file cannot be loaded, returns None.
2035  */
2036 X11_Cursor x11GraphicsWindow::
2037 get_cursor(const Filename &filename) {
2038  x11GraphicsPipe *x11_pipe;
2039  DCAST_INTO_R(x11_pipe, _pipe, None);
2040 
2041  if (x11_pipe->_xcursor_size == -1) {
2042  x11display_cat.info()
2043  << "libXcursor.so.1 not available; cannot change mouse cursor.\n";
2044  return None;
2045  }
2046 
2047  // First, look for the unresolved filename in our index.
2048  pmap<Filename, X11_Cursor>::iterator fi = _cursor_filenames.find(filename);
2049  if (fi != _cursor_filenames.end()) {
2050  return fi->second;
2051  }
2052 
2053  // If it wasn't found, resolve the filename and search for that.
2055  Filename resolved (filename);
2056  if (!vfs->resolve_filename(resolved, get_model_path())) {
2057  // The filename doesn't exist.
2058  x11display_cat.warning()
2059  << "Could not find cursor filename " << filename << "\n";
2060  return None;
2061  }
2062  fi = _cursor_filenames.find(resolved);
2063  if (fi != _cursor_filenames.end()) {
2064  return fi->second;
2065  }
2066 
2067  // Open the file through the virtual file system.
2068  istream *str = vfs->open_read_file(resolved, true);
2069  if (str == nullptr) {
2070  x11display_cat.warning()
2071  << "Could not open cursor file " << filename << "\n";
2072  return None;
2073  }
2074 
2075  // Check the first four bytes to see what kind of file it is.
2076  char magic[4];
2077  str->read(magic, 4);
2078  if (!str->good()) {
2079  x11display_cat.warning()
2080  << "Could not read from cursor file " << filename << "\n";
2081  return None;
2082  }
2083 
2084  // Put back the read bytes. Do not use seekg, because this will
2085  // corrupt the stream if it points to encrypted/compressed file
2086  str->putback(magic[3]);
2087  str->putback(magic[2]);
2088  str->putback(magic[1]);
2089  str->putback(magic[0]);
2090 
2091  X11_Cursor h = None;
2092  if (memcmp(magic, "Xcur", 4) == 0) {
2093  // X11 cursor.
2094  x11display_cat.debug()
2095  << "Loading X11 cursor " << filename << "\n";
2096  XcursorFile xcfile;
2097  xcfile.closure = str;
2098  xcfile.read = &xcursor_read;
2099  xcfile.write = &xcursor_write;
2100  xcfile.seek = &xcursor_seek;
2101 
2102  XcursorImages *images = x11_pipe->_XcursorXcFileLoadImages(&xcfile, x11_pipe->_xcursor_size);
2103  if (images != nullptr) {
2104  h = x11_pipe->_XcursorImagesLoadCursor(_display, images);
2105  x11_pipe->_XcursorImagesDestroy(images);
2106  }
2107 
2108  } else if (memcmp(magic, "\0\0\1\0", 4) == 0
2109  || memcmp(magic, "\0\0\2\0", 4) == 0) {
2110  // Windows .ico or .cur file.
2111  x11display_cat.debug()
2112  << "Loading Windows cursor " << filename << "\n";
2113  h = read_ico(*str);
2114  }
2115 
2116  // Delete the istream.
2117  vfs->close_read_file(str);
2118 
2119  if (h == None) {
2120  x11display_cat.warning()
2121  << "X11 cursor filename '" << resolved << "' could not be loaded!\n";
2122  }
2123 
2124  _cursor_filenames[resolved] = h;
2125  return h;
2126 }
2127 
2128 /**
2129  * Reads a Windows .ico or .cur file from the indicated stream and returns it
2130  * as an X11 Cursor. If the file cannot be loaded, returns None.
2131  */
2132 X11_Cursor x11GraphicsWindow::
2133 read_ico(istream &ico) {
2134  x11GraphicsPipe *x11_pipe;
2135  DCAST_INTO_R(x11_pipe, _pipe, None);
2136 
2137  // Local structs, this is just POD, make input easier
2138  typedef struct {
2139  uint16_t reserved, type, count;
2140  } IcoHeader;
2141 
2142  typedef struct {
2143  uint8_t width, height, colorCount, reserved;
2144  uint16_t xhot, yhot;
2145  uint32_t bitmapSize, offset;
2146  } IcoEntry;
2147 
2148  typedef struct {
2149  uint32_t headerSize, width, height;
2150  uint16_t planes, bitsPerPixel;
2151  uint32_t compression, imageSize, xPixelsPerM, yPixelsPerM, colorsUsed, colorsImportant;
2152  } IcoInfoHeader;
2153 
2154  typedef struct {
2155  uint8_t blue, green, red, reserved;
2156  } IcoColor;
2157 
2158  int i, entry = 0;
2159  unsigned int j, k, mask, shift;
2160  size_t colorCount, bitsPerPixel;
2161  IcoHeader header;
2162  IcoInfoHeader infoHeader;
2163  IcoEntry *entries = nullptr;
2164  IcoColor color, *palette = nullptr;
2165 
2166  size_t xorBmpSize, andBmpSize;
2167  char *curXor, *curAnd;
2168  char *xorBmp = nullptr, *andBmp = nullptr;
2169  XcursorImage *image = nullptr;
2170  X11_Cursor ret = None;
2171 
2172  int def_size = x11_pipe->_xcursor_size;
2173 
2174  // Get our header, note that ICO = type 1 and CUR = type 2.
2175  ico.read(reinterpret_cast<char *>(&header), sizeof(IcoHeader));
2176  if (!ico.good()) goto cleanup;
2177  if (header.type != 1 && header.type != 2) goto cleanup;
2178  if (header.count < 1) goto cleanup;
2179 
2180  // Read the entry table into memory, select the largest entry.
2181  entries = new IcoEntry[header.count];
2182  ico.read(reinterpret_cast<char *>(entries), header.count * sizeof(IcoEntry));
2183  if (!ico.good()) goto cleanup;
2184  for (i = 1; i < header.count; i++) {
2185  if (entries[i].width == def_size && entries[i].height == def_size) {
2186  // Wait, this is the default cursor size. This is perfect.
2187  entry = i;
2188  break;
2189  }
2190  if (entries[i].width > entries[entry].width ||
2191  entries[i].height > entries[entry].height)
2192  entry = i;
2193  }
2194 
2195  // Seek to the image in the ICO.
2196  ico.seekg(entries[entry].offset);
2197  if (!ico.good()) goto cleanup;
2198 
2199  if (ico.peek() == 0x89) {
2200  // Hang on, this is actually a PNG header.
2201  PNMImage img;
2203  if (!img.read(ico, "", reg->get_type_from_extension("png"))) {
2204  goto cleanup;
2205  }
2206  img.set_maxval(255);
2207 
2208  image = x11_pipe->_XcursorImageCreate(img.get_x_size(), img.get_y_size());
2209 
2210  xel *ptr = img.get_array();
2211  xelval *alpha = img.get_alpha_array();
2212  size_t num_pixels = (size_t)img.get_x_size() * (size_t)img.get_y_size();
2213  unsigned int *dest = image->pixels;
2214 
2215  if (alpha != nullptr) {
2216  for (size_t p = 0; p < num_pixels; ++p) {
2217  *dest++ = (*alpha << 24U) | (ptr->r << 16U) | (ptr->g << 8U) | (ptr->b);
2218  ++ptr;
2219  ++alpha;
2220  }
2221  } else {
2222  for (size_t p = 0; p < num_pixels; ++p) {
2223  *dest++ = 0xff000000U | (ptr->r << 16U) | (ptr->g << 8U) | (ptr->b);
2224  ++ptr;
2225  }
2226  }
2227 
2228  } else {
2229  ico.read(reinterpret_cast<char *>(&infoHeader), sizeof(IcoInfoHeader));
2230  if (!ico.good()) goto cleanup;
2231  bitsPerPixel = infoHeader.bitsPerPixel;
2232 
2233  if (infoHeader.compression != 0) goto cleanup;
2234 
2235  // Load the color palette, if one exists.
2236  if (bitsPerPixel != 24 && bitsPerPixel != 32) {
2237  colorCount = 1 << bitsPerPixel;
2238  palette = new IcoColor[colorCount];
2239  ico.read(reinterpret_cast<char *>(palette), colorCount * sizeof(IcoColor));
2240  if (!ico.good()) goto cleanup;
2241  }
2242 
2243  int and_stride = ((infoHeader.width >> 3) + 3) & ~0x03;
2244 
2245  // Read in the pixel data.
2246  xorBmpSize = (infoHeader.width * (infoHeader.height / 2) * bitsPerPixel) / 8;
2247  andBmpSize = and_stride * (infoHeader.height / 2);
2248  curXor = xorBmp = new char[xorBmpSize];
2249  curAnd = andBmp = new char[andBmpSize];
2250  ico.read(xorBmp, xorBmpSize);
2251  if (!ico.good()) goto cleanup;
2252  ico.read(andBmp, andBmpSize);
2253  if (!ico.good()) goto cleanup;
2254 
2255  image = x11_pipe->_XcursorImageCreate(infoHeader.width, infoHeader.height / 2);
2256 
2257  // Support all the formats that GIMP supports.
2258  switch (bitsPerPixel) {
2259  case 1:
2260  case 4:
2261  case 8:
2262  // For colors less that a byte wide, shift and mask the palette indices
2263  // off each element of the xorBmp and append them to the image.
2264  mask = ((1 << bitsPerPixel) - 1);
2265  for (i = image->height - 1; i >= 0; i--) {
2266  for (j = 0; j < image->width; j += 8 / bitsPerPixel) {
2267  for (k = 0; k < 8 / bitsPerPixel; k++) {
2268  shift = 8 - ((k + 1) * bitsPerPixel);
2269  color = palette[(*curXor & (mask << shift)) >> shift];
2270  image->pixels[(i * image->width) + j + k] = (color.red << 16) +
2271  (color.green << 8) +
2272  (color.blue);
2273  }
2274 
2275  curXor++;
2276  }
2277 
2278  // Set the alpha byte properly according to the andBmp.
2279  for (j = 0; j < image->width; j += 8) {
2280  for (k = 0; k < 8; k++) {
2281  shift = 7 - k;
2282  image->pixels[(i * image->width) + j + k] |=
2283  ((*curAnd & (1 << shift)) >> shift) ? 0x0 : (0xff << 24);
2284  }
2285 
2286  curAnd++;
2287  }
2288  }
2289  break;
2290 
2291  case 24:
2292  // Pack each of the three bytes into a single color, BGR -> 0RGB
2293  for (i = image->height - 1; i >= 0; i--) {
2294  for (j = 0; j < image->width; j++) {
2295  shift = 7 - (j & 0x7);
2296  uint32_t alpha = (curAnd[j >> 3] & (1 << shift)) ? 0 : 0xff000000U;
2297  image->pixels[(i * image->width) + j] = (uint8_t)curXor[0]
2298  | ((uint8_t)curXor[1] << 8u)
2299  | ((uint8_t)curXor[2] << 16u)
2300  | alpha;
2301  curXor += 3;
2302  }
2303  curAnd += and_stride;
2304  }
2305  break;
2306 
2307  case 32:
2308  // Pack each of the four bytes into a single color, BGRA -> ARGB
2309  for (i = image->height - 1; i >= 0; i--) {
2310  for (j = 0; j < image->width; j++) {
2311  image->pixels[(i * image->width) + j] = (*(curXor + 3) << 24) +
2312  (*(curXor + 2) << 16) +
2313  (*(curXor + 1) << 8) +
2314  (*curXor);
2315  curXor += 4;
2316  }
2317  }
2318  break;
2319 
2320  default:
2321  goto cleanup;
2322  }
2323  }
2324 
2325  // If this is an actual CUR not an ICO set up the hotspot properly.
2326  if (header.type == 2) {
2327  image->xhot = entries[entry].xhot;
2328  image->yhot = entries[entry].yhot;
2329  } else {
2330  image->xhot = 0;
2331  image->yhot = 0;
2332  }
2333 
2334  ret = x11_pipe->_XcursorImageLoadCursor(_display, image);
2335 
2336 cleanup:
2337  x11_pipe->_XcursorImageDestroy(image);
2338  delete[] entries;
2339  delete[] palette;
2340  delete[] xorBmp;
2341  delete[] andBmp;
2342 
2343  return ret;
2344 }
has_cursor_filename
Returns true if set_cursor_filename() has been specified.
set_mouse_mode
Specifies the mode in which the window is to operate its mouse pointer.
bool is_any_specified() const
Returns true if any properties have been specified, false otherwise.
static ClockObject * get_global_clock()
Returns a pointer to the global ClockObject.
Definition: clockObject.I:215
void button_down(ButtonHandle button, double time=ClockObject::get_global_clock() ->get_frame_time())
Records that the indicated button has been depressed.
void keystroke(int keycode, double time=ClockObject::get_global_clock() ->get_frame_time())
Records that the indicated keystroke has been generated.
clear_z_order
Removes the z_order specification from the properties.
virtual void process_events()
Do whatever processing is necessary to ensure that the window responds to user events.
get_mouse_mode
See set_mouse_mode().
has_minimized
Returns true if set_minimized() has been specified.
XIM get_im() const
Returns the input method opened for the pipe, or NULL if the input method could not be opened for som...
virtual bool move_pointer(int device, int x, int y)
Forces the pointer to the indicated position within the window, if possible.
get_display_height
Returns the height of the entire display, if it is known.
Definition: graphicsPipe.h:95
This graphics pipe represents the interface for creating graphics windows on an X-based client.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This is our own Panda specialization on the default STL map.
Definition: pmap.h:49
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
get_in_window
If this returns false, the pointer is not currently present in the window and the values returned by ...
Definition: pointerData.h:56
This object represents a window on the desktop, not necessarily a Panda window.
Definition: windowHandle.h:34
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
get_fixed_size
Returns true if the window cannot be resized by the user, false otherwise.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
The name of this class derives from the fact that we originally implemented it as a layer on top of t...
Definition: pnmImage.h:58
set_size
Specifies the requested size of the window, in pixels.
get_cursor_hidden
Returns true if the mouse cursor is invisible.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
virtual void clear(Thread *current_thread)
Clears the entire framebuffer before rendering, according to the settings of get_color_clear_active()...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
BEGIN_PUBLISH typedef PointerData MouseData
Deprecated alias for PointerData.
Definition: mouseData.h:23
get_foreground
Returns true if the window is in the foreground.
clear_cursor_filename
Removes the cursor_filename specification from the properties.
A hierarchy of directories and files that appears to be one continuous file system,...
std::istream * open_read_file(const Filename &filename, bool auto_unwrap) const
Convenience function; returns a newly allocated istream if the file exists and can be read,...
set_minimized
Specifies whether the window should be created minimized (true), or normal (false).
virtual void set_properties_now(WindowProperties &properties)
Applies the requested set of properties to the window, if possible, for instance to request a change ...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
get_cursor_filename
Returns the icon filename associated with the mouse cursor.
void set_pointer_in_window(double x, double y, double time=ClockObject::get_global_clock() ->get_frame_time())
To be called by a particular kind of GraphicsWindow to indicate that the pointer is within the window...
bool read(const Filename &filename, PNMFileType *type=nullptr, bool report_unknown_type=true)
Reads the indicated image filename.
Definition: pnmImage.cxx:278
clear_fixed_size
Removes the fixed_size specification from the properties.
A lightweight class that can be used to automatically start and stop a PStatCollector around a sectio...
Definition: pStatTimer.h:30
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
X11_Window get_root() const
Returns the handle to the root window on the pipe's display.
xel * get_array()
Directly access the underlying PNMImage array.
Definition: pnmImage.I:1084
bool resolve_filename(Filename &filename, const DSearchPath &searchpath, const std::string &default_extension=std::string()) const
Searches the given search path for the filename.
clear_origin
Removes the origin specification from the properties.
get_os_handle
Returns the OS-specific handle stored internally to the WindowHandle wrapper.
Definition: windowHandle.h:44
int get_y_origin() const
Returns the y coordinate of the window's top-left corner, not including decorations.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void release() const
Releases the lightReMutex.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
get_title
Returns the window's title.
int get_y_size() const
Returns the number of pixels in the Y direction.
A window, fullscreen or on a desktop, into which a graphics device sends its output for interactive d...
int get_x_size() const
Returns the number of pixels in the X direction.
A ButtonHandle represents a single button from any device, including keyboard buttons and mouse butto...
Definition: buttonHandle.h:26
static PNMFileTypeRegistry * get_global_ptr()
Returns a pointer to the global PNMFileTypeRegistry object.
has_fullscreen
Returns true if set_fullscreen() has been specified.
static void close_read_file(std::istream *stream)
Closes a file opened by a previous call to open_read_file().
get_close_request_event
Returns the name of the event set via set_close_request_event().
has_z_order
Returns true if the window z_order has been specified, false otherwise.
A container for the various kinds of properties we might ask to have on a graphics window before we o...
virtual void clear(Thread *current_thread)
Clears the entire framebuffer before rendering, according to the settings of get_color_clear_active()...
get_undecorated
Returns true if the window has no border.
has_origin
Returns true if the window origin has been specified, false otherwise.
has_fixed_size
Returns true if set_fixed_size() has been specified.
void disable_relative_mouse()
Disables relative mouse mode for this display.
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
int get_screen() const
Returns the X screen number associated with the pipe.
set_origin
Specifies the origin on the screen (in pixels, relative to the top-left corner) at which the window s...
static ButtonHandle button(int button_number)
Returns the ButtonHandle associated with the particular numbered mouse button (zero-based),...
Definition: mouseButton.cxx:32
Similar to MutexHolder, but for a light mutex.
static ButtonHandle wheel_down()
Returns the ButtonHandle generated when the mouse wheel is rolled one notch downwards.
Definition: mouseButton.cxx:93
An object to create GraphicsOutputs that share a particular 3-D API.
Definition: graphicsPipe.h:52
Holds the data that might be generated by a 2-d pointer input device, such as the mouse in the Graphi...
Definition: pointerData.h:38
static VirtualFileSystem * get_global_ptr()
Returns the default global VirtualFileSystem.
clear_mouse_mode
Removes the mouse_mode specification from the properties.
static ButtonHandle wheel_left()
Returns the ButtonHandle generated when the mouse is scrolled to the left.
void button_up(ButtonHandle button, double time=ClockObject::get_global_clock() ->get_frame_time())
Records that the indicated button has been released.
X11_Cursor get_hidden_cursor()
Returns an invisible Cursor suitable for assigning to windows that have the cursor_hidden property se...
void set_pointer_out_of_window(double time=ClockObject::get_global_clock() ->get_frame_time())
To be called by a particular kind of GraphicsWindow to indicate that the pointer is no longer within ...
X11_Display * get_display() const
Returns a pointer to the X display associated with the pipe: the display on which to create the windo...
clear_cursor_hidden
Removes the cursor_hidden specification from the properties.
get_ascii_equivalent
Returns the character code associated with the button, or '\0' if no ASCII code was associated.
Definition: buttonHandle.h:63
void raw_button_down(ButtonHandle button, double time=ClockObject::get_global_clock() ->get_frame_time())
Records that the indicated button has been depressed.
get_real_time
Returns the actual number of seconds elapsed since the ClockObject was created, or since it was last ...
Definition: clockObject.h:92
This is a virtual input device that represents the keyboard and mouse pair that is associated with a ...
virtual MouseData get_pointer(int device) const
Returns the MouseData associated with the nth input device's pointer.
has_ascii_equivalent
Returns true if the button was created with an ASCII equivalent code (e.g.
Definition: buttonHandle.h:63
xelval * get_alpha_array()
Directly access the underlying PNMImage array of alpha values.
Definition: pnmImage.I:1101
This is a base class for the various different classes that represent the result of a frame of render...
Similar to MutexHolder, but for a light reentrant mutex.
get_minimized
Returns true if the window is minimized.
PNMFileType * get_type_from_extension(const std::string &filename) const
Tries to determine what the PNMFileType is likely to be for a particular image file based on its exte...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
get_size
Returns size in pixels of the useful part of the window, not including decorations.
PointerData get_pointer() const
Returns the PointerData associated with the input device's pointer.
virtual void set_properties_now(WindowProperties &properties)
Applies the requested set of properties to the window, if possible, for instance to request a change ...
clear_fullscreen
Removes the fullscreen specification from the properties.
This class maintains the set of all known PNMFileTypes in the universe.
virtual bool is_any_clear_active() const
Returns true if any of the clear types (so far there are just color or depth) have been set active,...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
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.
has_cursor_hidden
Returns true if set_cursor_hidden() has been specified.
A thread; that is, a lightweight process.
Definition: thread.h:46
get_display_width
Returns the width of the entire display, if it is known.
Definition: graphicsPipe.h:94
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
Encapsulates all the communication with a particular instance of a given rendering backend.
has_title
Returns true if the window title has been specified, false otherwise.
static ButtonHandle wheel_right()
Returns the ButtonHandle generated when the mouse is scrolled to the right.
bool try_lock()
Alias for try_acquire() to match C++11 semantics.
bool is_of_type(TypeHandle handle) const
Returns true if the current object is or derives from the indicated type.
Definition: typedObject.I:28
This class is the main interface to controlling the render process.
int get_y_size() const
Returns size in pixels in the y dimension of the useful part of the window, not including decorations...
void set_maxval(xelval maxval)
Rescales the image to the indicated maxval.
Definition: pnmImage.cxx:794
int get_x_size() const
Returns size in pixels in the x dimension of the useful part of the window, not including decorations...
get_fullscreen
Returns true if the window is in fullscreen mode.
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
void focus_lost(double time=ClockObject::get_global_clock() ->get_frame_time())
This should be called when the window focus is lost, so that we may miss upcoming button events (espe...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A container for the various kinds of properties we might ask to have on a graphics frameBuffer before...
clear_foreground
Removes the foreground specification from the properties.
static ButtonHandle ascii_key(char ascii_equivalent)
Returns the ButtonHandle associated with the particular ASCII character, if there is one,...
virtual bool begin_frame(FrameMode mode, Thread *current_thread)
This function will be called within the draw thread before beginning rendering for a given frame.
clear_title
Removes the title specification from the properties.
bool is_fullscreen() const
Returns true if the window has been opened as a fullscreen window, false otherwise.
bool supports_relative_mouse() const
Returns true if relative mouse mode is supported on this display.
set_open
Specifies whether the window should be open.
int get_x_origin() const
Returns the x coordinate of the window's top-left corner, not including decorations.
get_z_order
Returns the window's z_order.
clear_size
Removes the size specification from the properties.
This class represents a map containing all of the buttons of a (keyboard) device, though it can also ...
Definition: buttonMap.h:30
bool enable_relative_mouse()
Enables relative mouse mode for this display.
has_size
Returns true if the window size has been specified, false otherwise.
has_foreground
Returns true if set_foreground() has been specified.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
virtual void process_events()
Do whatever processing is necessary to ensure that the window responds to user events.
static ButtonHandle wheel_up()
Returns the ButtonHandle generated when the mouse wheel is rolled one notch upwards.
Definition: mouseButton.cxx:84
Interfaces to the X11 window system.
void map_button(ButtonHandle raw_button, ButtonHandle button, const std::string &label="")
Registers a new button mapping.
Definition: buttonMap.cxx:23
void raw_button_up(ButtonHandle button, double time=ClockObject::get_global_clock() ->get_frame_time())
Records that the indicated button has been released.
set_foreground
Specifies whether the window should be opened in the foreground (true), or left in the background (fa...
virtual void end_frame(FrameMode mode, Thread *current_thread)
This function will be called within the draw thread after rendering is completed for a given frame.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
const FrameBufferProperties & get_fb_properties() const
Returns the framebuffer properties of the window.