一、复制粘贴的简单原理

复制粘贴大概可以分为以下两种场景:

  1. 在同一个进程中复制并粘贴
  2. 在进程A(源窗口)中复制,在进程B(目的窗口)中粘贴

场景1好理解,数据都在一个进程中,直接在内存中设置和读取就可以了,不涉及跨进程之间的问题

场景2数据会跨进程传递,那就应该用了某一种进程间通信技术,而在wayland平台使用的就是匿名管道(pipe)

但是,他们是直接进程A和进程B之间进行通信吗?还是怎样?具体下面会给出分析

二、几个关于复制粘贴相关的类

QClipboard: 提供了对窗口系统剪贴板的访问

  • 该类提供了一些方便的接口来访问普通数据类型,更灵活的数据可以通过mimeData()接口获取

  • 在应用程序中只有一个全局的QClipboard,你可以使用QApplication::clipboard()来获取

  • 你可以通过调用clear()清空剪贴板

  • QClipboard和QDragObject支持一样的数据类型,并且使用类似的机制

拓展:不同平台下的剪切板实现不同

X窗口系统有一个选择的概念—当文本被选择,它立即被复制到全局鼠标选择系统中,此时可用鼠标中键来粘贴全局鼠标选择系统中的内容;X窗口系统还有一个所有权的概念,如果你在一个窗口中改变了选择,X11仅仅通知变化的拥有者和前任拥有者

Windows仅仅在文本被显示地复制或者剪切的时候才被复制到剪贴板,是一个完完全全的全局资源,所以所有应用程序都会被通知变化

macOS支持一个单独的查找缓冲区,该缓冲区在“查找”操作中保存当前的搜索字符串。可以通过指定FindBuffer模式来访问此查找剪贴板。

通过以上拓展可以了解,QClipboard几个Mode {Clipboard, Selection, FindBuffer}的用处:

  • Clipboard 全局剪切板系统,所有平台都支持,包括wayland平台
  • Selection 全局鼠标选择系统,X11平台支持
  • FindBuffer 单独的查找缓冲区,只有macOS平台支持

QMimedata: 为数据提供一个容器,用来记录关于MIME类型数据的信息

  • 常用来描述保存在剪切板里信息,或者拖拽信息

  • QMimeData对象把它所保存的信息和正确的MIME类型连接起来,来保证信息可以被安全的在应用程序之间转移,或者在同一个应用程序之间拷贝

  • 对于最常见的MIME类型,QMimeData提供了方便的功能来访问数据:

    Tester Getter Setter MIME Types
    hasText() text() setText() text/plain
    hasHtml() html() setHtml() text/html
    hasUrls() urls() setUrls() text/uri-list
    hasImage() imageData() setImageData() image/ *
    hasColor() colorData() setColorData() application/x-color

以下是平台接口类

QInternalMimeData: 该类是一个接口类,由不同平台获取数据的类继承使用,其继承自QMimeData

QPlatformClipboard: 系统剪切板平台接口类,QClipboard数据设置和获取最终都是调用这个类

以下是wayland平台下和剪切板相关类:

QWaylandClipboard: wayland平台剪切板类,继承自QPlatformClipboard

QWaylandDataDevice: 该类是和窗口管理器交互的类,主要是通知和接受是否有剪切信息

QWaylandDataDeviceManager: 该类是管理QWaylandDataDevice的类,通过此类获取device对象

QWaylandDataSource: 该类是复制或剪切的源窗口需要构造的数据类

QWaylandDataOffer: 该类是粘贴的目的窗口需要构造的数据类

QWaylandMimeData: wayland平台真正接受剪切数据的类,继承自QInternalMimeData

总结了以上相关类的uml类图如下,可以更便于了解各个类之间的关系

三、wayland平台复制粘贴的内部实现

上面我们已经提前知道了,在wayland平台复制粘贴跨进程数据传递用的是匿名管道pipe,但这种通信是怎么进行的呢?带着上面的问题,再结合一下源代码详细梳理一下内部原理

首先我们通过调试代码知道,当我们在进程A中进行复制或剪切,会调用到代码:

void QClipboard::setMimeData(QMimeData* src, Mode mode)
{QPlatformClipboard *clipboard = QGuiApplicationPrivate::platformIntegration()->clipboard();if (!clipboard->supportsMode(mode)) {if (src != nullptr) {qDebug("Data set on unsupported clipboard mode. QMimeData object will be deleted.");src->deleteLater();}} else {clipboard->setMimeData(src,mode);}
}

此时会调用的wayland平台下的类中,即QWaylandClipboard类:

