工作閒暇瀏覽網頁、玩遊戲、寫日記時,老闆/媽媽/另一半突然出現 —— 手忙腳亂逐一點擊最小化按鈕?本專案實現全域滑鼠左鍵+右鍵同時按下 觸發隱藏所有可見視窗,再次觸發還原,並以系統托盤圖示提供介面,徹底解決「社死」危機。

Windows 全域鉤子 (WH_MOUSE) 要求鉤子程序位於 DLL 中,以便系統將其映射至所有目標進程位址空間。
CMousehook 封裝類。// Mousehook.cpp
#pragma data_seg("SHARED_DATA") // 共享段名稱
HINSTANCE g_hInst = NULL; // DLL 實例控制代碼
HHOOK g_hHook = NULL; // 鉤子控制代碼
HWND g_HiddenWnds[100] = {NULL}; // 隱藏視窗控制代碼陣列 (必須初始化!)
UINT g_HiddenCount = 0; // 陣列索引
BOOL g_LeftDown = FALSE; // 左鍵按下旗標
BOOL g_RightDown = FALSE; // 右鍵按下旗標
BOOL g_HideOrShow = FALSE; // 當前狀態:FALSE=隱藏, TRUE=還原
BOOL g_Check1 = FALSE, g_Check2 = FALSE, g_Check3 = FALSE; // UI 選項同步
#pragma data_seg()
// 連結器指示:設定共享段屬性為 READ/WRITE/SHARED
// 專案屬性 → 連結器 → 命令列 → /SECTION:SHARED_DATA,RWS
// 或 .def 檔:SECTIONS SHARED_DATA READ WRITE SHARED
關鍵: 變數必須初始化,否則編譯器會放入 .bss 未初始化段,無法共享。
LRESULT CALLBACK MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode < 0) return CallNextHookEx(g_hHook, nCode, wParam, lParam);
MOUSEHOOKSTRUCT* pInfo = (MOUSEHOOKSTRUCT*)lParam;
switch (wParam)
{
case WM_LBUTTONDOWN: g_LeftDown = TRUE; break;
case WM_LBUTTONUP: g_LeftDown = FALSE; break;
case WM_RBUTTONDOWN: g_RightDown = TRUE; break;
case WM_RBUTTONUP: g_RightDown = FALSE; break;
}
// 偵測「同時按下」:任一按下時檢查另一鍵是否已按下
if ((wParam == WM_LBUTTONDOWN && g_RightDown) ||
(wParam == WM_RBUTTONDOWN && g_LeftDown))
{
ToggleWindowsVisibility(pInfo->hwnd); // 忽略觸發視窗本身
}
return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}
void ToggleWindowsVisibility(HWND hTriggerWnd)
{
if (!g_HideOrShow) // 目前顯示中 → 隱藏
{
g_HiddenCount = 0;
EnumWindows(EnumWindowsProc, 0); // 列舉所有頂層視窗
g_HideOrShow = TRUE;
}
else // 目前隱藏中 → 還原
{
for (UINT i = 0; i < g_HiddenCount; ++i)
{
if (g_HiddenWnds[i] && IsWindow(g_HiddenWnds[i]))
ShowWindow(g_HiddenWnds[i], SW_SHOW);
}
g_HiddenCount = 0;
g_HideOrShow = FALSE;
}
}
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
{
// 篩選條件:可見、非觸發視窗、非工作列/托盤/桌面視窗
if (IsWindowVisible(hwnd) &&
hwnd != GetShellWindow() && // 排除桌面
GetWindow(hwnd, GW_OWNER) == NULL && // 僅頂層視窗
!IsIconic(hwnd)) // 未最小化
{
// 可選:排除特定類別名稱 (如 "Shell_TrayWnd", "Button" 等)
TCHAR className[256];
GetClassName(hwnd, className, 256);
if (_tcscmp(className, _T("Shell_TrayWnd")) == 0) return TRUE;
ShowWindow(hwnd, SW_HIDE);
if (g_HiddenCount < 100)
g_HiddenWnds[g_HiddenCount++] = hwnd;
}
return TRUE; // 繼續列舉
}
class AFX_EXT_CLASS CMousehook : public CObject
{
public:
CMousehook() {}
~CMousehook() { StopHook(); }
BOOL StartHook()
{
if (g_hHook) return TRUE;
g_hHook = SetWindowsHookEx(WH_MOUSE, MouseHookProc, g_hInst, 0); // 0 = 全域
return g_hHook != NULL;
}
BOOL StopHook()
{
if (g_hHook)
{
UnhookWindowsHookEx(g_hHook);
g_hHook = NULL;
// 確保退出時還原所有視窗
if (g_HideOrShow) ToggleWindowsVisibility(NULL);
return TRUE;
}
return FALSE;
}
// UI 同步方法...
void SetCheck1(UINT i) { g_Check1 = i; }
void SetCheck2(UINT i) { g_Check2 = i; }
void SetCheck3(UINT i) { g_Check3 = i; }
void UseForExit() { if (g_HideOrShow) ToggleWindowsVisibility(NULL); }
};
BOOL CHideWindowDlg::OnInitDialog()
{
COwnerDrawDialog::OnInitDialog();
// 1. 載入 DLL 並啟動鉤子
m_Hook.StartHook(); // m_Hook 為 CMousehook 成員
// 2. 隱藏工作列顯示,僅保留托盤圖示
LONG exStyle = GetWindowLong(GetSafeHwnd(), GWL_EXSTYLE);
SetWindowLong(GetSafeHwnd(), GWL_EXSTYLE, exStyle | WS_EX_TOOLWINDOW);
// 3. 建立托盤圖示
NOTIFYICONDATA nid = {0};
nid.cbSize = sizeof(NOTIFYICONDATA);
nid.hWnd = GetSafeHwnd();
nid.uID = IDI_MAINFRAME;
nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
nid.uCallbackMessage = WM_MY_TRAY_NOTIFY; // 自訂訊息
nid.hIcon = LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_MAINFRAME));
_tcscpy_s(nid.szTip, _T("魔高一丈 2.0 - 雙鍵隱藏視窗"));
Shell_NotifyIcon(NIM_ADD, &nid);
// 4. 立即隱藏主對話框
ShowWindow(SW_HIDE);
return TRUE;
}
LRESULT CHideWindowDlg::OnTrayNotify(WPARAM wParam, LPARAM lParam)
{
if (wParam != IDI_MAINFRAME) return 0;
if (lParam == WM_LBUTTONDOWN) // 左鍵單擊:顯示/隱藏設定對話框
{
if (IsWindowVisible()) ShowWindow(SW_HIDE);
else { ShowWindow(SW_SHOW); SetForegroundWindow(); }
}
else if (lParam == WM_RBUTTONDOWN) // 右鍵:彈出選單
{
CMenu menu; menu.LoadMenu(IDR_TRAY_MENU);
CMenu* pPopup = menu.GetSubMenu(0);
CPoint pt; GetCursorPos(&pt);
SetForegroundWindow(); // 修正選單焦點問題
pPopup->TrackPopupMenu(TPM_LEFTALIGN, pt.x, pt.y, this);
}
return 0;
}
CHideWindowDlg::~CHideWindowDlg()
{
// 1. 移除托盤圖示
NOTIFYICONDATA nid = {0};
nid.cbSize = sizeof(NOTIFYICONDATA);
nid.hWnd = GetSafeHwnd();
nid.uID = IDI_MAINFRAME;
Shell_NotifyIcon(NIM_DELETE, &nid);
// 2. 停止鉤子、還原視窗
m_Hook.UseForExit(); // 內部呼叫 StopHook()
m_Hook.StopHook();
}
| 限制 | 改進建議 |
|---|---|
固定陣列 g_HiddenWnds[100] 可能溢位 | 改用共享記憶體 mmap + 動態陣列或連結串列 |
| 無法隱藏 UWP/Store Apps 視窗 | 需更高權限或 IApplicationView 介面 |
| 全域鉤子注入所有進程,效能影響 | 改用 WH_MOUSE_LL 低層鉤子 (不需 DLL,但須在同一執行緒處理) |
| 僅支援 32 位或 64 位單一架構 | 提供 x86/x64 兩版 DLL,EXE 依架構載入 |
| 殺毒軟體可能誤報全域鉤子 | 簽名驗證、行為白名單、改用低層鉤子 |
《魔高一丈 2.0》展示了 Win32 全域鉤子、共享資料段、DLL 注入、托盤程式設計等經典技術的完整串聯。雖然現代 Windows 安全機制 (UIPI, CFG, HVCI) 對全域鉤子限制日益嚴格,但理解其原理對於系統級開發、安全研究、相容舊專案維護仍具核心價值。若重寫於今日,建議採用 WH_MOUSE_LL + 獨立服務進程 + 命名管道通訊 架構,在安全與功能間取得平衡。