fix(server): 关闭 Direct2D 设置窗口后悬浮工具栏不再消失 (#1002)

Direct2D 设置窗口是获得键盘焦点的 Win32 窗口,进程里的 TIP 会成为普通输入客户端。
正常退出时 DestroyWindow / CoUninitialize 让 TSF 反激活 TIP,发出终结性反激活,
Server 当成用户切走了输入法而隐藏工具栏;回到的窗口不一定重新激活,工具栏要等
点击别的窗口才回来。WebView2 前端的焦点在 WebView 子进程里,宿主线程从不持有路由,
所以没有这个问题。

Direct2D 前端在写入排空后不再销毁窗口、跳过 CoUninitialize,直接结束进程:
Server 只看到管道断开(仅挂起路由,不动工具栏),与 WebView2 前端一致。
This commit is contained in:
Lu Fan(SonnyCalcr) authored and GitHub committed 2026-10-16 19:28:02 +08:00
1 parent ef28cf690d
commit 81fa7d10c8
1 file changed
+25 -2
+25 -2
View File
@@ -2453,7 +2453,14 @@ LRESULT CALLBACK WindowProc(HWND hwnd, UINT message, WPARAM w_param, LPARAM l_pa
{
g_worker->Drain(!g_closing);
if (g_closing && g_worker->Stopped())
DestroyWindow(hwnd);
{
// The Direct2D frontend leaves without destroying its window: see the
// end of wWinMain for why the process must skip the TSF teardown.
if (g_native)
PostQuitMessage(0);
else
DestroyWindow(hwnd);
}
}
return 0;
case WM_CLOSE:
@@ -2649,7 +2656,10 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR command_line, int show_
DispatchMessageW(&message);
}
g_worker.reset(); // Drain accepted writes even if the window was destroyed externally.
CoUninitialize();
// The Direct2D frontend skips CoUninitialize like it skips DestroyWindow: both tear
// down this thread's TSF state (see below).
if (!use_native)
CoUninitialize();
if (g_relaunch_after_exit)
{
// The renderer was switched: reopen in it once this instance no longer holds the
@@ -2663,5 +2673,18 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR command_line, int show_
ShellExecuteW(nullptr, L"open", module_path.c_str(), nullptr, nullptr, SW_SHOWNORMAL);
}
}
if (use_native)
{
// The Direct2D window is a focused Win32 window, so this thread is an ordinary
// input client of the IME. Destroying the window or uninitializing COM lets TSF
// deactivate the text service here, which reports a terminal deactivation, and
// the Server hides the floating toolbar as if the user had switched input
// methods. The window the user returns to may not re-activate, so the toolbar
// stayed hidden until they clicked elsewhere. Every write has been drained
// above; end the process without that teardown, so the Server only sees the
// pipes close (a route suspension, which leaves the toolbar alone), as it does
// for the WebView2 frontend.
TerminateProcess(GetCurrentProcess(), static_cast<UINT>(message.wParam));
}
return static_cast<int>(message.wParam);
}