/* * This source file is part of RmlUi, the HTML/CSS Interface Middleware * * For the latest information, see http://github.com/mikke89/RmlUi * * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd * Copyright (c) 2019 The RmlUi Team, and contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ #include #include #include #include "ShellFileInterface.h" #include #include #include #include static LRESULT CALLBACK WindowProcedure(HWND window_handle, UINT message, WPARAM w_param, LPARAM l_param); static Rml::Context* context = nullptr; static ShellRenderInterfaceExtensions* shell_renderer = nullptr; static bool activated = true; static bool running = false; static Rml::U16String instance_name; static HWND window_handle = nullptr; static HINSTANCE instance_handle = nullptr; static bool has_dpi_support = false; static UINT window_dpi = USER_DEFAULT_SCREEN_DPI; static int window_width = 0; static int window_height = 0; static double time_frequency; static LARGE_INTEGER time_startup; static Rml::UniquePtr file_interface; static HCURSOR cursor_default = nullptr; static HCURSOR cursor_move = nullptr; static HCURSOR cursor_pointer = nullptr; static HCURSOR cursor_resize= nullptr; static HCURSOR cursor_cross = nullptr; static HCURSOR cursor_text = nullptr; static HCURSOR cursor_unavailable = nullptr; #ifndef DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 #define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((HANDLE)-4) #endif #ifndef WM_DPICHANGED #define WM_DPICHANGED 0x02E0 #endif // Declare pointers to the DPI aware Windows API functions. using ProcSetProcessDpiAwarenessContext = BOOL(WINAPI*)(HANDLE value); using ProcGetDpiForWindow = UINT(WINAPI*)(HWND hwnd); using ProcAdjustWindowRectExForDpi = BOOL(WINAPI*)(LPRECT lpRect, DWORD dwStyle, BOOL bMenu, DWORD dwExStyle, UINT dpi); static ProcSetProcessDpiAwarenessContext procSetProcessDpiAwarenessContext = NULL; static ProcGetDpiForWindow procGetDpiForWindow = NULL; static ProcAdjustWindowRectExForDpi procAdjustWindowRectExForDpi = NULL; static void UpdateDpi() { if (has_dpi_support) { UINT dpi = procGetDpiForWindow(window_handle); if (dpi != 0) { window_dpi = dpi; if (context) context->SetDensityIndependentPixelRatio(Shell::GetDensityIndependentPixelRatio()); } } } static void UpdateWindowDimensions(int width = 0, int height = 0) { if (width > 0) window_width = width; if (height > 0) window_height = height; if (context) context->SetDimensions(Rml::Vector2i(window_width, window_height)); if (shell_renderer) shell_renderer->SetViewport(window_width, window_height); } bool Shell::Initialise() { instance_handle = GetModuleHandle(nullptr); InputWin32::Initialise(); LARGE_INTEGER time_ticks_per_second; QueryPerformanceFrequency(&time_ticks_per_second); QueryPerformanceCounter(&time_startup); time_frequency = 1.0 / (double) time_ticks_per_second.QuadPart; // Load cursors cursor_default = LoadCursor(nullptr, IDC_ARROW); cursor_move = LoadCursor(nullptr, IDC_SIZEALL); cursor_pointer = LoadCursor(nullptr, IDC_HAND); cursor_resize = LoadCursor(nullptr, IDC_SIZENWSE); cursor_cross = LoadCursor(nullptr, IDC_CROSS); cursor_text = LoadCursor(nullptr, IDC_IBEAM); cursor_unavailable = LoadCursor(nullptr, IDC_NO); Rml::String root = FindSamplesRoot(); bool result = !root.empty(); file_interface = Rml::MakeUnique(root); Rml::SetFileInterface(file_interface.get()); // See if we have Per Monitor V2 DPI awareness. Requires Windows 10, version 1703. // Cast function pointers to void* first for MinGW not to emit errors. procSetProcessDpiAwarenessContext = (ProcSetProcessDpiAwarenessContext)(void*)GetProcAddress( GetModuleHandle(TEXT("User32.dll")), "SetProcessDpiAwarenessContext" ); procGetDpiForWindow = (ProcGetDpiForWindow)(void*)GetProcAddress( GetModuleHandle(TEXT("User32.dll")), "GetDpiForWindow" ); procAdjustWindowRectExForDpi = (ProcAdjustWindowRectExForDpi)(void*)GetProcAddress( GetModuleHandle(TEXT("User32.dll")), "AdjustWindowRectExForDpi" ); has_dpi_support = (procSetProcessDpiAwarenessContext != NULL && procGetDpiForWindow != NULL && procAdjustWindowRectExForDpi != NULL); return result; } void Shell::Shutdown() { InputWin32::Shutdown(); file_interface.reset(); } Rml::String Shell::FindSamplesRoot() { const char* candidate_paths[] = { "", "..\\Samples\\", "..\\..\\Samples\\", "..\\..\\..\\Samples\\", "..\\..\\..\\..\\Samples\\" }; // Fetch the path of the executable, test the candidate paths appended to that. char executable_file_name[MAX_PATH]; if (GetModuleFileNameA(instance_handle, executable_file_name, MAX_PATH) >= MAX_PATH && GetLastError() == ERROR_INSUFFICIENT_BUFFER) { executable_file_name[0] = 0; } Rml::String executable_path(executable_file_name); executable_path = executable_path.substr(0, executable_path.rfind('\\') + 1); // We assume we have found the correct path if we can find the lookup file from it const char* lookup_file = "assets\\rml.rcss"; for(const char* relative_path : candidate_paths) { Rml::String absolute_path = executable_path + relative_path; if (PathFileExistsA(Rml::String(absolute_path + lookup_file).c_str())) { char canonical_path[MAX_PATH]; if (!PathCanonicalizeA(canonical_path, absolute_path.c_str())) canonical_path[0] = 0; return Rml::String(canonical_path); } } return Rml::String(); } bool Shell::OpenWindow(const char* in_name, ShellRenderInterfaceExtensions *_shell_renderer, unsigned int width, unsigned int height, bool allow_resize) { // Activate Per Monitor V2. if (has_dpi_support && !procSetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) { has_dpi_support = false; } WNDCLASSW window_class; Rml::U16String name = Rml::StringUtilities::ToUTF16(Rml::String(in_name)); // Fill out the window class struct. window_class.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; window_class.lpfnWndProc = WindowProcedure; window_class.cbClsExtra = 0; window_class.cbWndExtra = 0; window_class.hInstance = instance_handle; window_class.hIcon = LoadIcon(nullptr, IDI_WINLOGO); window_class.hCursor = cursor_default; window_class.hbrBackground = nullptr; window_class.lpszMenuName = nullptr; window_class.lpszClassName = (LPCWSTR)name.data(); if (!RegisterClassW(&window_class)) { DisplayError("Could not register window class."); CloseWindow(); return false; } window_handle = CreateWindowExW(WS_EX_APPWINDOW | WS_EX_WINDOWEDGE, (LPCWSTR)name.data(), // Window class name. (LPCWSTR)name.data(), WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW, 0, 0, // Window position. 0, 0,// Window size. nullptr, nullptr, instance_handle, nullptr); if (!window_handle) { DisplayError("Could not create window."); CloseWindow(); return false; } window_width = width; window_height = height; UpdateDpi(); window_width = (width * window_dpi) / USER_DEFAULT_SCREEN_DPI; window_height = (height * window_dpi) / USER_DEFAULT_SCREEN_DPI; instance_name = name; DWORD style = (allow_resize ? WS_OVERLAPPEDWINDOW : (WS_OVERLAPPEDWINDOW & ~WS_SIZEBOX & ~WS_MAXIMIZEBOX)); DWORD extended_style = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; // Adjust the window size to take into account the edges RECT window_rect; window_rect.top = 0; window_rect.left = 0; window_rect.right = window_width; window_rect.bottom = window_height; if (has_dpi_support) procAdjustWindowRectExForDpi(&window_rect, style, FALSE, extended_style, window_dpi); else AdjustWindowRectEx(&window_rect, style, FALSE, extended_style); SetWindowLong(window_handle, GWL_EXSTYLE, extended_style); SetWindowLong(window_handle, GWL_STYLE, style); if (_shell_renderer != nullptr) { shell_renderer = _shell_renderer; if(!shell_renderer->AttachToNative(window_handle)) { CloseWindow(); return false; } } UpdateWindowDimensions(); // Resize the window. SetWindowPos(window_handle, HWND_TOP, 0, 0, window_rect.right - window_rect.left, window_rect.bottom - window_rect.top, SWP_NOACTIVATE); // Display the new window ShowWindow(window_handle, SW_SHOW); SetForegroundWindow(window_handle); SetFocus(window_handle); return true; } void Shell::CloseWindow() { if(shell_renderer) { shell_renderer->DetachFromNative(); } DestroyWindow(window_handle); UnregisterClassW((LPCWSTR)instance_name.data(), instance_handle); } // Returns a platform-dependent handle to the window. void* Shell::GetWindowHandle() { return window_handle; } void Shell::EventLoop(ShellIdleFunction idle_function) { MSG message; running = true; // Loop on PeekMessage() / GetMessage() until exit has been requested. while (running) { if (PeekMessage(&message, nullptr, 0, 0, PM_NOREMOVE)) { GetMessage(&message, nullptr, 0, 0); TranslateMessage(&message); DispatchMessage(&message); } idle_function(); } } void Shell::RequestExit() { running = false; } void Shell::DisplayError(const char* fmt, ...) { const int buffer_size = 1024; char buffer[buffer_size]; va_list argument_list; // Print the message to the buffer. va_start(argument_list, fmt); int len = vsnprintf(buffer, buffer_size - 2, fmt, argument_list); if ( len < 0 || len > buffer_size - 2 ) { len = buffer_size - 2; } buffer[len] = '\n'; buffer[len + 1] = '\0'; va_end(argument_list); MessageBoxW(window_handle, (LPCWSTR)Rml::StringUtilities::ToUTF16(buffer).c_str(), L"Shell Error", MB_OK); } void Shell::Log(const char* fmt, ...) { const int buffer_size = 1024; char buffer[buffer_size]; va_list argument_list; // Print the message to the buffer. va_start(argument_list, fmt); int len = vsnprintf(buffer, buffer_size - 2, fmt, argument_list); if ( len < 0 || len > buffer_size - 2 ) { len = buffer_size - 2; } buffer[len] = '\n'; buffer[len + 1] = '\0'; va_end(argument_list); OutputDebugStringW((LPCWSTR)Rml::StringUtilities::ToUTF16(buffer).c_str()); } double Shell::GetElapsedTime() { LARGE_INTEGER counter; QueryPerformanceCounter(&counter); return double(counter.QuadPart - time_startup.QuadPart) * time_frequency; } void Shell::SetMouseCursor(const Rml::String& cursor_name) { if (window_handle) { HCURSOR cursor_handle = nullptr; if (cursor_name.empty() || cursor_name == "arrow") cursor_handle = cursor_default; else if(cursor_name == "move") cursor_handle = cursor_move; else if (cursor_name == "pointer") cursor_handle = cursor_pointer; else if (cursor_name == "resize") cursor_handle = cursor_resize; else if (cursor_name == "cross") cursor_handle = cursor_cross; else if (cursor_name == "text") cursor_handle = cursor_text; else if (cursor_name == "unavailable") cursor_handle = cursor_unavailable; if (cursor_handle) { SetCursor(cursor_handle); SetClassLongPtrA(window_handle, GCLP_HCURSOR, (LONG_PTR)cursor_handle); } } } void Shell::SetClipboardText(const Rml::String& text_utf8) { if (window_handle) { if (!OpenClipboard(window_handle)) return; EmptyClipboard(); const Rml::U16String text = Rml::StringUtilities::ToUTF16(text_utf8); size_t size = sizeof(char16_t) * (text.size() + 1); HGLOBAL clipboard_data = GlobalAlloc(GMEM_FIXED, size); memcpy(clipboard_data, text.data(), size); if (SetClipboardData(CF_UNICODETEXT, clipboard_data) == nullptr) { CloseClipboard(); GlobalFree(clipboard_data); } else CloseClipboard(); } } void Shell::GetClipboardText(Rml::String& text) { if (window_handle) { if (!OpenClipboard(window_handle)) return; HANDLE clipboard_data = GetClipboardData(CF_UNICODETEXT); if (clipboard_data == nullptr) { CloseClipboard(); return; } const char16_t* clipboard_text = (const char16_t*)GlobalLock(clipboard_data); if (clipboard_text) text = Rml::StringUtilities::ToUTF8(clipboard_text); GlobalUnlock(clipboard_data); CloseClipboard(); } } void Shell::SetContext(Rml::Context* new_context) { context = new_context; UpdateDpi(); UpdateWindowDimensions(); } float Shell::GetDensityIndependentPixelRatio() { return float(window_dpi) / float(USER_DEFAULT_SCREEN_DPI); } static LRESULT CALLBACK WindowProcedure(HWND local_window_handle, UINT message, WPARAM w_param, LPARAM l_param) { // See what kind of message we've got. switch (message) { case WM_ACTIVATE: { if (LOWORD(w_param) != WA_INACTIVE) { activated = true; } else { activated = false; } } break; // When the window closes, request exit case WM_CLOSE: { running = false; return 0; } break; case WM_SIZE: { int width = LOWORD(l_param); int height = HIWORD(l_param); UpdateWindowDimensions(width, height); } break; case WM_DPICHANGED: { UpdateDpi(); RECT* const new_pos = (RECT*)l_param; SetWindowPos(window_handle, NULL, new_pos->left, new_pos->top, new_pos->right - new_pos->left, new_pos->bottom - new_pos->top, SWP_NOZORDER | SWP_NOACTIVATE); } break; default: { InputWin32::ProcessWindowsEvent(local_window_handle, message, w_param, l_param); } break; } // All unhandled messages go to DefWindowProc. return DefWindowProc(local_window_handle, message, w_param, l_param); }