辅助摸鱼的一个小玩具——窗口透明器

· · 科技·工程

窗口透明器

退役 Oier,并非专业干工程的,做得不够优秀的地方请多指教。

由 C++ 编写,用于 Windows。随便写的小玩具,也懒得大费周章上传 Github 什么的了。公布源代码,需要在自己电脑上编译一下。编译须知见后文,请务必阅读!使用说明程序里有。

发现 BUG 可找作者私信反馈,最多大概两天会看到。

:::info[源代码]

// 禁用Windows宏冲突
#ifdef _WIN32
    #ifndef NOMINMAX
        #define NOMINMAX
    #endif
    #ifndef WIN32_LEAN_AND_MEAN
        #define WIN32_LEAN_AND_MEAN
    #endif
#endif

#include<bits/stdc++.h>
#include<windows.h>

// 防止Windows宏污染
#undef DELETE
#undef ERROR

#define KEY_DOWN(VK_NONAME) ((GetAsyncKeyState(VK_NONAME) & 0x8000) ? 1:0)

using namespace std;

// 按键事件类型
// 按键事件类型
enum class KeyEventType {
    KEY_DOWN,
    KEY_UP,
    KEY_DOUBLE_CLICK
};

// 按键信息结构
struct KeyInfo {
    int vkCode;                  // 虚拟键码
    string keyName;        // 按键名称
    bool ctrlPressed;             // Ctrl是否按下
    bool altPressed;               // Alt是否按下
    bool shiftPressed;           // Shift是否按下
    bool winPressed;               // Win键是否按下
    KeyEventType eventType;     // 事件类型
    DWORD timestamp;               // 时间戳
    bool isHandled;             // 是否已处理(用于阻止系统行为)
};

// 按键组合标识
struct KeyCombo {
    int mainKey;      // 主键
    bool ctrl;      // Ctrl是否按下
    bool alt;        // Alt是否按下
    bool shift;    // Shift是否按下
    bool win;        // Win键是否按下

    // 用于map的比较
    bool operator<(const KeyCombo& other) const {
        if (mainKey != other.mainKey) return mainKey < other.mainKey;
        if (ctrl != other.ctrl) return ctrl < other.ctrl;
        if (alt != other.alt) return alt < other.alt;
        if (shift != other.shift) return shift < other.shift;
        return win < other.win;
    }

    bool operator==(const KeyCombo& other) const {
        return mainKey == other.mainKey &&
               ctrl == other.ctrl &&
               alt == other.alt &&
               shift == other.shift &&
               win == other.win;
    }

    // 正确的组合键匹配逻辑
    bool matches(int vk, bool c, bool a, bool s, bool w) const {
        if (mainKey != vk) return false;

        // 检查所有修饰键是否匹配
        // 如果组合键要求Ctrl,则必须按下Ctrl
        if (ctrl && !c) return false;
        if (alt && !a) return false;
        if (shift && !s) return false;
        if (win && !w) return false;

        // 如果组合键不要求某个修饰键,那么这个修饰键必须没有按下
        if (!ctrl && c) return false;
        if (!alt && a) return false;
        if (!shift && s) return false;
        if (!win && w) return false;

        return true;
    }

    // 获取组合键的描述字符串
    string toString() const {
        string result;
        if (ctrl) result += "Ctrl+";
        if (alt) result += "Alt+";
        if (shift) result += "Shift+";
        if (win) result += "Win+";

        // 获取按键名称
        if (mainKey >= 'A' && mainKey <= 'Z') {
            result += char(mainKey);
        } else if (mainKey >= '0' && mainKey <= '9') {
            result += char(mainKey);
        } else {
            // 特殊按键
            char keyName[256] = {0};
            UINT scanCode = MapVirtualKey(mainKey, MAPVK_VK_TO_VSC);
            LONG lParam = (scanCode << 16);
            if (GetKeyNameText(lParam, keyName, sizeof(keyName))) {
                result += keyName;
            } else {
                char buffer[32];
                snprintf(buffer, sizeof(buffer), "0x%02X", mainKey);
                result += buffer;
            }
        }
        return result;
    }
};

// 为KeyCombo提供hash函数,用于unordered_map
struct KeyComboHash {
    size_t operator()(const KeyCombo& k) const {
        return ((hash<int>()(k.mainKey) << 4) ^ (hash<bool>()(k.ctrl) << 3) ^ (hash<bool>()(k.alt) << 2) ^
                (hash<bool>()(k.shift) << 1) ^ hash<bool>()(k.win));
    }
};

// 回调函数类型
using KeyCallback = function<void(const KeyInfo&)>;

// 回调函数和阻止标志
struct CallbackInfo {
    KeyCallback callback;
    bool blockSystemBehavior;  // 是否阻止系统默认行为
};

class GlobalKeyListener {
    private:
        // 静态实例指针,用于钩子回调访问
        static GlobalKeyListener* s_instance;

        // 用于32位/64位兼容的线程ID存储
#if defined(_WIN64) || defined(__x86_64__) || defined(__amd64__)
        DWORD_PTR m_threadId;
#else
        DWORD m_threadId;
#endif

        HHOOK m_keyboardHook;
        atomic<bool> m_isRunning;
        thread m_messageThread;

        // 按键状态 - 使用GetAsyncKeyState获取实时状态,不在这里存储
        // 只在钩子回调中使用,确保状态准确

        // 回调函数映射
        unordered_map<KeyCombo, CallbackInfo, KeyComboHash> m_keyCallbacks;

        // 双击检测相关
        struct KeyPressRecord {
            DWORD lastPressTime;
            int pressCount;
        };
        unordered_map<int, KeyPressRecord> m_keyPressHistory;
        unordered_map<int, CallbackInfo> m_doubleClickCallbacks;
        unordered_map<int, int> m_doubleClickIntervals;

        // 需要阻止的组合键(用于快速检查)
        unordered_set<KeyCombo, KeyComboHash> m_blockedCombos;

        // 保护共享资源的互斥锁 - 使用mutable使其在const函数中可修改
        mutable recursive_mutex m_mutex;

