Host-side window records + message queue; WM_SIZE/WM_MOVE/WM_DESTROY sent synchronously as Win32 does; PostQuitMessage also queues WM_QUIT. 64-bit pointer casts ADAPTED. port.ms_window. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
362 lines
9.1 KiB
C++
362 lines
9.1 KiB
C++
// The <winuser.h> subset of common/shim/win32/winuser.h: window records and a thread-shared message queue,
|
|
// enough for 40250 CMSWindow / CMSApplication to run unchanged. The host (Godot/SDL) owns the real surface
|
|
// and its events; a window here is the bookkeeping 40250 reads back (class, text, styles, rect, visibility,
|
|
// user data) plus its window procedure, which the calls below invoke where Win32 sends a message
|
|
// synchronously (WM_SIZE / WM_MOVE on a resize or move, WM_DESTROY, WM_CLOSE in DefWindowProc).
|
|
//
|
|
// No non-client area: the client rect is the window rect's size (AdjustWindowRectEx leaves the rect
|
|
// unchanged). The host cursor (MtHostSetCursor, client coordinates of the host surface) is placed on the
|
|
// screen at the first created window's origin, so GetCursorPos + ScreenToClient of that window gives it back.
|
|
#include "EterLib/StdAfx.h"
|
|
#include "EterLib/HostCursor.h"
|
|
|
|
#include <condition_variable>
|
|
#include <deque>
|
|
#include <map>
|
|
#include <mutex>
|
|
#include <set>
|
|
#include <string>
|
|
|
|
struct HWND__
|
|
{
|
|
std::string className;
|
|
std::string text;
|
|
DWORD style = 0;
|
|
DWORD exStyle = 0;
|
|
RECT rect = {0, 0, 0, 0};
|
|
bool visible = false;
|
|
WNDPROC proc = NULL;
|
|
LONG_PTR userData = 0;
|
|
};
|
|
|
|
namespace {
|
|
|
|
std::recursive_mutex g_mutex;
|
|
std::condition_variable_any g_queueReady;
|
|
std::map<std::string, WNDCLASS> g_classes;
|
|
std::set<HWND> g_windows;
|
|
HWND g_mainWindow = NULL;
|
|
std::deque<MSG> g_queue;
|
|
ATOM g_nextAtom = 0xC000;
|
|
POINT g_hostCursor = {0, 0};
|
|
|
|
bool Alive(HWND hWnd)
|
|
{
|
|
return hWnd && g_windows.count(hWnd);
|
|
}
|
|
|
|
LRESULT Send(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
WNDPROC proc = NULL;
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lock(g_mutex);
|
|
if (Alive(hWnd))
|
|
proc = hWnd->proc;
|
|
}
|
|
return proc ? proc(hWnd, msg, wParam, lParam) : 0;
|
|
}
|
|
|
|
bool Matches(const MSG& msg, HWND hWnd, UINT wMin, UINT wMax)
|
|
{
|
|
if (hWnd && msg.hwnd != hWnd)
|
|
return false;
|
|
if (wMin == 0 && wMax == 0)
|
|
return true;
|
|
return msg.message >= wMin && msg.message <= wMax;
|
|
}
|
|
|
|
}
|
|
|
|
void MtHostSetCursor(int x, int y)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lock(g_mutex);
|
|
g_hostCursor.x = x;
|
|
g_hostCursor.y = y;
|
|
}
|
|
|
|
ATOM RegisterClass(const WNDCLASS* lpWndClass)
|
|
{
|
|
if (!lpWndClass || !lpWndClass->lpszClassName)
|
|
return 0;
|
|
std::lock_guard<std::recursive_mutex> lock(g_mutex);
|
|
if (!g_classes.emplace(lpWndClass->lpszClassName, *lpWndClass).second)
|
|
return 0; // ERROR_CLASS_ALREADY_EXISTS
|
|
return g_nextAtom++;
|
|
}
|
|
|
|
// Win32 also sends WM_NCCREATE/WM_CREATE here, before the caller can set GWL_USERDATA; a 40250 window
|
|
// procedure passes them to DefWindowProc, so they are not sent.
|
|
HWND CreateWindow(LPCSTR lpClassName, LPCSTR lpWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight,
|
|
HWND, HMENU, HINSTANCE, LPVOID)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lock(g_mutex);
|
|
auto cls = g_classes.find(lpClassName ? lpClassName : "");
|
|
if (cls == g_classes.end())
|
|
return NULL;
|
|
HWND hWnd = new HWND__;
|
|
hWnd->className = cls->first;
|
|
hWnd->text = lpWindowName ? lpWindowName : "";
|
|
hWnd->style = dwStyle;
|
|
hWnd->rect = {x, y, x + nWidth, y + nHeight};
|
|
hWnd->proc = cls->second.lpfnWndProc;
|
|
g_windows.insert(hWnd);
|
|
if (!g_mainWindow)
|
|
g_mainWindow = hWnd;
|
|
return hWnd;
|
|
}
|
|
|
|
BOOL DestroyWindow(HWND hWnd)
|
|
{
|
|
if (!IsWindow(hWnd))
|
|
return FALSE;
|
|
Send(hWnd, WM_DESTROY, 0, 0);
|
|
std::lock_guard<std::recursive_mutex> lock(g_mutex);
|
|
if (!g_windows.erase(hWnd))
|
|
return FALSE;
|
|
if (g_mainWindow == hWnd)
|
|
g_mainWindow = NULL;
|
|
for (auto i = g_queue.begin(); i != g_queue.end();)
|
|
i = i->hwnd == hWnd ? g_queue.erase(i) : i + 1;
|
|
delete hWnd;
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL IsWindow(HWND hWnd)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lock(g_mutex);
|
|
return Alive(hWnd) ? TRUE : FALSE;
|
|
}
|
|
|
|
LRESULT DefWindowProc(HWND hWnd, UINT Msg, WPARAM, LPARAM)
|
|
{
|
|
if (Msg == WM_CLOSE)
|
|
DestroyWindow(hWnd);
|
|
return 0;
|
|
}
|
|
|
|
LONG_PTR GetWindowLongPtr(HWND hWnd, int nIndex)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lock(g_mutex);
|
|
if (!Alive(hWnd))
|
|
return 0;
|
|
switch (nIndex)
|
|
{
|
|
case GWL_STYLE: return LONG_PTR(hWnd->style);
|
|
case GWL_EXSTYLE: return LONG_PTR(hWnd->exStyle);
|
|
case GWLP_USERDATA: return hWnd->userData;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
LONG_PTR SetWindowLongPtr(HWND hWnd, int nIndex, LONG_PTR dwNewLong)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lock(g_mutex);
|
|
if (!Alive(hWnd))
|
|
return 0;
|
|
LONG_PTR old = GetWindowLongPtr(hWnd, nIndex);
|
|
switch (nIndex)
|
|
{
|
|
case GWL_STYLE: hWnd->style = DWORD(dwNewLong); break;
|
|
case GWL_EXSTYLE: hWnd->exStyle = DWORD(dwNewLong); break;
|
|
case GWLP_USERDATA: hWnd->userData = dwNewLong; break;
|
|
}
|
|
return old;
|
|
}
|
|
|
|
LONG GetWindowLong(HWND hWnd, int nIndex)
|
|
{
|
|
return LONG(GetWindowLongPtr(hWnd, nIndex));
|
|
}
|
|
|
|
LONG SetWindowLong(HWND hWnd, int nIndex, LONG dwNewLong)
|
|
{
|
|
return LONG(SetWindowLongPtr(hWnd, nIndex, dwNewLong));
|
|
}
|
|
|
|
BOOL ShowWindow(HWND hWnd, int nCmdShow)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lock(g_mutex);
|
|
if (!Alive(hWnd))
|
|
return FALSE;
|
|
const BOOL wasVisible = hWnd->visible ? TRUE : FALSE;
|
|
hWnd->visible = nCmdShow != SW_HIDE;
|
|
return wasVisible;
|
|
}
|
|
|
|
// Win32 sends WM_MOVE (client origin) and WM_SIZE (SIZE_RESTORED, client size) after a change.
|
|
BOOL SetWindowPos(HWND hWnd, HWND, int X, int Y, int cx, int cy, UINT uFlags)
|
|
{
|
|
bool moved = false, sized = false;
|
|
RECT rc;
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lock(g_mutex);
|
|
if (!Alive(hWnd))
|
|
return FALSE;
|
|
rc = hWnd->rect;
|
|
const int w = (uFlags & SWP_NOSIZE) ? rc.right - rc.left : cx;
|
|
const int h = (uFlags & SWP_NOSIZE) ? rc.bottom - rc.top : cy;
|
|
const int x = (uFlags & SWP_NOMOVE) ? rc.left : X;
|
|
const int y = (uFlags & SWP_NOMOVE) ? rc.top : Y;
|
|
moved = x != rc.left || y != rc.top;
|
|
sized = w != rc.right - rc.left || h != rc.bottom - rc.top;
|
|
rc = {x, y, x + w, y + h};
|
|
hWnd->rect = rc;
|
|
}
|
|
if (moved)
|
|
Send(hWnd, WM_MOVE, 0, MAKELPARAM(rc.left, rc.top));
|
|
if (sized)
|
|
Send(hWnd, WM_SIZE, SIZE_RESTORED, MAKELPARAM(rc.right - rc.left, rc.bottom - rc.top));
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL MoveWindow(HWND hWnd, int X, int Y, int nWidth, int nHeight, BOOL)
|
|
{
|
|
return SetWindowPos(hWnd, NULL, X, Y, nWidth, nHeight, SWP_NOZORDER);
|
|
}
|
|
|
|
BOOL AdjustWindowRectEx(LPRECT lpRect, DWORD, BOOL, DWORD)
|
|
{
|
|
return lpRect ? TRUE : FALSE;
|
|
}
|
|
|
|
BOOL GetWindowRect(HWND hWnd, LPRECT lpRect)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lock(g_mutex);
|
|
if (!Alive(hWnd) || !lpRect)
|
|
return FALSE;
|
|
*lpRect = hWnd->rect;
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL GetClientRect(HWND hWnd, LPRECT lpRect)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lock(g_mutex);
|
|
if (!Alive(hWnd) || !lpRect)
|
|
return FALSE;
|
|
*lpRect = {0, 0, hWnd->rect.right - hWnd->rect.left, hWnd->rect.bottom - hWnd->rect.top};
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL SetWindowText(HWND hWnd, LPCSTR lpString)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lock(g_mutex);
|
|
if (!Alive(hWnd))
|
|
return FALSE;
|
|
hWnd->text = lpString ? lpString : "";
|
|
return TRUE;
|
|
}
|
|
|
|
// The host repaints every frame.
|
|
BOOL InvalidateRect(HWND hWnd, const RECT*, BOOL)
|
|
{
|
|
return hWnd == NULL || IsWindow(hWnd);
|
|
}
|
|
|
|
HMENU GetMenu(HWND)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
BOOL SetRect(LPRECT lprc, int xLeft, int yTop, int xRight, int yBottom)
|
|
{
|
|
if (!lprc)
|
|
return FALSE;
|
|
*lprc = {xLeft, yTop, xRight, yBottom};
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL GetCursorPos(LPPOINT lpPoint)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lock(g_mutex);
|
|
if (!lpPoint)
|
|
return FALSE;
|
|
*lpPoint = g_hostCursor;
|
|
if (g_mainWindow)
|
|
{
|
|
lpPoint->x += g_mainWindow->rect.left;
|
|
lpPoint->y += g_mainWindow->rect.top;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL ScreenToClient(HWND hWnd, LPPOINT lpPoint)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lock(g_mutex);
|
|
if (!Alive(hWnd) || !lpPoint)
|
|
return FALSE;
|
|
lpPoint->x -= hWnd->rect.left;
|
|
lpPoint->y -= hWnd->rect.top;
|
|
return TRUE;
|
|
}
|
|
|
|
// No executable resources: windows take the host's cursor and icon.
|
|
HCURSOR LoadCursor(HINSTANCE, LPCSTR)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
HICON LoadIcon(HINSTANCE, LPCSTR)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
// The host clears the surface; no stock brush is needed.
|
|
HGDIOBJ GetStockObject(int)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
BOOL PostMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lock(g_mutex);
|
|
if (hWnd && !Alive(hWnd))
|
|
return FALSE;
|
|
MSG msg = {};
|
|
msg.hwnd = hWnd;
|
|
msg.message = Msg;
|
|
msg.wParam = wParam;
|
|
msg.lParam = lParam;
|
|
msg.time = timeGetTime();
|
|
msg.pt = g_hostCursor;
|
|
g_queue.push_back(msg);
|
|
g_queueReady.notify_all();
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL PeekMessage(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lock(g_mutex);
|
|
for (auto i = g_queue.begin(); i != g_queue.end(); ++i)
|
|
{
|
|
if (!Matches(*i, hWnd, wMsgFilterMin, wMsgFilterMax))
|
|
continue;
|
|
if (lpMsg)
|
|
*lpMsg = *i;
|
|
if (wRemoveMsg & PM_REMOVE)
|
|
g_queue.erase(i);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL GetMessage(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax)
|
|
{
|
|
std::unique_lock<std::recursive_mutex> lock(g_mutex);
|
|
MSG msg;
|
|
g_queueReady.wait(lock, [&] { return PeekMessage(&msg, hWnd, wMsgFilterMin, wMsgFilterMax, PM_REMOVE); });
|
|
if (lpMsg)
|
|
*lpMsg = msg;
|
|
return msg.message == WM_QUIT ? FALSE : TRUE;
|
|
}
|
|
|
|
// Character input reaches the window manager from the host already translated (PythonBoot).
|
|
BOOL TranslateMessage(const MSG*)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
LRESULT DispatchMessage(const MSG* lpMsg)
|
|
{
|
|
return lpMsg ? Send(lpMsg->hwnd, lpMsg->message, lpMsg->wParam, lpMsg->lParam) : 0;
|
|
}
|