blob: c422915fc2f4c5a5a105fd31fc0bf5aae378bc4b [file] [log] [blame]
//
// Copyright 2014 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Win32Window.cpp: Implementation of OSWindow for Win32 (Windows)
#include "util/windows/win32/Win32Window.h"
#include <sstream>
#include "common/debug.h"
Key VirtualKeyCodeToKey(WPARAM key, LPARAM flags)
{
switch (key)
{
// Check the scancode to distinguish between left and right shift
case VK_SHIFT:
{
static unsigned int lShift = MapVirtualKey(VK_LSHIFT, MAPVK_VK_TO_VSC);
unsigned int scancode = static_cast<unsigned int>((flags & (0xFF << 16)) >> 16);
return scancode == lShift ? KEY_LSHIFT : KEY_RSHIFT;
}
// Check the "extended" flag to distinguish between left and right alt
case VK_MENU:
return (HIWORD(flags) & KF_EXTENDED) ? KEY_RALT : KEY_LALT;
// Check the "extended" flag to distinguish between left and right control
case VK_CONTROL:
return (HIWORD(flags) & KF_EXTENDED) ? KEY_RCONTROL : KEY_LCONTROL;
// Other keys are reported properly
case VK_LWIN:
return KEY_LSYSTEM;
case VK_RWIN:
return KEY_RSYSTEM;
case VK_APPS:
return KEY_MENU;
case VK_OEM_1:
return KEY_SEMICOLON;
case VK_OEM_2:
return KEY_SLASH;
case VK_OEM_PLUS:
return KEY_EQUAL;
case VK_OEM_MINUS:
return KEY_DASH;
case VK_OEM_4:
return KEY_LBRACKET;
case VK_OEM_6:
return KEY_RBRACKET;
case VK_OEM_COMMA:
return KEY_COMMA;
case VK_OEM_PERIOD:
return KEY_PERIOD;
case VK_OEM_7:
return KEY_QUOTE;
case VK_OEM_5:
return KEY_BACKSLASH;
case VK_OEM_3:
return KEY_TILDE;
case VK_ESCAPE:
return KEY_ESCAPE;
case VK_SPACE:
return KEY_SPACE;
case VK_RETURN:
return KEY_RETURN;
case VK_BACK:
return KEY_BACK;
case VK_TAB:
return KEY_TAB;
case VK_PRIOR:
return KEY_PAGEUP;
case VK_NEXT:
return KEY_PAGEDOWN;
case VK_END:
return KEY_END;
case VK_HOME:
return KEY_HOME;
case VK_INSERT:
return KEY_INSERT;
case VK_DELETE:
return KEY_DELETE;
case VK_ADD:
return KEY_ADD;
case VK_SUBTRACT:
return KEY_SUBTRACT;
case VK_MULTIPLY:
return KEY_MULTIPLY;
case VK_DIVIDE:
return KEY_DIVIDE;
case VK_PAUSE:
return KEY_PAUSE;
case VK_F1:
return KEY_F1;
case VK_F2:
return KEY_F2;
case VK_F3:
return KEY_F3;
case VK_F4:
return KEY_F4;
case VK_F5:
return KEY_F5;
case VK_F6:
return KEY_F6;
case VK_F7:
return KEY_F7;
case VK_F8:
return KEY_F8;
case VK_F9:
return KEY_F9;
case VK_F10:
return KEY_F10;
case VK_F11:
return KEY_F11;
case VK_F12:
return KEY_F12;
case VK_F13:
return KEY_F13;
case VK_F14:
return KEY_F14;
case VK_F15:
return KEY_F15;
case VK_LEFT:
return KEY_LEFT;
case VK_RIGHT:
return KEY_RIGHT;
case VK_UP:
return KEY_UP;
case VK_DOWN:
return KEY_DOWN;
case VK_NUMPAD0:
return KEY_NUMPAD0;
case VK_NUMPAD1:
return KEY_NUMPAD1;
case VK_NUMPAD2:
return KEY_NUMPAD2;
case VK_NUMPAD3:
return KEY_NUMPAD3;
case VK_NUMPAD4:
return KEY_NUMPAD4;
case VK_NUMPAD5:
return KEY_NUMPAD5;
case VK_NUMPAD6:
return KEY_NUMPAD6;
case VK_NUMPAD7:
return KEY_NUMPAD7;
case VK_NUMPAD8:
return KEY_NUMPAD8;
case VK_NUMPAD9:
return KEY_NUMPAD9;
case 'A':
return KEY_A;
case 'Z':
return KEY_Z;
case 'E':
return KEY_E;
case 'R':
return KEY_R;
case 'T':
return KEY_T;
case 'Y':
return KEY_Y;
case 'U':
return KEY_U;
case 'I':
return KEY_I;
case 'O':
return KEY_O;
case 'P':
return KEY_P;
case 'Q':
return KEY_Q;
case 'S':
return KEY_S;
case 'D':
return KEY_D;
case 'F':
return KEY_F;
case 'G':
return KEY_G;
case 'H':
return KEY_H;
case 'J':
return KEY_J;
case 'K':
return KEY_K;
case 'L':
return KEY_L;
case 'M':
return KEY_M;
case 'W':
return KEY_W;
case 'X':
return KEY_X;
case 'C':
return KEY_C;
case 'V':
return KEY_V;
case 'B':
return KEY_B;
case 'N':
return KEY_N;
case '0':
return KEY_NUM0;
case '1':
return KEY_NUM1;
case '2':
return KEY_NUM2;
case '3':
return KEY_NUM3;
case '4':
return KEY_NUM4;
case '5':
return KEY_NUM5;
case '6':
return KEY_NUM6;
case '7':
return KEY_NUM7;
case '8':
return KEY_NUM8;
case '9':
return KEY_NUM9;
}
return Key(0);
}
LRESULT CALLBACK Win32Window::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_NCCREATE:
{
LPCREATESTRUCT pCreateStruct = reinterpret_cast<LPCREATESTRUCT>(lParam);
SetWindowLongPtr(hWnd, GWLP_USERDATA,
reinterpret_cast<LONG_PTR>(pCreateStruct->lpCreateParams));
return DefWindowProcA(hWnd, message, wParam, lParam);
}
}
Win32Window *window = reinterpret_cast<Win32Window *>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
if (window)
{
switch (message)
{
case WM_DESTROY:
case WM_CLOSE:
{
Event event;
event.Type = Event::EVENT_CLOSED;
window->pushEvent(event);
break;
}
case WM_MOVE:
{
RECT winRect;
GetClientRect(hWnd, &winRect);
POINT topLeft;
topLeft.x = winRect.left;
topLeft.y = winRect.top;
ClientToScreen(hWnd, &topLeft);
Event event;
event.Type = Event::EVENT_MOVED;
event.Move.X = topLeft.x;
event.Move.Y = topLeft.y;
window->pushEvent(event);
break;
}
case WM_SIZE:
{
RECT winRect;
GetClientRect(hWnd, &winRect);
POINT topLeft;
topLeft.x = winRect.left;
topLeft.y = winRect.top;
ClientToScreen(hWnd, &topLeft);
POINT botRight;
botRight.x = winRect.right;
botRight.y = winRect.bottom;
ClientToScreen(hWnd, &botRight);
Event event;
event.Type = Event::EVENT_RESIZED;
event.Size.Width = botRight.x - topLeft.x;
event.Size.Height = botRight.y - topLeft.y;
window->pushEvent(event);
break;
}
case WM_SETFOCUS:
{
Event event;
event.Type = Event::EVENT_GAINED_FOCUS;
window->pushEvent(event);
break;
}
case WM_KILLFOCUS:
{
Event event;
event.Type = Event::EVENT_LOST_FOCUS;
window->pushEvent(event);
break;
}
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
case WM_KEYUP:
case WM_SYSKEYUP:
{
bool down = (message == WM_KEYDOWN || message == WM_SYSKEYDOWN);
Event event;
event.Type = down ? Event::EVENT_KEY_PRESSED : Event::EVENT_KEY_RELEASED;
event.Key.Alt = HIWORD(GetAsyncKeyState(VK_MENU)) != 0;
event.Key.Control = HIWORD(GetAsyncKeyState(VK_CONTROL)) != 0;
event.Key.Shift = HIWORD(GetAsyncKeyState(VK_SHIFT)) != 0;
event.Key.System =
HIWORD(GetAsyncKeyState(VK_LWIN)) || HIWORD(GetAsyncKeyState(VK_RWIN));
event.Key.Code = VirtualKeyCodeToKey(wParam, lParam);
window->pushEvent(event);
break;
}
case WM_MOUSEWHEEL:
{
Event event;
event.Type = Event::EVENT_MOUSE_WHEEL_MOVED;
event.MouseWheel.Delta = static_cast<short>(HIWORD(wParam)) / 120;
window->pushEvent(event);
break;
}
case WM_LBUTTONDOWN:
case WM_LBUTTONDBLCLK:
{
Event event;
event.Type = Event::EVENT_MOUSE_BUTTON_PRESSED;
event.MouseButton.Button = MOUSEBUTTON_LEFT;
event.MouseButton.X = static_cast<short>(LOWORD(lParam));
event.MouseButton.Y = static_cast<short>(HIWORD(lParam));
window->pushEvent(event);
break;
}
case WM_LBUTTONUP:
{
Event event;
event.Type = Event::EVENT_MOUSE_BUTTON_RELEASED;
event.MouseButton.Button = MOUSEBUTTON_LEFT;
event.MouseButton.X = static_cast<short>(LOWORD(lParam));
event.MouseButton.Y = static_cast<short>(HIWORD(lParam));
window->pushEvent(event);
break;
}
case WM_RBUTTONDOWN:
case WM_RBUTTONDBLCLK:
{
Event event;
event.Type = Event::EVENT_MOUSE_BUTTON_PRESSED;
event.MouseButton.Button = MOUSEBUTTON_RIGHT;
event.MouseButton.X = static_cast<short>(LOWORD(lParam));
event.MouseButton.Y = static_cast<short>(HIWORD(lParam));
window->pushEvent(event);
break;
}
// Mouse right button up event
case WM_RBUTTONUP:
{
Event event;
event.Type = Event::EVENT_MOUSE_BUTTON_RELEASED;
event.MouseButton.Button = MOUSEBUTTON_RIGHT;
event.MouseButton.X = static_cast<short>(LOWORD(lParam));
event.MouseButton.Y = static_cast<short>(HIWORD(lParam));
window->pushEvent(event);
break;
}
// Mouse wheel button down event
case WM_MBUTTONDOWN:
case WM_MBUTTONDBLCLK:
{
Event event;
event.Type = Event::EVENT_MOUSE_BUTTON_PRESSED;
event.MouseButton.Button = MOUSEBUTTON_MIDDLE;
event.MouseButton.X = static_cast<short>(LOWORD(lParam));
event.MouseButton.Y = static_cast<short>(HIWORD(lParam));
window->pushEvent(event);
break;
}
// Mouse wheel button up event
case WM_MBUTTONUP:
{
Event event;
event.Type = Event::EVENT_MOUSE_BUTTON_RELEASED;
event.MouseButton.Button = MOUSEBUTTON_MIDDLE;
event.MouseButton.X = static_cast<short>(LOWORD(lParam));
event.MouseButton.Y = static_cast<short>(HIWORD(lParam));
window->pushEvent(event);
break;
}
// Mouse X button down event
case WM_XBUTTONDOWN:
case WM_XBUTTONDBLCLK:
{
Event event;
event.Type = Event::EVENT_MOUSE_BUTTON_PRESSED;
event.MouseButton.Button =
(HIWORD(wParam) == XBUTTON1) ? MOUSEBUTTON_BUTTON4 : MOUSEBUTTON_BUTTON5;
event.MouseButton.X = static_cast<short>(LOWORD(lParam));
event.MouseButton.Y = static_cast<short>(HIWORD(lParam));
window->pushEvent(event);
break;
}
// Mouse X button up event
case WM_XBUTTONUP:
{
Event event;
event.Type = Event::EVENT_MOUSE_BUTTON_RELEASED;
event.MouseButton.Button =
(HIWORD(wParam) == XBUTTON1) ? MOUSEBUTTON_BUTTON4 : MOUSEBUTTON_BUTTON5;
event.MouseButton.X = static_cast<short>(LOWORD(lParam));
event.MouseButton.Y = static_cast<short>(HIWORD(lParam));
window->pushEvent(event);
break;
}
case WM_MOUSEMOVE:
{
if (!window->mIsMouseInWindow)
{
window->mIsMouseInWindow = true;
Event event;
event.Type = Event::EVENT_MOUSE_ENTERED;
window->pushEvent(event);
}
int mouseX = static_cast<short>(LOWORD(lParam));
int mouseY = static_cast<short>(HIWORD(lParam));
Event event;
event.Type = Event::EVENT_MOUSE_MOVED;
event.MouseMove.X = mouseX;
event.MouseMove.Y = mouseY;
window->pushEvent(event);
break;
}
case WM_MOUSELEAVE:
{
Event event;
event.Type = Event::EVENT_MOUSE_LEFT;
window->pushEvent(event);
window->mIsMouseInWindow = false;
break;
}
case WM_USER:
{
Event testEvent;
testEvent.Type = Event::EVENT_TEST;
window->pushEvent(testEvent);
break;
}
}
}
return DefWindowProcA(hWnd, message, wParam, lParam);
}
Win32Window::Win32Window()
: mIsVisible(false),
mIsMouseInWindow(false),
mNativeWindow(0),
mParentWindow(0),
mNativeDisplay(0)
{}
Win32Window::~Win32Window()
{
destroy();
}
bool Win32Window::initialize(const std::string &name, int width, int height)
{
destroy();
// Use a new window class name for ever window to ensure that a new window can be created
// even if the last one was not properly destroyed
static size_t windowIdx = 0;
std::ostringstream nameStream;
nameStream << name << "_" << windowIdx++;
mParentClassName = nameStream.str();
mChildClassName = mParentClassName + "_Child";
// Work around compile error from not defining "UNICODE" while Chromium does
const LPSTR idcArrow = MAKEINTRESOURCEA(32512);
WNDCLASSEXA parentWindowClass = {0};
parentWindowClass.cbSize = sizeof(WNDCLASSEXA);
parentWindowClass.style = 0;
parentWindowClass.lpfnWndProc = WndProc;
parentWindowClass.cbClsExtra = 0;
parentWindowClass.cbWndExtra = 0;
parentWindowClass.hInstance = GetModuleHandle(nullptr);
parentWindowClass.hIcon = nullptr;
parentWindowClass.hCursor = LoadCursorA(nullptr, idcArrow);
parentWindowClass.hbrBackground = 0;
parentWindowClass.lpszMenuName = nullptr;
parentWindowClass.lpszClassName = mParentClassName.c_str();
if (!RegisterClassExA(&parentWindowClass))
{
return false;
}
WNDCLASSEXA childWindowClass = {0};
childWindowClass.cbSize = sizeof(WNDCLASSEXA);
childWindowClass.style = CS_OWNDC;
childWindowClass.lpfnWndProc = WndProc;
childWindowClass.cbClsExtra = 0;
childWindowClass.cbWndExtra = 0;
childWindowClass.hInstance = GetModuleHandle(nullptr);
childWindowClass.hIcon = nullptr;
childWindowClass.hCursor = LoadCursorA(nullptr, idcArrow);
childWindowClass.hbrBackground = 0;
childWindowClass.lpszMenuName = nullptr;
childWindowClass.lpszClassName = mChildClassName.c_str();
if (!RegisterClassExA(&childWindowClass))
{
return false;
}
DWORD parentStyle = WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU;
DWORD parentExtendedStyle = WS_EX_APPWINDOW | WS_EX_TOOLWINDOW;
RECT sizeRect = {0, 0, static_cast<LONG>(width), static_cast<LONG>(height)};
AdjustWindowRectEx(&sizeRect, parentStyle, FALSE, parentExtendedStyle);
mParentWindow = CreateWindowExA(parentExtendedStyle, mParentClassName.c_str(), name.c_str(),
parentStyle, CW_USEDEFAULT, CW_USEDEFAULT,
sizeRect.right - sizeRect.left, sizeRect.bottom - sizeRect.top,
nullptr, nullptr, GetModuleHandle(nullptr), this);
mNativeWindow = CreateWindowExA(0, mChildClassName.c_str(), name.c_str(), WS_CHILD, 0, 0,
static_cast<int>(width), static_cast<int>(height),
mParentWindow, nullptr, GetModuleHandle(nullptr), this);
mNativeDisplay = GetDC(mNativeWindow);
if (!mNativeDisplay)
{
destroy();
return false;
}
return true;
}
void Win32Window::destroy()
{
if (mNativeDisplay)
{
ReleaseDC(mNativeWindow, mNativeDisplay);
mNativeDisplay = 0;
}
if (mNativeWindow)
{
DestroyWindow(mNativeWindow);
mNativeWindow = 0;
}
if (mParentWindow)
{
DestroyWindow(mParentWindow);
mParentWindow = 0;
}
UnregisterClassA(mParentClassName.c_str(), nullptr);
UnregisterClassA(mChildClassName.c_str(), nullptr);
}
bool Win32Window::takeScreenshot(uint8_t *pixelData)
{
if (mIsVisible)
{
return false;
}
bool error = false;
// Hack for DWM: There is no way to wait for DWM animations to finish, so we just have to wait
// for a while before issuing screenshot if window was just made visible.
{
static const double WAIT_WINDOW_VISIBLE_MS = 0.5; // Half a second for the animation
double timeSinceVisible = mSetVisibleTimer.getElapsedTime();
if (timeSinceVisible < WAIT_WINDOW_VISIBLE_MS)
{
Sleep(static_cast<DWORD>((WAIT_WINDOW_VISIBLE_MS - timeSinceVisible) * 1000));
}
}
HDC screenDC = nullptr;
HDC windowDC = nullptr;
HDC tmpDC = nullptr;
HBITMAP tmpBitmap = nullptr;
if (!error)
{
screenDC = GetDC(HWND_DESKTOP);
error = screenDC == nullptr;
}
if (!error)
{
windowDC = GetDC(mNativeWindow);
error = windowDC == nullptr;
}
if (!error)
{
tmpDC = CreateCompatibleDC(screenDC);
error = tmpDC == nullptr;
}
if (!error)
{
tmpBitmap = CreateCompatibleBitmap(screenDC, mWidth, mHeight);
error = tmpBitmap == nullptr;
}
POINT topLeft = {0, 0};
if (!error)
{
error = (MapWindowPoints(mNativeWindow, HWND_DESKTOP, &topLeft, 1) == 0);
}
if (!error)
{
error = SelectObject(tmpDC, tmpBitmap) == nullptr;
}
if (!error)
{
error = BitBlt(tmpDC, 0, 0, mWidth, mHeight, screenDC, topLeft.x, topLeft.y, SRCCOPY) == 0;
}
if (!error)
{
BITMAPINFOHEADER bitmapInfo;
bitmapInfo.biSize = sizeof(BITMAPINFOHEADER);
bitmapInfo.biWidth = mWidth;
bitmapInfo.biHeight = -mHeight;
bitmapInfo.biPlanes = 1;
bitmapInfo.biBitCount = 32;
bitmapInfo.biCompression = BI_RGB;
bitmapInfo.biSizeImage = 0;
bitmapInfo.biXPelsPerMeter = 0;
bitmapInfo.biYPelsPerMeter = 0;
bitmapInfo.biClrUsed = 0;
bitmapInfo.biClrImportant = 0;
int getBitsResult = GetDIBits(screenDC, tmpBitmap, 0, mHeight, pixelData,
reinterpret_cast<BITMAPINFO *>(&bitmapInfo), DIB_RGB_COLORS);
error = (getBitsResult == 0);
}
if (tmpBitmap != nullptr)
{
DeleteObject(tmpBitmap);
}
if (tmpDC != nullptr)
{
DeleteDC(tmpDC);
}
if (screenDC != nullptr)
{
ReleaseDC(nullptr, screenDC);
}
if (windowDC != nullptr)
{
ReleaseDC(mNativeWindow, windowDC);
}
return !error;
}
void Win32Window::resetNativeWindow() {}
EGLNativeWindowType Win32Window::getNativeWindow() const
{
return mNativeWindow;
}
EGLNativeDisplayType Win32Window::getNativeDisplay() const
{
return mNativeDisplay;
}
void Win32Window::messageLoop()
{
MSG msg;
while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
void Win32Window::setMousePosition(int x, int y)
{
RECT winRect;
GetClientRect(mNativeWindow, &winRect);
POINT topLeft;
topLeft.x = winRect.left;
topLeft.y = winRect.top;
ClientToScreen(mNativeWindow, &topLeft);
SetCursorPos(topLeft.x + x, topLeft.y + y);
}
bool Win32Window::setPosition(int x, int y)
{
if (mX == x && mY == y)
{
return true;
}
RECT windowRect;
if (!GetWindowRect(mParentWindow, &windowRect))
{
return false;
}
if (!MoveWindow(mParentWindow, x, y, windowRect.right - windowRect.left,
windowRect.bottom - windowRect.top, TRUE))
{
return false;
}
return true;
}
bool Win32Window::resize(int width, int height)
{
if (width == mWidth && height == mHeight)
{
return true;
}
RECT windowRect;
if (!GetWindowRect(mParentWindow, &windowRect))
{
return false;
}
RECT clientRect;
if (!GetClientRect(mParentWindow, &clientRect))
{
return false;
}
LONG diffX = (windowRect.right - windowRect.left) - clientRect.right;
LONG diffY = (windowRect.bottom - windowRect.top) - clientRect.bottom;
if (!MoveWindow(mParentWindow, windowRect.left, windowRect.top, width + diffX, height + diffY,
TRUE))
{
return false;
}
if (!MoveWindow(mNativeWindow, 0, 0, width, height, FALSE))
{
return false;
}
return true;
}
void Win32Window::setVisible(bool isVisible)
{
int flag = (isVisible ? SW_SHOW : SW_HIDE);
ShowWindow(mParentWindow, flag);
ShowWindow(mNativeWindow, flag);
if (isVisible)
{
mSetVisibleTimer.stop();
mSetVisibleTimer.start();
}
}
void Win32Window::pushEvent(Event event)
{
OSWindow::pushEvent(event);
switch (event.Type)
{
case Event::EVENT_RESIZED:
MoveWindow(mNativeWindow, 0, 0, mWidth, mHeight, FALSE);
break;
default:
break;
}
}
void Win32Window::signalTestEvent()
{
PostMessage(mNativeWindow, WM_USER, 0, 0);
}
// static
OSWindow *OSWindow::New()
{
return new Win32Window();
}