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

相关推荐
2401_8246976615 小时前
如何实现SQL存储过程状态监控_编写实时运行监控仪表盘
jvm·数据库·python
iAm_Ike15 小时前
c++怎么在写入文件流时通过peek预读功能实现复杂的逻辑判断【实战】
jvm·数据库·python
dFObBIMmai15 小时前
mysql如何确保主从数据完全同步_开启半同步复制机制
jvm·数据库·python
才兄说15 小时前
机器人二次开发机器狗巡检?长距离最优路径
python
m0_4708576415 小时前
CSS如何实现Bootstrap进度条自定义动画_利用keyframe关键帧
jvm·数据库·python
nashane15 小时前
HarmonyOS 6学习:Navigation Dialog模式与智能Web长截图融合实践
人工智能·pytorch·python
m0_6245785916 小时前
SQL高效实现基于JOIN的交叉分析_多表关联实现多维统计
jvm·数据库·python
威联通网络存储16 小时前
QNAP 闪存底座:制造企业 ERP 数据库容灾方案
数据库·python·制造
ZHW_AI课题组16 小时前
基于AnimeGANv2的照片动漫化
图像处理·python
茉莉玫瑰花茶16 小时前
LangGraph 入门教程:构建 AI 工作流 [ 案例三 ]
前端·人工智能·python