        // 私有构造函数,使用单例模式
        GlobalKeyListener() : m_keyboardHook(nullptr), m_isRunning(false) {
            s_instance = this;
        }

        // 键盘钩子处理函数(必须是静态的)
        static LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
            if (nCode >= 0 && s_instance) {
                KBDLLHOOKSTRUCT* kbStruct = reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam);
                int vkCode = kbStruct->vkCode;

                // 获取实时按键状态
                bool ctrlPressed = KEY_DOWN(VK_CONTROL);
                bool altPressed = KEY_DOWN(VK_MENU);
                bool shiftPressed = KEY_DOWN(VK_SHIFT);
                bool winPressed = KEY_DOWN(VK_LWIN) || KEY_DOWN(VK_RWIN);

                // 检查是否需要阻止此按键
                bool shouldBlock = false;

                // 只有按下事件才检查是否需要阻止
                if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) {
                    shouldBlock = s_instance->ShouldBlockKey(vkCode, ctrlPressed, altPressed, shiftPressed, winPressed);
                }

                // 发送按键事件到消息线程
                if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) {
                    s_instance->PostKeyEvent(vkCode, KeyEventType::KEY_DOWN, ctrlPressed, altPressed, shiftPressed, winPressed, shouldBlock);
                } else if (wParam == WM_KEYUP || wParam == WM_SYSKEYUP) {
                    s_instance->PostKeyEvent(vkCode, KeyEventType::KEY_UP, ctrlPressed, altPressed, shiftPressed, winPressed, false);

                    // 双击检测(在按键释放时进行)
                    if (vkCode != VK_CONTROL && vkCode != VK_MENU &&
                        vkCode != VK_SHIFT && vkCode != VK_LWIN && vkCode != VK_RWIN) {
                        s_instance->PostDoubleClickCheck(vkCode, ctrlPressed, altPressed, shiftPressed, winPressed);
                    }
                }

                // 如果需要阻止,返回1表示已处理,不让系统继续处理
                if (shouldBlock) {
                    return 1;
                }
            }
            return CallNextHookEx(nullptr, nCode, wParam, lParam);
        }

        // 检查是否需要阻止此按键
        bool ShouldBlockKey(int vkCode, bool ctrlPressed, bool altPressed, bool shiftPressed, bool winPressed) {
            lock_guard<recursive_mutex> lock(m_mutex);

            // 检查是否有需要阻止的组合键
            for (const auto& combo : m_blockedCombos) {
                if (combo.matches(vkCode, ctrlPressed, altPressed, shiftPressed, winPressed)) {
                    return true;
                }
            }
            return false;
        }

        // 发送按键事件到消息线程
        void PostKeyEvent(int vkCode, KeyEventType eventType, bool ctrlPressed, bool altPressed, bool shiftPressed, bool winPressed, bool isHandled) {
            if (!m_isRunning || !m_threadId) return;

            // 打包数据
            LPARAM lParam = MAKELPARAM(
                                (ctrlPressed ? 1 : 0) |
                                ((altPressed ? 1 : 0) << 1) |
                                ((shiftPressed ? 1 : 0) << 2) |
                                ((winPressed ? 1 : 0) << 3) |
                                ((isHandled ? 1 : 0) << 4),
                                vkCode
                            );

            WPARAM wParam = static_cast<WPARAM>(eventType);

            PostThreadMessage(m_threadId, WM_USER + 1, wParam, lParam);
        }

        // 发送双击检查到消息线程
        void PostDoubleClickCheck(int vkCode, bool ctrlPressed, bool altPressed, bool shiftPressed, bool winPressed) {
            if (!m_isRunning || !m_threadId) return;

            // 打包修饰键状态
            DWORD modifiers = (ctrlPressed ? 1 : 0) |
                              ((altPressed ? 1 : 0) << 1) |
                              ((shiftPressed ? 1 : 0) << 2) |
                              ((winPressed ? 1 : 0) << 3);

            LPARAM lParam = MAKELPARAM(modifiers, vkCode);

            PostThreadMessage(m_threadId, WM_USER + 2, vkCode, lParam);
        }

        // 获取按键名称
        string GetKeyName(int vkCode) const {
            char keyName[256] = {0};
            UINT scanCode = MapVirtualKey(vkCode, MAPVK_VK_TO_VSC);

            // 处理扩展键
            switch (vkCode) {
                case VK_INSERT:
                case VK_DELETE:
                case VK_HOME:
                case VK_END:
                case VK_PRIOR:
                case VK_NEXT:
                case VK_LEFT:
                case VK_RIGHT:
                case VK_UP:
                case VK_DOWN:
                case VK_NUMLOCK:
                case VK_CANCEL:
                case VK_SNAPSHOT:
                case VK_DIVIDE:
                    scanCode |= 0x0100; // 扩展键标志
                    break;
            }

            LONG lParam = (scanCode << 16);

            if (GetKeyNameText(lParam, keyName, sizeof(keyName)) > 0) {
                return string(keyName);
            }

            // 特殊键处理
            switch (vkCode) {
                case VK_CONTROL:
                    return "Ctrl";
                case VK_LCONTROL:
                    return "Left Ctrl";
                case VK_RCONTROL:
                    return "Right Ctrl";
                case VK_MENU:
                    return "Alt";
                case VK_LMENU:
                    return "Left Alt";
                case VK_RMENU:
                    return "Right Alt";
                case VK_SHIFT:
                    return "Shift";
                case VK_LSHIFT:
                    return "Left Shift";
                case VK_RSHIFT:
                    return "Right Shift";
                case VK_LWIN:
                    return "Left Win";
                case VK_RWIN:
                    return "Right Win";
                case VK_ESCAPE:
                    return "Esc";
                case VK_RETURN:
                    return "Enter";
                case VK_SPACE:
                    return "Space";
                case VK_BACK:
                    return "Backspace";
                case VK_TAB:
                    return "Tab";
                case VK_CAPITAL:
                    return "Caps Lock";
                case VK_NUMLOCK:
                    return "Num Lock";
                case VK_SCROLL:
                    return "Scroll Lock";
                case VK_INSERT:
                    return "Insert";
                case VK_DELETE:
                    return "Delete";
                case VK_HOME:
                    return "Home";
                case VK_END:
                    return "End";
                case VK_PRIOR:
                    return "Page Up";
                case VK_NEXT:
                    return "Page Down";
                case VK_UP:
                    return "Up Arrow";
                case VK_DOWN:
                    return "Down Arrow";
                case VK_LEFT:
                    return "Left Arrow";
                case VK_RIGHT:
                    return "Right Arrow";
                case VK_F1:
                    return "F1";
                case VK_F2:
                    return "F2";
                case VK_F3:
                    return "F3";
                case VK_F4:
                    return "F4";
                case VK_F5:
                    return "F5";
                case VK_F6:
                    return "F6";
                case VK_F7:
                    return "F7";
                case VK_F8:
                    return "F8";
                case VK_F9:
                    return "F9";
                case VK_F10:
                    return "F10";
                case VK_F11:
                    return "F11";
                case VK_F12:
                    return "F12";
                case VK_OEM_PLUS:
                    return "Plus";
                case VK_OEM_MINUS:
                    return "Minus";
                case VK_OEM_COMMA:
                    return "Comma";
                case VK_OEM_PERIOD:
                    return "Period";
                case VK_ADD:
                    return "NumPad +";
                case VK_SUBTRACT:
                    return "NumPad -";
                case VK_MULTIPLY:
                    return "NumPad *";
                case VK_DIVIDE:
                    return "NumPad /";
                case VK_DECIMAL:
                    return "NumPad .";
                case VK_NUMPAD0:
                    return "NumPad 0";
                case VK_NUMPAD1:
                    return "NumPad 1";
                case VK_NUMPAD2:
                    return "NumPad 2";
                case VK_NUMPAD3:
                    return "NumPad 3";
                case VK_NUMPAD4:
                    return "NumPad 4";
                case VK_NUMPAD5:
                    return "NumPad 5";
                case VK_NUMPAD6:
                    return "NumPad 6";
                case VK_NUMPAD7:
                    return "NumPad 7";
                case VK_NUMPAD8:
                    return "NumPad 8";
                case VK_NUMPAD9:
                    return "NumPad 9";
                default: {
                        // 尝试获取字符表示
                        BYTE keyboardState[256] = {0};
                        GetKeyboardState(keyboardState);

                        WCHAR charBuffer[10] = {0};
                        int result = ToUnicode(vkCode, scanCode, keyboardState, charBuffer, 10, 0);

                        if (result > 0) {
                            // 转换为多字节字符串
                            char mbBuffer[10] = {0};
                            WideCharToMultiByte(CP_UTF8, 0, charBuffer, -1, mbBuffer, sizeof(mbBuffer), nullptr, nullptr);
                            return string(mbBuffer);
                        }
                        // 返回虚拟键码
                        char buffer[32];
                        snprintf(buffer, sizeof(buffer), "Key 0x%02X", vkCode);
                        return string(buffer);
                    }
            }
        }

        // 处理按键事件(在消息线程中执行)
        void ProcessKeyEvent(WPARAM wParam, LPARAM lParam) {
            KeyEventType eventType = static_cast<KeyEventType>(wParam);

            int vkCode = HIWORD(lParam);
            WORD modifiers = LOWORD(lParam);

            bool ctrlPressed = (modifiers & 0x01) != 0;
            bool altPressed = (modifiers & 0x02) != 0;
            bool shiftPressed = (modifiers & 0x04) != 0;
            bool winPressed = (modifiers & 0x08) != 0;
            bool isHandled = (modifiers & 0x10) != 0;

            KeyInfo keyInfo;
            keyInfo.vkCode = vkCode;
            keyInfo.keyName = GetKeyName(vkCode);
            keyInfo.ctrlPressed = ctrlPressed;
            keyInfo.altPressed = altPressed;
            keyInfo.shiftPressed = shiftPressed;
            keyInfo.winPressed = winPressed;
            keyInfo.eventType = eventType;
            keyInfo.timestamp = GetTickCount();
            keyInfo.isHandled = isHandled;

            lock_guard<recursive_mutex> lock(m_mutex);

            // 查找匹配的回调函数
            vector<const CallbackInfo*> matchedCallbacks;

            for (const auto& pair : m_keyCallbacks) {
                const KeyCombo& combo = pair.first;
                if (combo.matches(vkCode, ctrlPressed, altPressed, shiftPressed, winPressed)) {
                    matchedCallbacks.push_back(&pair.second);
                }
            }

            // 执行回调
            for (const auto& callbackInfo : matchedCallbacks) {
                if (callbackInfo->callback) {
                    try {
                        callbackInfo->callback(keyInfo);
                    } catch (...) {
                        // 防止回调异常导致程序崩溃
                    }
                }
            }
        }

        // 处理双击检测
        void ProcessDoubleClick(WPARAM wParam, LPARAM lParam) {
            int vkCode = static_cast<int>(wParam);
            WORD modifiers = LOWORD(lParam);

            bool ctrlPressed = (modifiers & 0x01) != 0;
            bool altPressed = (modifiers & 0x02) != 0;
            bool shiftPressed = (modifiers & 0x04) != 0;
            bool winPressed = (modifiers & 0x08) != 0;

            DWORD currentTime = GetTickCount();

            lock_guard<recursive_mutex> lock(m_mutex);

            auto it = m_keyPressHistory.find(vkCode);
            if (it != m_keyPressHistory.end()) {
                DWORD lastTime = it->second.lastPressTime;
                int pressCount = it->second.pressCount;

                // 检查是否在双击时间间隔内
                auto intervalIt = m_doubleClickIntervals.find(vkCode);
                int maxInterval = (intervalIt != m_doubleClickIntervals.end()) ? intervalIt->second : 500; // 默认500ms

                if ((currentTime - lastTime) <= (DWORD)maxInterval) {
                    pressCount++;

                    if (pressCount >= 2) {
                        // 触发双击事件
                        KeyInfo keyInfo;
                        keyInfo.vkCode = vkCode;
                        keyInfo.keyName = GetKeyName(vkCode);
                        keyInfo.ctrlPressed = ctrlPressed;
                        keyInfo.altPressed = altPressed;
                        keyInfo.shiftPressed = shiftPressed;
                        keyInfo.winPressed = winPressed;
                        keyInfo.eventType = KeyEventType::KEY_DOUBLE_CLICK;
                        keyInfo.timestamp = currentTime;
                        keyInfo.isHandled = false;

                        // 执行双击回调
                        auto callbackIt = m_doubleClickCallbacks.find(vkCode);
                        if (callbackIt != m_doubleClickCallbacks.end() && callbackIt->second.callback) {
                            try {
                                callbackIt->second.callback(keyInfo);
                            } catch (...) {
                                // 防止回调异常
                            }
                        }

                        // 重置计数
                        pressCount = 0;
                    }

                    it->second.lastPressTime = currentTime;
                    it->second.pressCount = pressCount;
                } else {
                    // 超时,重新计数
                    it->second.lastPressTime = currentTime;
                    it->second.pressCount = 1;
                }
            } else {
                // 第一次按下
                m_keyPressHistory[vkCode] = {currentTime, 1};
            }
        }

        // 消息循环线程函数
        void MessageLoop() {
            // 存储线程ID用于跨平台兼容
#if defined(_WIN64) || defined(__x86_64__) || defined(__amd64__)
            m_threadId = (DWORD_PTR)GetCurrentThreadId();
#else
            m_threadId = GetCurrentThreadId();
#endif

            // 安装全局键盘钩子
            m_keyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandle(nullptr), 0);

            if (!m_keyboardHook) {
                DWORD error = GetLastError();
                system("cls");
                cerr << "Failed to set keyboard hook. Error: " << error << endl;

                // 尝试以管理员权限运行提示
                if (error == ERROR_ACCESS_DENIED) {
                    cerr << "Try running the program as administrator." << endl;
                }
                return;
            }

            system("cls");
            cout << "Global key listener started successfully." << endl;

            // 消息循环
            MSG msg;
            while (m_isRunning && GetMessage(&msg, nullptr, 0, 0)) {
                // 处理自定义消息
                if (msg.message == WM_USER + 1) {
                    ProcessKeyEvent(msg.wParam, msg.lParam);
                } else if (msg.message == WM_USER + 2) {
                    ProcessDoubleClick(msg.wParam, msg.lParam);
                }

                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }

            // 卸载钩子
            if (m_keyboardHook) {
                UnhookWindowsHookEx(m_keyboardHook);
                m_keyboardHook = nullptr;
            }

            system("cls");
            cout << "Global key listener stopped." << endl;
        }

    public:
        // 删除复制构造函数和赋值运算符
        GlobalKeyListener(const GlobalKeyListener&) = delete;
        GlobalKeyListener& operator=(const GlobalKeyListener&) = delete;

        // 获取单例实例
        static GlobalKeyListener& GetInstance() {
            static GlobalKeyListener instance;
            return instance;
        }

        // 启动监听
        bool Start() {
            if (m_isRunning) return true;

            m_isRunning = true;

            // 清空历史状态
            {
                lock_guard<recursive_mutex> lock(m_mutex);
                m_keyPressHistory.clear();
            }

            // 创建消息循环线程
            try {
                m_messageThread = thread(&GlobalKeyListener::MessageLoop, this);

                // 等待钩子设置完成
                this_thread::sleep_for(chrono::milliseconds(100));

                return m_keyboardHook != nullptr;
            } catch (const exception& e) {
                system("cls");
                cerr << "Failed to start listener: " << e.what() << endl;
                m_isRunning = false;
                return false;
            }
        }

        // 停止监听
        void Stop() {
            if (!m_isRunning) return;

            m_isRunning = false;

            // 发送退出消息
            if (m_threadId) {
                PostThreadMessage(m_threadId, WM_QUIT, 0, 0);
            }

            // 等待线程结束
            if (m_messageThread.joinable()) {
                try {
                    m_messageThread.join();
                } catch (...) {
                    // 忽略异常
                }
            }

            lock_guard<recursive_mutex> lock(m_mutex);
            m_keyCallbacks.clear();
            m_doubleClickCallbacks.clear();
            m_doubleClickIntervals.clear();
            m_keyPressHistory.clear();
            m_blockedCombos.clear();
        }

        // 注册按键监听(增强版,支持阻止系统行为)
        void RegisterKey(int vkCode, KeyCallback callback,
                         bool checkCtrl = false, bool checkAlt = false,
                         bool checkShift = false, bool checkWin = false,
                         bool blockSystemBehavior = false) {
            KeyCombo combo{vkCode, checkCtrl, checkAlt, checkShift, checkWin};
            CallbackInfo info{callback, blockSystemBehavior};

            lock_guard<recursive_mutex> lock(m_mutex);
            m_keyCallbacks[combo] = info;

            // 如果需要阻止系统行为,添加到阻止列表
            if (blockSystemBehavior) {
                m_blockedCombos.insert(combo);
            }
        }

        // 注册组合键监听(增强版)
        void RegisterComboKey(int mainKey, bool ctrl, bool alt, bool shift, bool win,
                              KeyCallback callback, bool blockSystemBehavior = false) {
            RegisterKey(mainKey, callback, ctrl, alt, shift, win, blockSystemBehavior);
        }

        // 注册双击监听
        void RegisterDoubleClick(int vkCode, int maxIntervalMs = 500,
                                 KeyCallback callback = nullptr, bool blockSystemBehavior = false) {
            lock_guard<recursive_mutex> lock(m_mutex);
            m_doubleClickIntervals[vkCode] = maxIntervalMs;
            if (callback) {
                m_doubleClickCallbacks[vkCode] = {callback, blockSystemBehavior};
            }
        }

        // 直接阻止组合键(无需回调)
        void BlockCombo(int mainKey, bool ctrl = false, bool alt = false,
                        bool shift = false, bool win = false) {
            KeyCombo combo{mainKey, ctrl, alt, shift, win};

            lock_guard<recursive_mutex> lock(m_mutex);
            m_blockedCombos.insert(combo);
        }

        // 解除阻止组合键
        void UnblockCombo(int mainKey, bool ctrl = false, bool alt = false,
                          bool shift = false, bool win = false) {
            KeyCombo combo{mainKey, ctrl, alt, shift, win};

            lock_guard<recursive_mutex> lock(m_mutex);
            m_blockedCombos.erase(combo);
        }

        // 移除按键监听
        void UnregisterKey(int vkCode, bool ctrl = false,
                           bool alt = false, bool shift = false, bool win = false) {
            KeyCombo combo{vkCode, ctrl, alt, shift, win};

            lock_guard<recursive_mutex> lock(m_mutex);
            m_keyCallbacks.erase(combo);

            // 同时从阻止列表中移除
            m_blockedCombos.erase(combo);
        }

        // 移除所有监听
        void UnregisterAll() {
            lock_guard<recursive_mutex> lock(m_mutex);
            m_keyCallbacks.clear();
            m_doubleClickCallbacks.clear();
            m_blockedCombos.clear();
        }

        // 设置双击检测间隔
        void SetDoubleClickInterval(int intervalMs) {
            lock_guard<recursive_mutex> lock(m_mutex);
            for (auto& pair : m_doubleClickIntervals) {
                pair.second = intervalMs;
            }
        }

        // 检查是否正在运行
        bool IsRunning() const {
            return m_isRunning;
        }

        // 获取当前修饰键状态(实时)
        void GetModifierState(bool& ctrl, bool& alt, bool& shift, bool& win) const {
            ctrl = KEY_DOWN(VK_CONTROL);
            alt = KEY_DOWN(VK_MENU);
            shift = KEY_DOWN(VK_SHIFT);
            win = KEY_DOWN(VK_LWIN) || KEY_DOWN(VK_RWIN);
        }

        // 获取已注册的快捷键列表
        vector<string> GetRegisteredHotkeys() const {
            vector<string> hotkeys;
            lock_guard<recursive_mutex> lock(m_mutex);

            for (const auto& pair : m_keyCallbacks) {
                hotkeys.push_back(pair.first.toString());
            }
            return hotkeys;
        }

        // 获取已阻止的快捷键列表
        vector<string> GetBlockedHotkeys() const {
            vector<string> hotkeys;
            lock_guard<recursive_mutex> lock(m_mutex);

            for (const auto& combo : m_blockedCombos) {
                hotkeys.push_back(combo.toString());
            }

            return hotkeys;
        }

        // 析构函数
        ~GlobalKeyListener() {
            Stop();
            s_instance = nullptr;
        }
};

