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