From 5d64311a4dacd9c2c821c1dbdbb996ceffd4750f Mon Sep 17 00:00:00 2001 From: shenlei Date: Wed, 30 Sep 2026 13:19:36 +0900 Subject: [PATCH] port: MSWindow/MSApplication verbatim over a winuser.h shim (Win32User.cpp) 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 --- audit/history.jsonl | 2 + audit/port-map/EterLib/MSApplication.cpp.json | 78 ++++ audit/port-map/EterLib/MSWindow.cpp.json | 258 ++++++++++++ src/platform/EterLib/MSApplication.cpp | 55 +-- src/platform/EterLib/MSWindow.cpp | 382 +++++++++++------- src/platform/ScriptLib/PythonBoot.cpp | 7 +- src/platform/Win32User.cpp | 361 +++++++++++++++++ src/port/CMakeLists.txt | 3 + src/port/common/shim/win32/windows.h | 8 +- src/port/common/shim/win32/windowsx.h | 3 + src/port/common/shim/win32/wingdi.h | 1 + src/port/common/shim/win32/winuser.h | 107 +++++ tests/port/port_ms_window_test.cpp | 91 +++++ 13 files changed, 1186 insertions(+), 170 deletions(-) create mode 100644 audit/port-map/EterLib/MSApplication.cpp.json create mode 100644 audit/port-map/EterLib/MSWindow.cpp.json create mode 100644 src/platform/Win32User.cpp create mode 100644 src/port/common/shim/win32/winuser.h create mode 100644 tests/port/port_ms_window_test.cpp diff --git a/audit/history.jsonl b/audit/history.jsonl index 5bc056c3..6aa04441 100644 --- a/audit/history.jsonl +++ b/audit/history.jsonl @@ -619,3 +619,5 @@ {"date": "2026-09-30", "unit": "EterLib/BlockTexture.cpp", "action": "Render ADAPTED (UI image of the CPU texture), rest PORTED verbatim", "evidence": "port.text_bar"} {"date": "2026-09-30", "unit": "UserInterface/ProcessCRC.cpp", "action": "port", "evidence": "verbatim; process introspection ADAPTED (no module snapshot -> ProcCRC 0); port.process_crc"} {"date": "2026-09-30", "unit": "UserInterface/WiseLogicXTrap.cpp", "action": "port", "evidence": "verbatim non-XTRAP build; mapped PORTED"} +{"date": "2026-09-30", "unit": "EterLib/MSWindow.cpp", "action": "port", "evidence": "verbatim over new winuser.h shim (platform/Win32User.cpp); 64-bit pointer casts ADAPTED; port.ms_window"} +{"date": "2026-09-30", "unit": "EterLib/MSApplication.cpp", "action": "port", "evidence": "verbatim over new winuser.h shim (platform/Win32User.cpp); 64-bit pointer casts ADAPTED; port.ms_window"} diff --git a/audit/port-map/EterLib/MSApplication.cpp.json b/audit/port-map/EterLib/MSApplication.cpp.json new file mode 100644 index 00000000..7012da4e --- /dev/null +++ b/audit/port-map/EterLib/MSApplication.cpp.json @@ -0,0 +1,78 @@ +{ + "reference": "EterLib/MSApplication.cpp", + "reference_sha256": "d3be4da92ba850fd1e043c39d32cf1402928e2be84ec95fd875f9981538d52e9", + "priority": "P4", + "contracts": [], + "functions": { + "CMSApplication::CMSApplication": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/MSApplication.cpp:CMSApplication::CMSApplication" + ], + "test": [ + "tests/port/port_ms_window_test.cpp" + ], + "note": "verbatim over the subset in src/platform/Win32User.cpp (host-side window records + message queue)" + }, + "CMSApplication::~CMSApplication": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/MSApplication.cpp:CMSApplication::~CMSApplication" + ], + "test": [ + "tests/port/port_ms_window_test.cpp" + ], + "note": "verbatim over the subset in src/platform/Win32User.cpp (host-side window records + message queue)" + }, + "CMSApplication::Initialize": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/MSApplication.cpp:CMSApplication::Initialize" + ], + "test": [ + "tests/port/port_ms_window_test.cpp" + ], + "note": "verbatim over the subset in src/platform/Win32User.cpp (host-side window records + message queue)" + }, + "CMSApplication::MessageLoop": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/MSApplication.cpp:CMSApplication::MessageLoop" + ], + "test": [ + "tests/port/port_ms_window_test.cpp" + ], + "note": "verbatim over the subset in src/platform/Win32User.cpp (host-side window records + message queue)" + }, + "CMSApplication::IsMessage": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/MSApplication.cpp:CMSApplication::IsMessage" + ], + "test": [ + "tests/port/port_ms_window_test.cpp" + ], + "note": "verbatim over the subset in src/platform/Win32User.cpp (host-side window records + message queue)" + }, + "CMSApplication::MessageProcess": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/MSApplication.cpp:CMSApplication::MessageProcess" + ], + "test": [ + "tests/port/port_ms_window_test.cpp" + ], + "note": "verbatim over the subset in src/platform/Win32User.cpp (host-side window records + message queue)" + }, + "CMSApplication::WindowProcedure": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/MSApplication.cpp:CMSApplication::WindowProcedure" + ], + "test": [ + "tests/port/port_ms_window_test.cpp" + ], + "note": "verbatim over the subset in src/platform/Win32User.cpp (host-side window records + message queue)" + } + } +} diff --git a/audit/port-map/EterLib/MSWindow.cpp.json b/audit/port-map/EterLib/MSWindow.cpp.json new file mode 100644 index 00000000..6208e891 --- /dev/null +++ b/audit/port-map/EterLib/MSWindow.cpp.json @@ -0,0 +1,258 @@ +{ + "reference": "EterLib/MSWindow.cpp", + "reference_sha256": "e1e01221043a37eb4394cf339716e17dfb4afc662426973c1ab7a9337898fff9", + "priority": "P4", + "contracts": [], + "functions": { + "MSWindowProcedure": { + "status": "ADAPTED", + "impl": [ + "src/platform/EterLib/MSWindow.cpp:MSWindowProcedure" + ], + "test": [ + "tests/port/port_ms_window_test.cpp" + ], + "note": "40250: GetWindowLong(GWL_USERDATA) gives the CMSWindow*. Port: GetWindowLongPtr(GWLP_USERDATA). Invariant: the pointer Create stored comes back whole on 64-bit." + }, + "CMSWindow::WindowProcedure": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/MSWindow.cpp:CMSWindow::WindowProcedure" + ], + "test": [ + "tests/port/port_ms_window_test.cpp" + ], + "note": "verbatim over the subset in src/platform/Win32User.cpp (host-side window records + message queue)" + }, + "CMSWindow::OnSize": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/MSWindow.cpp:CMSWindow::OnSize" + ], + "test": [ + "tests/port/port_ms_window_test.cpp" + ], + "note": "verbatim over the subset in src/platform/Win32User.cpp (host-side window records + message queue)" + }, + "CMSWindow::Destroy": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/MSWindow.cpp:CMSWindow::Destroy" + ], + "test": [ + "tests/port/port_ms_window_test.cpp" + ], + "note": "verbatim over the subset in src/platform/Win32User.cpp (host-side window records + message queue)" + }, + "CMSWindow::Create": { + "status": "ADAPTED", + "impl": [ + "src/platform/EterLib/MSWindow.cpp:CMSWindow::Create" + ], + "test": [ + "tests/port/port_ms_window_test.cpp" + ], + "note": "40250: SetWindowLong(GWL_USERDATA, (DWORD) this). Port: SetWindowLongPtr(GWLP_USERDATA, (LONG_PTR) this). Invariant: MSWindowProcedure reaches this object's WindowProcedure." + }, + "CMSWindow::SetVisibleMode": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/MSWindow.cpp:CMSWindow::SetVisibleMode" + ], + "test": [ + "tests/port/port_ms_window_test.cpp" + ], + "note": "verbatim over the subset in src/platform/Win32User.cpp (host-side window records + message queue)" + }, + "CMSWindow::Show": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/MSWindow.cpp:CMSWindow::Show" + ], + "test": [ + "tests/port/port_ms_window_test.cpp" + ], + "note": "verbatim over the subset in src/platform/Win32User.cpp (host-side window records + message queue)" + }, + "CMSWindow::Hide": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/MSWindow.cpp:CMSWindow::Hide" + ], + "test": [ + "tests/port/port_ms_window_test.cpp" + ], + "note": "verbatim over the subset in src/platform/Win32User.cpp (host-side window records + message queue)" + }, + "CMSWindow::IsVisible": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/MSWindow.cpp:CMSWindow::IsVisible" + ], + "test": [ + "tests/port/port_ms_window_test.cpp" + ], + "note": "verbatim over the subset in src/platform/Win32User.cpp (host-side window records + message queue)" + }, + "CMSWindow::IsActive": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/MSWindow.cpp:CMSWindow::IsActive" + ], + "test": [ + "tests/port/port_ms_window_test.cpp" + ], + "note": "verbatim over the subset in src/platform/Win32User.cpp (host-side window records + message queue)" + }, + "CMSWindow::GetInstance": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/MSWindow.cpp:CMSWindow::GetInstance" + ], + "test": [ + "tests/port/port_ms_window_test.cpp" + ], + "note": "verbatim over the subset in src/platform/Win32User.cpp (host-side window records + message queue)" + }, + "CMSWindow::GetWindowHandle": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/MSWindow.cpp:CMSWindow::GetWindowHandle" + ], + "test": [ + "tests/port/port_ms_window_test.cpp" + ], + "note": "verbatim over the subset in src/platform/Win32User.cpp (host-side window records + message queue)" + }, + "CMSWindow::GetScreenWidth": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/MSWindow.cpp:CMSWindow::GetScreenWidth" + ], + "test": [ + "tests/port/port_ms_window_test.cpp" + ], + "note": "verbatim over the subset in src/platform/Win32User.cpp (host-side window records + message queue)" + }, + "CMSWindow::GetScreenHeight": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/MSWindow.cpp:CMSWindow::GetScreenHeight" + ], + "test": [ + "tests/port/port_ms_window_test.cpp" + ], + "note": "verbatim over the subset in src/platform/Win32User.cpp (host-side window records + message queue)" + }, + "CMSWindow::GetWindowRect": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/MSWindow.cpp:CMSWindow::GetWindowRect" + ], + "test": [ + "tests/port/port_ms_window_test.cpp" + ], + "note": "verbatim over the subset in src/platform/Win32User.cpp (host-side window records + message queue)" + }, + "CMSWindow::GetClientRect": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/MSWindow.cpp:CMSWindow::GetClientRect" + ], + "test": [ + "tests/port/port_ms_window_test.cpp" + ], + "note": "verbatim over the subset in src/platform/Win32User.cpp (host-side window records + message queue)" + }, + "CMSWindow::GetMousePosition": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/MSWindow.cpp:CMSWindow::GetMousePosition" + ], + "test": [ + "tests/port/port_ms_window_test.cpp" + ], + "note": "verbatim over the subset in src/platform/Win32User.cpp (host-side window records + message queue)" + }, + "CMSWindow::SetPosition": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/MSWindow.cpp:CMSWindow::SetPosition" + ], + "test": [ + "tests/port/port_ms_window_test.cpp" + ], + "note": "verbatim over the subset in src/platform/Win32User.cpp (host-side window records + message queue)" + }, + "CMSWindow::SetCenterPosition": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/MSWindow.cpp:CMSWindow::SetCenterPosition" + ], + "test": [ + "tests/port/port_ms_window_test.cpp" + ], + "note": "verbatim over the subset in src/platform/Win32User.cpp (host-side window records + message queue)" + }, + "CMSWindow::AdjustSize": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/MSWindow.cpp:CMSWindow::AdjustSize" + ], + "test": [ + "tests/port/port_ms_window_test.cpp" + ], + "note": "verbatim over the subset in src/platform/Win32User.cpp (host-side window records + message queue)" + }, + "CMSWindow::SetText": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/MSWindow.cpp:CMSWindow::SetText" + ], + "test": [ + "tests/port/port_ms_window_test.cpp" + ], + "note": "verbatim over the subset in src/platform/Win32User.cpp (host-side window records + message queue)" + }, + "CMSWindow::SetSize": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/MSWindow.cpp:CMSWindow::SetSize" + ], + "test": [ + "tests/port/port_ms_window_test.cpp" + ], + "note": "verbatim over the subset in src/platform/Win32User.cpp (host-side window records + message queue)" + }, + "CMSWindow::RegisterWindowClass": { + "status": "ADAPTED", + "impl": [ + "src/platform/EterLib/MSWindow.cpp:CMSWindow::RegisterWindowClass" + ], + "test": [ + "tests/port/port_ms_window_test.cpp" + ], + "note": "40250: formats (DWORD) pfnWndProc into the class name. Port: (DWORD)(uintptr_t), the low 32 bits. Invariant: one class per style/brush/procedure, registered once." + }, + "CMSWindow::CMSWindow": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/MSWindow.cpp:CMSWindow::CMSWindow" + ], + "test": [ + "tests/port/port_ms_window_test.cpp" + ], + "note": "verbatim over the subset in src/platform/Win32User.cpp (host-side window records + message queue)" + }, + "CMSWindow::~CMSWindow": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/MSWindow.cpp:CMSWindow::~CMSWindow" + ], + "test": [ + "tests/port/port_ms_window_test.cpp" + ], + "note": "verbatim over the subset in src/platform/Win32User.cpp (host-side window records + message queue)" + } + } +} diff --git a/src/platform/EterLib/MSApplication.cpp b/src/platform/EterLib/MSApplication.cpp index 1ef3da95..55922cfc 100644 --- a/src/platform/EterLib/MSApplication.cpp +++ b/src/platform/EterLib/MSApplication.cpp @@ -1,49 +1,58 @@ -// Platform skeleton for EterLib/MSApplication.h (40250 EterLib/MSApplication.cpp), generated by platform_stub.py. -// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation. +// 40250 EterLib/MSApplication.cpp, verbatim over the subset of platform/Win32User.cpp. +// ClearWindowClass is declared in MSApplication.h but not defined in 40250. #include "EterLib/StdAfx.h" #include "EterLib/MSApplication.h" -#include "../PlatformStub.h" - CMSApplication::CMSApplication() { - MT_PLATFORM_STUB(); } CMSApplication::~CMSApplication() { - MT_PLATFORM_STUB(); +// for (TWindowClassSet::iterator i=ms_stWCSet.begin(); i!=ms_stWCSet.end(); ++i) +// UnregisterClass(*i, ms_hInstance); } -auto CMSApplication::Initialize(HINSTANCE) -> void +void CMSApplication::Initialize(HINSTANCE hInstance) { - MT_PLATFORM_STUB(); + ms_hInstance = hInstance; } -auto CMSApplication::MessageLoop() -> void +void CMSApplication::MessageLoop() { - MT_PLATFORM_STUB(); + while (MessageProcess()); } -auto CMSApplication::IsMessage() -> bool +bool CMSApplication::IsMessage() { - MT_PLATFORM_STUB(); - return mt_platform_stub_return(); + MSG msg; + + if (!PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) + return false; + + return true; } -auto CMSApplication::MessageProcess() -> bool +bool CMSApplication::MessageProcess() { - MT_PLATFORM_STUB(); - return mt_platform_stub_return(); + MSG msg; + + if (!GetMessage(&msg, NULL, 0, 0)) + return false; + + TranslateMessage(&msg); + DispatchMessage(&msg); + return true; } -auto CMSApplication::ClearWindowClass() -> void +LRESULT CMSApplication::WindowProcedure(HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam) { - MT_PLATFORM_STUB(); -} + switch (uiMsg) + { + case WM_CLOSE: + PostQuitMessage(0); + break; + } -auto CMSApplication::WindowProcedure(HWND, UINT, WPARAM, LPARAM) -> LRESULT -{ - MT_PLATFORM_STUB(); - return mt_platform_stub_return(); + return CMSWindow::WindowProcedure(hWnd, uiMsg, wParam, lParam); } diff --git a/src/platform/EterLib/MSWindow.cpp b/src/platform/EterLib/MSWindow.cpp index bcaf9605..d777fa3c 100644 --- a/src/platform/EterLib/MSWindow.cpp +++ b/src/platform/EterLib/MSWindow.cpp @@ -1,150 +1,254 @@ -// Platform skeleton for EterLib/MSWindow.h (40250 EterLib/MSWindow.cpp), generated by platform_stub.py. -// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation. +// 40250 EterLib/MSWindow.cpp, verbatim over the subset of platform/Win32User.cpp, except the +// two pointer-to-DWORD casts (PORT below), which do not fit a 64-bit pointer. #include "EterLib/StdAfx.h" #include "EterLib/MSWindow.h" -#include "../PlatformStub.h" -#include "HostCursor.h" +#include + +CMSWindow::TWindowClassSet CMSWindow::ms_stWCSet; +HINSTANCE CMSWindow::ms_hInstance = NULL; + +LRESULT CALLBACK MSWindowProcedure(HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam) +{ + // PORT: 40250 reads GetWindowLong(GWL_USERDATA), the 32-bit pointer SetWindowLong stored in Create. + CMSWindow * pWnd = (CMSWindow *) GetWindowLongPtr(hWnd, GWLP_USERDATA); + + if (pWnd) + return pWnd->WindowProcedure(hWnd, uiMsg, wParam, lParam); + + return DefWindowProc(hWnd, uiMsg, wParam, lParam); +} + +LRESULT CMSWindow::WindowProcedure(HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uiMsg) + { + case WM_SIZE: + OnSize(wParam, lParam); + break; + + case WM_ACTIVATEAPP: + m_isActive = (wParam == WA_ACTIVE) || (wParam == WA_CLICKACTIVE); + break; + } + + return DefWindowProc(hWnd, uiMsg, wParam, lParam); +} + +void CMSWindow::OnSize(WPARAM wParam, LPARAM /*lParam*/) +{ + if (wParam == SIZE_MINIMIZED) + { + InvalidateRect(m_hWnd, NULL, true); + m_isActive = false; + m_isVisible = false; + } + else + { + m_isActive = true; + m_isVisible = true; + } +} + +void CMSWindow::Destroy() +{ + if (!m_hWnd) + return; + + if (IsWindow(m_hWnd)) + DestroyWindow(m_hWnd); + + m_hWnd = NULL; + m_isVisible = false; +} + +bool CMSWindow::Create(const char* c_szName, int brush, DWORD cs, DWORD ws, HICON hIcon, int iCursorResource) +{ + //assert(ms_hInstance != NULL); + Destroy(); + + const char* c_szClassName = RegisterWindowClass(cs, brush, MSWindowProcedure, hIcon, iCursorResource); + + m_hWnd = CreateWindow( + c_szClassName, + c_szName, + ws, + 0, 0, 0, 0, + NULL, + NULL, + ms_hInstance, + NULL); + + if (!m_hWnd) + return false; + + // PORT: 40250 stores (DWORD) this with SetWindowLong(GWL_USERDATA); a 64-bit pointer needs the LONG_PTR slot. + SetWindowLongPtr(m_hWnd, GWLP_USERDATA, (LONG_PTR) this); + //DestroyWindow(ImmGetDefaultIMEWnd(m_hWnd)); + + return true; +} + +void CMSWindow::SetVisibleMode(bool isVisible) +{ + m_isVisible = isVisible; + + if (m_isVisible) + { + ShowWindow(m_hWnd, SW_SHOW); + } + else + { + ShowWindow(m_hWnd, SW_HIDE); + } +} + +void CMSWindow::Show() +{ + m_isVisible = true; + ShowWindow(m_hWnd, SW_SHOW); +} + +void CMSWindow::Hide() +{ + m_isVisible = false; + ShowWindow(m_hWnd, SW_HIDE); +} + +bool CMSWindow::IsVisible() +{ + return m_isVisible; +} + +bool CMSWindow::IsActive() +{ + return m_isActive; +} + +HINSTANCE CMSWindow::GetInstance() +{ + return ms_hInstance; +} + +HWND CMSWindow::GetWindowHandle() +{ + return m_hWnd; +} + +int CMSWindow::GetScreenWidth() +{ + return GetSystemMetrics(SM_CXSCREEN); +} + +int CMSWindow::GetScreenHeight() +{ + return GetSystemMetrics(SM_CYSCREEN); +} + +void CMSWindow::GetWindowRect(RECT* prc) +{ + ::GetWindowRect(m_hWnd, prc); +} + + +void CMSWindow::GetClientRect(RECT* prc) +{ + ::GetClientRect(m_hWnd, prc); +} + +void CMSWindow::GetMousePosition(POINT* ppt) +{ + GetCursorPos(ppt); + ScreenToClient(m_hWnd, ppt); +} + +void CMSWindow::SetPosition(int x, int y) +{ + SetWindowPos(m_hWnd, NULL, x, y, 0, 0, SWP_NOZORDER|SWP_NOSIZE); +} + +void CMSWindow::SetCenterPosition() +{ + RECT rc; + + GetClientRect(&rc); + + int windowWidth = rc.right - rc.left; + int windowHeight = rc.bottom - rc.top; + + SetPosition((GetScreenWidth()-windowWidth)/2, (GetScreenHeight()-windowHeight)/2); +} + +void CMSWindow::AdjustSize(int width, int height) +{ + SetRect(&m_rect, 0, 0, width, height); + + AdjustWindowRectEx(&m_rect, + GetWindowStyle(m_hWnd), + GetMenu(m_hWnd ) != NULL, + GetWindowExStyle(m_hWnd ) ); + + MoveWindow + ( + m_hWnd, + 0, + 0, + m_rect.right - m_rect.left, + m_rect.bottom - m_rect.top, + FALSE + ); +} + +void CMSWindow::SetText(const char* c_szText) +{ + SetWindowText(m_hWnd, c_szText); +} + +void CMSWindow::SetSize(int width, int height) +{ + SetWindowPos(m_hWnd, NULL, 0, 0, width, height, SWP_NOZORDER|SWP_NOMOVE); +} + +const char * CMSWindow::RegisterWindowClass(DWORD style, int brush, WNDPROC pfnWndProc, HICON hIcon, int iCursorResource) +{ + char szClassName[1024]; + // PORT: 40250 casts the procedure straight to DWORD; the low 32 bits of the 64-bit address, as there. + sprintf(szClassName, "eter - s%x:b%x:p:%x", style, brush, (DWORD) (uintptr_t) pfnWndProc); + + TWindowClassSet::iterator f = ms_stWCSet.find((char*) szClassName); + + if (f != ms_stWCSet.end()) + return *f; + + const char* c_szStaticClassName = stl_static_string(szClassName).c_str(); + + ms_stWCSet.insert((char * const) c_szStaticClassName); + + WNDCLASS wc; + + wc.style = 0; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.lpfnWndProc = pfnWndProc; + wc.hCursor = LoadCursor(ms_hInstance, MAKEINTRESOURCE(iCursorResource)); + wc.hIcon = hIcon ? hIcon : LoadIcon(ms_hInstance, IDI_APPLICATION); + wc.hbrBackground = (HBRUSH) GetStockObject(brush); + wc.hInstance = ms_hInstance; + wc.lpszClassName = c_szStaticClassName; + wc.lpszMenuName = ""; + + if (!RegisterClass(&wc)) + return ""; + + return c_szStaticClassName; +} CMSWindow::CMSWindow() { - MT_PLATFORM_STUB(); + m_hWnd=NULL; + m_isVisible=false; } CMSWindow::~CMSWindow() { - MT_PLATFORM_STUB(); } - -auto CMSWindow::Destroy() -> void -{ - MT_PLATFORM_STUB(); -} - -auto CMSWindow::Create(const char *, int, DWORD, DWORD, HICON, int) -> bool -{ - MT_PLATFORM_STUB(); - return mt_platform_stub_return(); -} - -auto CMSWindow::Show() -> void -{ - MT_PLATFORM_STUB(); -} - -auto CMSWindow::Hide() -> void -{ - MT_PLATFORM_STUB(); -} - -auto CMSWindow::SetVisibleMode(bool) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CMSWindow::SetPosition(int, int) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CMSWindow::SetCenterPosition() -> void -{ - MT_PLATFORM_STUB(); -} - -auto CMSWindow::SetText(const char *) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CMSWindow::AdjustSize(int, int) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CMSWindow::SetSize(int, int) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CMSWindow::IsVisible() -> bool -{ - MT_PLATFORM_STUB(); - return mt_platform_stub_return(); -} - -auto CMSWindow::IsActive() -> bool -{ - MT_PLATFORM_STUB(); - return mt_platform_stub_return(); -} - -namespace { -POINT g_host_cursor = {0, 0}; -} - -void MtHostSetCursor(int x, int y) -{ - g_host_cursor.x = x; - g_host_cursor.y = y; -} - -auto CMSWindow::GetMousePosition(POINT * ppt) -> void -{ - *ppt = g_host_cursor; -} - -auto CMSWindow::GetClientRect(RECT *) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CMSWindow::GetWindowRect(RECT *) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CMSWindow::GetScreenWidth() -> int -{ - MT_PLATFORM_STUB(); - return mt_platform_stub_return(); -} - -auto CMSWindow::GetScreenHeight() -> int -{ - MT_PLATFORM_STUB(); - return mt_platform_stub_return(); -} - -auto CMSWindow::GetWindowHandle() -> HWND -{ - MT_PLATFORM_STUB(); - return mt_platform_stub_return(); -} - -auto CMSWindow::GetInstance() -> HINSTANCE -{ - MT_PLATFORM_STUB(); - return mt_platform_stub_return(); -} - -auto CMSWindow::WindowProcedure(HWND, UINT, WPARAM, LPARAM) -> LRESULT -{ - MT_PLATFORM_STUB(); - return mt_platform_stub_return(); -} - -auto CMSWindow::OnSize(WPARAM, LPARAM) -> void -{ - MT_PLATFORM_STUB(); -} - -auto CMSWindow::RegisterWindowClass(DWORD, int, WNDPROC, HICON, int) -> const char * -{ - MT_PLATFORM_STUB(); - return mt_platform_stub_return(); -} - -decltype(CMSWindow::ms_stWCSet) CMSWindow::ms_stWCSet{}; - -decltype(CMSWindow::ms_hInstance) CMSWindow::ms_hInstance{}; diff --git a/src/platform/ScriptLib/PythonBoot.cpp b/src/platform/ScriptLib/PythonBoot.cpp index e9105ec0..fe34c2e1 100644 --- a/src/platform/ScriptLib/PythonBoot.cpp +++ b/src/platform/ScriptLib/PythonBoot.cpp @@ -1084,9 +1084,10 @@ void Stop() } -// winuser.h (declared in the port's windows.h shim). WM_QUIT ends 40250's CPythonApplication::Loop; -// here the script fiber's loop ends the same way. -void PostQuitMessage(int) +// winuser.h (common/shim/win32/winuser.h). WM_QUIT ends 40250's CPythonApplication::Loop; here the script +// fiber's loop ends the same way. The WM_QUIT is also queued for a GetMessage loop (CMSApplication). +void PostQuitMessage(int nExitCode) { + PostMessage(NULL, WM_QUIT, WPARAM(nExitCode), 0); PythonBoot::AppRequestQuit(); } diff --git a/src/platform/Win32User.cpp b/src/platform/Win32User.cpp new file mode 100644 index 00000000..4fd19e6f --- /dev/null +++ b/src/platform/Win32User.cpp @@ -0,0 +1,361 @@ +// The 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 +#include +#include +#include +#include +#include + +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 g_classes; +std::set g_windows; +HWND g_mainWindow = NULL; +std::deque 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 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 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 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 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 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 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 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 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 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 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 lock(g_mutex); + if (!Alive(hWnd) || !lpRect) + return FALSE; + *lpRect = hWnd->rect; + return TRUE; +} + +BOOL GetClientRect(HWND hWnd, LPRECT lpRect) +{ + std::lock_guard 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 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 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 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 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 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 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; +} diff --git a/src/port/CMakeLists.txt b/src/port/CMakeLists.txt index bebbf9f9..9ba7a78f 100644 --- a/src/port/CMakeLists.txt +++ b/src/port/CMakeLists.txt @@ -202,6 +202,9 @@ if(BUILD_TESTING AND CMAKE_SYSTEM_NAME STREQUAL CMAKE_HOST_SYSTEM_NAME) add_executable(port_process_crc_test ${PROJECT_SOURCE_DIR}/tests/port/port_process_crc_test.cpp) target_link_libraries(port_process_crc_test PRIVATE port_platform) add_test(NAME port.process_crc COMMAND $) + add_executable(port_ms_window_test ${PROJECT_SOURCE_DIR}/tests/port/port_ms_window_test.cpp) + target_link_libraries(port_ms_window_test PRIVATE port_platform) + add_test(NAME port.ms_window COMMAND $) # MilesLib: the 40250 sound managers over the Miles emulation (platform/MilesLib/MilesAil.cpp). add_executable(port_miles_test ${PROJECT_SOURCE_DIR}/tests/port/port_miles_test.cpp) diff --git a/src/port/common/shim/win32/windows.h b/src/port/common/shim/win32/windows.h index d9892a37..d27747c7 100644 --- a/src/port/common/shim/win32/windows.h +++ b/src/port/common/shim/win32/windows.h @@ -8,6 +8,7 @@ #include "../../Win32Types.h" #include "../../Win32Crt.h" #include "wingdi.h" +#include "winuser.h" // winuser.h / wingdi.h constants named in 40250 declarations (default arguments). #define WS_OVERLAPPED 0x00000000L @@ -39,10 +40,7 @@ inline int GetSystemMetrics(int nIndex) { - if (nIndex == SM_CXFULLSCREEN) return 1920; - if (nIndex == SM_CYFULLSCREEN) return 1080; + if (nIndex == SM_CXSCREEN || nIndex == SM_CXFULLSCREEN) return 1920; + if (nIndex == SM_CYSCREEN || nIndex == SM_CYFULLSCREEN) return 1080; return 0; } - -// winuser.h functions the platform layer implements (platform/ScriptLib/PythonBoot.cpp). -void PostQuitMessage(int nExitCode); diff --git a/src/port/common/shim/win32/windowsx.h b/src/port/common/shim/win32/windowsx.h index 720e64a9..26d8f354 100644 --- a/src/port/common/shim/win32/windowsx.h +++ b/src/port/common/shim/win32/windowsx.h @@ -1,2 +1,5 @@ #pragma once #include + +#define GetWindowStyle(hwnd) ((DWORD)GetWindowLong(hwnd, GWL_STYLE)) +#define GetWindowExStyle(hwnd) ((DWORD)GetWindowLong(hwnd, GWL_EXSTYLE)) diff --git a/src/port/common/shim/win32/wingdi.h b/src/port/common/shim/win32/wingdi.h index 5bc6ab19..eeda5cb7 100644 --- a/src/port/common/shim/win32/wingdi.h +++ b/src/port/common/shim/win32/wingdi.h @@ -54,6 +54,7 @@ int SetDIBitsToDevice(HDC hdc, int xDest, int yDest, DWORD w, DWORD h, int xSrc, HFONT CreateFontIndirect(const LOGFONT* lplf); BOOL DeleteObject(HGDIOBJ ho); HGDIOBJ SelectObject(HDC hdc, HGDIOBJ h); +HGDIOBJ GetStockObject(int i); // platform/Win32User.cpp (the window background brush) COLORREF SetTextColor(HDC hdc, COLORREF color); COLORREF SetBkColor(HDC hdc, COLORREF color); int SetBkMode(HDC hdc, int mode); diff --git a/src/port/common/shim/win32/winuser.h b/src/port/common/shim/win32/winuser.h new file mode 100644 index 00000000..3f47d2d5 --- /dev/null +++ b/src/port/common/shim/win32/winuser.h @@ -0,0 +1,107 @@ +#pragma once +// The window and message-queue subset 40250 EterLib/MSWindow.cpp and MSApplication.cpp call, +// implemented by platform/Win32User.cpp over host-side window records (the host owns the real surface). +// The A/W pairs are declared under their plain names: no 40250 caller spells the A or W form. +#include "../../Win32Types.h" +#include "wingdi.h" + +struct HMENU__; +typedef HMENU__* HMENU; +typedef WORD ATOM; + +#define WM_CREATE 0x0001 +#define WM_DESTROY 0x0002 +#define WM_MOVE 0x0003 +#define WM_SIZE 0x0005 +#define WM_CLOSE 0x0010 +#define WM_QUIT 0x0012 +#define WM_ACTIVATEAPP 0x001C + +#define WA_INACTIVE 0 +#define WA_ACTIVE 1 +#define WA_CLICKACTIVE 2 + +#define SIZE_RESTORED 0 +#define SIZE_MINIMIZED 1 + +#define SW_HIDE 0 +#define SW_SHOW 5 + +#define SWP_NOSIZE 0x0001 +#define SWP_NOMOVE 0x0002 +#define SWP_NOZORDER 0x0004 + +#define PM_NOREMOVE 0x0000 +#define PM_REMOVE 0x0001 + +#define GWL_STYLE (-16) +#define GWL_EXSTYLE (-20) +#define GWL_USERDATA (-21) +#define GWLP_USERDATA (-21) + +#define SM_CXSCREEN 0 +#define SM_CYSCREEN 1 + +#define MAKELPARAM(l, h) ((LPARAM)(DWORD)MAKELONG(l, h)) +#define MAKEINTRESOURCE(i) ((LPCSTR)((ULONG_PTR)((WORD)(i)))) +#define IDI_APPLICATION MAKEINTRESOURCE(32512) + +typedef struct tagMSG +{ + HWND hwnd; + UINT message; + WPARAM wParam; + LPARAM lParam; + DWORD time; + POINT pt; +} MSG, *PMSG, *LPMSG; + +typedef struct tagWNDCLASS +{ + UINT style; + WNDPROC lpfnWndProc; + int cbClsExtra; + int cbWndExtra; + HINSTANCE hInstance; + HICON hIcon; + HCURSOR hCursor; + HBRUSH hbrBackground; + LPCSTR lpszMenuName; + LPCSTR lpszClassName; +} WNDCLASS, *PWNDCLASS, *LPWNDCLASS; + +ATOM RegisterClass(const WNDCLASS* lpWndClass); +HWND CreateWindow(LPCSTR lpClassName, LPCSTR lpWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight, + HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam); +BOOL DestroyWindow(HWND hWnd); +BOOL IsWindow(HWND hWnd); +LRESULT DefWindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); + +LONG GetWindowLong(HWND hWnd, int nIndex); +LONG SetWindowLong(HWND hWnd, int nIndex, LONG dwNewLong); +LONG_PTR GetWindowLongPtr(HWND hWnd, int nIndex); +LONG_PTR SetWindowLongPtr(HWND hWnd, int nIndex, LONG_PTR dwNewLong); + +BOOL ShowWindow(HWND hWnd, int nCmdShow); +BOOL SetWindowPos(HWND hWnd, HWND hWndInsertAfter, int X, int Y, int cx, int cy, UINT uFlags); +BOOL MoveWindow(HWND hWnd, int X, int Y, int nWidth, int nHeight, BOOL bRepaint); +BOOL AdjustWindowRectEx(LPRECT lpRect, DWORD dwStyle, BOOL bMenu, DWORD dwExStyle); +BOOL GetWindowRect(HWND hWnd, LPRECT lpRect); +BOOL GetClientRect(HWND hWnd, LPRECT lpRect); +BOOL SetWindowText(HWND hWnd, LPCSTR lpString); +BOOL InvalidateRect(HWND hWnd, const RECT* lpRect, BOOL bErase); +HMENU GetMenu(HWND hWnd); +BOOL SetRect(LPRECT lprc, int xLeft, int yTop, int xRight, int yBottom); + +BOOL GetCursorPos(LPPOINT lpPoint); +BOOL ScreenToClient(HWND hWnd, LPPOINT lpPoint); + +HCURSOR LoadCursor(HINSTANCE hInstance, LPCSTR lpCursorName); +HICON LoadIcon(HINSTANCE hInstance, LPCSTR lpIconName); + +BOOL PostMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); +BOOL PeekMessage(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg); +BOOL GetMessage(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax); +BOOL TranslateMessage(const MSG* lpMsg); +LRESULT DispatchMessage(const MSG* lpMsg); +void PostQuitMessage(int nExitCode); diff --git a/tests/port/port_ms_window_test.cpp b/tests/port/port_ms_window_test.cpp new file mode 100644 index 00000000..31484f9e --- /dev/null +++ b/tests/port/port_ms_window_test.cpp @@ -0,0 +1,91 @@ +// 40250 CMSWindow / CMSApplication over the subset of platform/Win32User.cpp. +#include "EterLib/StdAfx.h" +#include "EterLib/MSApplication.h" +#include "../../src/platform/EterLib/HostCursor.h" + +#include +#include + +namespace { +struct App : CMSApplication +{ + std::vector seen; + LRESULT WindowProcedure(HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam) override + { + seen.push_back(uiMsg); + return CMSApplication::WindowProcedure(hWnd, uiMsg, wParam, lParam); + } + bool Active() const { return m_isActive; } + const char* Register() { return RegisterWindowClass(0, BLACK_BRUSH, DefWindowProc); } +}; +} + +int main() +{ + int failures = 0; + auto check = [&failures](bool ok, const char *name) { + if (!ok) { + std::fprintf(stderr, "FAIL %s\n", name); + ++failures; + } + }; + + App app; + app.Initialize(reinterpret_cast(0x400000)); + check(app.GetInstance() == reinterpret_cast(0x400000), "Initialize sets the shared instance"); + check(app.GetWindowHandle() == NULL && !app.IsVisible(), "constructor: no window, hidden"); + + const char* first = app.Register(); + check(first[0] != '\0' && app.Register() == first, "RegisterWindowClass reuses the class name"); + + check(app.Create("Metin2") && IsWindow(app.GetWindowHandle()), "Create"); + check(!app.IsVisible(), "Create leaves the window hidden"); + + // SetSize: Win32 sends WM_SIZE (SIZE_RESTORED) synchronously -> OnSize marks the window active and visible. + app.SetSize(800, 600); + RECT rc; + app.GetClientRect(&rc); + check(rc.left == 0 && rc.top == 0 && rc.right == 800 && rc.bottom == 600, "SetSize client rect"); + check(app.IsActive() && app.IsVisible(), "WM_SIZE restored -> active, visible"); + check(!app.seen.empty() && app.seen.back() == WM_SIZE, "WM_SIZE reaches the window procedure"); + + app.SetCenterPosition(); + app.GetWindowRect(&rc); + check(rc.left == (1920 - 800) / 2 && rc.top == (1080 - 600) / 2 && rc.right - rc.left == 800, "SetCenterPosition"); + + // The host cursor is in client coordinates; GetCursorPos + ScreenToClient round-trips it. + MtHostSetCursor(5, 7); + POINT pt; + app.GetMousePosition(&pt); + check(pt.x == 5 && pt.y == 7, "GetMousePosition"); + + app.AdjustSize(640, 480); + app.GetWindowRect(&rc); + check(rc.left == 0 && rc.top == 0 && rc.right == 640 && rc.bottom == 480, "AdjustSize moves to 0,0 at the client size"); + + app.Hide(); + check(!app.IsVisible(), "Hide"); + app.SetVisibleMode(true); + check(app.IsVisible(), "SetVisibleMode"); + + // The message queue: IsMessage peeks, MessageProcess dispatches to CMSWindow::WindowProcedure. + check(!app.IsMessage(), "IsMessage on an empty queue"); + PostMessage(app.GetWindowHandle(), WM_ACTIVATEAPP, WA_INACTIVE, 0); + check(app.IsMessage() && app.IsMessage(), "IsMessage does not remove"); + check(app.MessageProcess() && !app.Active(), "WM_ACTIVATEAPP inactive"); + PostMessage(app.GetWindowHandle(), WM_ACTIVATEAPP, WA_CLICKACTIVE, 0); + check(app.MessageProcess() && app.Active(), "WM_ACTIVATEAPP click-active"); + PostMessage(app.GetWindowHandle(), WM_SIZE, SIZE_MINIMIZED, 0); + check(app.MessageProcess() && !app.Active() && !app.IsVisible(), "WM_SIZE minimized -> inactive, hidden"); + + // WM_CLOSE: CMSApplication posts WM_QUIT, DefWindowProc destroys the window, MessageLoop ends on WM_QUIT. + HWND hWnd = app.GetWindowHandle(); + PostMessage(hWnd, WM_CLOSE, 0, 0); + app.seen.clear(); + app.MessageLoop(); + check(app.seen.size() == 2 && app.seen[0] == WM_CLOSE && app.seen[1] == WM_DESTROY, "WM_CLOSE then WM_DESTROY"); + check(!IsWindow(hWnd) && !app.IsMessage(), "window destroyed, queue drained"); + app.Destroy(); + check(app.GetWindowHandle() == NULL, "Destroy clears the handle"); + return failures ? 1 : 0; +}