summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLars Knoll <lars.knoll@nokia.com>2011-06-08 22:00:39 +0200
committerLars Knoll <lars.knoll@nokia.com>2011-06-20 12:50:16 +0200
commit813139f4f5bd78ed16577a3ae214b2cb91ef5170 (patch)
tree2e6873ad37951d1561a95a915ceadcfacba87565
parentfc266713cc20d0f90524bc2ec7e8fd6b57c61c99 (diff)
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.
-rw-r--r--src/plugins/platforms/xcb/qxcbclipboard.cpp200
-rw-r--r--src/plugins/platforms/xcb/qxcbclipboard.h18
-rw-r--r--src/plugins/platforms/xcb/qxcbconnection.cpp8
3 files changed, 149 insertions, 77 deletions
diff --git a/src/plugins/platforms/xcb/qxcbclipboard.cpp b/src/plugins/platforms/xcb/qxcbclipboard.cpp
index e25192fcd7..ea7cefb972 100644
--- a/src/plugins/platforms/xcb/qxcbclipboard.cpp
+++ b/src/plugins/platforms/xcb/qxcbclipboard.cpp
@@ -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);
- }
- xcb_window_t clipboardOwner = getSelectionOwner(XCB_ATOM_PRIMARY);
- if (clipboardOwner == owner()) {
- return m_clientSelection;
- } else {
- return m_xSelection;
- }
+ 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];
}
- 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;
+ if (mode > QClipboard::Selection)
+ return;
- case QClipboard::Clipboard:
- modeAtom = atom(QXcbAtom::CLIPBOARD);
- d = &m_clientClipboard;
- break;
+ xcb_atom_t modeAtom = atomForMode(mode);
- default:
- qWarning("QClipboard::setMimeData: unsupported mode '%d'", mode);
+ if (m_clientClipboard[mode] == data)
return;
- }
- xcb_window_t newOwner;
+ xcb_window_t newOwner = XCB_NONE;
- if (! data) { // no data, clear clipboard contents
- newOwner = XCB_NONE;
- } else {
+ 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 {
diff --git a/src/plugins/platforms/xcb/qxcbclipboard.h b/src/plugins/platforms/xcb/qxcbclipboard.h
index ff2d44144e..db31051ee7 100644
--- a/src/plugins/platforms/xcb/qxcbclipboard.h
+++ b/src/plugins/platforms/xcb/qxcbclipboard.h
@@ -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);
- QXcbScreen *m_screen;
+ xcb_atom_t atomForMode(QClipboard::Mode mode) const;
+ QClipboard::Mode modeForAtom(xcb_atom_t atom) const;
- QMimeData *m_xClipboard;
- QMimeData *m_clientClipboard;
+ QXcbScreen *m_screen;
- 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;
diff --git a/src/plugins/platforms/xcb/qxcbconnection.cpp b/src/plugins/platforms/xcb/qxcbconnection.cpp
index 0af71fa830..eddc3305d1 100644
--- a/src/plugins/platforms/xcb/qxcbconnection.cpp
+++ b/src/plugins/platforms/xcb/qxcbconnection.cpp
@@ -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;