界面渲染 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().
223 lines
7.0 KiB
C++
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;
|
|
}
|