/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2022 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #include "xenia/ui/windowed_app_context_win.h" #include #include "xenia/base/platform_win.h" namespace xe { namespace ui { bool Win32WindowedAppContext::pending_functions_window_class_registered_; Win32WindowedAppContext::~Win32WindowedAppContext() { if (pending_functions_hwnd_) { DestroyWindow(pending_functions_hwnd_); } if (user32_module_) { FreeLibrary(user32_module_); } if (shcore_module_) { FreeLibrary(shcore_module_); } } bool Win32WindowedAppContext::Initialize() { // Logging possibly not initialized in this function yet. // Obtain function pointers that may be used for windows if available. shcore_module_ = LoadLibraryW(L"SHCore.dll"); if (shcore_module_) { per_monitor_dpi_v1_api_available_ = true; per_monitor_dpi_v1_api_available_ &= (*reinterpret_cast( &per_monitor_dpi_v1_api_.get_dpi_for_monitor) = GetProcAddress(shcore_module_, "GetDpiForMonitor")) != nullptr; } user32_module_ = LoadLibraryW(L"user32.dll"); if (user32_module_) { per_monitor_dpi_v2_api_available_ = true; per_monitor_dpi_v2_api_available_ &= (*reinterpret_cast( &per_monitor_dpi_v2_api_.adjust_window_rect_ex_for_dpi) = GetProcAddress(user32_module_, "AdjustWindowRectExForDpi")) != nullptr; per_monitor_dpi_v2_api_available_ &= (*reinterpret_cast( &per_monitor_dpi_v2_api_.enable_non_client_dpi_scaling) = GetProcAddress(user32_module_, "EnableNonClientDpiScaling")) != nullptr; per_monitor_dpi_v2_api_available_ &= (*reinterpret_cast( &per_monitor_dpi_v2_api_.get_dpi_for_system) = GetProcAddress(user32_module_, "GetDpiForSystem")) != nullptr; per_monitor_dpi_v2_api_available_ &= (*reinterpret_cast( &per_monitor_dpi_v2_api_.get_dpi_for_window) = GetProcAddress(user32_module_, "GetDpiForWindow")) != nullptr; } // Create the message-only window for executing pending functions - using a // window instead of executing them between iterations so non-main message // loops, such as Windows modals, can execute pending functions too. static const WCHAR kPendingFunctionsWindowClassName[] = L"XeniaPendingFunctionsWindowClass"; if (!pending_functions_window_class_registered_) { WNDCLASSEXW pending_functions_window_class = {}; pending_functions_window_class.cbSize = sizeof(pending_functions_window_class); pending_functions_window_class.lpfnWndProc = PendingFunctionsWndProc; pending_functions_window_class.hInstance = hinstance_; pending_functions_window_class.lpszClassName = kPendingFunctionsWindowClassName; if (!RegisterClassExW(&pending_functions_window_class)) { return false; } pending_functions_window_class_registered_ = true; } pending_functions_hwnd_ = CreateWindowExW( 0, kPendingFunctionsWindowClassName, L"Xenia Pending Functions", WS_OVERLAPPED, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, HWND_MESSAGE, nullptr, hinstance_, this); if (!pending_functions_hwnd_) { return false; } return true; } void Win32WindowedAppContext::NotifyUILoopOfPendingFunctions() { while (!PostMessageW(pending_functions_hwnd_, kPendingFunctionsWindowClassMessageExecute, 0, 0)) { Sleep(1); } } void Win32WindowedAppContext::PlatformQuitFromUIThread() { // Send WM_QUIT to whichever loop happens to process it - may be the loop of a // built-in modal window, which is unaware of HasQuitFromUIThread, don't let // it delay quitting indefinitely. PostQuitMessage(EXIT_SUCCESS); } int Win32WindowedAppContext::RunMainMessageLoop() { int result = EXIT_SUCCESS; MSG message; // The HasQuitFromUIThread check is not absolutely required, but for // additional safety in case WM_QUIT is not received for any reason. while (!HasQuitFromUIThread()) { BOOL message_result = GetMessageW(&message, nullptr, 0, 0); if (message_result == 0 || message_result == -1) { // WM_QUIT (0 - this is the primary message loop, no need to resend, also // contains the result from PostQuitMessage in wParam) or an error // (-1). Quitting the context will run the pending functions. Getting // WM_QUIT doesn't imply that QuitFromUIThread has been called already, it // may originate in some place other than PlatformQuitFromUIThread as // well - call it to finish everything including the pending functions. QuitFromUIThread(); result = message_result ? EXIT_FAILURE : int(message.wParam); break; } TranslateMessage(&message); DispatchMessageW(&message); } return result; } LRESULT CALLBACK Win32WindowedAppContext::PendingFunctionsWndProc( HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { if (message == WM_CLOSE) { // Need the window for the entire context's lifetime, don't allow anything // to close it. return 0; } if (message == WM_NCCREATE) { SetWindowLongPtrW( hwnd, GWLP_USERDATA, reinterpret_cast( reinterpret_cast(lparam)->lpCreateParams)); } else { auto app_context = reinterpret_cast( GetWindowLongPtrW(hwnd, GWLP_USERDATA)); if (app_context) { switch (message) { case WM_DESTROY: // The message-only window owned by the context is being destroyed, // thus the context won't be able to execute pending functions // anymore - can't continue functioning normally. app_context->QuitFromUIThread(); break; case kPendingFunctionsWindowClassMessageExecute: app_context->ExecutePendingFunctionsFromUIThread(); return 0; default: break; } } } return DefWindowProcW(hwnd, message, wparam, lparam); } } // namespace ui } // namespace xe