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 // Prevent calling this recursively.
1312 if (_in_adjust_z_order) {
1313 return;
1314 }
1315
1316 HWND order;
1317 bool do_change = false;
1318
1319 switch (this_z_order) {
1320 case WindowProperties::Z_bottom:
1321 order = HWND_BOTTOM;
1322 do_change = true;
1323 break;
1324
1325 case WindowProperties::Z_normal:
1326 if ((last_z_order != WindowProperties::Z_normal) &&
1327 // If we aren't changing the window order, don't move it to the top.
1328 (last_z_order != WindowProperties::Z_bottom ||
1329 _properties.get_foreground())
1330 // If the window was previously on the bottom, but it doesn't have
1331 // focus now, don't move it to the top; it will get moved the next
1332 // time we get focus.
1333 ) {
1334 order = HWND_NOTOPMOST;
1335 do_change = true;
1336 }
1337 break;
1338
1339 case WindowProperties::Z_top:
1340 order = HWND_TOPMOST;
1341 do_change = true;
1342 break;
1343 }
1344 if (do_change) {
1345 _in_adjust_z_order = true;
1346 BOOL result = SetWindowPos(_hWnd, order, 0,0,0,0,
1347 SWP_NOMOVE | SWP_NOSENDCHANGING | SWP_NOSIZE);
1348 _in_adjust_z_order = false;
1349 if (!result) {
1350 windisplay_cat.warning()
1351 << "SetWindowPos failed.\n";
1352 }
1353 }
1354}
1355
1356/**
1357 * Intended to be called whenever mouse motion is detected within the window,
1358 * this indicates that the mouse is within the window and tells Windows that
1359 * we want to be told when the mouse leaves the window.
1360 */
1361void WinGraphicsWindow::
1362track_mouse_leaving(HWND hwnd) {
1363 WinGraphicsPipe *winpipe;
1364 DCAST_INTO_V(winpipe, _pipe);
1365
1366 TRACKMOUSEEVENT tme = {
1367 sizeof(TRACKMOUSEEVENT),
1368 TME_LEAVE,
1369 hwnd,
1370 0
1371 };
1372
1373 // tell win32 to post WM_MOUSELEAVE msgs
1374 BOOL bSucceeded = TrackMouseEvent(&tme);
1375
1376 if (!bSucceeded && windisplay_cat.is_debug()) {
1377 windisplay_cat.debug()
1378 << "TrackMouseEvent failed!, LastError=" << GetLastError() << endl;
1379 }
1380
1381 _tracking_mouse_leaving = true;
1382}
1383
1384/**
1385 * Confines the mouse cursor to the window.
1386 */
1387bool WinGraphicsWindow::
1388confine_cursor() {
1389 RECT clip;
1390 get_client_rect_screen(_hWnd, &clip);
1391
1392 windisplay_cat.info()
1393 << "ClipCursor() to " << clip.left << "," << clip.top << " to "
1394 << clip.right << "," << clip.bottom << endl;
1395
1396 if (!ClipCursor(&clip)) {
1397 windisplay_cat.warning()
1398 << "Failed to confine cursor to window.\n";
1399 return false;
1400 } else {
1401 return true;
1402 }
1403}
1404
1405/**
1406 * Attempts to set this window as the "focus" window, so that keyboard events
1407 * come here.
1408 */
1409void WinGraphicsWindow::
1410set_focus() {
1411 if (SetFocus(_hWnd) == nullptr && GetLastError() != 0) {
1412 // If the SetFocus() request failed, maybe we're running in the plugin
1413 // environment on Vista, with UAC enabled. In this case, we're not
1414 // allowed to assign focus to the Panda window for some stupid reason. So
1415 // instead, we have to ask the parent window (in the browser process) to
1416 // proxy our keyboard events for us.
1417 if (_parent_window_handle != nullptr && _window_handle != nullptr) {
1418 _parent_window_handle->request_keyboard_focus(_window_handle);
1419 } else {
1420 // Otherwise, something is wrong.
1421 windisplay_cat.error()
1422 << "SetFocus failed: " << GetLastError() << "\n";
1423 }
1424 }
1425}
1426
1427/**
1428 * This is called to receive a keyboard event generated by proxy by another
1429 * window in a parent process. This hacky system is used in the web plugin
1430 * system to allow the Panda window to receive keyboard events on Vista, which
1431 * doesn't allow the Panda window to set keyboard focus to itself.
1432 */
1434receive_windows_message(unsigned int msg, int wparam, int lparam) {
1435 // Well, we'll just deliver this directly to window_proc(), supplying our
1436 // own window handle. For the most part, we don't care about the window
1437 // handle anyway, but this might become an issue for the IME. TODO:
1438 // investigate IME issues.
1439
1440 window_proc(_hWnd, msg, wparam, lparam);
1441}
1442
1443/**
1444 * This is the nonstatic window_proc function. It is called to handle window
1445 * events for this particular window.
1446 */
1448window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
1449 if (windisplay_cat.is_spam()) {
1450 windisplay_cat.spam()
1452 << " window_proc(" << (void *)this << ", " << hwnd << ", "
1453 << msg << ", " << wparam << ", " << lparam << ")\n";
1454 }
1455 WindowProperties properties;
1456
1457 switch (msg) {
1458 case WM_MOUSEMOVE:
1459 if (!_tracking_mouse_leaving) {
1460 // need to re-call TrackMouseEvent every time mouse re-enters window
1461 track_mouse_leaving(hwnd);
1462 }
1463 set_cursor_in_window();
1464 if(handle_mouse_motion(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam))))
1465 return 0;
1466 break;
1467
1468 case WM_INPUT:
1469 handle_raw_input((HRAWINPUT)lparam);
1470 break;
1471
1472 case WM_MOUSELEAVE:
1473 _tracking_mouse_leaving = false;
1474 handle_mouse_exit();
1475 set_cursor_out_of_window();
1476 break;
1477
1478 case WM_CREATE:
1479 {
1480 track_mouse_leaving(hwnd);
1481 ClearToBlack(hwnd, _properties);
1482
1483 POINT cpos;
1484 GetCursorPos(&cpos);
1485 ScreenToClient(hwnd, &cpos);
1486 RECT clientRect;
1487 GetClientRect(hwnd, &clientRect);
1488 if (PtInRect(&clientRect,cpos)) {
1489 set_cursor_in_window(); // should window focus be true as well?
1490 } else {
1491 set_cursor_out_of_window();
1492 }
1493 }
1494 break;
1495
1496 /*
1497 case WM_SHOWWINDOW:
1498 // You'd think WM_SHOWWINDOW would be just the thing for embedded windows,
1499 // but it turns out it's not sent to the child windows when the parent is
1500 // minimized. I guess it's only sent for an explicit call to ShowWindow,
1501 // phooey.
1502 {
1503 if (windisplay_cat.is_debug()) {
1504 windisplay_cat.debug()
1505 << "WM_SHOWWINDOW: " << hwnd << ", " << wparam << "\n";
1506 }
1507 if (wparam) {
1508 // Window is being shown.
1509 properties.set_minimized(false);
1510 } else {
1511 // Window is being hidden.
1512 properties.set_minimized(true);
1513 }
1514 system_changed_properties(properties);
1515 }
1516 break;
1517 */
1518
1519 case WM_CLOSE:
1520 // This is a message from the system indicating that the user has
1521 // requested to close the window (e.g. alt-f4).
1522 {
1523 std::string close_request_event = get_close_request_event();
1524 if (!close_request_event.empty()) {
1525 // In this case, the app has indicated a desire to intercept the
1526 // request and process it directly.
1527 throw_event(close_request_event);
1528 return 0;
1529
1530 } else {
1531 // In this case, the default case, the app does not intend to service
1532 // the request, so we do by closing the window.
1533 close_window();
1534 properties.set_open(false);
1535 system_changed_properties(properties);
1536
1537 // TODO: make sure we release the GSG properly.
1538 }
1539 }
1540 break;
1541
1542 case WM_CHILDACTIVATE:
1543 if (windisplay_cat.is_debug()) {
1544 windisplay_cat.debug()
1545 << "WM_CHILDACTIVATE: " << hwnd << "\n";
1546 }
1547 break;
1548
1549 case WM_ACTIVATE:
1550 if (windisplay_cat.is_debug()) {
1551 windisplay_cat.debug()
1552 << "WM_ACTIVATE: " << hwnd << ", " << wparam << ", " << lparam << "\n";
1553 }
1554 properties.set_minimized((wparam & 0xffff0000) != 0);
1555 if ((wparam & 0xffff) != WA_INACTIVE)
1556 {
1557 properties.set_foreground(true);
1558 if (is_fullscreen())
1559 {
1560 // When a fullscreen window goes active, it automatically gets un-
1561 // minimized.
1562 int chg_result =
1563 ChangeDisplaySettings(&_fullscreen_display_mode, CDS_FULLSCREEN);
1564 if (chg_result != DISP_CHANGE_SUCCESSFUL) {
1565 const DEVMODE &dm = _fullscreen_display_mode;
1566 windisplay_cat.error()
1567 << "restore ChangeDisplaySettings failed (error code: "
1568 << chg_result << ") for specified res: "
1569 << dm.dmPelsWidth << " x " << dm.dmPelsHeight
1570 << " x " << dm.dmBitsPerPel << ", "
1571 << dm.dmDisplayFrequency << " Hz\n";
1572 }
1573
1574 GdiFlush();
1575 SetWindowPos(_hWnd, HWND_TOP, 0,0,0,0, SWP_NOMOVE | SWP_NOSENDCHANGING | SWP_NOSIZE | SWP_NOOWNERZORDER);
1576 fullscreen_restored(properties);
1577 }
1578
1579 // If we had a confined cursor, we must reconfine it upon activation.
1580 if (_properties.has_mouse_mode() &&
1581 _properties.get_mouse_mode() == WindowProperties::M_confined) {
1582 if (!confine_cursor()) {
1583 properties.set_mouse_mode(WindowProperties::M_absolute);
1584 }
1585 }
1586 }
1587 else
1588 {
1589 properties.set_foreground(false);
1590 if (is_fullscreen())
1591 {
1592 // When a fullscreen window goes inactive, it automatically gets
1593 // minimized.
1594 properties.set_minimized(true);
1595
1596 // It seems order is important here. We must minimize the window
1597 // before restoring the display settings, or risk losing the
1598 // graphics context.
1599 ShowWindow(_hWnd, SW_MINIMIZE);
1600 GdiFlush();
1601 do_fullscreen_disable();
1602 fullscreen_minimized(properties);
1603 }
1604 }
1605
1606 adjust_z_order();
1607 system_changed_properties(properties);
1608 break;
1609
1610 case WM_SIZE:
1611 // Actually, since we don't return in WM_WINDOWPOSCHANGED, WM_SIZE won't
1612 // end up being called at all. This is more efficient according to MSDN.
1613 if (windisplay_cat.is_debug()) {
1614 windisplay_cat.debug()
1615 << "WM_SIZE: " << hwnd << ", " << wparam << "\n";
1616 }
1617 break;
1618
1619 case WM_EXITSIZEMOVE:
1620 if (windisplay_cat.is_debug()) {
1621 windisplay_cat.debug()
1622 << "WM_EXITSIZEMOVE: " << hwnd << ", " << wparam << "\n";
1623 }
1624
1625 // If we had a confined cursor, we must reconfine it upon a resize.
1626 if (_properties.has_mouse_mode() &&
1627 _properties.get_mouse_mode() == WindowProperties::M_confined) {
1628 confine_cursor();
1629 }
1630 break;
1631
1632 case WM_WINDOWPOSCHANGED:
1633 if (windisplay_cat.is_debug()) {
1634 windisplay_cat.debug()
1635 << "WM_WINDOWPOSCHANGED: " << hwnd << ", " << wparam << "\n";
1636 }
1637 if (_hWnd != nullptr) {
1638 handle_reshape();
1639 }
1640 if (!_in_adjust_z_order) {
1641 adjust_z_order();
1642 }
1643 return 0;
1644
1645 case WM_PAINT:
1646 // In response to WM_PAINT, we check to see if there are any update
1647 // regions at all; if there are, we declare the window exposed. This is
1648 // used to implement !_unexposed_draw.
1649 if (GetUpdateRect(_hWnd, nullptr, false)) {
1650 if (windisplay_cat.is_spam()) {
1651 windisplay_cat.spam()
1652 << "Got update regions: " << this << "\n";
1653 }
1654 _got_expose_event = true;
1655 }
1656 break;
1657
1658 case WM_LBUTTONDOWN:
1659 if (_lost_keypresses) {
1660 resend_lost_keypresses();
1661 }
1662 SetCapture(hwnd);
1663 _input->set_pointer_in_window(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam)));
1664 _input->button_down(MouseButton::button(0), get_message_time());
1665
1666 // A button-click in the window means to grab the keyboard focus.
1667 set_focus();
1668 return 0;
1669
1670 case WM_MBUTTONDOWN:
1671 if (_lost_keypresses) {
1672 resend_lost_keypresses();
1673 }
1674 SetCapture(hwnd);
1675 _input->set_pointer_in_window(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam)));
1676 _input->button_down(MouseButton::button(1), get_message_time());
1677 // A button-click in the window means to grab the keyboard focus.
1678 set_focus();
1679 return 0;
1680
1681 case WM_RBUTTONDOWN:
1682 if (_lost_keypresses) {
1683 resend_lost_keypresses();
1684 }
1685 SetCapture(hwnd);
1686 _input->set_pointer_in_window(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam)));
1687 _input->button_down(MouseButton::button(2), get_message_time());
1688 // A button-click in the window means to grab the keyboard focus.
1689 set_focus();
1690 return 0;
1691
1692 case WM_XBUTTONDOWN:
1693 {
1694 if (_lost_keypresses) {
1695 resend_lost_keypresses();
1696 }
1697 SetCapture(hwnd);
1698 int whichButton = GET_XBUTTON_WPARAM(wparam);
1699 _input->set_pointer_in_window(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam)));
1700 if (whichButton == XBUTTON1) {
1701 _input->button_down(MouseButton::button(3), get_message_time());
1702 } else if (whichButton == XBUTTON2) {
1703 _input->button_down(MouseButton::button(4), get_message_time());
1704 }
1705 }
1706 return 0;
1707
1708 case WM_LBUTTONUP:
1709 if (_lost_keypresses) {
1710 resend_lost_keypresses();
1711 }
1712 if (wparam == 0) {
1713 ReleaseCapture();
1714 }
1715 _input->button_up(MouseButton::button(0), get_message_time());
1716 return 0;
1717
1718 case WM_MBUTTONUP:
1719 if (_lost_keypresses) {
1720 resend_lost_keypresses();
1721 }
1722 if (wparam == 0) {
1723 ReleaseCapture();
1724 }
1725 _input->button_up(MouseButton::button(1), get_message_time());
1726 return 0;
1727
1728 case WM_RBUTTONUP:
1729 if (_lost_keypresses) {
1730 resend_lost_keypresses();
1731 }
1732 if (wparam == 0) {
1733 ReleaseCapture();
1734 }
1735 _input->button_up(MouseButton::button(2), get_message_time());
1736 return 0;
1737
1738 case WM_XBUTTONUP:
1739 {
1740 if (_lost_keypresses) {
1741 resend_lost_keypresses();
1742 }
1743 if (wparam == 0) {
1744 ReleaseCapture();
1745 }
1746 int whichButton = GET_XBUTTON_WPARAM(wparam);
1747 if (whichButton == XBUTTON1) {
1748 _input->button_up(MouseButton::button(3), get_message_time());
1749 } else if (whichButton == XBUTTON2) {
1750 _input->button_up(MouseButton::button(4), get_message_time());
1751 }
1752 }
1753 return 0;
1754
1755 case WM_MOUSEWHEEL:
1756 {
1757 int delta = GET_WHEEL_DELTA_WPARAM(wparam);
1758
1759 POINT point;
1760 GetCursorPos(&point);
1761 ScreenToClient(hwnd, &point);
1762 double time = get_message_time();
1763
1764 if (delta >= 0) {
1765 while (delta > 0) {
1766 handle_keypress(MouseButton::wheel_up(), point.x, point.y, time);
1767 handle_keyrelease(MouseButton::wheel_up(), time);
1768 delta -= WHEEL_DELTA;
1769 }
1770 } else {
1771 while (delta < 0) {
1772 handle_keypress(MouseButton::wheel_down(), point.x, point.y, time);
1773 handle_keyrelease(MouseButton::wheel_down(), time);
1774 delta += WHEEL_DELTA;
1775 }
1776 }
1777 return 0;
1778 }
1779 break;
1780
1781 case WM_MOUSEHWHEEL:
1782 {
1783 int delta = GET_WHEEL_DELTA_WPARAM(wparam);
1784
1785 POINT point;
1786 GetCursorPos(&point);
1787 ScreenToClient(hwnd, &point);
1788 double time = get_message_time();
1789
1790 if (delta >= 0) {
1791 while (delta > 0) {
1792 handle_keypress(MouseButton::wheel_right(), point.x, point.y, time);
1793 handle_keyrelease(MouseButton::wheel_right(), time);
1794 delta -= WHEEL_DELTA;
1795 }
1796 } else {
1797 while (delta < 0) {
1798 handle_keypress(MouseButton::wheel_left(), point.x, point.y, time);
1799 handle_keyrelease(MouseButton::wheel_left(), time);
1800 delta += WHEEL_DELTA;
1801 }
1802 }
1803 return 0;
1804 }
1805 break;
1806
1807 case WM_IME_SETCONTEXT:
1808 if (!ime_hide)
1809 break;
1810
1811 windisplay_cat.debug() << "hwnd = " << hwnd << " and GetFocus = " << GetFocus() << endl;
1812 _ime_hWnd = ImmGetDefaultIMEWnd(hwnd);
1813 if (::SendMessage(_ime_hWnd, WM_IME_CONTROL, IMC_CLOSESTATUSWINDOW, 0))
1814 // if (::SendMessage(hwnd, WM_IME_CONTROL, IMC_CLOSESTATUSWINDOW, 0))
1815 windisplay_cat.debug() << "SendMessage failed for " << _ime_hWnd << endl;
1816 else
1817 windisplay_cat.debug() << "SendMessage Succeeded for " << _ime_hWnd << endl;
1818
1819 windisplay_cat.debug() << "wparam is " << wparam << ", lparam is " << lparam << endl;
1820 lparam &= ~ISC_SHOWUIALL;
1821 if (ImmIsUIMessage(_ime_hWnd, msg, wparam, lparam))
1822 windisplay_cat.debug() << "wparam is " << wparam << ", lparam is " << lparam << endl;
1823
1824 break;
1825
1826
1827 case WM_IME_NOTIFY:
1828 if (wparam == IMN_SETOPENSTATUS) {
1829 HIMC hIMC = ImmGetContext(hwnd);
1830 nassertr(hIMC != 0, 0);
1831 _ime_open = (ImmGetOpenStatus(hIMC) != 0);
1832 if (!_ime_open) {
1833 _ime_active = false; // Sanity enforcement.
1834 }
1835 if (ime_hide) {
1836 // if (0) {
1837 COMPOSITIONFORM comf;
1838 CANDIDATEFORM canf;
1839 ImmGetCompositionWindow(hIMC, &comf);
1840 ImmGetCandidateWindow(hIMC, 0, &canf);
1841 windisplay_cat.debug() <<
1842 "comf style " << comf.dwStyle <<
1843 " comf point: x" << comf.ptCurrentPos.x << ",y " << comf.ptCurrentPos.y <<
1844 " comf rect: l " << comf.rcArea.left << ",t " << comf.rcArea.top << ",r " <<
1845 comf.rcArea.right << ",b " << comf.rcArea.bottom << endl;
1846 windisplay_cat.debug() <<
1847 "canf style " << canf.dwStyle <<
1848 " canf point: x" << canf.ptCurrentPos.x << ",y " << canf.ptCurrentPos.y <<
1849 " canf rect: l " << canf.rcArea.left << ",t " << canf.rcArea.top << ",r " <<
1850 canf.rcArea.right << ",b " << canf.rcArea.bottom << endl;
1851 comf.dwStyle = CFS_POINT;
1852 comf.ptCurrentPos.x = 2000;
1853 comf.ptCurrentPos.y = 2000;
1854
1855 canf.dwStyle = CFS_EXCLUDE;
1856 canf.dwIndex = 0;
1857 canf.ptCurrentPos.x = 0;
1858 canf.ptCurrentPos.y = 0;
1859 canf.rcArea.left = 0;
1860 canf.rcArea.top = 0;
1861 canf.rcArea.right = 640;
1862 canf.rcArea.bottom = 480;
1863
1864#if 0
1865 comf.rcArea.left = 200;
1866 comf.rcArea.top = 200;
1867 comf.rcArea.right = 0;
1868 comf.rcArea.bottom = 0;
1869#endif
1870
1871 if (ImmSetCompositionWindow(hIMC, &comf))
1872 windisplay_cat.debug() << "set composition form: success\n";
1873 for (int i=0; i<3; ++i) {
1874 if (ImmSetCandidateWindow(hIMC, &canf))
1875 windisplay_cat.debug() << "set candidate form: success\n";
1876 canf.dwIndex++;
1877 }
1878 }
1879
1880 ImmReleaseContext(hwnd, hIMC);
1881 }
1882 break;
1883
1884 case WM_IME_STARTCOMPOSITION:
1885 support_overlay_window(true);
1886 _ime_active = true;
1887 break;
1888
1889 case WM_IME_ENDCOMPOSITION:
1890 support_overlay_window(false);
1891 _ime_active = false;
1892
1893 if (ime_aware) {
1894 wstring ws;
1895 _input->candidate(ws, 0, 0, 0);
1896 }
1897
1898 break;
1899
1900 case WM_IME_COMPOSITION:
1901 if (ime_aware) {
1902
1903 // If the ime window is not marked as active at this point, we must be
1904 // in the process of closing it down (in close_ime), and we don't want
1905 // to send the current composition string in that case. But we do need
1906 // to return 0 to tell windows not to try to send the composition string
1907 // through WM_CHAR messages.
1908 if (!_ime_active) {
1909 return 0;
1910 }
1911
1912 HIMC hIMC = ImmGetContext(hwnd);
1913 nassertr(hIMC != 0, 0);
1914
1915 DWORD result_size = 0;
1916 static const int ime_buffer_size = 256;
1917 static const int ime_buffer_size_bytes = ime_buffer_size / sizeof(wchar_t);
1918 wchar_t ime_buffer[ime_buffer_size];
1919 size_t cursor_pos, delta_start;
1920
1921 if (lparam & GCS_RESULTSTR) {
1922 result_size = ImmGetCompositionStringW(hIMC, GCS_RESULTSTR,
1923 ime_buffer, ime_buffer_size_bytes);
1924 size_t num_chars = result_size / sizeof(wchar_t);
1925 for (size_t i = 0; i < num_chars; ++i) {
1926 _input->keystroke(ime_buffer[i]);
1927 }
1928 }
1929
1930 if (lparam & GCS_COMPSTR) {
1931 result_size = ImmGetCompositionStringW(hIMC, GCS_CURSORPOS, nullptr, 0);
1932 cursor_pos = result_size & 0xffff;
1933
1934 result_size = ImmGetCompositionStringW(hIMC, GCS_DELTASTART, nullptr, 0);
1935 delta_start = result_size & 0xffff;
1936 result_size = ImmGetCompositionStringW(hIMC, GCS_COMPSTR, ime_buffer, ime_buffer_size);
1937 size_t num_chars = result_size / sizeof(wchar_t);
1938
1939 _input->candidate(wstring(ime_buffer, num_chars),
1940 std::min(cursor_pos, delta_start),
1941 std::max(cursor_pos, delta_start),
1942 cursor_pos);
1943 }
1944 ImmReleaseContext(hwnd, hIMC);
1945 }
1946 break;
1947
1948 case WM_CHAR:
1949 // Ignore WM_CHAR messages if we have the IME open, since everything will
1950 // come in through WM_IME_COMPOSITION. (It's supposed to come in through
1951 // WM_CHAR, too, but there seems to be a bug in Win2000 in that it only
1952 // sends question mark characters through here.)
1953
1954 // Actually, probably that "bug" was due to the fact that we were
1955 // previously using the ANSI versions of RegisterClass etc., in which case
1956 // the actual value passed to WM_CHAR seems to be poorly defined. Now we
1957 // are using RegisterClassW etc., which means WM_CHAR is absolutely
1958 // supposed to be utf-16.
1959 if (!_ime_open) {
1960 _input->keystroke(wparam);
1961 }
1962 break;
1963
1964 case WM_SYSKEYDOWN:
1965 if (_lost_keypresses) {
1966 resend_lost_keypresses();
1967 }
1968 if (windisplay_cat.is_debug()) {
1969 windisplay_cat.debug()
1970 << "syskeydown: " << wparam << " (" << lookup_key(wparam) << ")\n";
1971 }
1972 {
1973 // Alt and F10 are sent as WM_SYSKEYDOWN instead of WM_KEYDOWN want to
1974 // use defwindproc on Alt syskey so std windows cmd Alt-F4 works, etc
1975 POINT point;
1976 GetCursorPos(&point);
1977 ScreenToClient(hwnd, &point);
1978 handle_keypress(lookup_key(wparam), point.x, point.y,
1979 get_message_time());
1980
1981 if ((lparam & 0x40000000) == 0) {
1982 handle_raw_keypress(lookup_raw_key(lparam), get_message_time());
1983 }
1984
1985/*
1986 * wparam does not contain leftright information for SHIFT, CONTROL, or ALT,
1987 * so we have to track their status and do the right thing. We'll send the
1988 * leftright specific key event along with the general key event. Key
1989 * repeating is not being handled consistently for LALT and RALT, but from
1990 * comments below, it's only being handled the way it is for backspace, so
1991 * we'll leave it as is.
1992 */
1993 if (wparam == VK_MENU) {
1994 if ((GetKeyState(VK_LMENU) & 0x8000) != 0 && ! _lalt_down) {
1995 handle_keypress(KeyboardButton::lalt(), point.x, point.y,
1996 get_message_time());
1997 _lalt_down = true;
1998 }
1999 if ((GetKeyState(VK_RMENU) & 0x8000) != 0 && ! _ralt_down) {
2000 handle_keypress(KeyboardButton::ralt(), point.x, point.y,
2001 get_message_time());
2002 _ralt_down = true;
2003 }
2004 }
2005 if (wparam == VK_F10) {
2006 // bypass default windproc F10 behavior (it activates the main menu,
2007 // but we have none)
2008 return 0;
2009 }
2010 }
2011 break;
2012
2013 case WM_SYSCOMMAND:
2014 if (wparam == SC_KEYMENU) {
2015 // if Alt is released (alone wo other keys), defwindproc will send this
2016 // command, which will 'activate' the title bar menu (we have none) and
2017 // give focus to it. we don't want this to happen, so kill this msg.
2018
2019 // Note that the WM_SYSKEYUP message for Alt has already been sent (if
2020 // it is going to be), so ignoring this special message does no harm.
2021 return 0;
2022 }
2023 break;
2024
2025 case WM_KEYDOWN:
2026 if (_lost_keypresses) {
2027 resend_lost_keypresses();
2028 }
2029 if (windisplay_cat.is_debug()) {
2030 windisplay_cat.debug()
2031 << "keydown: " << wparam << " (" << lookup_key(wparam) << ")\n";
2032 }
2033
2034 // If this bit is not zero, this is just a keyrepeat echo; we ignore these
2035 // for handle_keypress (we respect keyrepeat only for handle_keystroke).
2036 if ((lparam & 0x40000000) == 0) {
2037 POINT point;
2038 GetCursorPos(&point);
2039 ScreenToClient(hwnd, &point);
2040 handle_keypress(lookup_key(wparam), point.x, point.y,
2041 get_message_time());
2042 handle_raw_keypress(lookup_raw_key(lparam), get_message_time());
2043
2044 // wparam does not contain leftright information for SHIFT, CONTROL, or
2045 // ALT, so we have to track their status and do the right thing. We'll
2046 // send the leftright specific key event along with the general key
2047 // event.
2048 if (wparam == VK_SHIFT) {
2049 if ((GetKeyState(VK_LSHIFT) & 0x8000) != 0 && ! _lshift_down) {
2050 handle_keypress(KeyboardButton::lshift(), point.x, point.y,
2051 get_message_time());
2052 _lshift_down = true;
2053 }
2054 if ((GetKeyState(VK_RSHIFT) & 0x8000) != 0 && ! _rshift_down) {
2055 handle_keypress(KeyboardButton::rshift(), point.x, point.y,
2056 get_message_time());
2057 _rshift_down = true;
2058 }
2059 } else if(wparam == VK_CONTROL) {
2060 if ((GetKeyState(VK_LCONTROL) & 0x8000) != 0 && ! _lcontrol_down) {
2061 handle_keypress(KeyboardButton::lcontrol(), point.x, point.y,
2062 get_message_time());
2063 _lcontrol_down = true;
2064 }
2065 if ((GetKeyState(VK_RCONTROL) & 0x8000) != 0 && ! _rcontrol_down) {
2066 handle_keypress(KeyboardButton::rcontrol(), point.x, point.y,
2067 get_message_time());
2068 _rcontrol_down = true;
2069 }
2070 }
2071
2072 // Handle Cntrl-V paste from clipboard. Is there a better way to detect
2073 // this hotkey?
2074 if ((wparam=='V') && (GetKeyState(VK_CONTROL) < 0) &&
2075 !_input_devices.empty() && paste_emit_keystrokes) {
2076 HGLOBAL hglb;
2077 char *lptstr;
2078
2079 if (IsClipboardFormatAvailable(CF_TEXT) && OpenClipboard(nullptr)) {
2080 // Maybe we should support CF_UNICODETEXT if it is available too?
2081 hglb = GetClipboardData(CF_TEXT);
2082 if (hglb!=nullptr) {
2083 lptstr = (char *) GlobalLock(hglb);
2084 if (lptstr != nullptr) {
2085 char *pChar;
2086 for (pChar = lptstr; *pChar; pChar++) {
2087 _input->keystroke((uchar)*pChar);
2088 }
2089 GlobalUnlock(hglb);
2090 }
2091 }
2092 CloseClipboard();
2093 }
2094 }
2095 } else {
2096 // Actually, for now we'll respect the repeat anyway, just so we support
2097 // backspace properly. Rethink later.
2098 POINT point;
2099 GetCursorPos(&point);
2100 ScreenToClient(hwnd, &point);
2101 handle_keypress(lookup_key(wparam), point.x, point.y,
2102 get_message_time());
2103
2104/*
2105 * wparam does not contain leftright information for SHIFT, CONTROL, or ALT,
2106 * so we have to track their status and do the right thing. We'll send the
2107 * leftright specific key event along with the general key event. If the user
2108 * presses LSHIFT and then RSHIFT, the RSHIFT event will come in with the
2109 * keyrepeat flag on (i.e. it will end up in this block). The logic below
2110 * should detect this correctly and only send the RSHIFT event. Note that the
2111 * CONTROL event will be sent twice, once for each keypress. Since keyrepeats
2112 * are currently being sent simply as additional keypress events, that should
2113 * be okay for now.
2114 */
2115 if (wparam == VK_SHIFT) {
2116 if (((GetKeyState(VK_LSHIFT) & 0x8000) != 0) && ! _lshift_down ) {
2117 handle_keypress(KeyboardButton::lshift(), point.x, point.y,
2118 get_message_time());
2119 _lshift_down = true;
2120 } else if (((GetKeyState(VK_RSHIFT) & 0x8000) != 0) && ! _rshift_down ) {
2121 handle_keypress(KeyboardButton::rshift(), point.x, point.y,
2122 get_message_time());
2123 _rshift_down = true;
2124 } else {
2125 if ((GetKeyState(VK_LSHIFT) & 0x8000) != 0) {
2126 handle_keypress(KeyboardButton::lshift(), point.x, point.y,
2127 get_message_time());
2128 }
2129 if ((GetKeyState(VK_RSHIFT) & 0x8000) != 0) {
2130 handle_keypress(KeyboardButton::rshift(), point.x, point.y,
2131 get_message_time());
2132 }
2133 }
2134 } else if(wparam == VK_CONTROL) {
2135 if (((GetKeyState(VK_LCONTROL) & 0x8000) != 0) && ! _lcontrol_down ) {
2136 handle_keypress(KeyboardButton::lcontrol(), point.x, point.y,
2137 get_message_time());
2138 _lcontrol_down = true;
2139 } else if (((GetKeyState(VK_RCONTROL) & 0x8000) != 0) && ! _rcontrol_down ) {
2140 handle_keypress(KeyboardButton::rcontrol(), point.x, point.y,
2141 get_message_time());
2142 _rcontrol_down = true;
2143 } else {
2144 if ((GetKeyState(VK_LCONTROL) & 0x8000) != 0) {
2145 handle_keypress(KeyboardButton::lcontrol(), point.x, point.y,
2146 get_message_time());
2147 }
2148 if ((GetKeyState(VK_RCONTROL) & 0x8000) != 0) {
2149 handle_keypress(KeyboardButton::rcontrol(), point.x, point.y,
2150 get_message_time());
2151 }
2152 }
2153 }
2154 }
2155 break;
2156
2157 case WM_SYSKEYUP:
2158 case WM_KEYUP:
2159 if (_lost_keypresses) {
2160 resend_lost_keypresses();
2161 }
2162 if (windisplay_cat.is_debug()) {
2163 windisplay_cat.debug()
2164 << "keyup: " << wparam << " (" << lookup_key(wparam) << ")\n";
2165 }
2166 handle_keyrelease(lookup_key(wparam), get_message_time());
2167 handle_raw_keyrelease(lookup_raw_key(lparam), get_message_time());
2168
2169 // wparam does not contain leftright information for SHIFT, CONTROL, or
2170 // ALT, so we have to track their status and do the right thing. We'll
2171 // send the leftright specific key event along with the general key event.
2172 if (wparam == VK_SHIFT) {
2173 if ((GetKeyState(VK_LSHIFT) & 0x8000) == 0 && _lshift_down) {
2174 handle_keyrelease(KeyboardButton::lshift(), get_message_time());
2175 _lshift_down = false;
2176 }
2177 if ((GetKeyState(VK_RSHIFT) & 0x8000) == 0 && _rshift_down) {
2178 handle_keyrelease(KeyboardButton::rshift(), get_message_time());
2179 _rshift_down = false;
2180 }
2181 } else if(wparam == VK_CONTROL) {
2182 if ((GetKeyState(VK_LCONTROL) & 0x8000) == 0 && _lcontrol_down) {
2183 handle_keyrelease(KeyboardButton::lcontrol(), get_message_time());
2184 _lcontrol_down = false;
2185 }
2186 if ((GetKeyState(VK_RCONTROL) & 0x8000) == 0 && _rcontrol_down) {
2187 handle_keyrelease(KeyboardButton::rcontrol(), get_message_time());
2188 _rcontrol_down = false;
2189 }
2190 } else if(wparam == VK_MENU) {
2191 if ((GetKeyState(VK_LMENU) & 0x8000) == 0 && _lalt_down) {
2192 handle_keyrelease(KeyboardButton::lalt(), get_message_time());
2193 _lalt_down = false;
2194 }
2195 if ((GetKeyState(VK_RMENU) & 0x8000) == 0 && _ralt_down) {
2196 handle_keyrelease(KeyboardButton::ralt(), get_message_time());
2197 _ralt_down = false;
2198 }
2199 }
2200 break;
2201
2202 case WM_KILLFOCUS:
2203 if (windisplay_cat.is_debug()) {
2204 windisplay_cat.debug()
2205 << "killfocus\n";
2206 }
2207
2208 _input->focus_lost(get_message_time());
2209 properties.set_foreground(false);
2210 system_changed_properties(properties);
2211 break;
2212
2213 case WM_SETFOCUS:
2214 // You would think that this would be a good time to call
2215 // resend_lost_keypresses(), but it turns out that we get WM_SETFOCUS
2216 // slightly before Windows starts resending key updown events to us.
2217
2218/*
2219 * In particular, if the user restored focus using alt-tab, then at this point
2220 * the keyboard state will indicate that both the alt and tab keys are held
2221 * down. However, there is a small window of opportunity for the user to
2222 * release these keys before Windows starts telling us about keyup events.
2223 * Thus, if we record the fact that alt and tab are being held down now, we
2224 * may miss the keyup events for them, and they can get "stuck" down.
2225 */
2226
2227 // So we have to defer calling resend_lost_keypresses() until we know
2228 // Windows is ready to send us key updown events. I don't know when we
2229 // can guarantee that, except when we actually do start to receive key
2230 // updown events, so that call is made there.
2231
2232 if (windisplay_cat.is_debug()) {
2233 windisplay_cat.debug()
2234 << "setfocus\n";
2235 }
2236
2237 if (_lost_keypresses) {
2238 resend_lost_keypresses();
2239 }
2240
2241 properties.set_foreground(true);
2242 system_changed_properties(properties);
2243 break;
2244
2245 case PM_ACTIVE:
2246 if (windisplay_cat.is_debug()) {
2247 windisplay_cat.debug()
2248 << "PM_ACTIVE\n";
2249 }
2250 properties.set_foreground(true);
2251 system_changed_properties(properties);
2252 break;
2253
2254 case PM_INACTIVE:
2255 if (windisplay_cat.is_debug()) {
2256 windisplay_cat.debug()
2257 << "PM_INACTIVE\n";
2258 }
2259 properties.set_foreground(false);
2260 system_changed_properties(properties);
2261 break;
2262
2263 case WM_DPICHANGED:
2264 // The window moved to a monitor of different DPI, or someone changed the
2265 // DPI setting in the configuration panel.
2266 if (windisplay_cat.is_debug()) {
2267 windisplay_cat.debug() << "DPI changed to " << LOWORD(wparam);
2268
2269 if (LOWORD(wparam) != HIWORD(wparam)) {
2270 windisplay_cat.debug(false) << "x" << HIWORD(wparam) << "\n";
2271 } else {
2272 windisplay_cat.debug(false) << "\n";
2273 }
2274 }
2275 // Resize the window if requested to match the new DPI. Obviously, don't
2276 // do this if a fixed size was requested.
2277 if (!_properties.get_fixed_size() && dpi_window_resize) {
2278 RECT &rect = *(LPRECT)lparam;
2279 SetWindowPos(_hWnd, HWND_TOP, rect.left, rect.top,
2280 rect.right - rect.left, rect.bottom - rect.top,
2281 SWP_NOZORDER | SWP_NOACTIVATE);
2282 }
2283 break;
2284
2285 case WM_TOUCH:
2286 _num_touches = LOWORD(wparam);
2287 if (_num_touches > MAX_TOUCHES) {
2288 _num_touches = MAX_TOUCHES;
2289 }
2290 if (pGetTouchInputInfo != 0) {
2291 pGetTouchInputInfo((HTOUCHINPUT)lparam, _num_touches, _touches, sizeof(TOUCHINPUT));
2292 pCloseTouchInputHandle((HTOUCHINPUT)lparam);
2293 }
2294 break;
2295 }
2296
2297 // do custom messages processing if any has been set
2298 for ( WinProcClasses::iterator it=_window_proc_classes.begin() ; it != _window_proc_classes.end(); it++ ){
2299 (*it)->wnd_proc(this, hwnd, msg, wparam, lparam);
2300 }
2301
2302 return DefWindowProcW(hwnd, msg, wparam, lparam);
2303}
2304
2305
2306/**
2307 * This is attached to the window class for all WinGraphicsWindow windows; it
2308 * is called to handle window events.
2309 */
2311static_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
2312 // Look up the window in our global map.
2313 WindowHandles::const_iterator wi;
2314 wi = _window_handles.find(hwnd);
2315 if (wi != _window_handles.end()) {
2316 // We found the window.
2317 return (*wi).second->window_proc(hwnd, msg, wparam, lparam);
2318 }
2319
2320 // The window wasn't in the map; we must be creating it right now.
2321 if (_creating_window != nullptr) {
2322 return _creating_window->window_proc(hwnd, msg, wparam, lparam);
2323 }
2324
2325 // Oops, we weren't creating a window! Don't know how to handle the
2326 // message, so just pass it on to Windows to deal with it.
2327 return DefWindowProcW(hwnd, msg, wparam, lparam);
2328}
2329
2330/**
2331 * Handles one event from the message queue.
2332 */
2333void WinGraphicsWindow::
2334process_1_event() {
2335 MSG msg;
2336
2337 if (!GetMessage(&msg, nullptr, 0, 0)) {
2338 // WM_QUIT received. We need a cleaner way to deal with this.
2339 // DestroyAllWindows(false);
2340 exit(msg.wParam); // this will invoke AtExitFn
2341 }
2342
2343 // Translate virtual key messages
2344 TranslateMessage(&msg);
2345 // Call window_proc
2346 DispatchMessage(&msg);
2347}
2348
2349/**
2350 * Called when the keyboard focus has been restored to the window after it has
2351 * been lost for a time, this rechecks the keyboard state and generates key
2352 * up/down messages for keys that have changed state in the meantime.
2353 */
2354void WinGraphicsWindow::
2355resend_lost_keypresses() {
2356 nassertv(_lost_keypresses);
2357 // This is now a no-op. Not sure we really want to generate new "down" or
2358 // "resume" events for keys that were held while the window focus is
2359 // restored.
2360
2361 _lost_keypresses = false;
2362}
2363
2364/**
2365 * Changes _cursor_window from its current value to the indicated value. This
2366 * also changes the cursor properties appropriately.
2367 */
2368void WinGraphicsWindow::
2369update_cursor_window(WinGraphicsWindow *to_window) {
2370 bool hide_cursor = false;
2371 if (to_window == nullptr) {
2372 // We are leaving a graphics window; we should restore the Win2000
2373 // effects.
2374 if (_got_saved_params) {
2375 SystemParametersInfo(SPI_SETMOUSETRAILS, _saved_mouse_trails,
2376 0, 0);
2377 SystemParametersInfo(SPI_SETCURSORSHADOW, 0,
2378 _saved_cursor_shadow ? (PVOID)1 : nullptr, 0);
2379 SystemParametersInfo(SPI_SETMOUSEVANISH, 0,
2380 _saved_mouse_vanish ? (PVOID)1 : nullptr, 0);
2381 _got_saved_params = false;
2382 }
2383
2384 } else {
2385 const WindowProperties &to_props = to_window->get_properties();
2386 hide_cursor = to_props.get_cursor_hidden();
2387
2388 // We are entering a graphics window; we should save and disable the
2389 // Win2000 effects. These don't work at all well over a 3-D window.
2390
2391 // These parameters are only defined for Win2000XP, but they should just
2392 // cause a silent error on earlier OS's, which is OK.
2393 if (!_got_saved_params) {
2394 SystemParametersInfo(SPI_GETMOUSETRAILS, 0,
2395 &_saved_mouse_trails, 0);
2396 SystemParametersInfo(SPI_GETCURSORSHADOW, 0,
2397 &_saved_cursor_shadow, 0);
2398 SystemParametersInfo(SPI_GETMOUSEVANISH, 0,
2399 &_saved_mouse_vanish, 0);
2400 _got_saved_params = true;
2401
2402 SystemParametersInfo(SPI_SETMOUSETRAILS, 0, (PVOID)0, 0);
2403 SystemParametersInfo(SPI_SETCURSORSHADOW, 0, (PVOID)false, 0);
2404 SystemParametersInfo(SPI_SETMOUSEVANISH, 0, (PVOID)false, 0);
2405 }
2406
2407 SetCursor(to_window->_cursor);
2408 }
2409
2410 hide_or_show_cursor(hide_cursor);
2411
2412 _cursor_window = to_window;
2413}
2414
2415/**
2416 * Hides or shows the mouse cursor according to the indicated parameter. This
2417 * is normally called when the mouse wanders into or out of a window with the
2418 * cursor_hidden properties.
2419 */
2420void WinGraphicsWindow::
2421hide_or_show_cursor(bool hide_cursor) {
2422 if (hide_cursor) {
2423 if (!_cursor_hidden) {
2424 ShowCursor(false);
2425 _cursor_hidden = true;
2426 }
2427 } else {
2428 if (_cursor_hidden) {
2429 ShowCursor(true);
2430 _cursor_hidden = false;
2431 }
2432 }
2433}
2434
2435// don't pick any video modes < MIN_REFRESH_RATE Hz
2436#define MIN_REFRESH_RATE 60
2437// EnumDisplaySettings may indicate 0 or 1 for refresh rate, which means use
2438// driver default rate (assume its >min_refresh_rate)
2439#define ACCEPTABLE_REFRESH_RATE(RATE) ((RATE >= MIN_REFRESH_RATE) || (RATE==0) || (RATE==1))
2440
2441/**
2442 * Looks for a fullscreen mode that meets the specified size and bitdepth
2443 * requirements. Returns true if a suitable mode is found, false otherwise.
2444 */
2445bool WinGraphicsWindow::
2446find_acceptable_display_mode(DWORD dwWidth, DWORD dwHeight, DWORD bpp,
2447 DEVMODE &dm) {
2448
2449 // Get the current mode. We'll try to match the refresh rate.
2450 DEVMODE cur_dm;
2451 ZeroMemory(&cur_dm, sizeof(cur_dm));
2452 cur_dm.dmSize = sizeof(cur_dm);
2453 EnumDisplaySettings(nullptr, ENUM_CURRENT_SETTINGS, &cur_dm);
2454
2455 int modenum = 0;
2456 int saved_modenum = -1;
2457
2458 while (1) {
2459 ZeroMemory(&dm, sizeof(dm));
2460 dm.dmSize = sizeof(dm);
2461
2462 if (!EnumDisplaySettings(nullptr, modenum, &dm)) {
2463 break;
2464 }
2465
2466 if ((dm.dmPelsWidth == dwWidth) && (dm.dmPelsHeight == dwHeight) &&
2467 (dm.dmBitsPerPel == bpp)) {
2468 // If this also matches in refresh rate, we're done here. Otherwise,
2469 // save this as a second choice for later.
2470 if (dm.dmDisplayFrequency == cur_dm.dmDisplayFrequency) {
2471 return true;
2472 } else if (saved_modenum == -1) {
2473 saved_modenum = modenum;
2474 }
2475 }
2476 modenum++;
2477 }
2478
2479 // Failed to find an exact match, but we do have a match that didn't match
2480 // the refresh rate.
2481 if (saved_modenum != -1) {
2482 ZeroMemory(&dm, sizeof(dm));
2483 dm.dmSize = sizeof(dm);
2484
2485 if (EnumDisplaySettings(nullptr, saved_modenum, &dm)) {
2486 return true;
2487 }
2488 }
2489
2490 return false;
2491}
2492
2493/**
2494 * Pops up a dialog box with the indicated Windows error message ID (or the
2495 * last error message generated) for meaningful display to the user.
2496 */
2497void WinGraphicsWindow::
2498show_error_message(DWORD message_id) {
2499 LPTSTR message_buffer;
2500
2501 if (message_id == 0) {
2502 message_id = GetLastError();
2503 }
2504
2505 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
2506 nullptr, message_id,
2507 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), //The user default language
2508 (LPTSTR)&message_buffer, // the weird ptrptr->ptr cast is intentional, see FORMAT_MESSAGE_ALLOCATE_BUFFER
2509 1024, nullptr);
2510 MessageBox(GetDesktopWindow(), message_buffer, _T(errorbox_title), MB_OK);
2511 windisplay_cat.fatal() << "System error msg: " << message_buffer << endl;
2512 LocalFree(message_buffer);
2513}
2514
2515/**
2516 *
2517 */
2518void WinGraphicsWindow::
2519handle_keypress(ButtonHandle key, int x, int y, double time) {
2520 _input->set_pointer_in_window(x, y);
2521 if (key != ButtonHandle::none()) {
2522 _input->button_down(key, time);
2523 }
2524}
2525
2526/**
2527 * Indicates we detected a key was already down when the focus is restored to
2528 * the window. Mainly useful for tracking the state of modifier keys.
2529 */
2530void WinGraphicsWindow::
2531handle_keyresume(ButtonHandle key, double time) {
2532 if (key != ButtonHandle::none()) {
2533 _input->button_resume_down(key, time);
2534 }
2535}
2536
2537/**
2538 *
2539 */
2540void WinGraphicsWindow::
2541handle_keyrelease(ButtonHandle key, double time) {
2542 if (key != ButtonHandle::none()) {
2543 _input->button_up(key, time);
2544 }
2545}
2546
2547/**
2548 *
2549 */
2550void WinGraphicsWindow::
2551handle_raw_keypress(ButtonHandle key, double time) {
2552 if (key != ButtonHandle::none()) {
2553 _input->raw_button_down(key, time);
2554 }
2555}
2556
2557/**
2558 *
2559 */
2560void WinGraphicsWindow::
2561handle_raw_keyrelease(ButtonHandle key, double time) {
2562 if (key != ButtonHandle::none()) {
2563 _input->raw_button_up(key, time);
2564 }
2565}
2566
2567/**
2568 * Translates the keycode reported by Windows to an appropriate Panda
2569 * ButtonHandle.
2570 */
2571ButtonHandle WinGraphicsWindow::
2572lookup_key(WPARAM wparam) const {
2573 // First, check for a few buttons that we filter out when the IME window is
2574 // open.
2575 if (!_ime_active) {
2576 switch(wparam) {
2577 case VK_BACK: return KeyboardButton::backspace();
2578 case VK_DELETE: return KeyboardButton::del();
2579 case VK_ESCAPE: return KeyboardButton::escape();
2580 case VK_SPACE: return KeyboardButton::space();
2581 case VK_UP: return KeyboardButton::up();
2582 case VK_DOWN: return KeyboardButton::down();
2583 case VK_LEFT: return KeyboardButton::left();
2584 case VK_RIGHT: return KeyboardButton::right();
2585 }
2586 }
2587
2588 // Now check for the rest of the buttons, including the ones that we allow
2589 // through even when the IME window is open.
2590 switch(wparam) {
2591 case VK_TAB: return KeyboardButton::tab();
2592 case VK_PRIOR: return KeyboardButton::page_up();
2593 case VK_NEXT: return KeyboardButton::page_down();
2594 case VK_HOME: return KeyboardButton::home();
2595 case VK_END: return KeyboardButton::end();
2596 case VK_F1: return KeyboardButton::f1();
2597 case VK_F2: return KeyboardButton::f2();
2598 case VK_F3: return KeyboardButton::f3();
2599 case VK_F4: return KeyboardButton::f4();
2600 case VK_F5: return KeyboardButton::f5();
2601 case VK_F6: return KeyboardButton::f6();
2602 case VK_F7: return KeyboardButton::f7();
2603 case VK_F8: return KeyboardButton::f8();
2604 case VK_F9: return KeyboardButton::f9();
2605 case VK_F10: return KeyboardButton::f10();
2606 case VK_F11: return KeyboardButton::f11();
2607 case VK_F12: return KeyboardButton::f12();
2608 case VK_INSERT: return KeyboardButton::insert();
2609 case VK_CAPITAL: return KeyboardButton::caps_lock();
2610 case VK_NUMLOCK: return KeyboardButton::num_lock();
2611 case VK_SCROLL: return KeyboardButton::scroll_lock();
2612 case VK_PAUSE: return KeyboardButton::pause();
2613 case VK_SNAPSHOT: return KeyboardButton::print_screen();
2614
2615 case VK_SHIFT: return KeyboardButton::shift();
2616 case VK_LSHIFT: return KeyboardButton::lshift();
2617 case VK_RSHIFT: return KeyboardButton::rshift();
2618
2619 case VK_CONTROL: return KeyboardButton::control();
2620 case VK_LCONTROL: return KeyboardButton::lcontrol();
2621 case VK_RCONTROL: return KeyboardButton::rcontrol();
2622
2623 case VK_MENU: return KeyboardButton::alt();
2624 case VK_LMENU: return KeyboardButton::lalt();
2625 case VK_RMENU: return KeyboardButton::ralt();
2626
2627 case VK_LWIN: return KeyboardButton::lmeta();
2628 case VK_RWIN: return KeyboardButton::rmeta();
2629 case VK_APPS: return KeyboardButton::menu();
2630
2631 default:
2632 int key = MapVirtualKey(wparam, 2);
2633 if (isascii(key) && key != 0) {
2634 // We used to try to remap lowercase to uppercase keys here based on the
2635 // state of the shift andor caps lock keys. But that's a mistake, and
2636 // doesn't allow for international or user-defined keyboards; let
2637 // Windows do that mapping.
2638
2639 // Nowadays, we make a distinction between a "button" and a "keystroke".
2640 // A button corresponds to a physical button on the keyboard and has a
2641 // down and up event associated. A keystroke may or may not correspond
2642 // to a physical button, but will be some Unicode character and will not
2643 // have a corresponding up event.
2644 return KeyboardButton::ascii_key(tolower(key));
2645 }
2646 break;
2647 }
2648 return ButtonHandle::none();
2649}
2650
2651/**
2652 * Translates the scancode reported by Windows to an appropriate Panda
2653 * ButtonHandle.
2654 */
2655ButtonHandle WinGraphicsWindow::
2656lookup_raw_key(LPARAM lparam) const {
2657 unsigned char vsc = (lparam & 0xff0000) >> 16;
2658
2659 if (lparam & 0x1000000) {
2660 // Extended keys
2661 switch (vsc) {
2662 case 28: return KeyboardButton::enter();
2663 case 29: return KeyboardButton::rcontrol();
2664 case 53: return KeyboardButton::ascii_key('/');
2665 case 55: return KeyboardButton::print_screen();
2666 case 56: return KeyboardButton::ralt();
2667 case 69: return KeyboardButton::num_lock();
2668 case 71: return KeyboardButton::home();
2669 case 72: return KeyboardButton::up();
2670 case 73: return KeyboardButton::page_up();
2671 case 75: return KeyboardButton::left();
2672 case 77: return KeyboardButton::right();
2673 case 79: return KeyboardButton::end();
2674 case 80: return KeyboardButton::down();
2675 case 81: return KeyboardButton::page_down();
2676 case 82: return KeyboardButton::insert();
2677 case 83: return KeyboardButton::del();
2678 case 91: return KeyboardButton::lmeta();
2679 case 92: return KeyboardButton::rmeta();
2680 case 93: return KeyboardButton::menu();
2681 }
2682 }
2683
2684 if (vsc <= 83) {
2685 static ButtonHandle raw_map[] = {
2686 ButtonHandle::none(),
2687 KeyboardButton::escape(),
2700 KeyboardButton::backspace(),
2701 KeyboardButton::tab(),
2714 KeyboardButton::enter(),
2715 KeyboardButton::lcontrol(),
2728 KeyboardButton::lshift(),
2740 KeyboardButton::rshift(),
2742 KeyboardButton::lalt(),
2743 KeyboardButton::space(),
2744 KeyboardButton::caps_lock(),
2745 KeyboardButton::f1(),
2746 KeyboardButton::f2(),
2747 KeyboardButton::f3(),
2748 KeyboardButton::f4(),
2749 KeyboardButton::f5(),
2750 KeyboardButton::f6(),
2751 KeyboardButton::f7(),
2752 KeyboardButton::f8(),
2753 KeyboardButton::f9(),
2754 KeyboardButton::f10(),
2755 KeyboardButton::pause(),
2756 KeyboardButton::scroll_lock(),
2770 };
2771 return raw_map[vsc];
2772 }
2773
2774 // A few additional keys don't fit well in the above table.
2775 switch (vsc) {
2776 case 86: return KeyboardButton::ascii_key('<'); // Between lshift and z
2777 case 87: return KeyboardButton::f11();
2778 case 88: return KeyboardButton::f12();
2779 default: return ButtonHandle::none();
2780 }
2781}
2782
2783/**
2784 * Returns a ButtonMap containing the association between raw buttons and
2785 * virtual buttons.
2786 *
2787 * Note that on Windows, the pause button and numpad keys are not mapped
2788 * reliably.
2789 */
2790ButtonMap *WinGraphicsWindow::
2791get_keyboard_map() const {
2792 ButtonMap *map = new ButtonMap;
2793
2794 wchar_t text[256];
2795 UINT vsc = 0;
2796 unsigned short ex_vsc[] = {0x56, 0x57, 0x58,
2797 0x011c, 0x011d, 0x0135, 0x0137, 0x0138, 0x0145, 0x0147, 0x0148, 0x0149, 0x014b, 0x014d, 0x014f, 0x0150, 0x0151, 0x0152, 0x0153, 0x015b, 0x015c, 0x015d};
2798
2799 for (int k = 1; k < 84 + sizeof(ex_vsc) / sizeof(short); ++k) {
2800 if (k >= 84) {
2801 vsc = ex_vsc[k - 84];
2802 } else {
2803 vsc = k;
2804 }
2805
2806 UINT lparam = vsc << 16;
2807 ButtonHandle raw_button = lookup_raw_key(lparam);
2808 if (raw_button == ButtonHandle::none()) {
2809 continue;
2810 }
2811
2812 ButtonHandle button;
2813 if (vsc == 0x45) {
2814 button = KeyboardButton::pause();
2815
2816 } else if (vsc >= 0x47 && vsc <= 0x53) {
2817 // The numpad keys are not mapped correctly, see KB72583
2818 button = raw_button;
2819
2820 } else {
2821 if (vsc == 0x145) {
2822 // Don't ask why - I have no idea.
2823 vsc = 0x45;
2824 }
2825 if (vsc & 0x0100) {
2826 // MapVirtualKey recognizes extended codes differently.
2827 vsc ^= 0xe100;
2828 }
2829
2830 UINT vk = MapVirtualKeyA(vsc, MAPVK_VSC_TO_VK_EX);
2831 button = lookup_key(vk);
2832 //if (button == ButtonHandle::none()) {
2833 // continue;
2834 //}
2835 }
2836
2837 int len = GetKeyNameTextW(lparam, text, 256);
2838 TextEncoder enc;
2839 enc.set_wtext(wstring(text, len));
2840 map->map_button(raw_button, button, enc.get_text());
2841 }
2842
2843 return map;
2844}
2845
2846/**
2847 *
2848 */
2849void WinGraphicsWindow::
2850handle_raw_input(HRAWINPUT hraw) {
2851 LPBYTE lpb;
2852 UINT dwSize;
2853
2854 if (hraw == 0) {
2855 return;
2856 }
2857 if (GetRawInputData(hraw, RID_INPUT, nullptr, &dwSize, sizeof(RAWINPUTHEADER)) == -1) {
2858 return;
2859 }
2860
2861 lpb = (LPBYTE)alloca(sizeof(LPBYTE) * dwSize);
2862 if (lpb == nullptr) {
2863 return;
2864 }
2865
2866 if (GetRawInputData(hraw, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER)) != dwSize) {
2867 return;
2868 }
2869
2870 RAWINPUT *raw = (RAWINPUT *)lpb;
2871 if (raw->header.hDevice == 0) {
2872 return;
2873 }
2874
2875 for (size_t i = 1; i < _input_devices.size(); ++i) {
2876 if (_input_device_handle[i] == raw->header.hDevice) {
2877 PT(GraphicsWindowInputDevice) input =
2878 DCAST(GraphicsWindowInputDevice, _input_devices[i]);
2879
2880 int adjx = raw->data.mouse.lLastX;
2881 int adjy = raw->data.mouse.lLastY;
2882
2883 if (raw->data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) {
2884 input->set_pointer_in_window(adjx, adjy);
2885 } else {
2886 input->pointer_moved(adjx, adjy);
2887 }
2888
2889 if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_DOWN) {
2890 input->button_down(MouseButton::button(0), get_message_time());
2891 }
2892 if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_UP) {
2893 input->button_up(MouseButton::button(0), get_message_time());
2894 }
2895 if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_2_DOWN) {
2896 input->button_down(MouseButton::button(2), get_message_time());
2897 }
2898 if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_2_UP) {
2899 input->button_up(MouseButton::button(2), get_message_time());
2900 }
2901 if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_3_DOWN) {
2902 input->button_down(MouseButton::button(1), get_message_time());
2903 }
2904 if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_3_UP) {
2905 input->button_up(MouseButton::button(1), get_message_time());
2906 }
2907 if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_DOWN) {
2908 input->button_down(MouseButton::button(3), get_message_time());
2909 }
2910 if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_UP) {
2911 input->button_up(MouseButton::button(3), get_message_time());
2912 }
2913 if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_DOWN) {
2914 input->button_down(MouseButton::button(4), get_message_time());
2915 }
2916 if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_UP) {
2917 input->button_up(MouseButton::button(4), get_message_time());
2918 }
2919 }
2920 }
2921}
2922
2923/**
2924 *
2925 */
2926bool WinGraphicsWindow::
2927handle_mouse_motion(int x, int y) {
2928 _input->set_pointer_in_window(x, y);
2929 return false;
2930}
2931
2932/**
2933 *
2934 */
2935void WinGraphicsWindow::
2936handle_mouse_exit() {
2937 // note: 'mouse_motion' is considered the 'entry' event
2938 _input->set_pointer_out_of_window();
2939}
2940
2941/**
2942 * Loads and returns an HICON corresponding to the indicated filename. If the
2943 * file cannot be loaded, returns 0.
2944 */
2945HICON WinGraphicsWindow::
2946get_icon(const Filename &filename) {
2947 // First, look for the unresolved filename in our index.
2948 IconFilenames::iterator fi = _icon_filenames.find(filename);
2949 if (fi != _icon_filenames.end()) {
2950 return (HICON)((*fi).second);
2951 }
2952
2953 // If it wasn't found, resolve the filename and search for that.
2954
2955 // Since we have to use a Windows call to load the image from a filename, we
2956 // can't load a virtual file and we can't use the virtual file system.
2957 Filename resolved = filename;
2958 if (!resolved.resolve_filename(get_model_path())) {
2959 // The filename doesn't exist along the search path.
2960 if (resolved.is_fully_qualified() && resolved.exists()) {
2961 // But it does exist locally, so accept it.
2962
2963 } else {
2964 windisplay_cat.warning()
2965 << "Could not find icon filename " << filename << "\n";
2966 return 0;
2967 }
2968 }
2969 fi = _icon_filenames.find(resolved);
2970 if (fi != _icon_filenames.end()) {
2971 _icon_filenames[filename] = (*fi).second;
2972 return (HICON)((*fi).second);
2973 }
2974
2975 Filename os = resolved.to_os_specific();
2976
2977 HANDLE h = LoadImage(nullptr, os.c_str(),
2978 IMAGE_ICON, 0, 0, LR_LOADFROMFILE | LR_DEFAULTSIZE);
2979 if (h == 0) {
2980 windisplay_cat.warning()
2981 << "windows icon filename '" << os << "' could not be loaded!!\n";
2982 }
2983
2984 _icon_filenames[filename] = h;
2985 _icon_filenames[resolved] = h;
2986 return (HICON)h;
2987}
2988
2989/**
2990 * Loads and returns an HCURSOR corresponding to the indicated filename. If
2991 * the file cannot be loaded, returns 0.
2992 */
2993HCURSOR WinGraphicsWindow::
2994get_cursor(const Filename &filename) {
2995 // The empty filename means to disable a custom cursor.
2996 if (filename.empty()) {
2997 return 0;
2998 }
2999
3000 // First, look for the unresolved filename in our index.
3001 IconFilenames::iterator fi = _cursor_filenames.find(filename);
3002 if (fi != _cursor_filenames.end()) {
3003 return (HCURSOR)((*fi).second);
3004 }
3005
3006 // If it wasn't found, resolve the filename and search for that.
3007
3008 // Since we have to use a Windows call to load the image from a filename, we
3009 // can't load a virtual file and we can't use the virtual file system.
3010 Filename resolved = filename;
3011 if (!resolved.resolve_filename(get_model_path())) {
3012 // The filename doesn't exist.
3013 windisplay_cat.warning()
3014 << "Could not find cursor filename " << filename << "\n";
3015 return 0;
3016 }
3017 fi = _cursor_filenames.find(resolved);
3018 if (fi != _cursor_filenames.end()) {
3019 _cursor_filenames[filename] = (*fi).second;
3020 return (HCURSOR)((*fi).second);
3021 }
3022
3023 Filename os = resolved.to_os_specific();
3024
3025 HANDLE h = LoadImage(nullptr, os.c_str(),
3026 IMAGE_CURSOR, 0, 0, LR_LOADFROMFILE);
3027 if (h == 0) {
3028 windisplay_cat.warning()
3029 << "windows cursor filename '" << os << "' could not be loaded!!\n";
3030 show_error_message();
3031 }
3032
3033 _cursor_filenames[filename] = h;
3034 _cursor_filenames[resolved] = h;
3035 return (HCURSOR)h;
3036}
3037
3038static HCURSOR get_cursor(const Filename &filename);
3039
3040/**
3041 * Registers a Window class appropriate for the indicated properties. This
3042 * class may be shared by multiple windows.
3043 */
3044const WinGraphicsWindow::WindowClass &WinGraphicsWindow::
3045register_window_class(const WindowProperties &props) {
3046 WindowClass wcreg(props);
3047 std::wostringstream wclass_name;
3048 wclass_name << L"WinGraphicsWindow" << _window_class_index;
3049 wcreg._name = wclass_name.str();
3050
3051 std::pair<WindowClasses::iterator, bool> found = _window_classes.insert(wcreg);
3052 const WindowClass &wclass = (*found.first);
3053
3054 if (!found.second) {
3055 // We have already created a window class.
3056 return wclass;
3057 }
3058
3059 // We have not yet created this window class.
3060 _window_class_index++;
3061
3062 WNDCLASSW wc;
3063
3064 HINSTANCE instance = GetModuleHandle(nullptr);
3065
3066 // Clear before filling in window structure!
3067 ZeroMemory(&wc, sizeof(wc));
3068 wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
3069 wc.lpfnWndProc = (WNDPROC)static_window_proc;
3070 wc.hInstance = instance;
3071
3072 wc.hIcon = wclass._icon;
3073
3074 wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
3075 wc.lpszMenuName = nullptr;
3076 wc.lpszClassName = wclass._name.c_str();
3077
3078 if (!RegisterClassW(&wc)) {
3079 windisplay_cat.error()
3080 << "could not register window class " << wclass._name << "!" << endl;
3081 return wclass;
3082 }
3083
3084 return wclass;
3085}
3086
3087/**
3088 *
3089 */
3090WinGraphicsWindow::WinWindowHandle::
3091WinWindowHandle(WinGraphicsWindow *window, const WindowHandle &copy) :
3092 WindowHandle(copy),
3093 _window(window)
3094{
3095}
3096
3097/**
3098 * Should be called by the WinGraphicsWindow's destructor, so that we don't
3099 * end up with a floating pointer should this object persist beyond the
3100 * lifespan of its window.
3101 */
3102void WinGraphicsWindow::WinWindowHandle::
3103clear_window() {
3104 _window = nullptr;
3105}
3106
3107/**
3108 * Called on a child handle to deliver a keyboard button event generated in
3109 * the parent window.
3110 */
3111void WinGraphicsWindow::WinWindowHandle::
3112receive_windows_message(unsigned int msg, int wparam, int lparam) {
3113 if (_window != nullptr) {
3114 _window->receive_windows_message(msg, wparam, lparam);
3115 }
3116}
3117
3118
3119// pops up MsgBox wsystem error msg
3120void PrintErrorMessage(DWORD msgID) {
3121 LPTSTR pMessageBuffer;
3122
3123 if (msgID==PRINT_LAST_ERROR)
3124 msgID=GetLastError();
3125
3126 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
3127 nullptr,msgID,
3128 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), //The user default language
3129 (LPTSTR) &pMessageBuffer, // the weird ptrptr->ptr cast is intentional, see FORMAT_MESSAGE_ALLOCATE_BUFFER
3130 1024, nullptr);
3131 MessageBox(GetDesktopWindow(),pMessageBuffer,_T(errorbox_title),MB_OK);
3132 windisplay_cat.fatal() << "System error msg: " << pMessageBuffer << endl;
3133 LocalFree( pMessageBuffer );
3134}
3135
3136void
3137ClearToBlack(HWND hWnd, const WindowProperties &props) {
3138 if (!props.has_origin()) {
3139 if (windisplay_cat.is_debug()) {
3140 windisplay_cat.debug()
3141 << "Skipping ClearToBlack, no origin specified yet.\n";
3142 }
3143 return;
3144 }
3145
3146 if (windisplay_cat.is_debug()) {
3147 windisplay_cat.debug()
3148 << "ClearToBlack(" << hWnd << ", " << props << ")\n";
3149 }
3150 // clear to black
3151 HDC hDC=GetDC(hWnd); // GetDC is not particularly fast. if this needs to be super-quick, we should cache GetDC's hDC
3152 RECT clrRect = {
3153 props.get_x_origin(), props.get_y_origin(),
3154 props.get_x_origin() + props.get_x_size(),
3155 props.get_y_origin() + props.get_y_size()
3156 };
3157 FillRect(hDC,&clrRect,(HBRUSH)GetStockObject(BLACK_BRUSH));
3158 ReleaseDC(hWnd,hDC);
3159 GdiFlush();
3160}
3161
3162/**
3163 * Fills view_rect with the coordinates of the client area of the indicated
3164 * window, converted to screen coordinates.
3165 */
3166void get_client_rect_screen(HWND hwnd, RECT *view_rect) {
3167 GetClientRect(hwnd, view_rect);
3168
3169 POINT ul, lr;
3170 ul.x = view_rect->left;
3171 ul.y = view_rect->top;
3172 lr.x = view_rect->right;
3173 lr.y = view_rect->bottom;
3174
3175 ClientToScreen(hwnd, &ul);
3176 ClientToScreen(hwnd, &lr);
3177
3178 view_rect->left = ul.x;
3179 view_rect->top = ul.y;
3180 view_rect->right = lr.x;
3181 view_rect->bottom = lr.y;
3182}
3183
3184/**
3185 * Adds the specified Windows proc event handler to be called whenever a
3186 * Windows event occurs.
3187 *
3188 */
3190 nassertv(wnd_proc != nullptr);
3191 _window_proc_classes.insert( (GraphicsWindowProc*)wnd_proc );
3192}
3193
3194/**
3195 * Removes the specified Windows proc event handler.
3196 *
3197 */
3199 nassertv(wnd_proc != nullptr);
3200 _window_proc_classes.erase( (GraphicsWindowProc*)wnd_proc );
3201}
3202
3203/**
3204 * Removes all Windows proc event handlers.
3205 *
3206 */
3208 _window_proc_classes.clear();
3209}
3210
3211/**
3212 * Returns whether this window supports adding of windows proc handlers.
3213 *
3214 */
3216 return true;
3217}
3218
3219/**
3220 * Returns whether the specified event msg is a touch message.
3221 *
3222 */
3225 return callbackData->get_msg() == WM_TOUCH;
3226}
3227
3228/**
3229 * Returns the current number of touches on this window.
3230 *
3231 */
3234 return _num_touches;
3235}
3236
3237/**
3238 * Returns the TouchInfo object describing the specified touch.
3239 *
3240 */
3242get_touch_info(int index) {
3243 nassertr(index >= 0 && index < MAX_TOUCHES, TouchInfo());
3244
3245 TOUCHINPUT ti = _touches[index];
3246 POINT point;
3247 point.x = TOUCH_COORD_TO_PIXEL(ti.x);
3248 point.y = TOUCH_COORD_TO_PIXEL(ti.y);
3249 ScreenToClient(_hWnd, &point);
3250
3251 TouchInfo ret = TouchInfo();
3252 ret.set_x(point.x);
3253 ret.set_y(point.y);
3254 ret.set_id(ti.dwID);
3255 ret.set_flags(ti.dwFlags);
3256 return ret;
3257}
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 ...
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
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.
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 ...