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