mirror of
https://github.com/WinampDesktop/winamp.git
synced 2025-04-28 15:45:41 -04:00
443 lines
13 KiB
C++
443 lines
13 KiB
C++
#include "win32_nogui_platform.h"
|
|
#include "common/log.h"
|
|
#include "common/scoped_guard.h"
|
|
#include "common/string_util.h"
|
|
#include "common/threading.h"
|
|
#include "core/host.h"
|
|
#include "core/host_settings.h"
|
|
#include "frontend-common/imgui_manager.h"
|
|
#include "nogui_host.h"
|
|
#include "resource.h"
|
|
#include "win32_key_names.h"
|
|
#include <Dbt.h>
|
|
#include <shellapi.h>
|
|
#include <tchar.h>
|
|
Log_SetChannel(Win32HostInterface);
|
|
|
|
static constexpr LPCWSTR WINDOW_CLASS_NAME = L"DuckStationNoGUI";
|
|
static constexpr DWORD WINDOWED_STYLE = WS_OVERLAPPEDWINDOW | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU | WS_SIZEBOX;
|
|
static constexpr DWORD WINDOWED_EXSTYLE = WS_EX_DLGMODALFRAME | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE;
|
|
static constexpr DWORD FULLSCREEN_STYLE = WS_POPUP | WS_MINIMIZEBOX;
|
|
|
|
static float GetWindowScale(HWND hwnd)
|
|
{
|
|
static UINT(WINAPI * get_dpi_for_window)(HWND hwnd);
|
|
if (!get_dpi_for_window)
|
|
{
|
|
HMODULE mod = GetModuleHandleW(L"user32.dll");
|
|
if (mod)
|
|
get_dpi_for_window = reinterpret_cast<decltype(get_dpi_for_window)>(GetProcAddress(mod, "GetDpiForWindow"));
|
|
}
|
|
if (!get_dpi_for_window)
|
|
return 1.0f;
|
|
|
|
// less than 100% scaling seems unlikely.
|
|
const UINT dpi = hwnd ? get_dpi_for_window(hwnd) : 96;
|
|
return (dpi > 0) ? std::max(1.0f, static_cast<float>(dpi) / 96.0f) : 1.0f;
|
|
}
|
|
|
|
Win32NoGUIPlatform::Win32NoGUIPlatform()
|
|
{
|
|
m_message_loop_running.store(true, std::memory_order_release);
|
|
}
|
|
|
|
Win32NoGUIPlatform::~Win32NoGUIPlatform()
|
|
{
|
|
UnregisterClassW(WINDOW_CLASS_NAME, GetModuleHandle(nullptr));
|
|
}
|
|
|
|
bool Win32NoGUIPlatform::Initialize()
|
|
{
|
|
WNDCLASSEXW wc = {};
|
|
wc.cbSize = sizeof(WNDCLASSEXW);
|
|
wc.style = 0;
|
|
wc.lpfnWndProc = WndProc;
|
|
wc.cbClsExtra = 0;
|
|
wc.cbWndExtra = 0;
|
|
wc.hInstance = GetModuleHandle(nullptr);
|
|
wc.hIcon = LoadIconA(wc.hInstance, (LPCSTR)IDI_ICON1);
|
|
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
|
|
wc.lpszMenuName = NULL;
|
|
wc.lpszClassName = WINDOW_CLASS_NAME;
|
|
wc.hIconSm = LoadIconA(wc.hInstance, (LPCSTR)IDI_ICON1);
|
|
|
|
if (!RegisterClassExW(&wc))
|
|
{
|
|
MessageBoxW(nullptr, L"Window registration failed.", L"Error", MB_ICONERROR | MB_OK);
|
|
return false;
|
|
}
|
|
|
|
m_window_thread_id = GetCurrentThreadId();
|
|
return true;
|
|
}
|
|
|
|
void Win32NoGUIPlatform::ReportError(const std::string_view& title, const std::string_view& message)
|
|
{
|
|
const std::wstring title_copy(StringUtil::UTF8StringToWideString(title));
|
|
const std::wstring message_copy(StringUtil::UTF8StringToWideString(message));
|
|
|
|
MessageBoxW(m_hwnd, message_copy.c_str(), title_copy.c_str(), MB_ICONERROR | MB_OK);
|
|
}
|
|
|
|
bool Win32NoGUIPlatform::ConfirmMessage(const std::string_view& title, const std::string_view& message)
|
|
{
|
|
const std::wstring title_copy(StringUtil::UTF8StringToWideString(title));
|
|
const std::wstring message_copy(StringUtil::UTF8StringToWideString(message));
|
|
|
|
return (MessageBoxW(m_hwnd, message_copy.c_str(), title_copy.c_str(), MB_ICONQUESTION | MB_YESNO) == IDYES);
|
|
}
|
|
|
|
void Win32NoGUIPlatform::SetDefaultConfig(SettingsInterface& si)
|
|
{
|
|
// noop
|
|
}
|
|
|
|
bool Win32NoGUIPlatform::CreatePlatformWindow(std::string title)
|
|
{
|
|
s32 window_x, window_y, window_width, window_height;
|
|
if (!NoGUIHost::GetSavedPlatformWindowGeometry(&window_x, &window_y, &window_width, &window_height))
|
|
{
|
|
window_x = CW_USEDEFAULT;
|
|
window_y = CW_USEDEFAULT;
|
|
window_width = DEFAULT_WINDOW_WIDTH;
|
|
window_height = DEFAULT_WINDOW_HEIGHT;
|
|
}
|
|
|
|
HWND hwnd = CreateWindowExW(WS_EX_CLIENTEDGE, WINDOW_CLASS_NAME, StringUtil::UTF8StringToWideString(title).c_str(),
|
|
WINDOWED_STYLE, window_x, window_y, window_width, window_height, nullptr, nullptr,
|
|
GetModuleHandleW(nullptr), this);
|
|
if (!hwnd)
|
|
{
|
|
MessageBoxW(nullptr, L"CreateWindowEx failed.", L"Error", MB_ICONERROR | MB_OK);
|
|
return false;
|
|
}
|
|
|
|
// deliberately not stored to m_hwnd yet, because otherwise the msg handlers will run
|
|
ShowWindow(hwnd, SW_SHOW);
|
|
UpdateWindow(hwnd);
|
|
m_hwnd = hwnd;
|
|
m_window_scale = GetWindowScale(m_hwnd);
|
|
m_last_mouse_buttons = 0;
|
|
|
|
if (m_fullscreen.load(std::memory_order_acquire))
|
|
SetFullscreen(true);
|
|
|
|
// We use these notifications to detect when a controller is connected or disconnected.
|
|
DEV_BROADCAST_DEVICEINTERFACE_W filter = {sizeof(DEV_BROADCAST_DEVICEINTERFACE_W), DBT_DEVTYP_DEVICEINTERFACE};
|
|
m_dev_notify_handle =
|
|
RegisterDeviceNotificationW(hwnd, &filter, DEVICE_NOTIFY_WINDOW_HANDLE | DEVICE_NOTIFY_ALL_INTERFACE_CLASSES);
|
|
|
|
return true;
|
|
}
|
|
|
|
void Win32NoGUIPlatform::DestroyPlatformWindow()
|
|
{
|
|
if (!m_hwnd)
|
|
return;
|
|
|
|
if (m_dev_notify_handle)
|
|
{
|
|
UnregisterDeviceNotification(m_dev_notify_handle);
|
|
m_dev_notify_handle = NULL;
|
|
}
|
|
|
|
RECT rc;
|
|
if (!m_fullscreen.load(std::memory_order_acquire) && GetWindowRect(m_hwnd, &rc))
|
|
{
|
|
NoGUIHost::SavePlatformWindowGeometry(rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top);
|
|
}
|
|
|
|
DestroyWindow(m_hwnd);
|
|
m_hwnd = {};
|
|
}
|
|
|
|
std::optional<WindowInfo> Win32NoGUIPlatform::GetPlatformWindowInfo()
|
|
{
|
|
if (!m_hwnd)
|
|
return std::nullopt;
|
|
|
|
RECT rc = {};
|
|
GetWindowRect(m_hwnd, &rc);
|
|
|
|
WindowInfo wi;
|
|
wi.surface_width = static_cast<u32>(rc.right - rc.left);
|
|
wi.surface_height = static_cast<u32>(rc.bottom - rc.top);
|
|
wi.surface_scale = m_window_scale;
|
|
wi.type = WindowInfo::Type::Win32;
|
|
wi.window_handle = m_hwnd;
|
|
return wi;
|
|
}
|
|
|
|
void Win32NoGUIPlatform::SetPlatformWindowTitle(std::string title)
|
|
{
|
|
if (!m_hwnd)
|
|
return;
|
|
|
|
SetWindowTextW(m_hwnd, StringUtil::UTF8StringToWideString(title).c_str());
|
|
}
|
|
|
|
void* Win32NoGUIPlatform::GetPlatformWindowHandle()
|
|
{
|
|
return m_hwnd;
|
|
}
|
|
|
|
std::optional<u32> Win32NoGUIPlatform::ConvertHostKeyboardStringToCode(const std::string_view& str)
|
|
{
|
|
std::optional<DWORD> converted(Win32KeyNames::GetKeyCodeForName(str));
|
|
return converted.has_value() ? std::optional<u32>(static_cast<u32>(converted.value())) : std::nullopt;
|
|
}
|
|
|
|
std::optional<std::string> Win32NoGUIPlatform::ConvertHostKeyboardCodeToString(u32 code)
|
|
{
|
|
const char* converted = Win32KeyNames::GetKeyName(code);
|
|
return converted ? std::optional<std::string>(converted) : std::nullopt;
|
|
}
|
|
|
|
void Win32NoGUIPlatform::RunMessageLoop()
|
|
{
|
|
while (m_message_loop_running.load(std::memory_order_acquire))
|
|
{
|
|
MSG msg;
|
|
if (GetMessageW(&msg, NULL, 0, 0))
|
|
{
|
|
// handle self messages (when we don't have a window yet)
|
|
if (msg.hwnd == NULL && msg.message >= WM_FIRST && msg.message <= WM_LAST)
|
|
{
|
|
WndProc(NULL, msg.message, msg.wParam, msg.lParam);
|
|
}
|
|
else
|
|
{
|
|
TranslateMessage(&msg);
|
|
DispatchMessageW(&msg);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Win32NoGUIPlatform::ExecuteInMessageLoop(std::function<void()> func)
|
|
{
|
|
std::function<void()>* pfunc = new std::function<void()>(std::move(func));
|
|
if (m_hwnd)
|
|
PostMessageW(m_hwnd, WM_FUNC, 0, reinterpret_cast<LPARAM>(pfunc));
|
|
else
|
|
PostThreadMessageW(m_window_thread_id, WM_FUNC, 0, reinterpret_cast<LPARAM>(pfunc));
|
|
}
|
|
|
|
void Win32NoGUIPlatform::QuitMessageLoop()
|
|
{
|
|
m_message_loop_running.store(false, std::memory_order_release);
|
|
PostThreadMessageW(m_window_thread_id, WM_WAKEUP, 0, 0);
|
|
}
|
|
|
|
void Win32NoGUIPlatform::SetFullscreen(bool enabled)
|
|
{
|
|
if (!m_hwnd || m_fullscreen.load(std::memory_order_acquire) == enabled)
|
|
return;
|
|
|
|
LONG style = GetWindowLong(m_hwnd, GWL_STYLE);
|
|
LONG exstyle = GetWindowLong(m_hwnd, GWL_EXSTYLE);
|
|
RECT rc;
|
|
|
|
if (enabled)
|
|
{
|
|
HMONITOR monitor = MonitorFromWindow(m_hwnd, MONITOR_DEFAULTTONEAREST);
|
|
if (!monitor)
|
|
return;
|
|
|
|
MONITORINFO mi = {sizeof(MONITORINFO)};
|
|
if (!GetMonitorInfo(monitor, &mi) || !GetWindowRect(m_hwnd, &m_windowed_rect))
|
|
return;
|
|
|
|
style = (style & ~WINDOWED_STYLE) | FULLSCREEN_STYLE;
|
|
exstyle = (style & ~WINDOWED_EXSTYLE);
|
|
rc = mi.rcMonitor;
|
|
}
|
|
else
|
|
{
|
|
style = (style & ~FULLSCREEN_STYLE) | WINDOWED_STYLE;
|
|
exstyle = exstyle | WINDOWED_EXSTYLE;
|
|
rc = m_windowed_rect;
|
|
}
|
|
|
|
SetWindowLongPtrW(m_hwnd, GWL_STYLE, style);
|
|
SetWindowLongPtrW(m_hwnd, GWL_EXSTYLE, exstyle);
|
|
SetWindowPos(m_hwnd, NULL, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, SWP_SHOWWINDOW);
|
|
|
|
m_fullscreen.store(enabled, std::memory_order_release);
|
|
}
|
|
|
|
bool Win32NoGUIPlatform::RequestRenderWindowSize(s32 new_window_width, s32 new_window_height)
|
|
{
|
|
RECT rc;
|
|
if (!m_hwnd || m_fullscreen.load(std::memory_order_acquire) || !GetWindowRect(m_hwnd, &rc))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return SetWindowPos(m_hwnd, NULL, rc.left, rc.top, new_window_width, new_window_height, SWP_SHOWWINDOW);
|
|
}
|
|
|
|
bool Win32NoGUIPlatform::OpenURL(const std::string_view& url)
|
|
{
|
|
return (ShellExecuteW(nullptr, L"open", StringUtil::UTF8StringToWideString(url).c_str(), nullptr, nullptr,
|
|
SW_SHOWNORMAL) != NULL);
|
|
}
|
|
|
|
bool Win32NoGUIPlatform::CopyTextToClipboard(const std::string_view& text)
|
|
{
|
|
const int wlen = MultiByteToWideChar(CP_UTF8, 0, text.data(), static_cast<int>(text.length()), nullptr, 0);
|
|
if (wlen < 0)
|
|
return false;
|
|
|
|
if (!OpenClipboard(m_hwnd))
|
|
return false;
|
|
|
|
ScopedGuard clipboard_cleanup([]() { CloseClipboard(); });
|
|
EmptyClipboard();
|
|
|
|
const HANDLE hText = GlobalAlloc(GMEM_MOVEABLE, (wlen + 1) * sizeof(wchar_t));
|
|
if (hText == NULL)
|
|
return false;
|
|
|
|
LPWSTR mem = static_cast<LPWSTR>(GlobalLock(hText));
|
|
MultiByteToWideChar(CP_UTF8, 0, text.data(), static_cast<int>(text.length()), mem, wlen);
|
|
mem[wlen] = 0;
|
|
GlobalUnlock(hText);
|
|
|
|
SetClipboardData(CF_UNICODETEXT, hText);
|
|
return true;
|
|
}
|
|
|
|
LRESULT CALLBACK Win32NoGUIPlatform::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
Win32NoGUIPlatform* platform = static_cast<Win32NoGUIPlatform*>(g_nogui_window.get());
|
|
if (hwnd != platform->m_hwnd && msg != WM_FUNC)
|
|
return DefWindowProcW(hwnd, msg, wParam, lParam);
|
|
|
|
switch (msg)
|
|
{
|
|
case WM_SIZE:
|
|
{
|
|
const u32 width = LOWORD(lParam);
|
|
const u32 height = HIWORD(lParam);
|
|
NoGUIHost::ProcessPlatformWindowResize(width, height, platform->m_window_scale);
|
|
}
|
|
break;
|
|
|
|
case WM_KEYDOWN:
|
|
case WM_KEYUP:
|
|
{
|
|
const bool pressed = (msg == WM_KEYDOWN);
|
|
NoGUIHost::ProcessPlatformKeyEvent(static_cast<s32>(wParam), pressed);
|
|
}
|
|
break;
|
|
|
|
case WM_CHAR:
|
|
{
|
|
if (ImGuiManager::WantsTextInput())
|
|
{
|
|
const WCHAR utf16[1] = {static_cast<WCHAR>(wParam)};
|
|
char utf8[8] = {};
|
|
const int utf8_len = WideCharToMultiByte(CP_UTF8, 0, utf16, static_cast<int>(std::size(utf16)), utf8,
|
|
static_cast<int>(sizeof(utf8)) - 1, nullptr, nullptr);
|
|
if (utf8_len > 0)
|
|
{
|
|
utf8[utf8_len] = 0;
|
|
NoGUIHost::ProcessPlatformTextEvent(utf8);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_MOUSEMOVE:
|
|
{
|
|
const float x = static_cast<float>(static_cast<s16>(LOWORD(lParam)));
|
|
const float y = static_cast<float>(static_cast<s16>(HIWORD(lParam)));
|
|
NoGUIHost::ProcessPlatformMouseMoveEvent(x, y);
|
|
}
|
|
break;
|
|
|
|
case WM_LBUTTONDOWN:
|
|
case WM_LBUTTONUP:
|
|
case WM_MBUTTONDOWN:
|
|
case WM_MBUTTONUP:
|
|
case WM_RBUTTONDOWN:
|
|
case WM_RBUTTONUP:
|
|
case WM_XBUTTONDOWN:
|
|
case WM_XBUTTONUP:
|
|
{
|
|
const DWORD buttons = static_cast<DWORD>(wParam);
|
|
const DWORD changed = platform->m_last_mouse_buttons ^ buttons;
|
|
platform->m_last_mouse_buttons = buttons;
|
|
|
|
static constexpr DWORD masks[] = {MK_LBUTTON, MK_RBUTTON, MK_MBUTTON, MK_XBUTTON1, MK_XBUTTON2};
|
|
for (u32 i = 0; i < std::size(masks); i++)
|
|
{
|
|
if (changed & masks[i])
|
|
NoGUIHost::ProcessPlatformMouseButtonEvent(i, (buttons & masks[i]) != 0);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_MOUSEWHEEL:
|
|
case WM_MOUSEHWHEEL:
|
|
{
|
|
const float d =
|
|
std::clamp(static_cast<float>(static_cast<s16>(HIWORD(wParam))) / static_cast<float>(WHEEL_DELTA), -1.0f, 1.0f);
|
|
NoGUIHost::ProcessPlatformMouseWheelEvent((msg == WM_MOUSEHWHEEL) ? d : 0.0f, (msg == WM_MOUSEWHEEL) ? d : 0.0f);
|
|
}
|
|
break;
|
|
|
|
case WM_ACTIVATEAPP:
|
|
{
|
|
if (wParam)
|
|
NoGUIHost::PlatformWindowFocusGained();
|
|
else
|
|
NoGUIHost::PlatformWindowFocusLost();
|
|
}
|
|
break;
|
|
|
|
case WM_CLOSE:
|
|
case WM_QUIT:
|
|
{
|
|
Host::RunOnCPUThread([]() { Host::RequestExit(g_settings.save_state_on_exit); });
|
|
}
|
|
break;
|
|
|
|
case WM_DEVICECHANGE:
|
|
{
|
|
if (wParam == DBT_DEVNODES_CHANGED)
|
|
NoGUIHost::PlatformDevicesChanged();
|
|
}
|
|
break;
|
|
|
|
case WM_FUNC:
|
|
{
|
|
std::function<void()>* pfunc = reinterpret_cast<std::function<void()>*>(lParam);
|
|
if (pfunc)
|
|
{
|
|
(*pfunc)();
|
|
delete pfunc;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_WAKEUP:
|
|
break;
|
|
|
|
default:
|
|
return DefWindowProcW(hwnd, msg, wParam, lParam);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
std::unique_ptr<NoGUIPlatform> NoGUIPlatform::CreateWin32Platform()
|
|
{
|
|
std::unique_ptr<Win32NoGUIPlatform> ret(new Win32NoGUIPlatform());
|
|
if (!ret->Initialize())
|
|
return {};
|
|
|
|
return ret;
|
|
} |