// 初始化静态成员
GlobalKeyListener* GlobalKeyListener::s_instance = nullptr;

//本程序的窗口句柄
HWND myWindow;
bool isMyWinVisible = true;

//颜色常量
const int COLOR_BLACK = 0;
const int COLOR_BLUE = 1;
const int COLOR_GREEN = 2;
const int COLOR_LIGHTGREEN = 3;
const int COLOR_RED = 4;
const int COLOR_PURPLE = 5;
const int COLOR_YELLOW = 6;
const int COLOR_WHITE = 7;
const int COLOR_GREY = 8;
const int COLOR_LIGHTBLUE = 9;
const int COLOR_LIGHTGREEN2 = 10;
const int COLOR_LIGHTGREEN3 = 11;
const int COLOR_LIGHTRED = 12;
const int COLOR_LIGHTPURPLE = 13;
const int COLOR_LIGHTYELLOW = 14;
const int COLOR_LIGHTWHITE = 15;

// 宽字符输出函数封装
void WriteConStr(const wstring& str) {
    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    DWORD charsWritten;
    WriteConsoleW(hConsole, str.c_str(), (DWORD)str.length(), &charsWritten, NULL);
}

void WriteConStr(const wchar_t* str) {
    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    DWORD charsWritten;
    WriteConsoleW(hConsole, str, (DWORD)wcslen(str), &charsWritten, NULL);
}

