Files
msime-windows/server/src/window/ime_windows.cpp
T

1323 lines
46 KiB
C++
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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 <debugapi.h>
#include <cmath>
#include <minwindef.h>
#include <string>
#include <windef.h>
#include <winuser.h>
#include <fmt/xchar.h>
#include "webview2/windows_webview2.h"
#include "utils/webview_utils.h"
#include "utils/window_utils.h"
#include <dwmapi.h>
#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 <windowsx.h>
#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<int>(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<int>(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<int>(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<double, double> 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<int>(std::ceil(containerSize.first * scale));
int newHeight = static_cast<int>(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<DWORD>(GetWindowLongPtr(hwnd, GWL_STYLE));
const DWORD exStyle = static_cast<DWORD>(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<UINT32>(GET_WHEEL_DELTA_WPARAM(wParam));
break;
case WM_MOUSEHWHEEL:
kind = COREWEBVIEW2_MOUSE_EVENT_KIND_HORIZONTAL_WHEEL;
mouseData = static_cast<UINT32>(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<COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS>(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<NCCALCSIZE_PARAMS *>(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<HDC>(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<MINMAXINFO *>(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<std::pair<int, int>> properPos = std::make_shared<std::pair<int, int>>();
GetContainerSizeCand(webviewCandWnd, [flag, //
scale, //
caretX, //
caretY, //
properPos, //
hwnd](std::pair<double, double> 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;
}