port 4-a: text input through CPythonIME (login typed, in-game chat)

- UserInterface/PythonIME(+Module) ported verbatim; ime module stubs removed, initime() after initdbg()
- EterLib/IME.cpp in platform/: 40250 buffer/WMChar bodies, IMM/TSF processors no-op; committed
  multi-byte text via PythonBoot::UIChar emulating GCS_RESULTSTR
- GameApplicationAdapter RunIME* -> CWindowManager; Godot keys -> ui_ime_key/ui_char
- port.login_flow/login_live type the account through the IME; chat CG_CHAT round-trip on the fake server
- python_game_render_test logs in with Godot key events

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-09-24 08:35:53 +09:00
co-authored by Claude Opus 5.5
parent 1bda4390d6
commit 6ea0fa9889
21 changed files with 2117 additions and 277 deletions
@@ -88,14 +88,15 @@ public:
}
void BlendEventCamera(const SCameraSetting&, float) override {}
void SetDefaultCamera() override { CPythonApplication::Instance().SetDefaultCamera(); }
void RunIMEUpdate() override {}
void RunIMETabEvent() override {}
void RunIMEReturnEvent() override {}
void RunIMEChangeCodePage() override {}
void RunIMEOpenCandidateListEvent() override {}
void RunIMECloseCandidateListEvent() override {}
void RunIMEOpenReadingWndEvent() override {}
void RunIMECloseReadingWndEvent() override {}
// 40250 CPythonApplication::RunIME* (PythonApplicationEvent.cpp:148-195): CPythonIME's event sink.
void RunIMEUpdate() override { UI::CWindowManager::Instance().RunIMEUpdate(); }
void RunIMETabEvent() override { UI::CWindowManager::Instance().RunIMETabEvent(); }
void RunIMEReturnEvent() override { UI::CWindowManager::Instance().RunIMEReturnEvent(); }
void RunIMEChangeCodePage() override { UI::CWindowManager::Instance().RunChangeCodePage(); }
void RunIMEOpenCandidateListEvent() override { UI::CWindowManager::Instance().RunOpenCandidate(); }
void RunIMECloseCandidateListEvent() override { UI::CWindowManager::Instance().RunCloseCandidate(); }
void RunIMEOpenReadingWndEvent() override { UI::CWindowManager::Instance().RunOpenReading(); }
void RunIMECloseReadingWndEvent() override { UI::CWindowManager::Instance().RunCloseReading(); }
};
std::unique_ptr<GameApplicationAdapter> g_game_application;
// The CPythonApplication members the game phase reaches through Instance() (批次 2V2), in 40250's member
@@ -109,6 +110,7 @@ struct GameSingletons
CItemManager kItemMgr;
CPythonCharacterManager kChrMgr;
CPythonGraphic pyGraphic; // owns CCullingManager (m_CullingManager)
CPythonIME pyIme;
CPythonItem pyItem;
CPythonShop pyShop;
CPythonExchange pyExchange;
@@ -266,6 +268,47 @@ void UIKey(int key, bool pressed)
g_window_manager->RunKeyUp(key);
}
// 40250 CPythonApplication::WindowProcedure (PythonApplicationProcedure.cpp:111-117): WM_CHAR goes to
// CPythonIME::WMChar and WM_KEYDOWN to OnIMEKeyDown (the VK code, next to DirectInput's DIK stream). The host
// delivers characters as Unicode code points; the 40250 window is an ANSI window, so a character that the input
// code page holds as one byte arrives as that byte, like WM_CHAR. PORT: anything else is what a Windows IME
// commits through WM_IME_COMPOSITION/GCS_RESULTSTR, which CIME::ResultProcess inserts as a wide string.
void UIChar(unsigned codepoint)
{
if (!g_game_singletons || !codepoint) return;
CPythonIME& ime = CPythonIME::Instance();
if (codepoint < 0x80)
{
ime.WMChar(NULL, WM_CHAR, codepoint, 0);
return;
}
const wchar_t wide = wchar_t(codepoint);
char bytes[8];
BOOL used_default = FALSE;
const int n = WideCharToMultiByte(CIME::ms_uInputCodePage, 0, &wide, 1, bytes, sizeof(bytes), "?", &used_default);
if (n == 1 && !used_default)
{
ime.WMChar(NULL, WM_CHAR, (unsigned char)bytes[0], 0);
return;
}
// 40250 CIME::WMComposition with GCS_RESULTSTR: ResultProcess inserts the committed string, then OnUpdate.
if (!CIME::ms_bCaptureInput)
return;
struct ResultAccess : CPythonIME
{
static void Insert(CPythonIME& ime, wchar_t* text, int len) { (ime.*(&ResultAccess::InsertString))(text, len); }
};
wchar_t text[1] = { wide };
ResultAccess::Insert(ime, text, 1);
if (CIME::ms_pEvent)
CIME::ms_pEvent->OnUpdate();
}
void UIIMEKeyDown(int vkey)
{
if (g_window_manager) g_window_manager->RunIMEKeyDown(vkey);
}
void UIUpdate()
{
// While system.py is inside app.Loop(), one host frame is one CPythonApplication::Process(),
@@ -341,6 +384,7 @@ bool run_main_script_body(const char* lpCmdLine, std::string* error)
// The module initializers follow 40250's order; missing modules arrive in later 2V steps.
initpack();
initdbg();
initime();
initgrp();
initgrpImage();
initgrpText();