QuickUp v4 新功能一览

QuickUp v4 新功能一览

引言

项目地址:Smart-Space/QuickUp: 一个Windows快捷应用组启动器QuickUp: QuickUp Gitee

视频简介:QuickUp 4.0 前瞻

以下为文本介绍,具体实现见源码仓库。


尝试调整任务窗口排布

通过监听窗口创建、进程任务名匹配方式尽可能获得准确的窗口句柄:

cpp 复制代码
namespace WindowMonitor {
    // 带有时间戳的窗口记录
    struct WindowRecord {
        HWND hwnd;
        std::chrono::steady_clock::time_point showTime;

        bool operator==(const HWND& other) const {
            return hwnd == other;
        }
    };

    std::vector<WindowRecord> recentWindows;
    std::mutex rwMutex;
    
    std::thread monitorThread;
    std::atomic<bool> isListening{false};
    DWORD threadId = 0;
    HWINEVENTHOOK hEventHook = nullptr;

    const size_t MAX_RECENT_WINDOWS = 30;

    // WinEvent 回调函数
    void CALLBACK WinEventProc(
        HWINEVENTHOOK hWinEventHook,
        DWORD event,
        HWND hwnd,
        LONG idObject,
        LONG idChild,
        DWORD dwEventThread,
        DWORD dwmsEventTime
    ) {
        // 只关心顶层窗口本身的事件
        if (idObject != OBJID_WINDOW || idChild != CHILDID_SELF || hwnd == nullptr) {
            return;
        }

        // 过滤掉非顶层窗口
        if (GetParent(hwnd) != NULL) {
            return;
        }

        // 过滤掉明显的非主窗口 (例如没有所有者的弹出窗口)
        if (GetWindow(hwnd, GW_OWNER) != NULL) {
            return;
        }

        // 跳过不含标题栏的某些阴影或辅助窗口
        long style = GetWindowLong(hwnd, GWL_STYLE);
        if (!(style & WS_CAPTION)) {
            return;
        }

        auto now = std::chrono::steady_clock::now();

        std::lock_guard<std::mutex> lock(rwMutex);
        
        // 避免重复添加相同的 HWND
        std::erase_if(recentWindows, [hwnd](const WindowRecord& r) {
            return r.hwnd == hwnd;
        });
        
        // 最新的在最后
        recentWindows.push_back({hwnd, now});

        // 维持 vector 大小
        if (recentWindows.size() > MAX_RECENT_WINDOWS) {
            recentWindows.erase(recentWindows.begin());
        }
    }

