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界面大眼瞪小眼占位置。

相关推荐
曲幽6 小时前
FastAPI + PostgreSQL 实战:从入门到不踩坑,一次讲透
python·sql·postgresql·fastapi·web·postgres·db·asyncpg
用户83562907805110 小时前
使用 C# 在 Excel 中创建数据透视表
后端·python
码路飞13 小时前
FastMCP 实战:一个 .py 文件,给 Claude Code 装上 3 个超实用工具
python·ai编程·mcp
dev派15 小时前
AI Agent 系统中的常用 Workflow 模式(2) Evaluator-Optimizer模式
python·langchain
前端付豪17 小时前
AI 数学辅导老师项目构想和初始化
前端·后端·python
用户03321266636717 小时前
将 PDF 文档转换为图片【Python 教程】
python
悟空爬虫19 小时前
UV实战教程,我啥要从Anaconda切换到uv来管理包?
python
dev派19 小时前
AI Agent 系统中的常用 Workflow 模式(1)
python·langchain
明月_清风21 小时前
从“能用”到“专业”:构建生产级装饰器与三层逻辑拆解
后端·python
曲幽1 天前
数据库实战:FastAPI + SQLAlchemy 2.0 + Alembic 从零搭建,踩坑实录
python·fastapi·web·sqlalchemy·db·asyncio·alembic