void QWaylandClipboard::setMimeData(QMimeData *data, QClipboard::Mode mode)
{auto *seat = mDisplay->currentInputDevice();if (!seat) {qCWarning(lcQpaWayland) << "Can't set clipboard contents with no wl_seats available";return;}static const QString plain = QStringLiteral("text/plain");static const QString utf8 = QStringLiteral("text/plain;charset=utf-8");if (data && data->hasFormat(plain) && !data->hasFormat(utf8))data->setData(utf8, data->data(plain));switch (mode) {case QClipboard::Clipboard:if (auto *dataDevice = seat->dataDevice()) {//构造QWaylandDataSource类,并设置给dataDevicedataDevice->setSelectionSource(data ? new QWaylandDataSource(mDisplay->dndSelectionHandler(), data) : nullptr);emitChanged(mode);}break;.....}
}

上面的关键函数为setSelectionSource,我们看下setSelectionSource的实现:

void QWaylandDataDevice::setSelectionSource(QWaylandDataSource *source)
{if (source)connect(source, &QWaylandDataSource::cancelled, this, &QWaylandDataDevice::selectionSourceCancelled);// wayland协议接口,通知窗口管理器此时有复制的数据set_selection(source ? source->object() : nullptr, m_inputDevice->serial());m_selectionSource.reset(source);
}

此时我们可以看出,进程A最终只是跟窗口管理器进行了通信,并不是直接跟进程B进行交互,这里也是回答了一开始提出的那个问题。

那窗口管理器又是如果通知到进程B的呢?当然我们猜测也是通过wayland协议接口,具体是哪几个接口呢?

这里先简单介绍一下wayland相关的内容,我们应该知道在wayland平台中所有窗口都是由窗口管理器进行统一管理的,而窗口和窗口管理器之间的交互是基于wayland协议。前面我们介绍了三个和窗口管理器通信的类,而这三个类就是继承了wayland的通信协议并实现了协议中的接口:

QWaylandDataDevice 继承自 wl_data_device

QWaylandDataSource 继承自 wl_data_source

QWaylandDataOffer 继承自 wl_data_offer

从第二章节我们了解到,QWaylandDataSource和QWaylandDataOffer是真正保存数据的类,并且前者是源窗口保存数据的类,后者是目的窗口保存数据的类,而QWaylandDataDevice就是管理和构造以上两个类,并且主要负责和窗口管理器进行同步的类。

从上面的复制过程的代码中,我们看到了QWaylandDataSource构造的过程,那目的窗口中的QWaylandDataOffer是如何构造的还不清楚,通过查看wayland_debug的日志可以看到,当进程B窗口刚激活时会有如下信号:

结合以上信号继续调试代码可以发现,会调到qtwayland的如下接口:

void QWaylandDataDevice::data_device_data_offer(struct ::wl_data_offer *id)
{// 这里创建QWaylandDataOffer对象new QWaylandDataOffer(m_display, id);
}
void QWaylandDataOffer::data_offer_offer(const QString &mime_type)
{// 同步format信息m_mimeData->appendFormat(mime_type);
}

最后调用selection接口,在这里发送了一个信号给到上层客户端,客户端可以通过绑定dataChanged()信号来开始获取剪切板的数据,但真正的数据还没获取到

void QWaylandDataDevice::data_device_selection(wl_data_offer *id)
{if (id)// 这里将创建的QWaylandDataOffer对象给到dataDevice保管m_selectionOffer.reset(static_cast<QWaylandDataOffer *>(wl_data_offer_get_user_data(id)));elsem_selectionOffer.reset();#if QT_CONFIG(clipboard)QGuiApplicationPrivate::platformIntegration()->clipboard()->emitChanged(QClipboard::Clipboard);
#endif
}

以下画了个交互图,方面理解wayland交互的流程:

对于上图的交互流程需要特别注意两点:

  1. wayland平台给客户端同步剪切板信息,只会在客户端激活的情况才会通知,没有激活是不会通知的
  2. 以上只是激活目的窗口的流程,激活后就会收到窗管的信号,并收到了相关的format信息,但此时真正的复制或剪切的数据还没有发送到目的窗口中。

此时在进程B中做粘贴的操作,继续调试代码,发现调用mimeData()接口取数据:

const QMimeData* QClipboard::mimeData(Mode mode) const
{QPlatformClipboard *clipboard = QGuiApplicationPrivate::platformIntegration()->clipboard();if (!clipboard->supportsMode(mode)) return nullptr;return clipboard->mimeData(mode);
}

查看对应wayland平台的类:

QMimeData *QWaylandClipboard::mimeData(QClipboard::Mode mode)
{auto *seat = mDisplay->currentInputDevice();if (!seat)return &m_emptyData;switch (mode) {case QClipboard::Clipboard:if (auto *dataDevice = seat->dataDevice()) {// 如果在同一进程复制粘贴,那source不为空,直接取source里面的mimedataif (auto *source = dataDevice->selectionSource()) {return source->mimeData();}// 如果是跨进程粘贴,那source为空,offer不为空,就取offer里面的mimedataif (auto *offer = dataDevice->selectionOffer()) {return offer->mimeData();}}return &m_emptyData;...}
}

从上面的代码中就可以看出,两种不同场景数据获取的来源。

获取了mimedata之后,需要取出里面的数据,例如text(),那看看怎么取text数据

QString QMimeData::text() const
{Q_D(const QMimeData);QVariant utf8Text = d->retrieveTypedData(textPlainUtf8Literal(), QMetaType::QString);if (!utf8Text.isNull())return utf8Text.toString();QVariant data = d->retrieveTypedData(textPlainLiteral(), QMetaType::QString);return data.toString();
}
QVariant QMimeDataPrivate::retrieveTypedData(const QString &format, QMetaType::Type type) const
{Q_Q(const QMimeData);QVariant data = q->retrieveData(format, QVariant::Type(type));...
}

这里看出,retrieveData接口是虚函数,就要看看子类的实现:

QVariant QInternalMimeData::retrieveData(const QString &mimeType, QVariant::Type type) const
{QVariant data = retrieveData_sys(mimeType, type);// 以下是对不同数据类型进行解析...return data;
}

接着看retrieveData_sys的实现:

QVariant QWaylandMimeData::retrieveData_sys(const QString &mimeType, QVariant::Type type) const
{Q_UNUSED(type);if (m_data.contains(mimeType))return m_data.value(mimeType);QString mime = mimeType;if (!m_types.contains(mimeType)) {if (mimeType == QStringLiteral("text/plain") && m_types.contains(utf8Text()))mime = utf8Text();elsereturn QVariant();}// 创建管道,pipefd[0]表示r端口,pipefd[1]表示w端口int pipefd[2];if (qt_safe_pipe(pipefd) == -1) {qWarning("QWaylandMimeData: pipe2() failed");return QVariant();}// 这里是发消息给窗管,通知窗管进程B准备接受数据,窗管会通知进程A写数据m_dataOffer->startReceiving(mime, pipefd[1]);close(pipefd[1]);QByteArray content;// 从管道中读数据if (readData(pipefd[0], content) != 0) {qWarning("QWaylandDataOffer: error reading data for mimeType %s", qPrintable(mimeType));content = QByteArray();}close(pipefd[0]);m_data.insert(mimeType, content);return content;
}

看到这里已经可以看出真相了,这里是开了一个管道,通过startReceiving接口通知窗管,再由窗管通知进程A写数据,然后在进程B中通过readData函数读取。

而进程A中写管道的代码如下:

void QWaylandDataSource::data_source_send(const QString &mime_type, int32_t fd)
{QByteArray content = QWaylandMimeHelper::getByteArray(m_mime_data, mime_type);if (!content.isEmpty()) {// Create a sigpipe handler that does nothing, or clients may be forced to terminate// if the pipe is closed in the other end.struct sigaction action, oldAction;action.sa_handler = SIG_IGN;sigemptyset (&action.sa_mask);action.sa_flags = 0;sigaction(SIGPIPE, &action, &oldAction);write(fd, content.constData(), content.size());sigaction(SIGPIPE, &oldAction, nullptr);}close(fd);
}

到此,关于wayland平台中复制粘贴的过程基本都和清楚了。以上的几个问题,也都得到了解答

总结一下:

  • 同一个进程的复制粘贴数据就保存在自己的内存中,直接设置和获取;

  • 不同进程之间复制粘贴的交互是通过窗口管理器进行间接交互的,数据是通过管道传输的。

四、目前wayland平台复制粘贴存在的问题

问题一:当在进程A中复制后,如果关闭了进程A,再在进程B中粘贴,发现没有数据?

原因:是因为wayland平台的数据传输是通过管道传输的,如果管道的一端关闭了,数据就传输不成功,如果要解决该问题,可能要修改wayland平台下的数据传输的方式,例如可以改成消息队列

问题二:大数据量(>70M)的复制和拷贝文本,在此过程中如果不停做点击鼠标和移动鼠标等动作,最后会导致进程崩溃?

原因:该问题的是flushrequest的问题,最终会打印:“The wayland connectting broken, …”,这个在qt官网有类似问题,具体是因为拷贝复制过程中数据量过大,导致主线程卡死,此时不停点击或移动鼠标会使socket数据不停积累最后溢出,导致wayland连接管道破裂。该问题暂时没找到好解决方案。

qt-wayland平台下复制粘贴原理相关推荐

  1. vim可视模式下复制粘贴文本

    [操作步骤] vim编辑器有两种操作模式:普通模式.插入模式.当打开要编辑的文件时,vim编辑器会进入普通模式.在普通模式下按 i 键进入插入模式,在插入模式下按 Esc 键返回普通模式. 在普通模式 ...

  2. 终端下 复制粘贴快捷键

    终端下复制粘贴快捷键 复制 CTRL+SHIFT+C 粘贴 CTRL+SHIFT+V

  3. Linux下复制粘贴

    Linux下复制粘贴 1. 图形界面 鼠标右键复制 -- 复制到系统剪贴板 鼠标右键粘贴 -- 粘贴系统剪贴板内容 Ctrl + c -- 复制到系统剪贴板 Ctrl + v -- 粘贴系统剪贴板内容 ...

  4. Linux命令行下复制粘贴文件

    Linux命令行下复制粘贴文件 一.复制单个文件 1.ls命令演示 2.cp命令 二.复制文件夹 1.cp -r 命令 三.复制多个文件 一.复制单个文件 1.ls命令演示 2.cp命令 我们下面将1 ...

  5. 在LINUX终端和VIM下复制粘贴

    http://www.tinylab.org/linux-terminal-and-paste-copy-under-vim/ 在GUI界面下,我们可以很自由的复制粘贴.但是在字符界面下,我们不得不用 ...

  6. linux qt 获取u盘名称,QT windows平台下获取U盘 QComboBox显示U盘盘符

    在windows平台下获取U盘信息,可以调用windows API函数比较方便.本来想用qt 来写的,网上关于这方面的代码比较多,但按照提示的步骤来写的就是无法编译,我也不知道为什么.如果有知道的朋友 ...

  7. linux系统下复制粘贴不了怎么办,VirtualBox 导致 Linux 桌面环境下无法复制粘贴

    两台 Optiplex 9020 工作站中的一台,跑的是 Linux 3.11 + GNOME 3.4 + Compiz 桌面环境,因为是干活用的,只求稳定不出问题所以平时很少折腾,最近一次重启都是3 ...

  8. linux平台下防火墙iptables原理(转)

    原文地址:http://www.cnblogs.com/ggjucheng/archive/2012/08/19/2646466.html iptables简介 netfilter/iptables( ...

  9. Qt剪切板QClipboard 复制粘贴自定义数据

    一.常用数据类型 QClipboard类提供了对操作系统剪切板的操作接口,最常用的做法是复制粘贴文本,如下面示例 QClipboard *clipboard = QGuiApplication::cl ...

最新文章

  1. PyTorch入门与代码模板
  2. VS2010与.NET4系列 5.代码优化的Web开发轮廓
  3. 纽约时代广场广告费才7千元每天,花钱装逼值不值?
  4. 百(垃)度(圾)之星初赛B hdu6114
  5. linux mint 19 内核4.9,Linux Kernel 4.4.59 LTS/4.9.19 LTS/4.10.7维护版本更新发布
  6. Backpack II 0-1背包
  7. chiinv函数java_Excel统计函数:CHIINV函数实例-excel技巧-电脑技巧收藏家
  8. python随笔系列--多进程多线程并发度初探
  9. 退出整个Android程序的工具类
  10. L1-06 吉老师的回归 (15 分)
  11. 洛谷P4135 作诗 --分块基础
  12. PHP每天自动更新静态文件下载地址,防盗链
  13. jq获取验证码成功之后弹出的提示框_验证码填写错误,请重新填写。。。
  14. 英文网站不用愁,必应在线翻译插件解烦忧
  15. 计算机操作系统-设备驱动实现实验报告
  16. 北京工业大学2020计算机考研复试科目,2020北京工业大学计算机考研专业课调整...
  17. 太极图形html5代码,HTML5 Canvas组件绘制太极图案
  18. IBUS-WARNING **: 09:23:08.407: The owner of /home/cl/.config/ibus/bus is not root!
  19. JAVA int类型 获取高低位
  20. 盛世昊通董车长2.0再上新,做任务吸粉看视频得收益

热门文章

  1. 快速接头 数字温度传感器 整体热电偶套管 气温传感器 测温传感器 温度传感器 温度传感器生产厂家 温度变送器 温度感应器 温度测量 热电偶 热电偶传感器 热电偶套管 热电偶温度传感器 热电偶温度计
  2. 淘宝,京东,拼多多,1688 API接口大全
  3. HUAWEI CLOUD Stack 私有云解决方案(HCS)
  4. 好未来晓黑板go-zero微服务框架: 你不需要懂微服务,懂业务就行
  5. centos6(centos6安装教程详解)
  6. C++ 类析构函数的显示调用和隐式调用
  7. k8s安装kube-promethues(超详细)
  8. C语言 紧跟printf之后的while(1)
  9. Android APK破解
  10. 家庭装修工作分解结构