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
+40 -4
View File
@@ -244,9 +244,29 @@ int main(int argc, char** argv)
char ports[128];
std::snprintf(ports, sizeof(ports), "_auth_port, _game_port, _slot = %d, %d, %d", auth_port, game_port, slot);
CHECK(py(ports));
CHECK(py("_stream.SetConnectInfo(_host, _game_port, _host, _auth_port)\n"
"_stream.curPhaseWindow.Connect(_login, _password)\n"
"del __main__._login, __main__._password"));
CHECK(py("_stream.SetConnectInfo(_host, _game_port, _host, _auth_port)"));
// Typed into the login board through the 40250 IME (WM_CHAR -> CPythonIME::WMChar, WM_KEYDOWN ->
// OnIMEKeyDown): Tab moves to the password line, Return runs __OnClickLoginButton -> Connect.
// The IME only reaches a rendering EditLine: open the login board as the server list's OK button does.
CHECK(py("_w = _stream.curPhaseWindow\n"
"_w._LoginWindow__OpenLoginBoard()\n"
"_w.idEditLine.SetText('')\n"
"_w.pwdEditLine.SetText('')\n"
"_w.idEditLine.SetFocus()"));
auto type = [](const std::string& text) {
for (unsigned char c : text)
PythonBoot::UIChar(c);
};
type(login + "xy");
PythonBoot::UIIMEKeyDown(VK_LEFT);
PythonBoot::UIIMEKeyDown(VK_DELETE); // y
PythonBoot::UIChar(VK_BACK); // x
CHECK(py("assert _w.idEditLine.GetText() == _login, repr(_w.idEditLine.GetText())"));
PythonBoot::UIChar(VK_TAB);
type(password);
CHECK(py("assert _w.pwdEditLine.GetText() == _password, len(_w.pwdEditLine.GetText())\n"
"del __main__._login, __main__._password, __main__._w"));
PythonBoot::UIChar(VK_RETURN);
// 3. The character list.
const bool selecting = pump_until(live ? 30 : 20, [&] {
@@ -614,6 +634,22 @@ int main(int argc, char** argv)
// uiexchange.CloseDialog armed wndMgr.OnceIgnoreMouseLeftButtonUpEvent (in the client that release belongs
// to the click that closed the dialog); spend it so step 8's click-release is not swallowed.
PythonBoot::UIMouseButton(1, false, 400, 520);
// Chat (批次 4-a): Return on the focused game.GameWindow → OnIMEReturn → interface.ToggleChat opens the
// chat line; the typed text goes through CPythonIME, a second Return sends CG_CHAT (TALKING) and the fake
// server echoes GC_CHAT back.
CHECK(py_true("not _stream.curPhaseWindow.interface.IsOpenChat()"));
PythonBoot::UIChar(VK_RETURN);
CHECK(pump_until(1, [] { return py_true("_stream.curPhaseWindow.interface.IsOpenChat()"); }));
for (char c : std::string("hello"))
PythonBoot::UIChar((unsigned char)c);
pump_until(0.05, [] { return false; });
PythonBoot::UIChar(VK_RETURN);
CHECK(pump_until(2, [&] { return server->Has("game2:chat 0 hello"); }));
// uichat.__SendTalkingChatPacket only resets the line; a Return on the empty line closes it (eventReturn).
CHECK(py_true("_stream.curPhaseWindow.interface.IsOpenChat() and __import__('ime').GetText(0) == ''"));
PythonBoot::UIChar(VK_RETURN);
CHECK(pump_until(1, [] { return py_true("not _stream.curPhaseWindow.interface.IsOpenChat()"); }));
}
// 8. (批次 2V3) Click-to-move: a left click on the ground below the actor reaches game.GameWindow's
@@ -664,7 +700,7 @@ int main(int argc, char** argv)
for (const auto& e : events)
if (e.rfind(prefix, 0) == 0 && e.find(":move ") == std::string::npos &&
e.find(":pickup ") == std::string::npos &&
e.find(":exchange ") == std::string::npos)
e.find(":exchange ") == std::string::npos && e.find(":chat ") == std::string::npos)
got.push_back(e);
CHECK(want == got);
}