#include #include #include #include "Win32Application.h" #include "Win32Internal.h" #include "Win32Display.h" namespace sp { #define WND_CLASSNAME "SPECTRE_WIN32_WNDCLASS" #if defined(_WIN64) && !defined(GWL_USERDATA) // x64 undefines this. So we define it again. #define GWL_USERDATA GWLP_USERDATA #endif /* defined(_WIN64) && !defined(GWL_USERDATA) */ static bool firstTime = true; Win32Display::Win32Display() : m_handle (NULL), m_icon (0), m_cursor (0), m_inResizeModalLoop (false), m_minSize (200, 200) { } Win32Display::~Win32Display() { if (m_icon) { DestroyIcon(m_icon); } if (m_handle) { DestroyWindow(m_handle); } } bool Win32Display::create(DisplayDescription description) { DWORD flags; Vector2i pos; if (firstTime) { registerClass(); firstTime = false; } // Set window to center and set decoration flags. pos = centerWindow(description.mode.width, description.mode.height); flags = getWin32Flags(description.decoration); // Create window. m_handle = CreateWindowExA(0, WND_CLASSNAME, "", flags, pos.x, pos.y, description.mode.width, description.mode.height, NULL, NULL, GetModuleHandle(NULL), (LPVOID) this); if (!m_handle) { Log::error("Win32 - Could not create window: %s", Win32GetMessage(GetLastError())); return false; } setSize(description.mode.width, description.mode.height); // Store the size for use later. m_size = getSize(); return true; } void Win32Display::destroy() { if (m_handle) { DestroyWindow(m_handle); m_handle = NULL; } } void* Win32Display::getHandle() const { return m_handle; } bool Win32Display::isValid() { return m_handle != NULL; } void Win32Display::setSize(unsigned int width, unsigned int height) { int w, h; RECT rect = {0, 0, (LONG) width, (LONG) height}; AdjustWindowRect(&rect, GetWindowLong(m_handle, GWL_STYLE), false); w = rect.right - rect.left; h = rect.bottom - rect.top; ::SetWindowPos(m_handle, NULL, 0, 0, w, h, SWP_NOMOVE | SWP_NOZORDER); } Vector2u Win32Display::getSize() const { RECT rect; Vector2u size; if (GetClientRect(m_handle, &rect)) { size.x = rect.right - rect.left; size.y = rect.bottom - rect.top; } return size; } void Win32Display::setPosition(unsigned int x, unsigned int y) { ::SetWindowPos(m_handle, NULL, x, y, 0, 0, SWP_NOSIZE); } Vector2u Win32Display::getPosition() const { RECT r; GetWindowRect(m_handle, &r); return Vector2u(r.left, r.top); } void Win32Display::setVisible(bool visible) { ::ShowWindow(m_handle, visible ? SW_SHOW : SW_HIDE); } void Win32Display::setDecoration(unsigned decoration) { ::SetWindowLong(m_handle, GWL_STYLE, getWin32Flags(decoration)); } void Win32Display::minimize() { ::ShowWindow(m_handle, SW_MINIMIZE); } void Win32Display::maximize() { ::ShowWindow(m_handle, SW_MAXIMIZE); } void Win32Display::setCaption(const std::string& caption) { ::SetWindowText(m_handle, caption.c_str()); } void Win32Display::showCursor(bool value) { if (value) { m_cursor = ::LoadCursor(NULL, IDC_ARROW); } else { m_cursor = 0; } ::SetCursor(m_cursor); } void Win32Display::grabCursor(bool value) { if (value) { RECT rect; GetClientRect(m_handle, &rect); MapWindowPoints(m_handle, NULL, reinterpret_cast(&rect), 2); ClipCursor(&rect); } else { ClipCursor(NULL); } } void Win32Display::setIcon(unsigned int width, unsigned int height, const uint8_t *pixels) { ::HDC hdc; ::ICONINFO ii; ::BITMAPV5HEADER bi; ::HBITMAP bmp_color, bmp_mask; unsigned char *bmp_data = NULL; ::ZeroMemory(&bi, sizeof(bi)); bi.bV5Size = sizeof(bi); bi.bV5Width = width; bi.bV5Height = height; bi.bV5Planes = 1; bi.bV5BitCount = 32; bi.bV5Compression = BI_RGB; bi.bV5CSType = LCS_WINDOWS_COLOR_SPACE; bi.bV5Intent = LCS_GM_IMAGES; hdc = ::GetDC(NULL); bmp_color = ::CreateDIBSection(hdc, reinterpret_cast(&bi), DIB_RGB_COLORS, (void **) &bmp_data, NULL, 0); ::ReleaseDC(NULL, hdc); if (!bmp_color) { Log::error("Win32Display::setIcon() - Failed to create RGBA bitmap"); return; } bmp_mask = CreateBitmap(width, height, 1, 1, NULL); if (!bmp_mask) { Log::error("Win32Display::setIcon() - Failed to create mask bitmap"); ::DeleteObject(bmp_color); return; } // pixels are always RGBA, WinApi wants BGRA for(int i = 0; i < width * height; i++) { bmp_data[i * 4 + 0] = pixels[i * 4 + 2]; // R <- B bmp_data[i * 4 + 1] = pixels[i * 4 + 1]; // G <- G bmp_data[i * 4 + 2] = pixels[i * 4 + 0]; // B <- R bmp_data[i * 4 + 3] = pixels[i * 4 + 3]; // A <- A } ::ZeroMemory(&ii, sizeof(ii)); ii.fIcon = TRUE; ii.xHotspot = 0; ii.yHotspot = 0; ii.hbmColor = bmp_color; ii.hbmMask = bmp_mask; if (m_icon) { ::DestroyIcon(m_icon); } m_icon = CreateIconIndirect(&ii); if (m_icon) { ::SendMessage(m_handle, WM_SETICON, ICON_SMALL, (LPARAM) m_icon); ::SendMessage(m_handle, WM_SETICON, ICON_BIG, (LPARAM) m_icon); } else { Log::error("Win32Display::setIcon() - Failed to create icon"); } ::DeleteObject(bmp_color); ::DeleteObject(bmp_mask); } void Win32Display::registerClass() { WNDCLASSA wndcl; ZeroMemory(&wndcl, sizeof(wndcl)); wndcl.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW; wndcl.lpfnWndProc = Win32Display::staticWndProc; wndcl.hInstance = ::GetModuleHandle(NULL); wndcl.lpszClassName = WND_CLASSNAME; ::RegisterClass(&wndcl); } DWORD Win32Display::getWin32Flags(unsigned int flags) { DWORD win32_flags = WS_VISIBLE; if (flags == DisplayDecorate::Empty) { win32_flags |= WS_POPUP; } else { if (flags & DisplayDecorate::Menu) { win32_flags |= WS_CAPTION | WS_MINIMIZEBOX; } if (flags & DisplayDecorate::Resize) { win32_flags |= WS_THICKFRAME | WS_MAXIMIZEBOX; } if (flags & DisplayDecorate::Close) { win32_flags |= WS_SYSMENU; } } return win32_flags; } Vector2i Win32Display::centerWindow(int width, int height) { Vector2i v; v.x = (::GetSystemMetrics(SM_CXSCREEN) - width) / 2; v.y = (::GetSystemMetrics(SM_CYSCREEN) - height) / 2; return v; } void Win32Display::enterFullscreen(DisplayMode mode) { LONG rc; ::DEVMODEW dev; dev.dmSize = sizeof(dev); dev.dmPelsWidth = mode.width; dev.dmPelsHeight = mode.height; dev.dmBitsPerPel = mode.bpp; dev.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL; rc = ::ChangeDisplaySettingsW(&dev, CDS_FULLSCREEN); if (rc != DISP_CHANGE_SUCCESSFUL) { const char *msg; switch(rc) { case DISP_CHANGE_BADDUALVIEW : msg = "The system is DualView capable"; break; case DISP_CHANGE_BADFLAGS : msg = "Invalid flags given"; break; case DISP_CHANGE_BADPARAM : msg = "Invalid parameter"; break; case DISP_CHANGE_BADMODE : msg = "Resolution not supported"; break; case DISP_CHANGE_FAILED : msg = "Display driver failed to set mode"; break; case DISP_CHANGE_NOTUPDATED : msg = "Unable to write settings to the registry"; break; case DISP_CHANGE_RESTART : msg = "System restart required"; break; default : msg = "Unkown error"; } sp::Log::error("Win32: Failed to switch to fullscreen mode: %s.", msg); return; } ::SetWindowLong(m_handle, GWL_STYLE, WS_VISIBLE | WS_POPUP); ::SetWindowPos(m_handle, HWND_TOP, 0, 0, mode.width, mode.height, SWP_FRAMECHANGED | SWP_SHOWWINDOW); grabCursor(true); m_fs_mode = mode; } void Win32Display::exitFullscreen() { if (!m_fs_mode.empty()) { // Restore to previous mode. ::ChangeDisplaySettingsW(NULL, 0); m_fs_mode = DisplayMode(); } } void Win32Display::processResizeMessage(const Vector2u& new_size) { // Check if the size has actually changed. if (m_size != new_size) { // Update the size and notify the higher layer. m_size = new_size; onReshape(m_size.x, m_size.y); } } void Win32Display::processMessage(UINT message, WPARAM wParam, LPARAM lParam) { switch(message) { case WM_SETCURSOR : if (LOWORD(lParam) == HTCLIENT) { ::SetCursor(m_cursor); } break; case WM_DESTROY : PostQuitMessage(0); break; case WM_SETFOCUS : Log::debug("WM_SETFOCUS"); if (m_fs_mode.empty() == false) { enterFullscreen(m_fs_mode); } break; case WM_KILLFOCUS : Log::debug("WM_KILLFOCUS"); // If in fullscreen mode. if (m_fs_mode.empty() == false) { // Switch to window mode. ::ChangeDisplaySettingsW(NULL, 0); // also minimize the window to get it out of the way. minimize(); } grabCursor(false); break; case WM_SIZE : if (!m_inResizeModalLoop && (wParam == SIZE_MAXIMIZED || wParam == SIZE_RESTORED)) { Vector2u size(LOWORD(lParam), HIWORD(lParam)); processResizeMessage(size); } break; case WM_GETMINMAXINFO : { MINMAXINFO* info = (MINMAXINFO*)lParam; info->ptMinTrackSize.x = m_minSize.x; info->ptMinTrackSize.y = m_minSize.y; info->ptMaxTrackSize.x = 99999; info->ptMaxTrackSize.y = 99999; break; } case WM_ENTERSIZEMOVE : m_inResizeModalLoop = true; break; case WM_EXITSIZEMOVE : m_inResizeModalLoop = false; processResizeMessage(getSize()); break; } } LRESULT CALLBACK Win32Display::staticWndProc(HWND handle, UINT message, WPARAM wParam, LPARAM lParam) { Win32Display *display; if (message == WM_NCCREATE) { LONG_PTR ptr = (LONG_PTR) ((LPCREATESTRUCT)lParam)->lpCreateParams; ::SetWindowLongPtr(handle, GWL_USERDATA, ptr); display = (Win32Display*) ptr; } else { display = (Win32Display*) ::GetWindowLongPtr(handle, GWL_USERDATA); } if (display) { display->processMessage(message, wParam, lParam); } return DefWindowProc(handle, message, wParam, lParam); } } // namespace sp