Files
msime-windows/server/src/window/window_hook.cpp
T
fanlusky a8c10a63a6 feat(settings): add a restart-server button under the UI backend setting
界面渲染 only takes effect at Server startup, so the setting already told the
user to restart the IME process but offered no way to do it outside the
Ctrl+Shift+Alt+R developer shortcut. Add a 重启 button in the same section.

Settings runs in its own process, so the request travels the Aux pipe — the
supported cross-integrity control path the config notifications already use —
and the Server exits with the Watchdog restart code. The shortcut and the
button now share RestartServerProcess().
2026-09-12 16:41:13 +08:00

223 lines
7.0 KiB
C++

#include "window_hook.h"
#include "config/ime_config.h"
#include "ime_windows.h"
#include "settings/settings_launcher.h"
#include "utils/webview_utils.h"
#include "webview2/windows_webview2.h"
#include "defines/globals.h"
#include "defines/defines.h"
#include "watchdog/watchdog_protocol.h"
#include "log/ftb_diag_log.h"
#include <atomic>
#include <fmt/xchar.h>
#include <winuser.h>
void OnWinEvent(HWND hwnd);
namespace
{
std::atomic<int> g_caps_lock{0};
}
void InitServerCapsLockState()
{
g_caps_lock.store((GetKeyState(VK_CAPITAL) & 0x0001) != 0 ? 1 : 0, std::memory_order_relaxed);
}
int GetServerCapsLockState()
{
return g_caps_lock.load(std::memory_order_relaxed);
}
bool IsKeyPressed(int vk)
{
return (GetAsyncKeyState(vk) & 0x8000) != 0;
}
void RestartServerProcess()
{
if (g_hHook)
UnhookWindowsHookEx(g_hHook);
ExitProcess(WatchdogProtocol::kRestartExitCode);
}
LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
// When Alt is held, Windows normally reports the following non-system key
// as WM_SYSKEYDOWN rather than WM_KEYDOWN. Handle both; otherwise all of
// the Ctrl+Shift+Alt shortcuts depend on modifier press order.
if (nCode == HC_ACTION && (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN))
{
KBDLLHOOKSTRUCT *p = (KBDLLHOOKSTRUCT *)lParam;
if (p->vkCode == VK_CAPITAL && (p->flags & LLKHF_UP) == 0)
{
const int next = g_caps_lock.load(std::memory_order_relaxed) ? 0 : 1;
g_caps_lock.store(next, std::memory_order_relaxed);
if (::global_hwnd_ftb && IsWindow(::global_hwnd_ftb))
{
PostMessage(::global_hwnd_ftb, UPDATE_FTB_CAPS_LOCK, static_cast<WPARAM>(next), 0);
}
}
bool ctrl = IsKeyPressed(VK_CONTROL);
bool alt = IsKeyPressed(VK_MENU);
bool shift = IsKeyPressed(VK_SHIFT);
bool win = IsKeyPressed(VK_LWIN) || IsKeyPressed(VK_RWIN);
// Ctrl + Shift + Win + K opens the standalone screen keyboard. Consume the
// shortcut so the foreground application does not receive a stray K.
if (ctrl && shift && win && !alt && p->vkCode == 'K')
{
OpenKeyboardPanelApplication();
return 1;
}
//
// Ctrl + Shift + Alt + T to terminate
//
if (ctrl && shift && alt && p->vkCode == 'T')
{
CloseSettingsApplication();
CloseEmojiPanelApplication();
CloseKeyboardPanelApplication();
CloseHandwritingPanelApplication();
ExitProcess(WatchdogProtocol::kStopExitCode);
}
//
// Ctrl + Shift + Alt + R to restart
//
if (ctrl && shift && alt && p->vkCode == 'R')
{
RestartServerProcess();
}
//
// Ctrl + Shift + Alt + C to clear ime engine cache
//
if (ctrl && shift && alt && p->vkCode == 'C')
{
PostMessage(::global_hwnd, WM_CLEAR_IME_ENGINE_CACHE, 0, 0);
}
//
// Ctrl + Alt + Shift + 1-8 to delete candidate
//
if (!::is_global_wnd_cand_shown)
{
return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}
if (ctrl && alt && shift)
{
// 只处理主键盘数字
int idx = -1;
if (p->vkCode >= '1' && p->vkCode <= '8')
idx = p->vkCode - '1';
if (idx >= 0)
{
// 执行候选项删除逻辑,PostMessage 给窗口过程去执行
#ifdef FANY_DEBUG
(void)0;
#endif
PostMessage(::global_hwnd, WM_DELETE_CANDIDATE, idx + 1, 0);
// This is an IME command, not text intended for the foreground
// application. Consuming it also prevents an Alt+number side
// effect in the application.
return 1;
}
}
}
// 放行其他程序
return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}
LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode >= 0)
{
if (wParam == WM_LBUTTONDOWN || wParam == WM_RBUTTONDOWN)
{
MSLLHOOKSTRUCT *p = (MSLLHOOKSTRUCT *)lParam;
POINT pt = p->pt;
RECT rc;
GetWindowRect(global_hwnd_menu, &rc);
if (!PtInRect(&rc, pt))
{
FTB_DIAG_LOGF(L"menu hide on click-outside {}", DescribeTrayMenuHostState());
ShowWindow(global_hwnd_menu, SW_HIDE);
UnhookWindowsHookEx(g_mouseHook);
g_mouseHook = nullptr;
}
}
}
return CallNextHookEx(g_mouseHook, nCode, wParam, lParam);
}
void CALLBACK WinEventProc(HWINEVENTHOOK, DWORD event, HWND hwnd, LONG idObject, LONG, DWORD, DWORD)
{
if (idObject != OBJID_WINDOW || !hwnd)
return;
if (event == EVENT_OBJECT_LOCATIONCHANGE || event == EVENT_SYSTEM_FOREGROUND)
{
OnWinEvent(hwnd);
}
}
static bool g_isHiddenDueToFullscreen = false;
void OnWinEvent(HWND hwnd)
{
if (!IsWindow(hwnd) || !IsWindowVisible(hwnd))
return;
// Stay out of the way until the toolbar can actually paint. While WebView2 is
// warming up the host is deliberately kept "visible" (DWM-cloaked), so the
// check below would happily act on a toolbar that is not on screen yet and
// leave g_isHiddenDueToFullscreen set for a restore nobody asked for.
// Skipping loses nothing: the apply that runs on navigation-completed
// evaluates fullscreen itself.
if (!IsFloatingToolbarWebviewReady())
return;
hwnd = GetForegroundWindow();
bool nowFullscreen = CheckFullscreen(hwnd);
// bool wasFullscreen = g_fullscreen_states[hwnd];
if (nowFullscreen)
{
if (IsWindowVisible(::global_hwnd_ftb))
{
// This hides the toolbar for as long as the foreground window keeps
// looking fullscreen, so a false positive here is indistinguishable
// from the toolbar failing to appear. Name the window that caused
// it: an overlay from a screenshot tool or a borderless app is very
// hard to tell apart from a real fullscreen client afterwards.
wchar_t foreground_class[64] = {};
GetClassNameW(hwnd, foreground_class, ARRAYSIZE(foreground_class));
RECT foreground_rect{};
GetWindowRect(hwnd, &foreground_rect);
FTB_DIAG_LOGF(L"fullscreen detected: class={} rect=({},{},{}x{}) -> hiding toolbar", foreground_class,
foreground_rect.left, foreground_rect.top, foreground_rect.right - foreground_rect.left,
foreground_rect.bottom - foreground_rect.top);
HideFloatingToolbarHost();
g_isHiddenDueToFullscreen = true;
}
}
else
{
if (g_isHiddenDueToFullscreen)
{
ApplyConfiguredFloatingToolbarVisibility(L"fullscreen-exit");
g_isHiddenDueToFullscreen = false;
}
}
// g_fullscreen_states[hwnd] = nowFullscreen;
}