// // Copyright (c) 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 "windows/win32/Win32Window.h" #include 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((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(lParam); SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast(pCreateStruct->lpCreateParams)); return DefWindowProcA(hWnd, message, wParam, lParam); } } Win32Window *window = reinterpret_cast(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(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(LOWORD(lParam)); event.mouseButton.y = static_cast(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(LOWORD(lParam)); event.mouseButton.y = static_cast(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(LOWORD(lParam)); event.mouseButton.y = static_cast(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(LOWORD(lParam)); event.mouseButton.y = static_cast(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(LOWORD(lParam)); event.mouseButton.y = static_cast(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(LOWORD(lParam)); event.mouseButton.y = static_cast(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(LOWORD(lParam)); event.mouseButton.y = static_cast(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(LOWORD(lParam)); event.mouseButton.y = static_cast(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(LOWORD(lParam)); int mouseY = static_cast(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), mSetVisibleTimer(CreateTimer()), mIsMouseInWindow(false), mNativeWindow(0), mParentWindow(0), mNativeDisplay(0) { } Win32Window::~Win32Window() { destroy(); delete mSetVisibleTimer; } bool Win32Window::initialize(const std::string &name, size_t width, size_t 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; RECT sizeRect = {0, 0, static_cast(width), static_cast(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(width), static_cast(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((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), 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; } EGLNativeWindowType Win32Window::getNativeWindow() const { return mNativeWindow; } EGLNativeDisplayType Win32Window::getNativeDisplay() const { return mNativeDisplay; } void* Win32Window::getFramebufferNativeWindow() const { return mNativeWindow; } 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); } OSWindow *CreateOSWindow() { return new Win32Window(); } 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); }