From 2e44cc4e24e0e2f7b7e99d76a676594d4aae5bd7 Mon Sep 17 00:00:00 2001 From: fanlusky <1730976608@qq.com> Date: Sat, 22 Aug 2026 18:34:13 +0800 Subject: [PATCH] Add logs. --- server/CMakeLists.txt | 2 + server/assets/config/config.toml | 2 + server/src/config/ime_config.cpp | 17 ++++ server/src/config/ime_config.h | 2 + server/src/ipc/event_listener.cpp | 31 ++++++- server/src/log/candidate_diag_log.cpp | 109 +++++++++++++++++++++++ server/src/log/candidate_diag_log.h | 27 ++++++ server/src/settings/settings_app.cpp | 5 +- server/src/webview2/windows_webview2.cpp | 43 ++++++++- server/src/webview2/windows_webview2.h | 2 + server/src/window/ime_windows.cpp | 48 +++++++++- 11 files changed, 280 insertions(+), 8 deletions(-) create mode 100644 server/src/log/candidate_diag_log.cpp create mode 100644 server/src/log/candidate_diag_log.h diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 3420383f..f2cd78a1 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -124,6 +124,8 @@ set(SRC_FILES "./src/kaomoji/kaomoji_ime.h" "./src/log/fanylog.cpp" "./src/log/fanylog.h" + "./src/log/candidate_diag_log.cpp" + "./src/log/candidate_diag_log.h" "./src/log/ftb_diag_log.cpp" "./src/log/ftb_diag_log.h" "./src/quick-phrases/quick_phrase_query.cpp" diff --git a/server/assets/config/config.toml b/server/assets/config/config.toml index 363d9e6a..0affda1f 100644 --- a/server/assets/config/config.toml +++ b/server/assets/config/config.toml @@ -10,6 +10,8 @@ theme = "dark" # 通用 # [general] +# 候选框诊断日志(默认关闭;不记录输入内容或候选文本) +candidate_window_diagnostic_log = false # 悬浮工具栏 floating_toolbar = true # 悬浮工具栏组件(中英文切换始终显示) diff --git a/server/src/config/ime_config.cpp b/server/src/config/ime_config.cpp index ee968a5d..64cc9fed 100644 --- a/server/src/config/ime_config.cpp +++ b/server/src/config/ime_config.cpp @@ -1,6 +1,7 @@ #include "ime_config.h" #include #include +#include #include #include #include @@ -53,6 +54,7 @@ std::string g_candidate_english_font = "Segoe UI"; std::string g_candidate_default_font = "Microsoft YaHei"; int g_candidate_font_size = 16; int g_candidate_window_preedit_font_size = 16; +std::atomic_bool g_candidate_window_diagnostic_log_enabled{false}; std::string g_candidate_text_color = "auto"; std::string g_shuangpin_schema = "xiaohe"; std::string g_wubi_schema = "wubi86"; @@ -648,6 +650,8 @@ bool LoadImeConfig() g_show_quanpin_helpcode_in_candidate_window = tbl["helpcode"]["show_qp_helpcode_in_candidate_window"].value_or(true); g_floating_toolbar_enabled = tbl["general"]["floating_toolbar"].value_or(true); + g_candidate_window_diagnostic_log_enabled.store( + tbl["general"]["candidate_window_diagnostic_log"].value_or(false), std::memory_order_relaxed); g_floating_toolbar_items.fullwidth = tbl["general"]["floating_toolbar_fullwidth"].value_or(true); g_floating_toolbar_items.punctuation = tbl["general"]["floating_toolbar_punctuation"].value_or(true); g_floating_toolbar_items.character_set = tbl["general"]["floating_toolbar_character_set"].value_or(true); @@ -1266,6 +1270,19 @@ bool SetConfiguredCandidateWindowPreeditFontSize(int font_size) return true; } +bool GetConfiguredCandidateWindowDiagnosticLogEnabled() +{ + return g_candidate_window_diagnostic_log_enabled.load(std::memory_order_relaxed); +} + +bool SetConfiguredCandidateWindowDiagnosticLogEnabled(bool enabled) +{ + if (!WriteConfiguredValue("general", "candidate_window_diagnostic_log", enabled ? "true" : "false")) + return false; + g_candidate_window_diagnostic_log_enabled.store(enabled, std::memory_order_relaxed); + return true; +} + namespace { bool IsValidCandidateTextColor(const std::string &color) diff --git a/server/src/config/ime_config.h b/server/src/config/ime_config.h index 422f483e..bb893244 100644 --- a/server/src/config/ime_config.h +++ b/server/src/config/ime_config.h @@ -130,6 +130,8 @@ int GetConfiguredCandidateFontSize(); bool SetConfiguredCandidateFontSize(int font_size); int GetConfiguredCandidateWindowPreeditFontSize(); bool SetConfiguredCandidateWindowPreeditFontSize(int font_size); +bool GetConfiguredCandidateWindowDiagnosticLogEnabled(); +bool SetConfiguredCandidateWindowDiagnosticLogEnabled(bool enabled); const std::string &GetConfiguredCandidateTextColor(); bool SetConfiguredCandidateTextColor(const std::string &color); // Installed font family names for settings dropdowns (cached after first call). diff --git a/server/src/ipc/event_listener.cpp b/server/src/ipc/event_listener.cpp index 395912a9..b534cf13 100644 --- a/server/src/ipc/event_listener.cpp +++ b/server/src/ipc/event_listener.cpp @@ -51,6 +51,7 @@ #include "kaomoji/kaomoji_query.h" #include "kaomoji/kaomoji_ime.h" #include "jianpin/jianpin_query.h" +#include "log/candidate_diag_log.h" #include "log/ftb_diag_log.h" #include "voice-input/voice_input_service.h" #include @@ -208,8 +209,11 @@ void RequestShowCandidateWindow() { if (IsUiLessMode() || !::global_hwnd) { + CAND_DIAG_LOGF(L"show request skipped uiless={} hwnd_present={}", IsUiLessMode(), ::global_hwnd != nullptr); return; } + CAND_DIAG_LOGF(L"show request posted raw_units={} candidate_count={}", + GlobalIme::composition.raw_input_with_cases.size(), Global::candidate_ui.items.size()); PostMessage(::global_hwnd, WM_SHOW_MAIN_WINDOW, 0, 0); } @@ -1021,6 +1025,11 @@ void RefreshCandidatePageUi(bool show_window) { ::WriteDataToSharedMemory(string_to_wstring(candidate_string), true); } + CAND_DIAG_LOGF(L"candidate UI refreshed show={} uiless={} items={} page_words={} selected={} page={} " + L"serialized_units={}", + show_window, IsUiLessMode(), Global::candidate_ui.items.size(), + Global::candidate_ui.page_words.size(), Global::candidate_ui.selected_index_in_page, + Global::candidate_ui.page_index, candidate_string.size()); if (show_window) { RequestShowCandidateWindow(); @@ -1030,6 +1039,7 @@ void RefreshCandidatePageUi(bool show_window) void LogPipeConnectResult(const wchar_t *pipe_name, BOOL connected) { const DWORD gle = connected ? ERROR_SUCCESS : GetLastError(); + CAND_DIAG_LOGF(L"pipe connect name={} connected={} gle={}", pipe_name, connected != FALSE, gle); if (connected) { FANY_IPC_LOGF(L"[msime]: [ipc] {} connected", pipe_name); @@ -1042,13 +1052,16 @@ void LogPipeConnectResult(const wchar_t *pipe_name, BOOL connected) void LogPipeReadFailure(const wchar_t *pipe_name, DWORD bytes_read) { + const DWORD gle = GetLastError(); FANY_IPC_LOGF(L"[msime]: [ipc] {} ReadFile failed or returned empty: gle={}, bytes_read={}", pipe_name, - GetLastError(), bytes_read); + gle, bytes_read); + CAND_DIAG_LOGF(L"pipe read failure name={} gle={} bytes={}", pipe_name, gle, bytes_read); } void LogPipeDisconnect(const wchar_t *pipe_name) { FANY_IPC_LOGF(L"[msime]: [ipc] {} disconnected", pipe_name); + CAND_DIAG_LOGF(L"pipe disconnected name={}", pipe_name); } void LogPipeEvent(const wchar_t *pipe_name, UINT event_type, UINT keycode, WCHAR wch, UINT modifiers_down) @@ -1391,6 +1404,9 @@ void WorkerThread() FTB_DIAG_LOGF(L"task {} epoch={} rejected as stale", task.type == TaskType::ClientDeactivated ? L"ClientDeactivated" : L"ClientSuspended", task.activation_epoch); + CAND_DIAG_LOGF(L"candidate lifecycle task rejected stale type={} epoch={}", + task.type == TaskType::ClientDeactivated ? L"deactivated" : L"suspended", + task.activation_epoch); continue; } } @@ -1428,6 +1444,9 @@ void WorkerThread() // refreshes (creating-word progress and candidate context-menu // actions), whose current packet does not carry a valid point. ::ReadDataFromNamedPipe(0b111111); + CAND_DIAG_LOGF(L"task ShowCandidate client={} epoch={} request={} caret=({},{}) input_units={}", + task.client_id, task.activation_epoch, task.pipe_data.request_id, Global::Point[0], + Global::Point[1], GlobalIme::composition.raw_input_with_cases.size()); PrepareCandidateList(task.client_id, task.activation_epoch); RequestShowCandidateWindow(); break; @@ -1435,6 +1454,8 @@ void WorkerThread() case TaskType::HideCandidate: { ::ReadDataFromNamedPipe(0b100000); + CAND_DIAG_LOGF(L"task HideCandidate client={} epoch={} request={}", task.client_id, + task.activation_epoch, task.pipe_data.request_id); PostMessage(::global_hwnd, WM_HIDE_MAIN_WINDOW, 0, 0); /* 清理状态 */ ClearState(); @@ -1444,6 +1465,8 @@ void WorkerThread() case TaskType::MoveCandidate: { static int cnt = 0; ::ReadDataFromNamedPipe(0b001000); + CAND_DIAG_LOGF(L"task MoveCandidate client={} epoch={} caret=({},{})", task.client_id, + task.activation_epoch, Global::Point[0], Global::Point[1]); PostMessage(::global_hwnd, WM_MOVE_CANDIDATE_WINDOW, 0, 0); break; } @@ -1527,6 +1550,8 @@ void WorkerThread() } case TaskType::ClientActivated: { + CAND_DIAG_LOGF(L"client activated client={} epoch={} hwnd_present={}", task.client_id, + task.activation_epoch, ::global_hwnd != nullptr); VoiceInput::SetImeActive(true); // Activation replaces all composition/candidate state from the // previous focus session. A terminal TIP activation also makes @@ -1564,6 +1589,7 @@ void WorkerThread() } case TaskType::ClientDeactivated: { + CAND_DIAG_LOGF(L"client deactivated client={} epoch={}", task.client_id, task.activation_epoch); VoiceInput::SetImeActive(false); // Unlike a route-only suspension, terminal TIP deactivation means // the user switched to another input method. @@ -1583,6 +1609,7 @@ void WorkerThread() } case TaskType::ClientSuspended: { + CAND_DIAG_LOGF(L"client suspended client={} epoch={}", task.client_id, task.activation_epoch); // A suspension rotates the IPC focus session while the TIP may // still own thread focus. It clears candidates just like terminal // deactivation, but never changes floating-toolbar visibility. @@ -2390,6 +2417,8 @@ void MainPipeClientThread(HANDLE clientPipe, uint64_t handlerId) { FANY_IPC_LOGF(L"[msime]: [ipc] ignored inactive main-pipe event: client_id={}, type={}", clientId, pipeData.event_type); + CAND_DIAG_LOGF(L"main-pipe event ignored inactive client={} type={} request={}", clientId, + pipeData.event_type, pipeData.request_id); continue; } diff --git a/server/src/log/candidate_diag_log.cpp b/server/src/log/candidate_diag_log.cpp new file mode 100644 index 00000000..dace4fa2 --- /dev/null +++ b/server/src/log/candidate_diag_log.cpp @@ -0,0 +1,109 @@ +#include "candidate_diag_log.h" + +#include + +#include +#include + +#include "config/ime_config.h" +#include "global/globals.h" +#include "utils/common_utils.h" + +namespace +{ +constexpr unsigned long long kMaxLogBytes = 4ull * 1024ull * 1024ull; + +std::mutex g_log_mutex; +bool g_path_resolved = false; +std::wstring g_log_path; + +const std::wstring &ResolveLogPathUnlocked() +{ + if (g_path_resolved) + return g_log_path; + g_path_resolved = true; + + const std::string local_appdata = CommonUtils::get_local_appdata_path(); + if (local_appdata.empty()) + return g_log_path; + + const std::wstring app_directory = string_to_wstring(local_appdata) + L"\\" + GlobalIme::AppName; + const std::wstring log_directory = app_directory + L"\\logs"; + CreateDirectoryW(app_directory.c_str(), nullptr); + if (!CreateDirectoryW(log_directory.c_str(), nullptr) && GetLastError() != ERROR_ALREADY_EXISTS) + return g_log_path; + + g_log_path = log_directory + L"\\candidate-window-diagnostic.log"; + return g_log_path; +} + +void RotateIfOversizedUnlocked(const std::wstring &path) +{ + WIN32_FILE_ATTRIBUTE_DATA attributes{}; + if (!GetFileAttributesExW(path.c_str(), GetFileExInfoStandard, &attributes)) + return; + const unsigned long long size = + (static_cast(attributes.nFileSizeHigh) << 32) | attributes.nFileSizeLow; + if (size < kMaxLogBytes) + return; + + const std::wstring previous = path + L".1"; + DeleteFileW(previous.c_str()); + MoveFileW(path.c_str(), previous.c_str()); +} + +std::wstring Timestamp() +{ + SYSTEMTIME now{}; + GetLocalTime(&now); + wchar_t buffer[40]{}; + swprintf_s(buffer, L"%04u-%02u-%02u %02u:%02u:%02u.%03u", static_cast(now.wYear), + static_cast(now.wMonth), static_cast(now.wDay), static_cast(now.wHour), + static_cast(now.wMinute), static_cast(now.wSecond), + static_cast(now.wMilliseconds)); + return buffer; +} +} // namespace + +namespace CandidateDiag +{ +bool IsEnabled() +{ + return GetConfiguredCandidateWindowDiagnosticLogEnabled(); +} + +void Write(const std::wstring &line) +{ + try + { + std::lock_guard lock(g_log_mutex); + const std::wstring &path = ResolveLogPathUnlocked(); + if (path.empty()) + return; + RotateIfOversizedUnlocked(path); + + const std::wstring record = Timestamp() + L" [p" + std::to_wstring(GetCurrentProcessId()) + L":t" + + std::to_wstring(GetCurrentThreadId()) + L"] " + line + L"\r\n"; + const std::string utf8 = wstring_to_string(record); + + HANDLE file = CreateFileW(path.c_str(), FILE_APPEND_DATA, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, + OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + if (file == INVALID_HANDLE_VALUE) + return; + if (GetLastError() != ERROR_ALREADY_EXISTS) + { + const char bom[] = "\xEF\xBB\xBF"; + DWORD written = 0; + WriteFile(file, bom, 3, &written, nullptr); + } + + DWORD written = 0; + WriteFile(file, utf8.data(), static_cast(utf8.size()), &written, nullptr); + CloseHandle(file); + } + catch (...) + { + // Diagnostics must never affect the input path. + } +} +} // namespace CandidateDiag diff --git a/server/src/log/candidate_diag_log.h b/server/src/log/candidate_diag_log.h new file mode 100644 index 00000000..133f1254 --- /dev/null +++ b/server/src/log/candidate_diag_log.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include "fmt/xchar.h" + +// Diagnostic trace for candidate-window disappearance investigations. The +// trace deliberately records only state, counts, identifiers, coordinates and +// error codes; user input and candidate text must never be written here. +namespace CandidateDiag +{ +// When disabled, call sites pay for one relaxed atomic load and do not format +// strings or touch the filesystem. +bool IsEnabled(); + +// Appends one timestamped line. Thread-safe, bounded by rotation, never throws. +void Write(const std::wstring &line); +} // namespace CandidateDiag + +#define CAND_DIAG_LOGF(...) \ + do \ + { \ + if (::CandidateDiag::IsEnabled()) \ + { \ + ::CandidateDiag::Write(fmt::format(__VA_ARGS__)); \ + } \ + } while (0) diff --git a/server/src/settings/settings_app.cpp b/server/src/settings/settings_app.cpp index 768b6ad8..c5b68d51 100644 --- a/server/src/settings/settings_app.cpp +++ b/server/src/settings/settings_app.cpp @@ -237,7 +237,8 @@ void PostConfig() {"smart_punctuation_repeat_to_chinese", GetConfiguredSmartPunctuationRepeatToChineseEnabled()}, {"paired_punctuation", GetConfiguredPairedPunctuationEnabled()}}}, {"general", - {{"floating_toolbar", GetConfiguredFloatingToolbarEnabled()}, + {{"candidate_window_diagnostic_log", GetConfiguredCandidateWindowDiagnosticLogEnabled()}, + {"floating_toolbar", GetConfiguredFloatingToolbarEnabled()}, {"floating_toolbar_fullwidth", toolbar.fullwidth}, {"floating_toolbar_punctuation", toolbar.punctuation}, {"floating_toolbar_character_set", toolbar.character_set}, @@ -433,6 +434,8 @@ bool ApplyConfigUpdate(const json::object &data) return SetConfiguredThemeVoice(json::value_to(data.at("value"))); if (path == "general.floating_toolbar") return SetConfiguredFloatingToolbarEnabled(json::value_to(data.at("value"))); + if (path == "general.candidate_window_diagnostic_log") + return SetConfiguredCandidateWindowDiagnosticLogEnabled(json::value_to(data.at("value"))); if (path == "general.floating_toolbar_scale") { const json::value &value = data.at("value"); diff --git a/server/src/webview2/windows_webview2.cpp b/server/src/webview2/windows_webview2.cpp index 318baf12..0d34a260 100644 --- a/server/src/webview2/windows_webview2.cpp +++ b/server/src/webview2/windows_webview2.cpp @@ -18,6 +18,7 @@ #include "ipc/ipc.h" #include "ipc/event_listener.h" #include "ipc/candidate_ui_action_policy.h" +#include "log/candidate_diag_log.h" #include "log/ftb_diag_log.h" #include "settings/settings_launcher.h" #include "utils/window_utils.h" @@ -961,6 +962,11 @@ bool AreSmallWindowWebviewsReady() return AreSmallWindowWebviewsReadyUnlocked(); } +bool IsCandidateWebviewReady() +{ + return candidateNavigationReady && webviewCandWnd != nullptr && webviewControllerCandWnd != nullptr; +} + bool IsFloatingToolbarWebviewReady() { return floatingToolbarNavigationReady && webviewControllerFtbWnd != nullptr; @@ -1085,6 +1091,18 @@ void UpdateSmallWindowWebviewVisibility(HWND hwnd, bool visible) } } +bool GetCandidateWebviewState(bool &isVisible, RECT &bounds) +{ + if (!webviewControllerCandWnd) + return false; + BOOL visible = FALSE; + if (FAILED(webviewControllerCandWnd->get_IsVisible(&visible)) || + FAILED(webviewControllerCandWnd->get_Bounds(&bounds))) + return false; + isVisible = visible != FALSE; + return true; +} + std::wstring ReadHtmlFile(const std::wstring &filePath) { std::wifstream file(filePath); @@ -1606,6 +1624,8 @@ HRESULT OnControllerCreatedCandWnd( // ICoreWebView2Controller *controller // ) { + CAND_DIAG_LOGF(L"webview controller callback hr={:#x} controller_present={} hwnd_valid={}", + static_cast(result), controller != nullptr, IsWindow(hwnd) != FALSE); if (!controller || FAILED(result)) { OnSmallWindowControllerSettled(FAILED(result) ? result : E_FAIL); @@ -1617,6 +1637,7 @@ HRESULT OnControllerCreatedCandWnd( // if (!webviewCandWnd) { + CAND_DIAG_LOGF(L"webview get_CoreWebView2 failed hr={:#x}", static_cast(getWebviewHr)); webviewControllerCandWnd.Reset(); OnSmallWindowControllerSettled(FAILED(getWebviewHr) ? getWebviewHr : E_FAIL); return E_FAIL; @@ -1696,6 +1717,8 @@ HRESULT OnControllerCreatedCandWnd( // // Navigate to HTML HRESULT hr = webviewCandWnd->NavigateToString(HTMLStringCandWnd.c_str()); + CAND_DIAG_LOGF(L"webview NavigateToString hr={:#x} html_chars={} skin={}", static_cast(hr), + HTMLStringCandWnd.size(), string_to_wstring(preparedCandidateSkin)); if (SUCCEEDED(hr)) { loadedCandidateSkin = preparedCandidateSkin; @@ -1847,7 +1870,10 @@ HRESULT OnControllerCreatedCandWnd( // COREWEBVIEW2_WEB_ERROR_STATUS errorStatus = COREWEBVIEW2_WEB_ERROR_STATUS_UNKNOWN; const HRESULT successHr = args->get_IsSuccess(&success); const HRESULT statusHr = args->get_WebErrorStatus(&errorStatus); - (void)0; + CAND_DIAG_LOGF(L"webview navigation completed success={} success_hr={:#x} status_hr={:#x} " + L"web_error={} logical_shown={}", + success != FALSE, static_cast(successHr), static_cast(statusHr), + static_cast(errorStatus), ::is_global_wnd_cand_shown); if (success) { NotifySmallWindowNavigationReady(candidateNavigationReady, L"candidate"); @@ -1882,7 +1908,8 @@ HRESULT OnControllerCreatedCandWnd( // COREWEBVIEW2_PROCESS_FAILED_KIND kind = COREWEBVIEW2_PROCESS_FAILED_KIND_BROWSER_PROCESS_EXITED; const HRESULT hr = args->get_ProcessFailedKind(&kind); - (void)0; + CAND_DIAG_LOGF(L"webview process failed kind={} hr={:#x}", + static_cast(kind), static_cast(hr)); return S_OK; }) .Get(), @@ -2817,6 +2844,15 @@ HRESULT OnControllerCreatedSettingsWnd( // PostSettingsConfig(); } } + else if (path == "general.candidate_window_diagnostic_log") + { + const bool value = json::value_to(data.at("value")); + if (SetConfiguredCandidateWindowDiagnosticLogEnabled(value)) + { + CAND_DIAG_LOGF(L"diagnostic logging enabled from Settings"); + PostSettingsConfig(); + } + } else if (path.rfind("tencent_tmt.", 0) == 0) { const std::string value = json::value_to(data.at("value")); @@ -3167,7 +3203,8 @@ void PostSettingsConfig() {"smart_punctuation_repeat_to_chinese", GetConfiguredSmartPunctuationRepeatToChineseEnabled()}, {"paired_punctuation", GetConfiguredPairedPunctuationEnabled()}}}, {"general", - {{"floating_toolbar", GetConfiguredFloatingToolbarEnabled()}, + {{"candidate_window_diagnostic_log", GetConfiguredCandidateWindowDiagnosticLogEnabled()}, + {"floating_toolbar", GetConfiguredFloatingToolbarEnabled()}, {"floating_toolbar_fullwidth", toolbar.fullwidth}, {"floating_toolbar_punctuation", toolbar.punctuation}, {"floating_toolbar_character_set", toolbar.character_set}, diff --git a/server/src/webview2/windows_webview2.h b/server/src/webview2/windows_webview2.h index 1ccd49cb..30db220f 100644 --- a/server/src/webview2/windows_webview2.h +++ b/server/src/webview2/windows_webview2.h @@ -99,6 +99,7 @@ bool EnsureSmallWindowsTopmost(const wchar_t *reason); // after the WebViews report ready. bool AreSmallWindowsTopmostApplied(); bool AreSmallWindowWebviewsReady(); +bool IsCandidateWebviewReady(); // True once the floating toolbar's WebView2 has completed its first navigation // and can actually paint. Its host must stay on-monitor and "visible" until // then, otherwise WebView2 never finishes raster setup for it. @@ -122,6 +123,7 @@ bool GetFloatingToolbarWebviewState(bool &isVisible, RECT &bounds); // Same for the tray menu, whose blank-but-clickable failure mode needs exactly // this to be separated from a host that was never uncloaked. bool GetTrayMenuWebviewState(bool &isVisible, RECT &bounds); +bool GetCandidateWebviewState(bool &isVisible, RECT &bounds); void LogSmallWindowReadyGate(const wchar_t *context); // Lift the tray menu to the front of the small-window topmost band (e.g. after // FTB was pinned last), renotifying its controller about the band change. Only diff --git a/server/src/window/ime_windows.cpp b/server/src/window/ime_windows.cpp index b9e8643d..6330bc7c 100644 --- a/server/src/window/ime_windows.cpp +++ b/server/src/window/ime_windows.cpp @@ -22,6 +22,7 @@ #include "utils/ime_utils.h" #include "window_hook.h" #include "window/floating_toolbar_visibility_policy.h" +#include "log/candidate_diag_log.h" #include "log/ftb_diag_log.h" #include "voice-input/voice_input_service.h" #include @@ -652,6 +653,16 @@ std::wstring DescribeFloatingToolbarHostState() } } // namespace +std::wstring DescribeCandidateHostState() +{ + bool webview_visible = false; + RECT webview_bounds{}; + const bool has_controller = GetCandidateWebviewState(webview_visible, webview_bounds); + return fmt::format(L"logical_shown={} nav_ready={} {}", ::is_global_wnd_cand_shown, + IsCandidateWebviewReady(), + DescribeHostWindowState(::global_hwnd, has_controller, webview_visible, webview_bounds)); +} + // External linkage, unlike its toolbar counterpart: the menu's own lifecycle // events live in windows_webview2.cpp and are the ones worth recording. std::wstring DescribeTrayMenuHostState() @@ -1052,6 +1063,7 @@ int CreateCandidateWindow(HINSTANCE hInstance) } ::global_hwnd = hwnd_cand; + CAND_DIAG_LOGF(L"candidate host created {}", DescribeCandidateHostState()); // A client can activate while the pipe server is already listening but this // window does not exist yet, which is the race that leaves the toolbar @@ -1205,6 +1217,11 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) if (message == WM_SHOWWINDOW) { UpdateSmallWindowWebviewVisibility(hwnd, wParam != FALSE); + if (hwnd == ::global_hwnd) + { + CAND_DIAG_LOGF(L"host WM_SHOWWINDOW showing={} reason={} {}", wParam != FALSE, + static_cast(lParam), DescribeCandidateHostState()); + } } if (message == WM_APPLY_IME_CONFIG || message == WM_APPLY_IME_INPUT_SCHEME) @@ -1239,6 +1256,14 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) LRESULT CALLBACK WndProcCandWindow(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { + if (message == WM_POWERBROADCAST || message == WM_DISPLAYCHANGE || message == WM_DWMCOMPOSITIONCHANGED || + message == WM_SETTINGCHANGE) + { + CAND_DIAG_LOGF(L"system window event message={:#x} wparam={:#x} lparam={:#x} {}", message, + static_cast(wParam), static_cast(lParam), + DescribeCandidateHostState()); + } + if (message == WM_IMEACTIVATE) { g_is_ime_active = true; @@ -1262,6 +1287,9 @@ LRESULT CALLBACK WndProcCandWindow(HWND hwnd, UINT message, WPARAM wParam, LPARA std::wstring str = preedit + L"," + Global::CandidateString; (void)0; LogSmallWindowReadyGate(L"show-candidate"); + CAND_DIAG_LOGF(L"show message preedit_units={} payload_units={} caret=({},{}) {}", preedit.size(), + Global::CandidateString.size(), Global::Point[0], Global::Point[1], + DescribeCandidateHostState()); if (!EnsureSmallWindowsTopmost(L"show-candidate")) { (void)0; @@ -1284,6 +1312,7 @@ LRESULT CALLBACK WndProcCandWindow(HWND hwnd, UINT message, WPARAM wParam, LPARA if (message == WM_HIDE_MAIN_WINDOW) { + CAND_DIAG_LOGF(L"hide message begin {}", DescribeCandidateHostState()); (void)0; // Clear first so any FineTuneWindow callback already queued bails out. ::is_global_wnd_cand_shown = false; @@ -1312,6 +1341,7 @@ LRESULT CALLBACK WndProcCandWindow(HWND hwnd, UINT message, WPARAM wParam, LPARA } SetHostWindowCloaked(hwnd, true); UpdateHtmlContentWithJavaScript(webviewCandWnd, L""); + CAND_DIAG_LOGF(L"hide message end {}", DescribeCandidateHostState()); /* 候选词部分使用全角空格来占位 */ // std::wstring str = L" , , , , , , , , "; // InflateCandWnd(str); @@ -1321,6 +1351,8 @@ LRESULT CALLBACK WndProcCandWindow(HWND hwnd, UINT message, WPARAM wParam, LPARA if (message == WM_MOVE_CANDIDATE_WINDOW) { + CAND_DIAG_LOGF(L"move message caret=({},{}) {}", Global::Point[0], Global::Point[1], + DescribeCandidateHostState()); FineTuneWindow(hwnd); return 0; } @@ -2448,9 +2480,12 @@ int FineTuneWindow(HWND hwnd) { (void)0; LogSmallWindowReadyGate(L"fine-tune-no-webview"); + CAND_DIAG_LOGF(L"fine-tune skipped: no webview {}", DescribeCandidateHostState()); return 0; } const uint64_t generation = ++g_candidate_finetune_generation; + CAND_DIAG_LOGF(L"fine-tune begin generation={} caret=({},{}) {}", generation, caretX, caretY, + DescribeCandidateHostState()); // Wrap/scroll budget = stable host size (half-screen DIP). Width wraps; // height scrolls inside the card when fonts make the list taller than host. const HalfScreenDipLimits measureLimits = QueryHalfScreenDipLimitsForPoint(caretPt); @@ -2477,12 +2512,15 @@ int FineTuneWindow(HWND hwnd) // measure callback was still pending — do not resurrect it. if (!::is_global_wnd_cand_shown || caretY == Global::INVALID_Y) { - (void)0; + CAND_DIAG_LOGF(L"fine-tune generation={} discarded: hidden={} invalid_caret={}", generation, + !::is_global_wnd_cand_shown, caretY == Global::INVALID_Y); return; } // A newer show/update already queued another FineTune — ignore this one. if (generation != g_candidate_finetune_generation.load()) { + CAND_DIAG_LOGF(L"fine-tune generation={} discarded: superseded_by={}", generation, + g_candidate_finetune_generation.load()); return; } @@ -2694,8 +2732,12 @@ int FineTuneWindow(HWND hwnd) UpdateSmallWindowWebviewVisibility(hwnd, true); RECT actualRect{}; GetWindowRect(hwnd, &actualRect); - (void)actualRect; - (void)0; + CAND_DIAG_LOGF(L"fine-tune generation={} completed size_dip=({:.1f},{:.1f}) " + L"margin=({},{}) rect=({},{},{}x{}) {}", + generation, finalSize.first, finalSize.second, Global::MarginLeft, + Global::MarginTop, actualRect.left, actualRect.top, + actualRect.right - actualRect.left, actualRect.bottom - actualRect.top, + DescribeCandidateHostState()); }; // Pass 2: remeasure the painted card after margins. Keep the stable