//更改颜色 文字+背景
void SetColorAndBackground(int ForgC, int BackC) {
    WORD wColor = ((BackC & 0x0F) << 4) + (ForgC & 0x0F);
    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), wColor);
    return;
}

// 设置光标位置
void SetCursorPosition(int x, int y) {
    COORD coord;
    coord.X = (short)x;
    coord.Y = (short)y;
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
    return;
}

// 获取光标位置
void GetCursorPosition(POINT &pt) {
    GetCursorPos(&pt);
    ScreenToClient(myWindow, &pt);
    pt.y = pt.y / 16;
    pt.x = pt.x / 8;
    return;
}

//设置光标可见性
void SetCursorVisibility(int x) {
    CONSOLE_CURSOR_INFO cursor_info = {1, x}; // 第二个值为 0 表示隐藏光标
    SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}

//按钮类(使用宽字符)
class Button {
    public:
        int x, y;
        wstring name;  // 改为宽字符串
        int len;

        // 构造函数
        Button(int _x, int _y, const wstring& _name, int _len, bool isPrint = true): x(_x), y(_y), name(_name), len(_len) {
            if (isPrint) {
                SetCursorPosition(x, y);
                WriteConStr(name);
            }
        }

        // 输出内容
        void printText() {
            SetCursorPosition(x, y);
            WriteConStr(name);
        }

