qt-wayland平台下复制粘贴原理
一、复制粘贴的简单原理
复制粘贴大概可以分为以下两种场景:
- 在同一个进程中复制并粘贴
- 在进程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交互的流程:
对于上图的交互流程需要特别注意两点:
- wayland平台给客户端同步剪切板信息,只会在客户端激活的情况才会通知,没有激活是不会通知的
- 以上只是激活目的窗口的流程,激活后就会收到窗管的信号,并收到了相关的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平台下复制粘贴原理相关推荐
- vim可视模式下复制粘贴文本
[操作步骤] vim编辑器有两种操作模式:普通模式.插入模式.当打开要编辑的文件时,vim编辑器会进入普通模式.在普通模式下按 i 键进入插入模式,在插入模式下按 Esc 键返回普通模式. 在普通模式 ...
- 终端下 复制粘贴快捷键
终端下复制粘贴快捷键 复制 CTRL+SHIFT+C 粘贴 CTRL+SHIFT+V
- Linux下复制粘贴
Linux下复制粘贴 1. 图形界面 鼠标右键复制 -- 复制到系统剪贴板 鼠标右键粘贴 -- 粘贴系统剪贴板内容 Ctrl + c -- 复制到系统剪贴板 Ctrl + v -- 粘贴系统剪贴板内容 ...
- Linux命令行下复制粘贴文件
Linux命令行下复制粘贴文件 一.复制单个文件 1.ls命令演示 2.cp命令 二.复制文件夹 1.cp -r 命令 三.复制多个文件 一.复制单个文件 1.ls命令演示 2.cp命令 我们下面将1 ...
- 在LINUX终端和VIM下复制粘贴
http://www.tinylab.org/linux-terminal-and-paste-copy-under-vim/ 在GUI界面下,我们可以很自由的复制粘贴.但是在字符界面下,我们不得不用 ...
- linux qt 获取u盘名称,QT windows平台下获取U盘 QComboBox显示U盘盘符
在windows平台下获取U盘信息,可以调用windows API函数比较方便.本来想用qt 来写的,网上关于这方面的代码比较多,但按照提示的步骤来写的就是无法编译,我也不知道为什么.如果有知道的朋友 ...
- linux系统下复制粘贴不了怎么办,VirtualBox 导致 Linux 桌面环境下无法复制粘贴
两台 Optiplex 9020 工作站中的一台,跑的是 Linux 3.11 + GNOME 3.4 + Compiz 桌面环境,因为是干活用的,只求稳定不出问题所以平时很少折腾,最近一次重启都是3 ...
- linux平台下防火墙iptables原理(转)
原文地址:http://www.cnblogs.com/ggjucheng/archive/2012/08/19/2646466.html iptables简介 netfilter/iptables( ...
- Qt剪切板QClipboard 复制粘贴自定义数据
一.常用数据类型 QClipboard类提供了对操作系统剪切板的操作接口,最常用的做法是复制粘贴文本,如下面示例 QClipboard *clipboard = QGuiApplication::cl ...
最新文章
- PyTorch入门与代码模板
- VS2010与.NET4系列 5.代码优化的Web开发轮廓
- 纽约时代广场广告费才7千元每天,花钱装逼值不值?
- 百(垃)度(圾)之星初赛B hdu6114
- linux mint 19 内核4.9,Linux Kernel 4.4.59 LTS/4.9.19 LTS/4.10.7维护版本更新发布
- Backpack II 0-1背包
- chiinv函数java_Excel统计函数:CHIINV函数实例-excel技巧-电脑技巧收藏家
- python随笔系列--多进程多线程并发度初探
- 退出整个Android程序的工具类
- L1-06 吉老师的回归 (15 分)
- 洛谷P4135 作诗 --分块基础
- PHP每天自动更新静态文件下载地址,防盗链
- jq获取验证码成功之后弹出的提示框_验证码填写错误,请重新填写。。。
- 英文网站不用愁,必应在线翻译插件解烦忧
- 计算机操作系统-设备驱动实现实验报告
- 北京工业大学2020计算机考研复试科目,2020北京工业大学计算机考研专业课调整...
- 太极图形html5代码,HTML5 Canvas组件绘制太极图案
- IBUS-WARNING **: 09:23:08.407: The owner of /home/cl/.config/ibus/bus is not root!
- JAVA int类型 获取高低位
- 盛世昊通董车长2.0再上新,做任务吸粉看视频得收益
热门文章
- 快速接头 数字温度传感器 整体热电偶套管 气温传感器 测温传感器 温度传感器 温度传感器生产厂家 温度变送器 温度感应器 温度测量 热电偶 热电偶传感器 热电偶套管 热电偶温度传感器 热电偶温度计
- 淘宝,京东,拼多多,1688 API接口大全
- HUAWEI CLOUD Stack 私有云解决方案(HCS)
- 好未来晓黑板go-zero微服务框架: 你不需要懂微服务,懂业务就行
- centos6(centos6安装教程详解)
- C++ 类析构函数的显示调用和隐式调用
- k8s安装kube-promethues(超详细)
- C语言 紧跟printf之后的while(1)
- Android APK破解
- 家庭装修工作分解结构