剪贴板(ClipBoard)是内存中的一块区域,是Windows内置的一个非常有用的工具,通过剪贴板,架起了一座彩桥,使得在各种应用程序之间,传递和共享信息成为可能。监控剪贴板是比较常见的一种剪贴板应用方式,下面我们通过一个简单实列完成相关知识的学习。

剪贴板监控功能控制

        在SystemPanel的菜单资源编辑器中添加新的菜单项

android剪贴板监听 手机剪贴板监控_学习

 修改菜单变量名,添加菜单事件处理函数

android剪贴板监听 手机剪贴板监控_学习_02

android剪贴板监听 手机剪贴板监控_#include_03

 在剪贴板监控控制菜单处理函数中添加如下代码:

void SystemPanel::Onm_ClipBoardSelected(wxCommandEvent& event)
{
    if(event.IsChecked())
    {
        AddClipboardFormatListener(wxTheApp->GetTopWindow()->GetHandle());
    }
    else
    {
        RemoveClipboardFormatListener(wxTheApp->GetTopWindow()->GetHandle());
    }
}

监控剪贴板消息

        由于剪贴板变化消息WM_CLIPBOARDUPDATE属于系统级消息,目前只能在主窗口中获取,所以我们只能通过重载主窗口系统消息处理函数然后转发给SystemPanel中的处理函数来实现。

首先,我们在主窗口MSWWindowProc添加WM_CLIPBOARDUPDATE处理,代码如下:

//处理系统消息和自定义消息
WXLRESULT SDKLearnFrame::MSWWindowProc(WXUINT message, WXWPARAM wParam, WXLPARAM lParam)
{
    switch (message)
    {
    case WM_CLIPBOARDUPDATE:
    {
        m_SystemPanel->DoClipboardUpdate(message,wParam,lParam);
    }
    break;
    default:
        break;
    }

    return wxFrame::MSWWindowProc(message, wParam, lParam);
}

剪贴板变化具体的处理过程将会放到SystemPanel的DoClipboardUpdate函数中进行。

处理剪贴板变化

在SystemPanel的DoClipboardUpdate函数中添加如下代码:

#include <filesystem>
using namespace filesystem;
//处理剪贴板改变消息
void SystemPanel::DoClipboardUpdate(WXUINT message,WXWPARAM wParam, WXLPARAM lParam)
{
    cout<<_("message: 剪贴板改变了!")<<message<<endl;

    if (!IsClipboardFormatAvailable(CF_HDROP))
        return;
    if (!OpenClipboard(NULL))
        return;

    HDROP hDrop = (HDROP)::GetClipboardData(CF_HDROP);

    GlobalLock(hDrop);
    if (hDrop != NULL)
    {
        TCHAR filename[MAX_PATH+1];
        int fileCount = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);

        long indexItem;
        for (int i = 0; i < fileCount; ++i)
        {
            DragQueryFile(hDrop, i, filename, sizeof(filename));
            path file_path(filename);
            UINT sizefile;

            //判断file_path是否为文件夹
            if(is_directory(file_path))
            {
                recursive_directory_iterator beg_iter(file_path);
                recursive_directory_iterator end_iter;
                for (; beg_iter != end_iter; ++beg_iter)
                {

                    if (is_directory(*beg_iter))
                    {
                        continue;
                    }
                    else
                    {
                        if(m_FileList->FindItem(-1,beg_iter->path().wstring())!=wxNOT_FOUND)
                            continue;
                        indexItem = m_FileList->InsertItem(m_FileList->GetItemCount(),_(""));

                        if(is_regular_file(beg_iter->path()))	//推断是否为普通文件
                        {
                            sizefile = file_size(beg_iter->path());  //文件大小(字节)
                            m_FileList->SetItem(indexItem, 0, beg_iter->path().wstring());
                            m_FileList->SetItem(indexItem, 1,to_string(file_size(beg_iter->path())));
                        }

                    }
                }
            }
            //是文件
            else
            {
                if(exists(file_path))  //推断文件存在性
                {
                    if(m_FileList->FindItem(-1,file_path.wstring())!=wxNOT_FOUND)
                        continue;
                    indexItem = m_FileList->InsertItem(m_FileList->GetItemCount(),_(""));
                    sizefile = file_size(file_path);  //文件大小(字节)
                    m_FileList->SetItem(indexItem, 0, file_path.wstring());
                    m_FileList->SetItem(indexItem, 1,to_string(sizefile));

//                        cout<<"File:"<<file_path.string()<<" SIZE:"<<ss.str()<<endl;
                }

            }
        }
    }

