复制粘贴——QT实现原理
QT 剪贴板相关类
QClipboard
对外通用的剪贴板类,一般通过QGuiApplication::clipboard()
来获取对应的剪贴板实例。
// qtbase/src/gui/kernel/qclipboard.h
class Q_GUI_EXPORT QClipboard : public QObject
{
Q_OBJECT
private:
explicit QClipboard(QObject *parent);
~QClipboard();
public:
enum Mode { Clipboard, Selection, FindBuffer, LastMode = FindBuffer };
void clear(Mode mode = Clipboard);
bool supportsSelection() const;
bool supportsFindBuffer() const;
bool ownsSelection() const;
bool ownsClipboard() const;
bool ownsFindBuffer() const;
QString text(Mode mode = Clipboard) const;
QString text(QString& subtype, Mode mode = Clipboard) const;
void setText(const QString &, Mode mode = Clipboard);
const QMimeData *mimeData(Mode mode = Clipboard ) const;
void setMimeData(QMimeData *data, Mode mode = Clipboard);
QImage image(Mode mode = Clipboard) const;
QPixmap pixmap(Mode mode = Clipboard) const;
void setImage(const QImage &, Mode mode = Clipboard);
void setPixmap(const QPixmap &, Mode mode = Clipboard);
Q_SIGNALS:
void changed(QClipboard::Mode mode);
void selectionChanged();
void findBufferChanged();
void dataChanged();
protected:
friend class QApplication;
friend class QApplicationPrivate;
friend class QGuiApplication;
friend class QBaseApplication;
friend class QDragManager;
friend class QPlatformClipboard;
private:
Q_DISABLE_COPY(QClipboard)
bool supportsMode(Mode mode) const;
bool ownsMode(Mode mode) const;
void emitChanged(Mode mode);
};
QPlatformClipboard
系统剪切板平台接口类,各种桌面平台(Windows,X11,Wayland等)通过这个类提供统一的剪贴板操作接口。
// qtbase/src/gui/kernel/qplatformclipboard.h
class Q_GUI_EXPORT QPlatformClipboard
{
public:
virtual ~QPlatformClipboard();
virtual QMimeData *mimeData(QClipboard::Mode mode = QClipboard::Clipboard);
virtual void setMimeData(QMimeData *data, QClipboard::Mode mode = QClipboard::Clipboard);
virtual bool supportsMode(QClipboard::Mode mode) const;
virtual bool ownsMode(QClipboard::Mode mode) const;
void emitChanged(QClipboard::Mode mode);
};
QXcbClipboard
X11平台实现的剪贴板接口类,继承自QPlatformClipboard
,它主要实现了基类的大部分接口,除了emitChanged
这个接口。
// qtbase/src/plugins/platforms/xcb/qxcbclipboard.h
class QXcbClipboard : public QXcbObject, public QPlatformClipboard
{
public:
QXcbClipboard(QXcbConnection *connection);
~QXcbClipboard();
QMimeData *mimeData(QClipboard::Mode mode) override;
void setMimeData(QMimeData *data, QClipboard::Mode mode) override;
bool supportsMode(QClipboard::Mode mode) const override;
bool ownsMode(QClipboard::Mode mode) const override;
...
};
QWindowsClipboard
Windows平台下的剪贴板接口类,继承自QPlatformClipboard
。
// qtbase/src/plugins/platforms/windows/qwindowsclipboard.h
class QWindowsClipboard : public QPlatformClipboard
{
public:
QWindowsClipboard();
~QWindowsClipboard();
void registerViewer(); // Call in initialization, when context is up.
void cleanup();
QMimeData *mimeData(QClipboard::Mode mode = QClipboard::Clipboard) override;
void setMimeData(QMimeData *data, QClipboard::Mode mode = QClipboard::Clipboard) override;
bool supportsMode(QClipboard::Mode mode) const override;
bool ownsMode(QClipboard::Mode mode) const override;
...
}
可以看出,同一目录下还有其他各种平台的实现接口:
QWaylandClipboard
Wayland平台实现的剪贴板接口.
// qtwayland/src/client/qwaylandclipboard_p.h
class Q_WAYLAND_CLIENT_EXPORT QWaylandClipboard : public QPlatformClipboard
{
public:
QWaylandClipboard(QWaylandDisplay *display);
~QWaylandClipboard() override;
QMimeData *mimeData(QClipboard::Mode mode = QClipboard::Clipboard) override;
void setMimeData(QMimeData *data, QClipboard::Mode mode = QClipboard::Clipboard) override;
bool supportsMode(QClipboard::Mode mode) const override;
bool ownsMode(QClipboard::Mode mode) const override;
private:
QWaylandDisplay *mDisplay = nullptr;
QMimeData m_emptyData;
};
QT 剪贴板相关接口
通过查看QClipboard
类的定义,我们比较关心的接口有:
const QMimeData *mimeData(Mode mode = Clipboard ) const;
void setMimeData(QMimeData *data, Mode mode = Clipboard);
Q_SIGNALS:
void changed(QClipboard::Mode mode);
void selectionChanged();
void findBufferChanged();
void dataChanged();
获取剪贴板最基础的应该是mimeData
这个接口:
const QMimeData* QClipboard::mimeData(Mode mode) const
{
// 获取一个QPlatformClipboard对象,根据不同平台返回的应该是不同的子类,比如x11下就返回的是QXcbClipboard
QPlatformClipboard *clipboard = QGuiApplicationPrivate::platformIntegration()->clipboard();
if (!clipboard->supportsMode(mode)) return 0;
return clipboard->mimeData(mode);
}
可以看出,最终是通过X11接口拿到的。
另外,我们关系剪贴板变化的信号在什么情况下发出来,从实现可以看出,基本是在emitChanged
里发出来的。
/*!
\internal
Emits the appropriate changed signal for \a mode.
*/
void QClipboard::emitChanged(Mode mode)
{
switch (mode) {
case Clipboard:
emit dataChanged();
break;
case Selection:
emit selectionChanged();
break;
case FindBuffer:
emit findBufferChanged();
break;
default:
break;
}
emit changed(mode);
}
还有一个地方会通过emitChanged
发出变化的信号:
void QPlatformClipboard::emitChanged(QClipboard::Mode mode)
{
if (!QGuiApplicationPrivate::is_app_closing) // QTBUG-39317, prevent emission when closing down.
QGuiApplication::clipboard()->emitChanged(mode);
}
可以再往下看下谁会调用emitChanged
,可以发现是QPlatformClipboard
的子类QXcbClipboard
:
// qtbase/src/plugins/platforms/xcb/qxcbclipboard.cpp
void QXcbClipboard::setMimeData(QMimeData *data, QClipboard::Mode mode)
{
if (mode > QClipboard::Selection)
return;
QXcbClipboardMime *xClipboard = 0;
// verify if there is data to be cleared on global X Clipboard.
if (!data) {
xClipboard = qobject_cast<QXcbClipboardMime *>(mimeData(mode));
if (xClipboard) {
if (xClipboard->isEmpty())
return;
}
}
if (!xClipboard && (m_clientClipboard[mode] == data))
return;
xcb_atom_t modeAtom = atomForMode(mode);
xcb_window_t newOwner = XCB_NONE;
if (m_clientClipboard[mode]) {
if (m_clientClipboard[QClipboard::Clipboard] != m_clientClipboard[QClipboard::Selection])
delete m_clientClipboard[mode];
m_clientClipboard[mode] = 0;
m_timestamp[mode] = XCB_CURRENT_TIME;
}
if (connection()->time() == XCB_CURRENT_TIME)
connection()->setTime(connection()->getTimestamp());
if (data) {
newOwner = owner();
m_clientClipboard[mode] = data;
m_timestamp[mode] = connection()->time();
}
xcb_set_selection_owner(xcb_connection(), newOwner, modeAtom, connection()->time());
if (getSelectionOwner(modeAtom) != newOwner) {
qWarning("QXcbClipboard::setMimeData: Cannot set X11 selection owner");
}
emitChanged(mode);
}
void QXcbClipboard::handleXFixesSelectionRequest(xcb_xfixes_selection_notify_event_t *event)
{
QClipboard::Mode mode = modeForAtom(event->selection);
if (mode > QClipboard::Selection)
return;
// Note1: Here we care only about the xfixes events that come from other processes.
// Note2: If the QClipboard::clear() is issued, event->owner is XCB_NONE,
// so we check selection_timestamp to not handle our own QClipboard::clear().
if (event->owner != owner() && event->selection_timestamp > m_timestamp[mode]) {
if (!m_xClipboard[mode]) {
m_xClipboard[mode].reset(new QXcbClipboardMime(mode, this));
} else {
m_xClipboard[mode]->reset();
}
emitChanged(mode);
} else if (event->subtype == XCB_XFIXES_SELECTION_EVENT_SELECTION_CLIENT_CLOSE ||
event->subtype == XCB_XFIXES_SELECTION_EVENT_SELECTION_WINDOW_DESTROY)
emitChanged(mode);
}
至此,我们可以知道QClipboard是如何发出剪贴板内容变化的信号了:
- QClipboard设置剪贴板内容(setMimeData),QXcbClipboard设置完剪贴板内容,emitChanged通知内容变化
- QXcbClipboard收到X11剪贴板变化的事件,主动emitChanged通知QClipboard剪贴板变化
总结
qt的剪贴板底层是由各个平台的剪贴板接口驱动的,如果是X11平台,那么整个剪贴板就是X11接口驱动的。关于如何使用X11原生剪贴板接口,参考:https://stackoverflow.com/questions/27378318/c-get-string-from-clipboard-on-linux