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