Panda3D
 All Classes Functions Variables Enumerations
tinyXGraphicsWindow.cxx
1 // Filename: tinyXGraphicsWindow.cxx
2 // Created by: drose (03May08)
3 //
4 ////////////////////////////////////////////////////////////////////
5 //
6 // PANDA 3D SOFTWARE
7 // Copyright (c) Carnegie Mellon University. All rights reserved.
8 //
9 // All use of this software is subject to the terms of the revised BSD
10 // license. You should have received a copy of this license along
11 // with this source code in a file named "LICENSE."
12 //
13 ////////////////////////////////////////////////////////////////////
14 
15 #include "pandabase.h"
16 
17 #ifdef HAVE_X11
18 
19 #include "tinyXGraphicsWindow.h"
20 #include "tinyGraphicsStateGuardian.h"
21 #include "tinyXGraphicsPipe.h"
22 #include "config_tinydisplay.h"
23 
24 #include "graphicsPipe.h"
25 #include "keyboardButton.h"
26 #include "mouseButton.h"
27 #include "clockObject.h"
28 #include "pStatTimer.h"
29 #include "textEncoder.h"
30 #include "throw_event.h"
31 #include "lightReMutexHolder.h"
32 #include "nativeWindowHandle.h"
33 
34 TypeHandle TinyXGraphicsWindow::_type_handle;
35 
36 ////////////////////////////////////////////////////////////////////
37 // Function: TinyXGraphicsWindow::Constructor
38 // Access: Public
39 // Description:
40 ////////////////////////////////////////////////////////////////////
41 TinyXGraphicsWindow::
42 TinyXGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe,
43  const string &name,
44  const FrameBufferProperties &fb_prop,
45  const WindowProperties &win_prop,
46  int flags,
48  GraphicsOutput *host) :
49  x11GraphicsWindow(engine, pipe, name, fb_prop, win_prop, flags, gsg, host)
50 {
51  _gc = (GC)NULL;
52 
53  _reduced_frame_buffer = NULL;
54  _full_frame_buffer = NULL;
55  _ximage = NULL;
56  update_pixel_factor();
57 }
58 
59 ////////////////////////////////////////////////////////////////////
60 // Function: TinyXGraphicsWindow::Destructor
61 // Access: Public, Virtual
62 // Description:
63 ////////////////////////////////////////////////////////////////////
64 TinyXGraphicsWindow::
65 ~TinyXGraphicsWindow() {
66  if (_gc != NULL && _display != NULL) {
67  XFreeGC(_display, _gc);
68  }
69  if (_ximage != NULL) {
70  PANDA_FREE_ARRAY(_ximage->data);
71  _ximage->data = NULL;
72  XDestroyImage(_ximage);
73  }
74 }
75 
76 ////////////////////////////////////////////////////////////////////
77 // Function: TinyXGraphicsWindow::begin_frame
78 // Access: Public, Virtual
79 // Description: This function will be called within the draw thread
80 // before beginning rendering for a given frame. It
81 // should do whatever setup is required, and return true
82 // if the frame should be rendered, or false if it
83 // should be skipped.
84 ////////////////////////////////////////////////////////////////////
85 bool TinyXGraphicsWindow::
86 begin_frame(FrameMode mode, Thread *current_thread) {
87  PStatTimer timer(_make_current_pcollector, current_thread);
88 
89  if (_xwindow == (X11_Window)NULL) {
90  return false;
91  }
92 
93  begin_frame_spam(mode);
94  if (_gsg == (GraphicsStateGuardian *)NULL) {
95  return false;
96  }
97  if (_awaiting_configure) {
98  // Don't attempt to draw while we have just reconfigured the
99  // window and we haven't got the notification back yet.
100  return false;
101  }
102 
103  TinyGraphicsStateGuardian *tinygsg;
104  DCAST_INTO_R(tinygsg, _gsg, false);
105 
106  if (_reduced_frame_buffer != (ZBuffer *)NULL) {
107  tinygsg->_current_frame_buffer = _reduced_frame_buffer;
108  } else {
109  tinygsg->_current_frame_buffer = _full_frame_buffer;
110  }
111  tinygsg->reset_if_new();
112 
113  _gsg->set_current_properties(&get_fb_properties());
114  return _gsg->begin_frame(current_thread);
115 }
116 
117 ////////////////////////////////////////////////////////////////////
118 // Function: TinyXGraphicsWindow::end_frame
119 // Access: Public, Virtual
120 // Description: This function will be called within the draw thread
121 // after rendering is completed for a given frame. It
122 // should do whatever finalization is required.
123 ////////////////////////////////////////////////////////////////////
124 void TinyXGraphicsWindow::
125 end_frame(FrameMode mode, Thread *current_thread) {
126  end_frame_spam(mode);
127  nassertv(_gsg != (GraphicsStateGuardian *)NULL);
128 
129  if (mode == FM_render) {
130  // end_render_texture();
131  copy_to_textures();
132  }
133 
134  _gsg->end_frame(current_thread);
135 
136  if (mode == FM_render) {
137  trigger_flip();
138  clear_cube_map_selection();
139  }
140 }
141 
142 ////////////////////////////////////////////////////////////////////
143 // Function: TinyXGraphicsWindow::end_flip
144 // Access: Public, Virtual
145 // Description: This function will be called within the draw thread
146 // after begin_flip() has been called on all windows, to
147 // finish the exchange of the front and back buffers.
148 //
149 // This should cause the window to wait for the flip, if
150 // necessary.
151 ////////////////////////////////////////////////////////////////////
152 void TinyXGraphicsWindow::
153 end_flip() {
154  if (_xwindow == (X11_Window)NULL || !_flip_ready) {
156  return;
157  }
158 
159  if (_reduced_frame_buffer != (ZBuffer *)NULL) {
160  // Zoom the reduced buffer onto the full buffer.
161  ZB_zoomFrameBuffer(_full_frame_buffer, 0, 0,
162  _full_frame_buffer->xsize, _full_frame_buffer->ysize,
163  _reduced_frame_buffer, 0, 0,
164  _reduced_frame_buffer->xsize, _reduced_frame_buffer->ysize);
165  }
166 
167  // We can't just point the XPutImage directly at our own framebuffer
168  // data, even if the bytes_per_pixel matches, because some X
169  // displays will respect the alpha channel and make the window
170  // transparent there. We don't want transparent windows where the
171  // alpha data happens to less than 1.0.
172  ZB_copyFrameBufferNoAlpha(_full_frame_buffer, _ximage->data, _pitch);
173 
174  XPutImage(_display, _xwindow, _gc, _ximage, 0, 0, 0, 0,
175  _full_frame_buffer->xsize, _full_frame_buffer->ysize);
176  XFlush(_display);
178 }
179 
180 ////////////////////////////////////////////////////////////////////
181 // Function: TinyXGraphicsWindow::supports_pixel_zoom
182 // Access: Published, Virtual
183 // Description: Returns true if a call to set_pixel_zoom() will be
184 // respected, false if it will be ignored. If this
185 // returns false, then get_pixel_factor() will always
186 // return 1.0, regardless of what value you specify for
187 // set_pixel_zoom().
188 //
189 // This may return false if the underlying renderer
190 // doesn't support pixel zooming, or if you have called
191 // this on a DisplayRegion that doesn't have both
192 // set_clear_color() and set_clear_depth() enabled.
193 ////////////////////////////////////////////////////////////////////
194 bool TinyXGraphicsWindow::
195 supports_pixel_zoom() const {
196  return true;
197 }
198 
199 ////////////////////////////////////////////////////////////////////
200 // Function: TinyXGraphicsWindow::process_events
201 // Access: Public, Virtual
202 // Description: Do whatever processing is necessary to ensure that
203 // the window responds to user events. Also, honor any
204 // requests recently made via request_properties()
205 //
206 // This function is called only within the window
207 // thread.
208 ////////////////////////////////////////////////////////////////////
209 void TinyXGraphicsWindow::
210 process_events() {
211  LightReMutexHolder holder(TinyXGraphicsPipe::_x_mutex);
212 
214 
215  if (_xwindow == (X11_Window)0) {
216  return;
217  }
218 
219  poll_raw_mice();
220 
221  XEvent event;
222  XKeyEvent keyrelease_event;
223  bool got_keyrelease_event = false;
224 
225  while (XCheckIfEvent(_display, &event, check_event, (char *)this)) {
226  if (XFilterEvent(&event, None)) {
227  continue;
228  }
229 
230  if (got_keyrelease_event) {
231  // If a keyrelease event is immediately followed by a matching
232  // keypress event, that's just key repeat and we should treat
233  // the two events accordingly. It would be nice if X provided a
234  // way to differentiate between keyrepeat and explicit
235  // keypresses more generally.
236  got_keyrelease_event = false;
237 
238  if (event.type == KeyPress &&
239  event.xkey.keycode == keyrelease_event.keycode &&
240  (event.xkey.time - keyrelease_event.time <= 1)) {
241  // In particular, we only generate down messages for the
242  // repeated keys, not down-and-up messages.
243  handle_keystroke(event.xkey);
244 
245  // We thought about not generating the keypress event, but we
246  // need that repeat for backspace. Rethink later.
247  handle_keypress(event.xkey);
248  continue;
249 
250  } else {
251  // This keyrelease event is not immediately followed by a
252  // matching keypress event, so it's a genuine release.
253  handle_keyrelease(keyrelease_event);
254  }
255  }
256 
257  WindowProperties properties;
258  ButtonHandle button;
259 
260  switch (event.type) {
261  case ReparentNotify:
262  break;
263 
264  case ConfigureNotify:
265  _awaiting_configure = false;
266  if (_properties.get_fixed_size()) {
267  // If the window properties indicate a fixed size only, undo
268  // any attempt by the user to change them. In X, there
269  // doesn't appear to be a way to universally disallow this
270  // directly (although we do set the min_size and max_size to
271  // the same value, which seems to work for most window
272  // managers.)
273  WindowProperties current_props = get_properties();
274  if (event.xconfigure.width != current_props.get_x_size() ||
275  event.xconfigure.height != current_props.get_y_size()) {
276  XWindowChanges changes;
277  changes.width = current_props.get_x_size();
278  changes.height = current_props.get_y_size();
279  int value_mask = (CWWidth | CWHeight);
280  XConfigureWindow(_display, _xwindow, value_mask, &changes);
281  }
282 
283  } else {
284  // A normal window may be resized by the user at will.
285  properties.set_size(event.xconfigure.width, event.xconfigure.height);
286  system_changed_properties(properties);
287  ZB_resize(_full_frame_buffer, NULL, _properties.get_x_size(), _properties.get_y_size());
288  _pitch = (_full_frame_buffer->xsize * _bytes_per_pixel + 3) & ~3;
289  create_reduced_frame_buffer();
290  create_ximage();
291  }
292  break;
293 
294  case ButtonPress:
295  // This refers to the mouse buttons.
296  button = get_mouse_button(event.xbutton);
297  _input_devices[0].set_pointer_in_window(event.xbutton.x, event.xbutton.y);
298  _input_devices[0].button_down(button);
299  break;
300 
301  case ButtonRelease:
302  button = get_mouse_button(event.xbutton);
303  _input_devices[0].set_pointer_in_window(event.xbutton.x, event.xbutton.y);
304  _input_devices[0].button_up(button);
305  break;
306 
307  case MotionNotify:
308  _input_devices[0].set_pointer_in_window(event.xmotion.x, event.xmotion.y);
309  break;
310 
311  case KeyPress:
312  handle_keystroke(event.xkey);
313  handle_keypress(event.xkey);
314  break;
315 
316  case KeyRelease:
317  // The KeyRelease can't be processed immediately, because we
318  // have to check first if it's immediately followed by a
319  // matching KeyPress event.
320  keyrelease_event = event.xkey;
321  got_keyrelease_event = true;
322  break;
323 
324  case EnterNotify:
325  _input_devices[0].set_pointer_in_window(event.xcrossing.x, event.xcrossing.y);
326  break;
327 
328  case LeaveNotify:
329  _input_devices[0].set_pointer_out_of_window();
330  break;
331 
332  case FocusIn:
333  properties.set_foreground(true);
334  system_changed_properties(properties);
335  break;
336 
337  case FocusOut:
338  _input_devices[0].focus_lost();
339  properties.set_foreground(false);
340  system_changed_properties(properties);
341  break;
342 
343  case UnmapNotify:
344  properties.set_minimized(true);
345  system_changed_properties(properties);
346  break;
347 
348  case MapNotify:
349  properties.set_minimized(false);
350  system_changed_properties(properties);
351 
352  // Auto-focus the window when it is mapped.
353  XSetInputFocus(_display, _xwindow, RevertToPointerRoot, CurrentTime);
354  break;
355 
356  case ClientMessage:
357  if ((Atom)(event.xclient.data.l[0]) == _wm_delete_window) {
358  // This is a message from the window manager indicating that
359  // the user has requested to close the window.
360  string close_request_event = get_close_request_event();
361  if (!close_request_event.empty()) {
362  // In this case, the app has indicated a desire to intercept
363  // the request and process it directly.
364  throw_event(close_request_event);
365 
366  } else {
367  // In this case, the default case, the app does not intend
368  // to service the request, so we do by closing the window.
369 
370  // TODO: don't release the gsg in the window thread.
371  close_window();
372  properties.set_open(false);
373  system_changed_properties(properties);
374  }
375  }
376  break;
377 
378  case DestroyNotify:
379  // Apparently, we never get a DestroyNotify on a toplevel
380  // window. Instead, we rely on hints from the window manager
381  // (see above).
382  tinydisplay_cat.info()
383  << "DestroyNotify\n";
384  break;
385 
386  default:
387  tinydisplay_cat.error()
388  << "unhandled X event type " << event.type << "\n";
389  }
390  }
391 
392  if (got_keyrelease_event) {
393  // This keyrelease event is not immediately followed by a
394  // matching keypress event, so it's a genuine release.
395  handle_keyrelease(keyrelease_event);
396  }
397 }
398 
399 ////////////////////////////////////////////////////////////////////
400 // Function: TinyXGraphicsWindow::close_window
401 // Access: Protected, Virtual
402 // Description: Closes the window right now. Called from the window
403 // thread.
404 ////////////////////////////////////////////////////////////////////
405 void TinyXGraphicsWindow::
406 close_window() {
407  if (_gsg != (GraphicsStateGuardian *)NULL) {
408  TinyGraphicsStateGuardian *tinygsg;
409  DCAST_INTO_V(tinygsg, _gsg);
410  tinygsg->_current_frame_buffer = NULL;
411  _gsg.clear();
412  }
413 
414  x11GraphicsWindow::close_window();
415 }
416 
417 ////////////////////////////////////////////////////////////////////
418 // Function: TinyXGraphicsWindow::open_window
419 // Access: Protected, Virtual
420 // Description: Opens the window right now. Called from the window
421 // thread. Returns true if the window is successfully
422 // opened, or false if there was a problem.
423 ////////////////////////////////////////////////////////////////////
424 bool TinyXGraphicsWindow::
425 open_window() {
426  TinyXGraphicsPipe *tinyx_pipe;
427  DCAST_INTO_R(tinyx_pipe, _pipe, false);
428 
429  // GSG Creation/Initialization
430  TinyGraphicsStateGuardian *tinygsg;
431  if (_gsg == 0) {
432  // There is no old gsg. Create a new one.
433  tinygsg = new TinyGraphicsStateGuardian(_engine, _pipe, NULL);
434  _gsg = tinygsg;
435  } else {
436  DCAST_INTO_R(tinygsg, _gsg, false);
437  }
438 
439  XVisualInfo vinfo_template;
440  vinfo_template.screen = _screen;
441  vinfo_template.depth = 32;
442  vinfo_template.c_class = TrueColor;
443 
444  // Try to get each of these properties in turn.
445  int try_masks[] = {
446  VisualScreenMask | VisualDepthMask | VisualClassMask,
447  VisualScreenMask | VisualClassMask,
448  VisualScreenMask | VisualDepthMask,
449  VisualScreenMask,
450  0,
451  };
452 
453  int i = 0;
454  int num_vinfos = 0;
455  XVisualInfo *vinfo_array;
456  while (try_masks[i] != 0 && num_vinfos == 0) {
457  vinfo_array =
458  XGetVisualInfo(_display, try_masks[i], &vinfo_template, &num_vinfos);
459  ++i;
460  }
461 
462  if (num_vinfos == 0) {
463  // No suitable X visual.
464  tinydisplay_cat.error()
465  << "No suitable X Visual available; cannot open window.\n";
466  return false;
467  }
468  _visual_info = &vinfo_array[0];
469 
470  _visual = _visual_info->visual;
471  _depth = _visual_info->depth;
472  _bytes_per_pixel = _depth / 8;
473  if (_bytes_per_pixel == 3) {
474  // Seems to be a special case.
475  _bytes_per_pixel = 4;
476  }
477  tinydisplay_cat.info()
478  << "Got X Visual with depth " << _depth << " (bpp " << _bytes_per_pixel << ") and class ";
479  switch (_visual_info->c_class) {
480  case TrueColor:
481  tinydisplay_cat.info(false) << "TrueColor\n";
482  break;
483 
484  case DirectColor:
485  tinydisplay_cat.info(false) << "DirectColor\n";
486  break;
487 
488  case StaticColor:
489  tinydisplay_cat.info(false) << "StaticColor\n";
490  break;
491 
492  case StaticGray:
493  tinydisplay_cat.info(false) << "StaticGray\n";
494  break;
495 
496  case GrayScale:
497  tinydisplay_cat.info(false) << "GrayScale\n";
498  break;
499 
500  case PseudoColor:
501  tinydisplay_cat.info(false) << "PseudoColor\n";
502  break;
503  }
504 
505  setup_colormap(_visual_info);
506 
507  if (!x11GraphicsWindow::open_window()) {
508  return false;
509  }
510 
511  _gc = XCreateGC(_display, _xwindow, 0, NULL);
512 
513  create_full_frame_buffer();
514  if (_full_frame_buffer == NULL) {
515  tinydisplay_cat.error()
516  << "Could not create frame buffer.\n";
517  return false;
518  }
519  create_reduced_frame_buffer();
520  create_ximage();
521  nassertr(_ximage != NULL, false);
522 
523  tinygsg->_current_frame_buffer = _full_frame_buffer;
524 
525  tinygsg->reset_if_new();
526  if (!tinygsg->is_valid()) {
527  close_window();
528  return false;
529  }
530 
531  XMapWindow(_display, _xwindow);
532 
533  if (_properties.get_raw_mice()) {
534  open_raw_mice();
535  } else {
536  if (tinydisplay_cat.is_debug()) {
537  tinydisplay_cat.debug()
538  << "Raw mice not requested.\n";
539  }
540  }
541 
542  // Create a WindowHandle for ourselves
543  _window_handle = NativeWindowHandle::make_x11(_xwindow);
544 
545  // And tell our parent window that we're now its child.
546  if (_parent_window_handle != (WindowHandle *)NULL) {
547  _parent_window_handle->attach_child(_window_handle);
548  }
549 
550  return true;
551 }
552 
553 ////////////////////////////////////////////////////////////////////
554 // Function: TinyXGraphicsWindow::pixel_factor_changed
555 // Access: Protected, Virtual
556 // Description: Called internally when the pixel factor changes.
557 ////////////////////////////////////////////////////////////////////
558 void TinyXGraphicsWindow::
559 pixel_factor_changed() {
560  x11GraphicsWindow::pixel_factor_changed();
561  create_reduced_frame_buffer();
562 }
563 
564 ////////////////////////////////////////////////////////////////////
565 // Function: TinyXGraphicsWindow::create_full_frame_buffer
566 // Access: Private
567 // Description: Creates a suitable frame buffer for the current
568 // window size.
569 ////////////////////////////////////////////////////////////////////
570 void TinyXGraphicsWindow::
571 create_full_frame_buffer() {
572  if (_full_frame_buffer != NULL) {
573  ZB_close(_full_frame_buffer);
574  _full_frame_buffer = NULL;
575  }
576 
577  int mode;
578  switch (_bytes_per_pixel) {
579  case 1:
580  tinydisplay_cat.error()
581  << "Palette images are currently not supported.\n";
582  return;
583 
584  case 2:
585  mode = ZB_MODE_5R6G5B;
586  break;
587  case 4:
588  mode = ZB_MODE_RGBA;
589  break;
590 
591  default:
592  return;
593  }
594 
595  _full_frame_buffer = ZB_open(_properties.get_x_size(), _properties.get_y_size(), mode, 0, 0, 0, 0);
596  _pitch = (_full_frame_buffer->xsize * _bytes_per_pixel + 3) & ~3;
597 }
598 
599 ////////////////////////////////////////////////////////////////////
600 // Function: TinyXGraphicsWindow::create_reduced_frame_buffer
601 // Access: Private
602 // Description: Creates a suitable frame buffer for the current
603 // window size and pixel zoom.
604 ////////////////////////////////////////////////////////////////////
605 void TinyXGraphicsWindow::
606 create_reduced_frame_buffer() {
607  if (!_full_frame_buffer) {
608  return;
609  }
610 
611  if (_reduced_frame_buffer != NULL) {
612  ZB_close(_reduced_frame_buffer);
613  _reduced_frame_buffer = NULL;
614  }
615 
616  int x_size = get_fb_x_size();
617  int y_size = get_fb_y_size();
618 
619  if (x_size == _full_frame_buffer->xsize) {
620  // No zooming is necessary.
621 
622  } else {
623  // The reduced size is different, so we need a separate buffer to
624  // render into.
625  _reduced_frame_buffer = ZB_open(x_size, y_size, _full_frame_buffer->mode, 0, 0, 0, 0);
626  }
627 }
628 
629 
630 ////////////////////////////////////////////////////////////////////
631 // Function: TinyXGraphicsWindow::create_ximage
632 // Access: Private
633 // Description: Creates a suitable XImage for the current
634 // window size.
635 ////////////////////////////////////////////////////////////////////
636 void TinyXGraphicsWindow::
637 create_ximage() {
638  if (_ximage != NULL) {
639  PANDA_FREE_ARRAY(_ximage->data);
640  _ximage->data = NULL;
641  XDestroyImage(_ximage);
642  _ximage = NULL;
643  }
644 
645  int image_size = _full_frame_buffer->ysize * _pitch;
646  char *data = (char *)PANDA_MALLOC_ARRAY(image_size);
647 
648  _ximage = XCreateImage(_display, _visual, _depth, ZPixmap, 0, data,
649  _full_frame_buffer->xsize, _full_frame_buffer->ysize,
650  32, 0);
651 }
652 
653 #endif // HAVE_X11
654 
virtual void process_events()
Do whatever processing is necessary to ensure that the window responds to user events.
This object represents a window on the desktop, not necessarily a Panda window.
Definition: windowHandle.h:40
virtual void end_flip()
This function will be called within the draw thread after begin_flip() has been called on all windows...
bool reset_if_new()
Calls reset() to initialize the GSG, but only if it hasn&#39;t been called yet.
int get_y_size() const
Returns size in pixels in the y dimension of the useful part of the window, not including decorations...
bool is_valid() const
Returns true if the GSG has been correctly initialized within a graphics context, false if there has ...
void set_size(const LVector2i &size)
Specifies the requested size of the window, in pixels.
A lightweight class that can be used to automatically start and stop a PStatCollector around a sectio...
Definition: pStatTimer.h:34
A ButtonHandle represents a single button from any device, including keyboard buttons and mouse butto...
Definition: buttonHandle.h:28
A container for the various kinds of properties we might ask to have on a graphics window before we o...
An object to create GraphicsOutputs that share a particular 3-D API.
Definition: graphicsPipe.h:58
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.
void set_foreground(bool foreground)
Specifies whether the window should be opened in the foreground (true), or left in the background (fa...
int get_x_size() const
Returns size in pixels in the x dimension of the useful part of the window, not including decorations...
A thread; that is, a lightweight process.
Definition: thread.h:51
void set_minimized(bool minimized)
Specifies whether the window should be created minimized (true), or normal (false).
Encapsulates all the communication with a particular instance of a given rendering backend...
This class is the main interface to controlling the render process.
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:85
A container for the various kinds of properties we might ask to have on a graphics frameBuffer before...
void set_open(bool open)
Specifies whether the window should be open.
An interface to the TinyPanda software rendering code within this module.
Interfaces to the X11 window system.