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 (is_fullscreen != want_fullscreen || (is_fullscreen && properties.has_size())) {
583  if (want_fullscreen) {
584  if (x11_pipe->_have_xrandr) {
585  XRRScreenConfiguration* conf = _XRRGetScreenInfo(_display, x11_pipe->get_root());
586  SizeID old_size_id = x11_pipe->_XRRConfigCurrentConfiguration(conf, &_orig_rotation);
587  SizeID new_size_id = (SizeID) -1;
588  int num_sizes = 0, reqsizex, reqsizey;
589  if (properties.has_size()) {
590  reqsizex = properties.get_x_size();
591  reqsizey = properties.get_y_size();
592  } else {
593  reqsizex = _properties.get_x_size();
594  reqsizey = _properties.get_y_size();
595  }
596  XRRScreenSize *xrrs;
597  xrrs = x11_pipe->_XRRSizes(_display, 0, &num_sizes);
598  for (int i = 0; i < num_sizes; ++i) {
599  if (xrrs[i].width == reqsizex &&
600  xrrs[i].height == reqsizey) {
601  new_size_id = i;
602  }
603  }
604  if (new_size_id == (SizeID) -1) {
605  x11display_cat.error()
606  << "Videocard has no supported display resolutions at specified res ("
607  << reqsizex << " x " << reqsizey << ")\n";
608  } else {
609  if (new_size_id != old_size_id) {
610 
611  _XRRSetScreenConfig(_display, conf, x11_pipe->get_root(), new_size_id, _orig_rotation, CurrentTime);
612  if (_orig_size_id == (SizeID) -1) {
613  // Remember the original resolution so we can switch back to it.
614  _orig_size_id = old_size_id;
615  }
616  }
617  }
618  } else {
619  // If we don't have Xrandr support, we fake the fullscreen support by
620  // setting the window size to the desktop size.
621  properties.set_size(x11_pipe->get_display_width(),
622  x11_pipe->get_display_height());
623  }
624  } else {
625  // Change the resolution back to what it was. Don't remove the SizeID
626  // typecast!
627  if (_orig_size_id != (SizeID) -1) {
628  XRRScreenConfiguration *conf = _XRRGetScreenInfo(_display, x11_pipe->get_root());
629  _XRRSetScreenConfig(_display, conf, x11_pipe->get_root(), _orig_size_id, _orig_rotation, CurrentTime);
630  _orig_size_id = (SizeID) -1;
631  }
632  // Set the origin back to what it was
633  if (!properties.has_origin() && _properties.has_origin()) {
634  properties.set_origin(_properties.get_x_origin(), _properties.get_y_origin());
635  }
636  }
637  }
638 
639  if (properties.has_origin()) {
640  // A coordinate of -2 means to center the window on screen.
641  if (properties.get_x_origin() == -2 || properties.get_y_origin() == -2) {
642  int x_origin = properties.get_x_origin();
643  int y_origin = properties.get_y_origin();
644  if (properties.has_size()) {
645  if (x_origin == -2) {
646  x_origin = 0.5 * (x11_pipe->get_display_width() - properties.get_x_size());
647  }
648  if (y_origin == -2) {
649  y_origin = 0.5 * (x11_pipe->get_display_height() - properties.get_y_size());
650  }
651  } else {
652  if (x_origin == -2) {
653  x_origin = 0.5 * (x11_pipe->get_display_width() - _properties.get_x_size());
654  }
655  if (y_origin == -2) {
656  y_origin = 0.5 * (x11_pipe->get_display_height() - _properties.get_y_size());
657  }
658  }
659  properties.set_origin(x_origin, y_origin);
660  }
661  }
662 
664  if (!properties.is_any_specified()) {
665  // The base class has already handled this case.
666  return;
667  }
668 
669  // The window is already open; we are limited to what we can change on the
670  // fly.
671 
672  // We'll pass some property requests on as a window manager hint.
673  set_wm_properties(properties, true);
674 
675  // The window title may be changed by issuing another hint request. Assume
676  // this will be honored.
677  if (properties.has_title()) {
678  _properties.set_title(properties.get_title());
679  properties.clear_title();
680  }
681 
682  // Same for fullscreen.
683  if (properties.has_fullscreen()) {
684  _properties.set_fullscreen(properties.get_fullscreen());
685  properties.clear_fullscreen();
686  }
687 
688  // The size and position of an already-open window are changed via explicit
689  // X calls. These may still get intercepted by the window manager. Rather
690  // than changing _properties immediately, we'll wait for the ConfigureNotify
691  // message to come back.
692  XWindowChanges changes;
693  int value_mask = 0;
694 
695  if (_properties.get_fullscreen()) {
696  changes.x = 0;
697  changes.y = 0;
698  value_mask |= CWX | CWY;
699  properties.clear_origin();
700 
701  } else if (properties.has_origin()) {
702  changes.x = properties.get_x_origin();
703  changes.y = properties.get_y_origin();
704  if (changes.x != -1) value_mask |= CWX;
705  if (changes.y != -1) value_mask |= CWY;
706  properties.clear_origin();
707  }
708 
709  // This, too. But we can't currently change out of fixed_size mode.
710  if (properties.has_fixed_size() && properties.get_fixed_size()) {
711  _properties.set_fixed_size(properties.get_fixed_size());
712  properties.clear_fixed_size();
713  _fixed_size = _properties.get_size();
714  }
715 
716  if (properties.has_size()) {
717  changes.width = properties.get_x_size();
718  changes.height = properties.get_y_size();
719  value_mask |= (CWWidth | CWHeight);
720 
721  if (_properties.get_fixed_size()) {
722  _fixed_size = properties.get_size();
723  }
724  properties.clear_size();
725  }
726 
727  if (properties.has_z_order()) {
728  // We'll send the classic stacking request through the standard interface,
729  // for users of primitive window managers; but we'll also send it as a
730  // window manager hint, for users of modern window managers.
731  _properties.set_z_order(properties.get_z_order());
732  switch (properties.get_z_order()) {
733  case WindowProperties::Z_bottom:
734  changes.stack_mode = Below;
735  break;
736 
737  case WindowProperties::Z_normal:
738  changes.stack_mode = TopIf;
739  break;
740 
741  case WindowProperties::Z_top:
742  changes.stack_mode = Above;
743  break;
744  }
745 
746  value_mask |= (CWStackMode);
747  properties.clear_z_order();
748  }
749 
750  // We hide the cursor by setting it to an invisible pixmap. We can also
751  // load a custom cursor from a file.
752  if (properties.has_cursor_hidden() || properties.has_cursor_filename()) {
753  if (properties.has_cursor_hidden()) {
754  _properties.set_cursor_hidden(properties.get_cursor_hidden());
755  properties.clear_cursor_hidden();
756  }
757  Filename cursor_filename;
758  if (properties.has_cursor_filename()) {
759  cursor_filename = properties.get_cursor_filename();
760  _properties.set_cursor_filename(cursor_filename);
761  properties.clear_cursor_filename();
762  }
763  Filename filename = properties.get_cursor_filename();
764  _properties.set_cursor_filename(filename);
765 
766  if (_properties.get_cursor_hidden()) {
767  XDefineCursor(_display, _xwindow, x11_pipe->get_hidden_cursor());
768 
769  } else if (!cursor_filename.empty()) {
770  // Note that if the cursor fails to load, cursor will be None
771  X11_Cursor cursor = get_cursor(cursor_filename);
772  XDefineCursor(_display, _xwindow, cursor);
773 
774  } else {
775  XDefineCursor(_display, _xwindow, None);
776  }
777 
778  // Regrab the mouse if we changed the cursor, otherwise it won't update.
779  if (!properties.has_mouse_mode() &&
780  _properties.get_mouse_mode() != WindowProperties::M_absolute) {
781  properties.set_mouse_mode(_properties.get_mouse_mode());
782  }
783  }
784 
785  if (properties.has_foreground()) {
786  if (properties.get_foreground()) {
787  XSetInputFocus(_display, _xwindow, RevertToPointerRoot, CurrentTime);
788  } else {
789  XSetInputFocus(_display, PointerRoot, RevertToPointerRoot, CurrentTime);
790  }
791  properties.clear_foreground();
792  }
793 
794  if (properties.has_mouse_mode()) {
795  switch (properties.get_mouse_mode()) {
796  case WindowProperties::M_absolute:
797  XUngrabPointer(_display, CurrentTime);
798  if (_dga_mouse_enabled) {
799  x11_pipe->disable_relative_mouse();
800  _dga_mouse_enabled = false;
801  }
802  _properties.set_mouse_mode(WindowProperties::M_absolute);
803  properties.clear_mouse_mode();
804  break;
805 
806  case WindowProperties::M_relative:
807  if (!_dga_mouse_enabled) {
808  if (x11_pipe->supports_relative_mouse()) {
809  X11_Cursor cursor = None;
810  if (_properties.get_cursor_hidden()) {
811  x11GraphicsPipe *x11_pipe;
812  DCAST_INTO_V(x11_pipe, _pipe);
813  cursor = x11_pipe->get_hidden_cursor();
814  }
815 
816  if (XGrabPointer(_display, _xwindow, True, 0, GrabModeAsync,
817  GrabModeAsync, _xwindow, cursor, CurrentTime) != GrabSuccess) {
818  x11display_cat.error() << "Failed to grab pointer!\n";
819  } else {
820  x11_pipe->enable_relative_mouse();
821 
822  _properties.set_mouse_mode(WindowProperties::M_relative);
823  properties.clear_mouse_mode();
824  _dga_mouse_enabled = true;
825 
826  // Get the real mouse position, so we can addsubtract our relative
827  // coordinates later.
828  XEvent event;
829  XQueryPointer(_display, _xwindow, &event.xbutton.root,
830  &event.xbutton.window, &event.xbutton.x_root, &event.xbutton.y_root,
831  &event.xbutton.x, &event.xbutton.y, &event.xbutton.state);
832  _input->set_pointer_in_window(event.xbutton.x, event.xbutton.y);
833  }
834  } else {
835  x11display_cat.info()
836  << "XF86DGA extension not available, cannot enable relative mouse mode\n";
837  _dga_mouse_enabled = false;
838  }
839  }
840  break;
841 
842  case WindowProperties::M_confined:
843  {
844  x11GraphicsPipe *x11_pipe;
845  DCAST_INTO_V(x11_pipe, _pipe);
846 
847  if (_dga_mouse_enabled) {
848  x11_pipe->disable_relative_mouse();
849  _dga_mouse_enabled = false;
850  }
851  X11_Cursor cursor = None;
852  if (_properties.get_cursor_hidden()) {
853  cursor = x11_pipe->get_hidden_cursor();
854  }
855 
856  if (XGrabPointer(_display, _xwindow, True, 0, GrabModeAsync,
857  GrabModeAsync, _xwindow, cursor, CurrentTime) != GrabSuccess) {
858  x11display_cat.error() << "Failed to grab pointer!\n";
859  } else {
860  _properties.set_mouse_mode(WindowProperties::M_confined);
861  properties.clear_mouse_mode();
862  }
863  }
864  break;
865  }
866  }
867 
868  if (value_mask != 0) {
869  // We must call this after changing the WM properties, otherwise we may
870  // get misleading ConfigureNotify events in the wrong order.
871  XReconfigureWMWindow(_display, _xwindow, _screen, value_mask, &changes);
872 
873  // Don't draw anything until this is done reconfiguring.
874  _awaiting_configure = true;
875  }
876 }
877 
878 /**
879  * Overridden from GraphicsWindow.
880  */
881 void x11GraphicsWindow::
882 mouse_mode_absolute() {
883  // unused: remove in 1.10!
884 }
885 
886 /**
887  * Overridden from GraphicsWindow.
888  */
889 void x11GraphicsWindow::
890 mouse_mode_relative() {
891  // unused: remove in 1.10!
892 }
893 
894 /**
895  * Closes the window right now. Called from the window thread.
896  */
897 void x11GraphicsWindow::
898 close_window() {
899  if (_gsg != nullptr) {
900  _gsg.clear();
901  }
902 
903  LightReMutexHolder holder(x11GraphicsPipe::_x_mutex);
904  if (_ic != (XIC)nullptr) {
905  XDestroyIC(_ic);
906  _ic = (XIC)nullptr;
907  }
908 
909  if (_xwindow != (X11_Window)nullptr) {
910  XDestroyWindow(_display, _xwindow);
911  _xwindow = (X11_Window)nullptr;
912 
913  // This may be necessary if we just closed the last X window in an
914  // application, so the server hears the close request.
915  XFlush(_display);
916  }
917 
918  // Change the resolution back to what it was. Don't remove the SizeID
919  // typecast!
920  if (_orig_size_id != (SizeID) -1) {
921  X11_Window root;
922  if (_pipe != nullptr) {
923  x11GraphicsPipe *x11_pipe;
924  DCAST_INTO_V(x11_pipe, _pipe);
925  root = x11_pipe->get_root();
926  } else {
927  // Oops. Looks like the pipe was destroyed before the window gets
928  // closed. Oh well, let's get the root window by ourselves.
929  root = RootWindow(_display, _screen);
930  }
931  XRRScreenConfiguration *conf = _XRRGetScreenInfo(_display, root);
932  _XRRSetScreenConfig(_display, conf, root, _orig_size_id, _orig_rotation, CurrentTime);
933  _orig_size_id = -1;
934  }
935 
936  GraphicsWindow::close_window();
937 }
938 
939 /**
940  * Opens the window right now. Called from the window thread. Returns true
941  * if the window is successfully opened, or false if there was a problem.
942  */
943 bool x11GraphicsWindow::
944 open_window() {
945  if (_visual_info == nullptr) {
946  // No X visual for this fbconfig; how can we open the window?
947  x11display_cat.error()
948  << "No X visual: cannot open window.\n";
949  return false;
950  }
951 
952  x11GraphicsPipe *x11_pipe;
953  DCAST_INTO_R(x11_pipe, _pipe, false);
954 
955  if (!_properties.has_origin()) {
956  _properties.set_origin(0, 0);
957  }
958  if (!_properties.has_size()) {
959  _properties.set_size(100, 100);
960  }
961 
962  // Make sure we are not making X11 calls from other threads.
963  LightReMutexHolder holder(x11GraphicsPipe::_x_mutex);
964 
965  if (_properties.get_fullscreen() && x11_pipe->_have_xrandr) {
966  XRRScreenConfiguration* conf = _XRRGetScreenInfo(_display, x11_pipe->get_root());
967  if (_orig_size_id == (SizeID) -1) {
968  _orig_size_id = x11_pipe->_XRRConfigCurrentConfiguration(conf, &_orig_rotation);
969  }
970  int num_sizes, new_size_id = -1;
971  XRRScreenSize *xrrs;
972  xrrs = x11_pipe->_XRRSizes(_display, 0, &num_sizes);
973  for (int i = 0; i < num_sizes; ++i) {
974  if (xrrs[i].width == _properties.get_x_size() &&
975  xrrs[i].height == _properties.get_y_size()) {
976  new_size_id = i;
977  }
978  }
979  if (new_size_id == -1) {
980  x11display_cat.error()
981  << "Videocard has no supported display resolutions at specified res ("
982  << _properties.get_x_size() << " x " << _properties.get_y_size() <<")\n";
983  _orig_size_id = -1;
984  return false;
985  }
986  if (new_size_id != _orig_size_id) {
987  _XRRSetScreenConfig(_display, conf, x11_pipe->get_root(), new_size_id, _orig_rotation, CurrentTime);
988  } else {
989  _orig_size_id = -1;
990  }
991  }
992 
993  X11_Window parent_window = x11_pipe->get_root();
994  WindowHandle *window_handle = _properties.get_parent_window();
995  if (window_handle != nullptr) {
996  x11display_cat.info()
997  << "Got parent_window " << *window_handle << "\n";
998  WindowHandle::OSHandle *os_handle = window_handle->get_os_handle();
999  if (os_handle != nullptr) {
1000  x11display_cat.info()
1001  << "os_handle type " << os_handle->get_type() << "\n";
1002 
1003  if (os_handle->is_of_type(NativeWindowHandle::X11Handle::get_class_type())) {
1004  NativeWindowHandle::X11Handle *x11_handle = DCAST(NativeWindowHandle::X11Handle, os_handle);
1005  parent_window = x11_handle->get_handle();
1006  } else if (os_handle->is_of_type(NativeWindowHandle::IntHandle::get_class_type())) {
1007  NativeWindowHandle::IntHandle *int_handle = DCAST(NativeWindowHandle::IntHandle, os_handle);
1008  parent_window = (X11_Window)int_handle->get_handle();
1009  }
1010  }
1011  }
1012  _parent_window_handle = window_handle;
1013 
1014  _event_mask =
1015  ButtonPressMask | ButtonReleaseMask |
1016  KeyPressMask | KeyReleaseMask |
1017  EnterWindowMask | LeaveWindowMask |
1018  PointerMotionMask |
1019  FocusChangeMask | StructureNotifyMask;
1020 
1021  // Initialize window attributes
1022  XSetWindowAttributes wa;
1023  wa.background_pixel = XBlackPixel(_display, _screen);
1024  wa.border_pixel = 0;
1025  wa.colormap = _colormap;
1026  wa.event_mask = _event_mask;
1027  wa.override_redirect = _override_redirect;
1028 
1029  unsigned long attrib_mask =
1030  CWBackPixel | CWBorderPixel | CWColormap | CWEventMask | CWOverrideRedirect;
1031 
1032  _xwindow = XCreateWindow
1033  (_display, parent_window,
1034  _properties.get_x_origin(), _properties.get_y_origin(),
1035  _properties.get_x_size(), _properties.get_y_size(),
1036  0, _visual_info->depth, InputOutput,
1037  _visual_info->visual, attrib_mask, &wa);
1038 
1039  if (_xwindow == (X11_Window)0) {
1040  x11display_cat.error()
1041  << "failed to create X window.\n";
1042  return false;
1043  }
1044 
1045  if (_properties.get_fixed_size()) {
1046  _fixed_size = _properties.get_size();
1047  }
1048 
1049  set_wm_properties(_properties, false);
1050 
1051  // We don't specify any fancy properties of the XIC. It would be nicer if
1052  // we could support fancy IM's that want preedit callbacks, etc., but that
1053  // can wait until we have an X server that actually supports these to test
1054  // it on.
1055  XIM im = x11_pipe->get_im();
1056  _ic = nullptr;
1057  if (im) {
1058  _ic = XCreateIC
1059  (im,
1060  XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
1061  nullptr);
1062  if (_ic == (XIC)nullptr) {
1063  x11display_cat.warning()
1064  << "Couldn't create input context.\n";
1065  }
1066  }
1067 
1068  if (_properties.get_cursor_hidden()) {
1069  XDefineCursor(_display, _xwindow, x11_pipe->get_hidden_cursor());
1070 
1071  } else if (_properties.has_cursor_filename() && !_properties.get_cursor_filename().empty()) {
1072  // Note that if the cursor fails to load, cursor will be None
1073  X11_Cursor cursor = get_cursor(_properties.get_cursor_filename());
1074  XDefineCursor(_display, _xwindow, cursor);
1075  }
1076 
1077  XMapWindow(_display, _xwindow);
1078 
1079  if (_properties.get_raw_mice()) {
1080  open_raw_mice();
1081  } else {
1082  if (x11display_cat.is_debug()) {
1083  x11display_cat.debug()
1084  << "Raw mice not requested.\n";
1085  }
1086  }
1087 
1088  // Create a WindowHandle for ourselves
1089  _window_handle = NativeWindowHandle::make_x11(_xwindow);
1090 
1091  // And tell our parent window that we're now its child.
1092  if (_parent_window_handle != nullptr) {
1093  _parent_window_handle->attach_child(_window_handle);
1094  }
1095 
1096  return true;
1097 }
1098 
1099 /**
1100  * Asks the window manager to set the appropriate properties. In X, these
1101  * properties cannot be specified directly by the application; they must be
1102  * requested via the window manager, which may or may not choose to honor the
1103  * request.
1104  *
1105  * If already_mapped is true, the window has already been mapped (manifested)
1106  * on the display. This means we may need to use a different action in some
1107  * cases.
1108  *
1109  * Assumes the X11 lock is held.
1110  */
1111 void x11GraphicsWindow::
1112 set_wm_properties(const WindowProperties &properties, bool already_mapped) {
1113  x11GraphicsPipe *x11_pipe;
1114  DCAST_INTO_V(x11_pipe, _pipe);
1115 
1116  // Name the window if there is a name
1117  XTextProperty window_name;
1118  XTextProperty *window_name_p = nullptr;
1119  if (properties.has_title()) {
1120  const char *name = properties.get_title().c_str();
1121  if (XStringListToTextProperty((char **)&name, 1, &window_name) != 0) {
1122  window_name_p = &window_name;
1123  }
1124  }
1125 
1126  // The size hints request a window of a particular size andor a particular
1127  // placement onscreen.
1128  XSizeHints *size_hints_p = nullptr;
1129  if (properties.has_origin() || properties.has_size()) {
1130  size_hints_p = XAllocSizeHints();
1131  if (size_hints_p != nullptr) {
1132  if (properties.has_origin()) {
1133  if (_properties.get_fullscreen()) {
1134  size_hints_p->x = 0;
1135  size_hints_p->y = 0;
1136  } else {
1137  size_hints_p->x = properties.get_x_origin();
1138  size_hints_p->y = properties.get_y_origin();
1139  }
1140  size_hints_p->flags |= USPosition;
1141  }
1142  LVecBase2i size = _properties.get_size();
1143  if (properties.has_size()) {
1144  size = properties.get_size();
1145  size_hints_p->width = size.get_x();
1146  size_hints_p->height = size.get_y();
1147  size_hints_p->flags |= USSize;
1148  }
1149  if (properties.get_fixed_size()) {
1150  size_hints_p->min_width = size.get_x();
1151  size_hints_p->min_height = size.get_y();
1152  size_hints_p->max_width = size.get_x();
1153  size_hints_p->max_height = size.get_y();
1154  size_hints_p->flags |= (PMinSize | PMaxSize);
1155  }
1156  }
1157  }
1158 
1159  // The window manager hints include requests to the window manager other
1160  // than those specific to window geometry.
1161  XWMHints *wm_hints_p = nullptr;
1162  wm_hints_p = XAllocWMHints();
1163  if (wm_hints_p != nullptr) {
1164  if (properties.has_minimized() && properties.get_minimized()) {
1165  wm_hints_p->initial_state = IconicState;
1166  } else {
1167  wm_hints_p->initial_state = NormalState;
1168  }
1169  wm_hints_p->flags = StateHint;
1170  }
1171 
1172  // Two competing window manager interfaces have evolved. One of them allows
1173  // to set certain properties as a "type"; the other one as a "state". We'll
1174  // try to honor both.
1175  static const int max_type_data = 32;
1176  int32_t type_data[max_type_data];
1177  int next_type_data = 0;
1178 
1179  static const int max_state_data = 32;
1180  int32_t state_data[max_state_data];
1181  int next_state_data = 0;
1182 
1183  static const int max_set_data = 32;
1184  class SetAction {
1185  public:
1186  inline SetAction() { }
1187  inline SetAction(Atom state, Atom action) : _state(state), _action(action) { }
1188  Atom _state;
1189  Atom _action;
1190  };
1191  SetAction set_data[max_set_data];
1192  int next_set_data = 0;
1193 
1194  if (properties.has_fullscreen()) {
1195  if (properties.get_fullscreen()) {
1196  // For a "fullscreen" request, we pass this through, hoping the window
1197  // manager will support EWMH.
1198  type_data[next_type_data++] = x11_pipe->_net_wm_window_type_fullscreen;
1199 
1200  // We also request it as a state.
1201  state_data[next_state_data++] = x11_pipe->_net_wm_state_fullscreen;
1202  // Don't ask me why this has to be 10 and not _net_wm_state_add. It
1203  // doesn't seem to work otherwise.
1204  set_data[next_set_data++] = SetAction(x11_pipe->_net_wm_state_fullscreen, 1);
1205 
1206  } else {
1207  set_data[next_set_data++] = SetAction(x11_pipe->_net_wm_state_fullscreen, 0);
1208  }
1209  }
1210 
1211  // If we asked for a window without a border, there's no excellent way to
1212  // arrange that. For users whose window managers follow the EWMH
1213  // specification, we can ask for a "splash" screen, which is usually
1214  // undecorated. It's not exactly right, but the spec doesn't give us an
1215  // exactly-right option.
1216 
1217  // For other users, we'll totally punt and just set the window's Class to
1218  // "Undecorated", and let the user configure hisher window manager not to
1219  // put a border around windows of this class.
1220  XClassHint *class_hints_p = nullptr;
1221  if (!x_wm_class.empty()) {
1222  // Unless the user wanted to use his own WM_CLASS, of course.
1223  class_hints_p = XAllocClassHint();
1224  class_hints_p->res_class = (char*) x_wm_class.c_str();
1225  if (!x_wm_class_name.empty()) {
1226  class_hints_p->res_name = (char*) x_wm_class_name.c_str();
1227  }
1228 
1229  } else if (properties.get_undecorated() || properties.get_fullscreen()) {
1230  class_hints_p = XAllocClassHint();
1231  class_hints_p->res_class = (char*) "Undecorated";
1232  }
1233 
1234  if (properties.get_undecorated() && !properties.get_fullscreen()) {
1235  type_data[next_type_data++] = x11_pipe->_net_wm_window_type_splash;
1236  }
1237 
1238  if (properties.has_z_order()) {
1239  switch (properties.get_z_order()) {
1240  case WindowProperties::Z_bottom:
1241  state_data[next_state_data++] = x11_pipe->_net_wm_state_below;
1242  set_data[next_set_data++] = SetAction(x11_pipe->_net_wm_state_below,
1243  x11_pipe->_net_wm_state_add);
1244  set_data[next_set_data++] = SetAction(x11_pipe->_net_wm_state_above,
1245  x11_pipe->_net_wm_state_remove);
1246  break;
1247 
1248  case WindowProperties::Z_normal:
1249  set_data[next_set_data++] = SetAction(x11_pipe->_net_wm_state_below,
1250  x11_pipe->_net_wm_state_remove);
1251  set_data[next_set_data++] = SetAction(x11_pipe->_net_wm_state_above,
1252  x11_pipe->_net_wm_state_remove);
1253  break;
1254 
1255  case WindowProperties::Z_top:
1256  state_data[next_state_data++] = x11_pipe->_net_wm_state_above;
1257  set_data[next_set_data++] = SetAction(x11_pipe->_net_wm_state_below,
1258  x11_pipe->_net_wm_state_remove);
1259  set_data[next_set_data++] = SetAction(x11_pipe->_net_wm_state_above,
1260  x11_pipe->_net_wm_state_add);
1261  break;
1262  }
1263  }
1264 
1265  nassertv(next_type_data < max_type_data);
1266  nassertv(next_state_data < max_state_data);
1267  nassertv(next_set_data < max_set_data);
1268 
1269  // Add the process ID as a convenience for other applications.
1270  int32_t pid = getpid();
1271  XChangeProperty(_display, _xwindow, x11_pipe->_net_wm_pid,
1272  XA_CARDINAL, 32, PropModeReplace,
1273  (unsigned char *)&pid, 1);
1274 
1275  // Disable compositing effects in fullscreen mode.
1276  if (properties.has_fullscreen()) {
1277  int32_t compositor = properties.get_fullscreen() ? 1 : 0;
1278  XChangeProperty(_display, _xwindow, x11_pipe->_net_wm_bypass_compositor,
1279  XA_CARDINAL, 32, PropModeReplace,
1280  (unsigned char *)&compositor, 1);
1281  }
1282 
1283  XChangeProperty(_display, _xwindow, x11_pipe->_net_wm_window_type,
1284  XA_ATOM, 32, PropModeReplace,
1285  (unsigned char *)type_data, next_type_data);
1286 
1287  // Request the state properties all at once.
1288  XChangeProperty(_display, _xwindow, x11_pipe->_net_wm_state,
1289  XA_ATOM, 32, PropModeReplace,
1290  (unsigned char *)state_data, next_state_data);
1291 
1292  if (already_mapped) {
1293  // We have to request state changes differently when the window has been
1294  // mapped. To do this, we need to send a client message to the root
1295  // window for each change.
1296 
1297  x11GraphicsPipe *x11_pipe;
1298  DCAST_INTO_V(x11_pipe, _pipe);
1299 
1300  for (int i = 0; i < next_set_data; ++i) {
1301  XClientMessageEvent event;
1302  memset(&event, 0, sizeof(event));
1303  event.type = ClientMessage;
1304  event.send_event = True;
1305  event.display = _display;
1306  event.window = _xwindow;
1307  event.message_type = x11_pipe->_net_wm_state;
1308  event.format = 32;
1309  event.data.l[0] = set_data[i]._action;
1310  event.data.l[1] = set_data[i]._state;
1311  event.data.l[2] = 0;
1312  event.data.l[3] = 1;
1313 
1314  XSendEvent(_display, x11_pipe->get_root(), True, SubstructureNotifyMask | SubstructureRedirectMask, (XEvent *)&event);
1315  }
1316  }
1317 
1318  XSetWMProperties(_display, _xwindow, window_name_p, window_name_p,
1319  nullptr, 0, size_hints_p, wm_hints_p, class_hints_p);
1320 
1321  if (size_hints_p != nullptr) {
1322  XFree(size_hints_p);
1323  }
1324  if (wm_hints_p != nullptr) {
1325  XFree(wm_hints_p);
1326  }
1327  if (class_hints_p != nullptr) {
1328  XFree(class_hints_p);
1329  }
1330 
1331  // Also, indicate to the window manager that we'd like to get a chance to
1332  // close our windows cleanly, rather than being rudely disconnected from the
1333  // X server if the user requests a window close.
1334  Atom protocols[] = {
1335  _wm_delete_window,
1336  };
1337 
1338  XSetWMProtocols(_display, _xwindow, protocols,
1339  sizeof(protocols) / sizeof(Atom));
1340 }
1341 
1342 /**
1343  * Allocates a colormap appropriate to the visual and stores in in the
1344  * _colormap method.
1345  */
1346 void x11GraphicsWindow::
1347 setup_colormap(XVisualInfo *visual) {
1348  x11GraphicsPipe *x11_pipe;
1349  DCAST_INTO_V(x11_pipe, _pipe);
1350  X11_Window root_window = x11_pipe->get_root();
1351 
1352  _colormap = XCreateColormap(_display, root_window,
1353  visual->visual, AllocNone);
1354 }
1355 
1356 /**
1357  * Adds raw mice to the _input_devices list.
1358  * @deprecated obtain raw devices via the device manager instead.
1359  */
1360 void x11GraphicsWindow::
1361 open_raw_mice() {
1362 #ifdef PHAVE_LINUX_INPUT_H
1363  bool any_present = false;
1364  bool any_mice = false;
1365 
1366  for (int i=0; i<64; i++) {
1367  ostringstream fnb;
1368  fnb << "/dev/input/event" << i;
1369  string fn = fnb.str();
1370  int fd = open(fn.c_str(), O_RDONLY | O_NONBLOCK, 0);
1371  if (fd >= 0) {
1372  EvdevInputDevice *device = new EvdevInputDevice(nullptr, fd);
1373  nassertd(device != NULL) continue;
1374 
1375  if (device->has_pointer()) {
1376  add_input_device(device);
1377 
1378  x11display_cat.info()
1379  << "Raw mouse " << _input_devices.size()
1380  << " detected: " << device->get_name() << "\n";
1381 
1382  any_mice = true;
1383  any_present = true;
1384  }
1385  } else {
1386  if (errno == ENOENT || errno == ENOTDIR) {
1387  break;
1388  } else {
1389  any_present = true;
1390  x11display_cat.error()
1391  << "Opening raw mice: " << strerror(errno) << " " << fn << "\n";
1392  }
1393  }
1394  }
1395 
1396  if (any_mice) {
1397  _properties.set_raw_mice(true);
1398 
1399  } else if (!any_present) {
1400  x11display_cat.error() <<
1401  "Opening raw mice: files not found: /dev/input/event*\n";
1402 
1403  } else {
1404  x11display_cat.error() <<
1405  "Opening raw mice: no mouse devices detected in /dev/input/event*\n";
1406  }
1407 #else
1408  x11display_cat.error() <<
1409  "Opening raw mice: panda not compiled with raw mouse support.\n";
1410 #endif
1411 }
1412 
1413 /**
1414  * Generates a keystroke corresponding to the indicated X KeyPress event.
1415  */
1416 void x11GraphicsWindow::
1417 handle_keystroke(XKeyEvent &event) {
1418  if (!_dga_mouse_enabled) {
1419  _input->set_pointer_in_window(event.x, event.y);
1420  }
1421 
1422  if (_ic) {
1423  // First, get the keystroke as a wide-character sequence.
1424  static const int buffer_size = 256;
1425  wchar_t buffer[buffer_size];
1426  Status status;
1427  int len = XwcLookupString(_ic, &event, buffer, buffer_size, nullptr,
1428  &status);
1429  if (status == XBufferOverflow) {
1430  x11display_cat.error()
1431  << "Overflowed input buffer.\n";
1432  }
1433 
1434  // Now each of the returned wide characters represents a keystroke.
1435  for (int i = 0; i < len; i++) {
1436  _input->keystroke(buffer[i]);
1437  }
1438 
1439  } else {
1440  // Without an input context, just get the ascii keypress.
1441  ButtonHandle button = get_button(event, true);
1442  if (button.has_ascii_equivalent()) {
1443  _input->keystroke(button.get_ascii_equivalent());
1444  }
1445  }
1446 }
1447 
1448 /**
1449  * Generates a keypress corresponding to the indicated X KeyPress event.
1450  */
1451 void x11GraphicsWindow::
1452 handle_keypress(XKeyEvent &event) {
1453  if (!_dga_mouse_enabled) {
1454  _input->set_pointer_in_window(event.x, event.y);
1455  }
1456 
1457  // Now get the raw unshifted button.
1458  ButtonHandle button = get_button(event, false);
1459  if (button != ButtonHandle::none()) {
1460  if (button == KeyboardButton::lcontrol() || button == KeyboardButton::rcontrol()) {
1461  _input->button_down(KeyboardButton::control());
1462  }
1463  if (button == KeyboardButton::lshift() || button == KeyboardButton::rshift()) {
1464  _input->button_down(KeyboardButton::shift());
1465  }
1466  if (button == KeyboardButton::lalt() || button == KeyboardButton::ralt()) {
1467  _input->button_down(KeyboardButton::alt());
1468  }
1469  if (button == KeyboardButton::lmeta() || button == KeyboardButton::rmeta()) {
1470  _input->button_down(KeyboardButton::meta());
1471  }
1472  _input->button_down(button);
1473  }
1474 
1475  if (event.keycode >= 9 && event.keycode <= 135) {
1476  ButtonHandle raw_button = map_raw_button(event.keycode);
1477  if (raw_button != ButtonHandle::none()) {
1478  _input->raw_button_down(raw_button);
1479  }
1480  }
1481 }
1482 
1483 /**
1484  * Generates a keyrelease corresponding to the indicated X KeyRelease event.
1485  */
1486 void x11GraphicsWindow::
1487 handle_keyrelease(XKeyEvent &event) {
1488  if (!_dga_mouse_enabled) {
1489  _input->set_pointer_in_window(event.x, event.y);
1490  }
1491 
1492  // Now get the raw unshifted button.
1493  ButtonHandle button = get_button(event, false);
1494  if (button != ButtonHandle::none()) {
1495  if (button == KeyboardButton::lcontrol() || button == KeyboardButton::rcontrol()) {
1496  _input->button_up(KeyboardButton::control());
1497  }
1498  if (button == KeyboardButton::lshift() || button == KeyboardButton::rshift()) {
1499  _input->button_up(KeyboardButton::shift());
1500  }
1501  if (button == KeyboardButton::lalt() || button == KeyboardButton::ralt()) {
1502  _input->button_up(KeyboardButton::alt());
1503  }
1504  if (button == KeyboardButton::lmeta() || button == KeyboardButton::rmeta()) {
1505  _input->button_up(KeyboardButton::meta());
1506  }
1507  _input->button_up(button);
1508  }
1509 
1510  if (event.keycode >= 9 && event.keycode <= 135) {
1511  ButtonHandle raw_button = map_raw_button(event.keycode);
1512  if (raw_button != ButtonHandle::none()) {
1513  _input->raw_button_up(raw_button);
1514  }
1515  }
1516 }
1517 
1518 /**
1519  * Returns the Panda ButtonHandle corresponding to the keyboard button
1520  * indicated by the given key event.
1521  */
1522 ButtonHandle x11GraphicsWindow::
1523 get_button(XKeyEvent &key_event, bool allow_shift) {
1524  KeySym key = XLookupKeysym(&key_event, 0);
1525 
1526  if ((key_event.state & Mod2Mask) != 0) {
1527  // Mod2Mask corresponds to NumLock being in effect. In this case, we want
1528  // to get the alternate keysym associated with any keypad keys. Weird
1529  // system.
1530  KeySym k2;
1531  ButtonHandle button;
1532  switch (key) {
1533  case XK_KP_Space:
1534  case XK_KP_Tab:
1535  case XK_KP_Enter:
1536  case XK_KP_F1:
1537  case XK_KP_F2:
1538  case XK_KP_F3:
1539  case XK_KP_F4:
1540  case XK_KP_Equal:
1541  case XK_KP_Multiply:
1542  case XK_KP_Add:
1543  case XK_KP_Separator:
1544  case XK_KP_Subtract:
1545  case XK_KP_Divide:
1546  case XK_KP_Left:
1547  case XK_KP_Up:
1548  case XK_KP_Right:
1549  case XK_KP_Down:
1550  case XK_KP_Begin:
1551  case XK_KP_Prior:
1552  case XK_KP_Next:
1553  case XK_KP_Home:
1554  case XK_KP_End:
1555  case XK_KP_Insert:
1556  case XK_KP_Delete:
1557  case XK_KP_0:
1558  case XK_KP_1:
1559  case XK_KP_2:
1560  case XK_KP_3:
1561  case XK_KP_4:
1562  case XK_KP_5:
1563  case XK_KP_6:
1564  case XK_KP_7:
1565  case XK_KP_8:
1566  case XK_KP_9:
1567  k2 = XLookupKeysym(&key_event, 1);
1568  button = map_button(k2);
1569  if (button != ButtonHandle::none()) {
1570  return button;
1571  }
1572  // If that didn't produce a button we know, just fall through and handle
1573  // the normal, un-numlocked key.
1574  break;
1575 
1576  default:
1577  break;
1578  }
1579  }
1580 
1581  if (allow_shift) {
1582  // If shift is held down, get the shifted keysym.
1583  if ((key_event.state & ShiftMask) != 0) {
1584  KeySym k2 = XLookupKeysym(&key_event, 1);
1585  ButtonHandle button = map_button(k2);
1586  if (button != ButtonHandle::none()) {
1587  return button;
1588  }
1589  }
1590 
1591  // If caps lock is down, shift lowercase letters to uppercase. We can do
1592  // this in just the ASCII set, because we handle international keyboards
1593  // elsewhere (via an input context).
1594  if ((key_event.state & (ShiftMask | LockMask)) != 0) {
1595  if (key >= XK_a && key <= XK_z) {
1596  key += (XK_A - XK_a);
1597  }
1598  }
1599  }
1600 
1601  return map_button(key);
1602 }
1603 
1604 /**
1605  * Maps from a single X keysym to Panda's ButtonHandle. Called by
1606  * get_button(), above.
1607  */
1608 ButtonHandle x11GraphicsWindow::
1609 map_button(KeySym key) const {
1610  switch (key) {
1611  case NoSymbol:
1612  return ButtonHandle::none();
1613  case XK_BackSpace:
1614  return KeyboardButton::backspace();
1615  case XK_Tab:
1616  case XK_KP_Tab:
1617  return KeyboardButton::tab();
1618  case XK_Return:
1619  case XK_KP_Enter:
1620  return KeyboardButton::enter();
1621  case XK_Escape:
1622  return KeyboardButton::escape();
1623  case XK_KP_Space:
1624  case XK_space:
1625  return KeyboardButton::space();
1626  case XK_exclam:
1627  return KeyboardButton::ascii_key('!');
1628  case XK_quotedbl:
1629  return KeyboardButton::ascii_key('"');
1630  case XK_numbersign:
1631  return KeyboardButton::ascii_key('#');
1632  case XK_dollar:
1633  return KeyboardButton::ascii_key('$');
1634  case XK_percent:
1635  return KeyboardButton::ascii_key('%');
1636  case XK_ampersand:
1637  return KeyboardButton::ascii_key('&');
1638  case XK_apostrophe: // == XK_quoteright
1639  case XK_dead_acute: // on int'l keyboards
1640  return KeyboardButton::ascii_key('\'');
1641  case XK_parenleft:
1642  return KeyboardButton::ascii_key('(');
1643  case XK_parenright:
1644  return KeyboardButton::ascii_key(')');
1645  case XK_asterisk:
1646  case XK_KP_Multiply:
1647  return KeyboardButton::ascii_key('*');
1648  case XK_plus:
1649  case XK_KP_Add:
1650  return KeyboardButton::ascii_key('+');
1651  case XK_comma:
1652  case XK_KP_Separator:
1653  return KeyboardButton::ascii_key(',');
1654  case XK_minus:
1655  case XK_KP_Subtract:
1656  return KeyboardButton::ascii_key('-');
1657  case XK_period:
1658  case XK_KP_Decimal:
1659  return KeyboardButton::ascii_key('.');
1660  case XK_slash:
1661  case XK_KP_Divide:
1662  return KeyboardButton::ascii_key('/');
1663  case XK_0:
1664  case XK_KP_0:
1665  return KeyboardButton::ascii_key('0');
1666  case XK_1:
1667  case XK_KP_1:
1668  return KeyboardButton::ascii_key('1');
1669  case XK_2:
1670  case XK_KP_2:
1671  return KeyboardButton::ascii_key('2');
1672  case XK_3:
1673  case XK_KP_3:
1674  return KeyboardButton::ascii_key('3');
1675  case XK_4:
1676  case XK_KP_4:
1677  return KeyboardButton::ascii_key('4');
1678  case XK_5:
1679  case XK_KP_5:
1680  return KeyboardButton::ascii_key('5');
1681  case XK_6:
1682  case XK_KP_6:
1683  return KeyboardButton::ascii_key('6');
1684  case XK_7:
1685  case XK_KP_7:
1686  return KeyboardButton::ascii_key('7');
1687  case XK_8:
1688  case XK_KP_8:
1689  return KeyboardButton::ascii_key('8');
1690  case XK_9:
1691  case XK_KP_9:
1692  return KeyboardButton::ascii_key('9');
1693  case XK_colon:
1694  return KeyboardButton::ascii_key(':');
1695  case XK_semicolon:
1696  return KeyboardButton::ascii_key(';');
1697  case XK_less:
1698  return KeyboardButton::ascii_key('<');
1699  case XK_equal:
1700  case XK_KP_Equal:
1701  return KeyboardButton::ascii_key('=');
1702  case XK_greater:
1703  return KeyboardButton::ascii_key('>');
1704  case XK_question:
1705  return KeyboardButton::ascii_key('?');
1706  case XK_at:
1707  return KeyboardButton::ascii_key('@');
1708  case XK_A:
1709  return KeyboardButton::ascii_key('A');
1710  case XK_B:
1711  return KeyboardButton::ascii_key('B');
1712  case XK_C:
1713  return KeyboardButton::ascii_key('C');
1714  case XK_D:
1715  return KeyboardButton::ascii_key('D');
1716  case XK_E:
1717  return KeyboardButton::ascii_key('E');
1718  case XK_F:
1719  return KeyboardButton::ascii_key('F');
1720  case XK_G:
1721  return KeyboardButton::ascii_key('G');
1722  case XK_H:
1723  return KeyboardButton::ascii_key('H');
1724  case XK_I:
1725  return KeyboardButton::ascii_key('I');
1726  case XK_J:
1727  return KeyboardButton::ascii_key('J');
1728  case XK_K:
1729  return KeyboardButton::ascii_key('K');
1730  case XK_L:
1731  return KeyboardButton::ascii_key('L');
1732  case XK_M:
1733  return KeyboardButton::ascii_key('M');
1734  case XK_N:
1735  return KeyboardButton::ascii_key('N');
1736  case XK_O:
1737  return KeyboardButton::ascii_key('O');
1738  case XK_P:
1739  return KeyboardButton::ascii_key('P');
1740  case XK_Q:
1741  return KeyboardButton::ascii_key('Q');
1742  case XK_R:
1743  return KeyboardButton::ascii_key('R');
1744  case XK_S:
1745  return KeyboardButton::ascii_key('S');
1746  case XK_T:
1747  return KeyboardButton::ascii_key('T');
1748  case XK_U:
1749  return KeyboardButton::ascii_key('U');
1750  case XK_V:
1751  return KeyboardButton::ascii_key('V');
1752  case XK_W:
1753  return KeyboardButton::ascii_key('W');
1754  case XK_X:
1755  return KeyboardButton::ascii_key('X');
1756  case XK_Y:
1757  return KeyboardButton::ascii_key('Y');
1758  case XK_Z:
1759  return KeyboardButton::ascii_key('Z');
1760  case XK_bracketleft:
1761  return KeyboardButton::ascii_key('[');
1762  case XK_backslash:
1763  return KeyboardButton::ascii_key('\\');
1764  case XK_bracketright:
1765  return KeyboardButton::ascii_key(']');
1766  case XK_asciicircum:
1767  return KeyboardButton::ascii_key('^');
1768  case XK_underscore:
1769  return KeyboardButton::ascii_key('_');
1770  case XK_grave: // == XK_quoteleft
1771  case XK_dead_grave: // on int'l keyboards
1772  return KeyboardButton::ascii_key('`');
1773  case XK_a:
1774  return KeyboardButton::ascii_key('a');
1775  case XK_b:
1776  return KeyboardButton::ascii_key('b');
1777  case XK_c:
1778  return KeyboardButton::ascii_key('c');
1779  case XK_d:
1780  return KeyboardButton::ascii_key('d');
1781  case XK_e:
1782  return KeyboardButton::ascii_key('e');
1783  case XK_f:
1784  return KeyboardButton::ascii_key('f');
1785  case XK_g:
1786  return KeyboardButton::ascii_key('g');
1787  case XK_h:
1788  return KeyboardButton::ascii_key('h');
1789  case XK_i:
1790  return KeyboardButton::ascii_key('i');
1791  case XK_j:
1792  return KeyboardButton::ascii_key('j');
1793  case XK_k:
1794  return KeyboardButton::ascii_key('k');
1795  case XK_l:
1796  return KeyboardButton::ascii_key('l');
1797  case XK_m:
1798  return KeyboardButton::ascii_key('m');
1799  case XK_n:
1800  return KeyboardButton::ascii_key('n');
1801  case XK_o:
1802  return KeyboardButton::ascii_key('o');
1803  case XK_p:
1804  return KeyboardButton::ascii_key('p');
1805  case XK_q:
1806  return KeyboardButton::ascii_key('q');
1807  case XK_r:
1808  return KeyboardButton::ascii_key('r');
1809  case XK_s:
1810  return KeyboardButton::ascii_key('s');
1811  case XK_t:
1812  return KeyboardButton::ascii_key('t');
1813  case XK_u:
1814  return KeyboardButton::ascii_key('u');
1815  case XK_v:
1816  return KeyboardButton::ascii_key('v');
1817  case XK_w:
1818  return KeyboardButton::ascii_key('w');
1819  case XK_x:
1820  return KeyboardButton::ascii_key('x');
1821  case XK_y:
1822  return KeyboardButton::ascii_key('y');
1823  case XK_z:
1824  return KeyboardButton::ascii_key('z');
1825  case XK_braceleft:
1826  return KeyboardButton::ascii_key('{');
1827  case XK_bar:
1828  return KeyboardButton::ascii_key('|');
1829  case XK_braceright:
1830  return KeyboardButton::ascii_key('}');
1831  case XK_asciitilde:
1832  return KeyboardButton::ascii_key('~');
1833  case XK_F1:
1834  case XK_KP_F1:
1835  return KeyboardButton::f1();
1836  case XK_F2:
1837  case XK_KP_F2:
1838  return KeyboardButton::f2();
1839  case XK_F3:
1840  case XK_KP_F3:
1841  return KeyboardButton::f3();
1842  case XK_F4:
1843  case XK_KP_F4:
1844  return KeyboardButton::f4();
1845  case XK_F5:
1846  return KeyboardButton::f5();
1847  case XK_F6:
1848  return KeyboardButton::f6();
1849  case XK_F7:
1850  return KeyboardButton::f7();
1851  case XK_F8:
1852  return KeyboardButton::f8();
1853  case XK_F9:
1854  return KeyboardButton::f9();
1855  case XK_F10:
1856  return KeyboardButton::f10();
1857  case XK_F11:
1858  return KeyboardButton::f11();
1859  case XK_F12:
1860  return KeyboardButton::f12();
1861  case XK_KP_Left:
1862  case XK_Left:
1863  return KeyboardButton::left();
1864  case XK_KP_Up:
1865  case XK_Up:
1866  return KeyboardButton::up();
1867  case XK_KP_Right:
1868  case XK_Right:
1869  return KeyboardButton::right();
1870  case XK_KP_Down:
1871  case XK_Down:
1872  return KeyboardButton::down();
1873  case XK_KP_Prior:
1874  case XK_Prior:
1875  return KeyboardButton::page_up();
1876  case XK_KP_Next:
1877  case XK_Next:
1878  return KeyboardButton::page_down();
1879  case XK_KP_Home:
1880  case XK_Home:
1881  return KeyboardButton::home();
1882  case XK_KP_End:
1883  case XK_End:
1884  return KeyboardButton::end();
1885  case XK_KP_Insert:
1886  case XK_Insert:
1887  return KeyboardButton::insert();
1888  case XK_KP_Delete:
1889  case XK_Delete:
1890  return KeyboardButton::del();
1891  case XK_Num_Lock:
1892  return KeyboardButton::num_lock();
1893  case XK_Scroll_Lock:
1894  return KeyboardButton::scroll_lock();
1895  case XK_Print:
1896  return KeyboardButton::print_screen();
1897  case XK_Pause:
1898  return KeyboardButton::pause();
1899  case XK_Menu:
1900  return KeyboardButton::menu();
1901  case XK_Shift_L:
1902  return KeyboardButton::lshift();
1903  case XK_Shift_R:
1904  return KeyboardButton::rshift();
1905  case XK_Control_L:
1906  return KeyboardButton::lcontrol();
1907  case XK_Control_R:
1908  return KeyboardButton::rcontrol();
1909  case XK_Alt_L:
1910  return KeyboardButton::lalt();
1911  case XK_Alt_R:
1912  return KeyboardButton::ralt();
1913  case XK_Meta_L:
1914  case XK_Super_L:
1915  return KeyboardButton::lmeta();
1916  case XK_Meta_R:
1917  case XK_Super_R:
1918  return KeyboardButton::rmeta();
1919  case XK_Caps_Lock:
1920  return KeyboardButton::caps_lock();
1921  case XK_Shift_Lock:
1922  return KeyboardButton::shift_lock();
1923  }
1924  if (x11display_cat.is_debug()) {
1925  x11display_cat.debug()
1926  << "Unrecognized keysym 0x" << std::hex << key << std::dec << "\n";
1927  }
1928  return ButtonHandle::none();
1929 }
1930 
1931 /**
1932  * Maps from a single X keycode to Panda's ButtonHandle.
1933  */
1934 ButtonHandle x11GraphicsWindow::
1935 map_raw_button(KeyCode key) const {
1936 #ifdef PHAVE_LINUX_INPUT_H
1937  // Most X11 servers are configured to use the evdev driver, which
1938  // adds 8 to the underlying evdev keycodes (not sure why).
1939  // In any case, this means we can use the same mapping as our raw
1940  // input code, which uses evdev directly.
1941  int index = key - 8;
1942  if (index >= 0) {
1943  return EvdevInputDevice::map_button(index);
1944  }
1945 #endif
1946  return ButtonHandle::none();
1947 }
1948 
1949 /**
1950  * Returns the Panda ButtonHandle corresponding to the mouse button indicated
1951  * by the given button event.
1952  */
1953 ButtonHandle x11GraphicsWindow::
1954 get_mouse_button(XButtonEvent &button_event) {
1955  int index = button_event.button;
1956  if (index == x_wheel_up_button) {
1957  return MouseButton::wheel_up();
1958  } else if (index == x_wheel_down_button) {
1959  return MouseButton::wheel_down();
1960  } else if (index == x_wheel_left_button) {
1961  return MouseButton::wheel_left();
1962  } else if (index == x_wheel_right_button) {
1963  return MouseButton::wheel_right();
1964  } else {
1965  return MouseButton::button(index - 1);
1966  }
1967 }
1968 
1969 /**
1970  * Returns a ButtonMap containing the association between raw buttons and
1971  * virtual buttons.
1972  */
1973 ButtonMap *x11GraphicsWindow::
1974 get_keyboard_map() const {
1975  // NB. This could be improved by using the Xkb API. XkbDescPtr desc =
1976  // XkbGetMap(_display, XkbAllMapComponentsMask, XkbUseCoreKbd);
1977  ButtonMap *map = new ButtonMap;
1978 
1979  for (int k = 9; k <= 135; ++k) {
1980  ButtonHandle raw_button = map_raw_button(k);
1981  if (raw_button == ButtonHandle::none()) {
1982  continue;
1983  }
1984 
1985  KeySym sym = XkbKeycodeToKeysym(_display, k, 0, 0);
1986  ButtonHandle button = map_button(sym);
1987  if (button == ButtonHandle::none()) {
1988  continue;
1989  }
1990 
1991  map->map_button(raw_button, button);
1992  }
1993 
1994  return map;
1995 }
1996 
1997 /**
1998  * This function is used as a predicate to XCheckIfEvent() to determine if the
1999  * indicated queued X event is relevant and should be returned to this window.
2000  */
2001 Bool x11GraphicsWindow::
2002 check_event(X11_Display *display, XEvent *event, char *arg) {
2003  const x11GraphicsWindow *self = (x11GraphicsWindow *)arg;
2004 
2005  // We accept any event that is sent to our window.
2006  return (event->xany.window == self->_xwindow);
2007 }
2008 
2009 /**
2010  * Loads and returns a Cursor corresponding to the indicated filename. If the
2011  * file cannot be loaded, returns None.
2012  */
2013 X11_Cursor x11GraphicsWindow::
2014 get_cursor(const Filename &filename) {
2015  x11GraphicsPipe *x11_pipe;
2016  DCAST_INTO_R(x11_pipe, _pipe, None);
2017 
2018  if (x11_pipe->_xcursor_size == -1) {
2019  x11display_cat.info()
2020  << "libXcursor.so.1 not available; cannot change mouse cursor.\n";
2021  return None;
2022  }
2023 
2024  // First, look for the unresolved filename in our index.
2025  pmap<Filename, X11_Cursor>::iterator fi = _cursor_filenames.find(filename);
2026  if (fi != _cursor_filenames.end()) {
2027  return fi->second;
2028  }
2029 
2030  // If it wasn't found, resolve the filename and search for that.
2032  Filename resolved (filename);
2033  if (!vfs->resolve_filename(resolved, get_model_path())) {
2034  // The filename doesn't exist.
2035  x11display_cat.warning()
2036  << "Could not find cursor filename " << filename << "\n";
2037  return None;
2038  }
2039  fi = _cursor_filenames.find(resolved);
2040  if (fi != _cursor_filenames.end()) {
2041  return fi->second;
2042  }
2043 
2044  // Open the file through the virtual file system.
2045  istream *str = vfs->open_read_file(resolved, true);
2046  if (str == nullptr) {
2047  x11display_cat.warning()
2048  << "Could not open cursor file " << filename << "\n";
2049  return None;
2050  }
2051 
2052  // Check the first four bytes to see what kind of file it is.
2053  char magic[4];
2054  str->read(magic, 4);
2055  if (!str->good()) {
2056  x11display_cat.warning()
2057  << "Could not read from cursor file " << filename << "\n";
2058  return None;
2059  }
2060 
2061  // Put back the read bytes. Do not use seekg, because this will
2062  // corrupt the stream if it points to encrypted/compressed file
2063  str->putback(magic[3]);
2064  str->putback(magic[2]);
2065  str->putback(magic[1]);
2066  str->putback(magic[0]);
2067 
2068  X11_Cursor h = None;
2069  if (memcmp(magic, "Xcur", 4) == 0) {
2070  // X11 cursor.
2071  x11display_cat.debug()
2072  << "Loading X11 cursor " << filename << "\n";
2073  XcursorFile xcfile;
2074  xcfile.closure = str;
2075  xcfile.read = &xcursor_read;
2076  xcfile.write = &xcursor_write;
2077  xcfile.seek = &xcursor_seek;
2078 
2079  XcursorImages *images = x11_pipe->_XcursorXcFileLoadImages(&xcfile, x11_pipe->_xcursor_size);
2080  if (images != nullptr) {
2081  h = x11_pipe->_XcursorImagesLoadCursor(_display, images);
2082  x11_pipe->_XcursorImagesDestroy(images);
2083  }
2084 
2085  } else if (memcmp(magic, "\0\0\1\0", 4) == 0
2086  || memcmp(magic, "\0\0\2\0", 4) == 0) {
2087  // Windows .ico or .cur file.
2088  x11display_cat.debug()
2089  << "Loading Windows cursor " << filename << "\n";
2090  h = read_ico(*str);
2091  }
2092 
2093  // Delete the istream.
2094  vfs->close_read_file(str);
2095 
2096  if (h == None) {
2097  x11display_cat.warning()
2098  << "X11 cursor filename '" << resolved << "' could not be loaded!\n";
2099  }
2100 
2101  _cursor_filenames[resolved] = h;
2102  return h;
2103 }
2104 
2105 /**
2106  * Reads a Windows .ico or .cur file from the indicated stream and returns it
2107  * as an X11 Cursor. If the file cannot be loaded, returns None.
2108  */
2109 X11_Cursor x11GraphicsWindow::
2110 read_ico(istream &ico) {
2111  x11GraphicsPipe *x11_pipe;
2112  DCAST_INTO_R(x11_pipe, _pipe, None);
2113 
2114  // Local structs, this is just POD, make input easier
2115  typedef struct {
2116  uint16_t reserved, type, count;
2117  } IcoHeader;
2118 
2119  typedef struct {
2120  uint8_t width, height, colorCount, reserved;
2121  uint16_t xhot, yhot;
2122  uint32_t bitmapSize, offset;
2123  } IcoEntry;
2124 
2125  typedef struct {
2126  uint32_t headerSize, width, height;
2127  uint16_t planes, bitsPerPixel;
2128  uint32_t compression, imageSize, xPixelsPerM, yPixelsPerM, colorsUsed, colorsImportant;
2129  } IcoInfoHeader;
2130 
2131  typedef struct {
2132  uint8_t blue, green, red, reserved;
2133  } IcoColor;
2134 
2135  int i, entry = 0;
2136  unsigned int j, k, mask, shift;
2137  size_t colorCount, bitsPerPixel;
2138  IcoHeader header;
2139  IcoInfoHeader infoHeader;
2140  IcoEntry *entries = nullptr;
2141  IcoColor color, *palette = nullptr;
2142 
2143  size_t xorBmpSize, andBmpSize;
2144  char *curXor, *curAnd;
2145  char *xorBmp = nullptr, *andBmp = nullptr;
2146  XcursorImage *image = nullptr;
2147  X11_Cursor ret = None;
2148 
2149  int def_size = x11_pipe->_xcursor_size;
2150 
2151  // Get our header, note that ICO = type 1 and CUR = type 2.
2152  ico.read(reinterpret_cast<char *>(&header), sizeof(IcoHeader));
2153  if (!ico.good()) goto cleanup;
2154  if (header.type != 1 && header.type != 2) goto cleanup;
2155  if (header.count < 1) goto cleanup;
2156 
2157  // Read the entry table into memory, select the largest entry.
2158  entries = new IcoEntry[header.count];
2159  ico.read(reinterpret_cast<char *>(entries), header.count * sizeof(IcoEntry));
2160  if (!ico.good()) goto cleanup;
2161  for (i = 1; i < header.count; i++) {
2162  if (entries[i].width == def_size && entries[i].height == def_size) {
2163  // Wait, this is the default cursor size. This is perfect.
2164  entry = i;
2165  break;
2166  }
2167  if (entries[i].width > entries[entry].width ||
2168  entries[i].height > entries[entry].height)
2169  entry = i;
2170  }
2171 
2172  // Seek to the image in the ICO.
2173  ico.seekg(entries[entry].offset);
2174  if (!ico.good()) goto cleanup;
2175 
2176  if (ico.peek() == 0x89) {
2177  // Hang on, this is actually a PNG header.
2178  PNMImage img;
2180  if (!img.read(ico, "", reg->get_type_from_extension("png"))) {
2181  goto cleanup;
2182  }
2183  img.set_maxval(255);
2184 
2185  image = x11_pipe->_XcursorImageCreate(img.get_x_size(), img.get_y_size());
2186 
2187  xel *ptr = img.get_array();
2188  xelval *alpha = img.get_alpha_array();
2189  size_t num_pixels = (size_t)img.get_x_size() * (size_t)img.get_y_size();
2190  unsigned int *dest = image->pixels;
2191 
2192  if (alpha != nullptr) {
2193  for (size_t p = 0; p < num_pixels; ++p) {
2194  *dest++ = (*alpha << 24U) | (ptr->r << 16U) | (ptr->g << 8U) | (ptr->b);
2195  ++ptr;
2196  ++alpha;
2197  }
2198  } else {
2199  for (size_t p = 0; p < num_pixels; ++p) {
2200  *dest++ = 0xff000000U | (ptr->r << 16U) | (ptr->g << 8U) | (ptr->b);
2201  ++ptr;
2202  }
2203  }
2204 
2205  } else {
2206  ico.read(reinterpret_cast<char *>(&infoHeader), sizeof(IcoInfoHeader));
2207  if (!ico.good()) goto cleanup;
2208  bitsPerPixel = infoHeader.bitsPerPixel;
2209 
2210  if (infoHeader.compression != 0) goto cleanup;
2211 
2212  // Load the color palette, if one exists.
2213  if (bitsPerPixel != 24 && bitsPerPixel != 32) {
2214  colorCount = 1 << bitsPerPixel;
2215  palette = new IcoColor[colorCount];
2216  ico.read(reinterpret_cast<char *>(palette), colorCount * sizeof(IcoColor));
2217  if (!ico.good()) goto cleanup;
2218  }
2219 
2220  int and_stride = ((infoHeader.width >> 3) + 3) & ~0x03;
2221 
2222  // Read in the pixel data.
2223  xorBmpSize = (infoHeader.width * (infoHeader.height / 2) * bitsPerPixel) / 8;
2224  andBmpSize = and_stride * (infoHeader.height / 2);
2225  curXor = xorBmp = new char[xorBmpSize];
2226  curAnd = andBmp = new char[andBmpSize];
2227  ico.read(xorBmp, xorBmpSize);
2228  if (!ico.good()) goto cleanup;
2229  ico.read(andBmp, andBmpSize);
2230  if (!ico.good()) goto cleanup;
2231 
2232  image = x11_pipe->_XcursorImageCreate(infoHeader.width, infoHeader.height / 2);
2233 
2234  // Support all the formats that GIMP supports.
2235  switch (bitsPerPixel) {
2236  case 1:
2237  case 4:
2238  case 8:
2239  // For colors less that a byte wide, shift and mask the palette indices
2240  // off each element of the xorBmp and append them to the image.
2241  mask = ((1 << bitsPerPixel) - 1);
2242  for (i = image->height - 1; i >= 0; i--) {
2243  for (j = 0; j < image->width; j += 8 / bitsPerPixel) {
2244  for (k = 0; k < 8 / bitsPerPixel; k++) {
2245  shift = 8 - ((k + 1) * bitsPerPixel);
2246  color = palette[(*curXor & (mask << shift)) >> shift];
2247  image->pixels[(i * image->width) + j + k] = (color.red << 16) +
2248  (color.green << 8) +
2249  (color.blue);
2250  }
2251 
2252  curXor++;
2253  }
2254 
2255  // Set the alpha byte properly according to the andBmp.
2256  for (j = 0; j < image->width; j += 8) {
2257  for (k = 0; k < 8; k++) {
2258  shift = 7 - k;
2259  image->pixels[(i * image->width) + j + k] |=
2260  ((*curAnd & (1 << shift)) >> shift) ? 0x0 : (0xff << 24);
2261  }
2262 
2263  curAnd++;
2264  }
2265  }
2266  break;
2267 
2268  case 24:
2269  // Pack each of the three bytes into a single color, BGR -> 0RGB
2270  for (i = image->height - 1; i >= 0; i--) {
2271  for (j = 0; j < image->width; j++) {
2272  shift = 7 - (j & 0x7);
2273  uint32_t alpha = (curAnd[j >> 3] & (1 << shift)) ? 0 : 0xff000000U;
2274  image->pixels[(i * image->width) + j] = (uint8_t)curXor[0]
2275  | ((uint8_t)curXor[1] << 8u)
2276  | ((uint8_t)curXor[2] << 16u)
2277  | alpha;
2278  curXor += 3;
2279  }
2280  curAnd += and_stride;
2281  }
2282  break;
2283 
2284  case 32:
2285  // Pack each of the four bytes into a single color, BGRA -> ARGB
2286  for (i = image->height - 1; i >= 0; i--) {
2287  for (j = 0; j < image->width; j++) {
2288  image->pixels[(i * image->width) + j] = (*(curXor + 3) << 24) +
2289  (*(curXor + 2) << 16) +
2290  (*(curXor + 1) << 8) +
2291  (*curXor);
2292  curXor += 4;
2293  }
2294  }
2295  break;
2296 
2297  default:
2298  goto cleanup;
2299  }
2300  }
2301 
2302  // If this is an actual CUR not an ICO set up the hotspot properly.
2303  if (header.type == 2) {
2304  image->xhot = entries[entry].xhot;
2305  image->yhot = entries[entry].yhot;
2306  } else {
2307  image->xhot = 0;
2308  image->yhot = 0;
2309  }
2310 
2311  ret = x11_pipe->_XcursorImageLoadCursor(_display, image);
2312 
2313 cleanup:
2314  x11_pipe->_XcursorImageDestroy(image);
2315  delete[] entries;
2316  delete[] palette;
2317  delete[] xorBmp;
2318  delete[] andBmp;
2319 
2320  return ret;
2321 }
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.
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.