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