    // 线程执行的监听循环
    void MessageLoop() {
        // 记录当前线程ID
        threadId = GetCurrentThreadId();

        hEventHook = SetWinEventHook(
            EVENT_OBJECT_SHOW, EVENT_OBJECT_SHOW,
            NULL,
            WinEventProc,
            0, 0, // 监听所有进程和线程
            WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS // 进程外回调,跳过自己
        );

        if (!hEventHook) {
            // std::wcerr << L"Failed to set WinEventHook!" << std::endl;
            return;
        }

        MSG msg;
        while (GetMessage(&msg, NULL, 0, 0)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        // 退出前清理
        if (hEventHook) {
            UnhookWinEvent(hEventHook);
            hEventHook = nullptr;
        }
    }

    //==========外部的接口==========

    void Start() {
        if (isListening) return;
        isListening = true;
        
        {
            std::lock_guard<std::mutex> lock(rwMutex);
            recentWindows.clear();
            recentWindows.reserve(MAX_RECENT_WINDOWS+1);// 预留一个位置
        }

        monitorThread = std::thread(MessageLoop);
    }

    void Stop() {
        if (!isListening) return;
        isListening = false;

        // 向子线程发送 WM_QUIT 消息
        if (threadId != 0) {
            PostThreadMessage(threadId, WM_QUIT, 0, 0);
        }

        if (monitorThread.joinable()) {
            monitorThread.join();
        }

        {
            std::lock_guard<std::mutex> lock(rwMutex);
            recentWindows.clear();
        }
        threadId = 0;
    }

    // 获取最近窗口的一份拷贝,以便主线程慢慢处理而不长时间占用锁
    std::vector<WindowRecord> GetRecentWindowsReversed() {
        std::lock_guard<std::mutex> lock(rwMutex);
        std::vector<WindowRecord> copy = recentWindows;
        std::reverse(copy.begin(), copy.end()); // 翻转,最新的在最前面
        return copy;
    }
}

以上非完整代码,但已经足够展现框架和逻辑。

接下来通过SetWindowPos调整窗口位置和尺寸。需要注意,SetWindowPos实际上调整的是整体窗口,而非窗口可见区域。在Windows上,窗口可能包含渲染阴影、不可见边框等等,因此需要转换(该代码见源码zoneutils.h中的WindowMonitor::AdjustRectForSizeWindowToRect,极大参考了powertoys)。窗口移动代码如下:

cpp 复制代码
void SmoothMoveWindow(HWND hwnd, int x, int y, int w, int h, bool zone_round) {
    if (IsIconic(hwnd)) {
        ShowWindow(hwnd, SW_RESTORE);
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
	
    // 是否保留圆角
    if (!zone_round) {
        WindowMonitor::DisableRoundCorners(hwnd);
    }

    RECT rect = { x, y, x + w, y + h };
    rect = WindowMonitor::AdjustRectForSizeWindowToRect(hwnd, rect);
    x = rect.left;
    y = rect.top;
    w = rect.right - x;
    h = rect.bottom - y;

    UINT flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_ASYNCWINDOWPOS;
    SetWindowPos(hwnd, NULL, x, y, w, h, flags);
}

显示任务进展

这是个UI提示元素,这是防止在任务运行期间多次点击运行、修改或删除,但这些仍然可以通过快捷键进行操作(运行和修改,删除不支持快捷键),顺便进行提示。

正式支持隐藏任务

在之前,QuickUp会自动略过以[x]结尾的任务。在4.0版本中,QuickUp正式启用了对隐藏任务的全流程支持:

  • 隐藏任务、普通任务的切换
  • 任务名、任务可见性双向感知
  • 支持显示隐藏任务

隐藏任务非常有用,当一个任务被多个其他任务调用,而自身并不常用;或者已经作为快捷方式被放到桌面、开始菜单或者其他什么地方时,就显得非常方便实用。

以第二个情景为例(创造需求中......😵)。众所周知,Windows下,git默认不走系统代理,如果代理应用只用系统代理且也不是一直使用的话,就需要:

  • 打开代理
  • 设置git proxy
  • 关闭代理
  • 还原proxy以正常使用

那在QuickUp就这样设置:

创建任务快捷方式、(可以)改名快捷方式并(随意)放上自己喜欢的图标、留在桌面或者移动到开始菜单文件夹以替换代理应用快捷方式、隐藏原任务。这样,点击快捷方式后,先设置proxy、再启用代理应用,等到代理应用关闭后,再还原proxy。同时,也不会QuickUp界面大眼瞪小眼占位置。

相关推荐
喵手2 小时前
Python爬虫实战:电商问答语料构建完整实战 - 从爬取到检索语料的工程化实现(附CSV导出 + SQLite持久化存储)!
爬虫·python·sqlite·爬虫实战·零基础python爬虫教学·电商问答语料构建·爬取到检索语料
APIshop3 小时前
淘宝商品评论接口实战解析:从抓包到数据抓取全链路技术指南
java·python
~央千澈~3 小时前
抖音弹幕游戏开发之第14集:添加更多整蛊效果·优雅草云桧·卓伊凡
开发语言·python·游戏
多打代码4 小时前
2026.02.11
开发语言·python
AI周红伟5 小时前
周红伟:智能体实战,通过使用 Flask 的 REST API 在 Python 中部署 PyTorch
后端·python·flask
清水白石0085 小时前
Python 性能分析实战指南:timeit、cProfile、line_profiler 从入门到精通
开发语言·python
ValhallaCoder5 小时前
hot100-二分查找
数据结构·python·算法·二分查找
Suryxin.5 小时前
从0开始复现nano-vllm「llm_engine.py」
人工智能·python·深度学习·ai·vllm
PieroPc5 小时前
用python 写的 Gitee 数据备份工具
开发语言·python·gitee