//    }
    GlobalUnlock(hDrop);
//    DragFinish(hDrop);//释放文件名缓冲区
    CloseClipboard();
}

在剪贴板变化消息处理过程中,我们希望把文件拷贝消息中的所有文件、目录、目录下的所有文件都显示在SystemPanel的ListCrtl控件中。

android剪贴板监听 手机剪贴板监控_android剪贴板监听_04

 首先,需要在资源编辑窗口中重命名ListCtrl变量为m_FileList。

ListCtrl配置及初始化过程如下:

android剪贴板监听 手机剪贴板监控_c++_05

在资源属性编辑窗口中设置ListCtrl为Report模式。

在SystemPanel初始化函数中添加如下代码设置ListCtrl的表头

//设置列表表头
    // 添加第一列的表头
    wxListItem col0;
    col0.SetId(0); // id必须设置,代表第0列
    col0.SetText("文件名");
    col0.SetWidth(350); // 设置列宽
    col0.SetAlign(wxLIST_FORMAT_CENTER); // 此列的文本居中显示
    m_FileList->InsertColumn(0, col0); // 插入列

    // 添加第二列的表头
    wxListItem col1;
    col1.SetId(1);
    col1.SetText("文件大小");
    m_FileList->InsertColumn(1, col1);

    // 添加第三列的表头
    wxListItem col2;
    col2.SetId(2);
    col2.SetText("状态");
    m_FileList->InsertColumn(2, col2);

剪贴板消息中如果包含文件或文件夹的信息,我们可以通过导入<filesystem>头文件来实现文件及文件夹相关操作。比如:

遍历目录获取目录中所有文件名

android剪贴板监听 手机剪贴板监控_#include_06

在菜单编辑窗口中,添加遍历目录菜单

android剪贴板监听 手机剪贴板监控_剪贴板_07

android剪贴板监听 手机剪贴板监控_android剪贴板监听_08

 

android剪贴板监听 手机剪贴板监控_android剪贴板监听_09

 消息响应函数代码如下:

//获取目录中所有文件
#include <filesystem>
using namespace std::filesystem;
#include <wx/dirdlg.h>

//提升用户权限,解决特殊目录监控软件崩溃的问题
#define LAA(se) {{se},SE_PRIVILEGE_ENABLED|SE_PRIVILEGE_ENABLED_BY_DEFAULT}
#define BEGIN_PRIVILEGES(tp, n) static const struct {ULONG PrivilegeCount;LUID_AND_ATTRIBUTES Privileges[n];} tp = {n,{
#define END_PRIVILEGES }};
// in case you not include wdm.h, where this defined
#define SE_BACKUP_PRIVILEGE (17L)
ULONG AdjustPrivileges()
{
    if (ImpersonateSelf(SecurityImpersonation))
    {
        HANDLE hToken;
        if (OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, TRUE, &hToken))
        {
            BEGIN_PRIVILEGES(tp, 1)
            LAA(SE_BACKUP_PRIVILEGE),
                END_PRIVILEGES
                AdjustTokenPrivileges(hToken, FALSE, (PTOKEN_PRIVILEGES)&tp, 0, 0, 0);
            CloseHandle(hToken);
        }
    }

    return GetLastError();
}
//参考https://stackoverflow.com/questions/51799541/c-filesystem-iterator-invalid-argument

