tinyXGraphicsWindow.cxx 20 KB

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