Enhance Xcb clipboard code
Some cleanup on the data structures. Properly implement support for timestamps on each selection. Transfer selections to the clipboard manager on exit if it exists. Properly clear our selection when X11 asks us to do so.
This commit is contained in:
parent
fc266713cc
commit
813139f4f5
@ -150,16 +150,47 @@ const int QXcbClipboard::clipboard_timeout = 5000;
|
||||
|
||||
QXcbClipboard::QXcbClipboard(QXcbConnection *c)
|
||||
: QXcbObject(c), QPlatformClipboard()
|
||||
, m_xClipboard(0)
|
||||
, m_clientClipboard(0)
|
||||
, m_xSelection(0)
|
||||
, m_clientSelection(0)
|
||||
, m_requestor(XCB_NONE)
|
||||
, m_owner(XCB_NONE)
|
||||
{
|
||||
Q_ASSERT(QClipboard::Clipboard == 0);
|
||||
Q_ASSERT(QClipboard::Selection == 1);
|
||||
m_xClipboard[QClipboard::Clipboard] = 0;
|
||||
m_xClipboard[QClipboard::Selection] = 0;
|
||||
m_clientClipboard[QClipboard::Clipboard] = 0;
|
||||
m_clientClipboard[QClipboard::Selection] = 0;
|
||||
m_timestamp[QClipboard::Clipboard] = XCB_CURRENT_TIME;
|
||||
m_timestamp[QClipboard::Selection] = XCB_CURRENT_TIME;
|
||||
|
||||
m_screen = connection()->screens().at(connection()->primaryScreen());
|
||||
}
|
||||
|
||||
QXcbClipboard::~QXcbClipboard()
|
||||
{
|
||||
// Transfer the clipboard content to the clipboard manager if we own a selection
|
||||
if (m_timestamp[QClipboard::Clipboard] != XCB_CURRENT_TIME ||
|
||||
m_timestamp[QClipboard::Selection] != XCB_CURRENT_TIME) {
|
||||
|
||||
// First we check if there is a clipboard manager.
|
||||
xcb_get_selection_owner_cookie_t cookie = xcb_get_selection_owner(xcb_connection(), atom(QXcbAtom::CLIPBOARD_MANAGER));
|
||||
xcb_get_selection_owner_reply_t *reply = xcb_get_selection_owner_reply(xcb_connection(), cookie, 0);
|
||||
if (reply && reply->owner != XCB_NONE) {
|
||||
// we delete the property so the manager saves all TARGETS.
|
||||
xcb_delete_property(xcb_connection(), m_owner, atom(QXcbAtom::_QT_SELECTION));
|
||||
xcb_convert_selection(xcb_connection(), m_owner, atom(QXcbAtom::CLIPBOARD_MANAGER), atom(QXcbAtom::SAVE_TARGETS),
|
||||
atom(QXcbAtom::_QT_SELECTION), connection()->time());
|
||||
connection()->sync();
|
||||
|
||||
// waiting until the clipboard manager fetches the content.
|
||||
if (!waitForClipboardEvent(m_owner, XCB_SELECTION_NOTIFY, 5000)) {
|
||||
qWarning("QClipboard: Unable to receive an event from the "
|
||||
"clipboard manager in a reasonable time");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
xcb_window_t QXcbClipboard::getSelectionOwner(xcb_atom_t atom) const
|
||||
{
|
||||
xcb_connection_t *c = xcb_connection();
|
||||
@ -171,60 +202,63 @@ xcb_window_t QXcbClipboard::getSelectionOwner(xcb_atom_t atom) const
|
||||
return win;
|
||||
}
|
||||
|
||||
xcb_atom_t QXcbClipboard::atomForMode(QClipboard::Mode mode) const
|
||||
{
|
||||
if (mode == QClipboard::Clipboard)
|
||||
return atom(QXcbAtom::CLIPBOARD);
|
||||
if (mode == QClipboard::Selection)
|
||||
return XCB_ATOM_PRIMARY;
|
||||
return XCB_NONE;
|
||||
}
|
||||
|
||||
QClipboard::Mode QXcbClipboard::modeForAtom(xcb_atom_t a) const
|
||||
{
|
||||
if (a == XCB_ATOM_PRIMARY)
|
||||
return QClipboard::Selection;
|
||||
if (a == atom(QXcbAtom::CLIPBOARD))
|
||||
return QClipboard::Clipboard;
|
||||
// not supported enum value, used to detect errors
|
||||
return QClipboard::FindBuffer;
|
||||
}
|
||||
|
||||
|
||||
QMimeData * QXcbClipboard::mimeData(QClipboard::Mode mode)
|
||||
{
|
||||
if (mode == QClipboard::Clipboard) {
|
||||
if (!m_xClipboard) {
|
||||
m_xClipboard = new QXcbClipboardMime(mode, this);
|
||||
}
|
||||
xcb_window_t clipboardOwner = getSelectionOwner(atom(QXcbAtom::CLIPBOARD));
|
||||
if (clipboardOwner == owner()) {
|
||||
return m_clientClipboard;
|
||||
} else {
|
||||
return m_xClipboard;
|
||||
}
|
||||
} else if (mode == QClipboard::Selection) {
|
||||
if (!m_xSelection) {
|
||||
m_xSelection = new QXcbClipboardMime(mode, this);
|
||||
if (mode > QClipboard::Selection)
|
||||
return 0;
|
||||
|
||||
xcb_window_t clipboardOwner = getSelectionOwner(atomForMode(mode));
|
||||
if (clipboardOwner == owner()) {
|
||||
return m_clientClipboard[mode];
|
||||
} else {
|
||||
if (!m_xClipboard[mode])
|
||||
m_xClipboard[mode] = new QXcbClipboardMime(mode, this);
|
||||
|
||||
return m_xClipboard[mode];
|
||||
}
|
||||
xcb_window_t clipboardOwner = getSelectionOwner(XCB_ATOM_PRIMARY);
|
||||
if (clipboardOwner == owner()) {
|
||||
return m_clientSelection;
|
||||
} else {
|
||||
return m_xSelection;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void QXcbClipboard::setMimeData(QMimeData *data, QClipboard::Mode mode)
|
||||
{
|
||||
xcb_atom_t modeAtom;
|
||||
QMimeData **d;
|
||||
switch (mode) {
|
||||
case QClipboard::Selection:
|
||||
modeAtom = XCB_ATOM_PRIMARY;
|
||||
d = &m_clientSelection;
|
||||
break;
|
||||
|
||||
case QClipboard::Clipboard:
|
||||
modeAtom = atom(QXcbAtom::CLIPBOARD);
|
||||
d = &m_clientClipboard;
|
||||
break;
|
||||
|
||||
default:
|
||||
qWarning("QClipboard::setMimeData: unsupported mode '%d'", mode);
|
||||
if (mode > QClipboard::Selection)
|
||||
return;
|
||||
}
|
||||
|
||||
xcb_window_t newOwner;
|
||||
xcb_atom_t modeAtom = atomForMode(mode);
|
||||
|
||||
if (! data) { // no data, clear clipboard contents
|
||||
newOwner = XCB_NONE;
|
||||
} else {
|
||||
if (m_clientClipboard[mode] == data)
|
||||
return;
|
||||
|
||||
xcb_window_t newOwner = XCB_NONE;
|
||||
|
||||
delete m_clientClipboard[mode];
|
||||
m_clientClipboard[mode] = 0;
|
||||
m_timestamp[mode] = XCB_CURRENT_TIME;
|
||||
|
||||
if (data) {
|
||||
newOwner = owner();
|
||||
|
||||
*d = data;
|
||||
m_clientClipboard[mode] = data;
|
||||
m_timestamp[mode] = connection()->time();
|
||||
}
|
||||
|
||||
xcb_set_selection_owner(xcb_connection(), newOwner, modeAtom, connection()->time());
|
||||
@ -237,11 +271,21 @@ void QXcbClipboard::setMimeData(QMimeData *data, QClipboard::Mode mode)
|
||||
|
||||
bool QXcbClipboard::supportsMode(QClipboard::Mode mode) const
|
||||
{
|
||||
if (mode == QClipboard::Clipboard || mode == QClipboard::Selection)
|
||||
if (mode <= QClipboard::Selection)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool QXcbClipboard::ownsMode(QClipboard::Mode mode) const
|
||||
{
|
||||
if (m_owner == XCB_NONE || mode > QClipboard::Selection)
|
||||
return false;
|
||||
|
||||
Q_ASSERT(m_timestamp[mode] == XCB_CURRENT_TIME || getSelectionOwner(atomForMode(mode)) == m_owner);
|
||||
|
||||
return m_timestamp[mode] != XCB_CURRENT_TIME;
|
||||
}
|
||||
|
||||
xcb_window_t QXcbClipboard::requestor() const
|
||||
{
|
||||
if (!m_requestor) {
|
||||
@ -280,7 +324,6 @@ xcb_window_t QXcbClipboard::owner() const
|
||||
{
|
||||
if (!m_owner) {
|
||||
int x = 0, y = 0, w = 3, h = 3;
|
||||
QXcbClipboard *that = const_cast<QXcbClipboard *>(this);
|
||||
|
||||
xcb_window_t window = xcb_generate_id(xcb_connection());
|
||||
Q_XCB_CALL(xcb_create_window(xcb_connection(),
|
||||
@ -294,19 +337,12 @@ xcb_window_t QXcbClipboard::owner() const
|
||||
0, // value mask
|
||||
0)); // value list
|
||||
|
||||
that->setOwner(window);
|
||||
QXcbClipboard *that = const_cast<QXcbClipboard *>(this);
|
||||
that->m_owner = window;
|
||||
}
|
||||
return m_owner;
|
||||
}
|
||||
|
||||
void QXcbClipboard::setOwner(xcb_window_t window)
|
||||
{
|
||||
if (m_owner != XCB_NONE){
|
||||
xcb_destroy_window(xcb_connection(), m_owner);
|
||||
}
|
||||
m_owner = window;
|
||||
}
|
||||
|
||||
xcb_atom_t QXcbClipboard::sendTargetsSelection(QMimeData *d, xcb_window_t window, xcb_atom_t property)
|
||||
{
|
||||
QVector<xcb_atom_t> types;
|
||||
@ -371,6 +407,30 @@ xcb_atom_t QXcbClipboard::sendSelection(QMimeData *d, xcb_atom_t target, xcb_win
|
||||
return property;
|
||||
}
|
||||
|
||||
void QXcbClipboard::handleSelectionClearRequest(xcb_selection_clear_event_t *event)
|
||||
{
|
||||
QClipboard::Mode mode = modeForAtom(event->selection);
|
||||
if (mode > QClipboard::Selection)
|
||||
return;
|
||||
|
||||
// ignore the event if it was generated before we gained selection ownership
|
||||
if (m_timestamp[mode] != XCB_CURRENT_TIME && event->time <= m_timestamp[mode])
|
||||
return;
|
||||
|
||||
// DEBUG("QClipboard: new selection owner 0x%lx at time %lx (ours %lx)",
|
||||
// XGetSelectionOwner(dpy, XA_PRIMARY),
|
||||
// xevent->xselectionclear.time, d->timestamp);
|
||||
|
||||
// if (!waiting_for_data) {
|
||||
setMimeData(0, mode);
|
||||
emitChanged(mode);
|
||||
// } else {
|
||||
// pending_selection_changed = true;
|
||||
// if (! pending_timer_id)
|
||||
// pending_timer_id = QApplication::clipboard()->startTimer(0);
|
||||
// }
|
||||
}
|
||||
|
||||
void QXcbClipboard::handleSelectionRequest(xcb_selection_request_event_t *req)
|
||||
{
|
||||
if (requestor() && req->requestor == requestor()) {
|
||||
@ -387,22 +447,28 @@ void QXcbClipboard::handleSelectionRequest(xcb_selection_request_event_t *req)
|
||||
event.time = req->time;
|
||||
|
||||
QMimeData *d;
|
||||
if (req->selection == XCB_ATOM_PRIMARY) {
|
||||
d = m_clientSelection;
|
||||
} else if (req->selection == atom(QXcbAtom::CLIPBOARD)) {
|
||||
d = m_clientClipboard;
|
||||
} else {
|
||||
QClipboard::Mode mode = modeForAtom(req->selection);
|
||||
if (mode > QClipboard::Selection) {
|
||||
qWarning() << "QClipboard: Unknown selection" << connection()->atomName(req->selection);
|
||||
xcb_send_event(xcb_connection(), false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (const char *)&event);
|
||||
return;
|
||||
}
|
||||
|
||||
d = m_clientClipboard[mode];
|
||||
|
||||
if (!d) {
|
||||
qWarning("QClipboard: Cannot transfer data, no data available");
|
||||
xcb_send_event(xcb_connection(), false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (const char *)&event);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_timestamp[mode] == XCB_CURRENT_TIME // we don't own the selection anymore
|
||||
|| (req->time != XCB_CURRENT_TIME && req->time < m_timestamp[mode])) {
|
||||
qDebug("QClipboard: SelectionRequest too old");
|
||||
xcb_send_event(xcb_connection(), false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (const char *)&event);
|
||||
return;
|
||||
}
|
||||
|
||||
xcb_atom_t xa_targets = atom(QXcbAtom::TARGETS);
|
||||
xcb_atom_t xa_multiple = atom(QXcbAtom::MULTIPLE);
|
||||
xcb_atom_t xa_timestamp = atom(QXcbAtom::TIMESTAMP);
|
||||
@ -448,13 +514,13 @@ void QXcbClipboard::handleSelectionRequest(xcb_selection_request_event_t *req)
|
||||
if (target == XCB_NONE || property == XCB_NONE) {
|
||||
;
|
||||
} else if (target == xa_timestamp) {
|
||||
// if (d->timestamp != CurrentTime) {
|
||||
// XChangeProperty(DISPLAY_FROM_XCB(connection()), req->requestor, property, QXcbAtom::XA_INTEGER, 32,
|
||||
// PropModeReplace, CurrentTime, 1);
|
||||
// ret = property;
|
||||
// } else {
|
||||
// qWarning("QClipboard: Invalid data timestamp");
|
||||
// }
|
||||
if (m_timestamp[mode] != XCB_CURRENT_TIME) {
|
||||
xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, req->requestor,
|
||||
property, XCB_ATOM_INTEGER, 32, 1, &m_timestamp[mode]);
|
||||
ret = property;
|
||||
} else {
|
||||
qWarning("QClipboard: Invalid data timestamp");
|
||||
}
|
||||
} else if (target == xa_targets) {
|
||||
ret = sendTargetsSelection(d, req->requestor, property);
|
||||
} else {
|
||||
|
@ -48,16 +48,19 @@
|
||||
|
||||
class QXcbConnection;
|
||||
class QXcbScreen;
|
||||
class QXcbClipboardMime;
|
||||
|
||||
class QXcbClipboard : public QXcbObject, public QPlatformClipboard
|
||||
{
|
||||
public:
|
||||
QXcbClipboard(QXcbConnection *connection);
|
||||
~QXcbClipboard();
|
||||
|
||||
QMimeData *mimeData(QClipboard::Mode mode);
|
||||
void setMimeData(QMimeData *data, QClipboard::Mode mode);
|
||||
|
||||
bool supportsMode(QClipboard::Mode mode) const;
|
||||
bool ownsMode(QClipboard::Mode mode) const;
|
||||
|
||||
QXcbScreen *screen() const { return m_screen; }
|
||||
|
||||
@ -67,6 +70,7 @@ public:
|
||||
xcb_window_t owner() const;
|
||||
|
||||
void handleSelectionRequest(xcb_selection_request_event_t *event);
|
||||
void handleSelectionClearRequest(xcb_selection_clear_event_t *event);
|
||||
|
||||
bool clipboardReadProperty(xcb_window_t win, xcb_atom_t property, bool deleteProperty, QByteArray *buffer, int *size, xcb_atom_t *type, int *format) const;
|
||||
QByteArray clipboardReadIncrementalProperty(xcb_window_t win, xcb_atom_t property, int nbytes, bool nullterm);
|
||||
@ -77,20 +81,20 @@ public:
|
||||
QByteArray getSelection(xcb_atom_t selection, xcb_atom_t target, xcb_atom_t property);
|
||||
|
||||
private:
|
||||
void setOwner(xcb_window_t window);
|
||||
|
||||
xcb_generic_event_t *waitForClipboardEvent(xcb_window_t win, int type, int timeout);
|
||||
|
||||
xcb_atom_t sendTargetsSelection(QMimeData *d, xcb_window_t window, xcb_atom_t property);
|
||||
xcb_atom_t sendSelection(QMimeData *d, xcb_atom_t target, xcb_window_t window, xcb_atom_t property);
|
||||
|
||||
xcb_atom_t atomForMode(QClipboard::Mode mode) const;
|
||||
QClipboard::Mode modeForAtom(xcb_atom_t atom) const;
|
||||
|
||||
QXcbScreen *m_screen;
|
||||
|
||||
QMimeData *m_xClipboard;
|
||||
QMimeData *m_clientClipboard;
|
||||
|
||||
QMimeData *m_xSelection;
|
||||
QMimeData *m_clientSelection;
|
||||
// Selection and Clipboard
|
||||
QXcbClipboardMime *m_xClipboard[2];
|
||||
QMimeData *m_clientClipboard[2];
|
||||
xcb_timestamp_t m_timestamp[2];
|
||||
|
||||
xcb_window_t m_requestor;
|
||||
xcb_window_t m_owner;
|
||||
|
@ -148,6 +148,8 @@ QXcbConnection::QXcbConnection(const char *displayName)
|
||||
|
||||
QXcbConnection::~QXcbConnection()
|
||||
{
|
||||
delete m_clipboard;
|
||||
|
||||
qDeleteAll(m_screens);
|
||||
|
||||
#ifdef XCB_USE_XLIB
|
||||
@ -157,7 +159,6 @@ QXcbConnection::~QXcbConnection()
|
||||
#endif
|
||||
|
||||
delete m_keyboard;
|
||||
delete m_clipboard;
|
||||
}
|
||||
|
||||
void QXcbConnection::addWindow(xcb_window_t id, QXcbWindow *window)
|
||||
@ -488,10 +489,11 @@ void QXcbConnection::handleXcbEvent(xcb_generic_event_t *event)
|
||||
}
|
||||
case XCB_SELECTION_CLEAR:
|
||||
setTime(((xcb_selection_clear_event_t *)event)->time);
|
||||
qDebug() << "XCB_SELECTION_CLEAR";
|
||||
handled = false;
|
||||
m_clipboard->handleSelectionClearRequest((xcb_selection_clear_event_t *)event);
|
||||
handled = true;
|
||||
break;
|
||||
case XCB_SELECTION_NOTIFY:
|
||||
setTime(((xcb_selection_notify_event_t *)event)->time);
|
||||
qDebug() << "XCB_SELECTION_NOTIFY";
|
||||
handled = false;
|
||||
break;
|
||||
|
Loading…
Reference in New Issue
Block a user