void SystemPanel::Onm_GetAllFilesSelected(wxCommandEvent& event)
{
     wxString pathname=wxDirSelector(_("请选择要遍历的目录!"), "",
                                    wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST);
    if ( pathname.empty() )
        return;
    AdjustPrivileges();//提升权限,解决特殊文件访问导致的软件崩溃
    for(auto &p:std::filesystem::recursive_directory_iterator(pathname.ToStdString()))
    {
        std::cout << p.path() << endl;
        if(m_FileList->FindItem(-1,p.path().wstring(),false)!=wxNOT_FOUND)
            continue;
        auto indexItem = m_FileList->InsertItem(m_FileList->GetItemCount(),_(""));
        m_FileList->SetItem(indexItem, 0, (p.path().wstring()));
        if(!std::filesystem::is_directory(p))
            m_FileList->SetItem(indexItem, 1,to_string(std::filesystem::file_size(p)));
        else
            m_FileList->SetItem(indexItem, 1,_("Dir"));
        m_FileList->SetItem(indexItem, 2,p.path().extension().wstring() );

    }
    cout<< "文件数量: "<<m_FileList->GetItemCount()<<endl;
}

在遍历目录的代码中增加了提升用户权限的代码,主要是针对部分系统目录或特殊软件目录,使用程序运行用户权限操作访问会导致错误提示,而我们没有提供错误处理机制,所以导致软件崩溃退出。

获取文件真实类型

android剪贴板监听 手机剪贴板监控_c++_10

在菜单编辑窗口中添加新菜单

android剪贴板监听 手机剪贴板监控_#include_11

 在资源属性窗口中修改菜单变量并添加菜单响应函数

android剪贴板监听 手机剪贴板监控_#include_12

此时我们会用到一个特别的库libmagic,这是一个用来根据文件头识别文件类型的开发库。该库的主页地址如下:

libmagic download | SourceForge.net

在Msys2命令行窗口中运行如下命令,安装该库

pacman -S mingw-w64-x86_64-file

在Msys2命令行窗口中运行如下命令,检查该库是否工作正常

file -i --mime 文件全路径

在Msys2命令行窗口中运行如下命令,找到文件头信息配置文件magic.mgc拷贝到程序代码根目录

pacman -Ql mingw-w64-x86_64-file

android剪贴板监听 手机剪贴板监控_学习_13

在项目资源管理窗口中添加需要加载的库文件libmagic.dll.a

android剪贴板监听 手机剪贴板监控_学习_14

android剪贴板监听 手机剪贴板监控_#include_15

android剪贴板监听 手机剪贴板监控_android剪贴板监听_16

android剪贴板监听 手机剪贴板监控_#include_17

android剪贴板监听 手机剪贴板监控_剪贴板_18

 在文件类型检测菜单响应函数中添加如下代码:

//获取文件真实类型
#include <wx/filedlg.h>
#include <magic.h>
magic_t handle_;
void SystemPanel::Onm_GetFileTypeSelected(wxCommandEvent& event)
{
    wxString filename=wxFileSelector(_("请选择文件!"));
    if ( filename.empty() )
        return;
    std::string magic_path = "magic.mgc";//数据库文件路径
   if (handle_ == NULL)
    {

        handle_ = magic_open(MAGIC_NONE | MAGIC_COMPRESS);
    }

    if (handle_ == NULL)
    {
        printf("magic_open handle failed!");
        return  ;
    }
    if (magic_load(handle_, magic_path.c_str()) != 0)
    {
        magic_close(handle_);
        printf("magic_load handle failed!");
        return  ;
    }
    auto type = magic_file(handle_, filename.c_str());
    if (type == NULL)
    {
        cout<< "The File Type unknown"<<endl;
        return ;
    }

    std::string file_type = type;

    cout<< "文件: "<<filename<<" 的类型是:"<<file_type<<endl;
}

编译并运行程序,此时监控拷贝目录消息、列出文件中所有文件及文件夹、文件类型检测功能已经实现。 

至此,剪贴板监控功能已经完整实现。