src/gui/kernel/qclipboard_x11.cpp

Go to the documentation of this file.
00001 /****************************************************************************
00002 **
00003 ** Copyright (C) 1992-2006 Trolltech ASA. All rights reserved.
00004 **
00005 ** This file is part of the QtGui module of the Qt Toolkit.
00006 **
00007 ** This file may be used under the terms of the GNU General Public
00008 ** License version 2.0 as published by the Free Software Foundation
00009 ** and appearing in the file LICENSE.GPL included in the packaging of
00010 ** this file.  Please review the following information to ensure GNU
00011 ** General Public Licensing requirements will be met:
00012 ** http://www.trolltech.com/products/qt/opensource.html
00013 **
00014 ** If you are unsure which license is appropriate for your use, please
00015 ** review the following information:
00016 ** http://www.trolltech.com/products/qt/licensing.html or contact the
00017 ** sales department at sales@trolltech.com.
00018 **
00019 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
00020 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
00021 **
00022 ****************************************************************************/
00023 
00024 // #define QCLIPBOARD_DEBUG
00025 // #define QCLIPBOARD_DEBUG_VERBOSE
00026 
00027 #ifdef QCLIPBOARD_DEBUG
00028 #  define DEBUG qDebug
00029 #else
00030 #  define DEBUG if (false) qDebug
00031 #endif
00032 
00033 #ifdef QCLIPBOARD_DEBUG_VERBOSE
00034 #  define VDEBUG qDebug
00035 #else
00036 #  define VDEBUG if (false) qDebug
00037 #endif
00038 
00039 #include "qplatformdefs.h"
00040 
00041 #include "qclipboard.h"
00042 
00043 #ifndef QT_NO_CLIPBOARD
00044 
00045 #include "qabstracteventdispatcher.h"
00046 #include "qapplication.h"
00047 #include "qdesktopwidget.h"
00048 #include "qbitmap.h"
00049 #include "qdatetime.h"
00050 #include "qiodevice.h"
00051 #include "qbuffer.h"
00052 #include "qtextcodec.h"
00053 #include "qlist.h"
00054 #include "qmap.h"
00055 #include "qapplication_p.h"
00056 #include "qevent.h"
00057 #include "qt_x11_p.h"
00058 #include "qx11info_x11.h"
00059 #include "qimagewriter.h"
00060 #include "qvariant.h"
00061 #include "qdnd_p.h"
00062 
00063 /*****************************************************************************
00064   Internal QClipboard functions for X11.
00065  *****************************************************************************/
00066 
00067 static int clipboard_timeout = 5000; // 5s timeout on clipboard operations
00068 
00069 static QWidget * owner = 0;
00070 static QWidget *requestor = 0;
00071 static bool timer_event_clear = false;
00072 static int timer_id = 0;
00073 
00074 static int pending_timer_id = 0;
00075 static bool pending_clipboard_changed = false;
00076 static bool pending_selection_changed = false;
00077 
00078 
00079 // event capture mechanism for qt_xclb_wait_for_event
00080 static bool waiting_for_data = false;
00081 static bool has_captured_event = false;
00082 static Window capture_event_win = XNone;
00083 static int capture_event_type = -1;
00084 static XEvent captured_event;
00085 
00086 class QClipboardWatcher; // forward decl
00087 static QClipboardWatcher *selection_watcher = 0;
00088 static QClipboardWatcher *clipboard_watcher = 0;
00089 
00090 static void cleanup()
00091 {
00092     delete owner;
00093     delete requestor;
00094     owner = 0;
00095     requestor = 0;
00096 }
00097 
00098 static
00099 void setupOwner()
00100 {
00101     if (owner)
00102         return;
00103     owner = new QWidget(0);
00104     owner->setObjectName(QLatin1String("internal clipboard owner"));
00105     owner->createWinId();
00106     requestor = new QWidget(0);
00107     requestor->createWinId();
00108     requestor->setObjectName(QLatin1String("internal clipboard requestor"));
00109     qAddPostRoutine(cleanup);
00110 }
00111 
00112 
00113 class QClipboardWatcher : public QInternalMimeData {
00114 public:
00115     QClipboardWatcher(QClipboard::Mode mode);
00116     ~QClipboardWatcher();
00117     bool empty() const;
00118     virtual bool hasFormat_sys(const QString &mimetype) const;
00119     virtual QStringList formats_sys() const;
00120 
00121     QVariant retrieveData_sys(const QString &mimetype, QVariant::Type type) const;
00122     QByteArray getDataInFormat(Atom fmtatom) const;
00123 
00124     Atom atom;
00125     mutable QStringList formatList;
00126     mutable QByteArray format_atoms;
00127 };
00128 
00129 
00130 
00131 class QClipboardData
00132 {
00133 public:
00134     QClipboardData();
00135     ~QClipboardData();
00136 
00137     void setSource(QMimeData* s)
00138     {
00139         delete src;
00140         src = s;
00141     }
00142 
00143     QMimeData *source() const { return src; }
00144 
00145     void clear();
00146 
00147     QMimeData *src;
00148     Time timestamp;
00149 };
00150 
00151 QClipboardData::QClipboardData()
00152 {
00153     src = 0;
00154     timestamp = CurrentTime;
00155 }
00156 
00157 QClipboardData::~QClipboardData()
00158 { clear(); }
00159 
00160 void QClipboardData::clear()
00161 {
00162     delete src;
00163     src = 0;
00164     timestamp = CurrentTime;
00165 }
00166 
00167 
00168 static QClipboardData *internalCbData = 0;
00169 static QClipboardData *internalSelData = 0;
00170 
00171 static void cleanupClipboardData()
00172 {
00173     delete internalCbData;
00174     internalCbData = 0;
00175 }
00176 
00177 static QClipboardData *clipboardData()
00178 {
00179     if (internalCbData == 0) {
00180         internalCbData = new QClipboardData;
00181         qAddPostRoutine(cleanupClipboardData);
00182     }
00183     return internalCbData;
00184 }
00185 
00186 static void cleanupSelectionData()
00187 {
00188     delete internalSelData;
00189     internalSelData = 0;
00190 }
00191 
00192 static QClipboardData *selectionData()
00193 {
00194     if (internalSelData == 0) {
00195         internalSelData = new QClipboardData;
00196         qAddPostRoutine(cleanupSelectionData);
00197     }
00198     return internalSelData;
00199 }
00200 
00201 class QClipboardINCRTransaction
00202 {
00203 public:
00204     QClipboardINCRTransaction(Window w, Atom p, Atom t, int f, QByteArray d, unsigned int i);
00205     ~QClipboardINCRTransaction(void);
00206 
00207     int x11Event(XEvent *event);
00208 
00209     Window window;
00210     Atom property, target;
00211     int format;
00212     QByteArray data;
00213     unsigned int increment;
00214     unsigned int offset;
00215 };
00216 
00217 typedef QMap<Window,QClipboardINCRTransaction*> TransactionMap;
00218 static TransactionMap *transactions = 0;
00219 static QApplication::EventFilter prev_event_filter = 0;
00220 static int incr_timer_id = 0;
00221 
00222 static bool qt_x11_incr_event_filter(void *message, long *result)
00223 {
00224     XEvent *event = reinterpret_cast<XEvent *>(message);
00225     TransactionMap::Iterator it = transactions->find(event->xany.window);
00226     if (it != transactions->end()) {
00227         if ((*it)->x11Event(event) != 0)
00228             return true;
00229     }
00230     if (prev_event_filter)
00231         return prev_event_filter(event, result);
00232     return false;
00233 }
00234 
00235 /*
00236   called when no INCR activity has happened for 'clipboard_timeout'
00237   milliseconds... we assume that all unfinished transactions have
00238   timed out and remove everything from the transaction map
00239 */
00240 static void qt_xclb_incr_timeout(void)
00241 {
00242     qWarning("QClipboard: Timed out while sending data");
00243 
00244     while (transactions)
00245         delete *transactions->begin();
00246 }
00247 
00248 QClipboardINCRTransaction::QClipboardINCRTransaction(Window w, Atom p, Atom t, int f,
00249                                                      QByteArray d, unsigned int i)
00250     : window(w), property(p), target(t), format(f), data(d), increment(i), offset(0u)
00251 {
00252     DEBUG("QClipboard: sending %d bytes (INCR transaction %p)", d.size(), this);
00253 
00254     XSelectInput(X11->display, window, PropertyChangeMask);
00255 
00256     if (! transactions) {
00257         VDEBUG("QClipboard: created INCR transaction map");
00258         transactions = new TransactionMap;
00259         prev_event_filter = qApp->setEventFilter(qt_x11_incr_event_filter);
00260         incr_timer_id = QApplication::clipboard()->startTimer(clipboard_timeout);
00261     }
00262     transactions->insert(window, this);
00263 }
00264 
00265 QClipboardINCRTransaction::~QClipboardINCRTransaction(void)
00266 {
00267     VDEBUG("QClipboard: destroyed INCR transacton %p", this);
00268 
00269     XSelectInput(X11->display, window, NoEventMask);
00270 
00271     transactions->remove(window);
00272     if (transactions->isEmpty()) {
00273         VDEBUG("QClipboard: no more INCR transactions");
00274         delete transactions;
00275         transactions = 0;
00276 
00277         (void)qApp->setEventFilter(prev_event_filter);
00278 
00279         if (incr_timer_id != 0) {
00280             QApplication::clipboard()->killTimer(incr_timer_id);
00281             incr_timer_id = 0;
00282         }
00283     }
00284 }
00285 
00286 int QClipboardINCRTransaction::x11Event(XEvent *event)
00287 {
00288     if (event->type != PropertyNotify
00289         || (event->xproperty.state != PropertyDelete
00290             || event->xproperty.atom != property))
00291         return 0;
00292 
00293     // restart the INCR timer
00294     if (incr_timer_id) QApplication::clipboard()->killTimer(incr_timer_id);
00295     incr_timer_id = QApplication::clipboard()->startTimer(clipboard_timeout);
00296 
00297     unsigned int bytes_left = data.size() - offset;
00298     if (bytes_left > 0) {
00299         unsigned int xfer = qMin(increment, bytes_left);
00300         VDEBUG("QClipboard: sending %d bytes, %d remaining (INCR transaction %p)",
00301                xfer, bytes_left - xfer, this);
00302 
00303         XChangeProperty(X11->display, window, property, target, format,
00304                         PropModeReplace, (uchar *) data.data() + offset, xfer);
00305         offset += xfer;
00306     } else {
00307         // INCR transaction finished...
00308         XChangeProperty(X11->display, window, property, target, format,
00309                         PropModeReplace, (uchar *) data.data(), 0);
00310         delete this;
00311     }
00312 
00313     return 1;
00314 }
00315 
00316 
00317 /*****************************************************************************
00318   QClipboard member functions for X11.
00319  *****************************************************************************/
00320 
00321 
00322 void QClipboard::clear(Mode mode)
00323 {
00324     setMimeData(0, mode);
00325 }
00326 
00327 
00328 bool QClipboard::supportsMode(Mode mode) const
00329 {
00330     return (mode == Clipboard || mode == Selection);
00331 }
00332 
00333 bool QClipboard::ownsMode(Mode mode) const
00334 {
00335     if (mode == Clipboard)
00336         return clipboardData()->timestamp != CurrentTime;
00337     else if(mode == Selection)
00338         return selectionData()->timestamp != CurrentTime;
00339     else
00340         return false;
00341 }
00342 
00343 
00344 // event filter function... captures interesting events while
00345 // qt_xclb_wait_for_event is running the event loop
00346 static bool qt_x11_clipboard_event_filter(void *message, long *)
00347 {
00348     XEvent *event = reinterpret_cast<XEvent *>(message);
00349     if (event->xany.type == capture_event_type &&
00350         event->xany.window == capture_event_win) {
00351         VDEBUG("QClipboard: event_filter(): caught event type %d", event->type);
00352         has_captured_event = true;
00353         captured_event = *event;
00354         return true;
00355     }
00356     return false;
00357 }
00358 
00359 bool QX11Data::clipboardWaitForEvent(Window win, int type, XEvent *event, int timeout)
00360 {
00361     QTime started = QTime::currentTime();
00362     QTime now = started;
00363 
00364     if (QAbstractEventDispatcher::instance()->inherits("QMotif")) {
00365         if (waiting_for_data)
00366             qFatal("QClipboard: internal error, qt_xclb_wait_for_event recursed");
00367         waiting_for_data = true;
00368 
00369 
00370         has_captured_event = false;
00371         capture_event_win = win;
00372         capture_event_type = type;
00373 
00374         QApplication::EventFilter old_event_filter =
00375             qApp->setEventFilter(qt_x11_clipboard_event_filter);
00376 
00377         do {
00378             if (XCheckTypedWindowEvent(display, win, type, event)) {
00379                 waiting_for_data = false;
00380                 qApp->setEventFilter(old_event_filter);
00381                 return true;
00382             }
00383 
00384             XSync(X11->display, false);
00385             usleep(50000);
00386 
00387             now = QTime::currentTime();
00388             if (started > now)                        // crossed midnight
00389                 started = now;
00390 
00391             QEventLoop::ProcessEventsFlags flags(QEventLoop::ExcludeUserInputEvents
00392                                                  | QEventLoop::ExcludeSocketNotifiers
00393                                                  | QEventLoop::WaitForMoreEvents
00394                                                  | QEventLoop::X11ExcludeTimers);
00395             QAbstractEventDispatcher *eventDispatcher = QAbstractEventDispatcher::instance();
00396             eventDispatcher->processEvents(flags);
00397 
00398             if (has_captured_event) {
00399                 waiting_for_data = false;
00400                 *event = captured_event;
00401                 qApp->setEventFilter(old_event_filter);
00402                 return true;
00403             }
00404         } while (started.msecsTo(now) < timeout);
00405 
00406         waiting_for_data = false;
00407         qApp->setEventFilter(old_event_filter);
00408     } else {
00409         do {
00410             if (XCheckTypedWindowEvent(X11->display,win,type,event))
00411                 return true;
00412 
00413             now = QTime::currentTime();
00414             if ( started > now )                        // crossed midnight
00415                 started = now;
00416 
00417             XFlush(X11->display);
00418 
00419             // sleep 50 ms, so we don't use up CPU cycles all the time.
00420             struct timeval usleep_tv;
00421             usleep_tv.tv_sec = 0;
00422             usleep_tv.tv_usec = 50000;
00423             select(0, 0, 0, 0, &usleep_tv);
00424         } while (started.msecsTo(now) < timeout);
00425     }
00426     return false;
00427 }
00428 
00429 
00430 static inline int maxSelectionIncr(Display *dpy)
00431 { return XMaxRequestSize(dpy) > 65536 ? 65536*4 : XMaxRequestSize(dpy)*4 - 100; }
00432 
00433 bool QX11Data::clipboardReadProperty(Window win, Atom property, bool deleteProperty,
00434                                      QByteArray *buffer, int *size, Atom *type, int *format, bool nullterm)
00435 {
00436     int           maxsize = maxSelectionIncr(display);
00437     ulong  bytes_left; // bytes_after
00438     ulong  length;     // nitems
00439     uchar *data;
00440     Atom   dummy_type;
00441     int    dummy_format;
00442     int    r;
00443 
00444     if (!type)                                // allow null args
00445         type = &dummy_type;
00446     if (!format)
00447         format = &dummy_format;
00448 
00449     // Don't read anything, just get the size of the property data
00450     r = XGetWindowProperty(display, win, property, 0, 0, False,
00451                             AnyPropertyType, type, format,
00452                             &length, &bytes_left, &data);
00453     if (r != Success || (type && *type == XNone)) {
00454         buffer->resize(0);
00455         return false;
00456     }
00457     XFree((char*)data);
00458 
00459     int  offset = 0, buffer_offset = 0, format_inc = 1, proplen = bytes_left;
00460 
00461     VDEBUG("QClipboard: read_property(): initial property length: %d", proplen);
00462 
00463     switch (*format) {
00464     case 8:
00465     default:
00466         format_inc = sizeof(char) / 1;
00467         break;
00468 
00469     case 16:
00470         format_inc = sizeof(short) / 2;
00471         proplen *= sizeof(short) / 2;
00472         break;
00473 
00474     case 32:
00475         format_inc = sizeof(long) / 4;
00476         proplen *= sizeof(long) / 4;
00477         break;
00478     }
00479 
00480     int newSize = proplen + (nullterm ? 1 : 0);
00481     buffer->resize(newSize);
00482 
00483     bool ok = (buffer->size() == newSize);
00484     VDEBUG("QClipboard: read_property(): buffer resized to %d", buffer->size());
00485 
00486     if (ok) {
00487         // could allocate buffer
00488 
00489         while (bytes_left) {
00490             // more to read...
00491 
00492             r = XGetWindowProperty(display, win, property, offset, maxsize/4,
00493                                    False, AnyPropertyType, type, format,
00494                                    &length, &bytes_left, &data);
00495             if (r != Success || (type && *type == XNone))
00496                 break;
00497 
00498             offset += length / (32 / *format);
00499             length *= format_inc * (*format) / 8;
00500 
00501             // Here we check if we get a buffer overflow and tries to
00502             // recover -- this shouldn't normally happen, but it doesn't
00503             // hurt to be defensive
00504             if ((int)(buffer_offset + length) > buffer->size()) {
00505                 length = buffer->size() - buffer_offset;
00506 
00507                 // escape loop
00508                 bytes_left = 0;
00509             }
00510 
00511             memcpy(buffer->data() + buffer_offset, data, length);
00512             buffer_offset += length;
00513 
00514             XFree((char*)data);
00515         }
00516 
00517         if (*format == 8 && *type == ATOM(COMPOUND_TEXT)) {
00518             // convert COMPOUND_TEXT to a multibyte string
00519             XTextProperty textprop;
00520             textprop.encoding = *type;
00521             textprop.format = *format;
00522             textprop.nitems = length;
00523             textprop.value = (unsigned char *) buffer->data();
00524 
00525             char **list_ret = 0;
00526             int count;
00527             if (XmbTextPropertyToTextList(display, &textprop, &list_ret,
00528                          &count) == Success && count && list_ret) {
00529                 offset = strlen(list_ret[0]);
00530                 buffer->resize(offset + (nullterm ? 1 : 0));
00531                 memcpy(buffer->data(), list_ret[0], offset);
00532             }
00533             if (list_ret) XFreeStringList(list_ret);
00534         }
00535 
00536         // zero-terminate (for text)
00537         if (nullterm)
00538             buffer->data()[buffer_offset] = '\0';
00539     }
00540 
00541     // correct size, not 0-term.
00542     if (size)
00543         *size = buffer_offset;
00544 
00545     VDEBUG("QClipboard: read_property(): buffer size %d, buffer offset %d, offset %d",
00546            buffer->size(), buffer_offset, offset);
00547 
00548     if (deleteProperty)
00549         XDeleteProperty(display, win, property);
00550 
00551     XFlush(display);
00552 
00553     return ok;
00554 }
00555 
00556 QByteArray QX11Data::clipboardReadIncrementalProperty(Window win, Atom property, int nbytes, bool nullterm)
00557 {
00558     XEvent event;
00559 
00560     QByteArray buf;
00561     QByteArray tmp_buf;
00562     bool alloc_error = false;
00563     int  length;
00564     int  offset = 0;
00565 
00566     if (nbytes > 0) {
00567         // Reserve buffer + zero-terminator (for text data)
00568         // We want to complete the INCR transfer even if we cannot
00569         // allocate more memory
00570         buf.resize(nbytes+1);
00571         alloc_error = buf.size() != nbytes+1;
00572     }
00573 
00574     for (;;) {
00575         XFlush(display);
00576         if (!clipboardWaitForEvent(win,PropertyNotify,&event,clipboard_timeout))
00577             break;
00578         if (event.xproperty.atom != property ||
00579              event.xproperty.state != PropertyNewValue)
00580             continue;
00581         if (X11->clipboardReadProperty(win, property, true, &tmp_buf, &length, 0, 0, false)) {
00582             if (length == 0) {                // no more data, we're done
00583                 if (nullterm) {
00584                     buf.resize(offset+1);
00585                     buf[offset] = '\0';
00586                 } else {
00587                     buf.resize(offset);
00588                 }
00589                 return buf;
00590             } else if (!alloc_error) {
00591                 if (offset+length > (int)buf.size()) {
00592                     buf.resize(offset+length+65535);
00593                     if (buf.size() != offset+length+65535) {
00594                         alloc_error = true;
00595                         length = buf.size() - offset;
00596                     }
00597                 }
00598                 memcpy(buf.data()+offset, tmp_buf.constData(), length);
00599                 tmp_buf.resize(0);
00600                 offset += length;
00601             }
00602         } else {
00603             break;
00604         }
00605     }
00606 
00607     // timed out ... create a new requestor window, otherwise the requestor
00608     // could consider next request to be still part of this timed out request
00609     delete requestor;
00610     requestor = new QWidget(0);
00611     requestor->setObjectName(QLatin1String("internal clipboard requestor"));
00612 
00613     return QByteArray();
00614 }
00615 
00616 static Atom send_targets_selection(QClipboardData *d, Window window, Atom property)
00617 {
00618     QVector<Atom> types;
00619     QStringList formats = QInternalMimeData::formatsHelper(d->source());
00620     for (int i = 0; i < formats.size(); ++i) {
00621         QList<Atom> atoms = X11->xdndMimeAtomsForFormat(formats.at(i));
00622         for (int j = 0; j < atoms.size(); ++j) {
00623             if (!types.contains(atoms.at(j)))
00624                 types.append(atoms.at(j));
00625         }
00626     }
00627     types.append(ATOM(TARGETS));
00628     types.append(ATOM(MULTIPLE));
00629     types.append(ATOM(TIMESTAMP));
00630 
00631     XChangeProperty(X11->display, window, property, XA_ATOM, 32,
00632                     PropModeReplace, (uchar *) types.data(), types.size());
00633     return property;
00634 }
00635 
00636 static Atom send_selection(QClipboardData *d, Atom target, Window window, Atom property)
00637 {
00638     Atom atomFormat = target;
00639     int dataFormat = 0;
00640     QByteArray data;
00641     if (X11->xdndMimeDataForAtom(target, d->source(), &data, &atomFormat, &dataFormat)) {
00642 
00643         VDEBUG("QClipboard: send_selection():\n"
00644           "    property type %lx\n"
00645           "    property name '%s'\n"
00646           "    format %d\n"
00647           "    %d bytes\n",
00648           target, X11->xdndMimeAtomToString(atomFormat).toLatin1().data(), dataFormat, data.size());
00649 
00650          // don't allow INCR transfers when using MULTIPLE or to
00651         // Motif clients (since Motif doesn't support INCR)
00652         static Atom motif_clip_temporary = ATOM(CLIP_TEMPORARY);
00653         bool allow_incr = property != motif_clip_temporary;
00654 
00655         // X_ChangeProperty protocol request is 24 bytes
00656         const int increment = (XMaxRequestSize(X11->display) * 4) - 24;
00657         if (data.size() > increment && allow_incr) {
00658             long bytes = data.size();
00659             XChangeProperty(X11->display, window, property,
00660                             ATOM(INCR), 32, PropModeReplace, (uchar *) &bytes, 1);
00661 
00662             (void)new QClipboardINCRTransaction(window, property, atomFormat, dataFormat, data, increment);
00663             return ATOM(INCR);
00664         }
00665 
00666         // make sure we can perform the XChangeProperty in a single request
00667         if (data.size() > increment)
00668             return XNone; // ### perhaps use several XChangeProperty calls w/ PropModeAppend?
00669         int dataSize = data.size() / (dataFormat / 8);
00670         // use a single request to transfer data
00671         XChangeProperty(X11->display, window, property, atomFormat,
00672                         dataFormat, PropModeReplace, (uchar *) data.data(),
00673                         dataSize);
00674     }
00675     return property;
00676 }
00677 
00681 void QClipboard::ownerDestroyed()
00682 { }
00683 
00684 
00688 void QClipboard::connectNotify(const char *)
00689 { }
00690 
00691 
00694 bool QClipboard::event(QEvent *e)
00695 {
00696     if (e->type() == QEvent::Timer) {
00697         QTimerEvent *te = (QTimerEvent *) e;
00698 
00699         if (waiting_for_data) // should never happen
00700             return false;
00701 
00702         if (te->timerId() == timer_id) {
00703             killTimer(timer_id);
00704             timer_id = 0;
00705 
00706             timer_event_clear = true;
00707             if (selection_watcher) // clear selection
00708                 selectionData()->clear();
00709             if (clipboard_watcher) // clear clipboard
00710                 clipboardData()->clear();
00711             timer_event_clear = false;
00712 
00713             return true;
00714         } else if (te->timerId() == pending_timer_id) {
00715             // I hate klipper
00716             killTimer(pending_timer_id);
00717             pending_timer_id = 0;
00718 
00719             if (pending_clipboard_changed) {
00720                 pending_clipboard_changed = false;
00721                 clipboardData()->clear();
00722                 emitChanged(QClipboard::Clipboard);
00723             }
00724             if (pending_selection_changed) {
00725                 pending_selection_changed = false;
00726                 selectionData()->clear();
00727                 emitChanged(QClipboard::Selection);
00728             }
00729 
00730             return true;
00731         } else if (te->timerId() == incr_timer_id) {
00732             killTimer(incr_timer_id);
00733             incr_timer_id = 0;
00734 
00735             qt_xclb_incr_timeout();
00736 
00737             return true;
00738         } else {
00739             return QObject::event(e);
00740         }
00741     } else if (e->type() != QEvent::Clipboard) {
00742         return QObject::event(e);
00743     }
00744 
00745     XEvent *xevent = (XEvent *)(((QClipboardEvent *)e)->data());
00746     Display *dpy = X11->display;
00747 
00748     if (!xevent)
00749         return true;
00750 
00751     switch (xevent->type) {
00752 
00753     case SelectionClear:
00754         // new selection owner
00755         if (xevent->xselectionclear.selection == XA_PRIMARY) {
00756             QClipboardData *d = selectionData();
00757 
00758             // ignore the event if it was generated before we gained selection ownership
00759             if (d->timestamp != CurrentTime && xevent->xselectionclear.time < d->timestamp)
00760                 break;
00761 
00762             DEBUG("QClipboard: new selection owner 0x%lx at time %lx (ours %lx)",
00763                   XGetSelectionOwner(dpy, XA_PRIMARY),
00764                   xevent->xselectionclear.time, d->timestamp);
00765 
00766             if (! waiting_for_data) {
00767                 d->clear();
00768                 emitChanged(QClipboard::Selection);
00769             } else {
00770                 pending_selection_changed = true;
00771                 if (! pending_timer_id)
00772                     pending_timer_id = QApplication::clipboard()->startTimer(0);
00773             }
00774         } else if (xevent->xselectionclear.selection == ATOM(CLIPBOARD)) {
00775             QClipboardData *d = clipboardData();
00776 
00777             // ignore the event if it was generated before we gained selection ownership
00778             if (d->timestamp != CurrentTime && xevent->xselectionclear.time < d->timestamp)
00779                 break;
00780 
00781             DEBUG("QClipboard: new clipboard owner 0x%lx at time %lx (%lx)",
00782                   XGetSelectionOwner(dpy, ATOM(CLIPBOARD)),
00783                   xevent->xselectionclear.time, d->timestamp);
00784 
00785             if (! waiting_for_data) {
00786                 d->clear();
00787                 emitChanged(QClipboard::Clipboard);
00788             } else {
00789                 pending_clipboard_changed = true;
00790                 if (! pending_timer_id)
00791                     pending_timer_id = QApplication::clipboard()->startTimer(0);
00792             }
00793         } else {
00794             qWarning("QClipboard: Unknown SelectionClear event received");
00795             return false;
00796         }
00797         break;
00798 
00799     case SelectionNotify:
00800         /*
00801           Something has delivered data to us, but this was not caught
00802           by QClipboardWatcher::getDataInFormat()
00803 
00804           Just skip the event to prevent Bad Things (tm) from
00805           happening later on...
00806         */
00807         break;
00808 
00809     case SelectionRequest:
00810         {
00811             // someone wants our data
00812             XSelectionRequestEvent *req = &xevent->xselectionrequest;
00813 
00814             if (requestor && req->requestor == requestor->internalWinId())
00815                 break;
00816 
00817             XEvent event;
00818             event.xselection.type      = SelectionNotify;
00819             event.xselection.display   = req->display;
00820             event.xselection.requestor = req->requestor;
00821             event.xselection.selection = req->selection;
00822             event.xselection.target    = req->target;
00823             event.xselection.property  = XNone;
00824             event.xselection.time      = req->time;
00825 
00826             DEBUG("QClipboard: SelectionRequest from %lx\n"
00827                   "    selection 0x%lx (%s) target 0x%lx (%s)",
00828                   req->requestor,
00829                   req->selection,
00830                   X11->xdndAtomToString(req->selection).data(),
00831                   req->target,
00832                   X11->xdndAtomToString(req->target).data());
00833 
00834             QClipboardData *d;
00835             if (req->selection == XA_PRIMARY) {
00836                 d = selectionData();
00837             } else if (req->selection == ATOM(CLIPBOARD)) {
00838                 d = clipboardData();
00839             } else {
00840                 qWarning("QClipboard: Unknown selection '%lx'", req->selection);
00841                 XSendEvent(dpy, req->requestor, False, NoEventMask, &event);
00842                 break;
00843             }
00844 
00845             if (! d->source()) {
00846                 qWarning("QClipboard: Cannot transfer data, no data available");
00847                 XSendEvent(dpy, req->requestor, False, NoEventMask, &event);
00848                 break;
00849             }
00850 
00851             DEBUG("QClipboard: SelectionRequest at time %lx (ours %lx)",
00852                   req->time, d->timestamp);
00853 
00854             if (d->timestamp == CurrentTime // we don't own the selection anymore
00855                 || (req->time != CurrentTime && req->time < d->timestamp)) {
00856                 DEBUG("QClipboard: SelectionRequest too old");
00857                 XSendEvent(dpy, req->requestor, False, NoEventMask, &event);
00858                 break;
00859             }
00860 
00861             Atom xa_targets = ATOM(TARGETS);
00862             Atom xa_multiple = ATOM(MULTIPLE);
00863             Atom xa_timestamp = ATOM(TIMESTAMP);
00864 
00865             struct AtomPair { Atom target; Atom property; } *multi = 0;
00866             Atom multi_type = XNone;
00867             int multi_format = 0;
00868             int nmulti = 0;
00869             int imulti = -1;
00870             bool multi_writeback = false;
00871 
00872             if (req->target == xa_multiple) {
00873                 QByteArray multi_data;
00874                 if (req->property == XNone
00875                     || !X11->clipboardReadProperty(req->requestor, req->property, false, &multi_data,
00876                                                    0, &multi_type, &multi_format, 0)
00877                     || multi_format != 32) {
00878                     // MULTIPLE property not formatted correctly
00879                     XSendEvent(dpy, req->requestor, False, NoEventMask, &event);
00880                     break;
00881                 }
00882                 nmulti = multi_data.size()/sizeof(*multi);
00883                 multi = new AtomPair[nmulti];
00884                 memcpy(multi,multi_data.data(),multi_data.size());
00885                 imulti = 0;
00886             }
00887 
00888             for (; imulti < nmulti; ++imulti) {
00889                 Atom target;
00890                 Atom property;
00891 
00892                 if (multi) {
00893                     target = multi[imulti].target;
00894                     property = multi[imulti].property;
00895                 } else {
00896                     target = req->target;
00897                     property = req->property;
00898                     if (property == XNone) // obsolete client
00899                         property = target;
00900                 }
00901 
00902                 Atom ret = XNone;
00903                 if (target == XNone || property == XNone) {
00904                     ;
00905                 } else if (target == xa_timestamp) {
00906                     if (d->timestamp != CurrentTime) {
00907                         XChangeProperty(dpy, req->requestor, property, xa_timestamp, 32,
00908                                         PropModeReplace, (uchar *) &d->timestamp, 1);
00909                         ret = property;
00910                     } else {
00911                         qWarning("QClipboard: Invalid data timestamp");
00912                     }
00913                 } else if (target == xa_targets) {
00914                     ret = send_targets_selection(d, req->requestor, property);
00915                 } else {
00916                     ret = send_selection(d, target, req->requestor, property);
00917                 }
00918 
00919                 if (nmulti > 0) {
00920                     if (ret == XNone) {
00921                         multi[imulti].property = XNone;
00922                         multi_writeback = true;
00923                     }
00924                 } else {
00925                     event.xselection.property = ret;
00926                     break;
00927                 }
00928             }
00929 
00930             if (nmulti > 0) {
00931                 if (multi_writeback) {
00932                     // according to ICCCM 2.6.2 says to put None back
00933                     // into the original property on the requestor window
00934                     XChangeProperty(dpy, req->requestor, req->property, multi_type, 32,
00935                                     PropModeReplace, (uchar *) multi, nmulti * 2);
00936                 }
00937 
00938                 delete [] multi;
00939                 event.xselection.property = req->property;
00940             }
00941 
00942             // send selection notify to requestor
00943             XSendEvent(dpy, req->requestor, False, NoEventMask, &event);
00944 
00945             DEBUG("QClipboard: SelectionNotify to 0x%lx\n"
00946                   "    property 0x%lx (%s)",
00947                   req->requestor, event.xselection.property,
00948                   X11->xdndAtomToString(event.xselection.property).data());
00949         }
00950         break;
00951     }
00952 
00953     return true;
00954 }
00955 
00956 
00957 
00958 
00959 
00960 
00961 QClipboardWatcher::QClipboardWatcher(QClipboard::Mode mode)
00962     : QInternalMimeData()
00963 {
00964     switch (mode) {
00965     case QClipboard::Selection:
00966         atom = XA_PRIMARY;
00967         break;
00968 
00969     case QClipboard::Clipboard:
00970         atom = ATOM(CLIPBOARD);
00971         break;
00972 
00973     default:
00974         qWarning("QClipboardWatcher: Internal error: Unsupported clipboard mode");
00975         break;
00976     }
00977 
00978     setupOwner();
00979 }
00980 
00981 QClipboardWatcher::~QClipboardWatcher()
00982 {
00983     if(selection_watcher == this)
00984         selection_watcher = 0;
00985     if(clipboard_watcher == this)
00986         clipboard_watcher = 0;
00987 }
00988 
00989 bool QClipboardWatcher::empty() const
00990 {
00991     Display *dpy = X11->display;
00992     Window win = XGetSelectionOwner(dpy, atom);
00993 
00994     if(win == requestor->internalWinId()) {
00995         qWarning("QClipboardWatcher::empty: Internal error: Application owns the selection");
00996         return true;
00997     }
00998 
00999     return win == XNone;
01000 }
01001 
01002 QStringList QClipboardWatcher::formats_sys() const
01003 {
01004     if (empty())
01005         return QStringList();
01006 
01007     if (!formatList.count()) {
01008         // get the list of targets from the current clipboard owner - we do this
01009         // once so that multiple calls to this function don't require multiple
01010         // server round trips...
01011 
01012         format_atoms = getDataInFormat(ATOM(TARGETS));
01013 
01014         if (format_atoms.size() > 0) {
01015             Atom *targets = (Atom *) format_atoms.data();
01016             int size = format_atoms.size() / sizeof(Atom);
01017 
01018             for (int i = 0; i < size; ++i) {
01019                 if (targets[i] == 0)
01020                     continue;
01021 
01022                 QStringList formatsForAtom = X11->xdndMimeFormatsForAtom(targets[i]);
01023                 for (int j = 0; j < formatsForAtom.size(); ++j) {
01024                     if (!formatList.contains(formatsForAtom.at(j)))
01025                         formatList.append(formatsForAtom.at(j));
01026                 }
01027                 VDEBUG("    format: %s", X11->xdndAtomToString(targets[i]).data());
01028                 VDEBUG("    data:\n%s\n", getDataInFormat(targets[i]).data());
01029             }
01030             DEBUG("QClipboardWatcher::format: %d formats available", formatList.count());
01031         }
01032     }
01033 
01034     return formatList;
01035 }
01036 
01037 bool QClipboardWatcher::hasFormat_sys(const QString &format) const
01038 {
01039     QStringList list = formats();
01040     return list.contains(format);
01041 }
01042 
01043 QVariant QClipboardWatcher::retrieveData_sys(const QString &fmt, QVariant::Type) const
01044 {
01045     if (fmt.isEmpty() || empty())
01046         return QByteArray();
01047 
01048     (void)formats(); // trigger update of format list
01049     DEBUG("QClipboardWatcher::data: fetching format '%s'", fmt.toLatin1().data());
01050 
01051     QList<Atom> atoms;
01052     Atom *targets = (Atom *) format_atoms.data();
01053     int size = format_atoms.size() / sizeof(Atom);
01054     for (int i = 0; i < size; ++i)
01055         atoms.append(targets[i]);
01056 
01057     Atom fmtatom = X11->xdndMimeAtomForFormat(fmt, atoms);
01058 
01059     if (fmtatom == 0)
01060         return QVariant();
01061 
01062     return X11->xdndMimeConvertToFormat(fmtatom, getDataInFormat(fmtatom), fmt);
01063 }
01064 
01065 QByteArray QClipboardWatcher::getDataInFormat(Atom fmtatom) const
01066 {
01067     QByteArray buf;
01068 
01069     Display *dpy = X11->display;
01070     requestor->createWinId();
01071     Window   win = requestor->internalWinId();
01072     Q_ASSERT(requestor->testAttribute(Qt::WA_WState_Created));
01073 
01074     DEBUG("QClipboardWatcher::getDataInFormat: selection '%s' format '%s'",
01075           X11->xdndAtomToString(atom).data(), X11->xdndAtomToString(fmtatom).data());
01076 
01077     XSelectInput(dpy, win, NoEventMask); // don't listen for any events
01078 
01079     XDeleteProperty(dpy, win, ATOM(_QT_SELECTION));
01080     XConvertSelection(dpy, atom, fmtatom, ATOM(_QT_SELECTION), win, X11->time);
01081     XSync(dpy, false);
01082 
01083     VDEBUG("QClipboardWatcher::getDataInFormat: waiting for SelectionNotify event");
01084 
01085     XEvent xevent;
01086     if (!X11->clipboardWaitForEvent(win,SelectionNotify,&xevent,clipboard_timeout) ||
01087          xevent.xselection.property == XNone) {
01088         DEBUG("QClipboardWatcher::getDataInFormat: format not available");
01089         return buf;
01090     }
01091 
01092     VDEBUG("QClipboardWatcher::getDataInFormat: fetching data...");
01093 
01094     Atom   type;
01095     XSelectInput(dpy, win, PropertyChangeMask);
01096 
01097     if (X11->clipboardReadProperty(win, ATOM(_QT_SELECTION), true, &buf, 0, &type, 0, false)) {
01098         if (type == ATOM(INCR)) {
01099             int nbytes = buf.size() >= 4 ? *((int*)buf.data()) : 0;
01100             buf = X11->clipboardReadIncrementalProperty(win, ATOM(_QT_SELECTION), nbytes, false);
01101         }
01102     }
01103 
01104     XSelectInput(dpy, win, NoEventMask);
01105 
01106     DEBUG("QClipboardWatcher::getDataInFormat: %d bytes received", buf.size());
01107 
01108     return buf;
01109 }
01110 
01111 
01112 const QMimeData* QClipboard::mimeData(Mode mode) const
01113 {
01114     QClipboardData *d = 0;
01115     switch (mode) {
01116     case Selection:
01117         d = selectionData();
01118         break;
01119     case Clipboard:
01120         d = clipboardData();
01121         break;
01122     default:
01123         qWarning("QClipboard::mimeData: unsupported mode '%d'", mode);
01124         return 0;
01125     }
01126 
01127     if (! d->source() && ! timer_event_clear) {
01128         if (mode == Selection) {
01129             if (! selection_watcher)
01130                 selection_watcher = new QClipboardWatcher(mode);
01131             d->setSource(selection_watcher);
01132         } else {
01133             if (! clipboard_watcher)
01134                 clipboard_watcher = new QClipboardWatcher(mode);
01135             d->setSource(clipboard_watcher);
01136         }
01137 
01138         if (! timer_id) {
01139             // start a zero timer - we will clear cached data when the timer
01140             // times out, which will be the next time we hit the event loop...
01141             // that way, the data is cached long enough for calls within a single
01142             // loop/function, but the data doesn't linger around in case the selection
01143             // changes
01144             QClipboard *that = ((QClipboard *) this);
01145             timer_id = that->startTimer(0);
01146         }
01147     }
01148 
01149     return d->source();
01150 }
01151 
01152 
01153 void QClipboard::setMimeData(QMimeData* src, Mode mode)
01154 {
01155     Atom atom, sentinel_atom;
01156     QClipboardData *d;
01157     switch (mode) {
01158     case Selection:
01159         atom = XA_PRIMARY;
01160         sentinel_atom = ATOM(_QT_SELECTION_SENTINEL);
01161         d = selectionData();
01162         break;
01163 
01164     case Clipboard:
01165         atom = ATOM(CLIPBOARD);
01166         sentinel_atom = ATOM(_QT_CLIPBOARD_SENTINEL);
01167         d = clipboardData();
01168         break;
01169 
01170     default:
01171         qWarning("QClipboard::setMimeData: unsupported mode '%d'", mode);
01172         return;
01173     }
01174 
01175     Display *dpy = X11->display;
01176     Window newOwner;
01177 
01178     if (! src) { // no data, clear clipboard contents
01179         newOwner = XNone;
01180         d->clear();
01181     } else {
01182         setupOwner();
01183 
01184         newOwner = owner->internalWinId();
01185 
01186         d->setSource(src);
01187         d->timestamp = X11->time;
01188     }
01189 
01190     Window prevOwner = XGetSelectionOwner(dpy, atom);
01191     // use X11->time, since d->timestamp == CurrentTime when clearing
01192     XSetSelectionOwner(dpy, atom, newOwner, X11->time);
01193 
01194     if (mode == Selection)
01195         emitChanged(QClipboard::Selection);
01196     else
01197         emitChanged(QClipboard::Clipboard);
01198 
01199     if (XGetSelectionOwner(dpy, atom) != newOwner) {
01200         qWarning("QClipboard::setData: Cannot set X11 selection owner for %s",
01201                  X11->xdndAtomToString(atom).data());
01202         d->clear();
01203         return;
01204     }
01205 
01206     // Signal to other Qt processes that the selection has changed
01207     Window owners[2];
01208     owners[0] = newOwner;
01209     owners[1] = prevOwner;
01210     XChangeProperty(dpy, QApplication::desktop()->screen(0)->internalWinId(),
01211                      sentinel_atom, XA_WINDOW, 32, PropModeReplace,
01212                      (unsigned char*)&owners, 2);
01213 }
01214 
01215 
01216 /*
01217   Called by the main event loop in qapplication_x11.cpp when the
01218   _QT_SELECTION_SENTINEL property has been changed (i.e. when some Qt
01219   process has performed QClipboard::setData(). If it returns true, the
01220   QClipBoard dataChanged() signal should be emitted.
01221 */
01222 
01223 bool qt_check_selection_sentinel()
01224 {
01225     bool doIt = true;
01226     if (owner) {
01227         /*
01228           Since the X selection mechanism cannot give any signal when
01229           the selection has changed, we emulate it (for Qt processes) here.
01230           The notification should be ignored in case of either
01231           a) This is the process that did setData (because setData()
01232           then has already emitted dataChanged())
01233           b) This is the process that owned the selection when dataChanged()
01234           was called (because we have then received a SelectionClear event,
01235           and have already emitted dataChanged() as a result of that)
01236         */
01237 
01238         Window* owners;
01239         Atom actualType;
01240         int actualFormat;
01241         ulong nitems;
01242         ulong bytesLeft;
01243 
01244         if (XGetWindowProperty(X11->display,
01245                                QApplication::desktop()->screen(0)->internalWinId(),
01246                                ATOM(_QT_SELECTION_SENTINEL), 0, 2, False, XA_WINDOW,
01247                                &actualType, &actualFormat, &nitems,
01248                                &bytesLeft, (unsigned char**)&owners) == Success) {
01249             if (actualType == XA_WINDOW && actualFormat == 32 && nitems == 2) {
01250                 Window win = owner->internalWinId();
01251                 if (owners[0] == win || owners[1] == win)
01252                     doIt = false;
01253             }
01254 
01255             XFree(owners);
01256         }
01257     }
01258 
01259     if (doIt) {
01260         if (waiting_for_data) {
01261             pending_selection_changed = true;
01262             if (! pending_timer_id)
01263                 pending_timer_id = QApplication::clipboard()->startTimer(0);
01264             doIt = false;
01265         } else {
01266             selectionData()->clear();
01267         }
01268     }
01269 
01270     return doIt;
01271 }
01272 
01273 
01274 bool qt_check_clipboard_sentinel()
01275 {
01276     bool doIt = true;
01277     if (owner) {
01278         Window *owners;
01279         Atom actualType;
01280         int actualFormat;
01281         unsigned long nitems, bytesLeft;
01282 
01283         if (XGetWindowProperty(X11->display,
01284                                QApplication::desktop()->screen(0)->internalWinId(),
01285                                ATOM(_QT_CLIPBOARD_SENTINEL), 0, 2, False, XA_WINDOW,
01286                                &actualType, &actualFormat, &nitems, &bytesLeft,
01287                                (unsigned char **) &owners) == Success) {
01288             if (actualType == XA_WINDOW && actualFormat == 32 && nitems == 2) {
01289                 Window win = owner->internalWinId();
01290                 if (owners[0] == win || owners[1] == win)
01291                     doIt = false;
01292             }
01293 
01294             XFree(owners);
01295         }
01296     }
01297 
01298     if (doIt) {
01299         if (waiting_for_data) {
01300             pending_clipboard_changed = true;
01301             if (! pending_timer_id)
01302                 pending_timer_id = QApplication::clipboard()->startTimer(0);
01303             doIt = false;
01304         } else {
01305             clipboardData()->clear();
01306         }
01307     }
01308 
01309     return doIt;
01310 }
01311 
01312 #endif // QT_NO_CLIPBOARD

Generated on Thu Mar 15 11:55:05 2007 for Qt 4.2 User's Guide by  doxygen 1.5.1