Skip to content

Feature Proposal: Add ClipboardWatcher for FastCopy #35

@AlexAdasCca

Description

@AlexAdasCca

考虑开启一个窗口捕获文件复制到剪贴板的操作,可以尝试解析多个文件的路径,Windows提供了此功能。

API 还可以分辨出是否是 Cut 操作。

然后,复用右键菜单的 Paste 等功能。

下面是一个快速原型验证:

// ClipboardCopyFileTest.cpp
//

#include <iostream>
#include <vector>
#include <functional>
#include <windows.h>
#include <shellapi.h>
#include <Shlobj.h>
#include <assert.h>


class ClipboardListener
{
public:
    struct FileClipboardEventArg
    {
        std::vector<std::wstring> files;
        bool isMove = false;
    };

    using Handler = std::function<void(FileClipboardEventArg const &)>;

    void OnFileClipboard(std::function<void(FileClipboardEventArg const &)> func)
    {
        m_handlers.push_back(std::move(func));
        createWindowIfNotCreated();
    }

    static ClipboardListener& GetInstance()
    {
        static ClipboardListener s;
        return s;
    }

private:
    constexpr static auto MessageWindowClassName = L"FastCopyClipboardMessageWindowClass";

    HWND m_hwnd{};
    std::vector<Handler> m_handlers;

    void createWindowIfNotCreated()
    {
        if (!m_hwnd) createWindow();
    }

    void createWindow()
    {
        WNDCLASSW wc{};
        wc.lpszClassName = MessageWindowClassName;
        wc.lpfnWndProc = +[](HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) -> LRESULT
            {
                auto& self = ClipboardListener::GetInstance();
                switch (msg)
                {
                case WM_CREATE:
                    AddClipboardFormatListener(hwnd);
                    break;

                case WM_CLIPBOARDUPDATE:
                    self.handleClipboardUpdate();
                    break;

                case WM_DESTROY:
                    RemoveClipboardFormatListener(hwnd);
                    break;
                }
                return DefWindowProcW(hwnd, msg, wParam, lParam);
            };

        RegisterClassW(&wc);
        m_hwnd = CreateWindowW(
            MessageWindowClassName, L"", 0,
            0, 0, 0, 0,
            HWND_MESSAGE, nullptr, nullptr, nullptr
        );
        assert(m_hwnd);
    }

    void handleClipboardUpdate()
    {
        FileClipboardEventArg arg;
        if (!readFileClipboard(arg))
            return;

        for (auto& h : m_handlers)
            h(arg);
    }

    static bool readFileClipboard(FileClipboardEventArg& out);
};

bool ClipboardListener::readFileClipboard(FileClipboardEventArg& out)
{
    if (!IsClipboardFormatAvailable(CF_HDROP))
        return false;

    if (!OpenClipboard(nullptr))
        return false;

    HDROP hDrop = (HDROP)GetClipboardData(CF_HDROP);
    if (!hDrop) {
        CloseClipboard();
        return false;
    }

    UINT count = DragQueryFileW(hDrop, 0xFFFFFFFF, nullptr, 0);
    if (!count) {
        CloseClipboard();
        return false;
    }

    out.files.clear();
    out.files.reserve(count);

    for (UINT i = 0; i < count; ++i)
    {
        UINT len = DragQueryFileW(hDrop, i, nullptr, 0);
        std::wstring path(len, L'\0');
        DragQueryFileW(hDrop, i, path.data(), len + 1);
        out.files.push_back(std::move(path));
    }

    // 判定 Copy / Move
    UINT cfDropEffect = RegisterClipboardFormatW(CFSTR_PREFERREDDROPEFFECT);
    bool isMove = false;
    if (IsClipboardFormatAvailable(cfDropEffect))
    {
        if (HGLOBAL hEffect = (HGLOBAL)GetClipboardData(cfDropEffect))
        {
            DWORD* p = (DWORD*)GlobalLock(hEffect);
            if (p)
            {
                isMove = (*p & DROPEFFECT_MOVE) != 0;
                GlobalUnlock(hEffect);
            }
        }
    }
    out.isMove = isMove;

    CloseClipboard();
    return !out.files.empty();
}


int main()
{
    // Test
    ClipboardListener::GetInstance().OnFileClipboard([](ClipboardListener::FileClipboardEventArg const & arg)
        {
            std::wcout << L"Got " << arg.files.size() << L" files, isMove=" << arg.isMove << std::endl;
            for (auto& f : arg.files)
                std::wcout << f << std::endl;
            std::wcout << std::endl;
        }   
    );

    MSG msg{};
    while (GetMessageW(&msg, nullptr, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessageW(&msg);
    }

    return 0;
}

实际效果如图所示:

Image

参考官方的文档了解更多有关剪贴板数据类型的信息:
https://learn.microsoft.com/zh-cn/windows/win32/shell/clipboard

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions