#include "global/globals.h" #include "config/ime_config.h" #include "ipc/ipc.h" #include "ime_windows.h" #include "defines/defines.h" #include "defines/globals.h" #include #include #include #include #include #include #include #include "webview2/windows_webview2.h" #include "utils/webview_utils.h" #include "utils/window_utils.h" #include #include "utils/window_utils.h" #include "ipc/event_listener.h" #include "utils/ime_utils.h" #include "window_hook.h" #include "window/floating_toolbar_visibility_policy.h" #include #include "resource/resource.h" #pragma comment(lib, "dwmapi.lib") constexpr UINT_PTR TIMER_ID_INIT_WEBVIEW_CAND = 1; constexpr UINT_PTR TIMER_ID_INIT_WEBVIEW_MENU = 2; constexpr UINT_PTR TIMER_ID_MOVE_WEBVIEW_SETTINGS = 3; constexpr UINT_PTR TIMER_ID_MOVE_WEBVIEW_FTB = 4; constexpr UINT_PTR TIMER_ID_PIN_WINDOWS_TO_TOP = 6; constexpr UINT_PTR TIMER_ID_CONFIG_SYNC = 7; constexpr UINT_PTR TIMER_ID_SETTINGS_ACTIVATION_RETRY = 8; constexpr UINT WM_ACTIVATE_SETTINGS_WINDOW = WM_APP + 110; int FineTuneWindow(HWND hwnd); int FineTuneWindow(HWND hwnd, UINT firstFlag, UINT secondFlag); namespace { int g_settings_activation_retries_remaining = 0; bool g_is_ime_active = false; void ScheduleSettingsWindowActivation(HWND hwnd) { // Mouse activation and Alt+Tab complete asynchronously. A foreground // change can therefore overwrite a single SetForegroundWindow call made // while handling the click. Retry only for a short bounded interval and // stop immediately once Windows confirms this HWND as foreground. g_settings_activation_retries_remaining = 6; PostMessage(hwnd, WM_ACTIVATE_SETTINGS_WINDOW, 0, 0); SetTimer(hwnd, TIMER_ID_SETTINGS_ACTIVATION_RETRY, 50, nullptr); } void CancelSettingsWindowActivation(HWND hwnd) { g_settings_activation_retries_remaining = 0; KillTimer(hwnd, TIMER_ID_SETTINGS_ACTIVATION_RETRY); } } bool ActivateSettingsWindow(HWND hwnd) { if (!IsWindow(hwnd)) { return false; } if (IsIconic(hwnd)) { ShowWindow(hwnd, SW_RESTORE); } else { ShowWindow(hwnd, SW_SHOW); } const HWND foreground = GetForegroundWindow(); const DWORD current_thread = GetCurrentThreadId(); const DWORD foreground_thread = foreground ? GetWindowThreadProcessId(foreground, nullptr) : 0; const bool should_attach_input = foreground_thread != 0 && foreground_thread != current_thread; bool input_attached = false; if (should_attach_input) { input_attached = AttachThreadInput(current_thread, foreground_thread, TRUE) != FALSE; } BringWindowToTop(hwnd); SetForegroundWindow(hwnd); SetActiveWindow(hwnd); SetFocus(hwnd); if (input_attached) { AttachThreadInput(current_thread, foreground_thread, FALSE); } // Detaching can restore some thread-local state, so verify foreground and // reassert this thread's active/focus state after the shared queue has been // separated again. if (GetForegroundWindow() != hwnd) { BringWindowToTop(hwnd); SetForegroundWindow(hwnd); } SetActiveWindow(hwnd); SetFocus(hwnd); return GetForegroundWindow() == hwnd; } void RequestSettingsWindowActivation(HWND hwnd) { if (!IsWindow(hwnd)) { return; } ShowWindow(hwnd, IsIconic(hwnd) ? SW_RESTORE : SW_SHOW); ScheduleSettingsWindowActivation(hwnd); } void ApplyConfiguredFloatingToolbarVisibility() { if (!::global_hwnd_ftb) { return; } const HWND foreground = GetForegroundWindow(); const bool fullscreen = foreground && CheckFullscreen(foreground); const bool should_show = FanyImeUi::ShouldShowFloatingToolbar( GetConfiguredFloatingToolbarEnabled(), fullscreen, g_is_ime_active); const bool is_visible = IsWindowVisible(::global_hwnd_ftb) != FALSE; if (is_visible != should_show) { ShowWindow(::global_hwnd_ftb, should_show ? SW_SHOWNA : SW_HIDE); } } void ApplyConfiguredInputScheme() { FanyNamedPipe::EnqueueReloadInputSessionTask(); } void ApplyConfiguredShuangpinSchema() { FanyNamedPipe::EnqueueReloadInputSessionTask(); } LRESULT RegisterCandidateWindowMessage() { WM_SHOW_MAIN_WINDOW = RegisterWindowMessage(L"WM_SHOW_MAIN_WINDOW"); WM_HIDE_MAIN_WINDOW = RegisterWindowMessage(L"WM_HIDE_MAIN_WINDOW"); WM_MOVE_CANDIDATE_WINDOW = RegisterWindowMessage(L"WM_MOVE_CANDIDATE_WINDOW"); return 0; } LRESULT RegisterIMEWindowsClass(WNDCLASSEX &wcex, HINSTANCE hInstance) { // // 注册窗口类 // wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_IME_ICON)); wcex.hIconSm = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_IME_ICON)); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); /* We do not need background color, otherwise it will flash when rendering */ wcex.hbrBackground = NULL; wcex.lpszMenuName = NULL; wcex.lpszClassName = szWindowClass; wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION); if (!RegisterClassEx(&wcex)) { #ifdef FANY_DEBUG OutputDebugString(fmt::format(L"[msime]: Call to RegisterClassEx failed!").c_str()); #endif return 1; } return 0; } int CreateCandidateWindow(HINSTANCE hInstance) { // // 候选框窗口 // DWORD dwExStyle = WS_EX_LAYERED | // WS_EX_TOOLWINDOW | // WS_EX_NOACTIVATE; // // WS_EX_TOPMOST; // FLOAT scale = GetForegroundWindowScale(); HWND hwnd_cand = CreateWindowEx( // dwExStyle, // szWindowClass, // lpWindowNameCand, // WS_POPUP, // 100, // 100, // (::CANDIDATE_WINDOW_WIDTH + ::SHADOW_WIDTH + ::POP_UP_WND_WIDTH) * scale, // (::CANDIDATE_WINDOW_HEIGHT + ::SHADOW_HEIGHT + ::POP_UP_WND_HEIGHT) * scale, // nullptr, // nullptr, // hInstance, // nullptr // ); // if (!hwnd_cand) { #ifdef FANY_DEBUG OutputDebugString(fmt::format(L"[msime]: Call to CreateWindow for candidate window failed!").c_str()); #endif return 1; } else { // Set the window to be fully not transparent SetLayeredWindowAttributes(hwnd_cand, 0, 255, LWA_ALPHA); MARGINS mar = {-1}; DwmExtendFrameIntoClientArea(hwnd_cand, &mar); } ::global_hwnd = hwnd_cand; SetWindowPos( // hwnd_cand, // HWND_TOP, // -10000, // -10000, // (::CANDIDATE_WINDOW_WIDTH + ::SHADOW_WIDTH + ::POP_UP_WND_WIDTH) * scale, // (::CANDIDATE_WINDOW_HEIGHT + ::SHADOW_HEIGHT + ::POP_UP_WND_HEIGHT) * scale, // SWP_SHOWWINDOW // ); SetWindowPos( // hwnd_cand, // HWND_TOP, // 100, // 100, // (::CANDIDATE_WINDOW_WIDTH + ::SHADOW_WIDTH + ::POP_UP_WND_WIDTH) * scale, // (::CANDIDATE_WINDOW_HEIGHT + ::SHADOW_HEIGHT + ::POP_UP_WND_HEIGHT) * scale, // SWP_SHOWWINDOW // ); // // 任务栏托盘区的菜单窗口 // TODO: 这里的初始 width 和 height 需要设置足够大,不然,底部的 item 会不接受响应。不然,也可以在 wndProc // 中刷新一下 webview // dwExStyle = WS_EX_LAYERED | // WS_EX_TOOLWINDOW | // WS_EX_NOACTIVATE; // // WS_EX_TOPMOST; // HWND hwnd_menu = CreateWindowEx( // dwExStyle, // szWindowClass, // lpWindowNameMenu, // WS_POPUP, // 200, // 200, // (::MENU_WINDOW_WIDTH)*scale, // (::MENU_WINDOW_HEIGHT * 2) * scale, // nullptr, // nullptr, // hInstance, // nullptr // ); // if (!hwnd_menu) { #ifdef FANY_DEBUG OutputDebugString(fmt::format(L"[msime]: Call to CreateWindow for menu failed!").c_str()); #endif return 1; } ::global_hwnd_menu = hwnd_menu; // // floating toolbar 窗口 // dwExStyle = WS_EX_LAYERED | // WS_EX_TOOLWINDOW | // WS_EX_NOACTIVATE; // // WS_EX_TOPMOST; // HWND hwnd_ftb = CreateWindowEx( // dwExStyle, // szWindowClass, // lpWindowNameFtb, // WS_POPUP, // 2216, // -12540, // (::FTB_WND_WIDTH + ::FTB_WND_SHADOW_WIDTH) * scale, // (::FTB_WND_HEIGHT + ::FTB_WND_SHADOW_WIDTH) * scale, // nullptr, // nullptr, // hInstance, // nullptr // ); // if (!hwnd_ftb) { #ifdef FANY_DEBUG OutputDebugString(fmt::format(L"[msime]: Call to CreateWindow for floating toolbar failed!").c_str()); #endif return 1; } ::global_hwnd_ftb = hwnd_ftb; FanyNamedPipe::RegisterStatusSnapshotWindow(hwnd_ftb); // // 候选窗口、菜单窗口、settings 窗口、floating toolbar 窗口、floating toolbar hover tip 窗口 // ShowWindow(hwnd_cand, SW_SHOW); ShowWindow(hwnd_menu, SW_SHOW); ApplyConfiguredFloatingToolbarVisibility(); UpdateWindow(hwnd_cand); UpdateWindow(hwnd_menu); UpdateWindow(hwnd_ftb); // // Preparing webview2 env // PrepareHtmlForWnds(); /* 候选框、托盘语言区右键菜单和 floating toolbar 共用一个 WebView2 environment */ InitSmallWindowWebviews(hwnd_cand, hwnd_menu, hwnd_ftb); /* 调整候选框窗口 size,顺便置顶 */ SetTimer(hwnd_cand, TIMER_ID_INIT_WEBVIEW_CAND, 200, nullptr); /* 调整菜单窗口 size,顺便置顶 */ SetTimer(hwnd_menu, TIMER_ID_INIT_WEBVIEW_MENU, 200, nullptr); /* 监听文本配置文件变化,并同步到运行中的候选框。Settings 已是独立进程。 */ SetTimer(hwnd_cand, TIMER_ID_CONFIG_SYNC, 300, nullptr); /* 调整 floating toolbar 窗口 position,顺便置顶 */ SetTimer(hwnd_ftb, TIMER_ID_MOVE_WEBVIEW_FTB, 200, nullptr); // // 注册一下全局钩子 // g_hHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandle(NULL), 0); if (!g_hHook) { #ifdef FANY_DEBUG OutputDebugString(fmt::format(L"[msime]: Kbd hook for IME failed!").c_str()); #endif return 1; } #ifdef FANY_DEBUG OutputDebugString(fmt::format(L"[msime]: Kbd hook for IME installed.").c_str()); #endif HWINEVENTHOOK hook = SetWinEventHook( // EVENT_SYSTEM_FOREGROUND, // EVENT_OBJECT_LOCATIONCHANGE, // nullptr, // WinEventProc, // 0, // 0, // WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS); MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } /* 卸载钩子 */ UnhookWindowsHookEx(g_hHook); return (int)msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { if (message == WM_SHOWWINDOW) { UpdateSmallWindowWebviewVisibility(hwnd, wParam != FALSE); } /* 候选窗口 */ if (hwnd == ::global_hwnd) { return WndProcCandWindow(hwnd, message, wParam, lParam); } /* tray icon 菜单窗口 */ if (hwnd == ::global_hwnd_menu) { return WndProcMenuWindow(hwnd, message, wParam, lParam); } /* floating toolbar 窗口 */ if (hwnd == ::global_hwnd_ftb) { return WndProcFtbWindow(hwnd, message, wParam, lParam); } return DefWindowProc(hwnd, message, wParam, lParam); } LRESULT CALLBACK WndProcCandWindow(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { if (message == WM_IMEACTIVATE) { g_is_ime_active = true; ApplyConfiguredFloatingToolbarVisibility(); return 0; } if (message == WM_IMEDEACTIVATE) { g_is_ime_active = false; ApplyConfiguredFloatingToolbarVisibility(); return 0; } if (message == WM_SHOW_MAIN_WINDOW) { /* Read candidate string */ ::ReadDataFromSharedMemory(0b1000000); std::wstring preedit = GetPreeditWithCaretMarker(); std::wstring str = preedit + L"," + Global::CandidateString; InflateMeasureDivCandWnd(str); FineTuneWindow(hwnd); ::is_global_wnd_cand_shown = true; return 0; } if (message == WM_HIDE_MAIN_WINDOW) { FLOAT scale = GetForegroundWindowScale(); if (scale < 1.5) { scale = 1.5; } SetWindowPos( // hwnd, // HWND_TOP, // 0, // Global::INVALID_Y, // (::CANDIDATE_WINDOW_WIDTH + ::SHADOW_WIDTH + ::POP_UP_WND_WIDTH) * scale, // (::CANDIDATE_WINDOW_HEIGHT + ::SHADOW_HEIGHT + ::POP_UP_WND_HEIGHT) * scale, // SWP_SHOWWINDOW // ); UpdateHtmlContentWithJavaScript(webviewCandWnd, L""); /* 候选词部分使用全角空格来占位 */ // std::wstring str = L" , , , , , , , , "; // InflateCandWnd(str); ::is_global_wnd_cand_shown = false; return 0; } if (message == WM_MOVE_CANDIDATE_WINDOW) { FineTuneWindow(hwnd); return 0; } if (message == WM_IMESWITCH) { if (wParam == 0) // 此时是英文状态 { /* 更新 floating toolbar 的中英文状态为英文,同时更新标点的全角和半角状态 */ UpdateFtbCnEnAndPuncState(::webviewFtbWnd, 0, 0); } else // 此时是中文状态 { /* 更新 floating toolbar 的中英文状态为中文,同时更新标点的全角和半角状态 */ UpdateFtbCnEnAndPuncState(::webviewFtbWnd, 1, 1); } return 0; } if (message == WM_PUNCSWITCH) { if (wParam == 0) // 此时是中文标点状态 { /* 更新 floating toolbar 的标点全角和半角状态为全角 */ UpdateFtbPuncState(::webviewFtbWnd, 0); } else // 此时是英文标点状态 { /* 更新 floating toolbar 的标点全角和半角状态为半角 */ UpdateFtbPuncState(::webviewFtbWnd, 1); } return 0; } if (message == WM_DOUBLESINGLEBYTESWITCH) { if (wParam == 0) // 此时是半角状态 { /* 更新 floating toolbar 的全角和半角状态为半角 */ UpdateFtbDoubleSingleByteState(::webviewFtbWnd, 0); } else // 此时是全角状态 { /* 更新 floating toolbar 的全角和半角状态为全角 */ UpdateFtbDoubleSingleByteState(::webviewFtbWnd, 1); } return 0; } switch (message) { case WM_TIMER: { if (wParam == TIMER_ID_INIT_WEBVIEW_CAND) { KillTimer(hwnd, TIMER_ID_INIT_WEBVIEW_CAND); if (::webviewCandWnd) { UINT flag = SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOMOVE; SetWindowPos( // ::global_hwnd, // HWND_TOPMOST, // 0, // 0, // 0, // 0, // flag); break; } else { SetTimer(hwnd, TIMER_ID_INIT_WEBVIEW_CAND, 100, nullptr); break; } } else if (wParam == TIMER_ID_CONFIG_SYNC) { const SchemeType previous_input_scheme = GetConfiguredInputScheme(); const std::string previous_shuangpin_schema = GetConfiguredShuangpinSchema(); const std::string previous_layout = GetConfiguredCandidateWindowLayout(); const bool previous_floating_toolbar = GetConfiguredFloatingToolbarEnabled(); const bool previous_comma_period = GetConfiguredPagingCommaPeriodEnabled(); if (ReloadImeConfigIfChanged()) { FanyNamedPipe::EnqueueApplyCandidatePageSizeTask(); if (previous_input_scheme != GetConfiguredInputScheme()) ApplyConfiguredInputScheme(); else if (previous_shuangpin_schema != GetConfiguredShuangpinSchema()) ApplyConfiguredShuangpinSchema(); if (previous_layout != GetConfiguredCandidateWindowLayout()) ApplyConfiguredCandidateWindowLayout(); if (previous_floating_toolbar != GetConfiguredFloatingToolbarEnabled()) ApplyConfiguredFloatingToolbarVisibility(); if (previous_comma_period != GetConfiguredPagingCommaPeriodEnabled()) { SendToTsfWorkerThreadViaNamedpipe( Global::DataFromServerMsgTypeToTsfWorkerThread::PagingCommaPeriodChanged, GetConfiguredPagingCommaPeriodEnabled() ? L"1" : L"0"); } } } break; } case WM_MOUSEACTIVATE: // Stop the window from being activated by mouse click return MA_NOACTIVATE; case WM_ACTIVATE: { if (LOWORD(wParam) != WA_INACTIVE) { ShowWindow(hwnd, SW_SHOWNOACTIVATE); } break; } /* Clear dictionary buffer cache */ case WM_CLS_DICT_CACHE: { FanyNamedPipe::EnqueueResetInputSessionCacheTask(); #ifdef FANY_DEBUG OutputDebugString(fmt::format(L"[msime]: Cleared dictionary buffer cache.").c_str()); #endif break; } case WM_COMMIT_CANDIDATE: { int one_based = static_cast(wParam); #ifdef FANY_DEBUG OutputDebugString(fmt::format(L"[msime]: Really to commit candidate {}", one_based).c_str()); #endif FanyNamedPipe::EnqueueCandidateUiAction(FanyNamedPipe::CandidateUiAction::Commit, one_based); break; } case WM_PIN_TO_TOP_CANDIDATE: { int one_based = static_cast(wParam); #ifdef FANY_DEBUG OutputDebugString(fmt::format(L"[msime]: Really to pin to top candidate {}", one_based).c_str()); #endif FanyNamedPipe::EnqueueCandidateUiAction(FanyNamedPipe::CandidateUiAction::Pin, one_based); break; } case WM_DELETE_CANDIDATE: { int one_based = static_cast(wParam); #ifdef FANY_DEBUG OutputDebugString(fmt::format(L"[msime]: Really to delete candidate {}", one_based).c_str()); #endif FanyNamedPipe::EnqueueCandidateUiAction(FanyNamedPipe::CandidateUiAction::Delete, one_based); break; } case WM_CLEAR_IME_ENGINE_CACHE: { #ifdef FANY_DEBUG OutputDebugString(L"[msime]: Clearing IME engine cache"); #endif /* 清除候选词缓存 */ FanyNamedPipe::EnqueueResetInputSessionCacheTask(); break; } case WM_DESTROY: { PostQuitMessage(0); break; } default: return DefWindowProc(hwnd, message, wParam, lParam); } return 0; } LRESULT CALLBACK WndProcMenuWindow(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_LANGBAR_RIGHTCLICK: { int left = Global::Point[0]; int top = Global::Point[1]; int right = Global::Keycode; int bottom = Global::ModifiersDown; int iconWidth = (right - left) * ::SCALE; int iconHeight = (bottom - top) * ::SCALE; int iconMiddleX = left + iconWidth / 2; int menuX = iconMiddleX - ::MENU_WINDOW_WIDTH / 2; int menuY = top - ::MENU_WINDOW_HEIGHT; // Do not specify SWP_NOZORDER here: it would discard HWND_TOPMOST. UINT flag = SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOACTIVATE; SetLastError(0); BOOL okShowMenu = SetWindowPos( // ::global_hwnd_menu, // HWND_TOPMOST, // menuX, // menuY, // 0, // 0, // flag // ); if (okShowMenu && ::webviewControllerMenuWnd) { RECT bounds{}; GetClientRect(hwnd, &bounds); ::webviewControllerMenuWnd->put_Bounds(bounds); ::webviewControllerMenuWnd->NotifyParentWindowPositionChanged(); UpdateSmallWindowWebviewVisibility(hwnd, true); } SetForegroundWindow(::global_hwnd_menu); /* 安装鼠标钩子 */ if (!g_mouseHook) { g_mouseHook = SetWindowsHookEx(WH_MOUSE_LL, LowLevelMouseProc, nullptr, 0); } break; } case WM_SIZE: { // The menu is resized to match its HTML content after WebView startup. // Keep the controller surface aligned with the resized host HWND so it // can be hidden and shown repeatedly without losing its painted area. if (::webviewControllerMenuWnd && wParam != SIZE_MINIMIZED) { RECT bounds{}; GetClientRect(hwnd, &bounds); ::webviewControllerMenuWnd->put_Bounds(bounds); } break; } case WM_REFRESH_MENU_SIZE: SetTimer(hwnd, TIMER_ID_INIT_WEBVIEW_MENU, 1, nullptr); return 0; case WM_TIMER: { if (wParam == TIMER_ID_INIT_WEBVIEW_MENU) { KillTimer(hwnd, TIMER_ID_INIT_WEBVIEW_MENU); if (::webviewMenuWnd) // 确保 webview 已初始化 { GetContainerSizeMenu(webviewMenuWnd, [hwnd](std::pair containerSize) { if (hwnd == ::global_hwnd_menu) { // UINT flag = SWP_NOZORDER | SWP_NOMOVE | SWP_HIDEWINDOW; UINT flag = SWP_NOMOVE | SWP_HIDEWINDOW | SWP_NOACTIVATE; FLOAT scale = GetForegroundWindowScale(); // CSS layout can produce fractional pixels. Truncating // here makes the viewport fractionally smaller than the // content, which causes Chromium to add both scrollbars. int newWidth = static_cast(std::ceil(containerSize.first * scale)); int newHeight = static_cast(std::ceil(containerSize.second * scale)); ::SCALE = scale; ::MENU_WINDOW_WIDTH = newWidth; ::MENU_WINDOW_HEIGHT = newHeight; /* 调整菜单窗口 size */ SetWindowPos( // hwnd, // HWND_TOPMOST, // 0, // 0, // newWidth, // newHeight, // flag // ); } }); } else { // 如果 webview 还没准备好,再等一会 SetTimer(hwnd, TIMER_ID_INIT_WEBVIEW_MENU, 100, nullptr); } } break; } default: { return DefWindowProc(hwnd, message, wParam, lParam); } } return 0; } int GetTopNcInsetForWindow(HWND hwnd) { const UINT dpi = GetDpiForWindow(hwnd); const DWORD style = static_cast(GetWindowLongPtr(hwnd, GWL_STYLE)); const DWORD exStyle = static_cast(GetWindowLongPtr(hwnd, GWL_EXSTYLE)); RECT withCaption{0, 0, 0, 0}; RECT withoutCaption{0, 0, 0, 0}; AdjustWindowRectExForDpi(&withCaption, style, FALSE, exStyle, dpi); AdjustWindowRectExForDpi(&withoutCaption, style & ~WS_CAPTION, FALSE, exStyle, dpi); const int captionInset = withoutCaption.top - withCaption.top; const int frameInset = GetSystemMetricsForDpi(SM_CYSIZEFRAME, dpi) + GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi); return captionInset + frameInset; } bool IsPointInMaximizeButtonSettingsWnd(HWND hwnd, POINT screenPoint) { if (!hasMaximizeButtonRectSettingsWnd) { return false; } POINT clientPoint = screenPoint; ScreenToClient(hwnd, &clientPoint); return PtInRect(&maximizeButtonRectSettingsWnd, clientPoint) != FALSE; } void PostMaximizeButtonEventSettingsWnd(const char *eventName) { if (!::webviewSettingsWnd || !eventName) { return; } std::string ev(eventName); std::string payload = R"({"type":"maxButtonEvent","data":{"event":")" + ev + R"("}})"; const std::wstring message = string_to_wstring(payload); ::webviewSettingsWnd->PostWebMessageAsJson(message.c_str()); } UINT32 GetMouseVirtualKeysSettingsWnd(WPARAM wParam) { UINT32 keys = COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_NONE; if (wParam & MK_LBUTTON) keys |= COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_LEFT_BUTTON; if (wParam & MK_RBUTTON) keys |= COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_RIGHT_BUTTON; if (wParam & MK_MBUTTON) keys |= COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_MIDDLE_BUTTON; if (wParam & MK_SHIFT) keys |= COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_SHIFT; if (wParam & MK_CONTROL) keys |= COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_CONTROL; if (wParam & MK_XBUTTON1) keys |= COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_X_BUTTON1; if (wParam & MK_XBUTTON2) keys |= COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_X_BUTTON2; return keys; } bool ForwardMouseMessageToWebViewSettingsWnd(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { if (!::webviewCompositionControllerSettingsWnd) return false; COREWEBVIEW2_MOUSE_EVENT_KIND kind{}; UINT32 mouseData = 0; bool handled = true; switch (message) { case WM_MOUSEMOVE: kind = COREWEBVIEW2_MOUSE_EVENT_KIND_MOVE; break; case WM_LBUTTONDOWN: kind = COREWEBVIEW2_MOUSE_EVENT_KIND_LEFT_BUTTON_DOWN; break; case WM_LBUTTONUP: kind = COREWEBVIEW2_MOUSE_EVENT_KIND_LEFT_BUTTON_UP; break; case WM_LBUTTONDBLCLK: kind = COREWEBVIEW2_MOUSE_EVENT_KIND_LEFT_BUTTON_DOUBLE_CLICK; break; case WM_RBUTTONDOWN: kind = COREWEBVIEW2_MOUSE_EVENT_KIND_RIGHT_BUTTON_DOWN; break; case WM_RBUTTONUP: kind = COREWEBVIEW2_MOUSE_EVENT_KIND_RIGHT_BUTTON_UP; break; case WM_RBUTTONDBLCLK: kind = COREWEBVIEW2_MOUSE_EVENT_KIND_RIGHT_BUTTON_DOUBLE_CLICK; break; case WM_MBUTTONDOWN: kind = COREWEBVIEW2_MOUSE_EVENT_KIND_MIDDLE_BUTTON_DOWN; break; case WM_MBUTTONUP: kind = COREWEBVIEW2_MOUSE_EVENT_KIND_MIDDLE_BUTTON_UP; break; case WM_MBUTTONDBLCLK: kind = COREWEBVIEW2_MOUSE_EVENT_KIND_MIDDLE_BUTTON_DOUBLE_CLICK; break; case WM_XBUTTONDOWN: kind = COREWEBVIEW2_MOUSE_EVENT_KIND_X_BUTTON_DOWN; mouseData = GET_XBUTTON_WPARAM(wParam); break; case WM_XBUTTONUP: kind = COREWEBVIEW2_MOUSE_EVENT_KIND_X_BUTTON_UP; mouseData = GET_XBUTTON_WPARAM(wParam); break; case WM_XBUTTONDBLCLK: kind = COREWEBVIEW2_MOUSE_EVENT_KIND_X_BUTTON_DOUBLE_CLICK; mouseData = GET_XBUTTON_WPARAM(wParam); break; case WM_MOUSEWHEEL: kind = COREWEBVIEW2_MOUSE_EVENT_KIND_WHEEL; mouseData = static_cast(GET_WHEEL_DELTA_WPARAM(wParam)); break; case WM_MOUSEHWHEEL: kind = COREWEBVIEW2_MOUSE_EVENT_KIND_HORIZONTAL_WHEEL; mouseData = static_cast(GET_WHEEL_DELTA_WPARAM(wParam)); break; case WM_MOUSELEAVE: kind = COREWEBVIEW2_MOUSE_EVENT_KIND_LEAVE; break; default: handled = false; break; } if (!handled) return false; POINT point{}; if (message == WM_MOUSEWHEEL || message == WM_MOUSEHWHEEL) { point.x = GET_X_LPARAM(lParam); point.y = GET_Y_LPARAM(lParam); ScreenToClient(hwnd, &point); } else { point.x = GET_X_LPARAM(lParam); point.y = GET_Y_LPARAM(lParam); } if (message == WM_MOUSEMOVE) { TRACKMOUSEEVENT tme{}; tme.cbSize = sizeof(tme); tme.dwFlags = TME_LEAVE; tme.hwndTrack = hwnd; TrackMouseEvent(&tme); } ::webviewCompositionControllerSettingsWnd->SendMouseInput( kind, static_cast(GetMouseVirtualKeysSettingsWnd(wParam)), mouseData, point); return true; } LRESULT CALLBACK WndProcSettingsWindow(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_ACTIVATE_SETTINGS_WINDOW: if (!IsWindowVisible(hwnd) || IsIconic(hwnd)) { CancelSettingsWindowActivation(hwnd); return 0; } ActivateSettingsWindow(hwnd); return 0; case WM_MOUSEACTIVATE: // The click itself gives Windows permission to activate this window. // Request foreground synchronously, before button-down, but do not use // ActivateSettingsWindow here: temporarily attaching the two input // queues interferes with subsequent mouse activations. Deferring this // work can also split the down/up pair forwarded to WebView. BringWindowToTop(hwnd); SetForegroundWindow(hwnd); return MA_ACTIVATE; case WM_ACTIVATE: { const LRESULT result = DefWindowProc(hwnd, message, wParam, lParam); if (LOWORD(wParam) != WA_INACTIVE) { // Restoring from the taskbar or switching back from another app // activates the top-level HWND without necessarily producing a new // WM_SETFOCUS. Re-establish both the host and Composition WebView // focus only on that real activation transition. if (GetFocus() != hwnd) { SetFocus(hwnd); } else if (::webviewControllerSettingsWnd) { ::webviewControllerSettingsWnd->MoveFocus(COREWEBVIEW2_MOVE_FOCUS_REASON_PROGRAMMATIC); } } return result; } case WM_SYSCOMMAND: if ((wParam & 0xFFF0) == SC_MINIMIZE) { CancelSettingsWindowActivation(hwnd); } if ((wParam & 0xFFF0) == SC_RESTORE) { const LRESULT result = DefWindowProc(hwnd, message, wParam, lParam); ScheduleSettingsWindowActivation(hwnd); return result; } break; case WM_TIMER: { if (wParam == TIMER_ID_SETTINGS_ACTIVATION_RETRY) { if (!IsWindowVisible(hwnd) || IsIconic(hwnd) || GetForegroundWindow() == hwnd || g_settings_activation_retries_remaining <= 0) { CancelSettingsWindowActivation(hwnd); } else { --g_settings_activation_retries_remaining; PostMessage(hwnd, WM_ACTIVATE_SETTINGS_WINDOW, 0, 0); } } else if (wParam == TIMER_ID_CONFIG_SYNC) { const SchemeType previous_input_scheme = GetConfiguredInputScheme(); const std::string previous_shuangpin_schema = GetConfiguredShuangpinSchema(); const std::string previous_layout = GetConfiguredCandidateWindowLayout(); const bool previous_floating_toolbar = GetConfiguredFloatingToolbarEnabled(); if (ReloadImeConfigIfChanged()) { FanyNamedPipe::EnqueueApplyCandidatePageSizeTask(); if (previous_input_scheme != GetConfiguredInputScheme()) { ApplyConfiguredInputScheme(); } else if (previous_shuangpin_schema != GetConfiguredShuangpinSchema()) { ApplyConfiguredShuangpinSchema(); } if (previous_layout != GetConfiguredCandidateWindowLayout()) { ApplyConfiguredCandidateWindowLayout(); } if (previous_floating_toolbar != GetConfiguredFloatingToolbarEnabled()) { ApplyConfiguredFloatingToolbarVisibility(); } PostSettingsConfig(); } } else if (wParam == TIMER_ID_MOVE_WEBVIEW_SETTINGS) { KillTimer(hwnd, TIMER_ID_MOVE_WEBVIEW_SETTINGS); if (::webviewSettingsWnd) { // 放在屏幕右下角 // 获取主屏幕尺寸 MonitorCoordinates coordinates = GetMainMonitorCoordinates(); // 获取窗口尺寸 RECT rect; GetWindowRect(hwnd, &rect); // 获取任务栏高度 int taskbarHeight = GetTaskbarHeight(); // 移动窗口 SetWindowPos( // hwnd, // 0, // coordinates.right / 2 - (rect.right - rect.left) / 2, // coordinates.bottom / 2 - (rect.bottom - rect.top) / 2 - taskbarHeight, // 0, // 0, // SWP_NOSIZE | SWP_HIDEWINDOW); break; } else { // 如果 webview 还没准备好,再等一会 SetTimer(hwnd, TIMER_ID_MOVE_WEBVIEW_SETTINGS, 100, nullptr); } } break; } case WM_NCCALCSIZE: { if (wParam) { NCCALCSIZE_PARAMS *params = reinterpret_cast(lParam); const LRESULT defResult = DefWindowProc(hwnd, message, wParam, lParam); if (!IsZoomed(hwnd)) { params->rgrc[0].top -= GetTopNcInsetForWindow(hwnd); } else { params->rgrc[0].top -= GetSystemMetrics(SM_CYCAPTION); } return defResult; } break; } case WM_NCHITTEST: { const LRESULT result = DefWindowProcW(hwnd, message, wParam, lParam); if (result == HTCLIENT && hasMaximizeButtonRectSettingsWnd) { POINT screenPoint{GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; if (IsPointInMaximizeButtonSettingsWnd(hwnd, screenPoint)) { return HTMAXBUTTON; } } return result; } case WM_MOVE: case WM_MOVING: if (::webviewControllerSettingsWnd) { ::webviewControllerSettingsWnd->NotifyParentWindowPositionChanged(); } break; case WM_SETFOCUS: if (::webviewControllerSettingsWnd) { ::webviewControllerSettingsWnd->MoveFocus(COREWEBVIEW2_MOVE_FOCUS_REASON_PROGRAMMATIC); } break; case WM_MOUSEMOVE: case WM_MOUSELEAVE: case WM_LBUTTONDOWN: case WM_LBUTTONUP: case WM_LBUTTONDBLCLK: case WM_RBUTTONDOWN: case WM_RBUTTONUP: case WM_RBUTTONDBLCLK: case WM_MBUTTONDOWN: case WM_MBUTTONUP: case WM_MBUTTONDBLCLK: case WM_XBUTTONDOWN: case WM_XBUTTONUP: case WM_XBUTTONDBLCLK: case WM_MOUSEWHEEL: case WM_MOUSEHWHEEL: if (ForwardMouseMessageToWebViewSettingsWnd(hwnd, message, wParam, lParam)) { // Start the bounded foreground retry only after WebView has // received the complete click. Scheduling from WM_MOUSEACTIVATE // can split the down/up pair; scheduling here also handles a real // foreground application (such as Chrome) reclaiming activation // shortly after the click. Hidden windows cancel the retry above. if (message == WM_LBUTTONUP) { ScheduleSettingsWindowActivation(hwnd); } if (message == WM_XBUTTONDOWN || message == WM_XBUTTONUP || message == WM_XBUTTONDBLCLK) { return TRUE; } return 0; } break; case WM_SETCURSOR: if (::webviewCompositionControllerSettingsWnd && LOWORD(lParam) == HTCLIENT) { UINT32 cursorId = 0; if (SUCCEEDED(::webviewCompositionControllerSettingsWnd->get_SystemCursorId(&cursorId)) && cursorId != 0) { SetCursor(LoadCursorW(nullptr, MAKEINTRESOURCEW(cursorId))); return TRUE; } } break; case WM_NCMOUSEMOVE: { POINT screenPoint{GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; if (IsPointInMaximizeButtonSettingsWnd(hwnd, screenPoint)) { if (!isMaximizeButtonHoverSettingsWnd) { isMaximizeButtonHoverSettingsWnd = true; PostMaximizeButtonEventSettingsWnd("enter"); } TRACKMOUSEEVENT tme{}; tme.cbSize = sizeof(tme); tme.dwFlags = TME_LEAVE | TME_NONCLIENT; tme.hwndTrack = hwnd; TrackMouseEvent(&tme); return 0; } break; } case WM_NCMOUSELEAVE: if (isMaximizeButtonHoverSettingsWnd) { isMaximizeButtonHoverSettingsWnd = false; PostMaximizeButtonEventSettingsWnd("leave"); } break; case WM_NCLBUTTONDOWN: { POINT screenPoint{GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; if (IsPointInMaximizeButtonSettingsWnd(hwnd, screenPoint)) { PostMaximizeButtonEventSettingsWnd("down"); return 0; } break; } case WM_NCLBUTTONUP: { POINT screenPoint{GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; if (IsPointInMaximizeButtonSettingsWnd(hwnd, screenPoint)) { PostMaximizeButtonEventSettingsWnd("up"); return 0; } break; } case WM_CLOSE: { // 不销毁窗口,只隐藏 ShowWindow(hwnd, SW_HIDE); return 0; } case WM_ERASEBKGND: { HDC hdc = reinterpret_cast(wParam); RECT rc; GetClientRect(hwnd, &rc); HBRUSH darkBrush = CreateSolidBrush(RGB(32, 32, 32)); FillRect(hdc, &rc, darkBrush); DeleteObject(darkBrush); return 1; } case WM_GETMINMAXINFO: { auto *mmi = reinterpret_cast(lParam); // 设置 settings 窗口最小可拖拽尺寸 mmi->ptMinTrackSize.x = 1200; mmi->ptMinTrackSize.y = 800; return 0; } case WM_SIZE: { if (wParam == SIZE_MINIMIZED) { CancelSettingsWindowActivation(hwnd); } if (::webviewControllerSettingsWnd) { RECT rect; GetClientRect(hwnd, &rect); webviewControllerSettingsWnd->put_Bounds(rect); PostSettingsWindowState(hwnd); } break; } } return DefWindowProc(hwnd, message, wParam, lParam); } LRESULT CALLBACK WndProcFtbWindow(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case UPDATE_FTB_STATUS: { int cnEnState = (wParam >> 2) & 0x1; int doubleSingleByteState = (wParam >> 1) & 0x1; int puncState = wParam & 0x1; UpdateFtbCnEnAndDoubleSingleAndPuncState(::webviewFtbWnd, cnEnState, doubleSingleByteState, puncState); break; } case WM_TIMER: { if (wParam == TIMER_ID_MOVE_WEBVIEW_FTB) { KillTimer(hwnd, TIMER_ID_MOVE_WEBVIEW_FTB); if (::webviewFtbWnd) { // 放在屏幕右下角 // 获取主屏幕尺寸 MonitorCoordinates coordinates = GetMainMonitorCoordinates(); // 获取窗口尺寸 RECT rect; GetWindowRect(hwnd, &rect); // 获取任务栏高度 int taskbarHeight = GetTaskbarHeight(); // 移动窗口 SetWindowPos( // hwnd, // HWND_TOPMOST, // coordinates.right - (rect.right - rect.left) - 10, // coordinates.bottom - (rect.bottom - rect.top) - taskbarHeight - 10, // 0, // 0, // SWP_NOSIZE); break; } else { // 如果 webview 还没准备好,再等一会 SetTimer(hwnd, TIMER_ID_MOVE_WEBVIEW_FTB, 100, nullptr); } } break; } default: { return DefWindowProc(hwnd, message, wParam, lParam); } } return 0; } int FineTuneWindow(HWND hwnd) { UINT flag = SWP_NOZORDER | SWP_SHOWWINDOW; FLOAT scale = GetForegroundWindowScale(); int caretX = Global::Point[0]; int caretY = Global::Point[1]; std::shared_ptr> properPos = std::make_shared>(); GetContainerSizeCand(webviewCandWnd, [flag, // scale, // caretX, // caretY, // properPos, // hwnd](std::pair containerSize) { POINT pt = {caretX, caretY}; /* Whether need to adjust candidate window position */ if (caretY == Global::INVALID_Y) { properPos->first = caretX; properPos->second = caretY; } else { AdjustCandidateWindowPosition(&pt, containerSize, properPos); } std::wstring preedit = GetPreeditWithCaretMarker(); std::wstring str = preedit + L"," + Global::CandidateString; InflateCandWnd(str); int newWidth = 0; int newHeight = 0; UINT newFlag = flag; /* 默认情况下,输入法候选框是不用动的 */ if (containerSize.first > ::CANDIDATE_WINDOW_WIDTH) { newWidth = (containerSize.first + ::SHADOW_WIDTH + ::POP_UP_WND_WIDTH) * scale; newHeight = (::CANDIDATE_WINDOW_HEIGHT + ::SHADOW_HEIGHT + ::POP_UP_WND_HEIGHT) * scale; // newHeight = (containerSize.second + ::SHADOW_WIDTH) * scale; } else { newFlag = flag | SWP_NOSIZE; } SetWindowPos( // hwnd, // nullptr, // properPos->first, // properPos->second, // newWidth, // newHeight, // newFlag // ); }); return 0; }