        // 点击监听
        bool isClicked() {
            if (!isMyWinVisible || GetForegroundWindow() != myWindow) {
                return 0;
            }
            POINT pt;
            GetCursorPosition(pt);
            if (KEY_DOWN(MOUSE_MOVED)) {
                if (pt.y == y && (pt.x >= x && pt.x <= x + len)) {
                    return 1;
                }
            }
            return 0;
        }
};

// 将宽字符串转换为UTF-8字符串
string WStringToUTF8(const wstring& wstr) {
    if (wstr.empty()) return string();

    int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL);
    string strTo(size_needed, 0);
    WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &strTo[0], size_needed, NULL, NULL);
    return strTo;
}

// 将系统本地编码(GBK)字符串转换为宽字符串
wstring LocalToWString(const string& local) {
    if (local.empty()) return wstring();

    int size_needed = MultiByteToWideChar(CP_ACP, 0, local.c_str(), (int)local.size(), NULL, 0);
    wstring wstr(size_needed, 0);
    MultiByteToWideChar(CP_ACP, 0, local.c_str(), (int)local.size(), &wstr[0], size_needed);
    return wstr;
}

// IsWindow 检查窗口句柄是否存在
bool isHandleValid(HWND hwnd) {
    return ::IsWindow(hwnd);
}

