3036 lines
95 KiB
C++
3036 lines
95 KiB
C++
/**
|
|
* PANDA 3D SOFTWARE
|
|
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
|
*
|
|
* All use of this software is subject to the terms of the revised BSD
|
|
* license. You should have received a copy of this license along
|
|
* with this source code in a file named "LICENSE."
|
|
*
|
|
* @file winGraphicsWindow.cxx
|
|
* @author drose
|
|
* @date 2002-12-20
|
|
*/
|
|
|
|
#include "winGraphicsWindow.h"
|
|
#include "config_windisplay.h"
|
|
#include "winGraphicsPipe.h"
|
|
|
|
#include "graphicsPipe.h"
|
|
#include "keyboardButton.h"
|
|
#include "mouseButton.h"
|
|
#include "clockObject.h"
|
|
#include "config_util.h"
|
|
#include "throw_event.h"
|
|
#include "nativeWindowHandle.h"
|
|
|
|
#include <tchar.h>
|
|
|
|
#ifndef WM_DPICHANGED
|
|
#define WM_DPICHANGED 0x02E0
|
|
#endif
|
|
|
|
#ifndef WM_TOUCH
|
|
#define WM_TOUCH 0x0240
|
|
#endif
|
|
|
|
#if WINVER < 0x0601
|
|
// Not used on Windows XP, but we still need to define it.
|
|
#define TOUCH_COORD_TO_PIXEL(l) ((l) / 100)
|
|
|
|
DECLARE_HANDLE(HTOUCHINPUT);
|
|
#endif
|
|
|
|
TypeHandle WinGraphicsWindow::_type_handle;
|
|
TypeHandle WinGraphicsWindow::WinWindowHandle::_type_handle;
|
|
|
|
WinGraphicsWindow::WindowHandles WinGraphicsWindow::_window_handles;
|
|
WinGraphicsWindow *WinGraphicsWindow::_creating_window = NULL;
|
|
|
|
WinGraphicsWindow *WinGraphicsWindow::_cursor_window = NULL;
|
|
bool WinGraphicsWindow::_cursor_hidden = false;
|
|
|
|
RECT WinGraphicsWindow::_mouse_unconfined_cliprect;
|
|
|
|
// These are used to save the previous state of the fancy Win2000 effects that
|
|
// interfere with rendering when the mouse wanders into a window's client
|
|
// area.
|
|
bool WinGraphicsWindow::_got_saved_params = false;
|
|
int WinGraphicsWindow::_saved_mouse_trails;
|
|
BOOL WinGraphicsWindow::_saved_cursor_shadow;
|
|
BOOL WinGraphicsWindow::_saved_mouse_vanish;
|
|
|
|
WinGraphicsWindow::IconFilenames WinGraphicsWindow::_icon_filenames;
|
|
WinGraphicsWindow::IconFilenames WinGraphicsWindow::_cursor_filenames;
|
|
|
|
WinGraphicsWindow::WindowClasses WinGraphicsWindow::_window_classes;
|
|
int WinGraphicsWindow::_window_class_index = 0;
|
|
|
|
static const char * const errorbox_title = "Panda3D Error";
|
|
|
|
// These static variables contain pointers to the touch input functions, which
|
|
// are dynamically extracted from USER32.DLL
|
|
typedef WINUSERAPI BOOL (WINAPI *PFN_REGISTERTOUCHWINDOW)(IN HWND hWnd, IN ULONG ulFlags);
|
|
typedef WINUSERAPI BOOL (WINAPI *PFN_GETTOUCHINPUTINFO)(IN HTOUCHINPUT hTouchInput, IN UINT cInputs, OUT PTOUCHINPUT pInputs, IN int cbSize);
|
|
typedef WINUSERAPI BOOL (WINAPI *PFN_CLOSETOUCHINPUTHANDLE)(IN HTOUCHINPUT hTouchInput);
|
|
|
|
static PFN_REGISTERTOUCHWINDOW pRegisterTouchWindow = 0;
|
|
static PFN_GETTOUCHINPUTINFO pGetTouchInputInfo = 0;
|
|
static PFN_CLOSETOUCHINPUTHANDLE pCloseTouchInputHandle = 0;
|
|
|
|
/**
|
|
*
|
|
*/
|
|
WinGraphicsWindow::
|
|
WinGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe,
|
|
const string &name,
|
|
const FrameBufferProperties &fb_prop,
|
|
const WindowProperties &win_prop,
|
|
int flags,
|
|
GraphicsStateGuardian *gsg,
|
|
GraphicsOutput *host) :
|
|
GraphicsWindow(engine, pipe, name, fb_prop, win_prop, flags, gsg, host)
|
|
{
|
|
initialize_input_devices();
|
|
_hWnd = (HWND)0;
|
|
_ime_open = false;
|
|
_ime_active = false;
|
|
_tracking_mouse_leaving = false;
|
|
_cursor = 0;
|
|
_lost_keypresses = false;
|
|
_lshift_down = false;
|
|
_rshift_down = false;
|
|
_lcontrol_down = false;
|
|
_rcontrol_down = false;
|
|
_lalt_down = false;
|
|
_ralt_down = false;
|
|
_hparent = NULL;
|
|
_num_touches = 0;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
WinGraphicsWindow::
|
|
~WinGraphicsWindow() {
|
|
if (_window_handle != (WindowHandle *)NULL) {
|
|
DCAST(WinWindowHandle, _window_handle)->clear_window();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Forces the pointer to the indicated position within the window, if
|
|
* possible.
|
|
*
|
|
* Returns true if successful, false on failure. This may fail if the mouse
|
|
* is not currently within the window, or if the API doesn't support this
|
|
* operation.
|
|
*/
|
|
bool WinGraphicsWindow::
|
|
move_pointer(int device, int x, int y) {
|
|
// First, indicate that the IME is no longer active, so that it won't send
|
|
// the string through WM_IME_COMPOSITION. But we still leave _ime_open
|
|
// true, so that it also won't send the string through WM_CHAR.
|
|
_ime_active = false;
|
|
|
|
// Note: this is not thread-safe; it should be called only from App.
|
|
// Probably not an issue.
|
|
if (device == 0) {
|
|
// Move the system mouse pointer.
|
|
if (!_properties.get_foreground() )
|
|
// !_input_devices[0].get_pointer().get_in_window())
|
|
{
|
|
// If the window doesn't have input focus, or the mouse isn't
|
|
// currently within the window, forget it.
|
|
return false;
|
|
}
|
|
|
|
RECT view_rect;
|
|
get_client_rect_screen(_hWnd, &view_rect);
|
|
|
|
SetCursorPos(view_rect.left + x, view_rect.top + y);
|
|
_input_devices[0].set_pointer_in_window(x, y);
|
|
return true;
|
|
} else {
|
|
// Move a raw mouse.
|
|
if ((device < 1)||(device >= (int)_input_devices.size())) {
|
|
return false;
|
|
}
|
|
_input_devices[device].set_pointer_in_window(x, y);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Forces the ime window to close, if any
|
|
*
|
|
*/
|
|
void WinGraphicsWindow::
|
|
close_ime() {
|
|
// Check if the ime window is open
|
|
if (!_ime_open)
|
|
return;
|
|
|
|
HIMC hIMC = ImmGetContext(_hWnd);
|
|
if (hIMC != 0) {
|
|
if (!ImmSetOpenStatus(hIMC, false)) {
|
|
windisplay_cat.debug() << "ImmSetOpenStatus failed\n";
|
|
}
|
|
ImmReleaseContext(_hWnd, hIMC);
|
|
}
|
|
_ime_open = false;
|
|
|
|
windisplay_cat.debug() << "success: closed ime window\n";
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* This function will be called within the draw thread after end_frame() has
|
|
* been called on all windows, to initiate the exchange of the front and back
|
|
* buffers.
|
|
*
|
|
* This should instruct the window to prepare for the flip at the next video
|
|
* sync, but it should not wait.
|
|
*
|
|
* We have the two separate functions, begin_flip() and end_flip(), to make it
|
|
* easier to flip all of the windows at the same time.
|
|
*/
|
|
void WinGraphicsWindow::
|
|
begin_flip() {
|
|
}
|
|
|
|
/**
|
|
* Do whatever processing is necessary to ensure that the window responds to
|
|
* user events. Also, honor any requests recently made via
|
|
* request_properties()
|
|
*
|
|
* This function is called only within the window thread.
|
|
*/
|
|
void WinGraphicsWindow::
|
|
process_events() {
|
|
GraphicsWindow::process_events();
|
|
|
|
// We can't treat the message loop specially just because the window is
|
|
// minimized, because we might be reading messages queued up for some other
|
|
// window, which is not minimized.
|
|
/*
|
|
if (!_window_active) {
|
|
// Get 1 msg at a time until no more are left and we block and sleep, or
|
|
// message changes _return_control_to_app or !_window_active status
|
|
|
|
while(!_window_active && (!_return_control_to_app)) {
|
|
process_1_event();
|
|
}
|
|
_return_control_to_app = false;
|
|
|
|
} else
|
|
*/
|
|
|
|
MSG msg;
|
|
|
|
// Handle all the messages on the queue in a row. Some of these might be
|
|
// for another window, but they will get dispatched appropriately.
|
|
while (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) {
|
|
process_1_event();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Applies the requested set of properties to the window, if possible, for
|
|
* instance to request a change in size or minimization status.
|
|
*
|
|
* The window properties are applied immediately, rather than waiting until
|
|
* the next frame. This implies that this method may *only* be called from
|
|
* within the window thread.
|
|
*
|
|
* The properties that have been applied are cleared from the structure by
|
|
* this function; so on return, whatever remains in the properties structure
|
|
* are those that were unchanged for some reason (probably because the
|
|
* underlying interface does not support changing that property on an open
|
|
* window).
|
|
*/
|
|
void WinGraphicsWindow::
|
|
set_properties_now(WindowProperties &properties) {
|
|
GraphicsWindow::set_properties_now(properties);
|
|
if (!properties.is_any_specified()) {
|
|
// The base class has already handled this case.
|
|
return;
|
|
}
|
|
|
|
if (properties.has_title()) {
|
|
string title = properties.get_title();
|
|
_properties.set_title(title);
|
|
TextEncoder encoder;
|
|
wstring title_w = encoder.decode_text(title);
|
|
SetWindowTextW(_hWnd, title_w.c_str());
|
|
properties.clear_title();
|
|
}
|
|
|
|
if (properties.has_icon_filename()) {
|
|
HICON icon = get_icon(properties.get_icon_filename());
|
|
if (icon != 0) {
|
|
::SendMessage(_hWnd, WM_SETICON, ICON_SMALL, (LPARAM)icon);
|
|
::SendMessage(_hWnd, WM_SETICON, ICON_BIG, (LPARAM)icon);
|
|
|
|
_properties.set_icon_filename(properties.get_icon_filename());
|
|
properties.clear_icon_filename();
|
|
}
|
|
}
|
|
|
|
if (properties.has_cursor_hidden()) {
|
|
bool hide_cursor = properties.get_cursor_hidden();
|
|
_properties.set_cursor_hidden(hide_cursor);
|
|
if (_cursor_window == this) {
|
|
hide_or_show_cursor(hide_cursor);
|
|
}
|
|
|
|
properties.clear_cursor_hidden();
|
|
}
|
|
|
|
if (properties.has_cursor_filename()) {
|
|
Filename filename = properties.get_cursor_filename();
|
|
_properties.set_cursor_filename(filename);
|
|
|
|
_cursor = get_cursor(filename);
|
|
if (_cursor == 0) {
|
|
_cursor = LoadCursor(NULL, IDC_ARROW);
|
|
}
|
|
|
|
if (_cursor_window == this) {
|
|
SetCursor(_cursor);
|
|
}
|
|
|
|
properties.clear_cursor_filename();
|
|
}
|
|
|
|
if (properties.has_z_order()) {
|
|
WindowProperties::ZOrder last_z_order = _properties.get_z_order();
|
|
_properties.set_z_order(properties.get_z_order());
|
|
adjust_z_order(last_z_order, properties.get_z_order());
|
|
|
|
properties.clear_z_order();
|
|
}
|
|
|
|
if (properties.has_foreground() && properties.get_foreground()) {
|
|
if (!SetActiveWindow(_hWnd)) {
|
|
windisplay_cat.warning()
|
|
<< "SetForegroundWindow() failed!\n";
|
|
} else {
|
|
_properties.set_foreground(true);
|
|
}
|
|
|
|
properties.clear_foreground();
|
|
}
|
|
|
|
if (properties.has_minimized()) {
|
|
if (_properties.get_minimized() != properties.get_minimized()) {
|
|
if (properties.get_minimized()) {
|
|
ShowWindow(_hWnd, SW_MINIMIZE);
|
|
} else {
|
|
ShowWindow(_hWnd, SW_RESTORE);
|
|
}
|
|
_properties.set_minimized(properties.get_minimized());
|
|
_properties.set_foreground(!properties.get_minimized());
|
|
}
|
|
properties.clear_minimized();
|
|
}
|
|
|
|
if (properties.has_fullscreen()) {
|
|
if (properties.get_fullscreen() && !is_fullscreen()) {
|
|
if (do_fullscreen_switch()){
|
|
_properties.set_fullscreen(true);
|
|
properties.clear_fullscreen();
|
|
} else {
|
|
windisplay_cat.warning()
|
|
<< "Switching to fullscreen mode failed!\n";
|
|
}
|
|
} else if (!properties.get_fullscreen() && is_fullscreen()){
|
|
if (do_windowed_switch()){
|
|
_properties.set_fullscreen(false);
|
|
properties.clear_fullscreen();
|
|
} else {
|
|
windisplay_cat.warning()
|
|
<< "Switching to windowed mode failed!\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
if (properties.has_mouse_mode()) {
|
|
if (properties.get_mouse_mode() != _properties.get_mouse_mode()) {
|
|
switch (properties.get_mouse_mode()) {
|
|
case WindowProperties::M_absolute:
|
|
case WindowProperties::M_relative: // not implemented, treat as absolute
|
|
|
|
if (_properties.get_mouse_mode() == WindowProperties::M_confined) {
|
|
ClipCursor(NULL);
|
|
windisplay_cat.info() << "Unconfining cursor from window\n";
|
|
}
|
|
_properties.set_mouse_mode(WindowProperties::M_absolute);
|
|
break;
|
|
|
|
case WindowProperties::M_confined:
|
|
{
|
|
RECT clip;
|
|
|
|
if (!GetWindowRect(_hWnd, &clip)) {
|
|
windisplay_cat.warning()
|
|
<< "GetWindowRect() failed in set_properties_now. Cannot confine cursor.\n";
|
|
} else {
|
|
windisplay_cat.info()
|
|
<< "ClipCursor() to " << clip.left << "," << clip.top << " to "
|
|
<< clip.right << "," << clip.bottom << endl;
|
|
|
|
GetClipCursor(&_mouse_unconfined_cliprect);
|
|
if (!ClipCursor(&clip)) {
|
|
windisplay_cat.warning()
|
|
<< "ClipCursor() failed in set_properties_now. Ignoring.\n";
|
|
} else {
|
|
_properties.set_mouse_mode(WindowProperties::M_confined);
|
|
windisplay_cat.info() << "Confining cursor to window\n";
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
properties.clear_mouse_mode();
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* To be called at the end of the frame, after the window has successfully
|
|
* been drawn and is ready to be flipped (if appropriate).
|
|
*/
|
|
void WinGraphicsWindow::
|
|
trigger_flip() {
|
|
GraphicsWindow::trigger_flip();
|
|
|
|
if (!get_unexposed_draw()) {
|
|
// Now that we've drawn or whatever, invalidate the rectangle so we won't
|
|
// redraw again until we get the WM_PAINT message.
|
|
|
|
InvalidateRect(_hWnd, NULL, FALSE);
|
|
_got_expose_event = false;
|
|
|
|
if (windisplay_cat.is_spam()) {
|
|
windisplay_cat.spam()
|
|
<< "InvalidateRect: " << this << "\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Closes the window right now. Called from the window thread.
|
|
*/
|
|
void WinGraphicsWindow::
|
|
close_window() {
|
|
set_cursor_out_of_window();
|
|
DestroyWindow(_hWnd);
|
|
|
|
if (is_fullscreen()) {
|
|
// revert to default display mode.
|
|
do_fullscreen_disable();
|
|
}
|
|
|
|
// Remove the window handle from our global map.
|
|
_window_handles.erase(_hWnd);
|
|
_hWnd = (HWND)0;
|
|
|
|
GraphicsWindow::close_window();
|
|
}
|
|
|
|
/**
|
|
* Opens the window right now. Called from the window thread. Returns true
|
|
* if the window is successfully opened, or false if there was a problem.
|
|
*/
|
|
bool WinGraphicsWindow::
|
|
open_window() {
|
|
if (_properties.has_cursor_filename()) {
|
|
_cursor = get_cursor(_properties.get_cursor_filename());
|
|
}
|
|
if (_cursor == 0) {
|
|
_cursor = LoadCursor(NULL, IDC_ARROW);
|
|
}
|
|
bool want_foreground = (!_properties.has_foreground() || _properties.get_foreground());
|
|
bool want_minimized = (_properties.has_minimized() && _properties.get_minimized()) && !want_foreground;
|
|
|
|
HWND old_foreground_window = GetForegroundWindow();
|
|
|
|
// Store the current window pointer in _creating_window, so we can call
|
|
// CreateWindow() and know which window it is sending events to even before
|
|
// it gives us a handle. Warning: this is not thread safe!
|
|
_creating_window = this;
|
|
bool opened = open_graphic_window(is_fullscreen());
|
|
_creating_window = (WinGraphicsWindow *)NULL;
|
|
|
|
if (!opened) {
|
|
return false;
|
|
}
|
|
|
|
// Now that we have a window handle, store it in our global map, so future
|
|
// messages for this window can be routed properly.
|
|
_window_handles.insert(WindowHandles::value_type(_hWnd, this));
|
|
|
|
// move window to top of zorder.
|
|
SetWindowPos(_hWnd, HWND_TOP, 0,0,0,0,
|
|
SWP_NOMOVE | SWP_NOSENDCHANGING | SWP_NOSIZE);
|
|
|
|
// need to do twice to override any minimized flags in StartProcessInfo
|
|
if (want_minimized) {
|
|
ShowWindow(_hWnd, SW_MINIMIZE);
|
|
ShowWindow(_hWnd, SW_MINIMIZE);
|
|
} else {
|
|
ShowWindow(_hWnd, SW_SHOWNORMAL);
|
|
ShowWindow(_hWnd, SW_SHOWNORMAL);
|
|
}
|
|
|
|
HWND new_foreground_window = _hWnd;
|
|
if (!want_foreground) {
|
|
// If we specifically requested the window not to be on top, restore the
|
|
// previous foreground window (if we can).
|
|
new_foreground_window = old_foreground_window;
|
|
}
|
|
|
|
if (!SetActiveWindow(new_foreground_window)) {
|
|
windisplay_cat.warning()
|
|
<< "SetActiveWindow() failed!\n";
|
|
}
|
|
|
|
// Let's aggressively call SetForegroundWindow() in addition to
|
|
// SetActiveWindow(). It seems to work in some cases to make the window
|
|
// come to the top, where SetActiveWindow doesn't work.
|
|
if (!SetForegroundWindow(new_foreground_window)) {
|
|
windisplay_cat.warning()
|
|
<< "SetForegroundWindow() failed!\n";
|
|
}
|
|
|
|
// Determine the initial open status of the IME.
|
|
_ime_open = false;
|
|
_ime_active = false;
|
|
HIMC hIMC = ImmGetContext(_hWnd);
|
|
if (hIMC != 0) {
|
|
_ime_open = (ImmGetOpenStatus(hIMC) != 0);
|
|
ImmReleaseContext(_hWnd, hIMC);
|
|
}
|
|
|
|
// Registers to receive the WM_INPUT messages
|
|
if (_input_devices.size() > 1) {
|
|
RAWINPUTDEVICE Rid;
|
|
Rid.usUsagePage = 0x01;
|
|
Rid.usUsage = 0x02;
|
|
Rid.dwFlags = 0;// RIDEV_NOLEGACY; // adds HID mouse and also ignores legacy mouse messages
|
|
Rid.hwndTarget = _hWnd;
|
|
RegisterRawInputDevices(&Rid, 1, sizeof (Rid));
|
|
}
|
|
|
|
// Create a WindowHandle for ourselves
|
|
_window_handle = NativeWindowHandle::make_win(_hWnd);
|
|
|
|
// Actually, we want a WinWindowHandle.
|
|
_window_handle = new WinWindowHandle(this, *_window_handle);
|
|
|
|
// And tell our parent window that we're now its child.
|
|
if (_parent_window_handle != (WindowHandle *)NULL) {
|
|
_parent_window_handle->attach_child(_window_handle);
|
|
}
|
|
|
|
// set us as the focus window for keyboard input
|
|
set_focus();
|
|
|
|
// Try initializing the touch function pointers.
|
|
static bool initialized = false;
|
|
if (!initialized) {
|
|
initialized = true;
|
|
HMODULE user32 = GetModuleHandleA("user32.dll");
|
|
if (user32) {
|
|
// Introduced in Windows 7.
|
|
pRegisterTouchWindow = (PFN_REGISTERTOUCHWINDOW)GetProcAddress(user32, "RegisterTouchWindow");
|
|
pGetTouchInputInfo = (PFN_GETTOUCHINPUTINFO)GetProcAddress(user32, "GetTouchInputInfo");
|
|
pCloseTouchInputHandle = (PFN_CLOSETOUCHINPUTHANDLE)GetProcAddress(user32, "CloseTouchInputHandle");
|
|
}
|
|
}
|
|
|
|
// Register for Win7 touch events.
|
|
if (pRegisterTouchWindow != NULL) {
|
|
pRegisterTouchWindow(_hWnd, 0);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Creates the array of input devices. The first one is always the system
|
|
* mouse and keyboard. Each subsequent one is a raw mouse device. Also
|
|
* initializes a parallel array, _input_device_handle, with the win32 handle
|
|
* of each raw input device.
|
|
*/
|
|
|
|
void WinGraphicsWindow::
|
|
initialize_input_devices() {
|
|
UINT nInputDevices;
|
|
PRAWINPUTDEVICELIST pRawInputDeviceList;
|
|
|
|
nassertv(_input_devices.size() == 0);
|
|
|
|
// Clear the handle array, and set up the system keyboardmouse
|
|
memset(_input_device_handle, 0, sizeof(_input_device_handle));
|
|
GraphicsWindowInputDevice device =
|
|
GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard_mouse");
|
|
add_input_device(device);
|
|
|
|
// Get the number of devices.
|
|
if (GetRawInputDeviceList(NULL, &nInputDevices, sizeof(RAWINPUTDEVICELIST)) != 0) {
|
|
return;
|
|
}
|
|
|
|
// Allocate the array to hold the DeviceList
|
|
pRawInputDeviceList = (PRAWINPUTDEVICELIST)alloca(sizeof(RAWINPUTDEVICELIST) * nInputDevices);
|
|
if (pRawInputDeviceList==0) {
|
|
return;
|
|
}
|
|
|
|
// Fill the Array
|
|
if (GetRawInputDeviceList(pRawInputDeviceList, &nInputDevices, sizeof(RAWINPUTDEVICELIST)) == -1) {
|
|
return;
|
|
}
|
|
|
|
// Loop through all raw devices and find the raw mice
|
|
for (int i = 0; i < (int)nInputDevices; i++) {
|
|
if (pRawInputDeviceList[i].dwType == RIM_TYPEMOUSE) {
|
|
// Fetch information about specified mouse device.
|
|
UINT nSize;
|
|
if (GetRawInputDeviceInfoA(pRawInputDeviceList[i].hDevice, RIDI_DEVICENAME, (LPVOID)0, &nSize) != 0) {
|
|
return;
|
|
}
|
|
char *psName = (char*)alloca(sizeof(TCHAR) * nSize);
|
|
if (psName == 0) return;
|
|
if (GetRawInputDeviceInfoA(pRawInputDeviceList[i].hDevice, RIDI_DEVICENAME, (LPVOID)psName, &nSize) < 0) {
|
|
return;
|
|
}
|
|
|
|
// If it's not an RDP mouse, add it to the list of raw mice.
|
|
if (strncmp(psName,"\\??\\Root#RDP_MOU#0000#",22)!=0) {
|
|
if (_input_devices.size() < 32) {
|
|
if (strncmp(psName,"\\??\\",4)==0) psName += 4;
|
|
char *pound1 = strchr(psName,'#');
|
|
char *pound2 = pound1 ? strchr(pound1+1,'#') : 0;
|
|
char *pound3 = pound2 ? strchr(pound2+1,'#') : 0;
|
|
if (pound3) *pound3 = 0;
|
|
for (char *p = psName; *p; p++) {
|
|
if (((*p<'a')||(*p>'z')) && ((*p<'A')||(*p>'Z')) && ((*p<'0')||(*p>'9'))) {
|
|
*p = '_';
|
|
}
|
|
}
|
|
if (pound2) *pound2 = '.';
|
|
_input_device_handle[_input_devices.size()] = pRawInputDeviceList[i].hDevice;
|
|
GraphicsWindowInputDevice device = GraphicsWindowInputDevice::pointer_only(this, psName);
|
|
device.set_pointer_in_window(0,0);
|
|
add_input_device(device);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This is a hook for derived classes to do something special, if necessary,
|
|
* when a fullscreen window has been minimized. The given WindowProperties
|
|
* struct will be applied to this window's properties after this function
|
|
* returns.
|
|
*/
|
|
void WinGraphicsWindow::
|
|
fullscreen_minimized(WindowProperties &) {
|
|
}
|
|
|
|
/**
|
|
* This is a hook for derived classes to do something special, if necessary,
|
|
* when a fullscreen window has been restored after being minimized. The
|
|
* given WindowProperties struct will be applied to this window's properties
|
|
* after this function returns.
|
|
*/
|
|
void WinGraphicsWindow::
|
|
fullscreen_restored(WindowProperties &) {
|
|
}
|
|
|
|
/**
|
|
* Called from the window thread in response to a request from within the code
|
|
* (via request_properties()) to change the size and/or position of the
|
|
* window. Returns true if the window is successfully changed, or false if
|
|
* there was a problem.
|
|
*/
|
|
bool WinGraphicsWindow::
|
|
do_reshape_request(int x_origin, int y_origin, bool has_origin,
|
|
int x_size, int y_size) {
|
|
if (windisplay_cat.is_debug()) {
|
|
windisplay_cat.debug()
|
|
<< "Got reshape request (" << x_origin << ", " << y_origin
|
|
<< ", " << has_origin << ", " << x_size << ", " << y_size << ")\n";
|
|
}
|
|
if (!is_fullscreen()) {
|
|
if (has_origin) {
|
|
// A coordinate of -2 means to center the window in its client area.
|
|
if (x_origin == -2) {
|
|
x_origin = 0.5 * (_pipe->get_display_width() - x_size);
|
|
}
|
|
if (y_origin == -2) {
|
|
y_origin = 0.5 * (_pipe->get_display_height() - y_size);
|
|
}
|
|
_properties.set_origin(x_origin, y_origin);
|
|
|
|
if (x_origin == -1 && y_origin == -1) {
|
|
x_origin = 0;
|
|
y_origin = 0;
|
|
has_origin = false;
|
|
}
|
|
}
|
|
|
|
// Compute the appropriate size and placement for the window, including
|
|
// decorations.
|
|
RECT view_rect;
|
|
SetRect(&view_rect, x_origin, y_origin,
|
|
x_origin + x_size, y_origin + y_size);
|
|
WINDOWINFO wi;
|
|
GetWindowInfo(_hWnd, &wi);
|
|
AdjustWindowRectEx(&view_rect, wi.dwStyle, FALSE, wi.dwExStyle);
|
|
|
|
UINT flags = SWP_NOZORDER | SWP_NOSENDCHANGING;
|
|
|
|
if (has_origin) {
|
|
x_origin = view_rect.left;
|
|
y_origin = view_rect.top;
|
|
} else {
|
|
x_origin = CW_USEDEFAULT;
|
|
y_origin = CW_USEDEFAULT;
|
|
flags |= SWP_NOMOVE;
|
|
}
|
|
|
|
SetWindowPos(_hWnd, NULL, x_origin, y_origin,
|
|
view_rect.right - view_rect.left,
|
|
view_rect.bottom - view_rect.top,
|
|
flags);
|
|
|
|
handle_reshape();
|
|
return true;
|
|
}
|
|
|
|
// Resizing a fullscreen window is a little trickier.
|
|
return do_fullscreen_resize(x_size, y_size);
|
|
}
|
|
|
|
/**
|
|
* Called in the window thread when the window size or location is changed,
|
|
* this updates the properties structure accordingly.
|
|
*/
|
|
void WinGraphicsWindow::
|
|
handle_reshape() {
|
|
RECT view_rect;
|
|
if (!GetClientRect(_hWnd, &view_rect)) {
|
|
// Sometimes we get a "reshape" before the window is fully created, in
|
|
// which case GetClientRect() ought to fail. Ignore this.
|
|
if (windisplay_cat.is_debug()) {
|
|
windisplay_cat.debug()
|
|
<< "GetClientRect() failed in handle_reshape. Ignoring.\n";
|
|
}
|
|
return;
|
|
}
|
|
|
|
// But in practice, GetClientRect() doesn't really fail, but just returns
|
|
// all zeroes. Ignore this too.
|
|
if (view_rect.left == 0 && view_rect.right == 0 &&
|
|
view_rect.bottom == 0 && view_rect.top == 0) {
|
|
if (windisplay_cat.is_debug()) {
|
|
windisplay_cat.debug()
|
|
<< "GetClientRect() returned all zeroes in handle_reshape. Ignoring.\n";
|
|
}
|
|
return;
|
|
}
|
|
|
|
bool result = (FALSE != ClientToScreen(_hWnd, (POINT*)&view_rect.left)); // translates top,left pnt
|
|
if (result) {
|
|
result = (FALSE != ClientToScreen(_hWnd, (POINT*)&view_rect.right)); // translates right,bottom pnt
|
|
}
|
|
|
|
if (!result) {
|
|
if (windisplay_cat.is_debug()) {
|
|
windisplay_cat.debug()
|
|
<< "ClientToScreen() failed in handle_reshape. Ignoring.\n";
|
|
}
|
|
return;
|
|
}
|
|
|
|
WindowProperties properties;
|
|
properties.set_size((view_rect.right - view_rect.left),
|
|
(view_rect.bottom - view_rect.top));
|
|
|
|
// _props origin should reflect upper left of view rectangle
|
|
properties.set_origin(view_rect.left, view_rect.top);
|
|
|
|
if (windisplay_cat.is_debug()) {
|
|
windisplay_cat.debug()
|
|
<< "reshape to origin: (" << properties.get_x_origin() << ","
|
|
<< properties.get_y_origin() << "), size: (" << properties.get_x_size()
|
|
<< "," << properties.get_y_size() << ")\n";
|
|
}
|
|
|
|
adjust_z_order();
|
|
system_changed_properties(properties);
|
|
}
|
|
|
|
/**
|
|
* Called in the window thread to resize a fullscreen window.
|
|
*/
|
|
bool WinGraphicsWindow::
|
|
do_fullscreen_resize(int x_size, int y_size) {
|
|
HWND hDesktopWindow = GetDesktopWindow();
|
|
HDC scrnDC = GetDC(hDesktopWindow);
|
|
DWORD dwFullScreenBitDepth = GetDeviceCaps(scrnDC, BITSPIXEL);
|
|
ReleaseDC(hDesktopWindow, scrnDC);
|
|
|
|
// resize will always leave screen bitdepth unchanged
|
|
|
|
// allowing resizing of lowvidmem cards to > 640x480. why? I'll assume
|
|
// check was already done by caller, so he knows what he wants
|
|
|
|
DEVMODE dm;
|
|
if (!find_acceptable_display_mode(x_size, y_size,
|
|
dwFullScreenBitDepth, dm)) {
|
|
windisplay_cat.error()
|
|
<< "window resize(" << x_size << ", " << y_size
|
|
<< ") failed, no compatible fullscreen display mode found!\n";
|
|
return false;
|
|
}
|
|
|
|
// this causes WM_SIZE msg to be produced
|
|
SetWindowPos(_hWnd, NULL, 0,0, x_size, y_size,
|
|
SWP_NOZORDER | SWP_NOMOVE | SWP_NOSENDCHANGING);
|
|
int chg_result = ChangeDisplaySettings(&dm, CDS_FULLSCREEN);
|
|
|
|
if (chg_result != DISP_CHANGE_SUCCESSFUL) {
|
|
windisplay_cat.error()
|
|
<< "resize ChangeDisplaySettings failed (error code: "
|
|
<< chg_result << ") for specified res: "
|
|
<< dm.dmPelsWidth << " x " << dm.dmPelsHeight
|
|
<< " x " << dm.dmBitsPerPel << ", "
|
|
<< dm.dmDisplayFrequency << " Hz\n";
|
|
return false;
|
|
}
|
|
|
|
_fullscreen_display_mode = dm;
|
|
|
|
windisplay_cat.info()
|
|
<< "Resized fullscreen window to " << x_size << ", " << y_size
|
|
<< " bitdepth " << dwFullScreenBitDepth << ", "
|
|
<< dm.dmDisplayFrequency << "Hz\n";
|
|
|
|
_properties.set_size(x_size, y_size);
|
|
set_size_and_recalc(x_size, y_size);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Called in the set_properties_now function to switch to fullscreen.
|
|
*/
|
|
bool WinGraphicsWindow::
|
|
do_fullscreen_switch() {
|
|
if (!do_fullscreen_enable()) {
|
|
// Couldn't get fullscreen.
|
|
return false;
|
|
}
|
|
|
|
DWORD window_style = make_style(true);
|
|
SetWindowLong(_hWnd, GWL_STYLE, window_style);
|
|
|
|
WINDOW_METRICS metrics;
|
|
bool has_origin;
|
|
if (!calculate_metrics(true, window_style, metrics, has_origin)){
|
|
return false;
|
|
}
|
|
|
|
SetWindowPos(_hWnd, HWND_NOTOPMOST, 0, 0, metrics.width, metrics.height,
|
|
SWP_FRAMECHANGED | SWP_SHOWWINDOW);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Called in the set_properties_now function to switch to windowed mode.
|
|
*/
|
|
bool WinGraphicsWindow::
|
|
do_windowed_switch() {
|
|
do_fullscreen_disable();
|
|
DWORD window_style = make_style(false);
|
|
SetWindowLong(_hWnd, GWL_STYLE, window_style);
|
|
|
|
WINDOW_METRICS metrics;
|
|
bool has_origin;
|
|
|
|
if (!calculate_metrics(false, window_style, metrics, has_origin)){
|
|
return false;
|
|
}
|
|
|
|
// We send SWP_FRAMECHANGED so that the new styles are taken into account.
|
|
// Also, we place the Windows at 0,0 to play safe until we decide how to get
|
|
// Panda to remember the windowed origin.
|
|
|
|
SetWindowPos(_hWnd, HWND_NOTOPMOST, 0, 0,
|
|
metrics.width, metrics.height,
|
|
SWP_FRAMECHANGED | SWP_SHOWWINDOW);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Called before creating a fullscreen window to give the driver a chance to
|
|
* adjust the particular resolution request, if necessary.
|
|
*/
|
|
void WinGraphicsWindow::
|
|
reconsider_fullscreen_size(DWORD &, DWORD &, DWORD &) {
|
|
}
|
|
|
|
/**
|
|
* Some windows graphics contexts (e.g. DirectX) require special support to
|
|
* enable the displaying of an overlay window (particularly the IME window)
|
|
* over the fullscreen graphics window. This is a hook for the window to
|
|
* enable or disable that mode when necessary.
|
|
*/
|
|
void WinGraphicsWindow::
|
|
support_overlay_window(bool) {
|
|
}
|
|
|
|
/**
|
|
* Constructs a dwStyle for the specified mode, be it windowed or fullscreen.
|
|
*/
|
|
DWORD WinGraphicsWindow::
|
|
make_style(bool fullscreen) {
|
|
// from MSDN: An OpenGL window has its own pixel format. Because of this,
|
|
// only device contexts retrieved for the client area of an OpenGL window
|
|
// are allowed to draw into the window. As a result, an OpenGL window
|
|
// should be created with the WS_CLIPCHILDREN and WS_CLIPSIBLINGS styles.
|
|
// Additionally, the window class attribute should not include the
|
|
// CS_PARENTDC style.
|
|
|
|
DWORD window_style = WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
|
|
|
|
if (fullscreen){
|
|
window_style |= WS_SYSMENU;
|
|
} else if (!_properties.get_undecorated()) {
|
|
window_style |= (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX);
|
|
|
|
if (!_properties.get_fixed_size()) {
|
|
window_style |= (WS_SIZEBOX | WS_MAXIMIZEBOX);
|
|
} else {
|
|
window_style |= WS_BORDER;
|
|
}
|
|
}
|
|
return window_style;
|
|
}
|
|
|
|
/**
|
|
* Calculates the metrics for the specified mode, be it windowed or
|
|
* fullscreen.
|
|
*/
|
|
bool WinGraphicsWindow::
|
|
calculate_metrics(bool fullscreen, DWORD window_style, WINDOW_METRICS &metrics,
|
|
bool &has_origin) {
|
|
metrics.x = 0;
|
|
metrics.y = 0;
|
|
has_origin = _properties.has_origin();
|
|
if (!fullscreen && has_origin) {
|
|
metrics.x = _properties.get_x_origin();
|
|
metrics.y = _properties.get_y_origin();
|
|
|
|
// A coordinate of -2 means to center the window in its client area.
|
|
if (metrics.x == -2) {
|
|
metrics.x = 0.5 * (_pipe->get_display_width() - _properties.get_x_size());
|
|
}
|
|
if (metrics.y == -2) {
|
|
metrics.y = 0.5 * (_pipe->get_display_height() - _properties.get_y_size());
|
|
}
|
|
_properties.set_origin(metrics.x, metrics.y);
|
|
|
|
if (metrics.x == -1 && metrics.y == -1) {
|
|
metrics.x = 0;
|
|
metrics.y = 0;
|
|
has_origin = false;
|
|
}
|
|
}
|
|
|
|
metrics.width = _properties.get_x_size();
|
|
metrics.height = _properties.get_y_size();
|
|
|
|
if (!fullscreen){
|
|
RECT win_rect;
|
|
SetRect(&win_rect, metrics.x, metrics.y,
|
|
metrics.x + metrics.width, metrics.y + metrics.height);
|
|
|
|
// Compute window size based on desired client area size
|
|
if (!AdjustWindowRect(&win_rect, window_style, FALSE)) {
|
|
windisplay_cat.error()
|
|
<< "AdjustWindowRect failed!" << endl;
|
|
return false;
|
|
}
|
|
|
|
if (has_origin) {
|
|
metrics.x = win_rect.left;
|
|
metrics.y = win_rect.top;
|
|
} else {
|
|
metrics.x = CW_USEDEFAULT;
|
|
metrics.y = CW_USEDEFAULT;
|
|
}
|
|
metrics.width = win_rect.right - win_rect.left;
|
|
metrics.height = win_rect.bottom - win_rect.top;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Creates a regular or fullscreen window.
|
|
*/
|
|
bool WinGraphicsWindow::
|
|
open_graphic_window(bool fullscreen) {
|
|
DWORD window_style = make_style(fullscreen);
|
|
|
|
wstring title;
|
|
if (_properties.has_title()) {
|
|
TextEncoder encoder;
|
|
title = encoder.decode_text(_properties.get_title());
|
|
}
|
|
|
|
if (!_properties.has_size()) {
|
|
// Just fill in a conservative default size if one isn't specified.
|
|
_properties.set_size(640, 480);
|
|
}
|
|
|
|
WINDOW_METRICS metrics;
|
|
bool has_origin;
|
|
if (!calculate_metrics(fullscreen, window_style, metrics, has_origin)){
|
|
return false;
|
|
}
|
|
|
|
const WindowClass &wclass = register_window_class(_properties);
|
|
HINSTANCE hinstance = GetModuleHandle(NULL);
|
|
|
|
_hparent = NULL;
|
|
|
|
if (!fullscreen){
|
|
WindowHandle *window_handle = _properties.get_parent_window();
|
|
if (window_handle != NULL) {
|
|
windisplay_cat.info()
|
|
<< "Got parent_window " << *window_handle << "\n";
|
|
WindowHandle::OSHandle *os_handle = window_handle->get_os_handle();
|
|
if (os_handle != NULL) {
|
|
windisplay_cat.info()
|
|
<< "os_handle type " << os_handle->get_type() << "\n";
|
|
|
|
if (os_handle->is_of_type(NativeWindowHandle::WinHandle::get_class_type())) {
|
|
NativeWindowHandle::WinHandle *win_handle = DCAST(NativeWindowHandle::WinHandle, os_handle);
|
|
_hparent = win_handle->get_handle();
|
|
} else if (os_handle->is_of_type(NativeWindowHandle::IntHandle::get_class_type())) {
|
|
NativeWindowHandle::IntHandle *int_handle = DCAST(NativeWindowHandle::IntHandle, os_handle);
|
|
_hparent = (HWND)int_handle->get_handle();
|
|
}
|
|
}
|
|
}
|
|
_parent_window_handle = window_handle;
|
|
} else {
|
|
_parent_window_handle = NULL;
|
|
}
|
|
|
|
if (!_hparent) { // This can be a regular window or a fullscreen window
|
|
_hWnd = CreateWindowW(wclass._name.c_str(), title.c_str(), window_style,
|
|
metrics.x, metrics.y,
|
|
metrics.width,
|
|
metrics.height,
|
|
NULL, NULL, hinstance, 0);
|
|
} else { // This is a regular window with a parent
|
|
int x_origin = 0;
|
|
int y_origin = 0;
|
|
|
|
if (!fullscreen && has_origin) {
|
|
x_origin = _properties.get_x_origin();
|
|
y_origin = _properties.get_y_origin();
|
|
}
|
|
|
|
_hWnd = CreateWindowW(wclass._name.c_str(), title.c_str(),
|
|
WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS ,
|
|
x_origin, y_origin,
|
|
_properties.get_x_size(), _properties.get_y_size(),
|
|
_hparent, NULL, hinstance, 0);
|
|
|
|
if (_hWnd) {
|
|
// join our keyboard state with the parents
|
|
|
|
// Actually, let's not. Is there really any reason to do this? It
|
|
// causes problems with the browser plugin--it deadlocks when the parent
|
|
// process is waiting on the child process.
|
|
// AttachThreadInput(GetWindowThreadProcessId(_hparent,NULL),
|
|
// GetCurrentThreadId(),TRUE);
|
|
|
|
WindowProperties properties;
|
|
properties.set_foreground(true);
|
|
system_changed_properties(properties);
|
|
}
|
|
}
|
|
|
|
if (!_hWnd) {
|
|
windisplay_cat.error()
|
|
<< "CreateWindow() failed!" << endl;
|
|
show_error_message();
|
|
return false;
|
|
}
|
|
|
|
// I'd prefer to CreateWindow after DisplayChange in case it messes up GL
|
|
// somehow, but I need the window's black background to cover up the desktop
|
|
// during the mode change.
|
|
|
|
if (fullscreen){
|
|
if (!do_fullscreen_enable()){
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* This is a low-level function that just puts Windows in fullscreen mode.
|
|
* Not to confuse with do_fullscreen_switch().
|
|
*/
|
|
bool WinGraphicsWindow::
|
|
do_fullscreen_enable() {
|
|
|
|
HWND hDesktopWindow = GetDesktopWindow();
|
|
HDC scrnDC = GetDC(hDesktopWindow);
|
|
DWORD cur_bitdepth = GetDeviceCaps(scrnDC, BITSPIXEL);
|
|
// DWORD drvr_ver = GetDeviceCaps(scrnDC, DRIVERVERSION); DWORD
|
|
// cur_scrnwidth = GetDeviceCaps(scrnDC, HORZRES); DWORD cur_scrnheight =
|
|
// GetDeviceCaps(scrnDC, VERTRES);
|
|
ReleaseDC(hDesktopWindow, scrnDC);
|
|
|
|
DWORD dwWidth = _properties.get_x_size();
|
|
DWORD dwHeight = _properties.get_y_size();
|
|
DWORD dwFullScreenBitDepth = cur_bitdepth;
|
|
|
|
DEVMODE dm;
|
|
reconsider_fullscreen_size(dwWidth, dwHeight, dwFullScreenBitDepth);
|
|
if (!find_acceptable_display_mode(dwWidth, dwHeight, dwFullScreenBitDepth, dm)) {
|
|
windisplay_cat.error()
|
|
<< "Videocard has no supported display resolutions at specified res ("
|
|
<< dwWidth << " x " << dwHeight << " x " << dwFullScreenBitDepth <<")\n";
|
|
return false;
|
|
}
|
|
|
|
dm.dmPelsWidth = dwWidth;
|
|
dm.dmPelsHeight = dwHeight;
|
|
dm.dmBitsPerPel = dwFullScreenBitDepth;
|
|
int chg_result = ChangeDisplaySettings(&dm, CDS_FULLSCREEN);
|
|
|
|
if (chg_result != DISP_CHANGE_SUCCESSFUL) {
|
|
windisplay_cat.error()
|
|
<< "ChangeDisplaySettings failed (error code: "
|
|
<< chg_result << ") for specified res: "
|
|
<< dm.dmPelsWidth << " x " << dm.dmPelsHeight
|
|
<< " x " << dm.dmBitsPerPel << ", "
|
|
<< dm.dmDisplayFrequency << " Hz\n";
|
|
return false;
|
|
}
|
|
|
|
_fullscreen_display_mode = dm;
|
|
|
|
_properties.set_origin(0, 0);
|
|
_properties.set_size(dwWidth, dwHeight);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
/**
|
|
* This is a low-level function that just gets Windows out of fullscreen mode.
|
|
* Not to confuse with do_windowed_switch().
|
|
*/
|
|
bool WinGraphicsWindow::
|
|
do_fullscreen_disable() {
|
|
int chg_result = ChangeDisplaySettings(NULL, 0x0);
|
|
if (chg_result != DISP_CHANGE_SUCCESSFUL) {
|
|
windisplay_cat.warning()
|
|
<< "ChangeDisplaySettings failed to restore Windowed mode\n";
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Adjusts the Z-order of a window after it has been moved.
|
|
*/
|
|
void WinGraphicsWindow::
|
|
adjust_z_order() {
|
|
WindowProperties::ZOrder z_order = _properties.get_z_order();
|
|
adjust_z_order(z_order, z_order);
|
|
}
|
|
|
|
/**
|
|
* Adjusts the Z-order of a window after it has been moved.
|
|
*/
|
|
void WinGraphicsWindow::
|
|
adjust_z_order(WindowProperties::ZOrder last_z_order,
|
|
WindowProperties::ZOrder this_z_order) {
|
|
HWND order;
|
|
bool do_change = false;
|
|
|
|
switch (this_z_order) {
|
|
case WindowProperties::Z_bottom:
|
|
order = HWND_BOTTOM;
|
|
do_change = true;
|
|
break;
|
|
|
|
case WindowProperties::Z_normal:
|
|
if ((last_z_order != WindowProperties::Z_normal) &&
|
|
// If we aren't changing the window order, don't move it to the top.
|
|
(last_z_order != WindowProperties::Z_bottom ||
|
|
_properties.get_foreground())
|
|
// If the window was previously on the bottom, but it doesn't have
|
|
// focus now, don't move it to the top; it will get moved the next
|
|
// time we get focus.
|
|
) {
|
|
order = HWND_NOTOPMOST;
|
|
do_change = true;
|
|
}
|
|
break;
|
|
|
|
case WindowProperties::Z_top:
|
|
order = HWND_TOPMOST;
|
|
do_change = true;
|
|
break;
|
|
}
|
|
if (do_change) {
|
|
BOOL result = SetWindowPos(_hWnd, order, 0,0,0,0,
|
|
SWP_NOMOVE | SWP_NOSENDCHANGING | SWP_NOSIZE);
|
|
if (!result) {
|
|
windisplay_cat.warning()
|
|
<< "SetWindowPos failed.\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Intended to be called whenever mouse motion is detected within the window,
|
|
* this indicates that the mouse is within the window and tells Windows that
|
|
* we want to be told when the mouse leaves the window.
|
|
*/
|
|
void WinGraphicsWindow::
|
|
track_mouse_leaving(HWND hwnd) {
|
|
WinGraphicsPipe *winpipe;
|
|
DCAST_INTO_V(winpipe, _pipe);
|
|
|
|
TRACKMOUSEEVENT tme = {
|
|
sizeof(TRACKMOUSEEVENT),
|
|
TME_LEAVE,
|
|
hwnd,
|
|
0
|
|
};
|
|
|
|
// tell win32 to post WM_MOUSELEAVE msgs
|
|
BOOL bSucceeded = TrackMouseEvent(&tme);
|
|
|
|
if (!bSucceeded && windisplay_cat.is_debug()) {
|
|
windisplay_cat.debug()
|
|
<< "TrackMouseEvent failed!, LastError=" << GetLastError() << endl;
|
|
}
|
|
|
|
_tracking_mouse_leaving = true;
|
|
}
|
|
|
|
/**
|
|
* Attempts to set this window as the "focus" window, so that keyboard events
|
|
* come here.
|
|
*/
|
|
void WinGraphicsWindow::
|
|
set_focus() {
|
|
if (SetFocus(_hWnd) == NULL && GetLastError() != 0) {
|
|
// If the SetFocus() request failed, maybe we're running in the plugin
|
|
// environment on Vista, with UAC enabled. In this case, we're not
|
|
// allowed to assign focus to the Panda window for some stupid reason. So
|
|
// instead, we have to ask the parent window (in the browser process) to
|
|
// proxy our keyboard events for us.
|
|
if (_parent_window_handle != NULL && _window_handle != NULL) {
|
|
_parent_window_handle->request_keyboard_focus(_window_handle);
|
|
} else {
|
|
// Otherwise, something is wrong.
|
|
windisplay_cat.error()
|
|
<< "SetFocus failed: " << GetLastError() << "\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This is called to receive a keyboard event generated by proxy by another
|
|
* window in a parent process. This hacky system is used in the web plugin
|
|
* system to allow the Panda window to receive keyboard events on Vista, which
|
|
* doesn't allow the Panda window to set keyboard focus to itself.
|
|
*/
|
|
void WinGraphicsWindow::
|
|
receive_windows_message(unsigned int msg, int wparam, int lparam) {
|
|
// Well, we'll just deliver this directly to window_proc(), supplying our
|
|
// own window handle. For the most part, we don't care about the window
|
|
// handle anyway, but this might become an issue for the IME. TODO:
|
|
// investigate IME issues.
|
|
|
|
window_proc(_hWnd, msg, wparam, lparam);
|
|
}
|
|
|
|
/**
|
|
* This is the nonstatic window_proc function. It is called to handle window
|
|
* events for this particular window.
|
|
*/
|
|
LONG WinGraphicsWindow::
|
|
window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
|
|
if (windisplay_cat.is_spam()) {
|
|
windisplay_cat.spam()
|
|
<< ClockObject::get_global_clock()->get_real_time()
|
|
<< " window_proc(" << (void *)this << ", " << hwnd << ", "
|
|
<< msg << ", " << wparam << ", " << lparam << ")\n";
|
|
}
|
|
WindowProperties properties;
|
|
int button = -1;
|
|
|
|
switch (msg) {
|
|
case WM_MOUSEMOVE:
|
|
if (!_tracking_mouse_leaving) {
|
|
// need to re-call TrackMouseEvent every time mouse re-enters window
|
|
track_mouse_leaving(hwnd);
|
|
}
|
|
set_cursor_in_window();
|
|
if(handle_mouse_motion(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam))))
|
|
return 0;
|
|
break;
|
|
|
|
case WM_INPUT:
|
|
handle_raw_input((HRAWINPUT)lparam);
|
|
break;
|
|
|
|
case WM_MOUSELEAVE:
|
|
_tracking_mouse_leaving = false;
|
|
handle_mouse_exit();
|
|
set_cursor_out_of_window();
|
|
break;
|
|
|
|
case WM_CREATE:
|
|
{
|
|
track_mouse_leaving(hwnd);
|
|
ClearToBlack(hwnd, _properties);
|
|
|
|
POINT cpos;
|
|
GetCursorPos(&cpos);
|
|
ScreenToClient(hwnd, &cpos);
|
|
RECT clientRect;
|
|
GetClientRect(hwnd, &clientRect);
|
|
if (PtInRect(&clientRect,cpos)) {
|
|
set_cursor_in_window(); // should window focus be true as well?
|
|
} else {
|
|
set_cursor_out_of_window();
|
|
}
|
|
}
|
|
break;
|
|
|
|
/*
|
|
case WM_SHOWWINDOW:
|
|
// You'd think WM_SHOWWINDOW would be just the thing for embedded windows,
|
|
// but it turns out it's not sent to the child windows when the parent is
|
|
// minimized. I guess it's only sent for an explicit call to ShowWindow,
|
|
// phooey.
|
|
{
|
|
if (windisplay_cat.is_debug()) {
|
|
windisplay_cat.debug()
|
|
<< "WM_SHOWWINDOW: " << hwnd << ", " << wparam << "\n";
|
|
}
|
|
if (wparam) {
|
|
// Window is being shown.
|
|
properties.set_minimized(false);
|
|
} else {
|
|
// Window is being hidden.
|
|
properties.set_minimized(true);
|
|
}
|
|
system_changed_properties(properties);
|
|
}
|
|
break;
|
|
*/
|
|
|
|
case WM_CLOSE:
|
|
// This is a message from the system indicating that the user has
|
|
// requested to close the window (e.g. alt-f4).
|
|
{
|
|
string close_request_event = get_close_request_event();
|
|
if (!close_request_event.empty()) {
|
|
// In this case, the app has indicated a desire to intercept the
|
|
// request and process it directly.
|
|
throw_event(close_request_event);
|
|
return 0;
|
|
|
|
} else {
|
|
// In this case, the default case, the app does not intend to service
|
|
// the request, so we do by closing the window.
|
|
close_window();
|
|
properties.set_open(false);
|
|
system_changed_properties(properties);
|
|
|
|
// TODO: make sure we release the GSG properly.
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_CHILDACTIVATE:
|
|
if (windisplay_cat.is_debug()) {
|
|
windisplay_cat.debug()
|
|
<< "WM_CHILDACTIVATE: " << hwnd << "\n";
|
|
}
|
|
break;
|
|
|
|
case WM_ACTIVATE:
|
|
if (windisplay_cat.is_debug()) {
|
|
windisplay_cat.debug()
|
|
<< "WM_ACTIVATE: " << hwnd << ", " << wparam << ", " << lparam << "\n";
|
|
}
|
|
properties.set_minimized((wparam & 0xffff0000) != 0);
|
|
if ((wparam & 0xffff) != WA_INACTIVE)
|
|
{
|
|
properties.set_foreground(true);
|
|
if (is_fullscreen())
|
|
{
|
|
// When a fullscreen window goes active, it automatically gets un-
|
|
// minimized.
|
|
int chg_result =
|
|
ChangeDisplaySettings(&_fullscreen_display_mode, CDS_FULLSCREEN);
|
|
if (chg_result != DISP_CHANGE_SUCCESSFUL) {
|
|
const DEVMODE &dm = _fullscreen_display_mode;
|
|
windisplay_cat.error()
|
|
<< "restore ChangeDisplaySettings failed (error code: "
|
|
<< chg_result << ") for specified res: "
|
|
<< dm.dmPelsWidth << " x " << dm.dmPelsHeight
|
|
<< " x " << dm.dmBitsPerPel << ", "
|
|
<< dm.dmDisplayFrequency << " Hz\n";
|
|
}
|
|
|
|
GdiFlush();
|
|
SetWindowPos(_hWnd, HWND_TOP, 0,0,0,0, SWP_NOMOVE | SWP_NOSENDCHANGING | SWP_NOSIZE | SWP_NOOWNERZORDER);
|
|
fullscreen_restored(properties);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
properties.set_foreground(false);
|
|
if (is_fullscreen())
|
|
{
|
|
// When a fullscreen window goes inactive, it automatically gets
|
|
// minimized.
|
|
properties.set_minimized(true);
|
|
|
|
// It seems order is important here. We must minimize the window
|
|
// before restoring the display settings, or risk losing the
|
|
// graphics context.
|
|
ShowWindow(_hWnd, SW_MINIMIZE);
|
|
GdiFlush();
|
|
do_fullscreen_disable();
|
|
fullscreen_minimized(properties);
|
|
}
|
|
}
|
|
|
|
adjust_z_order();
|
|
system_changed_properties(properties);
|
|
break;
|
|
|
|
case WM_SIZE:
|
|
if (windisplay_cat.is_debug()) {
|
|
windisplay_cat.debug()
|
|
<< "WM_SIZE: " << hwnd << ", " << wparam << "\n";
|
|
}
|
|
|
|
// Resist calling handle_reshape before the window has opened.
|
|
if (_hWnd != NULL) {
|
|
handle_reshape();
|
|
}
|
|
break;
|
|
|
|
case WM_EXITSIZEMOVE:
|
|
// handle_reshape();
|
|
break;
|
|
|
|
case WM_WINDOWPOSCHANGED:
|
|
adjust_z_order();
|
|
break;
|
|
|
|
case WM_PAINT:
|
|
// In response to WM_PAINT, we check to see if there are any update
|
|
// regions at all; if there are, we declare the window exposed. This is
|
|
// used to implement !_unexposed_draw.
|
|
if (GetUpdateRect(_hWnd, NULL, false)) {
|
|
if (windisplay_cat.is_spam()) {
|
|
windisplay_cat.spam()
|
|
<< "Got update regions: " << this << "\n";
|
|
}
|
|
_got_expose_event = true;
|
|
}
|
|
break;
|
|
|
|
case WM_LBUTTONDOWN:
|
|
if (_lost_keypresses) {
|
|
resend_lost_keypresses();
|
|
}
|
|
SetCapture(hwnd);
|
|
_input_devices[0].set_pointer_in_window(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam)));
|
|
_input_devices[0].button_down(MouseButton::button(0), get_message_time());
|
|
|
|
// A button-click in the window means to grab the keyboard focus.
|
|
set_focus();
|
|
return 0;
|
|
|
|
case WM_MBUTTONDOWN:
|
|
if (_lost_keypresses) {
|
|
resend_lost_keypresses();
|
|
}
|
|
SetCapture(hwnd);
|
|
_input_devices[0].set_pointer_in_window(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam)));
|
|
_input_devices[0].button_down(MouseButton::button(1), get_message_time());
|
|
// A button-click in the window means to grab the keyboard focus.
|
|
set_focus();
|
|
return 0;
|
|
|
|
case WM_RBUTTONDOWN:
|
|
if (_lost_keypresses) {
|
|
resend_lost_keypresses();
|
|
}
|
|
SetCapture(hwnd);
|
|
_input_devices[0].set_pointer_in_window(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam)));
|
|
_input_devices[0].button_down(MouseButton::button(2), get_message_time());
|
|
// A button-click in the window means to grab the keyboard focus.
|
|
set_focus();
|
|
return 0;
|
|
|
|
case WM_XBUTTONDOWN:
|
|
{
|
|
if (_lost_keypresses) {
|
|
resend_lost_keypresses();
|
|
}
|
|
SetCapture(hwnd);
|
|
int whichButton = GET_XBUTTON_WPARAM(wparam);
|
|
_input_devices[0].set_pointer_in_window(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam)));
|
|
if (whichButton == XBUTTON1) {
|
|
_input_devices[0].button_down(MouseButton::button(3), get_message_time());
|
|
} else if (whichButton == XBUTTON2) {
|
|
_input_devices[0].button_down(MouseButton::button(4), get_message_time());
|
|
}
|
|
}
|
|
return 0;
|
|
|
|
case WM_LBUTTONUP:
|
|
if (_lost_keypresses) {
|
|
resend_lost_keypresses();
|
|
}
|
|
ReleaseCapture();
|
|
_input_devices[0].button_up(MouseButton::button(0), get_message_time());
|
|
return 0;
|
|
|
|
case WM_MBUTTONUP:
|
|
if (_lost_keypresses) {
|
|
resend_lost_keypresses();
|
|
}
|
|
ReleaseCapture();
|
|
_input_devices[0].button_up(MouseButton::button(1), get_message_time());
|
|
return 0;
|
|
|
|
case WM_RBUTTONUP:
|
|
if (_lost_keypresses) {
|
|
resend_lost_keypresses();
|
|
}
|
|
ReleaseCapture();
|
|
_input_devices[0].button_up(MouseButton::button(2), get_message_time());
|
|
return 0;
|
|
|
|
case WM_XBUTTONUP:
|
|
{
|
|
if (_lost_keypresses) {
|
|
resend_lost_keypresses();
|
|
}
|
|
ReleaseCapture();
|
|
int whichButton = GET_XBUTTON_WPARAM(wparam);
|
|
if (whichButton == XBUTTON1) {
|
|
_input_devices[0].button_up(MouseButton::button(3), get_message_time());
|
|
} else if (whichButton == XBUTTON2) {
|
|
_input_devices[0].button_up(MouseButton::button(4), get_message_time());
|
|
}
|
|
}
|
|
return 0;
|
|
|
|
case WM_MOUSEWHEEL:
|
|
{
|
|
int delta = GET_WHEEL_DELTA_WPARAM(wparam);
|
|
|
|
POINT point;
|
|
GetCursorPos(&point);
|
|
ScreenToClient(hwnd, &point);
|
|
double time = get_message_time();
|
|
|
|
if (delta >= 0) {
|
|
while (delta > 0) {
|
|
handle_keypress(MouseButton::wheel_up(), point.x, point.y, time);
|
|
handle_keyrelease(MouseButton::wheel_up(), time);
|
|
delta -= WHEEL_DELTA;
|
|
}
|
|
} else {
|
|
while (delta < 0) {
|
|
handle_keypress(MouseButton::wheel_down(), point.x, point.y, time);
|
|
handle_keyrelease(MouseButton::wheel_down(), time);
|
|
delta += WHEEL_DELTA;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
|
|
case WM_IME_SETCONTEXT:
|
|
if (!ime_hide)
|
|
break;
|
|
|
|
windisplay_cat.debug() << "hwnd = " << hwnd << " and GetFocus = " << GetFocus() << endl;
|
|
_ime_hWnd = ImmGetDefaultIMEWnd(hwnd);
|
|
if (::SendMessage(_ime_hWnd, WM_IME_CONTROL, IMC_CLOSESTATUSWINDOW, 0))
|
|
// if (::SendMessage(hwnd, WM_IME_CONTROL, IMC_CLOSESTATUSWINDOW, 0))
|
|
windisplay_cat.debug() << "SendMessage failed for " << _ime_hWnd << endl;
|
|
else
|
|
windisplay_cat.debug() << "SendMessage Succeeded for " << _ime_hWnd << endl;
|
|
|
|
windisplay_cat.debug() << "wparam is " << wparam << ", lparam is " << lparam << endl;
|
|
lparam &= ~ISC_SHOWUIALL;
|
|
if (ImmIsUIMessage(_ime_hWnd, msg, wparam, lparam))
|
|
windisplay_cat.debug() << "wparam is " << wparam << ", lparam is " << lparam << endl;
|
|
|
|
break;
|
|
|
|
|
|
case WM_IME_NOTIFY:
|
|
if (wparam == IMN_SETOPENSTATUS) {
|
|
HIMC hIMC = ImmGetContext(hwnd);
|
|
nassertr(hIMC != 0, 0);
|
|
_ime_open = (ImmGetOpenStatus(hIMC) != 0);
|
|
if (!_ime_open) {
|
|
_ime_active = false; // Sanity enforcement.
|
|
}
|
|
if (ime_hide) {
|
|
// if (0) {
|
|
COMPOSITIONFORM comf;
|
|
CANDIDATEFORM canf;
|
|
ImmGetCompositionWindow(hIMC, &comf);
|
|
ImmGetCandidateWindow(hIMC, 0, &canf);
|
|
windisplay_cat.debug() <<
|
|
"comf style " << comf.dwStyle <<
|
|
" comf point: x" << comf.ptCurrentPos.x << ",y " << comf.ptCurrentPos.y <<
|
|
" comf rect: l " << comf.rcArea.left << ",t " << comf.rcArea.top << ",r " <<
|
|
comf.rcArea.right << ",b " << comf.rcArea.bottom << endl;
|
|
windisplay_cat.debug() <<
|
|
"canf style " << canf.dwStyle <<
|
|
" canf point: x" << canf.ptCurrentPos.x << ",y " << canf.ptCurrentPos.y <<
|
|
" canf rect: l " << canf.rcArea.left << ",t " << canf.rcArea.top << ",r " <<
|
|
canf.rcArea.right << ",b " << canf.rcArea.bottom << endl;
|
|
comf.dwStyle = CFS_POINT;
|
|
comf.ptCurrentPos.x = 2000;
|
|
comf.ptCurrentPos.y = 2000;
|
|
|
|
canf.dwStyle = CFS_EXCLUDE;
|
|
canf.dwIndex = 0;
|
|
canf.ptCurrentPos.x = 0;
|
|
canf.ptCurrentPos.y = 0;
|
|
canf.rcArea.left = 0;
|
|
canf.rcArea.top = 0;
|
|
canf.rcArea.right = 640;
|
|
canf.rcArea.bottom = 480;
|
|
|
|
#if 0
|
|
comf.rcArea.left = 200;
|
|
comf.rcArea.top = 200;
|
|
comf.rcArea.right = 0;
|
|
comf.rcArea.bottom = 0;
|
|
#endif
|
|
|
|
if (ImmSetCompositionWindow(hIMC, &comf))
|
|
windisplay_cat.debug() << "set composition form: success\n";
|
|
for (int i=0; i<3; ++i) {
|
|
if (ImmSetCandidateWindow(hIMC, &canf))
|
|
windisplay_cat.debug() << "set candidate form: success\n";
|
|
canf.dwIndex++;
|
|
}
|
|
}
|
|
|
|
ImmReleaseContext(hwnd, hIMC);
|
|
}
|
|
break;
|
|
|
|
case WM_IME_STARTCOMPOSITION:
|
|
support_overlay_window(true);
|
|
_ime_active = true;
|
|
break;
|
|
|
|
case WM_IME_ENDCOMPOSITION:
|
|
support_overlay_window(false);
|
|
_ime_active = false;
|
|
|
|
if (ime_aware) {
|
|
wstring ws;
|
|
_input_devices[0].candidate(ws, 0, 0, 0);
|
|
}
|
|
|
|
break;
|
|
|
|
case WM_IME_COMPOSITION:
|
|
if (ime_aware) {
|
|
|
|
// If the ime window is not marked as active at this point, we must be
|
|
// in the process of closing it down (in close_ime), and we don't want
|
|
// to send the current composition string in that case. But we do need
|
|
// to return 0 to tell windows not to try to send the composition string
|
|
// through WM_CHAR messages.
|
|
if (!_ime_active) {
|
|
return 0;
|
|
}
|
|
|
|
HIMC hIMC = ImmGetContext(hwnd);
|
|
nassertr(hIMC != 0, 0);
|
|
|
|
DWORD result_size = 0;
|
|
static const int ime_buffer_size = 256;
|
|
static const int ime_buffer_size_bytes = ime_buffer_size / sizeof(wchar_t);
|
|
wchar_t ime_buffer[ime_buffer_size];
|
|
size_t cursor_pos, delta_start;
|
|
|
|
if (lparam & GCS_RESULTSTR) {
|
|
result_size = ImmGetCompositionStringW(hIMC, GCS_RESULTSTR,
|
|
ime_buffer, ime_buffer_size_bytes);
|
|
size_t num_chars = result_size / sizeof(wchar_t);
|
|
for (size_t i = 0; i < num_chars; ++i) {
|
|
_input_devices[0].keystroke(ime_buffer[i]);
|
|
}
|
|
}
|
|
|
|
if (lparam & GCS_COMPSTR) {
|
|
result_size = ImmGetCompositionStringW(hIMC, GCS_CURSORPOS, NULL, 0);
|
|
cursor_pos = result_size & 0xffff;
|
|
|
|
result_size = ImmGetCompositionStringW(hIMC, GCS_DELTASTART, NULL, 0);
|
|
delta_start = result_size & 0xffff;
|
|
result_size = ImmGetCompositionStringW(hIMC, GCS_COMPSTR, ime_buffer, ime_buffer_size);
|
|
size_t num_chars = result_size / sizeof(wchar_t);
|
|
|
|
_input_devices[0].candidate(wstring(ime_buffer, num_chars),
|
|
min(cursor_pos, delta_start),
|
|
max(cursor_pos, delta_start),
|
|
cursor_pos);
|
|
}
|
|
ImmReleaseContext(hwnd, hIMC);
|
|
}
|
|
break;
|
|
|
|
case WM_CHAR:
|
|
// Ignore WM_CHAR messages if we have the IME open, since everything will
|
|
// come in through WM_IME_COMPOSITION. (It's supposed to come in through
|
|
// WM_CHAR, too, but there seems to be a bug in Win2000 in that it only
|
|
// sends question mark characters through here.)
|
|
|
|
// Actually, probably that "bug" was due to the fact that we were
|
|
// previously using the ANSI versions of RegisterClass etc., in which case
|
|
// the actual value passed to WM_CHAR seems to be poorly defined. Now we
|
|
// are using RegisterClassW etc., which means WM_CHAR is absolutely
|
|
// supposed to be utf-16.
|
|
if (!_ime_open) {
|
|
_input_devices[0].keystroke(wparam);
|
|
}
|
|
break;
|
|
|
|
case WM_SYSKEYDOWN:
|
|
if (_lost_keypresses) {
|
|
resend_lost_keypresses();
|
|
}
|
|
if (windisplay_cat.is_debug()) {
|
|
windisplay_cat.debug()
|
|
<< "syskeydown: " << wparam << " (" << lookup_key(wparam) << ")\n";
|
|
}
|
|
{
|
|
// Alt and F10 are sent as WM_SYSKEYDOWN instead of WM_KEYDOWN want to
|
|
// use defwindproc on Alt syskey so std windows cmd Alt-F4 works, etc
|
|
POINT point;
|
|
GetCursorPos(&point);
|
|
ScreenToClient(hwnd, &point);
|
|
handle_keypress(lookup_key(wparam), point.x, point.y,
|
|
get_message_time());
|
|
|
|
if ((lparam & 0x40000000) == 0) {
|
|
handle_raw_keypress(lookup_raw_key(lparam), get_message_time());
|
|
}
|
|
|
|
/*
|
|
* wparam does not contain leftright information for SHIFT, CONTROL, or ALT,
|
|
* so we have to track their status and do the right thing. We'll send the
|
|
* leftright specific key event along with the general key event. Key
|
|
* repeating is not being handled consistently for LALT and RALT, but from
|
|
* comments below, it's only being handled the way it is for backspace, so
|
|
* we'll leave it as is.
|
|
*/
|
|
if (wparam == VK_MENU) {
|
|
if ((GetKeyState(VK_LMENU) & 0x8000) != 0 && ! _lalt_down) {
|
|
handle_keypress(KeyboardButton::lalt(), point.x, point.y,
|
|
get_message_time());
|
|
_lalt_down = true;
|
|
}
|
|
if ((GetKeyState(VK_RMENU) & 0x8000) != 0 && ! _ralt_down) {
|
|
handle_keypress(KeyboardButton::ralt(), point.x, point.y,
|
|
get_message_time());
|
|
_ralt_down = true;
|
|
}
|
|
}
|
|
if (wparam == VK_F10) {
|
|
// bypass default windproc F10 behavior (it activates the main menu,
|
|
// but we have none)
|
|
return 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_SYSCOMMAND:
|
|
if (wparam == SC_KEYMENU) {
|
|
// if Alt is released (alone wo other keys), defwindproc will send this
|
|
// command, which will 'activate' the title bar menu (we have none) and
|
|
// give focus to it. we don't want this to happen, so kill this msg.
|
|
|
|
// Note that the WM_SYSKEYUP message for Alt has already been sent (if
|
|
// it is going to be), so ignoring this special message does no harm.
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case WM_KEYDOWN:
|
|
if (_lost_keypresses) {
|
|
resend_lost_keypresses();
|
|
}
|
|
if (windisplay_cat.is_debug()) {
|
|
windisplay_cat.debug()
|
|
<< "keydown: " << wparam << " (" << lookup_key(wparam) << ")\n";
|
|
}
|
|
|
|
// If this bit is not zero, this is just a keyrepeat echo; we ignore these
|
|
// for handle_keypress (we respect keyrepeat only for handle_keystroke).
|
|
if ((lparam & 0x40000000) == 0) {
|
|
POINT point;
|
|
GetCursorPos(&point);
|
|
ScreenToClient(hwnd, &point);
|
|
handle_keypress(lookup_key(wparam), point.x, point.y,
|
|
get_message_time());
|
|
handle_raw_keypress(lookup_raw_key(lparam), get_message_time());
|
|
|
|
// wparam does not contain leftright information for SHIFT, CONTROL, or
|
|
// ALT, so we have to track their status and do the right thing. We'll
|
|
// send the leftright specific key event along with the general key
|
|
// event.
|
|
if (wparam == VK_SHIFT) {
|
|
if ((GetKeyState(VK_LSHIFT) & 0x8000) != 0 && ! _lshift_down) {
|
|
handle_keypress(KeyboardButton::lshift(), point.x, point.y,
|
|
get_message_time());
|
|
_lshift_down = true;
|
|
}
|
|
if ((GetKeyState(VK_RSHIFT) & 0x8000) != 0 && ! _rshift_down) {
|
|
handle_keypress(KeyboardButton::rshift(), point.x, point.y,
|
|
get_message_time());
|
|
_rshift_down = true;
|
|
}
|
|
} else if(wparam == VK_CONTROL) {
|
|
if ((GetKeyState(VK_LCONTROL) & 0x8000) != 0 && ! _lcontrol_down) {
|
|
handle_keypress(KeyboardButton::lcontrol(), point.x, point.y,
|
|
get_message_time());
|
|
_lcontrol_down = true;
|
|
}
|
|
if ((GetKeyState(VK_RCONTROL) & 0x8000) != 0 && ! _rcontrol_down) {
|
|
handle_keypress(KeyboardButton::rcontrol(), point.x, point.y,
|
|
get_message_time());
|
|
_rcontrol_down = true;
|
|
}
|
|
}
|
|
|
|
// Handle Cntrl-V paste from clipboard. Is there a better way to detect
|
|
// this hotkey?
|
|
if ((wparam=='V') && (GetKeyState(VK_CONTROL) < 0) &&
|
|
!_input_devices.empty()) {
|
|
HGLOBAL hglb;
|
|
char *lptstr;
|
|
|
|
if (IsClipboardFormatAvailable(CF_TEXT) && OpenClipboard(NULL)) {
|
|
// Maybe we should support CF_UNICODETEXT if it is available too?
|
|
hglb = GetClipboardData(CF_TEXT);
|
|
if (hglb!=NULL) {
|
|
lptstr = (char *) GlobalLock(hglb);
|
|
if (lptstr != NULL) {
|
|
char *pChar;
|
|
for (pChar=lptstr; *pChar!=NULL; pChar++) {
|
|
_input_devices[0].keystroke((uchar)*pChar);
|
|
}
|
|
GlobalUnlock(hglb);
|
|
}
|
|
}
|
|
CloseClipboard();
|
|
}
|
|
}
|
|
} else {
|
|
// Actually, for now we'll respect the repeat anyway, just so we support
|
|
// backspace properly. Rethink later.
|
|
POINT point;
|
|
GetCursorPos(&point);
|
|
ScreenToClient(hwnd, &point);
|
|
handle_keypress(lookup_key(wparam), point.x, point.y,
|
|
get_message_time());
|
|
|
|
/*
|
|
* wparam does not contain leftright information for SHIFT, CONTROL, or ALT,
|
|
* so we have to track their status and do the right thing. We'll send the
|
|
* leftright specific key event along with the general key event. If the user
|
|
* presses LSHIFT and then RSHIFT, the RSHIFT event will come in with the
|
|
* keyrepeat flag on (i.e. it will end up in this block). The logic below
|
|
* should detect this correctly and only send the RSHIFT event. Note that the
|
|
* CONTROL event will be sent twice, once for each keypress. Since keyrepeats
|
|
* are currently being sent simply as additional keypress events, that should
|
|
* be okay for now.
|
|
*/
|
|
if (wparam == VK_SHIFT) {
|
|
if (((GetKeyState(VK_LSHIFT) & 0x8000) != 0) && ! _lshift_down ) {
|
|
handle_keypress(KeyboardButton::lshift(), point.x, point.y,
|
|
get_message_time());
|
|
_lshift_down = true;
|
|
} else if (((GetKeyState(VK_RSHIFT) & 0x8000) != 0) && ! _rshift_down ) {
|
|
handle_keypress(KeyboardButton::rshift(), point.x, point.y,
|
|
get_message_time());
|
|
_rshift_down = true;
|
|
} else {
|
|
if ((GetKeyState(VK_LSHIFT) & 0x8000) != 0) {
|
|
handle_keypress(KeyboardButton::lshift(), point.x, point.y,
|
|
get_message_time());
|
|
}
|
|
if ((GetKeyState(VK_RSHIFT) & 0x8000) != 0) {
|
|
handle_keypress(KeyboardButton::rshift(), point.x, point.y,
|
|
get_message_time());
|
|
}
|
|
}
|
|
} else if(wparam == VK_CONTROL) {
|
|
if (((GetKeyState(VK_LCONTROL) & 0x8000) != 0) && ! _lcontrol_down ) {
|
|
handle_keypress(KeyboardButton::lcontrol(), point.x, point.y,
|
|
get_message_time());
|
|
_lcontrol_down = true;
|
|
} else if (((GetKeyState(VK_RCONTROL) & 0x8000) != 0) && ! _rcontrol_down ) {
|
|
handle_keypress(KeyboardButton::rcontrol(), point.x, point.y,
|
|
get_message_time());
|
|
_rcontrol_down = true;
|
|
} else {
|
|
if ((GetKeyState(VK_LCONTROL) & 0x8000) != 0) {
|
|
handle_keypress(KeyboardButton::lcontrol(), point.x, point.y,
|
|
get_message_time());
|
|
}
|
|
if ((GetKeyState(VK_RCONTROL) & 0x8000) != 0) {
|
|
handle_keypress(KeyboardButton::rcontrol(), point.x, point.y,
|
|
get_message_time());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_SYSKEYUP:
|
|
case WM_KEYUP:
|
|
if (_lost_keypresses) {
|
|
resend_lost_keypresses();
|
|
}
|
|
if (windisplay_cat.is_debug()) {
|
|
windisplay_cat.debug()
|
|
<< "keyup: " << wparam << " (" << lookup_key(wparam) << ")\n";
|
|
}
|
|
handle_keyrelease(lookup_key(wparam), get_message_time());
|
|
handle_raw_keyrelease(lookup_raw_key(lparam), get_message_time());
|
|
|
|
// wparam does not contain leftright information for SHIFT, CONTROL, or
|
|
// ALT, so we have to track their status and do the right thing. We'll
|
|
// send the leftright specific key event along with the general key event.
|
|
if (wparam == VK_SHIFT) {
|
|
if ((GetKeyState(VK_LSHIFT) & 0x8000) == 0 && _lshift_down) {
|
|
handle_keyrelease(KeyboardButton::lshift(), get_message_time());
|
|
_lshift_down = false;
|
|
}
|
|
if ((GetKeyState(VK_RSHIFT) & 0x8000) == 0 && _rshift_down) {
|
|
handle_keyrelease(KeyboardButton::rshift(), get_message_time());
|
|
_rshift_down = false;
|
|
}
|
|
} else if(wparam == VK_CONTROL) {
|
|
if ((GetKeyState(VK_LCONTROL) & 0x8000) == 0 && _lcontrol_down) {
|
|
handle_keyrelease(KeyboardButton::lcontrol(), get_message_time());
|
|
_lcontrol_down = false;
|
|
}
|
|
if ((GetKeyState(VK_RCONTROL) & 0x8000) == 0 && _rcontrol_down) {
|
|
handle_keyrelease(KeyboardButton::rcontrol(), get_message_time());
|
|
_rcontrol_down = false;
|
|
}
|
|
} else if(wparam == VK_MENU) {
|
|
if ((GetKeyState(VK_LMENU) & 0x8000) == 0 && _lalt_down) {
|
|
handle_keyrelease(KeyboardButton::lalt(), get_message_time());
|
|
_lalt_down = false;
|
|
}
|
|
if ((GetKeyState(VK_RMENU) & 0x8000) == 0 && _ralt_down) {
|
|
handle_keyrelease(KeyboardButton::ralt(), get_message_time());
|
|
_ralt_down = false;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_KILLFOCUS:
|
|
if (windisplay_cat.is_debug()) {
|
|
windisplay_cat.debug()
|
|
<< "killfocus\n";
|
|
}
|
|
|
|
_input_devices[0].focus_lost(get_message_time());
|
|
properties.set_foreground(false);
|
|
system_changed_properties(properties);
|
|
break;
|
|
|
|
case WM_SETFOCUS:
|
|
// You would think that this would be a good time to call
|
|
// resend_lost_keypresses(), but it turns out that we get WM_SETFOCUS
|
|
// slightly before Windows starts resending key updown events to us.
|
|
|
|
/*
|
|
* In particular, if the user restored focus using alt-tab, then at this point
|
|
* the keyboard state will indicate that both the alt and tab keys are held
|
|
* down. However, there is a small window of opportunity for the user to
|
|
* release these keys before Windows starts telling us about keyup events.
|
|
* Thus, if we record the fact that alt and tab are being held down now, we
|
|
* may miss the keyup events for them, and they can get "stuck" down.
|
|
*/
|
|
|
|
// So we have to defer calling resend_lost_keypresses() until we know
|
|
// Windows is ready to send us key updown events. I don't know when we
|
|
// can guarantee that, except when we actually do start to receive key
|
|
// updown events, so that call is made there.
|
|
|
|
if (windisplay_cat.is_debug()) {
|
|
windisplay_cat.debug()
|
|
<< "setfocus\n";
|
|
}
|
|
|
|
if (_lost_keypresses) {
|
|
resend_lost_keypresses();
|
|
}
|
|
|
|
properties.set_foreground(true);
|
|
system_changed_properties(properties);
|
|
break;
|
|
|
|
case PM_ACTIVE:
|
|
if (windisplay_cat.is_debug()) {
|
|
windisplay_cat.debug()
|
|
<< "PM_ACTIVE\n";
|
|
}
|
|
properties.set_foreground(true);
|
|
system_changed_properties(properties);
|
|
break;
|
|
|
|
case PM_INACTIVE:
|
|
if (windisplay_cat.is_debug()) {
|
|
windisplay_cat.debug()
|
|
<< "PM_INACTIVE\n";
|
|
}
|
|
properties.set_foreground(false);
|
|
system_changed_properties(properties);
|
|
break;
|
|
|
|
case WM_DPICHANGED:
|
|
// The window moved to a monitor of different DPI, or someone changed the
|
|
// DPI setting in the configuration panel.
|
|
if (windisplay_cat.is_debug()) {
|
|
windisplay_cat.debug() << "DPI changed to " << LOWORD(wparam);
|
|
|
|
if (LOWORD(wparam) != HIWORD(wparam)) {
|
|
windisplay_cat.debug(false) << "x" << HIWORD(wparam) << "\n";
|
|
} else {
|
|
windisplay_cat.debug(false) << "\n";
|
|
}
|
|
}
|
|
// Resize the window if requested to match the new DPI. Obviously, don't
|
|
// do this if a fixed size was requested.
|
|
if (!_properties.get_fixed_size() && dpi_window_resize) {
|
|
RECT &rect = *(LPRECT)lparam;
|
|
SetWindowPos(_hWnd, HWND_TOP, rect.left, rect.top,
|
|
rect.right - rect.left, rect.bottom - rect.top,
|
|
SWP_NOZORDER | SWP_NOACTIVATE);
|
|
}
|
|
break;
|
|
|
|
case WM_TOUCH:
|
|
_num_touches = LOWORD(wparam);
|
|
if (_num_touches > MAX_TOUCHES) {
|
|
_num_touches = MAX_TOUCHES;
|
|
}
|
|
if (pGetTouchInputInfo != 0) {
|
|
pGetTouchInputInfo((HTOUCHINPUT)lparam, _num_touches, _touches, sizeof(TOUCHINPUT));
|
|
pCloseTouchInputHandle((HTOUCHINPUT)lparam);
|
|
}
|
|
break;
|
|
}
|
|
|
|
// do custom messages processing if any has been set
|
|
for ( WinProcClasses::iterator it=_window_proc_classes.begin() ; it != _window_proc_classes.end(); it++ ){
|
|
(*it)->wnd_proc(this, hwnd, msg, wparam, lparam);
|
|
}
|
|
|
|
return DefWindowProcW(hwnd, msg, wparam, lparam);
|
|
}
|
|
|
|
|
|
/**
|
|
* This is attached to the window class for all WinGraphicsWindow windows; it
|
|
* is called to handle window events.
|
|
*/
|
|
LONG WINAPI WinGraphicsWindow::
|
|
static_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
|
|
// Look up the window in our global map.
|
|
WindowHandles::const_iterator wi;
|
|
wi = _window_handles.find(hwnd);
|
|
if (wi != _window_handles.end()) {
|
|
// We found the window.
|
|
return (*wi).second->window_proc(hwnd, msg, wparam, lparam);
|
|
}
|
|
|
|
// The window wasn't in the map; we must be creating it right now.
|
|
if (_creating_window != (WinGraphicsWindow *)NULL) {
|
|
return _creating_window->window_proc(hwnd, msg, wparam, lparam);
|
|
}
|
|
|
|
// Oops, we weren't creating a window! Don't know how to handle the
|
|
// message, so just pass it on to Windows to deal with it.
|
|
return DefWindowProcW(hwnd, msg, wparam, lparam);
|
|
}
|
|
|
|
/**
|
|
* Handles one event from the message queue.
|
|
*/
|
|
void WinGraphicsWindow::
|
|
process_1_event() {
|
|
MSG msg;
|
|
|
|
if (!GetMessage(&msg, NULL, 0, 0)) {
|
|
// WM_QUIT received. We need a cleaner way to deal with this.
|
|
// DestroyAllWindows(false);
|
|
exit(msg.wParam); // this will invoke AtExitFn
|
|
}
|
|
|
|
// Translate virtual key messages
|
|
TranslateMessage(&msg);
|
|
// Call window_proc
|
|
DispatchMessage(&msg);
|
|
}
|
|
|
|
/**
|
|
* Called when the keyboard focus has been restored to the window after it has
|
|
* been lost for a time, this rechecks the keyboard state and generates key
|
|
* up/down messages for keys that have changed state in the meantime.
|
|
*/
|
|
void WinGraphicsWindow::
|
|
resend_lost_keypresses() {
|
|
nassertv(_lost_keypresses);
|
|
// This is now a no-op. Not sure we really want to generate new "down" or
|
|
// "resume" events for keys that were held while the window focus is
|
|
// restored.
|
|
|
|
_lost_keypresses = false;
|
|
}
|
|
|
|
/**
|
|
* Changes _cursor_window from its current value to the indicated value. This
|
|
* also changes the cursor properties appropriately.
|
|
*/
|
|
void WinGraphicsWindow::
|
|
update_cursor_window(WinGraphicsWindow *to_window) {
|
|
bool hide_cursor = false;
|
|
if (to_window == (WinGraphicsWindow *)NULL) {
|
|
// We are leaving a graphics window; we should restore the Win2000
|
|
// effects.
|
|
if (_got_saved_params) {
|
|
SystemParametersInfo(SPI_SETMOUSETRAILS, NULL,
|
|
(PVOID)_saved_mouse_trails, NULL);
|
|
SystemParametersInfo(SPI_SETCURSORSHADOW, NULL,
|
|
(PVOID)_saved_cursor_shadow, NULL);
|
|
SystemParametersInfo(SPI_SETMOUSEVANISH, NULL,
|
|
(PVOID)_saved_mouse_vanish, NULL);
|
|
_got_saved_params = false;
|
|
}
|
|
|
|
} else {
|
|
const WindowProperties &to_props = to_window->get_properties();
|
|
hide_cursor = to_props.get_cursor_hidden();
|
|
|
|
// We are entering a graphics window; we should save and disable the
|
|
// Win2000 effects. These don't work at all well over a 3-D window.
|
|
|
|
// These parameters are only defined for Win2000XP, but they should just
|
|
// cause a silent error on earlier OS's, which is OK.
|
|
if (!_got_saved_params) {
|
|
SystemParametersInfo(SPI_GETMOUSETRAILS, NULL,
|
|
&_saved_mouse_trails, NULL);
|
|
SystemParametersInfo(SPI_GETCURSORSHADOW, NULL,
|
|
&_saved_cursor_shadow, NULL);
|
|
SystemParametersInfo(SPI_GETMOUSEVANISH, NULL,
|
|
&_saved_mouse_vanish, NULL);
|
|
_got_saved_params = true;
|
|
|
|
SystemParametersInfo(SPI_SETMOUSETRAILS, NULL, (PVOID)0, NULL);
|
|
SystemParametersInfo(SPI_SETCURSORSHADOW, NULL, (PVOID)false, NULL);
|
|
SystemParametersInfo(SPI_SETMOUSEVANISH, NULL, (PVOID)false, NULL);
|
|
}
|
|
|
|
SetCursor(to_window->_cursor);
|
|
}
|
|
|
|
hide_or_show_cursor(hide_cursor);
|
|
|
|
_cursor_window = to_window;
|
|
}
|
|
|
|
/**
|
|
* Hides or shows the mouse cursor according to the indicated parameter. This
|
|
* is normally called when the mouse wanders into or out of a window with the
|
|
* cursor_hidden properties.
|
|
*/
|
|
void WinGraphicsWindow::
|
|
hide_or_show_cursor(bool hide_cursor) {
|
|
if (hide_cursor) {
|
|
if (!_cursor_hidden) {
|
|
ShowCursor(false);
|
|
_cursor_hidden = true;
|
|
}
|
|
} else {
|
|
if (_cursor_hidden) {
|
|
ShowCursor(true);
|
|
_cursor_hidden = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// don't pick any video modes < MIN_REFRESH_RATE Hz
|
|
#define MIN_REFRESH_RATE 60
|
|
// EnumDisplaySettings may indicate 0 or 1 for refresh rate, which means use
|
|
// driver default rate (assume its >min_refresh_rate)
|
|
#define ACCEPTABLE_REFRESH_RATE(RATE) ((RATE >= MIN_REFRESH_RATE) || (RATE==0) || (RATE==1))
|
|
|
|
/**
|
|
* Looks for a fullscreen mode that meets the specified size and bitdepth
|
|
* requirements. Returns true if a suitable mode is found, false otherwise.
|
|
*/
|
|
bool WinGraphicsWindow::
|
|
find_acceptable_display_mode(DWORD dwWidth, DWORD dwHeight, DWORD bpp,
|
|
DEVMODE &dm) {
|
|
|
|
// Get the current mode. We'll try to match the refresh rate.
|
|
DEVMODE cur_dm;
|
|
ZeroMemory(&cur_dm, sizeof(cur_dm));
|
|
cur_dm.dmSize = sizeof(cur_dm);
|
|
EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &cur_dm);
|
|
|
|
int modenum = 0;
|
|
int saved_modenum = -1;
|
|
|
|
while (1) {
|
|
ZeroMemory(&dm, sizeof(dm));
|
|
dm.dmSize = sizeof(dm);
|
|
|
|
if (!EnumDisplaySettings(NULL, modenum, &dm)) {
|
|
break;
|
|
}
|
|
|
|
if ((dm.dmPelsWidth == dwWidth) && (dm.dmPelsHeight == dwHeight) &&
|
|
(dm.dmBitsPerPel == bpp)) {
|
|
// If this also matches in refresh rate, we're done here. Otherwise,
|
|
// save this as a second choice for later.
|
|
if (dm.dmDisplayFrequency == cur_dm.dmDisplayFrequency) {
|
|
return true;
|
|
} else if (saved_modenum == -1) {
|
|
saved_modenum = modenum;
|
|
}
|
|
}
|
|
modenum++;
|
|
}
|
|
|
|
// Failed to find an exact match, but we do have a match that didn't match
|
|
// the refresh rate.
|
|
if (saved_modenum != -1) {
|
|
ZeroMemory(&dm, sizeof(dm));
|
|
dm.dmSize = sizeof(dm);
|
|
|
|
if (EnumDisplaySettings(NULL, saved_modenum, &dm)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Pops up a dialog box with the indicated Windows error message ID (or the
|
|
* last error message generated) for meaningful display to the user.
|
|
*/
|
|
void WinGraphicsWindow::
|
|
show_error_message(DWORD message_id) {
|
|
LPTSTR message_buffer;
|
|
|
|
if (message_id == 0) {
|
|
message_id = GetLastError();
|
|
}
|
|
|
|
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
|
|
NULL, message_id,
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), //The user default language
|
|
(LPTSTR)&message_buffer, // the weird ptrptr->ptr cast is intentional, see FORMAT_MESSAGE_ALLOCATE_BUFFER
|
|
1024, NULL);
|
|
MessageBox(GetDesktopWindow(), message_buffer, _T(errorbox_title), MB_OK);
|
|
windisplay_cat.fatal() << "System error msg: " << message_buffer << endl;
|
|
LocalFree(message_buffer);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
void WinGraphicsWindow::
|
|
handle_keypress(ButtonHandle key, int x, int y, double time) {
|
|
_input_devices[0].set_pointer_in_window(x, y);
|
|
if (key != ButtonHandle::none()) {
|
|
_input_devices[0].button_down(key, time);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Indicates we detected a key was already down when the focus is restored to
|
|
* the window. Mainly useful for tracking the state of modifier keys.
|
|
*/
|
|
void WinGraphicsWindow::
|
|
handle_keyresume(ButtonHandle key, double time) {
|
|
if (key != ButtonHandle::none()) {
|
|
_input_devices[0].button_resume_down(key, time);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
void WinGraphicsWindow::
|
|
handle_keyrelease(ButtonHandle key, double time) {
|
|
if (key != ButtonHandle::none()) {
|
|
_input_devices[0].button_up(key, time);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
void WinGraphicsWindow::
|
|
handle_raw_keypress(ButtonHandle key, double time) {
|
|
if (key != ButtonHandle::none()) {
|
|
_input_devices[0].raw_button_down(key, time);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
void WinGraphicsWindow::
|
|
handle_raw_keyrelease(ButtonHandle key, double time) {
|
|
if (key != ButtonHandle::none()) {
|
|
_input_devices[0].raw_button_up(key, time);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Translates the keycode reported by Windows to an appropriate Panda
|
|
* ButtonHandle.
|
|
*/
|
|
ButtonHandle WinGraphicsWindow::
|
|
lookup_key(WPARAM wparam) const {
|
|
// First, check for a few buttons that we filter out when the IME window is
|
|
// open.
|
|
if (!_ime_active) {
|
|
switch(wparam) {
|
|
case VK_BACK: return KeyboardButton::backspace();
|
|
case VK_DELETE: return KeyboardButton::del();
|
|
case VK_ESCAPE: return KeyboardButton::escape();
|
|
case VK_SPACE: return KeyboardButton::space();
|
|
case VK_UP: return KeyboardButton::up();
|
|
case VK_DOWN: return KeyboardButton::down();
|
|
case VK_LEFT: return KeyboardButton::left();
|
|
case VK_RIGHT: return KeyboardButton::right();
|
|
}
|
|
}
|
|
|
|
// Now check for the rest of the buttons, including the ones that we allow
|
|
// through even when the IME window is open.
|
|
switch(wparam) {
|
|
case VK_TAB: return KeyboardButton::tab();
|
|
case VK_PRIOR: return KeyboardButton::page_up();
|
|
case VK_NEXT: return KeyboardButton::page_down();
|
|
case VK_HOME: return KeyboardButton::home();
|
|
case VK_END: return KeyboardButton::end();
|
|
case VK_F1: return KeyboardButton::f1();
|
|
case VK_F2: return KeyboardButton::f2();
|
|
case VK_F3: return KeyboardButton::f3();
|
|
case VK_F4: return KeyboardButton::f4();
|
|
case VK_F5: return KeyboardButton::f5();
|
|
case VK_F6: return KeyboardButton::f6();
|
|
case VK_F7: return KeyboardButton::f7();
|
|
case VK_F8: return KeyboardButton::f8();
|
|
case VK_F9: return KeyboardButton::f9();
|
|
case VK_F10: return KeyboardButton::f10();
|
|
case VK_F11: return KeyboardButton::f11();
|
|
case VK_F12: return KeyboardButton::f12();
|
|
case VK_INSERT: return KeyboardButton::insert();
|
|
case VK_CAPITAL: return KeyboardButton::caps_lock();
|
|
case VK_NUMLOCK: return KeyboardButton::num_lock();
|
|
case VK_SCROLL: return KeyboardButton::scroll_lock();
|
|
case VK_PAUSE: return KeyboardButton::pause();
|
|
case VK_SNAPSHOT: return KeyboardButton::print_screen();
|
|
|
|
case VK_SHIFT: return KeyboardButton::shift();
|
|
case VK_LSHIFT: return KeyboardButton::lshift();
|
|
case VK_RSHIFT: return KeyboardButton::rshift();
|
|
|
|
case VK_CONTROL: return KeyboardButton::control();
|
|
case VK_LCONTROL: return KeyboardButton::lcontrol();
|
|
case VK_RCONTROL: return KeyboardButton::rcontrol();
|
|
|
|
case VK_MENU: return KeyboardButton::alt();
|
|
case VK_LMENU: return KeyboardButton::lalt();
|
|
case VK_RMENU: return KeyboardButton::ralt();
|
|
|
|
default:
|
|
int key = MapVirtualKey(wparam, 2);
|
|
if (isascii(key) && key != 0) {
|
|
// We used to try to remap lowercase to uppercase keys here based on the
|
|
// state of the shift andor caps lock keys. But that's a mistake, and
|
|
// doesn't allow for international or user-defined keyboards; let
|
|
// Windows do that mapping.
|
|
|
|
// Nowadays, we make a distinction between a "button" and a "keystroke".
|
|
// A button corresponds to a physical button on the keyboard and has a
|
|
// down and up event associated. A keystroke may or may not correspond
|
|
// to a physical button, but will be some Unicode character and will not
|
|
// have a corresponding up event.
|
|
return KeyboardButton::ascii_key(tolower(key));
|
|
}
|
|
break;
|
|
}
|
|
return ButtonHandle::none();
|
|
}
|
|
|
|
/**
|
|
* Translates the scancode reported by Windows to an appropriate Panda
|
|
* ButtonHandle.
|
|
*/
|
|
ButtonHandle WinGraphicsWindow::
|
|
lookup_raw_key(LPARAM lparam) const {
|
|
unsigned char vsc = (lparam & 0xff0000) >> 16;
|
|
|
|
if (lparam & 0x1000000) {
|
|
// Extended keys
|
|
switch (vsc) {
|
|
case 28: return KeyboardButton::enter();
|
|
case 29: return KeyboardButton::rcontrol();
|
|
case 53: return KeyboardButton::ascii_key('/');
|
|
case 55: return KeyboardButton::print_screen();
|
|
case 56: return KeyboardButton::ralt();
|
|
case 69: return KeyboardButton::num_lock();
|
|
case 71: return KeyboardButton::home();
|
|
case 72: return KeyboardButton::up();
|
|
case 73: return KeyboardButton::page_up();
|
|
case 75: return KeyboardButton::left();
|
|
case 77: return KeyboardButton::right();
|
|
case 79: return KeyboardButton::end();
|
|
case 80: return KeyboardButton::down();
|
|
case 81: return KeyboardButton::page_down();
|
|
case 82: return KeyboardButton::insert();
|
|
case 83: return KeyboardButton::del();
|
|
case 91: return KeyboardButton::lmeta();
|
|
case 92: return KeyboardButton::rmeta();
|
|
case 93: return KeyboardButton::menu();
|
|
}
|
|
}
|
|
|
|
if (vsc <= 83) {
|
|
static ButtonHandle raw_map[] = {
|
|
ButtonHandle::none(),
|
|
KeyboardButton::escape(),
|
|
KeyboardButton::ascii_key('1'),
|
|
KeyboardButton::ascii_key('2'),
|
|
KeyboardButton::ascii_key('3'),
|
|
KeyboardButton::ascii_key('4'),
|
|
KeyboardButton::ascii_key('5'),
|
|
KeyboardButton::ascii_key('6'),
|
|
KeyboardButton::ascii_key('7'),
|
|
KeyboardButton::ascii_key('8'),
|
|
KeyboardButton::ascii_key('9'),
|
|
KeyboardButton::ascii_key('0'),
|
|
KeyboardButton::ascii_key('-'),
|
|
KeyboardButton::ascii_key('='),
|
|
KeyboardButton::backspace(),
|
|
KeyboardButton::tab(),
|
|
KeyboardButton::ascii_key('q'),
|
|
KeyboardButton::ascii_key('w'),
|
|
KeyboardButton::ascii_key('e'),
|
|
KeyboardButton::ascii_key('r'),
|
|
KeyboardButton::ascii_key('t'),
|
|
KeyboardButton::ascii_key('y'),
|
|
KeyboardButton::ascii_key('u'),
|
|
KeyboardButton::ascii_key('i'),
|
|
KeyboardButton::ascii_key('o'),
|
|
KeyboardButton::ascii_key('p'),
|
|
KeyboardButton::ascii_key('['),
|
|
KeyboardButton::ascii_key(']'),
|
|
KeyboardButton::enter(),
|
|
KeyboardButton::lcontrol(),
|
|
KeyboardButton::ascii_key('a'),
|
|
KeyboardButton::ascii_key('s'),
|
|
KeyboardButton::ascii_key('d'),
|
|
KeyboardButton::ascii_key('f'),
|
|
KeyboardButton::ascii_key('g'),
|
|
KeyboardButton::ascii_key('h'),
|
|
KeyboardButton::ascii_key('j'),
|
|
KeyboardButton::ascii_key('k'),
|
|
KeyboardButton::ascii_key('l'),
|
|
KeyboardButton::ascii_key(';'),
|
|
KeyboardButton::ascii_key('\''),
|
|
KeyboardButton::ascii_key('`'),
|
|
KeyboardButton::lshift(),
|
|
KeyboardButton::ascii_key('\\'),
|
|
KeyboardButton::ascii_key('z'),
|
|
KeyboardButton::ascii_key('x'),
|
|
KeyboardButton::ascii_key('c'),
|
|
KeyboardButton::ascii_key('v'),
|
|
KeyboardButton::ascii_key('b'),
|
|
KeyboardButton::ascii_key('n'),
|
|
KeyboardButton::ascii_key('m'),
|
|
KeyboardButton::ascii_key(','),
|
|
KeyboardButton::ascii_key('.'),
|
|
KeyboardButton::ascii_key('/'),
|
|
KeyboardButton::rshift(),
|
|
KeyboardButton::ascii_key('*'),
|
|
KeyboardButton::lalt(),
|
|
KeyboardButton::space(),
|
|
KeyboardButton::caps_lock(),
|
|
KeyboardButton::f1(),
|
|
KeyboardButton::f2(),
|
|
KeyboardButton::f3(),
|
|
KeyboardButton::f4(),
|
|
KeyboardButton::f5(),
|
|
KeyboardButton::f6(),
|
|
KeyboardButton::f7(),
|
|
KeyboardButton::f8(),
|
|
KeyboardButton::f9(),
|
|
KeyboardButton::f10(),
|
|
KeyboardButton::pause(),
|
|
KeyboardButton::scroll_lock(),
|
|
KeyboardButton::ascii_key('7'),
|
|
KeyboardButton::ascii_key('8'),
|
|
KeyboardButton::ascii_key('9'),
|
|
KeyboardButton::ascii_key('-'),
|
|
KeyboardButton::ascii_key('4'),
|
|
KeyboardButton::ascii_key('5'),
|
|
KeyboardButton::ascii_key('6'),
|
|
KeyboardButton::ascii_key('+'),
|
|
KeyboardButton::ascii_key('1'),
|
|
KeyboardButton::ascii_key('2'),
|
|
KeyboardButton::ascii_key('3'),
|
|
KeyboardButton::ascii_key('0'),
|
|
KeyboardButton::ascii_key('.')
|
|
};
|
|
return raw_map[vsc];
|
|
}
|
|
|
|
// A few additional keys don't fit well in the above table.
|
|
switch (vsc) {
|
|
case 87: return KeyboardButton::f11();
|
|
case 88: return KeyboardButton::f12();
|
|
default: return ButtonHandle::none();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a ButtonMap containing the association between raw buttons and
|
|
* virtual buttons.
|
|
*
|
|
* Note that on Windows, the pause button and numpad keys are not mapped
|
|
* reliably.
|
|
*/
|
|
ButtonMap *WinGraphicsWindow::
|
|
get_keyboard_map() const {
|
|
ButtonMap *map = new ButtonMap;
|
|
|
|
wchar_t text[256];
|
|
UINT vsc = 0;
|
|
unsigned short ex_vsc[] = {0x57, 0x58,
|
|
0x011c, 0x011d, 0x0135, 0x0137, 0x0138, 0x0145, 0x0147, 0x0148, 0x0149, 0x014b, 0x014d, 0x014f, 0x0150, 0x0151, 0x0152, 0x0153, 0x015b, 0x015c, 0x015d};
|
|
|
|
for (int k = 1; k < 84 + 17; ++k) {
|
|
if (k >= 84) {
|
|
vsc = ex_vsc[k - 84];
|
|
} else {
|
|
vsc = k;
|
|
}
|
|
|
|
UINT lparam = vsc << 16;
|
|
ButtonHandle raw_button = lookup_raw_key(lparam);
|
|
if (raw_button == ButtonHandle::none()) {
|
|
continue;
|
|
}
|
|
|
|
ButtonHandle button;
|
|
if (vsc == 0x45) {
|
|
button = KeyboardButton::pause();
|
|
|
|
} else if (vsc >= 0x47 && vsc <= 0x53) {
|
|
// The numpad keys are not mapped correctly, see KB72583
|
|
button = raw_button;
|
|
|
|
} else {
|
|
if (vsc == 0x145) {
|
|
// Don't ask why - I have no idea.
|
|
vsc = 0x45;
|
|
}
|
|
if (vsc & 0x0100) {
|
|
// MapVirtualKey recognizes extended codes differently.
|
|
vsc ^= 0xe100;
|
|
}
|
|
|
|
UINT vk = MapVirtualKeyA(vsc, MAPVK_VSC_TO_VK_EX);
|
|
button = lookup_key(vk);
|
|
//if (button == ButtonHandle::none()) {
|
|
// continue;
|
|
//}
|
|
}
|
|
|
|
int len = GetKeyNameTextW(lparam, text, 256);
|
|
TextEncoder enc;
|
|
enc.set_wtext(wstring(text, len));
|
|
map->map_button(raw_button, button, enc.get_text());
|
|
}
|
|
|
|
return map;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
void WinGraphicsWindow::
|
|
handle_raw_input(HRAWINPUT hraw) {
|
|
LPBYTE lpb;
|
|
UINT dwSize;
|
|
|
|
if (hraw == 0) {
|
|
return;
|
|
}
|
|
if (GetRawInputData(hraw, RID_INPUT, NULL, &dwSize, sizeof(RAWINPUTHEADER)) == -1) {
|
|
return;
|
|
}
|
|
|
|
lpb = (LPBYTE)alloca(sizeof(LPBYTE) * dwSize);
|
|
if (lpb == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (GetRawInputData(hraw, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER)) != dwSize) {
|
|
return;
|
|
}
|
|
|
|
RAWINPUT *raw = (RAWINPUT *)lpb;
|
|
if (raw->header.hDevice == 0) {
|
|
return;
|
|
}
|
|
|
|
for (int i = 1; i < (int)(_input_devices.size()); ++i) {
|
|
if (_input_device_handle[i] == raw->header.hDevice) {
|
|
int adjx = raw->data.mouse.lLastX;
|
|
int adjy = raw->data.mouse.lLastY;
|
|
|
|
if (raw->data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) {
|
|
_input_devices[i].set_pointer_in_window(adjx, adjy);
|
|
} else {
|
|
int oldx = _input_devices[i].get_raw_pointer().get_x();
|
|
int oldy = _input_devices[i].get_raw_pointer().get_y();
|
|
_input_devices[i].set_pointer_in_window(oldx + adjx, oldy + adjy);
|
|
}
|
|
|
|
if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_DOWN) {
|
|
_input_devices[i].button_down(MouseButton::button(0), get_message_time());
|
|
}
|
|
if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_UP) {
|
|
_input_devices[i].button_up(MouseButton::button(0), get_message_time());
|
|
}
|
|
if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_2_DOWN) {
|
|
_input_devices[i].button_down(MouseButton::button(2), get_message_time());
|
|
}
|
|
if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_2_UP) {
|
|
_input_devices[i].button_up(MouseButton::button(2), get_message_time());
|
|
}
|
|
if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_3_DOWN) {
|
|
_input_devices[i].button_down(MouseButton::button(1), get_message_time());
|
|
}
|
|
if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_3_UP) {
|
|
_input_devices[i].button_up(MouseButton::button(1), get_message_time());
|
|
}
|
|
if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_DOWN) {
|
|
_input_devices[i].button_down(MouseButton::button(3), get_message_time());
|
|
}
|
|
if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_UP) {
|
|
_input_devices[i].button_up(MouseButton::button(3), get_message_time());
|
|
}
|
|
if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_DOWN) {
|
|
_input_devices[i].button_down(MouseButton::button(4), get_message_time());
|
|
}
|
|
if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_UP) {
|
|
_input_devices[i].button_up(MouseButton::button(4), get_message_time());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
bool WinGraphicsWindow::
|
|
handle_mouse_motion(int x, int y) {
|
|
_input_devices[0].set_pointer_in_window(x, y);
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
void WinGraphicsWindow::
|
|
handle_mouse_exit() {
|
|
// note: 'mouse_motion' is considered the 'entry' event
|
|
_input_devices[0].set_pointer_out_of_window();
|
|
}
|
|
|
|
/**
|
|
* Loads and returns an HICON corresponding to the indicated filename. If the
|
|
* file cannot be loaded, returns 0.
|
|
*/
|
|
HICON WinGraphicsWindow::
|
|
get_icon(const Filename &filename) {
|
|
// First, look for the unresolved filename in our index.
|
|
IconFilenames::iterator fi = _icon_filenames.find(filename);
|
|
if (fi != _icon_filenames.end()) {
|
|
return (HICON)((*fi).second);
|
|
}
|
|
|
|
// If it wasn't found, resolve the filename and search for that.
|
|
|
|
// Since we have to use a Windows call to load the image from a filename, we
|
|
// can't load a virtual file and we can't use the virtual file system.
|
|
Filename resolved = filename;
|
|
if (!resolved.resolve_filename(get_model_path())) {
|
|
// The filename doesn't exist along the search path.
|
|
if (resolved.is_fully_qualified() && resolved.exists()) {
|
|
// But it does exist locally, so accept it.
|
|
|
|
} else {
|
|
windisplay_cat.warning()
|
|
<< "Could not find icon filename " << filename << "\n";
|
|
return 0;
|
|
}
|
|
}
|
|
fi = _icon_filenames.find(resolved);
|
|
if (fi != _icon_filenames.end()) {
|
|
_icon_filenames[filename] = (*fi).second;
|
|
return (HICON)((*fi).second);
|
|
}
|
|
|
|
Filename os = resolved.to_os_specific();
|
|
|
|
HANDLE h = LoadImage(NULL, os.c_str(),
|
|
IMAGE_ICON, 0, 0, LR_LOADFROMFILE | LR_DEFAULTSIZE);
|
|
if (h == 0) {
|
|
windisplay_cat.warning()
|
|
<< "windows icon filename '" << os << "' could not be loaded!!\n";
|
|
}
|
|
|
|
_icon_filenames[filename] = h;
|
|
_icon_filenames[resolved] = h;
|
|
return (HICON)h;
|
|
}
|
|
|
|
/**
|
|
* Loads and returns an HCURSOR corresponding to the indicated filename. If
|
|
* the file cannot be loaded, returns 0.
|
|
*/
|
|
HCURSOR WinGraphicsWindow::
|
|
get_cursor(const Filename &filename) {
|
|
// The empty filename means to disable a custom cursor.
|
|
if (filename.empty()) {
|
|
return 0;
|
|
}
|
|
|
|
// First, look for the unresolved filename in our index.
|
|
IconFilenames::iterator fi = _cursor_filenames.find(filename);
|
|
if (fi != _cursor_filenames.end()) {
|
|
return (HCURSOR)((*fi).second);
|
|
}
|
|
|
|
// If it wasn't found, resolve the filename and search for that.
|
|
|
|
// Since we have to use a Windows call to load the image from a filename, we
|
|
// can't load a virtual file and we can't use the virtual file system.
|
|
Filename resolved = filename;
|
|
if (!resolved.resolve_filename(get_model_path())) {
|
|
// The filename doesn't exist.
|
|
windisplay_cat.warning()
|
|
<< "Could not find cursor filename " << filename << "\n";
|
|
return 0;
|
|
}
|
|
fi = _cursor_filenames.find(resolved);
|
|
if (fi != _cursor_filenames.end()) {
|
|
_cursor_filenames[filename] = (*fi).second;
|
|
return (HCURSOR)((*fi).second);
|
|
}
|
|
|
|
Filename os = resolved.to_os_specific();
|
|
|
|
HANDLE h = LoadImage(NULL, os.c_str(),
|
|
IMAGE_CURSOR, 0, 0, LR_LOADFROMFILE);
|
|
if (h == 0) {
|
|
windisplay_cat.warning()
|
|
<< "windows cursor filename '" << os << "' could not be loaded!!\n";
|
|
show_error_message();
|
|
}
|
|
|
|
_cursor_filenames[filename] = h;
|
|
_cursor_filenames[resolved] = h;
|
|
return (HCURSOR)h;
|
|
}
|
|
|
|
static HCURSOR get_cursor(const Filename &filename);
|
|
|
|
/**
|
|
* Registers a Window class appropriate for the indicated properties. This
|
|
* class may be shared by multiple windows.
|
|
*/
|
|
const WinGraphicsWindow::WindowClass &WinGraphicsWindow::
|
|
register_window_class(const WindowProperties &props) {
|
|
WindowClass wcreg(props);
|
|
wostringstream wclass_name;
|
|
wclass_name << L"WinGraphicsWindow" << _window_class_index;
|
|
wcreg._name = wclass_name.str();
|
|
|
|
pair<WindowClasses::iterator, bool> found = _window_classes.insert(wcreg);
|
|
const WindowClass &wclass = (*found.first);
|
|
|
|
if (!found.second) {
|
|
// We have already created a window class.
|
|
return wclass;
|
|
}
|
|
|
|
// We have not yet created this window class.
|
|
_window_class_index++;
|
|
|
|
WNDCLASSW wc;
|
|
|
|
HINSTANCE instance = GetModuleHandle(NULL);
|
|
|
|
// Clear before filling in window structure!
|
|
ZeroMemory(&wc, sizeof(wc));
|
|
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
|
|
wc.lpfnWndProc = (WNDPROC)static_window_proc;
|
|
wc.hInstance = instance;
|
|
|
|
wc.hIcon = wclass._icon;
|
|
|
|
wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
|
|
wc.lpszMenuName = NULL;
|
|
wc.lpszClassName = wclass._name.c_str();
|
|
|
|
if (!RegisterClassW(&wc)) {
|
|
windisplay_cat.error()
|
|
<< "could not register window class " << wclass._name << "!" << endl;
|
|
return wclass;
|
|
}
|
|
|
|
return wclass;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
WinGraphicsWindow::WinWindowHandle::
|
|
WinWindowHandle(WinGraphicsWindow *window, const WindowHandle ©) :
|
|
WindowHandle(copy),
|
|
_window(window)
|
|
{
|
|
}
|
|
|
|
/**
|
|
* Should be called by the WinGraphicsWindow's destructor, so that we don't
|
|
* end up with a floating pointer should this object persist beyond the
|
|
* lifespan of its window.
|
|
*/
|
|
void WinGraphicsWindow::WinWindowHandle::
|
|
clear_window() {
|
|
_window = NULL;
|
|
}
|
|
|
|
/**
|
|
* Called on a child handle to deliver a keyboard button event generated in
|
|
* the parent window.
|
|
*/
|
|
void WinGraphicsWindow::WinWindowHandle::
|
|
receive_windows_message(unsigned int msg, int wparam, int lparam) {
|
|
if (_window != NULL) {
|
|
_window->receive_windows_message(msg, wparam, lparam);
|
|
}
|
|
}
|
|
|
|
|
|
// pops up MsgBox wsystem error msg
|
|
void PrintErrorMessage(DWORD msgID) {
|
|
LPTSTR pMessageBuffer;
|
|
|
|
if (msgID==PRINT_LAST_ERROR)
|
|
msgID=GetLastError();
|
|
|
|
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
|
|
NULL,msgID,
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), //The user default language
|
|
(LPTSTR) &pMessageBuffer, // the weird ptrptr->ptr cast is intentional, see FORMAT_MESSAGE_ALLOCATE_BUFFER
|
|
1024, NULL);
|
|
MessageBox(GetDesktopWindow(),pMessageBuffer,_T(errorbox_title),MB_OK);
|
|
windisplay_cat.fatal() << "System error msg: " << pMessageBuffer << endl;
|
|
LocalFree( pMessageBuffer );
|
|
}
|
|
|
|
void
|
|
ClearToBlack(HWND hWnd, const WindowProperties &props) {
|
|
if (!props.has_origin()) {
|
|
if (windisplay_cat.is_debug()) {
|
|
windisplay_cat.debug()
|
|
<< "Skipping ClearToBlack, no origin specified yet.\n";
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (windisplay_cat.is_debug()) {
|
|
windisplay_cat.debug()
|
|
<< "ClearToBlack(" << hWnd << ", " << props << ")\n";
|
|
}
|
|
// clear to black
|
|
HDC hDC=GetDC(hWnd); // GetDC is not particularly fast. if this needs to be super-quick, we should cache GetDC's hDC
|
|
RECT clrRect = {
|
|
props.get_x_origin(), props.get_y_origin(),
|
|
props.get_x_origin() + props.get_x_size(),
|
|
props.get_y_origin() + props.get_y_size()
|
|
};
|
|
FillRect(hDC,&clrRect,(HBRUSH)GetStockObject(BLACK_BRUSH));
|
|
ReleaseDC(hWnd,hDC);
|
|
GdiFlush();
|
|
}
|
|
|
|
/**
|
|
* Fills view_rect with the coordinates of the client area of the indicated
|
|
* window, converted to screen coordinates.
|
|
*/
|
|
void get_client_rect_screen(HWND hwnd, RECT *view_rect) {
|
|
GetClientRect(hwnd, view_rect);
|
|
|
|
POINT ul, lr;
|
|
ul.x = view_rect->left;
|
|
ul.y = view_rect->top;
|
|
lr.x = view_rect->right;
|
|
lr.y = view_rect->bottom;
|
|
|
|
ClientToScreen(hwnd, &ul);
|
|
ClientToScreen(hwnd, &lr);
|
|
|
|
view_rect->left = ul.x;
|
|
view_rect->top = ul.y;
|
|
view_rect->right = lr.x;
|
|
view_rect->bottom = lr.y;
|
|
}
|
|
|
|
/**
|
|
* Adds the specified Windows proc event handler to be called whenever a
|
|
* Windows event occurs.
|
|
*
|
|
*/
|
|
void WinGraphicsWindow::add_window_proc( const GraphicsWindowProc* wnd_proc ){
|
|
nassertv(wnd_proc != NULL);
|
|
_window_proc_classes.insert( (GraphicsWindowProc*)wnd_proc );
|
|
}
|
|
|
|
/**
|
|
* Removes the specified Windows proc event handler.
|
|
*
|
|
*/
|
|
void WinGraphicsWindow::remove_window_proc( const GraphicsWindowProc* wnd_proc ){
|
|
nassertv(wnd_proc != NULL);
|
|
_window_proc_classes.erase( (GraphicsWindowProc*)wnd_proc );
|
|
}
|
|
|
|
/**
|
|
* Removes all Windows proc event handlers.
|
|
*
|
|
*/
|
|
void WinGraphicsWindow::clear_window_procs(){
|
|
_window_proc_classes.clear();
|
|
}
|
|
|
|
/**
|
|
* Returns whether this window supports adding of windows proc handlers.
|
|
*
|
|
*/
|
|
bool WinGraphicsWindow::supports_window_procs() const{
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Returns whether the specified event msg is a touch message.
|
|
*
|
|
*/
|
|
bool WinGraphicsWindow::
|
|
is_touch_event(GraphicsWindowProcCallbackData *callbackData) {
|
|
return callbackData->get_msg() == WM_TOUCH;
|
|
}
|
|
|
|
/**
|
|
* Returns the current number of touches on this window.
|
|
*
|
|
*/
|
|
int WinGraphicsWindow::
|
|
get_num_touches(){
|
|
return _num_touches;
|
|
}
|
|
|
|
/**
|
|
* Returns the TouchInfo object describing the specified touch.
|
|
*
|
|
*/
|
|
TouchInfo WinGraphicsWindow::
|
|
get_touch_info(int index) {
|
|
nassertr(index >= 0 && index < MAX_TOUCHES, TouchInfo());
|
|
|
|
TOUCHINPUT ti = _touches[index];
|
|
POINT point;
|
|
point.x = TOUCH_COORD_TO_PIXEL(ti.x);
|
|
point.y = TOUCH_COORD_TO_PIXEL(ti.y);
|
|
ScreenToClient(_hWnd, &point);
|
|
|
|
TouchInfo ret = TouchInfo();
|
|
ret.set_x(point.x);
|
|
ret.set_y(point.y);
|
|
ret.set_id(ti.dwID);
|
|
ret.set_flags(ti.dwFlags);
|
|
return ret;
|
|
}
|