From 69efed6068b2ae30880403cc7d013cafb3988a73 Mon Sep 17 00:00:00 2001 From: fanlumaster <1730976608@qq.com> Date: Sat, 3 Jan 2026 14:01:09 +0800 Subject: [PATCH] [ftb]: Hide ftb when fullscreen detected --- server/src/defines/globals.h | 8 ++- server/src/utils/webview_utils.h | 4 +- server/src/utils/window_utils.cpp | 101 ++++++++++++++++++++++++++++++ server/src/window/ime_windows.cpp | 9 +++ server/src/window/window_hook.cpp | 35 +++++++++++ server/src/window/window_hook.h | 4 +- 6 files changed, 158 insertions(+), 3 deletions(-) diff --git a/server/src/defines/globals.h b/server/src/defines/globals.h index 24e93d15..06ff41c6 100644 --- a/server/src/defines/globals.h +++ b/server/src/defines/globals.h @@ -1,6 +1,7 @@ #pragma once #include +#include inline float SCALE = 1.0f; inline int SHADOW_WIDTH = 15; @@ -53,4 +54,9 @@ inline int FTB_WND_HEIGHT = 38; inline int FTB_WND_SHADOW_WIDTH = 8; inline HWND global_hwnd_ftb = NULL; -inline bool is_global_wnd_ftb_shown = false; \ No newline at end of file +inline bool is_global_wnd_ftb_shown = false; + +// +// 全屏状态 +// +inline std::unordered_map g_fullscreen_states; diff --git a/server/src/utils/webview_utils.h b/server/src/utils/webview_utils.h index 6bb5d457..75b44f2a 100644 --- a/server/src/utils/webview_utils.h +++ b/server/src/utils/webview_utils.h @@ -8,4 +8,6 @@ void MeasureDomUpdateTime(ComPtr); void GetContainerSizeCand(ComPtr webview, std::function)> callback); void GetContainerSizeMenu(ComPtr webview, std::function)> callback); void MoveContainerBottom(ComPtr webview, int marginTop); -void MakeBodyVisible(ComPtr webview); \ No newline at end of file +void MakeBodyVisible(ComPtr webview); + +bool CheckFullscreen(HWND hwnd); \ No newline at end of file diff --git a/server/src/utils/window_utils.cpp b/server/src/utils/window_utils.cpp index ba0ceaba..dba742b3 100644 --- a/server/src/utils/window_utils.cpp +++ b/server/src/utils/window_utils.cpp @@ -3,6 +3,8 @@ #include "ipc/ipc.h" #include "webview_utils.h" #include +#include +#include FLOAT GetWindowScale(HWND hwnd) { @@ -183,4 +185,103 @@ int AdjustWndPosition( // properPos[1] = properPos[1] - ::DEFAULT_WINDOW_HEIGHT - 30 - 2; } return 0; +} + +bool CoversMonitor(HWND hwnd) +{ + if (!IsWindow(hwnd) || IsIconic(hwnd) || !IsWindowVisible(hwnd)) + return false; + + if (hwnd == GetDesktopWindow() || hwnd == GetShellWindow()) + return false; + + RECT rcWin; + GetWindowRect(hwnd, &rcWin); + + HMONITOR hMon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); + MONITORINFO mi{sizeof(mi)}; + GetMonitorInfo(hMon, &mi); + + RECT rcMon = mi.rcMonitor; + + constexpr int tolerance = 2; // 容忍 1~2px + return abs(rcWin.left - rcMon.left) <= tolerance // + && abs(rcWin.top - rcMon.top) <= tolerance // + && abs(rcWin.right - rcMon.right) <= tolerance // + && abs(rcWin.bottom - rcMon.bottom) <= tolerance; +} + +bool IsActuallyFullscreen(HWND hwnd) +{ + // 1. 基础合法性检查 + if (!IsWindow(hwnd) || !IsWindowVisible(hwnd) || IsIconic(hwnd)) + return false; + + // 2. 过滤掉桌面、任务栏等系统窗口 + char className[256]; + if (GetClassNameA(hwnd, className, sizeof(className))) + { + if (strcmp(className, "Progman") == 0 || strcmp(className, "WorkerW") == 0 || + strcmp(className, "Shell_TrayWnd") == 0) + { + return false; + } + } + + // 3. 获取窗口真实的物理位置 (DWM 坐标) + // 使用 DWM 才能排除掉 Win10/11 窗口四周那种看不见的“隐形边框” + RECT rcWin; + HRESULT hr = DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &rcWin, sizeof(rcWin)); + if (FAILED(hr)) + { + // 如果 DWM 获取失败(比如句柄失效),回退到普通 Rect + if (!GetWindowRect(hwnd, &rcWin)) + return false; + } + + // 4. 获取窗口所在的显示器信息 + HMONITOR hMon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); + MONITORINFO mi = {sizeof(mi)}; + if (!GetMonitorInfo(hMon, &mi)) + return false; + + // 5. 核心逻辑判断 + // 全屏窗口必须完全覆盖(甚至超出)屏幕的每一个边缘 + // 注意:全屏窗口的坐标往往会比 rcMonitor 稍微大一点点(例如 -1, -1),所以用 <= 和 >= + bool coversMonitor = (rcWin.left <= mi.rcMonitor.left && rcWin.top <= mi.rcMonitor.top && + rcWin.right >= mi.rcMonitor.right && rcWin.bottom >= mi.rcMonitor.bottom); + + if (!coversMonitor) + return false; + + // 6. 排除“最大化”但不是“全屏”的情况 + // 最大化窗口会被限制在 mi.rcWork (即避开任务栏后的区域) + // 真正的全屏会盖住任务栏 + bool touchesWorkArea = (rcWin.left == mi.rcWork.left && rcWin.top == mi.rcWork.top && + rcWin.right == mi.rcWork.right && rcWin.bottom == mi.rcWork.bottom); + + // 如果窗口和工作区完全重合,且工作区小于屏幕(有任务栏存在),那它只是最大化 + if (touchesWorkArea) + { + if (mi.rcWork.bottom != mi.rcMonitor.bottom || mi.rcWork.right != mi.rcMonitor.right) + { + return false; + } + } + + // 7. 样式检查:全屏窗口通常没有标题栏 + LONG style = GetWindowLong(hwnd, GWL_STYLE); + if (style & WS_CAPTION) + { + // 特殊情况:有些窗口即使全屏也保留了样式位,但在屏幕外。 + // 所以如果已经盖住了屏幕,样式只是辅助参考。 + return false; + } + + return true; +} + +bool CheckFullscreen(HWND hwnd) +{ + return IsActuallyFullscreen(hwnd); } \ No newline at end of file diff --git a/server/src/window/ime_windows.cpp b/server/src/window/ime_windows.cpp index b6a2fb42..1d0be27b 100644 --- a/server/src/window/ime_windows.cpp +++ b/server/src/window/ime_windows.cpp @@ -276,6 +276,15 @@ int CreateCandidateWindow(HINSTANCE hInstance) } OutputDebugString(fmt::format(L"Kbd hook for IME installed.\n").c_str()); + 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)) { diff --git a/server/src/window/window_hook.cpp b/server/src/window/window_hook.cpp index f909bf46..283d197d 100644 --- a/server/src/window/window_hook.cpp +++ b/server/src/window/window_hook.cpp @@ -1,9 +1,12 @@ #include "window_hook.h" +#include "utils/webview_utils.h" #include "defines/globals.h" #include "defines/defines.h" #include #include +void OnWinEvent(HWND hwnd); + bool IsKeyPressed(int vk) { return (GetAsyncKeyState(vk) & 0x8000) != 0; @@ -110,4 +113,36 @@ LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam) } } 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); + } +} + +void OnWinEvent(HWND hwnd) +{ + if (!IsWindow(hwnd) || !IsWindowVisible(hwnd)) + return; + + hwnd = GetForegroundWindow(); + bool nowFullscreen = CheckFullscreen(hwnd); + bool wasFullscreen = g_fullscreen_states[hwnd]; + + if (nowFullscreen) + { + ShowWindow(::global_hwnd_ftb, SW_HIDE); + } + else if (wasFullscreen && !nowFullscreen) + { + ShowWindow(::global_hwnd_ftb, SW_SHOW); + } + + g_fullscreen_states[hwnd] = nowFullscreen; } \ No newline at end of file diff --git a/server/src/window/window_hook.h b/server/src/window/window_hook.h index 9b138cd1..812918a8 100644 --- a/server/src/window/window_hook.h +++ b/server/src/window/window_hook.h @@ -4,4 +4,6 @@ inline HHOOK g_hHook = NULL; inline HHOOK g_mouseHook = NULL; LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam); -LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam); \ No newline at end of file +LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam); + +void CALLBACK WinEventProc(HWINEVENTHOOK, DWORD event, HWND hwnd, LONG idObject, LONG, DWORD, DWORD); \ No newline at end of file