// 显示或隐藏窗口
bool SetWindowVisibility(HWND hwnd, bool visible) {
    if (hwnd == NULL) return false;

    if (visible) {
        ShowWindow(hwnd, SW_SHOW);
        SetForegroundWindow(hwnd);
    } else {
        ShowWindow(hwnd, SW_HIDE);
    }
    return true;
}

// 查询窗口透明度
int GetWindowTransparency(HWND hwnd) {
    // 获取窗口扩展样式
    LONG_PTR exStyle = GetWindowLongPtr(hwnd, GWL_EXSTYLE);
    // 检查窗口是否支持分层(透明)
    if (!(exStyle & WS_EX_LAYERED)) {
        return 255; // 完全不透明
    }
    // 获取透明度值
    BYTE alpha = 255;
    DWORD flags;
    COLORREF color;
    GetLayeredWindowAttributes(hwnd, &color, &alpha, &flags);
    // 如果使用Alpha值,则返回
    if (flags & LWA_ALPHA) {
        return (int)alpha;
    }
    return 255; // 没有使用Alpha透明度
}

// 设置窗口透明度 (0-255, 0=完全透明, 255=完全不透明)
bool SetWindowTransparency(HWND hwnd, int alpha) {
    if (hwnd == NULL) return false;
    // 设置分层窗口属性
    LONG_PTR exStyle = GetWindowLongPtr(hwnd, GWL_EXSTYLE);
    SetWindowLongPtr(hwnd, GWL_EXSTYLE, exStyle | WS_EX_LAYERED);
    // 设置透明度
    return SetLayeredWindowAttributes(hwnd, 0, (BYTE)alpha, LWA_ALPHA);
}

// 获取窗口标题(返回宽字符串)
wstring GetWindowTitleW(HWND hwnd) {
    wchar_t windowTitle[1024];
    GetWindowTextW(hwnd, windowTitle, 1024);
    wstring res = windowTitle;
    if ((int)res.size() > 33) {
        res = res.substr(0, 33);
        res += L"...";
    }
    return res;
}

// 获取窗口标题(兼容旧代码,返回UTF-8字符串)
string GetWindowTitle(HWND hwnd) {
    wstring wtitle = GetWindowTitleW(hwnd);
    return WStringToUTF8(wtitle);
}

//查询指定窗口是否隐藏
bool isWindowHidden(HWND hwnd) {
    if (hwnd == NULL) {
        return 0;
    }
    LONG style = GetWindowLong(hwnd, GWL_STYLE);
    return (style & WS_VISIBLE) == 0;
}

//查询指定窗口是否置顶
bool IsWindowTopmost(HWND hwnd) {
    LONG_PTR exStyle = GetWindowLongPtr(hwnd, GWL_EXSTYLE);
    return (exStyle & WS_EX_TOPMOST) != 0;
}

