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