Files
msime-windows/server/src/window/ime_windows.cpp
T
2025-11-03 13:12:51 +08:00

584 lines
19 KiB
C++

#include "global/globals.h"
#include "ipc/ipc.h"
#include "ime_windows.h"
#include "defines/defines.h"
#include "defines/globals.h"
#include <debugapi.h>
#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 <windowsx.h>
#include "resource/resource.h"
#pragma comment(lib, "dwmapi.lib")
constexpr UINT_PTR TIMER_ID_INIT_WEBVIEW = 1;
int FineTuneWindow(HWND hwnd);
int FineTuneWindow(HWND hwnd, UINT firstFlag, UINT secondFlag);
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))
{
OutputDebugString(fmt::format(L"Call to RegisterClassEx failed!\n").c_str());
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 = CreateWindowEx( //
dwExStyle, //
szWindowClass, //
lpWindowNameCand, //
WS_POPUP, //
100, //
100, //
(::CANDIDATE_WINDOW_WIDTH + ::SHADOW_WIDTH) * scale, //
(::CANDIDATE_WINDOW_HEIGHT + ::SHADOW_WIDTH) * scale, //
nullptr, //
nullptr, //
hInstance, //
nullptr //
); //
if (!hwnd)
{
OutputDebugString(fmt::format(L"Call to CreateWindow for candidate window failed!\n").c_str());
return 1;
}
else
{
// Set the window to be fully not transparent
SetLayeredWindowAttributes(hwnd, 0, 255, LWA_ALPHA);
MARGINS mar = {-1};
DwmExtendFrameIntoClientArea(hwnd, &mar);
}
::global_hwnd = hwnd;
SetWindowPos( //
hwnd, //
HWND_TOPMOST, //
-10000, //
-10000, //
(::CANDIDATE_WINDOW_WIDTH + ::SHADOW_WIDTH) * scale, //
(::CANDIDATE_WINDOW_HEIGHT + ::SHADOW_WIDTH) * scale, //
SWP_SHOWWINDOW //
);
SetWindowPos( //
hwnd, //
HWND_TOPMOST, //
100, //
100, //
(::CANDIDATE_WINDOW_WIDTH + ::SHADOW_WIDTH) * scale, //
(::CANDIDATE_WINDOW_HEIGHT + ::SHADOW_WIDTH) * 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)
{
OutputDebugString(fmt::format(L"Call to CreateWindow for menu failed!\n").c_str());
return 1;
}
::global_hwnd_menu = hwnd_menu;
//
// settings 窗口
//
dwExStyle = 0; //
HWND hwnd_settings = CreateWindowEx( //
dwExStyle, //
szWindowClass, //
lpWindowNameSettings, //
WS_OVERLAPPEDWINDOW, //
400, //
400, //
(::SETTINGS_WINDOW_WIDTH)*scale, //
(::SETTINGS_WINDOW_HEIGHT)*scale, //
nullptr, //
nullptr, //
hInstance, //
nullptr //
); //
if (!hwnd_settings)
{
OutputDebugString(fmt::format(L"Call to CreateWindow for settings failed!\n").c_str());
return 1;
}
// 使用 DWM 允许透明
DWM_BLURBEHIND bb = {0};
bb.dwFlags = DWM_BB_ENABLE;
bb.fEnable = TRUE;
bb.hRgnBlur = nullptr;
DwmEnableBlurBehindWindow(hwnd_settings, &bb);
BOOL useDarkMode = TRUE;
DwmSetWindowAttribute( //
hwnd_settings, //
DWMWA_USE_IMMERSIVE_DARK_MODE, //
&useDarkMode, //
sizeof(useDarkMode) //
);
::global_hwnd_settings = hwnd_settings;
BOOL cloak = TRUE;
DwmSetWindowAttribute(hwnd_settings, DWMWA_CLOAK, &cloak, sizeof(cloak));
//
// floating toolbar 窗口
//
dwExStyle = WS_EX_LAYERED | //
WS_EX_TOOLWINDOW | //
WS_EX_NOACTIVATE | //
WS_EX_TOPMOST; //
HWND hwnd_ftb = CreateWindowEx( //
dwExStyle, //
szWindowClass, //
lpWindowNameFtb, //
WS_POPUP, //
800, //
800, //
(::FTB_WND_WIDTH + ::FTB_WND_SHADOW_WIDTH) * scale, //
(::FTB_WND_HEIGHT + ::FTB_WND_SHADOW_WIDTH) * scale, //
nullptr, //
nullptr, //
hInstance, //
nullptr //
); //
if (!hwnd_ftb)
{
OutputDebugString(fmt::format(L"Call to CreateWindow for floating toolbar failed!\n").c_str());
return 1;
}
::global_hwnd_ftb = hwnd_ftb;
//
// 候选窗口、菜单窗口、settings 窗口、floating toolbar 窗口、floating toolbar hover tip 窗口
//
ShowWindow(hwnd, SW_SHOW);
ShowWindow(hwnd_menu, SW_SHOW);
ShowWindow(hwnd_settings, SW_SHOWMINIMIZED);
ShowWindow(hwnd_settings, SW_RESTORE);
ShowWindow(hwnd_ftb, SW_SHOW);
UpdateWindow(hwnd);
UpdateWindow(hwnd_menu);
UpdateWindow(hwnd_settings);
UpdateWindow(hwnd_ftb);
//
// Preparing webview2 env
//
PrepareHtmlForWnds();
/* 候选框窗口 */
InitWebviewCandWnd(hwnd);
/* 托盘语言区右键菜单窗口 */
InitWebviewMenuWnd(hwnd_menu);
/* settings 窗口 */
InitWebviewSettingsWnd(hwnd_settings);
/* flaoting toolbar 窗口 */
InitWebviewFtbWnd(hwnd_ftb);
/* 调整菜单窗口 size */
SetTimer(hwnd_menu, TIMER_ID_INIT_WEBVIEW, 200, nullptr);
//
// 注册一下全局钩子
//
g_hHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandle(NULL), 0);
if (!g_hHook)
{
OutputDebugString(fmt::format(L"键盘钩子安装失败\n").c_str());
return 1;
}
OutputDebugString(fmt::format(L"键盘钩子安装成功").c_str());
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 (hwnd == ::global_hwnd)
{
return WndProcCandWindow(hwnd, message, wParam, lParam);
}
/* tray icon 菜单窗口 */
if (hwnd == ::global_hwnd_menu)
{
return WndProcMenuWindow(hwnd, message, wParam, lParam);
}
/* settings 窗口 */
if (hwnd == ::global_hwnd_settings)
{
return WndProcSettingsWindow(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_SHOW_MAIN_WINDOW)
{
/* Read candidate string */
::ReadDataFromSharedMemory(0b1000000);
std::wstring preedit = GetPreedit();
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_TOPMOST, //
0, //
Global::INVALID_Y, //
(::CANDIDATE_WINDOW_WIDTH + ::SHADOW_WIDTH) * scale, //
(::CANDIDATE_WINDOW_HEIGHT + ::SHADOW_WIDTH) * scale, //
SWP_SHOWWINDOW //
);
UpdateHtmlContentWithJavaScript(webviewCandWnd, L"");
std::wstring str = L"n,那,年,女,难,内,你,男,哪";
// InflateCandidateWindow(str);
::is_global_wnd_cand_shown = false;
return 0;
}
if (message == WM_MOVE_CANDIDATE_WINDOW)
{
FineTuneWindow(hwnd);
return 0;
}
switch (message)
{
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: {
g_dictQuery->reset_cache();
OutputDebugString(fmt::format(L"Cleared dictionary buffer cache.").c_str());
break;
}
case WM_DELETE_CANDIDATE: {
int one_based = static_cast<int>(wParam);
int zero_based = one_based - 1;
OutputDebugString(fmt::format(L"Really to delete candidate {}\n", one_based).c_str());
if (one_based > Global::CandidateWordList.size())
{
break;
}
//
// 在词库中删除
//
/* 先取出拼音和汉字 */
DictionaryUlPb::WordItem curWordItem =
Global::CandidateList[zero_based + Global::PageIndex * Global::CountOfOnePage];
std::string curWord = std::get<1>(curWordItem);
std::string curWordPinyin = std::get<0>(curWordItem);
/* 删除条目 */
g_dictQuery->delete_by_pinyin_and_word(curWordPinyin, curWord);
/* 刷新候选窗列表 */
g_dictQuery->reset_cache();
g_dictQuery->handleVkCode(0, 0); // 重新查一次
/* 刷新窗口 */
FanyNamedPipe::PrepareCandidateList();
PostMessage(hwnd, WM_SHOW_MAIN_WINDOW, 0, 0);
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;
UINT flag = SWP_NOZORDER | SWP_NOSIZE | SWP_SHOWWINDOW;
SetWindowPos( //
hwnd, //
nullptr, //
menuX, //
menuY, //
0, //
0, //
flag //
);
/* 安装鼠标钩子 */
g_mouseHook = SetWindowsHookEx(WH_MOUSE_LL, LowLevelMouseProc, nullptr, 0);
break;
}
case WM_TIMER: {
if (wParam == TIMER_ID_INIT_WEBVIEW)
{
KillTimer(hwnd, TIMER_ID_INIT_WEBVIEW);
if (webviewMenuWnd) // 确保 webview 已初始化
{
GetContainerSize(webviewMenuWnd, [hwnd](std::pair<double, double> containerSize) {
if (hwnd == ::global_hwnd_menu)
{
UINT flag = SWP_NOZORDER | SWP_NOMOVE;
FLOAT scale = GetForegroundWindowScale();
int newWidth = (containerSize.first) * scale;
int newHeight = (containerSize.second) * scale;
::SCALE = scale;
::MENU_WINDOW_WIDTH = newWidth;
::MENU_WINDOW_HEIGHT = newHeight;
/* 调整菜单窗口 size */
SetWindowPos( //
hwnd, //
nullptr, //
0, //
0, //
newWidth, //
newHeight, //
flag //
);
}
});
}
else
{
// 如果 webview 还没准备好,再等一会
SetTimer(hwnd, TIMER_ID_INIT_WEBVIEW, 100, nullptr);
}
}
break;
}
default: {
return DefWindowProc(hwnd, message, wParam, lParam);
}
}
return 0;
}
LRESULT CALLBACK WndProcSettingsWindow(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_ERASEBKGND: { // Make the background dark
// HDC hdc = (HDC)wParam;
// RECT rc;
// GetClientRect(hwnd, &rc);
// HBRUSH darkBrush = CreateSolidBrush(RGB(32, 32, 32));
// FillRect(hdc, &rc, darkBrush);
// DeleteObject(darkBrush);
return 1;
}
case WM_SIZE: {
if (::webviewControllerSettingsWnd)
{
RECT rect;
GetClientRect(hwnd, &rect);
webviewControllerSettingsWnd->put_Bounds(rect);
}
break;
}
default: {
return DefWindowProc(hwnd, message, wParam, lParam);
}
}
return 0;
}
LRESULT CALLBACK WndProcFtbWindow(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
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>>();
GetContainerSize(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 = GetPreedit();
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) * scale;
newHeight = (::CANDIDATE_WINDOW_HEIGHT + ::SHADOW_WIDTH) * scale;
}
else
{
newFlag = flag | SWP_NOSIZE;
}
SetWindowPos( //
hwnd, //
nullptr, //
properPos->first, //
properPos->second, //
newWidth, //
newHeight, //
newFlag //
);
});
return 0;
}