Panda3D
|
00001 // Filename: tinyXGraphicsWindow.cxx 00002 // Created by: drose (03May08) 00003 // 00004 //////////////////////////////////////////////////////////////////// 00005 // 00006 // PANDA 3D SOFTWARE 00007 // Copyright (c) Carnegie Mellon University. All rights reserved. 00008 // 00009 // All use of this software is subject to the terms of the revised BSD 00010 // license. You should have received a copy of this license along 00011 // with this source code in a file named "LICENSE." 00012 // 00013 //////////////////////////////////////////////////////////////////// 00014 00015 #include "pandabase.h" 00016 00017 #ifdef HAVE_X11 00018 00019 #include "tinyXGraphicsWindow.h" 00020 #include "tinyGraphicsStateGuardian.h" 00021 #include "tinyXGraphicsPipe.h" 00022 #include "config_tinydisplay.h" 00023 00024 #include "graphicsPipe.h" 00025 #include "keyboardButton.h" 00026 #include "mouseButton.h" 00027 #include "clockObject.h" 00028 #include "pStatTimer.h" 00029 #include "textEncoder.h" 00030 #include "throw_event.h" 00031 #include "lightReMutexHolder.h" 00032 #include "nativeWindowHandle.h" 00033 00034 TypeHandle TinyXGraphicsWindow::_type_handle; 00035 00036 //////////////////////////////////////////////////////////////////// 00037 // Function: TinyXGraphicsWindow::Constructor 00038 // Access: Public 00039 // Description: 00040 //////////////////////////////////////////////////////////////////// 00041 TinyXGraphicsWindow:: 00042 TinyXGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe, 00043 const string &name, 00044 const FrameBufferProperties &fb_prop, 00045 const WindowProperties &win_prop, 00046 int flags, 00047 GraphicsStateGuardian *gsg, 00048 GraphicsOutput *host) : 00049 x11GraphicsWindow(engine, pipe, name, fb_prop, win_prop, flags, gsg, host) 00050 { 00051 _gc = (GC)NULL; 00052 00053 _reduced_frame_buffer = NULL; 00054 _full_frame_buffer = NULL; 00055 _ximage = NULL; 00056 update_pixel_factor(); 00057 } 00058 00059 //////////////////////////////////////////////////////////////////// 00060 // Function: TinyXGraphicsWindow::Destructor 00061 // Access: Public, Virtual 00062 // Description: 00063 //////////////////////////////////////////////////////////////////// 00064 TinyXGraphicsWindow:: 00065 ~TinyXGraphicsWindow() { 00066 if (_gc != NULL && _display != NULL) { 00067 XFreeGC(_display, _gc); 00068 } 00069 if (_ximage != NULL) { 00070 PANDA_FREE_ARRAY(_ximage->data); 00071 _ximage->data = NULL; 00072 XDestroyImage(_ximage); 00073 } 00074 } 00075 00076 //////////////////////////////////////////////////////////////////// 00077 // Function: TinyXGraphicsWindow::begin_frame 00078 // Access: Public, Virtual 00079 // Description: This function will be called within the draw thread 00080 // before beginning rendering for a given frame. It 00081 // should do whatever setup is required, and return true 00082 // if the frame should be rendered, or false if it 00083 // should be skipped. 00084 //////////////////////////////////////////////////////////////////// 00085 bool TinyXGraphicsWindow:: 00086 begin_frame(FrameMode mode, Thread *current_thread) { 00087 PStatTimer timer(_make_current_pcollector, current_thread); 00088 00089 if (_xwindow == (X11_Window)NULL) { 00090 return false; 00091 } 00092 00093 begin_frame_spam(mode); 00094 if (_gsg == (GraphicsStateGuardian *)NULL) { 00095 return false; 00096 } 00097 if (_awaiting_configure) { 00098 // Don't attempt to draw while we have just reconfigured the 00099 // window and we haven't got the notification back yet. 00100 return false; 00101 } 00102 00103 TinyGraphicsStateGuardian *tinygsg; 00104 DCAST_INTO_R(tinygsg, _gsg, false); 00105 00106 if (_reduced_frame_buffer != (ZBuffer *)NULL) { 00107 tinygsg->_current_frame_buffer = _reduced_frame_buffer; 00108 } else { 00109 tinygsg->_current_frame_buffer = _full_frame_buffer; 00110 } 00111 tinygsg->reset_if_new(); 00112 00113 _gsg->set_current_properties(&get_fb_properties()); 00114 return _gsg->begin_frame(current_thread); 00115 } 00116 00117 //////////////////////////////////////////////////////////////////// 00118 // Function: TinyXGraphicsWindow::end_frame 00119 // Access: Public, Virtual 00120 // Description: This function will be called within the draw thread 00121 // after rendering is completed for a given frame. It 00122 // should do whatever finalization is required. 00123 //////////////////////////////////////////////////////////////////// 00124 void TinyXGraphicsWindow:: 00125 end_frame(FrameMode mode, Thread *current_thread) { 00126 end_frame_spam(mode); 00127 nassertv(_gsg != (GraphicsStateGuardian *)NULL); 00128 00129 if (mode == FM_render) { 00130 // end_render_texture(); 00131 copy_to_textures(); 00132 } 00133 00134 _gsg->end_frame(current_thread); 00135 00136 if (mode == FM_render) { 00137 trigger_flip(); 00138 clear_cube_map_selection(); 00139 } 00140 } 00141 00142 //////////////////////////////////////////////////////////////////// 00143 // Function: TinyXGraphicsWindow::end_flip 00144 // Access: Public, Virtual 00145 // Description: This function will be called within the draw thread 00146 // after begin_flip() has been called on all windows, to 00147 // finish the exchange of the front and back buffers. 00148 // 00149 // This should cause the window to wait for the flip, if 00150 // necessary. 00151 //////////////////////////////////////////////////////////////////// 00152 void TinyXGraphicsWindow:: 00153 end_flip() { 00154 if (_xwindow == (X11_Window)NULL || !_flip_ready) { 00155 GraphicsWindow::end_flip(); 00156 return; 00157 } 00158 00159 if (_reduced_frame_buffer != (ZBuffer *)NULL) { 00160 // Zoom the reduced buffer onto the full buffer. 00161 ZB_zoomFrameBuffer(_full_frame_buffer, 0, 0, 00162 _full_frame_buffer->xsize, _full_frame_buffer->ysize, 00163 _reduced_frame_buffer, 0, 0, 00164 _reduced_frame_buffer->xsize, _reduced_frame_buffer->ysize); 00165 } 00166 00167 // We can't just point the XPutImage directly at our own framebuffer 00168 // data, even if the bytes_per_pixel matches, because some X 00169 // displays will respect the alpha channel and make the window 00170 // transparent there. We don't want transparent windows where the 00171 // alpha data happens to less than 1.0. 00172 ZB_copyFrameBufferNoAlpha(_full_frame_buffer, _ximage->data, _pitch); 00173 00174 XPutImage(_display, _xwindow, _gc, _ximage, 0, 0, 0, 0, 00175 _full_frame_buffer->xsize, _full_frame_buffer->ysize); 00176 XFlush(_display); 00177 GraphicsWindow::end_flip(); 00178 } 00179 00180 //////////////////////////////////////////////////////////////////// 00181 // Function: TinyXGraphicsWindow::supports_pixel_zoom 00182 // Access: Published, Virtual 00183 // Description: Returns true if a call to set_pixel_zoom() will be 00184 // respected, false if it will be ignored. If this 00185 // returns false, then get_pixel_factor() will always 00186 // return 1.0, regardless of what value you specify for 00187 // set_pixel_zoom(). 00188 // 00189 // This may return false if the underlying renderer 00190 // doesn't support pixel zooming, or if you have called 00191 // this on a DisplayRegion that doesn't have both 00192 // set_clear_color() and set_clear_depth() enabled. 00193 //////////////////////////////////////////////////////////////////// 00194 bool TinyXGraphicsWindow:: 00195 supports_pixel_zoom() const { 00196 return true; 00197 } 00198 00199 //////////////////////////////////////////////////////////////////// 00200 // Function: TinyXGraphicsWindow::process_events 00201 // Access: Public, Virtual 00202 // Description: Do whatever processing is necessary to ensure that 00203 // the window responds to user events. Also, honor any 00204 // requests recently made via request_properties() 00205 // 00206 // This function is called only within the window 00207 // thread. 00208 //////////////////////////////////////////////////////////////////// 00209 void TinyXGraphicsWindow:: 00210 process_events() { 00211 LightReMutexHolder holder(TinyXGraphicsPipe::_x_mutex); 00212 00213 GraphicsWindow::process_events(); 00214 00215 if (_xwindow == (X11_Window)0) { 00216 return; 00217 } 00218 00219 poll_raw_mice(); 00220 00221 XEvent event; 00222 XKeyEvent keyrelease_event; 00223 bool got_keyrelease_event = false; 00224 00225 while (XCheckIfEvent(_display, &event, check_event, (char *)this)) { 00226 if (XFilterEvent(&event, None)) { 00227 continue; 00228 } 00229 00230 if (got_keyrelease_event) { 00231 // If a keyrelease event is immediately followed by a matching 00232 // keypress event, that's just key repeat and we should treat 00233 // the two events accordingly. It would be nice if X provided a 00234 // way to differentiate between keyrepeat and explicit 00235 // keypresses more generally. 00236 got_keyrelease_event = false; 00237 00238 if (event.type == KeyPress && 00239 event.xkey.keycode == keyrelease_event.keycode && 00240 (event.xkey.time - keyrelease_event.time <= 1)) { 00241 // In particular, we only generate down messages for the 00242 // repeated keys, not down-and-up messages. 00243 handle_keystroke(event.xkey); 00244 00245 // We thought about not generating the keypress event, but we 00246 // need that repeat for backspace. Rethink later. 00247 handle_keypress(event.xkey); 00248 continue; 00249 00250 } else { 00251 // This keyrelease event is not immediately followed by a 00252 // matching keypress event, so it's a genuine release. 00253 handle_keyrelease(keyrelease_event); 00254 } 00255 } 00256 00257 WindowProperties properties; 00258 ButtonHandle button; 00259 00260 switch (event.type) { 00261 case ReparentNotify: 00262 break; 00263 00264 case ConfigureNotify: 00265 _awaiting_configure = false; 00266 if (_properties.get_fixed_size()) { 00267 // If the window properties indicate a fixed size only, undo 00268 // any attempt by the user to change them. In X, there 00269 // doesn't appear to be a way to universally disallow this 00270 // directly (although we do set the min_size and max_size to 00271 // the same value, which seems to work for most window 00272 // managers.) 00273 WindowProperties current_props = get_properties(); 00274 if (event.xconfigure.width != current_props.get_x_size() || 00275 event.xconfigure.height != current_props.get_y_size()) { 00276 XWindowChanges changes; 00277 changes.width = current_props.get_x_size(); 00278 changes.height = current_props.get_y_size(); 00279 int value_mask = (CWWidth | CWHeight); 00280 XConfigureWindow(_display, _xwindow, value_mask, &changes); 00281 } 00282 00283 } else { 00284 // A normal window may be resized by the user at will. 00285 properties.set_size(event.xconfigure.width, event.xconfigure.height); 00286 system_changed_properties(properties); 00287 ZB_resize(_full_frame_buffer, NULL, _properties.get_x_size(), _properties.get_y_size()); 00288 _pitch = (_full_frame_buffer->xsize * _bytes_per_pixel + 3) & ~3; 00289 create_reduced_frame_buffer(); 00290 create_ximage(); 00291 } 00292 break; 00293 00294 case ButtonPress: 00295 // This refers to the mouse buttons. 00296 button = get_mouse_button(event.xbutton); 00297 _input_devices[0].set_pointer_in_window(event.xbutton.x, event.xbutton.y); 00298 _input_devices[0].button_down(button); 00299 break; 00300 00301 case ButtonRelease: 00302 button = get_mouse_button(event.xbutton); 00303 _input_devices[0].set_pointer_in_window(event.xbutton.x, event.xbutton.y); 00304 _input_devices[0].button_up(button); 00305 break; 00306 00307 case MotionNotify: 00308 _input_devices[0].set_pointer_in_window(event.xmotion.x, event.xmotion.y); 00309 break; 00310 00311 case KeyPress: 00312 handle_keystroke(event.xkey); 00313 handle_keypress(event.xkey); 00314 break; 00315 00316 case KeyRelease: 00317 // The KeyRelease can't be processed immediately, because we 00318 // have to check first if it's immediately followed by a 00319 // matching KeyPress event. 00320 keyrelease_event = event.xkey; 00321 got_keyrelease_event = true; 00322 break; 00323 00324 case EnterNotify: 00325 _input_devices[0].set_pointer_in_window(event.xcrossing.x, event.xcrossing.y); 00326 break; 00327 00328 case LeaveNotify: 00329 _input_devices[0].set_pointer_out_of_window(); 00330 break; 00331 00332 case FocusIn: 00333 properties.set_foreground(true); 00334 system_changed_properties(properties); 00335 break; 00336 00337 case FocusOut: 00338 _input_devices[0].focus_lost(); 00339 properties.set_foreground(false); 00340 system_changed_properties(properties); 00341 break; 00342 00343 case UnmapNotify: 00344 properties.set_minimized(true); 00345 system_changed_properties(properties); 00346 break; 00347 00348 case MapNotify: 00349 properties.set_minimized(false); 00350 system_changed_properties(properties); 00351 00352 // Auto-focus the window when it is mapped. 00353 XSetInputFocus(_display, _xwindow, RevertToPointerRoot, CurrentTime); 00354 break; 00355 00356 case ClientMessage: 00357 if ((Atom)(event.xclient.data.l[0]) == _wm_delete_window) { 00358 // This is a message from the window manager indicating that 00359 // the user has requested to close the window. 00360 string close_request_event = get_close_request_event(); 00361 if (!close_request_event.empty()) { 00362 // In this case, the app has indicated a desire to intercept 00363 // the request and process it directly. 00364 throw_event(close_request_event); 00365 00366 } else { 00367 // In this case, the default case, the app does not intend 00368 // to service the request, so we do by closing the window. 00369 00370 // TODO: don't release the gsg in the window thread. 00371 close_window(); 00372 properties.set_open(false); 00373 system_changed_properties(properties); 00374 } 00375 } 00376 break; 00377 00378 case DestroyNotify: 00379 // Apparently, we never get a DestroyNotify on a toplevel 00380 // window. Instead, we rely on hints from the window manager 00381 // (see above). 00382 tinydisplay_cat.info() 00383 << "DestroyNotify\n"; 00384 break; 00385 00386 default: 00387 tinydisplay_cat.error() 00388 << "unhandled X event type " << event.type << "\n"; 00389 } 00390 } 00391 00392 if (got_keyrelease_event) { 00393 // This keyrelease event is not immediately followed by a 00394 // matching keypress event, so it's a genuine release. 00395 handle_keyrelease(keyrelease_event); 00396 } 00397 } 00398 00399 //////////////////////////////////////////////////////////////////// 00400 // Function: TinyXGraphicsWindow::close_window 00401 // Access: Protected, Virtual 00402 // Description: Closes the window right now. Called from the window 00403 // thread. 00404 //////////////////////////////////////////////////////////////////// 00405 void TinyXGraphicsWindow:: 00406 close_window() { 00407 if (_gsg != (GraphicsStateGuardian *)NULL) { 00408 TinyGraphicsStateGuardian *tinygsg; 00409 DCAST_INTO_V(tinygsg, _gsg); 00410 tinygsg->_current_frame_buffer = NULL; 00411 _gsg.clear(); 00412 } 00413 00414 x11GraphicsWindow::close_window(); 00415 } 00416 00417 //////////////////////////////////////////////////////////////////// 00418 // Function: TinyXGraphicsWindow::open_window 00419 // Access: Protected, Virtual 00420 // Description: Opens the window right now. Called from the window 00421 // thread. Returns true if the window is successfully 00422 // opened, or false if there was a problem. 00423 //////////////////////////////////////////////////////////////////// 00424 bool TinyXGraphicsWindow:: 00425 open_window() { 00426 TinyXGraphicsPipe *tinyx_pipe; 00427 DCAST_INTO_R(tinyx_pipe, _pipe, false); 00428 00429 // GSG Creation/Initialization 00430 TinyGraphicsStateGuardian *tinygsg; 00431 if (_gsg == 0) { 00432 // There is no old gsg. Create a new one. 00433 tinygsg = new TinyGraphicsStateGuardian(_engine, _pipe, NULL); 00434 _gsg = tinygsg; 00435 } else { 00436 DCAST_INTO_R(tinygsg, _gsg, false); 00437 } 00438 00439 XVisualInfo vinfo_template; 00440 vinfo_template.screen = _screen; 00441 vinfo_template.depth = 32; 00442 vinfo_template.c_class = TrueColor; 00443 00444 // Try to get each of these properties in turn. 00445 int try_masks[] = { 00446 VisualScreenMask | VisualDepthMask | VisualClassMask, 00447 VisualScreenMask | VisualClassMask, 00448 VisualScreenMask | VisualDepthMask, 00449 VisualScreenMask, 00450 0, 00451 }; 00452 00453 int i = 0; 00454 int num_vinfos = 0; 00455 XVisualInfo *vinfo_array; 00456 while (try_masks[i] != 0 && num_vinfos == 0) { 00457 vinfo_array = 00458 XGetVisualInfo(_display, try_masks[i], &vinfo_template, &num_vinfos); 00459 ++i; 00460 } 00461 00462 if (num_vinfos == 0) { 00463 // No suitable X visual. 00464 tinydisplay_cat.error() 00465 << "No suitable X Visual available; cannot open window.\n"; 00466 return false; 00467 } 00468 _visual_info = &vinfo_array[0]; 00469 00470 _visual = _visual_info->visual; 00471 _depth = _visual_info->depth; 00472 _bytes_per_pixel = _depth / 8; 00473 if (_bytes_per_pixel == 3) { 00474 // Seems to be a special case. 00475 _bytes_per_pixel = 4; 00476 } 00477 tinydisplay_cat.info() 00478 << "Got X Visual with depth " << _depth << " (bpp " << _bytes_per_pixel << ") and class "; 00479 switch (_visual_info->c_class) { 00480 case TrueColor: 00481 tinydisplay_cat.info(false) << "TrueColor\n"; 00482 break; 00483 00484 case DirectColor: 00485 tinydisplay_cat.info(false) << "DirectColor\n"; 00486 break; 00487 00488 case StaticColor: 00489 tinydisplay_cat.info(false) << "StaticColor\n"; 00490 break; 00491 00492 case StaticGray: 00493 tinydisplay_cat.info(false) << "StaticGray\n"; 00494 break; 00495 00496 case GrayScale: 00497 tinydisplay_cat.info(false) << "GrayScale\n"; 00498 break; 00499 00500 case PseudoColor: 00501 tinydisplay_cat.info(false) << "PseudoColor\n"; 00502 break; 00503 } 00504 00505 setup_colormap(_visual_info); 00506 00507 if (!x11GraphicsWindow::open_window()) { 00508 return false; 00509 } 00510 00511 _gc = XCreateGC(_display, _xwindow, 0, NULL); 00512 00513 create_full_frame_buffer(); 00514 if (_full_frame_buffer == NULL) { 00515 tinydisplay_cat.error() 00516 << "Could not create frame buffer.\n"; 00517 return false; 00518 } 00519 create_reduced_frame_buffer(); 00520 create_ximage(); 00521 nassertr(_ximage != NULL, false); 00522 00523 tinygsg->_current_frame_buffer = _full_frame_buffer; 00524 00525 tinygsg->reset_if_new(); 00526 if (!tinygsg->is_valid()) { 00527 close_window(); 00528 return false; 00529 } 00530 00531 XMapWindow(_display, _xwindow); 00532 00533 if (_properties.get_raw_mice()) { 00534 open_raw_mice(); 00535 } else { 00536 if (tinydisplay_cat.is_debug()) { 00537 tinydisplay_cat.debug() 00538 << "Raw mice not requested.\n"; 00539 } 00540 } 00541 00542 // Create a WindowHandle for ourselves 00543 _window_handle = NativeWindowHandle::make_x11(_xwindow); 00544 00545 // And tell our parent window that we're now its child. 00546 if (_parent_window_handle != (WindowHandle *)NULL) { 00547 _parent_window_handle->attach_child(_window_handle); 00548 } 00549 00550 return true; 00551 } 00552 00553 //////////////////////////////////////////////////////////////////// 00554 // Function: TinyXGraphicsWindow::pixel_factor_changed 00555 // Access: Protected, Virtual 00556 // Description: Called internally when the pixel factor changes. 00557 //////////////////////////////////////////////////////////////////// 00558 void TinyXGraphicsWindow:: 00559 pixel_factor_changed() { 00560 x11GraphicsWindow::pixel_factor_changed(); 00561 create_reduced_frame_buffer(); 00562 } 00563 00564 //////////////////////////////////////////////////////////////////// 00565 // Function: TinyXGraphicsWindow::create_full_frame_buffer 00566 // Access: Private 00567 // Description: Creates a suitable frame buffer for the current 00568 // window size. 00569 //////////////////////////////////////////////////////////////////// 00570 void TinyXGraphicsWindow:: 00571 create_full_frame_buffer() { 00572 if (_full_frame_buffer != NULL) { 00573 ZB_close(_full_frame_buffer); 00574 _full_frame_buffer = NULL; 00575 } 00576 00577 int mode; 00578 switch (_bytes_per_pixel) { 00579 case 1: 00580 tinydisplay_cat.error() 00581 << "Palette images are currently not supported.\n"; 00582 return; 00583 00584 case 2: 00585 mode = ZB_MODE_5R6G5B; 00586 break; 00587 case 4: 00588 mode = ZB_MODE_RGBA; 00589 break; 00590 00591 default: 00592 return; 00593 } 00594 00595 _full_frame_buffer = ZB_open(_properties.get_x_size(), _properties.get_y_size(), mode, 0, 0, 0, 0); 00596 _pitch = (_full_frame_buffer->xsize * _bytes_per_pixel + 3) & ~3; 00597 } 00598 00599 //////////////////////////////////////////////////////////////////// 00600 // Function: TinyXGraphicsWindow::create_reduced_frame_buffer 00601 // Access: Private 00602 // Description: Creates a suitable frame buffer for the current 00603 // window size and pixel zoom. 00604 //////////////////////////////////////////////////////////////////// 00605 void TinyXGraphicsWindow:: 00606 create_reduced_frame_buffer() { 00607 if (_reduced_frame_buffer != NULL) { 00608 ZB_close(_reduced_frame_buffer); 00609 _reduced_frame_buffer = NULL; 00610 } 00611 00612 int x_size = get_fb_x_size(); 00613 int y_size = get_fb_y_size(); 00614 00615 if (x_size == _full_frame_buffer->xsize) { 00616 // No zooming is necessary. 00617 00618 } else { 00619 // The reduced size is different, so we need a separate buffer to 00620 // render into. 00621 _reduced_frame_buffer = ZB_open(x_size, y_size, _full_frame_buffer->mode, 0, 0, 0, 0); 00622 } 00623 } 00624 00625 00626 //////////////////////////////////////////////////////////////////// 00627 // Function: TinyXGraphicsWindow::create_ximage 00628 // Access: Private 00629 // Description: Creates a suitable XImage for the current 00630 // window size. 00631 //////////////////////////////////////////////////////////////////// 00632 void TinyXGraphicsWindow:: 00633 create_ximage() { 00634 if (_ximage != NULL) { 00635 PANDA_FREE_ARRAY(_ximage->data); 00636 _ximage->data = NULL; 00637 XDestroyImage(_ximage); 00638 _ximage = NULL; 00639 } 00640 00641 int image_size = _full_frame_buffer->ysize * _pitch; 00642 char *data = (char *)PANDA_MALLOC_ARRAY(image_size); 00643 00644 _ximage = XCreateImage(_display, _visual, _depth, ZPixmap, 0, data, 00645 _full_frame_buffer->xsize, _full_frame_buffer->ysize, 00646 32, 0); 00647 } 00648 00649 #endif // HAVE_X11 00650