//设置指定窗口是否置顶
void SetWindowTopmost(HWND hwnd, bool topmost) {
    HWND insertAfter = topmost ? HWND_TOPMOST : HWND_NOTOPMOST;
    SetWindowPos(hwnd, insertAfter, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
}

int deltaAlpha = 20;

vector<HWND> hiddenWindow;

bool isListen = 0;

//  给按键监听器进行实例化
GlobalKeyListener& listener = GlobalKeyListener::GetInstance();

void addListener() {
//  绑定按键监听
//  Ctrl+Alt+Space 藏自己
    listener.RegisterComboKey(VK_SPACE, 1, 1, 0, 0, [](const KeyInfo & info) {
        if (info.eventType == KeyEventType::KEY_UP) {
            if (SetWindowVisibility(myWindow, !isMyWinVisible)) {
                isMyWinVisible = !isMyWinVisible;
            }
        }
    }, 1);

//  Alt+VK_RIGHT 不透明
    listener.RegisterComboKey(VK_RIGHT, 0, 1, 0, 0, [](const KeyInfo & info) {
        if (info.eventType == KeyEventType::KEY_UP) {
            HWND nowH = GetForegroundWindow();
            int nowA = GetWindowTransparency(nowH);
            if (nowA > 255 - deltaAlpha) {
                SetWindowTransparency(nowH, 255);
            } else {
                SetWindowTransparency(nowH, nowA + deltaAlpha);
            }
        }
    }, 1);

//  Alt+VK_LEFT 透明
    listener.RegisterComboKey(VK_LEFT, 0, 1, 0, 0, [](const KeyInfo & info) {
        if (info.eventType == KeyEventType::KEY_UP) {
            HWND nowH = GetForegroundWindow();
            int nowA = GetWindowTransparency(nowH);
            if (nowA < 2 * deltaAlpha) {
                SetWindowTransparency(nowH, deltaAlpha);
            } else {
                SetWindowTransparency(nowH, nowA - deltaAlpha);
            }
        }
    }, 1);

//  Ctrl+Space 隐藏
    listener.RegisterComboKey(VK_SPACE, 1, 0, 0, 0, [](const KeyInfo & info) {
        if (info.eventType == KeyEventType::KEY_UP) {
            HWND nowH = GetForegroundWindow();
            if (!isWindowHidden(nowH) && SetWindowVisibility(nowH, 0)) {
                if (nowH == myWindow) {
                    isMyWinVisible = false;
                } else {
                    hiddenWindow.push_back(nowH);
                }
            }
        }
    }, 1);

//  Alt+VK_UP 置顶
    listener.RegisterComboKey(VK_UP, 0, 1, 0, 0, [](const KeyInfo & info) {
        if (info.eventType == KeyEventType::KEY_UP) {
            HWND nowH = GetForegroundWindow();
            SetWindowTopmost(nowH, 1);
        }
    }, 1);

//  Alt+VK_DOWN 置顶
    listener.RegisterComboKey(VK_DOWN, 0, 1, 0, 0, [](const KeyInfo & info) {
        if (info.eventType == KeyEventType::KEY_UP) {
            HWND nowH = GetForegroundWindow();
            SetWindowTopmost(nowH, 0);
        }
    }, 1);
}

int main() {
    // 设置控制台输出编码为UTF-8(尽管我们使用WriteConsoleW,但系统命令可能需要)
//  system("chcp 65001 > nul");

    // 设置控制台标题为宽字符
    SetConsoleTitleW(L"窗口透明器");

    // 设置长宽  70 列,18 行,最好别改
    system("mode con cols=70 lines=18");

    system("cls");

    // 尝试获取本程序的窗口句柄
    try {
        myWindow = GetConsoleWindow();
        if (myWindow == NULL) {
            // 无法获取,抛异常
            throw runtime_error("无法获取窗口句柄");
        }
    } catch (const runtime_error& e) {
        // 报错,退出程序
        SetCursorPosition(0, 0);
        SetColorAndBackground(COLOR_LIGHTWHITE, COLOR_RED);
        WriteConStr(L"ERROR:");
        SetColorAndBackground(COLOR_WHITE, COLOR_BLACK);

        // 转换错误信息为宽字符
        wstring errorMsg = LocalToWString(e.what());
        WriteConStr(errorMsg + L"\n");

        system("pause");
        exit(0);
    } catch (...) {
        system("cls");
        SetCursorPosition(0, 0);
        SetColorAndBackground(COLOR_LIGHTWHITE, COLOR_RED);
        WriteConStr(L"UNKNOWN ERROR");
        system("pause");
        exit(0);
    }

    hiddenWindow.clear();

    // 隐藏光标
    SetCursorVisibility(0);

    system("cls");

    // IDE 内编译警告:
    SetCursorPosition(0, 0);
    SetColorAndBackground(COLOR_LIGHTRED, COLOR_BLACK);
    WriteConStr(L"编译完成后,请勿通过 IDE 直接运行!\n\n");
    WriteConStr(L"请通过文件夹内双击程序运行!\n\n");
    WriteConStr(L"如果对任务管理器等窗口不生效,请尝试用管理员身份运行!\n\n");
    SetColorAndBackground(COLOR_LIGHTYELLOW, COLOR_BLACK);
    WriteConStr(L"建议不要开启快速编辑模式!\n");
    WriteConStr(L"否则像下面的按钮可能会无法点击!\n");

    Button knowWarning(0, 9, L">我明白了", 9);
    while (1) {
        if (knowWarning.isClicked()) {
            while (knowWarning.isClicked()) {
                // TODO
            }
            break;
        }
        Sleep(5);
    }

    // 主页面
    system("cls");

    // 启动监听

    addListener();
    try {
        // 失败:
        if (!listener.Start()) {
            throw runtime_error("无法监听按键");
        } else {
            isListen = 1;
        }
    } catch (const runtime_error& e) {
        SetColorAndBackground(COLOR_LIGHTWHITE, COLOR_RED);
        WriteConStr(L"\nERROR:");
        SetColorAndBackground(COLOR_WHITE, COLOR_BLACK);

        wstring errorMsg = LocalToWString(e.what());
        WriteConStr(errorMsg + L"\n");

        system("pause");
        exit(0);
    } catch (...) {
        WriteConStr(L"\n");
        SetColorAndBackground(COLOR_LIGHTWHITE, COLOR_RED);
        WriteConStr(L"UNKNOWN ERROR\n");
        system("pause");
        exit(0);
    }

    system("cls");

    SetColorAndBackground(COLOR_LIGHTPURPLE, COLOR_BLACK);
    WriteConStr(L"窗口透明 v1.2.0\n");

    SetColorAndBackground(COLOR_LIGHTGREEN, COLOR_BLACK);
    Button help(0, 3, L">程序使用指南", 13);
    SetColorAndBackground(COLOR_LIGHTGREEN2, COLOR_BLACK);
    Button showList(0, 5, L">隐藏窗口列表", 13);
    SetColorAndBackground(COLOR_LIGHTGREEN3, COLOR_BLACK);
    Button getRest(0, 7, L">暂停软件功能", 13);
    SetColorAndBackground(COLOR_WHITE, COLOR_BLACK);
    Button leave(0, 9, L">退出程序并显示所有隐藏的窗口", 29);

    Button goHome(0, 17, L">返回", 5, 0);

    while (1) {
        if (help.isClicked()) {
            system("cls");
            SetCursorPosition(0, 0);
            SetColorAndBackground(COLOR_PURPLE, COLOR_BLACK);
            WriteConStr(L"欢迎使用本程序!\n\n");
            SetColorAndBackground(COLOR_LIGHTBLUE, COLOR_BLACK);
            WriteConStr(L"Alt+方向键右 可以让最前方的窗口更不透明!\n");
            WriteConStr(L"Alt+方向键左 可以让最前方的窗口更透明!\n\n");
            SetColorAndBackground(COLOR_LIGHTGREEN2, COLOR_BLACK);
            WriteConStr(L"Ctrl+Alt+空格 可以隐藏或显示本窗口!\n\n");
            SetColorAndBackground(COLOR_LIGHTRED, COLOR_BLACK);
            WriteConStr(L"Ctrl+空格 可以隐藏任意窗口!\n");
            WriteConStr(L"隐藏的窗口可以在主页的\"隐藏窗口列表\"中恢复!\n\n");
            SetColorAndBackground(COLOR_LIGHTWHITE, COLOR_BLACK);
            WriteConStr(L"Alt+方向键上 可以置顶任意窗口!\n");
            WriteConStr(L"Alt+方向键下 可以取消置顶任意窗口!\n\n");
            SetColorAndBackground(COLOR_LIGHTPURPLE, COLOR_BLACK);
            WriteConStr(L"点击主页\"暂停/恢复软件功能\"可以开启或暂停功能!\n\n");
            SetColorAndBackground(COLOR_WHITE, COLOR_GREEN);
            WriteConStr(L"适当的摸鱼是为了提高效率 ^_^\n");
            SetColorAndBackground(COLOR_WHITE, COLOR_BLACK);
            goHome.printText();
            while (1) {
                if (goHome.isClicked()) {
                    goto HomeText;
                }
                Sleep(2);
            }
        } else if (showList.isClicked()) {
            while (showList.isClicked()) {
                //TODO
            }

            int Start = 0;
            vector<Button> Items;
            Button Front(0, 16, L">上一页", 7, 0);
            Button Next(60, 16, L">下一页", 7, 0);
            Button Renew(62, 17, L">刷新", 5, 0);

            while (1) {
                hiddenWindow.erase(remove_if(hiddenWindow.begin(), hiddenWindow.end(), [](const HWND & nowH) {
                    return !isHandleValid(nowH) || !isWindowHidden(nowH);
                }), hiddenWindow.end());

                if (!hiddenWindow.size()) {
                    Start = 0;
                } else {
                    while (Start >= (int)hiddenWindow.size()) {
                        Start -= 15;
                    }
                }

                system("cls");
                SetCursorPosition(0, 0);
                SetColorAndBackground(COLOR_YELLOW, COLOR_BLACK);
                WriteConStr(L"隐藏的窗口如下,点击显示:\n");
                SetColorAndBackground(COLOR_WHITE, COLOR_BLACK);

                Items.clear();
                for (int i = 0; i < min(15, (int)hiddenWindow.size() - Start); i++) {
                    wstring n = GetWindowTitleW(hiddenWindow.at(Start + i));
                    if (n == L"") {
                        n = L"[无名窗口]";
                    }
                    // 确保显示宽度不超过70个字符
                    // 注意:中文和英文字符宽度不同,这里简化为字符数
                    Button tp(0, i + 1, n, 70, 1);
                    Items.push_back(tp);
                }

                SetColorAndBackground(COLOR_LIGHTBLUE, COLOR_BLACK);
                if (Start >= 15) {
                    Front.printText();
                }
                if (Start + 15 < (int)hiddenWindow.size()) {
                    Next.printText();
                }

                SetColorAndBackground(COLOR_WHITE, COLOR_BLACK);
                goHome.printText();
                Renew.printText();
                while (1) {
                    if (goHome.isClicked()) {
                        goto HomeText;
                    } else if (Start >= 15 && Front.isClicked()) {
                        Start -= 15;
                        break;
                    } else if (Start + 15 < (int)hiddenWindow.size() && Next.isClicked()) {
                        Start += 15;
                        break;
                    } else if (Renew.isClicked()) {
                        goto ListInner;
                    } else {
                        for (int i = 0; i < min(15, (int)Items.size()); i++) {
                            static HWND tp;
                            if (Items[i].isClicked()) {
                                tp = hiddenWindow.at(i + Start);
                                if (isHandleValid(tp)) {
                                    SetWindowVisibility(tp, 1);
                                }
                                hiddenWindow.erase(remove(hiddenWindow.begin(), hiddenWindow.end(), tp), hiddenWindow.end());
                                while (Items[i].isClicked()) {
                                    // TODO
                                }
                                goto ListInner;
                            }
                        }
                    }
                    Sleep(2);
                }
ListInner:
                ;
            }
        } else if (leave.isClicked()) {
            listener.Stop();
            hiddenWindow.erase(remove_if(hiddenWindow.begin(), hiddenWindow.end(), [](const HWND & nowH) {
                return !isHandleValid(nowH);
            }), hiddenWindow.end());
            for (auto i : hiddenWindow) {
                SetWindowVisibility(i, 1);
                Sleep(2);
            }
            exit(0);
        } else if (goHome.isClicked()) {
            while (goHome.isClicked()) {
                //TODO
            }
HomeText:
            ;
            system("cls");
            SetColorAndBackground(COLOR_LIGHTPURPLE, COLOR_BLACK);
            WriteConStr(L"窗口透明 v1.2.0\n");
            SetColorAndBackground(COLOR_LIGHTGREEN, COLOR_BLACK);
            help.printText();
            SetColorAndBackground(COLOR_LIGHTGREEN2, COLOR_BLACK);
            showList.printText();
            SetColorAndBackground(COLOR_LIGHTGREEN3, COLOR_BLACK);
            getRest.printText();
            SetColorAndBackground(COLOR_WHITE, COLOR_BLACK);
            leave.printText();
        } else if (getRest.isClicked()) {
            while (getRest.isClicked()) {
                //TODO
            }
            if (isListen) {
                listener.Stop();
                getRest.name = L">恢复软件功能";
                isListen = 0;
            } else {
                if (listener.Start()) {
                    addListener();
                    getRest.name = L">暂停软件功能";
                    isListen = 1;
                }
            }
            getRest.printText();  // 更新按钮显示
            goto HomeText;
        }
        Sleep(2);
    }
    return 0;
}

:::

编译须知

至少需要 C++14。同时,代码大量使用宽字符,确保你的 IDE 保存代码时用的是 UTF-8 格式,编译时也是用的 UTF-8。也就是说,请你在“编译时加入下列选项”中添加:

-std=c++14 -finput-charset=UTF-8

:::error[注意] 有一条与上面很像的编译命令,为:

-fexec-charset=UTF-8

除非你知道在干什么,否则不要添加。 :::

如果希望可执行文件在不同电脑上都可以运行,请在“链接时加入下列选项”中添加:

-static-libgcc
-static-libstdc++

免责 & 后记

如果您选择使用本程序,那么将默认您会承担使用本程序所造成的一切后果。

作者鼓励并倡导将此程序用于正当的学习用途。如果因使用者使用不当,由此造成的一切后果作者概不负责。

作者承诺在发布代码前已经尽可能地进行了检查,但本程序仍然不能排除有存在错误与漏洞的可能。如果您发现了错误与漏洞,请尽快与作者私信反馈。如果本程序的错误与漏洞对您的电脑造成了损害,作者不承担任何责任,但会积极与您配合减小损害。

未经作者亲自许可,不得随意修改本程序的任何部分。如果未经允许随意修改本程序,由此造成的一切后果作者概不负责。

本程序有关按键监听的部分代码由 DeepSeek 编写。

Update