//SPDX-License-Identifier: LGPL-3.0-only //SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® // The Win32 backend's text, through a real HWND. The library speaks UTF-8 // and Windows UTF-16, and the window class decides what a window procedure // hears: one registered with RegisterClassA gets text in the process code // page, which is CP1252 on a Western system. // // - the title given to the constructor is the window's: SetTitle had no // Win32 body, so every Win32 window was untitled // - SetTitle replaces it, and characters outside the code page survive. // On an ANSI class they would not: WM_SETTEXT would reach // DefWindowProcA in the code page, and 日本 would come back as ?? // - SetTitle takes only the view's bytes, though the string goes on past // them, and an empty title clears the old one // - StartSync's loop delivers WM_CHAR as UTF-16 code units, as the handler // always assumed. On the ANSI class € arrived as CP1252's 0x80, taken // for U+0080, and 日 and both halves of a surrogate pair as ?. A loop // that pumped with PeekMessageA would still turn 日 and the surrogates // into ?, whatever the class // // The window is disabled, so no real keystroke reaches the WM_CHAR check, // while the messages posted to it still arrive. Needs a Vulkan device, for // the Window's swapchain. Built on Linux, it runs under Wine: // crafter-build test --target=x86_64-w64-mingw32 TextWin32 #include #include import Crafter.Graphics; import Crafter.Event; import std; using namespace Crafter; namespace { int failures = 0; void Check(bool ok, std::string_view what) { std::println("{} {}", ok ? "PASS" : "FAIL", what); if (!ok) ++failures; } // A string's code units in hex, for a failed check to say what it got. template std::string Units(std::basic_string_view text) { std::string out; for (const Char unit : text) { out += std::format(" {:0{}X}", static_cast>(unit), sizeof(Char) * 2); } return out; } // The title as Windows has it, which is what the title bar, the taskbar // and Alt+Tab show. void CheckTitle(HWND hwnd, std::wstring_view expected, std::string_view what) { std::wstring title(static_cast(GetWindowTextLengthW(hwnd)) + 1, L'\0'); title.resize(static_cast(GetWindowTextW(hwnd, title.data(), static_cast(title.size())))); Check(title == expected, what); if (title != expected) std::println(" the window has [{} ]", Units(std::wstring_view(title))); } } // namespace int main() { Device::Initialize(); auto window = std::make_unique(320, 240, "Crafter"); const HWND hwnd = window->hwnd; // Windows activates the window when it is shown, so keys typed now // would reach it. A disabled window gets no keyboard input, and what // was typed before that is dropped. EnableWindow(hwnd, FALSE); MSG msg; while (PeekMessageW(&msg, hwnd, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE)) {} CheckTitle(hwnd, L"Crafter", "the constructor's title"); // é and € are in CP1252, 日本 and 😀 in no Western code page. window->SetTitle("café € 日本 \U0001F600"); CheckTitle(hwnd, L"café € 日本 \U0001F600", "SetTitle, with characters outside the code page"); const std::string longer = "Crafter.Graphics"; window->SetTitle(std::string_view(longer).substr(0, 7)); CheckTitle(hwnd, L"Crafter", "SetTitle takes the view's bytes, not the string's up to its NUL"); window->SetTitle(""); CheckTitle(hwnd, L"", "an empty title clears the old one"); std::vector typed; EventListener textSub(&window->onTextInput, [&](const std::string_view text) { typed.emplace_back(text); }); // As TranslateMessage posts them, a UTF-16 code unit each: é is 0xE9 in // CP1252 and Unicode alike, € is 0x80 in CP1252, 日 is in no Western // code page, and 😀 is a surrogate pair. StartSync's own loop delivers // them, and returns once the WM_CLOSE after them has cleared `open`. for (const wchar_t unit : std::wstring_view(L"é€日\U0001F600")) { PostMessageW(hwnd, WM_CHAR, unit, 1); } PostMessageW(hwnd, WM_CLOSE, 0, 0); window->StartSync(); const std::vector expected{ "é", "€", "日", "\U0001F600" }; Check(typed == expected, "WM_CHAR through StartSync: each character as itself in UTF-8, the surrogate pair as one"); if (typed != expected) { for (const std::string& text : typed) std::println(" onTextInput had [{} ]", Units(std::string_view(text))); } if (failures != 0) { std::println("{} check(s) failed", failures); return EXIT_FAILURE; } std::println("all checks passed"); return EXIT_SUCCESS; }