Panda3D
winGraphicsWindow.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 winGraphicsWindow.cxx
10  * @author drose
11  * @date 2002-12-20
12  */
13 
14 #include "winGraphicsWindow.h"
15 #include "config_windisplay.h"
16 #include "winGraphicsPipe.h"
17 
18 #include "graphicsPipe.h"
19 #include "keyboardButton.h"
20 #include "mouseButton.h"
21 #include "clockObject.h"
22 #include "config_putil.h"
23 #include "throw_event.h"
24 #include "nativeWindowHandle.h"
25 
26 #include <tchar.h>
27 
28 #ifndef WM_DPICHANGED
29 #define WM_DPICHANGED 0x02E0
30 #endif
31 
32 #ifndef WM_TOUCH
33 #define WM_TOUCH 0x0240
34 #endif
35 
36 #if WINVER < 0x0601
37 // Not used on Windows XP, but we still need to define it.
38 #define TOUCH_COORD_TO_PIXEL(l) ((l) / 100)
39 
40 using std::endl;
41 using std::wstring;
42 
43 DECLARE_HANDLE(HTOUCHINPUT);
44 #endif
45 
46 TypeHandle WinGraphicsWindow::_type_handle;
47 TypeHandle WinGraphicsWindow::WinWindowHandle::_type_handle;
48 
49 WinGraphicsWindow::WindowHandles WinGraphicsWindow::_window_handles;
50 WinGraphicsWindow *WinGraphicsWindow::_creating_window = nullptr;
51 
52 WinGraphicsWindow *WinGraphicsWindow::_cursor_window = nullptr;
53 bool WinGraphicsWindow::_cursor_hidden = false;
54 
55 RECT WinGraphicsWindow::_mouse_unconfined_cliprect;
56 
57 // These are used to save the previous state of the fancy Win2000 effects that
58 // interfere with rendering when the mouse wanders into a window's client
59 // area.
60 bool WinGraphicsWindow::_got_saved_params = false;
61 int WinGraphicsWindow::_saved_mouse_trails;
62 BOOL WinGraphicsWindow::_saved_cursor_shadow;
63 BOOL WinGraphicsWindow::_saved_mouse_vanish;
64 
65 WinGraphicsWindow::IconFilenames WinGraphicsWindow::_icon_filenames;
66 WinGraphicsWindow::IconFilenames WinGraphicsWindow::_cursor_filenames;
67 
68 WinGraphicsWindow::WindowClasses WinGraphicsWindow::_window_classes;
69 int WinGraphicsWindow::_window_class_index = 0;
70 
71 static const char * const errorbox_title = "Panda3D Error";
72 
73 // These static variables contain pointers to the touch input functions, which
74 // are dynamically extracted from USER32.DLL
75 typedef BOOL (WINAPI *PFN_REGISTERTOUCHWINDOW)(IN HWND hWnd, IN ULONG ulFlags);
76 typedef BOOL (WINAPI *PFN_GETTOUCHINPUTINFO)(IN HTOUCHINPUT hTouchInput, IN UINT cInputs, OUT PTOUCHINPUT pInputs, IN int cbSize);
77 typedef BOOL (WINAPI *PFN_CLOSETOUCHINPUTHANDLE)(IN HTOUCHINPUT hTouchInput);
78 
79 static PFN_REGISTERTOUCHWINDOW pRegisterTouchWindow = 0;
80 static PFN_GETTOUCHINPUTINFO pGetTouchInputInfo = 0;
81 static PFN_CLOSETOUCHINPUTHANDLE pCloseTouchInputHandle = 0;
82 
83 /**
84  *
85  */
86 WinGraphicsWindow::
87 WinGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe,
88  const std::string &name,
89  const FrameBufferProperties &fb_prop,
90  const WindowProperties &win_prop,
91  int flags,
93  GraphicsOutput *host) :
94  GraphicsWindow(engine, pipe, name, fb_prop, win_prop, flags, gsg, host)
95 {
96  initialize_input_devices();
97  _hWnd = (HWND)0;
98  _ime_open = false;
99  _ime_active = false;
100  _tracking_mouse_leaving = false;
101  _cursor = 0;
102  _lost_keypresses = false;
103  _lshift_down = false;
104  _rshift_down = false;
105  _lcontrol_down = false;
106  _rcontrol_down = false;
107  _lalt_down = false;
108  _ralt_down = false;
109  _hparent = nullptr;
110  _num_touches = 0;
111 }
112 
113 /**
114  *
115  */
116 WinGraphicsWindow::
117 ~WinGraphicsWindow() {
118  if (_window_handle != nullptr) {
119  DCAST(WinWindowHandle, _window_handle)->clear_window();
120  }
121 }
122 
123 /**
124  * Returns the MouseData associated with the nth input device's pointer.
125  */
127 get_pointer(int device) const {
128  MouseData result;
129  {
130  LightMutexHolder holder(_input_lock);
131  nassertr(device >= 0 && device < (int)_input_devices.size(), MouseData());
132 
133  result = ((const GraphicsWindowInputDevice *)_input_devices[device].p())->get_pointer();
134 
135  // We recheck this immediately to get the most up-to-date value.
136  POINT cpos;
137  if (device == 0 && result._in_window && GetCursorPos(&cpos) && ScreenToClient(_hWnd, &cpos)) {
138  double time = ClockObject::get_global_clock()->get_real_time();
139  result._xpos = cpos.x;
140  result._ypos = cpos.y;
141  ((GraphicsWindowInputDevice *)_input_devices[0].p())->set_pointer_in_window(result._xpos, result._ypos, time);
142  }
143  }
144  return result;
145 }
146 
147 /**
148  * Forces the pointer to the indicated position within the window, if
149  * possible.
150  *
151  * Returns true if successful, false on failure. This may fail if the mouse
152  * is not currently within the window, or if the API doesn't support this
153  * operation.
154  */
156 move_pointer(int device, int x, int y) {
157  // First, indicate that the IME is no longer active, so that it won't send
158  // the string through WM_IME_COMPOSITION. But we still leave _ime_open
159  // true, so that it also won't send the string through WM_CHAR.
160  _ime_active = false;
161 
162  // Note: this is not thread-safe; it should be called only from App.
163  // Probably not an issue.
164  if (device == 0) {
165  // Move the system mouse pointer.
166  if (!_properties.get_foreground() )
167  // !_input->get_pointer().get_in_window())
168  {
169  // If the window doesn't have input focus, or the mouse isn't
170  // currently within the window, forget it.
171  return false;
172  }
173 
174  RECT view_rect;
175  get_client_rect_screen(_hWnd, &view_rect);
176 
177  SetCursorPos(view_rect.left + x, view_rect.top + y);
178  _input->set_pointer_in_window(x, y);
179  return true;
180  } else {
181  // Move a raw mouse.
182  if ((device < 1)||(device >= (int)_input_devices.size())) {
183  return false;
184  }
185  //_input_devices[device].set_pointer_in_window(x, y);
186  return true;
187  }
188 }
189 
190 /**
191  * Forces the ime window to close, if any
192  *
193  */
196  // Check if the ime window is open
197  if (!_ime_open)
198  return;
199 
200  HIMC hIMC = ImmGetContext(_hWnd);
201  if (hIMC != 0) {
202  if (!ImmSetOpenStatus(hIMC, false)) {
203  windisplay_cat.debug() << "ImmSetOpenStatus failed\n";
204  }
205  ImmReleaseContext(_hWnd, hIMC);
206  }
207  _ime_open = false;
208 
209  windisplay_cat.debug() << "success: closed ime window\n";
210  return;
211 }
212 
213 /**
214  * This function will be called within the draw thread after end_frame() has
215  * been called on all windows, to initiate the exchange of the front and back
216  * buffers.
217  *
218  * This should instruct the window to prepare for the flip at the next video
219  * sync, but it should not wait.
220  *
221  * We have the two separate functions, begin_flip() and end_flip(), to make it
222  * easier to flip all of the windows at the same time.
223  */
226 }
227 
228 /**
229  * Do whatever processing is necessary to ensure that the window responds to
230  * user events. Also, honor any requests recently made via
231  * request_properties()
232  *
233  * This function is called only within the window thread.
234  */
238 
239  // We can't treat the message loop specially just because the window is
240  // minimized, because we might be reading messages queued up for some other
241  // window, which is not minimized.
242  /*
243  if (!_window_active) {
244  // Get 1 msg at a time until no more are left and we block and sleep, or
245  // message changes _return_control_to_app or !_window_active status
246 
247  while(!_window_active && (!_return_control_to_app)) {
248  process_1_event();
249  }
250  _return_control_to_app = false;
251 
252  } else
253  */
254 
255  MSG msg;
256 
257  // Handle all the messages on the queue in a row. Some of these might be
258  // for another window, but they will get dispatched appropriately.
259  while (PeekMessage(&msg, nullptr, 0, 0, PM_NOREMOVE)) {
260  process_1_event();
261  }
262 }
263 
264 /**
265  * Applies the requested set of properties to the window, if possible, for
266  * instance to request a change in size or minimization status.
267  *
268  * The window properties are applied immediately, rather than waiting until
269  * the next frame. This implies that this method may *only* be called from
270  * within the window thread.
271  *
272  * The properties that have been applied are cleared from the structure by
273  * this function; so on return, whatever remains in the properties structure
274  * are those that were unchanged for some reason (probably because the
275  * underlying interface does not support changing that property on an open
276  * window).
277  */
281  if (!properties.is_any_specified()) {
282  // The base class has already handled this case.
283  return;
284  }
285 
286  if (properties.has_undecorated() ||
287  properties.has_fixed_size()) {
288  if (properties.has_undecorated()) {
289  _properties.set_undecorated(properties.get_undecorated());
290  properties.clear_undecorated();
291  }
292  if (properties.has_fixed_size()) {
293  _properties.set_fixed_size(properties.get_fixed_size());
294  properties.clear_fixed_size();
295  }
296  // When switching undecorated mode, Windows will keep the window at the
297  // current outer size, whereas we want to keep it with the configured
298  // inner size. Store the current size and origin.
299  LPoint2i top_left = _properties.get_origin();
300  LPoint2i bottom_right = top_left + _properties.get_size();
301 
302  DWORD window_style = make_style(_properties);
303  SetWindowLong(_hWnd, GWL_STYLE, window_style);
304 
305  // Now calculate the proper size and origin with the new window style.
306  RECT view_rect;
307  SetRect(&view_rect, top_left[0], top_left[1],
308  bottom_right[0], bottom_right[1]);
309  WINDOWINFO wi;
310  GetWindowInfo(_hWnd, &wi);
311  AdjustWindowRectEx(&view_rect, wi.dwStyle, FALSE, wi.dwExStyle);
312 
313  // We need to call this to ensure that the style change takes effect.
314  SetWindowPos(_hWnd, HWND_NOTOPMOST, view_rect.left, view_rect.top,
315  view_rect.right - view_rect.left,
316  view_rect.bottom - view_rect.top,
317  SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED |
318  SWP_NOSENDCHANGING | SWP_SHOWWINDOW);
319  }
320 
321  if (properties.has_title()) {
322  std::string title = properties.get_title();
323  _properties.set_title(title);
324  TextEncoder encoder;
325  wstring title_w = encoder.decode_text(title);
326  SetWindowTextW(_hWnd, title_w.c_str());
327  properties.clear_title();
328  }
329 
330  if (properties.has_icon_filename()) {
331  HICON icon = get_icon(properties.get_icon_filename());
332  if (icon != 0) {
333  ::SendMessage(_hWnd, WM_SETICON, ICON_SMALL, (LPARAM)icon);
334  ::SendMessage(_hWnd, WM_SETICON, ICON_BIG, (LPARAM)icon);
335 
336  _properties.set_icon_filename(properties.get_icon_filename());
337  properties.clear_icon_filename();
338  }
339  }
340 
341  if (properties.has_cursor_hidden()) {
342  bool hide_cursor = properties.get_cursor_hidden();
343  _properties.set_cursor_hidden(hide_cursor);
344  if (_cursor_window == this) {
345  hide_or_show_cursor(hide_cursor);
346  }
347 
348  properties.clear_cursor_hidden();
349  }
350 
351  if (properties.has_cursor_filename()) {
352  Filename filename = properties.get_cursor_filename();
353  _properties.set_cursor_filename(filename);
354 
355  _cursor = get_cursor(filename);
356  if (_cursor == 0) {
357  _cursor = LoadCursor(nullptr, IDC_ARROW);
358  }
359 
360  if (_cursor_window == this) {
361  SetCursor(_cursor);
362  }
363 
364  properties.clear_cursor_filename();
365  }
366 
367  if (properties.has_z_order()) {
368  WindowProperties::ZOrder last_z_order = _properties.get_z_order();
369  _properties.set_z_order(properties.get_z_order());
370  adjust_z_order(last_z_order, properties.get_z_order());
371 
372  properties.clear_z_order();
373  }
374 
375  if (properties.has_foreground() && properties.get_foreground()) {
376  if (!SetActiveWindow(_hWnd)) {
377  windisplay_cat.warning()
378  << "SetForegroundWindow() failed!\n";
379  } else {
380  _properties.set_foreground(true);
381  }
382 
383  properties.clear_foreground();
384  }
385 
386  if (properties.has_minimized()) {
387  if (_properties.get_minimized() != properties.get_minimized()) {
388  if (properties.get_minimized()) {
389  ShowWindow(_hWnd, SW_MINIMIZE);
390  } else {
391  ShowWindow(_hWnd, SW_RESTORE);
392  }
393  _properties.set_minimized(properties.get_minimized());
394  _properties.set_foreground(!properties.get_minimized());
395  }
396  properties.clear_minimized();
397  }
398 
399  if (properties.has_fullscreen()) {
400  if (properties.get_fullscreen() && !is_fullscreen()) {
401  if (do_fullscreen_switch()){
402  _properties.set_fullscreen(true);
403  properties.clear_fullscreen();
404  } else {
405  windisplay_cat.warning()
406  << "Switching to fullscreen mode failed!\n";
407  }
408  } else if (!properties.get_fullscreen() && is_fullscreen()){
409  if (do_windowed_switch()){
410  _properties.set_fullscreen(false);
411  properties.clear_fullscreen();
412  } else {
413  windisplay_cat.warning()
414  << "Switching to windowed mode failed!\n";
415  }
416  }
417  }
418 
419  if (properties.has_mouse_mode()) {
420  if (properties.get_mouse_mode() != _properties.get_mouse_mode()) {
421  switch (properties.get_mouse_mode()) {
422  case WindowProperties::M_absolute:
423  case WindowProperties::M_relative: // not implemented, treat as absolute
424 
425  if (_properties.get_mouse_mode() == WindowProperties::M_confined) {
426  ClipCursor(nullptr);
427  windisplay_cat.info() << "Unconfining cursor from window\n";
428  }
429  _properties.set_mouse_mode(WindowProperties::M_absolute);
430  break;
431 
432  case WindowProperties::M_confined:
433  {
434  RECT clip;
435 
436  if (!GetWindowRect(_hWnd, &clip)) {
437  windisplay_cat.warning()
438  << "GetWindowRect() failed in set_properties_now. Cannot confine cursor.\n";
439  } else {
440  windisplay_cat.info()
441  << "ClipCursor() to " << clip.left << "," << clip.top << " to "
442  << clip.right << "," << clip.bottom << endl;
443 
444  GetClipCursor(&_mouse_unconfined_cliprect);
445  if (!ClipCursor(&clip)) {
446  windisplay_cat.warning()
447  << "ClipCursor() failed in set_properties_now. Ignoring.\n";
448  } else {
449  _properties.set_mouse_mode(WindowProperties::M_confined);
450  windisplay_cat.info() << "Confining cursor to window\n";
451  }
452  }
453  }
454  break;
455  }
456  }
457  properties.clear_mouse_mode();
458  }
459 
460 }
461 
462 /**
463  * To be called at the end of the frame, after the window has successfully
464  * been drawn and is ready to be flipped (if appropriate).
465  */
466 void WinGraphicsWindow::
467 trigger_flip() {
468  GraphicsWindow::trigger_flip();
469 
470  if (!get_unexposed_draw()) {
471  // Now that we've drawn or whatever, invalidate the rectangle so we won't
472  // redraw again until we get the WM_PAINT message.
473 
474  InvalidateRect(_hWnd, nullptr, FALSE);
475  _got_expose_event = false;
476 
477  if (windisplay_cat.is_spam()) {
478  windisplay_cat.spam()
479  << "InvalidateRect: " << this << "\n";
480  }
481  }
482 }
483 
484 /**
485  * Closes the window right now. Called from the window thread.
486  */
487 void WinGraphicsWindow::
488 close_window() {
489  set_cursor_out_of_window();
490  DestroyWindow(_hWnd);
491 
492  if (is_fullscreen()) {
493  // revert to default display mode.
494  do_fullscreen_disable();
495  }
496 
497  // Remove the window handle from our global map.
498  _window_handles.erase(_hWnd);
499  _hWnd = (HWND)0;
500 
501  GraphicsWindow::close_window();
502 }
503 
504 /**
505  * Opens the window right now. Called from the window thread. Returns true
506  * if the window is successfully opened, or false if there was a problem.
507  */
508 bool WinGraphicsWindow::
509 open_window() {
510  if (_properties.has_cursor_filename()) {
511  _cursor = get_cursor(_properties.get_cursor_filename());
512  }
513  if (_cursor == 0) {
514  _cursor = LoadCursor(nullptr, IDC_ARROW);
515  }
516  bool want_foreground = (!_properties.has_foreground() || _properties.get_foreground());
517  bool want_minimized = (_properties.has_minimized() && _properties.get_minimized()) && !want_foreground;
518 
519  HWND old_foreground_window = GetForegroundWindow();
520 
521  // Store the current window pointer in _creating_window, so we can call
522  // CreateWindow() and know which window it is sending events to even before
523  // it gives us a handle. Warning: this is not thread safe!
524  _creating_window = this;
525  bool opened = open_graphic_window();
526  _creating_window = nullptr;
527 
528  if (!opened) {
529  return false;
530  }
531 
532  // Now that we have a window handle, store it in our global map, so future
533  // messages for this window can be routed properly.
534  _window_handles.insert(WindowHandles::value_type(_hWnd, this));
535 
536  // move window to top of zorder.
537  SetWindowPos(_hWnd, HWND_TOP, 0,0,0,0,
538  SWP_NOMOVE | SWP_NOSENDCHANGING | SWP_NOSIZE);
539 
540  // need to do twice to override any minimized flags in StartProcessInfo
541  if (want_minimized) {
542  ShowWindow(_hWnd, SW_MINIMIZE);
543  ShowWindow(_hWnd, SW_MINIMIZE);
544  } else {
545  ShowWindow(_hWnd, SW_SHOWNORMAL);
546  ShowWindow(_hWnd, SW_SHOWNORMAL);
547  }
548 
549  HWND new_foreground_window = _hWnd;
550  if (!want_foreground) {
551  // If we specifically requested the window not to be on top, restore the
552  // previous foreground window (if we can).
553  new_foreground_window = old_foreground_window;
554  }
555 
556  if (!SetActiveWindow(new_foreground_window)) {
557  windisplay_cat.warning()
558  << "SetActiveWindow() failed!\n";
559  }
560 
561  // Let's aggressively call SetForegroundWindow() in addition to
562  // SetActiveWindow(). It seems to work in some cases to make the window
563  // come to the top, where SetActiveWindow doesn't work.
564  if (!SetForegroundWindow(new_foreground_window)) {
565  windisplay_cat.warning()
566  << "SetForegroundWindow() failed!\n";
567  }
568 
569  // Determine the initial open status of the IME.
570  _ime_open = false;
571  _ime_active = false;
572  HIMC hIMC = ImmGetContext(_hWnd);
573  if (hIMC != 0) {
574  _ime_open = (ImmGetOpenStatus(hIMC) != 0);
575  ImmReleaseContext(_hWnd, hIMC);
576  }
577 
578  // Registers to receive the WM_INPUT messages
579  if (_input_devices.size() > 1) {
580  RAWINPUTDEVICE Rid;
581  Rid.usUsagePage = 0x01;
582  Rid.usUsage = 0x02;
583  Rid.dwFlags = 0;// RIDEV_NOLEGACY; // adds HID mouse and also ignores legacy mouse messages
584  Rid.hwndTarget = _hWnd;
585  RegisterRawInputDevices(&Rid, 1, sizeof (Rid));
586  }
587 
588  // Create a WindowHandle for ourselves
589  _window_handle = NativeWindowHandle::make_win(_hWnd);
590 
591  // Actually, we want a WinWindowHandle.
592  _window_handle = new WinWindowHandle(this, *_window_handle);
593 
594  // And tell our parent window that we're now its child.
595  if (_parent_window_handle != nullptr) {
596  _parent_window_handle->attach_child(_window_handle);
597  }
598 
599  // set us as the focus window for keyboard input
600  set_focus();
601 
602  // Try initializing the touch function pointers.
603  static bool initialized = false;
604  if (!initialized) {
605  initialized = true;
606  HMODULE user32 = GetModuleHandleA("user32.dll");
607  if (user32) {
608  // Introduced in Windows 7.
609  pRegisterTouchWindow = (PFN_REGISTERTOUCHWINDOW)GetProcAddress(user32, "RegisterTouchWindow");
610  pGetTouchInputInfo = (PFN_GETTOUCHINPUTINFO)GetProcAddress(user32, "GetTouchInputInfo");
611  pCloseTouchInputHandle = (PFN_CLOSETOUCHINPUTHANDLE)GetProcAddress(user32, "CloseTouchInputHandle");
612  }
613  }
614 
615  // Register for Win7 touch events.
616  if (pRegisterTouchWindow != nullptr) {
617  pRegisterTouchWindow(_hWnd, 0);
618  }
619 
620  return true;
621 }
622 
623 /**
624  * Creates the array of input devices. The first one is always the system
625  * mouse and keyboard. Each subsequent one is a raw mouse device. Also
626  * initializes a parallel array, _input_device_handle, with the win32 handle
627  * of each raw input device.
628  */
629 void WinGraphicsWindow::
630 initialize_input_devices() {
631  UINT nInputDevices;
632  PRAWINPUTDEVICELIST pRawInputDeviceList;
633 
634  nassertv(_input_devices.size() == 0);
635 
636  // Clear the handle array, and set up the system keyboardmouse
637  memset(_input_device_handle, 0, sizeof(_input_device_handle));
638  PT(GraphicsWindowInputDevice) device =
639  GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard_mouse");
640  add_input_device(device);
641  _input = device;
642 
643  // Get the number of devices.
644  if (GetRawInputDeviceList(nullptr, &nInputDevices, sizeof(RAWINPUTDEVICELIST)) != 0) {
645  return;
646  }
647 
648  // Allocate the array to hold the DeviceList
649  pRawInputDeviceList = (PRAWINPUTDEVICELIST)alloca(sizeof(RAWINPUTDEVICELIST) * nInputDevices);
650  if (pRawInputDeviceList==0) {
651  return;
652  }
653 
654  // Fill the Array
655  if (GetRawInputDeviceList(pRawInputDeviceList, &nInputDevices, sizeof(RAWINPUTDEVICELIST)) == -1) {
656  return;
657  }
658 
659  // Loop through all raw devices and find the raw mice
660  for (int i = 0; i < (int)nInputDevices; i++) {
661  if (pRawInputDeviceList[i].dwType == RIM_TYPEMOUSE) {
662  // Fetch information about specified mouse device.
663  UINT nSize;
664  if (GetRawInputDeviceInfoA(pRawInputDeviceList[i].hDevice, RIDI_DEVICENAME, (LPVOID)0, &nSize) != 0) {
665  return;
666  }
667  char *psName = (char*)alloca(sizeof(TCHAR) * nSize);
668  if (psName == 0) return;
669  if (GetRawInputDeviceInfoA(pRawInputDeviceList[i].hDevice, RIDI_DEVICENAME, (LPVOID)psName, &nSize) < 0) {
670  return;
671  }
672 
673  // If it's not an RDP mouse, add it to the list of raw mice.
674  if (strncmp(psName,"\\??\\Root#RDP_MOU#0000#",22)!=0) {
675  if (_input_devices.size() < 32) {
676  if (strncmp(psName,"\\??\\",4)==0) psName += 4;
677  char *pound1 = strchr(psName,'#');
678  char *pound2 = pound1 ? strchr(pound1+1,'#') : 0;
679  char *pound3 = pound2 ? strchr(pound2+1,'#') : 0;
680  if (pound3) *pound3 = 0;
681  for (char *p = psName; *p; p++) {
682  if (!isalnum(*p)) {
683  *p = '_';
684  }
685  }
686  if (pound2) *pound2 = '.';
687  _input_device_handle[_input_devices.size()] = pRawInputDeviceList[i].hDevice;
688 
689  PT(GraphicsWindowInputDevice) device = GraphicsWindowInputDevice::pointer_only(this, psName);
690  device->set_pointer_in_window(0, 0);
691  add_input_device(device);
692  }
693  }
694  }
695  }
696 }
697 
698 /**
699  * This is a hook for derived classes to do something special, if necessary,
700  * when a fullscreen window has been minimized. The given WindowProperties
701  * struct will be applied to this window's properties after this function
702  * returns.
703  */
704 void WinGraphicsWindow::
705 fullscreen_minimized(WindowProperties &) {
706 }
707 
708 /**
709  * This is a hook for derived classes to do something special, if necessary,
710  * when a fullscreen window has been restored after being minimized. The
711  * given WindowProperties struct will be applied to this window's properties
712  * after this function returns.
713  */
714 void WinGraphicsWindow::
715 fullscreen_restored(WindowProperties &) {
716 }
717 
718 /**
719  * Called from the window thread in response to a request from within the code
720  * (via request_properties()) to change the size and/or position of the
721  * window. Returns true if the window is successfully changed, or false if
722  * there was a problem.
723  */
724 bool WinGraphicsWindow::
725 do_reshape_request(int x_origin, int y_origin, bool has_origin,
726  int x_size, int y_size) {
727  if (windisplay_cat.is_debug()) {
728  windisplay_cat.debug()
729  << "Got reshape request (" << x_origin << ", " << y_origin
730  << ", " << has_origin << ", " << x_size << ", " << y_size << ")\n";
731  }
732  if (!is_fullscreen()) {
733  if (has_origin) {
734  // A coordinate of -2 means to center the window in its client area.
735  if (x_origin == -2) {
736  x_origin = 0.5 * (_pipe->get_display_width() - x_size);
737  }
738  if (y_origin == -2) {
739  y_origin = 0.5 * (_pipe->get_display_height() - y_size);
740  }
741  _properties.set_origin(x_origin, y_origin);
742 
743  if (x_origin == -1 && y_origin == -1) {
744  x_origin = 0;
745  y_origin = 0;
746  has_origin = false;
747  }
748  }
749 
750  // Compute the appropriate size and placement for the window, including
751  // decorations.
752  RECT view_rect;
753  SetRect(&view_rect, x_origin, y_origin,
754  x_origin + x_size, y_origin + y_size);
755  WINDOWINFO wi;
756  GetWindowInfo(_hWnd, &wi);
757  AdjustWindowRectEx(&view_rect, wi.dwStyle, FALSE, wi.dwExStyle);
758 
759  UINT flags = SWP_NOZORDER | SWP_NOSENDCHANGING;
760 
761  if (has_origin) {
762  x_origin = view_rect.left;
763  y_origin = view_rect.top;
764  } else {
765  x_origin = CW_USEDEFAULT;
766  y_origin = CW_USEDEFAULT;
767  flags |= SWP_NOMOVE;
768  }
769 
770  SetWindowPos(_hWnd, nullptr, x_origin, y_origin,
771  view_rect.right - view_rect.left,
772  view_rect.bottom - view_rect.top,
773  flags);
774 
775  handle_reshape();
776  return true;
777  }
778 
779  // Resizing a fullscreen window is a little trickier.
780  return do_fullscreen_resize(x_size, y_size);
781 }
782 
783 /**
784  * Called in the window thread when the window size or location is changed,
785  * this updates the properties structure accordingly.
786  */
787 void WinGraphicsWindow::
788 handle_reshape() {
789  RECT view_rect;
790  if (!GetClientRect(_hWnd, &view_rect)) {
791  // Sometimes we get a "reshape" before the window is fully created, in
792  // which case GetClientRect() ought to fail. Ignore this.
793  if (windisplay_cat.is_debug()) {
794  windisplay_cat.debug()
795  << "GetClientRect() failed in handle_reshape. Ignoring.\n";
796  }
797  return;
798  }
799 
800  // But in practice, GetClientRect() doesn't really fail, but just returns
801  // all zeroes. Ignore this too.
802  if (view_rect.left == 0 && view_rect.right == 0 &&
803  view_rect.bottom == 0 && view_rect.top == 0) {
804  if (windisplay_cat.is_debug()) {
805  windisplay_cat.debug()
806  << "GetClientRect() returned all zeroes in handle_reshape. Ignoring.\n";
807  }
808  return;
809  }
810 
811  bool result = (FALSE != ClientToScreen(_hWnd, (POINT*)&view_rect.left)); // translates top,left pnt
812  if (result) {
813  result = (FALSE != ClientToScreen(_hWnd, (POINT*)&view_rect.right)); // translates right,bottom pnt
814  }
815 
816  if (!result) {
817  if (windisplay_cat.is_debug()) {
818  windisplay_cat.debug()
819  << "ClientToScreen() failed in handle_reshape. Ignoring.\n";
820  }
821  return;
822  }
823 
824  WindowProperties properties;
825  properties.set_size((view_rect.right - view_rect.left),
826  (view_rect.bottom - view_rect.top));
827 
828  // _props origin should reflect upper left of view rectangle
829  properties.set_origin(view_rect.left, view_rect.top);
830 
831  if (windisplay_cat.is_debug()) {
832  windisplay_cat.debug()
833  << "reshape to origin: (" << properties.get_x_origin() << ","
834  << properties.get_y_origin() << "), size: (" << properties.get_x_size()
835  << "," << properties.get_y_size() << ")\n";
836  }
837 
838  adjust_z_order();
839  system_changed_properties(properties);
840 }
841 
842 /**
843  * Called in the window thread to resize a fullscreen window.
844  */
845 bool WinGraphicsWindow::
846 do_fullscreen_resize(int x_size, int y_size) {
847  HWND hDesktopWindow = GetDesktopWindow();
848  HDC scrnDC = GetDC(hDesktopWindow);
849  DWORD dwFullScreenBitDepth = GetDeviceCaps(scrnDC, BITSPIXEL);
850  ReleaseDC(hDesktopWindow, scrnDC);
851 
852  // resize will always leave screen bitdepth unchanged
853 
854  // allowing resizing of lowvidmem cards to > 640x480. why? I'll assume
855  // check was already done by caller, so he knows what he wants
856 
857  DEVMODE dm;
858  if (!find_acceptable_display_mode(x_size, y_size,
859  dwFullScreenBitDepth, dm)) {
860  windisplay_cat.error()
861  << "window resize(" << x_size << ", " << y_size
862  << ") failed, no compatible fullscreen display mode found!\n";
863  return false;
864  }
865 
866  // this causes WM_SIZE msg to be produced
867  SetWindowPos(_hWnd, nullptr, 0,0, x_size, y_size,
868  SWP_NOZORDER | SWP_NOMOVE | SWP_NOSENDCHANGING);
869  int chg_result = ChangeDisplaySettings(&dm, CDS_FULLSCREEN);
870 
871  if (chg_result != DISP_CHANGE_SUCCESSFUL) {
872  windisplay_cat.error()
873  << "resize ChangeDisplaySettings failed (error code: "
874  << chg_result << ") for specified res: "
875  << dm.dmPelsWidth << " x " << dm.dmPelsHeight
876  << " x " << dm.dmBitsPerPel << ", "
877  << dm.dmDisplayFrequency << " Hz\n";
878  return false;
879  }
880 
881  _fullscreen_display_mode = dm;
882 
883  windisplay_cat.info()
884  << "Resized fullscreen window to " << x_size << ", " << y_size
885  << " bitdepth " << dwFullScreenBitDepth << ", "
886  << dm.dmDisplayFrequency << "Hz\n";
887 
888  _properties.set_size(x_size, y_size);
889  set_size_and_recalc(x_size, y_size);
890 
891  return true;
892 }
893 
894 /**
895  * Called in the set_properties_now function to switch to fullscreen.
896  */
897 bool WinGraphicsWindow::
898 do_fullscreen_switch() {
899  if (!do_fullscreen_enable()) {
900  // Couldn't get fullscreen.
901  return false;
902  }
903 
904  WindowProperties props(_properties);
905  props.set_fullscreen(true);
906  DWORD window_style = make_style(props);
907  SetWindowLong(_hWnd, GWL_STYLE, window_style);
908 
909  WINDOW_METRICS metrics;
910  bool has_origin;
911  if (!calculate_metrics(true, window_style, metrics, has_origin)){
912  return false;
913  }
914 
915  SetWindowPos(_hWnd, HWND_NOTOPMOST, 0, 0, metrics.width, metrics.height,
916  SWP_FRAMECHANGED | SWP_SHOWWINDOW);
917  return true;
918 }
919 
920 /**
921  * Called in the set_properties_now function to switch to windowed mode.
922  */
923 bool WinGraphicsWindow::
924 do_windowed_switch() {
925  do_fullscreen_disable();
926 
927  WindowProperties props(_properties);
928  props.set_fullscreen(false);
929  DWORD window_style = make_style(props);
930  SetWindowLong(_hWnd, GWL_STYLE, window_style);
931 
932  WINDOW_METRICS metrics;
933  bool has_origin;
934 
935  if (!calculate_metrics(false, window_style, metrics, has_origin)){
936  return false;
937  }
938 
939  // We send SWP_FRAMECHANGED so that the new styles are taken into account.
940  // Also, we place the Windows at 0,0 to play safe until we decide how to get
941  // Panda to remember the windowed origin.
942 
943  SetWindowPos(_hWnd, HWND_NOTOPMOST, 0, 0,
944  metrics.width, metrics.height,
945  SWP_FRAMECHANGED | SWP_SHOWWINDOW);
946 
947  return true;
948 }
949 
950 /**
951  * Called before creating a fullscreen window to give the driver a chance to
952  * adjust the particular resolution request, if necessary.
953  */
954 void WinGraphicsWindow::
955 reconsider_fullscreen_size(DWORD &, DWORD &, DWORD &) {
956 }
957 
958 /**
959  * Some windows graphics contexts (e.g. DirectX) require special support to
960  * enable the displaying of an overlay window (particularly the IME window)
961  * over the fullscreen graphics window. This is a hook for the window to
962  * enable or disable that mode when necessary.
963  */
964 void WinGraphicsWindow::
965 support_overlay_window(bool) {
966 }
967 
968 /**
969  * Constructs a dwStyle for the specified mode, be it windowed or fullscreen.
970  */
971 DWORD WinGraphicsWindow::
972 make_style(const WindowProperties &properties) {
973  // from MSDN: An OpenGL window has its own pixel format. Because of this,
974  // only device contexts retrieved for the client area of an OpenGL window
975  // are allowed to draw into the window. As a result, an OpenGL window
976  // should be created with the WS_CLIPCHILDREN and WS_CLIPSIBLINGS styles.
977  // Additionally, the window class attribute should not include the
978  // CS_PARENTDC style.
979 
980  DWORD window_style = WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
981 
982  if (_properties.get_fullscreen()) {
983  window_style |= WS_SYSMENU;
984  } else if (!_properties.get_undecorated()) {
985  window_style |= (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX);
986 
987  if (!_properties.get_fixed_size()) {
988  window_style |= (WS_SIZEBOX | WS_MAXIMIZEBOX);
989  } else {
990  window_style |= WS_BORDER;
991  }
992  }
993  return window_style;
994 }
995 
996 /**
997  * Calculates the metrics for the specified mode, be it windowed or
998  * fullscreen.
999  */
1000 bool WinGraphicsWindow::
1001 calculate_metrics(bool fullscreen, DWORD window_style, WINDOW_METRICS &metrics,
1002  bool &has_origin) {
1003  metrics.x = 0;
1004  metrics.y = 0;
1005  has_origin = _properties.has_origin();
1006  if (!fullscreen && has_origin) {
1007  metrics.x = _properties.get_x_origin();
1008  metrics.y = _properties.get_y_origin();
1009 
1010  // A coordinate of -2 means to center the window in its client area.
1011  if (metrics.x == -2) {
1012  metrics.x = 0.5 * (_pipe->get_display_width() - _properties.get_x_size());
1013  }
1014  if (metrics.y == -2) {
1015  metrics.y = 0.5 * (_pipe->get_display_height() - _properties.get_y_size());
1016  }
1017  _properties.set_origin(metrics.x, metrics.y);
1018 
1019  if (metrics.x == -1 && metrics.y == -1) {
1020  metrics.x = 0;
1021  metrics.y = 0;
1022  has_origin = false;
1023  }
1024  }
1025 
1026  metrics.width = _properties.get_x_size();
1027  metrics.height = _properties.get_y_size();
1028 
1029  if (!fullscreen){
1030  RECT win_rect;
1031  SetRect(&win_rect, metrics.x, metrics.y,
1032  metrics.x + metrics.width, metrics.y + metrics.height);
1033 
1034  // Compute window size based on desired client area size
1035  if (!AdjustWindowRect(&win_rect, window_style, FALSE)) {
1036  windisplay_cat.error()
1037  << "AdjustWindowRect failed!" << endl;
1038  return false;
1039  }
1040 
1041  if (has_origin) {
1042  metrics.x = win_rect.left;
1043  metrics.y = win_rect.top;
1044  } else {
1045  metrics.x = CW_USEDEFAULT;
1046  metrics.y = CW_USEDEFAULT;
1047  }
1048  metrics.width = win_rect.right - win_rect.left;
1049  metrics.height = win_rect.bottom - win_rect.top;
1050  }
1051 
1052  return true;
1053 }
1054 
1055 /**
1056  * Creates a regular or fullscreen window.
1057  */
1058 bool WinGraphicsWindow::
1059 open_graphic_window() {
1060  DWORD window_style = make_style(_properties);
1061 
1062  wstring title;
1063  if (_properties.has_title()) {
1064  TextEncoder encoder;
1065  title = encoder.decode_text(_properties.get_title());
1066  }
1067 
1068  if (!_properties.has_size()) {
1069  // Just fill in a conservative default size if one isn't specified.
1070  _properties.set_size(640, 480);
1071  }
1072 
1073  WINDOW_METRICS metrics;
1074  bool has_origin;
1075  if (!calculate_metrics(fullscreen, window_style, metrics, has_origin)){
1076  return false;
1077  }
1078 
1079  const WindowClass &wclass = register_window_class(_properties);
1080  HINSTANCE hinstance = GetModuleHandle(nullptr);
1081 
1082  _hparent = nullptr;
1083 
1084  if (!fullscreen){
1085  WindowHandle *window_handle = _properties.get_parent_window();
1086  if (window_handle != nullptr) {
1087  windisplay_cat.info()
1088  << "Got parent_window " << *window_handle << "\n";
1089  WindowHandle::OSHandle *os_handle = window_handle->get_os_handle();
1090  if (os_handle != nullptr) {
1091  windisplay_cat.info()
1092  << "os_handle type " << os_handle->get_type() << "\n";
1093 
1094  if (os_handle->is_of_type(NativeWindowHandle::WinHandle::get_class_type())) {
1095  NativeWindowHandle::WinHandle *win_handle = DCAST(NativeWindowHandle::WinHandle, os_handle);
1096  _hparent = win_handle->get_handle();
1097  } else if (os_handle->is_of_type(NativeWindowHandle::IntHandle::get_class_type())) {
1098  NativeWindowHandle::IntHandle *int_handle = DCAST(NativeWindowHandle::IntHandle, os_handle);
1099  _hparent = (HWND)int_handle->get_handle();
1100  }
1101  }
1102  }
1103  _parent_window_handle = window_handle;
1104  } else {
1105  _parent_window_handle = nullptr;
1106  }
1107 
1108  if (!_hparent) { // This can be a regular window or a fullscreen window
1109  _hWnd = CreateWindowW(wclass._name.c_str(), title.c_str(), window_style,
1110  metrics.x, metrics.y,
1111  metrics.width,
1112  metrics.height,
1113  nullptr, nullptr, hinstance, 0);
1114  } else { // This is a regular window with a parent
1115  int x_origin = 0;
1116  int y_origin = 0;
1117 
1118  if (!fullscreen && has_origin) {
1119  x_origin = _properties.get_x_origin();
1120  y_origin = _properties.get_y_origin();
1121  }
1122 
1123  _hWnd = CreateWindowW(wclass._name.c_str(), title.c_str(),
1124  WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS ,
1125  x_origin, y_origin,
1126  _properties.get_x_size(), _properties.get_y_size(),
1127  _hparent, nullptr, hinstance, 0);
1128 
1129  if (_hWnd) {
1130  // join our keyboard state with the parents
1131 
1132  // Actually, let's not. Is there really any reason to do this? It
1133  // causes problems with the browser plugin--it deadlocks when the parent
1134  // process is waiting on the child process.
1135  // AttachThreadInput(GetWindowThreadProcessId(_hparent,NULL),
1136  // GetCurrentThreadId(),TRUE);
1137 
1138  WindowProperties properties;
1139  properties.set_foreground(true);
1140  system_changed_properties(properties);
1141  }
1142  }
1143 
1144  if (!_hWnd) {
1145  windisplay_cat.error()
1146  << "CreateWindow() failed!" << endl;
1147  show_error_message();
1148  return false;
1149  }
1150 
1151  // I'd prefer to CreateWindow after DisplayChange in case it messes up GL
1152  // somehow, but I need the window's black background to cover up the desktop
1153  // during the mode change.
1154 
1155  if (fullscreen){
1156  if (!do_fullscreen_enable()){
1157  return false;
1158  }
1159  }
1160 
1161  return true;
1162 }
1163 
1164 /**
1165  * This is a low-level function that just puts Windows in fullscreen mode.
1166  * Not to confuse with do_fullscreen_switch().
1167  */
1168 bool WinGraphicsWindow::
1169 do_fullscreen_enable() {
1170 
1171  HWND hDesktopWindow = GetDesktopWindow();
1172  HDC scrnDC = GetDC(hDesktopWindow);
1173  DWORD cur_bitdepth = GetDeviceCaps(scrnDC, BITSPIXEL);
1174  // DWORD drvr_ver = GetDeviceCaps(scrnDC, DRIVERVERSION); DWORD
1175  // cur_scrnwidth = GetDeviceCaps(scrnDC, HORZRES); DWORD cur_scrnheight =
1176  // GetDeviceCaps(scrnDC, VERTRES);
1177  ReleaseDC(hDesktopWindow, scrnDC);
1178 
1179  DWORD dwWidth = _properties.get_x_size();
1180  DWORD dwHeight = _properties.get_y_size();
1181  DWORD dwFullScreenBitDepth = cur_bitdepth;
1182 
1183  DEVMODE dm;
1184  reconsider_fullscreen_size(dwWidth, dwHeight, dwFullScreenBitDepth);
1185  if (!find_acceptable_display_mode(dwWidth, dwHeight, dwFullScreenBitDepth, dm)) {
1186  windisplay_cat.error()
1187  << "Videocard has no supported display resolutions at specified res ("
1188  << dwWidth << " x " << dwHeight << " x " << dwFullScreenBitDepth <<")\n";
1189  return false;
1190  }
1191 
1192  dm.dmPelsWidth = dwWidth;
1193  dm.dmPelsHeight = dwHeight;
1194  dm.dmBitsPerPel = dwFullScreenBitDepth;
1195  int chg_result = ChangeDisplaySettings(&dm, CDS_FULLSCREEN);
1196 
1197  if (chg_result != DISP_CHANGE_SUCCESSFUL) {
1198  windisplay_cat.error()
1199  << "ChangeDisplaySettings failed (error code: "
1200  << chg_result << ") for specified res: "
1201  << dm.dmPelsWidth << " x " << dm.dmPelsHeight
1202  << " x " << dm.dmBitsPerPel << ", "
1203  << dm.dmDisplayFrequency << " Hz\n";
1204  return false;
1205  }
1206 
1207  _fullscreen_display_mode = dm;
1208 
1209  _properties.set_origin(0, 0);
1210  _properties.set_size(dwWidth, dwHeight);
1211 
1212  return true;
1213 
1214 }
1215 
1216 /**
1217  * This is a low-level function that just gets Windows out of fullscreen mode.
1218  * Not to confuse with do_windowed_switch().
1219  */
1220 bool WinGraphicsWindow::
1221 do_fullscreen_disable() {
1222  int chg_result = ChangeDisplaySettings(nullptr, 0x0);
1223  if (chg_result != DISP_CHANGE_SUCCESSFUL) {
1224  windisplay_cat.warning()
1225  << "ChangeDisplaySettings failed to restore Windowed mode\n";
1226  return false;
1227  }
1228  return true;
1229 }
1230 
1231 /**
1232  * Adjusts the Z-order of a window after it has been moved.
1233  */
1234 void WinGraphicsWindow::
1235 adjust_z_order() {
1236  WindowProperties::ZOrder z_order = _properties.get_z_order();
1237  adjust_z_order(z_order, z_order);
1238 }
1239 
1240 /**
1241  * Adjusts the Z-order of a window after it has been moved.
1242  */
1243 void WinGraphicsWindow::
1244 adjust_z_order(WindowProperties::ZOrder last_z_order,
1245  WindowProperties::ZOrder this_z_order) {
1246  HWND order;
1247  bool do_change = false;
1248 
1249  switch (this_z_order) {
1250  case WindowProperties::Z_bottom:
1251  order = HWND_BOTTOM;
1252  do_change = true;
1253  break;
1254 
1255  case WindowProperties::Z_normal:
1256  if ((last_z_order != WindowProperties::Z_normal) &&
1257  // If we aren't changing the window order, don't move it to the top.
1258  (last_z_order != WindowProperties::Z_bottom ||
1259  _properties.get_foreground())
1260  // If the window was previously on the bottom, but it doesn't have
1261  // focus now, don't move it to the top; it will get moved the next
1262  // time we get focus.
1263  ) {
1264  order = HWND_NOTOPMOST;
1265  do_change = true;
1266  }
1267  break;
1268 
1269  case WindowProperties::Z_top:
1270  order = HWND_TOPMOST;
1271  do_change = true;
1272  break;
1273  }
1274  if (do_change) {
1275  BOOL result = SetWindowPos(_hWnd, order, 0,0,0,0,
1276  SWP_NOMOVE | SWP_NOSENDCHANGING | SWP_NOSIZE);
1277  if (!result) {
1278  windisplay_cat.warning()
1279  << "SetWindowPos failed.\n";
1280  }
1281  }
1282 }
1283 
1284 /**
1285  * Intended to be called whenever mouse motion is detected within the window,
1286  * this indicates that the mouse is within the window and tells Windows that
1287  * we want to be told when the mouse leaves the window.
1288  */
1289 void WinGraphicsWindow::
1290 track_mouse_leaving(HWND hwnd) {
1291  WinGraphicsPipe *winpipe;
1292  DCAST_INTO_V(winpipe, _pipe);
1293 
1294  TRACKMOUSEEVENT tme = {
1295  sizeof(TRACKMOUSEEVENT),
1296  TME_LEAVE,
1297  hwnd,
1298  0
1299  };
1300 
1301  // tell win32 to post WM_MOUSELEAVE msgs
1302  BOOL bSucceeded = TrackMouseEvent(&tme);
1303 
1304  if (!bSucceeded && windisplay_cat.is_debug()) {
1305  windisplay_cat.debug()
1306  << "TrackMouseEvent failed!, LastError=" << GetLastError() << endl;
1307  }
1308 
1309  _tracking_mouse_leaving = true;
1310 }
1311 
1312 /**
1313  * Attempts to set this window as the "focus" window, so that keyboard events
1314  * come here.
1315  */
1316 void WinGraphicsWindow::
1317 set_focus() {
1318  if (SetFocus(_hWnd) == nullptr && GetLastError() != 0) {
1319  // If the SetFocus() request failed, maybe we're running in the plugin
1320  // environment on Vista, with UAC enabled. In this case, we're not
1321  // allowed to assign focus to the Panda window for some stupid reason. So
1322  // instead, we have to ask the parent window (in the browser process) to
1323  // proxy our keyboard events for us.
1324  if (_parent_window_handle != nullptr && _window_handle != nullptr) {
1325  _parent_window_handle->request_keyboard_focus(_window_handle);
1326  } else {
1327  // Otherwise, something is wrong.
1328  windisplay_cat.error()
1329  << "SetFocus failed: " << GetLastError() << "\n";
1330  }
1331  }
1332 }
1333 
1334 /**
1335  * This is called to receive a keyboard event generated by proxy by another
1336  * window in a parent process. This hacky system is used in the web plugin
1337  * system to allow the Panda window to receive keyboard events on Vista, which
1338  * doesn't allow the Panda window to set keyboard focus to itself.
1339  */
1341 receive_windows_message(unsigned int msg, int wparam, int lparam) {
1342  // Well, we'll just deliver this directly to window_proc(), supplying our
1343  // own window handle. For the most part, we don't care about the window
1344  // handle anyway, but this might become an issue for the IME. TODO:
1345  // investigate IME issues.
1346 
1347  window_proc(_hWnd, msg, wparam, lparam);
1348 }
1349 
1350 /**
1351  * This is the nonstatic window_proc function. It is called to handle window
1352  * events for this particular window.
1353  */
1355 window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
1356  if (windisplay_cat.is_spam()) {
1357  windisplay_cat.spam()
1359  << " window_proc(" << (void *)this << ", " << hwnd << ", "
1360  << msg << ", " << wparam << ", " << lparam << ")\n";
1361  }
1362  WindowProperties properties;
1363 
1364  switch (msg) {
1365  case WM_MOUSEMOVE:
1366  if (!_tracking_mouse_leaving) {
1367  // need to re-call TrackMouseEvent every time mouse re-enters window
1368  track_mouse_leaving(hwnd);
1369  }
1370  set_cursor_in_window();
1371  if(handle_mouse_motion(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam))))
1372  return 0;
1373  break;
1374 
1375  case WM_INPUT:
1376  handle_raw_input((HRAWINPUT)lparam);
1377  break;
1378 
1379  case WM_MOUSELEAVE:
1380  _tracking_mouse_leaving = false;
1381  handle_mouse_exit();
1382  set_cursor_out_of_window();
1383  break;
1384 
1385  case WM_CREATE:
1386  {
1387  track_mouse_leaving(hwnd);
1388  ClearToBlack(hwnd, _properties);
1389 
1390  POINT cpos;
1391  GetCursorPos(&cpos);
1392  ScreenToClient(hwnd, &cpos);
1393  RECT clientRect;
1394  GetClientRect(hwnd, &clientRect);
1395  if (PtInRect(&clientRect,cpos)) {
1396  set_cursor_in_window(); // should window focus be true as well?
1397  } else {
1398  set_cursor_out_of_window();
1399  }
1400  }
1401  break;
1402 
1403  /*
1404  case WM_SHOWWINDOW:
1405  // You'd think WM_SHOWWINDOW would be just the thing for embedded windows,
1406  // but it turns out it's not sent to the child windows when the parent is
1407  // minimized. I guess it's only sent for an explicit call to ShowWindow,
1408  // phooey.
1409  {
1410  if (windisplay_cat.is_debug()) {
1411  windisplay_cat.debug()
1412  << "WM_SHOWWINDOW: " << hwnd << ", " << wparam << "\n";
1413  }
1414  if (wparam) {
1415  // Window is being shown.
1416  properties.set_minimized(false);
1417  } else {
1418  // Window is being hidden.
1419  properties.set_minimized(true);
1420  }
1421  system_changed_properties(properties);
1422  }
1423  break;
1424  */
1425 
1426  case WM_CLOSE:
1427  // This is a message from the system indicating that the user has
1428  // requested to close the window (e.g. alt-f4).
1429  {
1430  std::string close_request_event = get_close_request_event();
1431  if (!close_request_event.empty()) {
1432  // In this case, the app has indicated a desire to intercept the
1433  // request and process it directly.
1434  throw_event(close_request_event);
1435  return 0;
1436 
1437  } else {
1438  // In this case, the default case, the app does not intend to service
1439  // the request, so we do by closing the window.
1440  close_window();
1441  properties.set_open(false);
1442  system_changed_properties(properties);
1443 
1444  // TODO: make sure we release the GSG properly.
1445  }
1446  }
1447  break;
1448 
1449  case WM_CHILDACTIVATE:
1450  if (windisplay_cat.is_debug()) {
1451  windisplay_cat.debug()
1452  << "WM_CHILDACTIVATE: " << hwnd << "\n";
1453  }
1454  break;
1455 
1456  case WM_ACTIVATE:
1457  if (windisplay_cat.is_debug()) {
1458  windisplay_cat.debug()
1459  << "WM_ACTIVATE: " << hwnd << ", " << wparam << ", " << lparam << "\n";
1460  }
1461  properties.set_minimized((wparam & 0xffff0000) != 0);
1462  if ((wparam & 0xffff) != WA_INACTIVE)
1463  {
1464  properties.set_foreground(true);
1465  if (is_fullscreen())
1466  {
1467  // When a fullscreen window goes active, it automatically gets un-
1468  // minimized.
1469  int chg_result =
1470  ChangeDisplaySettings(&_fullscreen_display_mode, CDS_FULLSCREEN);
1471  if (chg_result != DISP_CHANGE_SUCCESSFUL) {
1472  const DEVMODE &dm = _fullscreen_display_mode;
1473  windisplay_cat.error()
1474  << "restore ChangeDisplaySettings failed (error code: "
1475  << chg_result << ") for specified res: "
1476  << dm.dmPelsWidth << " x " << dm.dmPelsHeight
1477  << " x " << dm.dmBitsPerPel << ", "
1478  << dm.dmDisplayFrequency << " Hz\n";
1479  }
1480 
1481  GdiFlush();
1482  SetWindowPos(_hWnd, HWND_TOP, 0,0,0,0, SWP_NOMOVE | SWP_NOSENDCHANGING | SWP_NOSIZE | SWP_NOOWNERZORDER);
1483  fullscreen_restored(properties);
1484  }
1485  }
1486  else
1487  {
1488  properties.set_foreground(false);
1489  if (is_fullscreen())
1490  {
1491  // When a fullscreen window goes inactive, it automatically gets
1492  // minimized.
1493  properties.set_minimized(true);
1494 
1495  // It seems order is important here. We must minimize the window
1496  // before restoring the display settings, or risk losing the
1497  // graphics context.
1498  ShowWindow(_hWnd, SW_MINIMIZE);
1499  GdiFlush();
1500  do_fullscreen_disable();
1501  fullscreen_minimized(properties);
1502  }
1503  }
1504 
1505  adjust_z_order();
1506  system_changed_properties(properties);
1507  break;
1508 
1509  case WM_SIZE:
1510  // Actually, since we don't return in WM_WINDOWPOSCHANGED, WM_SIZE won't
1511  // end up being called at all. This is more efficient according to MSDN.
1512  if (windisplay_cat.is_debug()) {
1513  windisplay_cat.debug()
1514  << "WM_SIZE: " << hwnd << ", " << wparam << "\n";
1515  }
1516  break;
1517 
1518  case WM_EXITSIZEMOVE:
1519  // handle_reshape();
1520  break;
1521 
1522  case WM_WINDOWPOSCHANGED:
1523  if (windisplay_cat.is_debug()) {
1524  windisplay_cat.debug()
1525  << "WM_WINDOWPOSCHANGED: " << hwnd << ", " << wparam << "\n";
1526  }
1527  if (_hWnd != nullptr) {
1528  handle_reshape();
1529  }
1530  adjust_z_order();
1531  return 0;
1532 
1533  case WM_PAINT:
1534  // In response to WM_PAINT, we check to see if there are any update
1535  // regions at all; if there are, we declare the window exposed. This is
1536  // used to implement !_unexposed_draw.
1537  if (GetUpdateRect(_hWnd, nullptr, false)) {
1538  if (windisplay_cat.is_spam()) {
1539  windisplay_cat.spam()
1540  << "Got update regions: " << this << "\n";
1541  }
1542  _got_expose_event = true;
1543  }
1544  break;
1545 
1546  case WM_LBUTTONDOWN:
1547  if (_lost_keypresses) {
1548  resend_lost_keypresses();
1549  }
1550  SetCapture(hwnd);
1551  _input->set_pointer_in_window(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam)));
1552  _input->button_down(MouseButton::button(0), get_message_time());
1553 
1554  // A button-click in the window means to grab the keyboard focus.
1555  set_focus();
1556  return 0;
1557 
1558  case WM_MBUTTONDOWN:
1559  if (_lost_keypresses) {
1560  resend_lost_keypresses();
1561  }
1562  SetCapture(hwnd);
1563  _input->set_pointer_in_window(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam)));
1564  _input->button_down(MouseButton::button(1), get_message_time());
1565  // A button-click in the window means to grab the keyboard focus.
1566  set_focus();
1567  return 0;
1568 
1569  case WM_RBUTTONDOWN:
1570  if (_lost_keypresses) {
1571  resend_lost_keypresses();
1572  }
1573  SetCapture(hwnd);
1574  _input->set_pointer_in_window(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam)));
1575  _input->button_down(MouseButton::button(2), get_message_time());
1576  // A button-click in the window means to grab the keyboard focus.
1577  set_focus();
1578  return 0;
1579 
1580  case WM_XBUTTONDOWN:
1581  {
1582  if (_lost_keypresses) {
1583  resend_lost_keypresses();
1584  }
1585  SetCapture(hwnd);
1586  int whichButton = GET_XBUTTON_WPARAM(wparam);
1587  _input->set_pointer_in_window(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam)));
1588  if (whichButton == XBUTTON1) {
1589  _input->button_down(MouseButton::button(3), get_message_time());
1590  } else if (whichButton == XBUTTON2) {
1591  _input->button_down(MouseButton::button(4), get_message_time());
1592  }
1593  }
1594  return 0;
1595 
1596  case WM_LBUTTONUP:
1597  if (_lost_keypresses) {
1598  resend_lost_keypresses();
1599  }
1600  ReleaseCapture();
1601  _input->button_up(MouseButton::button(0), get_message_time());
1602  return 0;
1603 
1604  case WM_MBUTTONUP:
1605  if (_lost_keypresses) {
1606  resend_lost_keypresses();
1607  }
1608  ReleaseCapture();
1609  _input->button_up(MouseButton::button(1), get_message_time());
1610  return 0;
1611 
1612  case WM_RBUTTONUP:
1613  if (_lost_keypresses) {
1614  resend_lost_keypresses();
1615  }
1616  ReleaseCapture();
1617  _input->button_up(MouseButton::button(2), get_message_time());
1618  return 0;
1619 
1620  case WM_XBUTTONUP:
1621  {
1622  if (_lost_keypresses) {
1623  resend_lost_keypresses();
1624  }
1625  ReleaseCapture();
1626  int whichButton = GET_XBUTTON_WPARAM(wparam);
1627  if (whichButton == XBUTTON1) {
1628  _input->button_up(MouseButton::button(3), get_message_time());
1629  } else if (whichButton == XBUTTON2) {
1630  _input->button_up(MouseButton::button(4), get_message_time());
1631  }
1632  }
1633  return 0;
1634 
1635  case WM_MOUSEWHEEL:
1636  {
1637  int delta = GET_WHEEL_DELTA_WPARAM(wparam);
1638 
1639  POINT point;
1640  GetCursorPos(&point);
1641  ScreenToClient(hwnd, &point);
1642  double time = get_message_time();
1643 
1644  if (delta >= 0) {
1645  while (delta > 0) {
1646  handle_keypress(MouseButton::wheel_up(), point.x, point.y, time);
1647  handle_keyrelease(MouseButton::wheel_up(), time);
1648  delta -= WHEEL_DELTA;
1649  }
1650  } else {
1651  while (delta < 0) {
1652  handle_keypress(MouseButton::wheel_down(), point.x, point.y, time);
1653  handle_keyrelease(MouseButton::wheel_down(), time);
1654  delta += WHEEL_DELTA;
1655  }
1656  }
1657  return 0;
1658  }
1659  break;
1660 
1661 
1662  case WM_IME_SETCONTEXT:
1663  if (!ime_hide)
1664  break;
1665 
1666  windisplay_cat.debug() << "hwnd = " << hwnd << " and GetFocus = " << GetFocus() << endl;
1667  _ime_hWnd = ImmGetDefaultIMEWnd(hwnd);
1668  if (::SendMessage(_ime_hWnd, WM_IME_CONTROL, IMC_CLOSESTATUSWINDOW, 0))
1669  // if (::SendMessage(hwnd, WM_IME_CONTROL, IMC_CLOSESTATUSWINDOW, 0))
1670  windisplay_cat.debug() << "SendMessage failed for " << _ime_hWnd << endl;
1671  else
1672  windisplay_cat.debug() << "SendMessage Succeeded for " << _ime_hWnd << endl;
1673 
1674  windisplay_cat.debug() << "wparam is " << wparam << ", lparam is " << lparam << endl;
1675  lparam &= ~ISC_SHOWUIALL;
1676  if (ImmIsUIMessage(_ime_hWnd, msg, wparam, lparam))
1677  windisplay_cat.debug() << "wparam is " << wparam << ", lparam is " << lparam << endl;
1678 
1679  break;
1680 
1681 
1682  case WM_IME_NOTIFY:
1683  if (wparam == IMN_SETOPENSTATUS) {
1684  HIMC hIMC = ImmGetContext(hwnd);
1685  nassertr(hIMC != 0, 0);
1686  _ime_open = (ImmGetOpenStatus(hIMC) != 0);
1687  if (!_ime_open) {
1688  _ime_active = false; // Sanity enforcement.
1689  }
1690  if (ime_hide) {
1691  // if (0) {
1692  COMPOSITIONFORM comf;
1693  CANDIDATEFORM canf;
1694  ImmGetCompositionWindow(hIMC, &comf);
1695  ImmGetCandidateWindow(hIMC, 0, &canf);
1696  windisplay_cat.debug() <<
1697  "comf style " << comf.dwStyle <<
1698  " comf point: x" << comf.ptCurrentPos.x << ",y " << comf.ptCurrentPos.y <<
1699  " comf rect: l " << comf.rcArea.left << ",t " << comf.rcArea.top << ",r " <<
1700  comf.rcArea.right << ",b " << comf.rcArea.bottom << endl;
1701  windisplay_cat.debug() <<
1702  "canf style " << canf.dwStyle <<
1703  " canf point: x" << canf.ptCurrentPos.x << ",y " << canf.ptCurrentPos.y <<
1704  " canf rect: l " << canf.rcArea.left << ",t " << canf.rcArea.top << ",r " <<
1705  canf.rcArea.right << ",b " << canf.rcArea.bottom << endl;
1706  comf.dwStyle = CFS_POINT;
1707  comf.ptCurrentPos.x = 2000;
1708  comf.ptCurrentPos.y = 2000;
1709 
1710  canf.dwStyle = CFS_EXCLUDE;
1711  canf.dwIndex = 0;
1712  canf.ptCurrentPos.x = 0;
1713  canf.ptCurrentPos.y = 0;
1714  canf.rcArea.left = 0;
1715  canf.rcArea.top = 0;
1716  canf.rcArea.right = 640;
1717  canf.rcArea.bottom = 480;
1718 
1719 #if 0
1720  comf.rcArea.left = 200;
1721  comf.rcArea.top = 200;
1722  comf.rcArea.right = 0;
1723  comf.rcArea.bottom = 0;
1724 #endif
1725 
1726  if (ImmSetCompositionWindow(hIMC, &comf))
1727  windisplay_cat.debug() << "set composition form: success\n";
1728  for (int i=0; i<3; ++i) {
1729  if (ImmSetCandidateWindow(hIMC, &canf))
1730  windisplay_cat.debug() << "set candidate form: success\n";
1731  canf.dwIndex++;
1732  }
1733  }
1734 
1735  ImmReleaseContext(hwnd, hIMC);
1736  }
1737  break;
1738 
1739  case WM_IME_STARTCOMPOSITION:
1740  support_overlay_window(true);
1741  _ime_active = true;
1742  break;
1743 
1744  case WM_IME_ENDCOMPOSITION:
1745  support_overlay_window(false);
1746  _ime_active = false;
1747 
1748  if (ime_aware) {
1749  wstring ws;
1750  _input->candidate(ws, 0, 0, 0);
1751  }
1752 
1753  break;
1754 
1755  case WM_IME_COMPOSITION:
1756  if (ime_aware) {
1757 
1758  // If the ime window is not marked as active at this point, we must be
1759  // in the process of closing it down (in close_ime), and we don't want
1760  // to send the current composition string in that case. But we do need
1761  // to return 0 to tell windows not to try to send the composition string
1762  // through WM_CHAR messages.
1763  if (!_ime_active) {
1764  return 0;
1765  }
1766 
1767  HIMC hIMC = ImmGetContext(hwnd);
1768  nassertr(hIMC != 0, 0);
1769 
1770  DWORD result_size = 0;
1771  static const int ime_buffer_size = 256;
1772  static const int ime_buffer_size_bytes = ime_buffer_size / sizeof(wchar_t);
1773  wchar_t ime_buffer[ime_buffer_size];
1774  size_t cursor_pos, delta_start;
1775 
1776  if (lparam & GCS_RESULTSTR) {
1777  result_size = ImmGetCompositionStringW(hIMC, GCS_RESULTSTR,
1778  ime_buffer, ime_buffer_size_bytes);
1779  size_t num_chars = result_size / sizeof(wchar_t);
1780  for (size_t i = 0; i < num_chars; ++i) {
1781  _input->keystroke(ime_buffer[i]);
1782  }
1783  }
1784 
1785  if (lparam & GCS_COMPSTR) {
1786  result_size = ImmGetCompositionStringW(hIMC, GCS_CURSORPOS, nullptr, 0);
1787  cursor_pos = result_size & 0xffff;
1788 
1789  result_size = ImmGetCompositionStringW(hIMC, GCS_DELTASTART, nullptr, 0);
1790  delta_start = result_size & 0xffff;
1791  result_size = ImmGetCompositionStringW(hIMC, GCS_COMPSTR, ime_buffer, ime_buffer_size);
1792  size_t num_chars = result_size / sizeof(wchar_t);
1793 
1794  _input->candidate(wstring(ime_buffer, num_chars),
1795  std::min(cursor_pos, delta_start),
1796  std::max(cursor_pos, delta_start),
1797  cursor_pos);
1798  }
1799  ImmReleaseContext(hwnd, hIMC);
1800  }
1801  break;
1802 
1803  case WM_CHAR:
1804  // Ignore WM_CHAR messages if we have the IME open, since everything will
1805  // come in through WM_IME_COMPOSITION. (It's supposed to come in through
1806  // WM_CHAR, too, but there seems to be a bug in Win2000 in that it only
1807  // sends question mark characters through here.)
1808 
1809  // Actually, probably that "bug" was due to the fact that we were
1810  // previously using the ANSI versions of RegisterClass etc., in which case
1811  // the actual value passed to WM_CHAR seems to be poorly defined. Now we
1812  // are using RegisterClassW etc., which means WM_CHAR is absolutely
1813  // supposed to be utf-16.
1814  if (!_ime_open) {
1815  _input->keystroke(wparam);
1816  }
1817  break;
1818 
1819  case WM_SYSKEYDOWN:
1820  if (_lost_keypresses) {
1821  resend_lost_keypresses();
1822  }
1823  if (windisplay_cat.is_debug()) {
1824  windisplay_cat.debug()
1825  << "syskeydown: " << wparam << " (" << lookup_key(wparam) << ")\n";
1826  }
1827  {
1828  // Alt and F10 are sent as WM_SYSKEYDOWN instead of WM_KEYDOWN want to
1829  // use defwindproc on Alt syskey so std windows cmd Alt-F4 works, etc
1830  POINT point;
1831  GetCursorPos(&point);
1832  ScreenToClient(hwnd, &point);
1833  handle_keypress(lookup_key(wparam), point.x, point.y,
1834  get_message_time());
1835 
1836  if ((lparam & 0x40000000) == 0) {
1837  handle_raw_keypress(lookup_raw_key(lparam), get_message_time());
1838  }
1839 
1840 /*
1841  * wparam does not contain leftright information for SHIFT, CONTROL, or ALT,
1842  * so we have to track their status and do the right thing. We'll send the
1843  * leftright specific key event along with the general key event. Key
1844  * repeating is not being handled consistently for LALT and RALT, but from
1845  * comments below, it's only being handled the way it is for backspace, so
1846  * we'll leave it as is.
1847  */
1848  if (wparam == VK_MENU) {
1849  if ((GetKeyState(VK_LMENU) & 0x8000) != 0 && ! _lalt_down) {
1850  handle_keypress(KeyboardButton::lalt(), point.x, point.y,
1851  get_message_time());
1852  _lalt_down = true;
1853  }
1854  if ((GetKeyState(VK_RMENU) & 0x8000) != 0 && ! _ralt_down) {
1855  handle_keypress(KeyboardButton::ralt(), point.x, point.y,
1856  get_message_time());
1857  _ralt_down = true;
1858  }
1859  }
1860  if (wparam == VK_F10) {
1861  // bypass default windproc F10 behavior (it activates the main menu,
1862  // but we have none)
1863  return 0;
1864  }
1865  }
1866  break;
1867 
1868  case WM_SYSCOMMAND:
1869  if (wparam == SC_KEYMENU) {
1870  // if Alt is released (alone wo other keys), defwindproc will send this
1871  // command, which will 'activate' the title bar menu (we have none) and
1872  // give focus to it. we don't want this to happen, so kill this msg.
1873 
1874  // Note that the WM_SYSKEYUP message for Alt has already been sent (if
1875  // it is going to be), so ignoring this special message does no harm.
1876  return 0;
1877  }
1878  break;
1879 
1880  case WM_KEYDOWN:
1881  if (_lost_keypresses) {
1882  resend_lost_keypresses();
1883  }
1884  if (windisplay_cat.is_debug()) {
1885  windisplay_cat.debug()
1886  << "keydown: " << wparam << " (" << lookup_key(wparam) << ")\n";
1887  }
1888 
1889  // If this bit is not zero, this is just a keyrepeat echo; we ignore these
1890  // for handle_keypress (we respect keyrepeat only for handle_keystroke).
1891  if ((lparam & 0x40000000) == 0) {
1892  POINT point;
1893  GetCursorPos(&point);
1894  ScreenToClient(hwnd, &point);
1895  handle_keypress(lookup_key(wparam), point.x, point.y,
1896  get_message_time());
1897  handle_raw_keypress(lookup_raw_key(lparam), get_message_time());
1898 
1899  // wparam does not contain leftright information for SHIFT, CONTROL, or
1900  // ALT, so we have to track their status and do the right thing. We'll
1901  // send the leftright specific key event along with the general key
1902  // event.
1903  if (wparam == VK_SHIFT) {
1904  if ((GetKeyState(VK_LSHIFT) & 0x8000) != 0 && ! _lshift_down) {
1905  handle_keypress(KeyboardButton::lshift(), point.x, point.y,
1906  get_message_time());
1907  _lshift_down = true;
1908  }
1909  if ((GetKeyState(VK_RSHIFT) & 0x8000) != 0 && ! _rshift_down) {
1910  handle_keypress(KeyboardButton::rshift(), point.x, point.y,
1911  get_message_time());
1912  _rshift_down = true;
1913  }
1914  } else if(wparam == VK_CONTROL) {
1915  if ((GetKeyState(VK_LCONTROL) & 0x8000) != 0 && ! _lcontrol_down) {
1916  handle_keypress(KeyboardButton::lcontrol(), point.x, point.y,
1917  get_message_time());
1918  _lcontrol_down = true;
1919  }
1920  if ((GetKeyState(VK_RCONTROL) & 0x8000) != 0 && ! _rcontrol_down) {
1921  handle_keypress(KeyboardButton::rcontrol(), point.x, point.y,
1922  get_message_time());
1923  _rcontrol_down = true;
1924  }
1925  }
1926 
1927  // Handle Cntrl-V paste from clipboard. Is there a better way to detect
1928  // this hotkey?
1929  if ((wparam=='V') && (GetKeyState(VK_CONTROL) < 0) &&
1930  !_input_devices.empty()) {
1931  HGLOBAL hglb;
1932  char *lptstr;
1933 
1934  if (IsClipboardFormatAvailable(CF_TEXT) && OpenClipboard(nullptr)) {
1935  // Maybe we should support CF_UNICODETEXT if it is available too?
1936  hglb = GetClipboardData(CF_TEXT);
1937  if (hglb!=nullptr) {
1938  lptstr = (char *) GlobalLock(hglb);
1939  if (lptstr != nullptr) {
1940  char *pChar;
1941  for (pChar = lptstr; *pChar; pChar++) {
1942  _input->keystroke((uchar)*pChar);
1943  }
1944  GlobalUnlock(hglb);
1945  }
1946  }
1947  CloseClipboard();
1948  }
1949  }
1950  } else {
1951  // Actually, for now we'll respect the repeat anyway, just so we support
1952  // backspace properly. Rethink later.
1953  POINT point;
1954  GetCursorPos(&point);
1955  ScreenToClient(hwnd, &point);
1956  handle_keypress(lookup_key(wparam), point.x, point.y,
1957  get_message_time());
1958 
1959 /*
1960  * wparam does not contain leftright information for SHIFT, CONTROL, or ALT,
1961  * so we have to track their status and do the right thing. We'll send the
1962  * leftright specific key event along with the general key event. If the user
1963  * presses LSHIFT and then RSHIFT, the RSHIFT event will come in with the
1964  * keyrepeat flag on (i.e. it will end up in this block). The logic below
1965  * should detect this correctly and only send the RSHIFT event. Note that the
1966  * CONTROL event will be sent twice, once for each keypress. Since keyrepeats
1967  * are currently being sent simply as additional keypress events, that should
1968  * be okay for now.
1969  */
1970  if (wparam == VK_SHIFT) {
1971  if (((GetKeyState(VK_LSHIFT) & 0x8000) != 0) && ! _lshift_down ) {
1972  handle_keypress(KeyboardButton::lshift(), point.x, point.y,
1973  get_message_time());
1974  _lshift_down = true;
1975  } else if (((GetKeyState(VK_RSHIFT) & 0x8000) != 0) && ! _rshift_down ) {
1976  handle_keypress(KeyboardButton::rshift(), point.x, point.y,
1977  get_message_time());
1978  _rshift_down = true;
1979  } else {
1980  if ((GetKeyState(VK_LSHIFT) & 0x8000) != 0) {
1981  handle_keypress(KeyboardButton::lshift(), point.x, point.y,
1982  get_message_time());
1983  }
1984  if ((GetKeyState(VK_RSHIFT) & 0x8000) != 0) {
1985  handle_keypress(KeyboardButton::rshift(), point.x, point.y,
1986  get_message_time());
1987  }
1988  }
1989  } else if(wparam == VK_CONTROL) {
1990  if (((GetKeyState(VK_LCONTROL) & 0x8000) != 0) && ! _lcontrol_down ) {
1991  handle_keypress(KeyboardButton::lcontrol(), point.x, point.y,
1992  get_message_time());
1993  _lcontrol_down = true;
1994  } else if (((GetKeyState(VK_RCONTROL) & 0x8000) != 0) && ! _rcontrol_down ) {
1995  handle_keypress(KeyboardButton::rcontrol(), point.x, point.y,
1996  get_message_time());
1997  _rcontrol_down = true;
1998  } else {
1999  if ((GetKeyState(VK_LCONTROL) & 0x8000) != 0) {
2000  handle_keypress(KeyboardButton::lcontrol(), point.x, point.y,
2001  get_message_time());
2002  }
2003  if ((GetKeyState(VK_RCONTROL) & 0x8000) != 0) {
2004  handle_keypress(KeyboardButton::rcontrol(), point.x, point.y,
2005  get_message_time());
2006  }
2007  }
2008  }
2009  }
2010  break;
2011 
2012  case WM_SYSKEYUP:
2013  case WM_KEYUP:
2014  if (_lost_keypresses) {
2015  resend_lost_keypresses();
2016  }
2017  if (windisplay_cat.is_debug()) {
2018  windisplay_cat.debug()
2019  << "keyup: " << wparam << " (" << lookup_key(wparam) << ")\n";
2020  }
2021  handle_keyrelease(lookup_key(wparam), get_message_time());
2022  handle_raw_keyrelease(lookup_raw_key(lparam), get_message_time());
2023 
2024  // wparam does not contain leftright information for SHIFT, CONTROL, or
2025  // ALT, so we have to track their status and do the right thing. We'll
2026  // send the leftright specific key event along with the general key event.
2027  if (wparam == VK_SHIFT) {
2028  if ((GetKeyState(VK_LSHIFT) & 0x8000) == 0 && _lshift_down) {
2029  handle_keyrelease(KeyboardButton::lshift(), get_message_time());
2030  _lshift_down = false;
2031  }
2032  if ((GetKeyState(VK_RSHIFT) & 0x8000) == 0 && _rshift_down) {
2033  handle_keyrelease(KeyboardButton::rshift(), get_message_time());
2034  _rshift_down = false;
2035  }
2036  } else if(wparam == VK_CONTROL) {
2037  if ((GetKeyState(VK_LCONTROL) & 0x8000) == 0 && _lcontrol_down) {
2038  handle_keyrelease(KeyboardButton::lcontrol(), get_message_time());
2039  _lcontrol_down = false;
2040  }
2041  if ((GetKeyState(VK_RCONTROL) & 0x8000) == 0 && _rcontrol_down) {
2042  handle_keyrelease(KeyboardButton::rcontrol(), get_message_time());
2043  _rcontrol_down = false;
2044  }
2045  } else if(wparam == VK_MENU) {
2046  if ((GetKeyState(VK_LMENU) & 0x8000) == 0 && _lalt_down) {
2047  handle_keyrelease(KeyboardButton::lalt(), get_message_time());
2048  _lalt_down = false;
2049  }
2050  if ((GetKeyState(VK_RMENU) & 0x8000) == 0 && _ralt_down) {
2051  handle_keyrelease(KeyboardButton::ralt(), get_message_time());
2052  _ralt_down = false;
2053  }
2054  }
2055  break;
2056 
2057  case WM_KILLFOCUS:
2058  if (windisplay_cat.is_debug()) {
2059  windisplay_cat.debug()
2060  << "killfocus\n";
2061  }
2062 
2063  _input->focus_lost(get_message_time());
2064  properties.set_foreground(false);
2065  system_changed_properties(properties);
2066  break;
2067 
2068  case WM_SETFOCUS:
2069  // You would think that this would be a good time to call
2070  // resend_lost_keypresses(), but it turns out that we get WM_SETFOCUS
2071  // slightly before Windows starts resending key updown events to us.
2072 
2073 /*
2074  * In particular, if the user restored focus using alt-tab, then at this point
2075  * the keyboard state will indicate that both the alt and tab keys are held
2076  * down. However, there is a small window of opportunity for the user to
2077  * release these keys before Windows starts telling us about keyup events.
2078  * Thus, if we record the fact that alt and tab are being held down now, we
2079  * may miss the keyup events for them, and they can get "stuck" down.
2080  */
2081 
2082  // So we have to defer calling resend_lost_keypresses() until we know
2083  // Windows is ready to send us key updown events. I don't know when we
2084  // can guarantee that, except when we actually do start to receive key
2085  // updown events, so that call is made there.
2086 
2087  if (windisplay_cat.is_debug()) {
2088  windisplay_cat.debug()
2089  << "setfocus\n";
2090  }
2091 
2092  if (_lost_keypresses) {
2093  resend_lost_keypresses();
2094  }
2095 
2096  properties.set_foreground(true);
2097  system_changed_properties(properties);
2098  break;
2099 
2100  case PM_ACTIVE:
2101  if (windisplay_cat.is_debug()) {
2102  windisplay_cat.debug()
2103  << "PM_ACTIVE\n";
2104  }
2105  properties.set_foreground(true);
2106  system_changed_properties(properties);
2107  break;
2108 
2109  case PM_INACTIVE:
2110  if (windisplay_cat.is_debug()) {
2111  windisplay_cat.debug()
2112  << "PM_INACTIVE\n";
2113  }
2114  properties.set_foreground(false);
2115  system_changed_properties(properties);
2116  break;
2117 
2118  case WM_DPICHANGED:
2119  // The window moved to a monitor of different DPI, or someone changed the
2120  // DPI setting in the configuration panel.
2121  if (windisplay_cat.is_debug()) {
2122  windisplay_cat.debug() << "DPI changed to " << LOWORD(wparam);
2123 
2124  if (LOWORD(wparam) != HIWORD(wparam)) {
2125  windisplay_cat.debug(false) << "x" << HIWORD(wparam) << "\n";
2126  } else {
2127  windisplay_cat.debug(false) << "\n";
2128  }
2129  }
2130  // Resize the window if requested to match the new DPI. Obviously, don't
2131  // do this if a fixed size was requested.
2132  if (!_properties.get_fixed_size() && dpi_window_resize) {
2133  RECT &rect = *(LPRECT)lparam;
2134  SetWindowPos(_hWnd, HWND_TOP, rect.left, rect.top,
2135  rect.right - rect.left, rect.bottom - rect.top,
2136  SWP_NOZORDER | SWP_NOACTIVATE);
2137  }
2138  break;
2139 
2140  case WM_TOUCH:
2141  _num_touches = LOWORD(wparam);
2142  if (_num_touches > MAX_TOUCHES) {
2143  _num_touches = MAX_TOUCHES;
2144  }
2145  if (pGetTouchInputInfo != 0) {
2146  pGetTouchInputInfo((HTOUCHINPUT)lparam, _num_touches, _touches, sizeof(TOUCHINPUT));
2147  pCloseTouchInputHandle((HTOUCHINPUT)lparam);
2148  }
2149  break;
2150  }
2151 
2152  // do custom messages processing if any has been set
2153  for ( WinProcClasses::iterator it=_window_proc_classes.begin() ; it != _window_proc_classes.end(); it++ ){
2154  (*it)->wnd_proc(this, hwnd, msg, wparam, lparam);
2155  }
2156 
2157  return DefWindowProcW(hwnd, msg, wparam, lparam);
2158 }
2159 
2160 
2161 /**
2162  * This is attached to the window class for all WinGraphicsWindow windows; it
2163  * is called to handle window events.
2164  */
2165 LONG WINAPI WinGraphicsWindow::
2166 static_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
2167  // Look up the window in our global map.
2168  WindowHandles::const_iterator wi;
2169  wi = _window_handles.find(hwnd);
2170  if (wi != _window_handles.end()) {
2171  // We found the window.
2172  return (*wi).second->window_proc(hwnd, msg, wparam, lparam);
2173  }
2174 
2175  // The window wasn't in the map; we must be creating it right now.
2176  if (_creating_window != nullptr) {
2177  return _creating_window->window_proc(hwnd, msg, wparam, lparam);
2178  }
2179 
2180  // Oops, we weren't creating a window! Don't know how to handle the
2181  // message, so just pass it on to Windows to deal with it.
2182  return DefWindowProcW(hwnd, msg, wparam, lparam);
2183 }
2184 
2185 /**
2186  * Handles one event from the message queue.
2187  */
2188 void WinGraphicsWindow::
2189 process_1_event() {
2190  MSG msg;
2191 
2192  if (!GetMessage(&msg, nullptr, 0, 0)) {
2193  // WM_QUIT received. We need a cleaner way to deal with this.
2194  // DestroyAllWindows(false);
2195  exit(msg.wParam); // this will invoke AtExitFn
2196  }
2197 
2198  // Translate virtual key messages
2199  TranslateMessage(&msg);
2200  // Call window_proc
2201  DispatchMessage(&msg);
2202 }
2203 
2204 /**
2205  * Called when the keyboard focus has been restored to the window after it has
2206  * been lost for a time, this rechecks the keyboard state and generates key
2207  * up/down messages for keys that have changed state in the meantime.
2208  */
2209 void WinGraphicsWindow::
2210 resend_lost_keypresses() {
2211  nassertv(_lost_keypresses);
2212  // This is now a no-op. Not sure we really want to generate new "down" or
2213  // "resume" events for keys that were held while the window focus is
2214  // restored.
2215 
2216  _lost_keypresses = false;
2217 }
2218 
2219 /**
2220  * Changes _cursor_window from its current value to the indicated value. This
2221  * also changes the cursor properties appropriately.
2222  */
2223 void WinGraphicsWindow::
2224 update_cursor_window(WinGraphicsWindow *to_window) {
2225  bool hide_cursor = false;
2226  if (to_window == nullptr) {
2227  // We are leaving a graphics window; we should restore the Win2000
2228  // effects.
2229  if (_got_saved_params) {
2230  SystemParametersInfo(SPI_SETMOUSETRAILS, _saved_mouse_trails,
2231  0, 0);
2232  SystemParametersInfo(SPI_SETCURSORSHADOW, 0,
2233  _saved_cursor_shadow ? (PVOID)1 : nullptr, 0);
2234  SystemParametersInfo(SPI_SETMOUSEVANISH, 0,
2235  _saved_mouse_vanish ? (PVOID)1 : nullptr, 0);
2236  _got_saved_params = false;
2237  }
2238 
2239  } else {
2240  const WindowProperties &to_props = to_window->get_properties();
2241  hide_cursor = to_props.get_cursor_hidden();
2242 
2243  // We are entering a graphics window; we should save and disable the
2244  // Win2000 effects. These don't work at all well over a 3-D window.
2245 
2246  // These parameters are only defined for Win2000XP, but they should just
2247  // cause a silent error on earlier OS's, which is OK.
2248  if (!_got_saved_params) {
2249  SystemParametersInfo(SPI_GETMOUSETRAILS, 0,
2250  &_saved_mouse_trails, 0);
2251  SystemParametersInfo(SPI_GETCURSORSHADOW, 0,
2252  &_saved_cursor_shadow, 0);
2253  SystemParametersInfo(SPI_GETMOUSEVANISH, 0,
2254  &_saved_mouse_vanish, 0);
2255  _got_saved_params = true;
2256 
2257  SystemParametersInfo(SPI_SETMOUSETRAILS, 0, (PVOID)0, 0);
2258  SystemParametersInfo(SPI_SETCURSORSHADOW, 0, (PVOID)false, 0);
2259  SystemParametersInfo(SPI_SETMOUSEVANISH, 0, (PVOID)false, 0);
2260  }
2261 
2262  SetCursor(to_window->_cursor);
2263  }
2264 
2265  hide_or_show_cursor(hide_cursor);
2266 
2267  _cursor_window = to_window;
2268 }
2269 
2270 /**
2271  * Hides or shows the mouse cursor according to the indicated parameter. This
2272  * is normally called when the mouse wanders into or out of a window with the
2273  * cursor_hidden properties.
2274  */
2275 void WinGraphicsWindow::
2276 hide_or_show_cursor(bool hide_cursor) {
2277  if (hide_cursor) {
2278  if (!_cursor_hidden) {
2279  ShowCursor(false);
2280  _cursor_hidden = true;
2281  }
2282  } else {
2283  if (_cursor_hidden) {
2284  ShowCursor(true);
2285  _cursor_hidden = false;
2286  }
2287  }
2288 }
2289 
2290 // don't pick any video modes < MIN_REFRESH_RATE Hz
2291 #define MIN_REFRESH_RATE 60
2292 // EnumDisplaySettings may indicate 0 or 1 for refresh rate, which means use
2293 // driver default rate (assume its >min_refresh_rate)
2294 #define ACCEPTABLE_REFRESH_RATE(RATE) ((RATE >= MIN_REFRESH_RATE) || (RATE==0) || (RATE==1))
2295 
2296 /**
2297  * Looks for a fullscreen mode that meets the specified size and bitdepth
2298  * requirements. Returns true if a suitable mode is found, false otherwise.
2299  */
2300 bool WinGraphicsWindow::
2301 find_acceptable_display_mode(DWORD dwWidth, DWORD dwHeight, DWORD bpp,
2302  DEVMODE &dm) {
2303 
2304  // Get the current mode. We'll try to match the refresh rate.
2305  DEVMODE cur_dm;
2306  ZeroMemory(&cur_dm, sizeof(cur_dm));
2307  cur_dm.dmSize = sizeof(cur_dm);
2308  EnumDisplaySettings(nullptr, ENUM_CURRENT_SETTINGS, &cur_dm);
2309 
2310  int modenum = 0;
2311  int saved_modenum = -1;
2312 
2313  while (1) {
2314  ZeroMemory(&dm, sizeof(dm));
2315  dm.dmSize = sizeof(dm);
2316 
2317  if (!EnumDisplaySettings(nullptr, modenum, &dm)) {
2318  break;
2319  }
2320 
2321  if ((dm.dmPelsWidth == dwWidth) && (dm.dmPelsHeight == dwHeight) &&
2322  (dm.dmBitsPerPel == bpp)) {
2323  // If this also matches in refresh rate, we're done here. Otherwise,
2324  // save this as a second choice for later.
2325  if (dm.dmDisplayFrequency == cur_dm.dmDisplayFrequency) {
2326  return true;
2327  } else if (saved_modenum == -1) {
2328  saved_modenum = modenum;
2329  }
2330  }
2331  modenum++;
2332  }
2333 
2334  // Failed to find an exact match, but we do have a match that didn't match
2335  // the refresh rate.
2336  if (saved_modenum != -1) {
2337  ZeroMemory(&dm, sizeof(dm));
2338  dm.dmSize = sizeof(dm);
2339 
2340  if (EnumDisplaySettings(nullptr, saved_modenum, &dm)) {
2341  return true;
2342  }
2343  }
2344 
2345  return false;
2346 }
2347 
2348 /**
2349  * Pops up a dialog box with the indicated Windows error message ID (or the
2350  * last error message generated) for meaningful display to the user.
2351  */
2352 void WinGraphicsWindow::
2353 show_error_message(DWORD message_id) {
2354  LPTSTR message_buffer;
2355 
2356  if (message_id == 0) {
2357  message_id = GetLastError();
2358  }
2359 
2360  FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
2361  nullptr, message_id,
2362  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), //The user default language
2363  (LPTSTR)&message_buffer, // the weird ptrptr->ptr cast is intentional, see FORMAT_MESSAGE_ALLOCATE_BUFFER
2364  1024, nullptr);
2365  MessageBox(GetDesktopWindow(), message_buffer, _T(errorbox_title), MB_OK);
2366  windisplay_cat.fatal() << "System error msg: " << message_buffer << endl;
2367  LocalFree(message_buffer);
2368 }
2369 
2370 /**
2371  *
2372  */
2373 void WinGraphicsWindow::
2374 handle_keypress(ButtonHandle key, int x, int y, double time) {
2375  _input->set_pointer_in_window(x, y);
2376  if (key != ButtonHandle::none()) {
2377  _input->button_down(key, time);
2378  }
2379 }
2380 
2381 /**
2382  * Indicates we detected a key was already down when the focus is restored to
2383  * the window. Mainly useful for tracking the state of modifier keys.
2384  */
2385 void WinGraphicsWindow::
2386 handle_keyresume(ButtonHandle key, double time) {
2387  if (key != ButtonHandle::none()) {
2388  _input->button_resume_down(key, time);
2389  }
2390 }
2391 
2392 /**
2393  *
2394  */
2395 void WinGraphicsWindow::
2396 handle_keyrelease(ButtonHandle key, double time) {
2397  if (key != ButtonHandle::none()) {
2398  _input->button_up(key, time);
2399  }
2400 }
2401 
2402 /**
2403  *
2404  */
2405 void WinGraphicsWindow::
2406 handle_raw_keypress(ButtonHandle key, double time) {
2407  if (key != ButtonHandle::none()) {
2408  _input->raw_button_down(key, time);
2409  }
2410 }
2411 
2412 /**
2413  *
2414  */
2415 void WinGraphicsWindow::
2416 handle_raw_keyrelease(ButtonHandle key, double time) {
2417  if (key != ButtonHandle::none()) {
2418  _input->raw_button_up(key, time);
2419  }
2420 }
2421 
2422 /**
2423  * Translates the keycode reported by Windows to an appropriate Panda
2424  * ButtonHandle.
2425  */
2426 ButtonHandle WinGraphicsWindow::
2427 lookup_key(WPARAM wparam) const {
2428  // First, check for a few buttons that we filter out when the IME window is
2429  // open.
2430  if (!_ime_active) {
2431  switch(wparam) {
2432  case VK_BACK: return KeyboardButton::backspace();
2433  case VK_DELETE: return KeyboardButton::del();
2434  case VK_ESCAPE: return KeyboardButton::escape();
2435  case VK_SPACE: return KeyboardButton::space();
2436  case VK_UP: return KeyboardButton::up();
2437  case VK_DOWN: return KeyboardButton::down();
2438  case VK_LEFT: return KeyboardButton::left();
2439  case VK_RIGHT: return KeyboardButton::right();
2440  }
2441  }
2442 
2443  // Now check for the rest of the buttons, including the ones that we allow
2444  // through even when the IME window is open.
2445  switch(wparam) {
2446  case VK_TAB: return KeyboardButton::tab();
2447  case VK_PRIOR: return KeyboardButton::page_up();
2448  case VK_NEXT: return KeyboardButton::page_down();
2449  case VK_HOME: return KeyboardButton::home();
2450  case VK_END: return KeyboardButton::end();
2451  case VK_F1: return KeyboardButton::f1();
2452  case VK_F2: return KeyboardButton::f2();
2453  case VK_F3: return KeyboardButton::f3();
2454  case VK_F4: return KeyboardButton::f4();
2455  case VK_F5: return KeyboardButton::f5();
2456  case VK_F6: return KeyboardButton::f6();
2457  case VK_F7: return KeyboardButton::f7();
2458  case VK_F8: return KeyboardButton::f8();
2459  case VK_F9: return KeyboardButton::f9();
2460  case VK_F10: return KeyboardButton::f10();
2461  case VK_F11: return KeyboardButton::f11();
2462  case VK_F12: return KeyboardButton::f12();
2463  case VK_INSERT: return KeyboardButton::insert();
2464  case VK_CAPITAL: return KeyboardButton::caps_lock();
2465  case VK_NUMLOCK: return KeyboardButton::num_lock();
2466  case VK_SCROLL: return KeyboardButton::scroll_lock();
2467  case VK_PAUSE: return KeyboardButton::pause();
2468  case VK_SNAPSHOT: return KeyboardButton::print_screen();
2469 
2470  case VK_SHIFT: return KeyboardButton::shift();
2471  case VK_LSHIFT: return KeyboardButton::lshift();
2472  case VK_RSHIFT: return KeyboardButton::rshift();
2473 
2474  case VK_CONTROL: return KeyboardButton::control();
2475  case VK_LCONTROL: return KeyboardButton::lcontrol();
2476  case VK_RCONTROL: return KeyboardButton::rcontrol();
2477 
2478  case VK_MENU: return KeyboardButton::alt();
2479  case VK_LMENU: return KeyboardButton::lalt();
2480  case VK_RMENU: return KeyboardButton::ralt();
2481 
2482  default:
2483  int key = MapVirtualKey(wparam, 2);
2484  if (isascii(key) && key != 0) {
2485  // We used to try to remap lowercase to uppercase keys here based on the
2486  // state of the shift andor caps lock keys. But that's a mistake, and
2487  // doesn't allow for international or user-defined keyboards; let
2488  // Windows do that mapping.
2489 
2490  // Nowadays, we make a distinction between a "button" and a "keystroke".
2491  // A button corresponds to a physical button on the keyboard and has a
2492  // down and up event associated. A keystroke may or may not correspond
2493  // to a physical button, but will be some Unicode character and will not
2494  // have a corresponding up event.
2495  return KeyboardButton::ascii_key(tolower(key));
2496  }
2497  break;
2498  }
2499  return ButtonHandle::none();
2500 }
2501 
2502 /**
2503  * Translates the scancode reported by Windows to an appropriate Panda
2504  * ButtonHandle.
2505  */
2506 ButtonHandle WinGraphicsWindow::
2507 lookup_raw_key(LPARAM lparam) const {
2508  unsigned char vsc = (lparam & 0xff0000) >> 16;
2509 
2510  if (lparam & 0x1000000) {
2511  // Extended keys
2512  switch (vsc) {
2513  case 28: return KeyboardButton::enter();
2514  case 29: return KeyboardButton::rcontrol();
2515  case 53: return KeyboardButton::ascii_key('/');
2516  case 55: return KeyboardButton::print_screen();
2517  case 56: return KeyboardButton::ralt();
2518  case 69: return KeyboardButton::num_lock();
2519  case 71: return KeyboardButton::home();
2520  case 72: return KeyboardButton::up();
2521  case 73: return KeyboardButton::page_up();
2522  case 75: return KeyboardButton::left();
2523  case 77: return KeyboardButton::right();
2524  case 79: return KeyboardButton::end();
2525  case 80: return KeyboardButton::down();
2526  case 81: return KeyboardButton::page_down();
2527  case 82: return KeyboardButton::insert();
2528  case 83: return KeyboardButton::del();
2529  case 91: return KeyboardButton::lmeta();
2530  case 92: return KeyboardButton::rmeta();
2531  case 93: return KeyboardButton::menu();
2532  }
2533  }
2534 
2535  if (vsc <= 83) {
2536  static ButtonHandle raw_map[] = {
2537  ButtonHandle::none(),
2538  KeyboardButton::escape(),
2551  KeyboardButton::backspace(),
2552  KeyboardButton::tab(),
2565  KeyboardButton::enter(),
2566  KeyboardButton::lcontrol(),
2579  KeyboardButton::lshift(),
2591  KeyboardButton::rshift(),
2593  KeyboardButton::lalt(),
2594  KeyboardButton::space(),
2595  KeyboardButton::caps_lock(),
2596  KeyboardButton::f1(),
2597  KeyboardButton::f2(),
2598  KeyboardButton::f3(),
2599  KeyboardButton::f4(),
2600  KeyboardButton::f5(),
2601  KeyboardButton::f6(),
2602  KeyboardButton::f7(),
2603  KeyboardButton::f8(),
2604  KeyboardButton::f9(),
2605  KeyboardButton::f10(),
2606  KeyboardButton::pause(),
2607  KeyboardButton::scroll_lock(),
2621  };
2622  return raw_map[vsc];
2623  }
2624 
2625  // A few additional keys don't fit well in the above table.
2626  switch (vsc) {
2627  case 87: return KeyboardButton::f11();
2628  case 88: return KeyboardButton::f12();
2629  default: return ButtonHandle::none();
2630  }
2631 }
2632 
2633 /**
2634  * Returns a ButtonMap containing the association between raw buttons and
2635  * virtual buttons.
2636  *
2637  * Note that on Windows, the pause button and numpad keys are not mapped
2638  * reliably.
2639  */
2640 ButtonMap *WinGraphicsWindow::
2641 get_keyboard_map() const {
2642  ButtonMap *map = new ButtonMap;
2643 
2644  wchar_t text[256];
2645  UINT vsc = 0;
2646  unsigned short ex_vsc[] = {0x57, 0x58,
2647  0x011c, 0x011d, 0x0135, 0x0137, 0x0138, 0x0145, 0x0147, 0x0148, 0x0149, 0x014b, 0x014d, 0x014f, 0x0150, 0x0151, 0x0152, 0x0153, 0x015b, 0x015c, 0x015d};
2648 
2649  for (int k = 1; k < 84 + 17; ++k) {
2650  if (k >= 84) {
2651  vsc = ex_vsc[k - 84];
2652  } else {
2653  vsc = k;
2654  }
2655 
2656  UINT lparam = vsc << 16;
2657  ButtonHandle raw_button = lookup_raw_key(lparam);
2658  if (raw_button == ButtonHandle::none()) {
2659  continue;
2660  }
2661 
2662  ButtonHandle button;
2663  if (vsc == 0x45) {
2664  button = KeyboardButton::pause();
2665 
2666  } else if (vsc >= 0x47 && vsc <= 0x53) {
2667  // The numpad keys are not mapped correctly, see KB72583
2668  button = raw_button;
2669 
2670  } else {
2671  if (vsc == 0x145) {
2672  // Don't ask why - I have no idea.
2673  vsc = 0x45;
2674  }
2675  if (vsc & 0x0100) {
2676  // MapVirtualKey recognizes extended codes differently.
2677  vsc ^= 0xe100;
2678  }
2679 
2680  UINT vk = MapVirtualKeyA(vsc, MAPVK_VSC_TO_VK_EX);
2681  button = lookup_key(vk);
2682  //if (button == ButtonHandle::none()) {
2683  // continue;
2684  //}
2685  }
2686 
2687  int len = GetKeyNameTextW(lparam, text, 256);
2688  TextEncoder enc;
2689  enc.set_wtext(wstring(text, len));
2690  map->map_button(raw_button, button, enc.get_text());
2691  }
2692 
2693  return map;
2694 }
2695 
2696 /**
2697  *
2698  */
2699 void WinGraphicsWindow::
2700 handle_raw_input(HRAWINPUT hraw) {
2701  LPBYTE lpb;
2702  UINT dwSize;
2703 
2704  if (hraw == 0) {
2705  return;
2706  }
2707  if (GetRawInputData(hraw, RID_INPUT, nullptr, &dwSize, sizeof(RAWINPUTHEADER)) == -1) {
2708  return;
2709  }
2710 
2711  lpb = (LPBYTE)alloca(sizeof(LPBYTE) * dwSize);
2712  if (lpb == nullptr) {
2713  return;
2714  }
2715 
2716  if (GetRawInputData(hraw, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER)) != dwSize) {
2717  return;
2718  }
2719 
2720  RAWINPUT *raw = (RAWINPUT *)lpb;
2721  if (raw->header.hDevice == 0) {
2722  return;
2723  }
2724 
2725  for (size_t i = 1; i < _input_devices.size(); ++i) {
2726  if (_input_device_handle[i] == raw->header.hDevice) {
2727  PT(GraphicsWindowInputDevice) input =
2728  DCAST(GraphicsWindowInputDevice, _input_devices[i]);
2729 
2730  int adjx = raw->data.mouse.lLastX;
2731  int adjy = raw->data.mouse.lLastY;
2732 
2733  if (raw->data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) {
2734  input->set_pointer_in_window(adjx, adjy);
2735  } else {
2736  input->pointer_moved(adjx, adjy);
2737  }
2738 
2739  if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_DOWN) {
2740  input->button_down(MouseButton::button(0), get_message_time());
2741  }
2742  if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_UP) {
2743  input->button_up(MouseButton::button(0), get_message_time());
2744  }
2745  if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_2_DOWN) {
2746  input->button_down(MouseButton::button(2), get_message_time());
2747  }
2748  if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_2_UP) {
2749  input->button_up(MouseButton::button(2), get_message_time());
2750  }
2751  if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_3_DOWN) {
2752  input->button_down(MouseButton::button(1), get_message_time());
2753  }
2754  if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_3_UP) {
2755  input->button_up(MouseButton::button(1), get_message_time());
2756  }
2757  if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_DOWN) {
2758  input->button_down(MouseButton::button(3), get_message_time());
2759  }
2760  if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_UP) {
2761  input->button_up(MouseButton::button(3), get_message_time());
2762  }
2763  if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_DOWN) {
2764  input->button_down(MouseButton::button(4), get_message_time());
2765  }
2766  if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_UP) {
2767  input->button_up(MouseButton::button(4), get_message_time());
2768  }
2769  }
2770  }
2771 }
2772 
2773 /**
2774  *
2775  */
2776 bool WinGraphicsWindow::
2777 handle_mouse_motion(int x, int y) {
2778  _input->set_pointer_in_window(x, y);
2779  return false;
2780 }
2781 
2782 /**
2783  *
2784  */
2785 void WinGraphicsWindow::
2786 handle_mouse_exit() {
2787  // note: 'mouse_motion' is considered the 'entry' event
2788  _input->set_pointer_out_of_window();
2789 }
2790 
2791 /**
2792  * Loads and returns an HICON corresponding to the indicated filename. If the
2793  * file cannot be loaded, returns 0.
2794  */
2795 HICON WinGraphicsWindow::
2796 get_icon(const Filename &filename) {
2797  // First, look for the unresolved filename in our index.
2798  IconFilenames::iterator fi = _icon_filenames.find(filename);
2799  if (fi != _icon_filenames.end()) {
2800  return (HICON)((*fi).second);
2801  }
2802 
2803  // If it wasn't found, resolve the filename and search for that.
2804 
2805  // Since we have to use a Windows call to load the image from a filename, we
2806  // can't load a virtual file and we can't use the virtual file system.
2807  Filename resolved = filename;
2808  if (!resolved.resolve_filename(get_model_path())) {
2809  // The filename doesn't exist along the search path.
2810  if (resolved.is_fully_qualified() && resolved.exists()) {
2811  // But it does exist locally, so accept it.
2812 
2813  } else {
2814  windisplay_cat.warning()
2815  << "Could not find icon filename " << filename << "\n";
2816  return 0;
2817  }
2818  }
2819  fi = _icon_filenames.find(resolved);
2820  if (fi != _icon_filenames.end()) {
2821  _icon_filenames[filename] = (*fi).second;
2822  return (HICON)((*fi).second);
2823  }
2824 
2825  Filename os = resolved.to_os_specific();
2826 
2827  HANDLE h = LoadImage(nullptr, os.c_str(),
2828  IMAGE_ICON, 0, 0, LR_LOADFROMFILE | LR_DEFAULTSIZE);
2829  if (h == 0) {
2830  windisplay_cat.warning()
2831  << "windows icon filename '" << os << "' could not be loaded!!\n";
2832  }
2833 
2834  _icon_filenames[filename] = h;
2835  _icon_filenames[resolved] = h;
2836  return (HICON)h;
2837 }
2838 
2839 /**
2840  * Loads and returns an HCURSOR corresponding to the indicated filename. If
2841  * the file cannot be loaded, returns 0.
2842  */
2843 HCURSOR WinGraphicsWindow::
2844 get_cursor(const Filename &filename) {
2845  // The empty filename means to disable a custom cursor.
2846  if (filename.empty()) {
2847  return 0;
2848  }
2849 
2850  // First, look for the unresolved filename in our index.
2851  IconFilenames::iterator fi = _cursor_filenames.find(filename);
2852  if (fi != _cursor_filenames.end()) {
2853  return (HCURSOR)((*fi).second);
2854  }
2855 
2856  // If it wasn't found, resolve the filename and search for that.
2857 
2858  // Since we have to use a Windows call to load the image from a filename, we
2859  // can't load a virtual file and we can't use the virtual file system.
2860  Filename resolved = filename;
2861  if (!resolved.resolve_filename(get_model_path())) {
2862  // The filename doesn't exist.
2863  windisplay_cat.warning()
2864  << "Could not find cursor filename " << filename << "\n";
2865  return 0;
2866  }
2867  fi = _cursor_filenames.find(resolved);
2868  if (fi != _cursor_filenames.end()) {
2869  _cursor_filenames[filename] = (*fi).second;
2870  return (HCURSOR)((*fi).second);
2871  }
2872 
2873  Filename os = resolved.to_os_specific();
2874 
2875  HANDLE h = LoadImage(nullptr, os.c_str(),
2876  IMAGE_CURSOR, 0, 0, LR_LOADFROMFILE);
2877  if (h == 0) {
2878  windisplay_cat.warning()
2879  << "windows cursor filename '" << os << "' could not be loaded!!\n";
2880  show_error_message();
2881  }
2882 
2883  _cursor_filenames[filename] = h;
2884  _cursor_filenames[resolved] = h;
2885  return (HCURSOR)h;
2886 }
2887 
2888 static HCURSOR get_cursor(const Filename &filename);
2889 
2890 /**
2891  * Registers a Window class appropriate for the indicated properties. This
2892  * class may be shared by multiple windows.
2893  */
2894 const WinGraphicsWindow::WindowClass &WinGraphicsWindow::
2895 register_window_class(const WindowProperties &props) {
2896  WindowClass wcreg(props);
2897  std::wostringstream wclass_name;
2898  wclass_name << L"WinGraphicsWindow" << _window_class_index;
2899  wcreg._name = wclass_name.str();
2900 
2901  std::pair<WindowClasses::iterator, bool> found = _window_classes.insert(wcreg);
2902  const WindowClass &wclass = (*found.first);
2903 
2904  if (!found.second) {
2905  // We have already created a window class.
2906  return wclass;
2907  }
2908 
2909  // We have not yet created this window class.
2910  _window_class_index++;
2911 
2912  WNDCLASSW wc;
2913 
2914  HINSTANCE instance = GetModuleHandle(nullptr);
2915 
2916  // Clear before filling in window structure!
2917  ZeroMemory(&wc, sizeof(wc));
2918  wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
2919  wc.lpfnWndProc = (WNDPROC)static_window_proc;
2920  wc.hInstance = instance;
2921 
2922  wc.hIcon = wclass._icon;
2923 
2924  wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
2925  wc.lpszMenuName = nullptr;
2926  wc.lpszClassName = wclass._name.c_str();
2927 
2928  if (!RegisterClassW(&wc)) {
2929  windisplay_cat.error()
2930  << "could not register window class " << wclass._name << "!" << endl;
2931  return wclass;
2932  }
2933 
2934  return wclass;
2935 }
2936 
2937 /**
2938  *
2939  */
2940 WinGraphicsWindow::WinWindowHandle::
2941 WinWindowHandle(WinGraphicsWindow *window, const WindowHandle &copy) :
2942  WindowHandle(copy),
2943  _window(window)
2944 {
2945 }
2946 
2947 /**
2948  * Should be called by the WinGraphicsWindow's destructor, so that we don't
2949  * end up with a floating pointer should this object persist beyond the
2950  * lifespan of its window.
2951  */
2952 void WinGraphicsWindow::WinWindowHandle::
2953 clear_window() {
2954  _window = nullptr;
2955 }
2956 
2957 /**
2958  * Called on a child handle to deliver a keyboard button event generated in
2959  * the parent window.
2960  */
2961 void WinGraphicsWindow::WinWindowHandle::
2962 receive_windows_message(unsigned int msg, int wparam, int lparam) {
2963  if (_window != nullptr) {
2964  _window->receive_windows_message(msg, wparam, lparam);
2965  }
2966 }
2967 
2968 
2969 // pops up MsgBox wsystem error msg
2970 void PrintErrorMessage(DWORD msgID) {
2971  LPTSTR pMessageBuffer;
2972 
2973  if (msgID==PRINT_LAST_ERROR)
2974  msgID=GetLastError();
2975 
2976  FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
2977  nullptr,msgID,
2978  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), //The user default language
2979  (LPTSTR) &pMessageBuffer, // the weird ptrptr->ptr cast is intentional, see FORMAT_MESSAGE_ALLOCATE_BUFFER
2980  1024, nullptr);
2981  MessageBox(GetDesktopWindow(),pMessageBuffer,_T(errorbox_title),MB_OK);
2982  windisplay_cat.fatal() << "System error msg: " << pMessageBuffer << endl;
2983  LocalFree( pMessageBuffer );
2984 }
2985 
2986 void
2987 ClearToBlack(HWND hWnd, const WindowProperties &props) {
2988  if (!props.has_origin()) {
2989  if (windisplay_cat.is_debug()) {
2990  windisplay_cat.debug()
2991  << "Skipping ClearToBlack, no origin specified yet.\n";
2992  }
2993  return;
2994  }
2995 
2996  if (windisplay_cat.is_debug()) {
2997  windisplay_cat.debug()
2998  << "ClearToBlack(" << hWnd << ", " << props << ")\n";
2999  }
3000  // clear to black
3001  HDC hDC=GetDC(hWnd); // GetDC is not particularly fast. if this needs to be super-quick, we should cache GetDC's hDC
3002  RECT clrRect = {
3003  props.get_x_origin(), props.get_y_origin(),
3004  props.get_x_origin() + props.get_x_size(),
3005  props.get_y_origin() + props.get_y_size()
3006  };
3007  FillRect(hDC,&clrRect,(HBRUSH)GetStockObject(BLACK_BRUSH));
3008  ReleaseDC(hWnd,hDC);
3009  GdiFlush();
3010 }
3011 
3012 /**
3013  * Fills view_rect with the coordinates of the client area of the indicated
3014  * window, converted to screen coordinates.
3015  */
3016 void get_client_rect_screen(HWND hwnd, RECT *view_rect) {
3017  GetClientRect(hwnd, view_rect);
3018 
3019  POINT ul, lr;
3020  ul.x = view_rect->left;
3021  ul.y = view_rect->top;
3022  lr.x = view_rect->right;
3023  lr.y = view_rect->bottom;
3024 
3025  ClientToScreen(hwnd, &ul);
3026  ClientToScreen(hwnd, &lr);
3027 
3028  view_rect->left = ul.x;
3029  view_rect->top = ul.y;
3030  view_rect->right = lr.x;
3031  view_rect->bottom = lr.y;
3032 }
3033 
3034 /**
3035  * Adds the specified Windows proc event handler to be called whenever a
3036  * Windows event occurs.
3037  *
3038  */
3040  nassertv(wnd_proc != nullptr);
3041  _window_proc_classes.insert( (GraphicsWindowProc*)wnd_proc );
3042 }
3043 
3044 /**
3045  * Removes the specified Windows proc event handler.
3046  *
3047  */
3049  nassertv(wnd_proc != nullptr);
3050  _window_proc_classes.erase( (GraphicsWindowProc*)wnd_proc );
3051 }
3052 
3053 /**
3054  * Removes all Windows proc event handlers.
3055  *
3056  */
3058  _window_proc_classes.clear();
3059 }
3060 
3061 /**
3062  * Returns whether this window supports adding of windows proc handlers.
3063  *
3064  */
3066  return true;
3067 }
3068 
3069 /**
3070  * Returns whether the specified event msg is a touch message.
3071  *
3072  */
3075  return callbackData->get_msg() == WM_TOUCH;
3076 }
3077 
3078 /**
3079  * Returns the current number of touches on this window.
3080  *
3081  */
3084  return _num_touches;
3085 }
3086 
3087 /**
3088  * Returns the TouchInfo object describing the specified touch.
3089  *
3090  */
3092 get_touch_info(int index) {
3093  nassertr(index >= 0 && index < MAX_TOUCHES, TouchInfo());
3094 
3095  TOUCHINPUT ti = _touches[index];
3096  POINT point;
3097  point.x = TOUCH_COORD_TO_PIXEL(ti.x);
3098  point.y = TOUCH_COORD_TO_PIXEL(ti.y);
3099  ScreenToClient(_hWnd, &point);
3100 
3101  TouchInfo ret = TouchInfo();
3102  ret.set_x(point.x);
3103  ret.set_y(point.y);
3104  ret.set_id(ti.dwID);
3105  ret.set_flags(ti.dwFlags);
3106  return ret;
3107 }
virtual void receive_windows_message(unsigned int msg, int wparam, int lparam)
Called on a child handle to deliver a keyboard button event generated in the parent window.
has_cursor_filename
Returns true if set_cursor_filename() has been specified.
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.
This specialization on CallbackData is passed when the callback is initiated from from an implementat...
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_icon_filename
Returns true if set_icon_filename() has been specified.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
has_minimized
Returns true if set_minimized() has been specified.
void set_size_and_recalc(int x, int y)
Changes the x_size and y_size, then recalculates structures that depend on size.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This is our own Panda specialization on the default STL map.
Definition: pmap.h:49
This object represents a window on the desktop, not necessarily a Panda window.
Definition: windowHandle.h:34
get_fixed_size
Returns true if the window cannot be resized by the user, false otherwise.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
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.
BEGIN_PUBLISH typedef PointerData MouseData
Deprecated alias for PointerData.
Definition: mouseData.h:23
This class can be used to convert text between multiple representations, e.g.
Definition: textEncoder.h:33
get_foreground
Returns true if the window is in the foreground.
virtual TouchInfo get_touch_info(int index)
Returns the TouchInfo object describing the specified touch.
clear_cursor_filename
Removes the cursor_filename specification from the properties.
virtual void begin_flip()
This function will be called within the draw thread after end_frame() has been called on all windows,...
clear_icon_filename
Removes the icon_filename specification from the properties.
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...
clear_fixed_size
Removes the fixed_size specification from the properties.
bool resolve_filename(const DSearchPath &searchpath, const std::string &default_extension=std::string())
Searches the given search path for the filename.
Definition: filename.cxx:1581
virtual bool move_pointer(int device, int x, int y)
Forces the pointer to the indicated position within the window, if possible.
virtual int get_num_touches()
Returns the current number of touches on this window.
void receive_windows_message(unsigned int msg, int wparam, int lparam)
This is called to receive a keyboard event generated by proxy by another window in a parent process.
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.
virtual void remove_window_proc(const GraphicsWindowProc *wnd_proc_object)
Removes the specified Windows proc event handler.
bool is_fully_qualified() const
Returns true if the filename is fully qualified, e.g.
Definition: filename.I:562
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
get_title
Returns the window's title.
A window, fullscreen or on a desktop, into which a graphics device sends its output for interactive d...
A ButtonHandle represents a single button from any device, including keyboard buttons and mouse butto...
Definition: buttonHandle.h:26
has_fullscreen
Returns true if set_fullscreen() has been specified.
void candidate(const std::wstring &candidate_string, size_t highlight_start, size_t highlight_end, size_t cursor_pos)
Records that the indicated candidate string has been highlighted.
virtual LONG window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
This is the nonstatic window_proc function.
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...
An abstract base class for glGraphicsWindow and dxGraphicsWindow (and, in general,...
static LONG WINAPI static_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
This is attached to the window class for all WinGraphicsWindow windows; it is called to handle window...
get_undecorated
Returns true if the window has no border.
clear_undecorated
Removes the undecorated specification from the properties.
has_origin
Returns true if the window origin has been specified, false otherwise.
Stores information for a single touch event.
Definition: touchInfo.h:22
has_fixed_size
Returns true if set_fixed_size() has been specified.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
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.
get_icon_filename
Returns the icon filename associated with the window.
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
clear_mouse_mode
Removes the mouse_mode specification from the properties.
void button_up(ButtonHandle button, double time=ClockObject::get_global_clock() ->get_frame_time())
Records that the indicated button has been released.
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 ...
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_minimized
Removes the minimized specification from the properties.
clear_cursor_hidden
Removes the cursor_hidden specification from the properties.
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 ...
get_text
Returns the current text, as encoded via the current encoding system.
Definition: textEncoder.h:124
This is a base class for the various different classes that represent the result of a frame of render...
get_minimized
Returns true if the window is minimized.
virtual MouseData get_pointer(int device) const
Returns the MouseData associated with the nth input device's pointer.
Defines an interface for storing platform-specific window processor methods.
virtual void add_window_proc(const GraphicsWindowProc *wnd_proc_object)
Adds the specified Windows proc event handler to be called whenever a Windows event occurs.
clear_fullscreen
Removes the fullscreen specification from the properties.
void get_client_rect_screen(HWND hwnd, RECT *view_rect)
Fills view_rect with the coordinates of the client area of the indicated window, converted to screen ...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
has_cursor_hidden
Returns true if set_cursor_hidden() has been specified.
This is an abstract base class for wglGraphicsPipe and wdxGraphicsPipe; that is, those graphics pipes...
get_properties
Returns the current properties of the window.
virtual void process_events()
Do whatever processing is necessary to ensure that the window responds to user events.
Encapsulates all the communication with a particular instance of a given rendering backend.
virtual bool is_touch_event(GraphicsWindowProcCallbackData *callbackData)
Returns whether the specified event msg is a touch message.
has_undecorated
Returns true if set_undecorated() has been specified.
has_title
Returns true if the window title has been specified, false otherwise.
void button_resume_down(ButtonHandle button, double time=ClockObject::get_global_clock() ->get_frame_time())
Records that the indicated button was depressed earlier, and we only just detected the event after th...
virtual void clear_window_procs()
Removes all Windows proc event handlers.
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 is our own Panda specialization on the default STL set.
Definition: pset.h:49
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...
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
std::wstring decode_text(const std::string &text) const
Returns the given wstring decoded to a single-byte string, via the current encoding system.
Definition: textEncoder.I:490
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...
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.
virtual void close_ime()
Forces the ime window to close, if any.
static ButtonHandle ascii_key(char ascii_equivalent)
Returns the ButtonHandle associated with the particular ASCII character, if there is one,...
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.
std::string to_os_specific() const
Converts the filename from our generic Unix-like convention (forward slashes starting with the root a...
Definition: filename.cxx:1123
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.
This class represents a map containing all of the buttons of a (keyboard) device, though it can also ...
Definition: buttonMap.h:30
has_foreground
Returns true if set_foreground() has been specified.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
static ButtonHandle wheel_up()
Returns the ButtonHandle generated when the mouse wheel is rolled one notch upwards.
Definition: mouseButton.cxx:84
void map_button(ButtonHandle raw_button, ButtonHandle button, const std::string &label="")
Registers a new button mapping.
Definition: buttonMap.cxx:23
void set_wtext(const std::wstring &wtext)
Changes the text that is stored in the encoder.
Definition: textEncoder.I:443
bool exists() const
Returns true if the filename exists on the disk, false otherwise.
Definition: filename.cxx:1267
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 bool supports_window_procs() const
Returns whether this window supports adding of windows proc handlers.