diff options
Diffstat (limited to 'tqtinterface/qt4/src/kernel/tqclipboard_x11.cpp')
-rw-r--r-- | tqtinterface/qt4/src/kernel/tqclipboard_x11.cpp | 2020 |
1 files changed, 2020 insertions, 0 deletions
diff --git a/tqtinterface/qt4/src/kernel/tqclipboard_x11.cpp b/tqtinterface/qt4/src/kernel/tqclipboard_x11.cpp new file mode 100644 index 0000000..14e7f08 --- /dev/null +++ b/tqtinterface/qt4/src/kernel/tqclipboard_x11.cpp @@ -0,0 +1,2020 @@ +/**************************************************************************** +** +** Implementation of TQClipboard class for X11 +** +** Created : 960430 +** +** Copyright (C) 2010 Timothy Pearson and (C) 1992-2008 Trolltech ASA. +** +** This file is part of the kernel module of the TQt GUI Toolkit. +** +** This file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free +** Software Foundation and appearing in the files LICENSE.GPL2 +** and LICENSE.GPL3 included in the packaging of this file. +** Alternatively you may (at your option) use any later version +** of the GNU General Public License if such license has been +** publicly approved by Trolltech ASA (or its successors, if any) +** and the KDE Free TQt Foundation. +** +** Please review the following information to ensure GNU General +** Public Licensing requirements will be met: +** http://trolltech.com/products/qt/licenses/licensing/opensource/. +** If you are unsure which license is appropriate for your use, please +** review the following information: +** http://trolltech.com/products/qt/licenses/licensing/licensingoverview +** or contact the sales department at sales@trolltech.com. +** +** This file may be used under the terms of the Q Public License as +** defined by Trolltech ASA and appearing in the file LICENSE.TQPL +** included in the packaging of this file. Licensees holding valid TQt +** Commercial licenses may use this file in accordance with the TQt +** Commercial License Agreement provided with the Software. +** +** This file is provided "AS IS" with NO WARRANTY OF ANY KIND, +** INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE. Trolltech reserves all rights not granted +** herein. +** +**********************************************************************/ + +// #define TQCLIPBOARD_DEBUG +// #define TQCLIPBOARD_DEBUG_VERBOSE + +#ifdef TQCLIPBOARD_DEBUG +# define TQDEBUG qDebug +#else +# define TQDEBUG if (FALSE) qDebug +#endif + +#ifdef TQCLIPBOARD_DEBUG_VERBOSE +# define VTQDEBUG qDebug +#else +# define VTQDEBUG if (FALSE) qDebug +#endif + +#include "tqplatformdefs.h" + +// POSIX Large File Support redefines open -> open64 +#if defined(open) +# undef open +#endif + +#include "tqclipboard.h" + +#ifndef TQT_NO_CLIPBOARD + +#include "tqapplication.h" +#include "tqeventloop.h" +#include "tqbitmap.h" +#include "tqdatetime.h" +#include "tqdragobject.h" +#include "tqbuffer.h" +#include "tqtextcodec.h" +#include "tqvaluelist.h" +#include "tqmap.h" +#include "tqt_x11_p.h" +#include "tqapplication_p.h" + +// #ifdef USE_QT4 +#if 0 + +/***************************************************************************** + Internal TQClipboard functions for X11. + *****************************************************************************/ + +// from qapplication_x11.cpp +typedef int (*QX11EventFilter) (XEvent*); +extern QX11EventFilter qt_set_x11_event_filter (QX11EventFilter filter); + +extern Time qt_x_incr; // def. in qapplication_x11.cpp +extern Atom qt_xa_clipboard; +extern Atom qt_selection_property; +extern Atom qt_clipboard_sentinel; +extern Atom qt_selection_sentinel; +extern Atom qt_utf8_string; + +// from qdnd_x11.cpp +extern Atom* qt_xdnd_str_to_atom( const char *mimeType ); +extern const char* qt_xdnd_atom_to_str( Atom ); + + +static int clipboard_timeout = 5000; // 5s timeout on clipboard operations + +static TQWidget * owner = 0; +static TQWidget *requestor = 0; +static bool inSelectionMode_obsolete = FALSE; // ### remove 4.0 +static bool timer_event_clear = FALSE; +static int timer_id = 0; + +static int pending_timer_id = 0; +static bool pending_clipboard_changed = FALSE; +static bool pending_selection_changed = FALSE; + + +// event capture mechanism for qt_xclb_wait_for_event +static bool waiting_for_data = FALSE; +static bool has_captured_event = FALSE; +static Window capture_event_win = None; +static int capture_event_type = -1; +static XEvent captured_event; + +// event filter function... captures interesting events while +// qt_xclb_wait_for_event is running the event loop +static int qt_xclb_event_filter(XEvent *event) +{ + if (event->xany.type == capture_event_type && + event->xany.window == capture_event_win) { + VTQDEBUG( "TQClipboard: event_filter(): caught event type %d", event->type ); + has_captured_event = TRUE; + captured_event = *event; + return 1; + } + + return 0; +} + +static Bool checkForClipboardEvents(Display *, XEvent *e, XPointer) +{ + return ((e->type == SelectionRequest && (e->xselectionrequest.selection == XA_PRIMARY + || e->xselectionrequest.selection == qt_xa_clipboard)) + || (e->type == SelectionClear && (e->xselectionclear.selection == XA_PRIMARY + || e->xselectionclear.selection == qt_xa_clipboard))); +} + +bool qt_xclb_wait_for_event( Display *dpy, Window win, int type, XEvent *event, + int timeout ) +{ + TQTime started = TQTime::currentTime(); + TQTime now = started; + + if (tqApp->eventLoop()->inherits("TQMotif")) { // yes yes, evil hack, we know + if ( waiting_for_data ) + qFatal( "TQClipboard: internal error, qt_xclb_wait_for_event recursed" ); + + waiting_for_data = TRUE; + has_captured_event = FALSE; + capture_event_win = win; + capture_event_type = type; + + QX11EventFilter old_event_filter = qt_set_x11_event_filter(qt_xclb_event_filter); + + do { + if ( XCheckTypedWindowEvent(dpy,win,type,event) ) { + waiting_for_data = FALSE; + qt_set_x11_event_filter(old_event_filter); + return TRUE; + } + + now = TQTime::currentTime(); + if ( started > now ) // crossed midnight + started = now; + + // 0x08 == ExcludeTimers for X11 only + tqApp->eventLoop()->processEvents( TQEventLoop::ExcludeUserInput | + TQEventLoop::ExcludeSocketNotifiers | + TQEventLoop::WaitForMore | 0x08 ); + + if ( has_captured_event ) { + waiting_for_data = FALSE; + *event = captured_event; + qt_set_x11_event_filter(old_event_filter); + return TRUE; + } + } while ( started.msecsTo(now) < timeout ); + + waiting_for_data = FALSE; + qt_set_x11_event_filter(old_event_filter); + + return FALSE; + } + + bool flushed = FALSE; + do { + if ( XCheckTypedWindowEvent(dpy,win,type,event) ) + return TRUE; + + // process other clipboard events, since someone is probably requesting data from us + XEvent e; + if (XCheckIfEvent(dpy, &e, checkForClipboardEvents, 0)) + tqApp->x11ProcessEvent(&e); + + now = TQTime::currentTime(); + if ( started > now ) // crossed midnight + started = now; + + if(!flushed) { + XFlush( dpy ); + flushed = TRUE; + } + + // sleep 50ms, so we don't use up CPU cycles all the time. + struct timeval usleep_tv; + usleep_tv.tv_sec = 0; + usleep_tv.tv_usec = 50000; + select(0, 0, 0, 0, &usleep_tv); + } while ( started.msecsTo(now) < timeout ); + + return FALSE; +} + +static inline int maxSelectionIncr( Display *dpy ) +{ return XMaxRequestSize(dpy) > 65536 ? 65536*4 : XMaxRequestSize(dpy)*4 - 100; } + +// uglyhack: externed into qt_xdnd.cpp. qt is really not designed for +// single-platform, multi-purpose blocks of code... +bool qt_xclb_read_property( Display *dpy, Window win, Atom property, + bool deleteProperty, + TQByteArray *buffer, int *size, Atom *type, + int *format, bool nullterm ) +{ + int maxsize = maxSelectionIncr(dpy); + ulong bytes_left; // bytes_after + ulong length; // nitems + uchar *data; + Atom dummy_type; + int dummy_format; + int r; + + if ( !type ) // allow null args + type = &dummy_type; + if ( !format ) + format = &dummy_format; + + // Don't read anything, just get the size of the property data + r = XGetWindowProperty( dpy, win, property, 0, 0, False, + AnyPropertyType, type, format, + &length, &bytes_left, &data ); + if (r != Success || (type && *type == None)) { + buffer->resize( 0 ); + return FALSE; + } + XFree( (char*)data ); + + int offset = 0, buffer_offset = 0, format_inc = 1, proplen = bytes_left; + + VTQDEBUG("TQClipboard: read_property(): initial property length: %d", proplen); + + switch (*format) { + case 8: + default: + format_inc = sizeof(char) / 1; + break; + + case 16: + format_inc = sizeof(short) / 2; + proplen *= sizeof(short) / 2; + break; + + case 32: + format_inc = sizeof(long) / 4; + proplen *= sizeof(long) / 4; + break; + } + + bool ok = buffer->tqresize( proplen + (nullterm ? 1 : 0) ); + + VTQDEBUG("TQClipboard: read_property(): buffer resized to %d", buffer->size()); + + if ( ok ) { + // could allocate buffer + + while ( bytes_left ) { + // more to read... + + r = XGetWindowProperty( dpy, win, property, offset, maxsize/4, + False, AnyPropertyType, type, format, + &length, &bytes_left, &data ); + if (r != Success || (type && *type == None)) + break; + + offset += length / (32 / *format); + length *= format_inc * (*format) / 8; + + // Here we check if we get a buffer overflow and tries to + // recover -- this shouldn't normally happen, but it doesn't + // hurt to be defensive + if (buffer_offset + length > buffer->size()) { + length = buffer->size() - buffer_offset; + + // escape loop + bytes_left = 0; + } + + memcpy(buffer->data() + buffer_offset, data, length); + buffer_offset += length; + + XFree( (char*)data ); + } + + static Atom xa_compound_text = *qt_xdnd_str_to_atom( "COMPOUND_TEXT" ); + if ( *format == 8 && *type == xa_compound_text ) { + // convert COMPOUND_TEXT to a multibyte string + XTextProperty textprop; + textprop.encoding = *type; + textprop.format = *format; + textprop.nitems = length; + textprop.value = (unsigned char *) buffer->data(); + + char **list_ret = 0; + int count; + if ( XmbTextPropertyToTextList( dpy, &textprop, &list_ret, + &count ) == Success && + count && list_ret ) { + offset = strlen( list_ret[0] ); + buffer->resize( offset + ( nullterm ? 1 : 0 ) ); + memcpy( buffer->data(), list_ret[0], offset ); + } + if (list_ret) XFreeStringList(list_ret); + } + + // zero-terminate (for text) + if (nullterm) + buffer->tqat(buffer_offset) = '\0'; + } + + // correct size, not 0-term. + if ( size ) + *size = buffer_offset; + + VTQDEBUG("TQClipboard: read_property(): buffer size %d, buffer offset %d, offset %d", + buffer->size(), buffer_offset, offset); + + if ( deleteProperty ) + XDeleteProperty( dpy, win, property ); + + XFlush( dpy ); + + return ok; +} + +// this is externed into qt_xdnd.cpp too. +TQByteArray qt_xclb_read_incremental_property( Display *dpy, Window win, + Atom property, int nbytes, + bool nullterm ) +{ + XEvent event; + + TQByteArray buf; + TQByteArray tmp_buf; + bool alloc_error = FALSE; + int length; + int offset = 0; + + if ( nbytes > 0 ) { + // Reserve buffer + zero-terminator (for text data) + // We want to complete the INCR transfer even if we cannot + // allocate more memory + alloc_error = !buf.tqresize(nbytes+1); + } + + for (;;) { + XFlush( dpy ); + if ( !qt_xclb_wait_for_event(dpy,win,PropertyNotify,&event,clipboard_timeout) ) + break; + if ( event.xproperty.atom != property || + event.xproperty.state != PropertyNewValue ) + continue; + if ( qt_xclb_read_property(dpy, win, property, TRUE, &tmp_buf, + &length,0, 0, FALSE) ) { + if ( length == 0 ) { // no more data, we're done + if ( nullterm ) { + buf.resize( offset+1 ); + buf.tqat( offset ) = '\0'; + } else { + buf.resize(offset); + } + return buf; + } else if ( !alloc_error ) { + if ( offset+length > (int)buf.size() ) { + if ( !buf.tqresize(offset+length+65535) ) { + alloc_error = TRUE; + length = buf.size() - offset; + } + } + memcpy( buf.data()+offset, tmp_buf.data(), length ); + tmp_buf.resize( 0 ); + offset += length; + } + } else { + break; + } + } + + // timed out ... create a new requestor window, otherwise the requestor + // could consider next request to be still part of this timed out request + delete requestor; + requestor = new TQWidget( 0, "internal clipboard requestor" ); + + return TQByteArray(); +} + +/*! \obsolete + + Use the TQClipboard::data(), TQClipboard::setData() and related functions + which take a TQClipboard::Mode argument. + + Sets the clipboard selection mode. If \a enable is TRUE, then + subsequent calls to TQClipboard::setData() and other functions + which put data into the clipboard will put the data into the mouse + selection, otherwise the data will be put into the clipboard. + + \sa supportsSelection(), selectionModeEnabled() +*/ +void TQClipboard::setSelectionMode(bool enable) +{ inSelectionMode_obsolete = enable; } + + +/*! \obsolete + + Use the TQClipboard::data(), TQClipboard::setData() and related functions + which take a TQClipboard::Mode argument. + + Returns the selection mode. + + \sa setSelectionMode(), supportsSelection() +*/ +bool TQClipboard::selectionModeEnabled() const +{ return inSelectionMode_obsolete; } + +#else // USE_QT4 + + +// REVISED: arnt + +/***************************************************************************** + Internal TQClipboard functions for X11. + *****************************************************************************/ + +// from qapplication_x11.cpp +typedef int (*QX11EventFilter) (XEvent*); +extern QX11EventFilter qt_set_x11_event_filter (QX11EventFilter filter); + +extern Time qt_x_incr; // def. in qapplication_x11.cpp +extern Atom qt_xa_clipboard; +extern Atom qt_selection_property; +extern Atom qt_clipboard_sentinel; +extern Atom qt_selection_sentinel; +extern Atom qt_utf8_string; + +// from qdnd_x11.cpp +extern Atom* qt_xdnd_str_to_atom( const char *mimeType ); +extern const char* qt_xdnd_atom_to_str( Atom ); + + +static int clipboard_timeout = 5000; // 5s timeout on clipboard operations + +static TQWidget * owner = 0; +static TQWidget *requestor = 0; +static bool inSelectionMode_obsolete = FALSE; // ### remove 4.0 +static bool timer_event_clear = FALSE; +static int timer_id = 0; + +static int pending_timer_id = 0; +static bool pending_clipboard_changed = FALSE; +static bool pending_selection_changed = FALSE; + + +// event capture mechanism for qt_xclb_wait_for_event +static bool waiting_for_data = FALSE; +static bool has_captured_event = FALSE; +static Window capture_event_win = None; +static int capture_event_type = -1; +static XEvent captured_event; + +class TQClipboardWatcher; // forward decl +static TQClipboardWatcher *selection_watcher = 0; +static TQClipboardWatcher *clipboard_watcher = 0; + +static void cleanup() +{ + delete owner; + delete requestor; + owner = 0; + requestor = 0; +} + +static +void setupOwner() +{ + if ( owner ) + return; + owner = new TQWidget( 0, "internal clipboard owner" ); + requestor = new TQWidget(0, "internal clipboard requestor"); + qAddPostRoutine( cleanup ); +} + +static +int sizeof_format(int format) +{ + int sz; + switch (format) { + default: + case 8: sz = sizeof( char); break; + case 16: sz = sizeof(short); break; + case 32: sz = sizeof( long); break; + } + return sz; +} + +class TQClipboardWatcher : public TQMimeSource { +public: + TQClipboardWatcher( TQClipboard::Mode mode ); + ~TQClipboardWatcher(); + bool empty() const; + const char* format( int n ) const; + TQByteArray tqencodedData( const char* fmt ) const; + TQByteArray getDataInFormat(Atom fmtatom) const; + + Atom atom; + TQValueList<const char *> formatList; +}; + + + +class TQClipboardData +{ +public: + TQClipboardData(); + ~TQClipboardData(); + + void setSource(TQMimeSource* s) + { + clear(TRUE); + src = s; + } + + TQMimeSource *source() const { return src; } + + void addTransferredPixmap(TQPixmap pm) + { + /* TODO: queue them */ + transferred[tindex] = pm; + tindex=(tindex+1)%2; + } + void clearTransfers() + { + transferred[0] = TQPixmap(); + transferred[1] = TQPixmap(); + } + + void clear(bool destruct=TRUE); + + TQMimeSource *src; + Time timestamp; + + TQPixmap transferred[2]; + int tindex; +}; + +TQClipboardData::TQClipboardData() +{ + src = 0; + timestamp = CurrentTime; + tindex=0; +} + +TQClipboardData::~TQClipboardData() +{ clear(); } + +void TQClipboardData::clear(bool destruct) +{ + if(destruct) + delete src; + src = 0; + timestamp = CurrentTime; +} + + +static TQClipboardData *internalCbData = 0; +static TQClipboardData *internalSelData = 0; + +static void cleanupClipboardData() +{ + delete internalCbData; + internalCbData = 0; +} + +static TQClipboardData *clipboardData() +{ + if ( internalCbData == 0 ) { + internalCbData = new TQClipboardData; + TQ_CHECK_PTR( internalCbData ); + qAddPostRoutine( cleanupClipboardData ); + } + return internalCbData; +} + +void qt_clipboard_cleanup_mime_source(TQMimeSource *src) +{ + if(internalCbData && internalCbData->source() == src) + internalCbData->clear(FALSE); +} + +static void cleanupSelectionData() +{ + delete internalSelData; + internalSelData = 0; +} + +static TQClipboardData *selectionData() +{ + if (internalSelData == 0) { + internalSelData = new TQClipboardData; + TQ_CHECK_PTR(internalSelData); + qAddPostRoutine(cleanupSelectionData); + } + return internalSelData; +} + +class TQClipboardINCRTransaction +{ +public: + TQClipboardINCRTransaction(Window w, Atom p, Atom t, int f, TQByteArray d, unsigned int i); + ~TQClipboardINCRTransaction(void); + + int x11Event(XEvent *event); + + Window window; + Atom property, target; + int format; + TQByteArray data; + unsigned int increment; + unsigned int offset; +}; + +typedef TQMap<Window,TQClipboardINCRTransaction*> TransactionMap; +static TransactionMap *transactions = 0; +static QX11EventFilter prev_x11_event_filter = 0; +static int incr_timer_id = 0; + +static int qt_xclb_transation_event_handler(XEvent *event) +{ + TransactionMap::Iterator it = transactions->tqfind(event->xany.window); + if (it != transactions->end()) { + if ((*it)->x11Event(event) != 0) + return 1; + } + if (prev_x11_event_filter) + return prev_x11_event_filter(event); + return 0; +} + +/* + called when no INCR activity has happened for 'clipboard_timeout' + milliseconds... we assume that all unfinished transactions have + timed out and remove everything from the transaction map +*/ +static void qt_xclb_incr_timeout(void) +{ + qWarning("TQClipboard: timed out while sending data"); + + while (transactions) + delete *transactions->begin(); +} + +TQClipboardINCRTransaction::TQClipboardINCRTransaction(Window w, Atom p, Atom t, int f, + TQByteArray d, unsigned int i) + : window(w), property(p), target(t), format(f), data(d), increment(i), offset(0u) +{ + TQDEBUG("TQClipboard: sending %d bytes (INCR transaction %p)", d.size(), this); + + XSelectInput(TQPaintDevice::x11AppDisplay(), window, PropertyChangeMask); + + if (! transactions) { + VTQDEBUG("TQClipboard: created INCR transaction map"); + transactions = new TransactionMap; + prev_x11_event_filter = qt_set_x11_event_filter(qt_xclb_transation_event_handler); + + incr_timer_id = TQApplication::clipboard()->startTimer(clipboard_timeout); + } + transactions->insert(window, this); +} + +TQClipboardINCRTransaction::~TQClipboardINCRTransaction(void) +{ + VTQDEBUG("TQClipboard: destroyed INCR transacton %p", this); + + XSelectInput(TQPaintDevice::x11AppDisplay(), window, NoEventMask); + + transactions->remove(window); + if (transactions->isEmpty()) { + VTQDEBUG("TQClipboard: no more INCR transations"); + delete transactions; + transactions = 0; + (void)qt_set_x11_event_filter(prev_x11_event_filter); + + if (incr_timer_id != 0) { + TQApplication::clipboard()->killTimer(incr_timer_id); + incr_timer_id = 0; + } + } +} + +int TQClipboardINCRTransaction::x11Event(XEvent *event) +{ + if (event->type != PropertyNotify + || (event->xproperty.state != PropertyDelete + || event->xproperty.atom != property)) + return 0; + + // restart the INCR timer + if (incr_timer_id) TQApplication::clipboard()->killTimer(incr_timer_id); + incr_timer_id = TQApplication::clipboard()->startTimer(clipboard_timeout); + + unsigned int bytes_left = data.size() - offset; + if (bytes_left > 0) { + unsigned int xfer = TQMIN(increment, bytes_left); + VTQDEBUG("TQClipboard: sending %d bytes, %d remaining (INCR transaction %p)", + xfer, bytes_left - xfer, this); + + XChangeProperty(TQPaintDevice::x11AppDisplay(), window, property, target, format, + PropModeReplace, (uchar *) data.data() + offset, xfer); + offset += xfer; + } else { + // INCR transaction finished... + XChangeProperty(TQPaintDevice::x11AppDisplay(), window, property, target, format, + PropModeReplace, (uchar *) data.data(), 0); + delete this; + } + + return 1; +} + + +/***************************************************************************** + TQClipboard member functions for X11. + *****************************************************************************/ + + +void TQClipboard::clear( Mode mode ) +{ setData(0, mode); } + + +/*! + Returns TRUE if the clipboard supports mouse selection; otherwise + returns FALSE. +*/ +bool TQClipboard::supportsSelection() const +{ return TRUE; } + + +/*! + Returns TRUE if this clipboard object owns the mouse selection + data; otherwise returns FALSE. +*/ +bool TQClipboard::ownsSelection() const +{ return selectionData()->timestamp != CurrentTime; } + +/*! + Returns TRUE if this clipboard object owns the clipboard data; + otherwise returns FALSE. +*/ +bool TQClipboard::ownsClipboard() const +{ return clipboardData()->timestamp != CurrentTime; } + + +/*! \obsolete + + Use the TQClipboard::data(), TQClipboard::setData() and related functions + which take a TQClipboard::Mode argument. + + Sets the clipboard selection mode. If \a enable is TRUE, then + subsequent calls to TQClipboard::setData() and other functions + which put data into the clipboard will put the data into the mouse + selection, otherwise the data will be put into the clipboard. + + \sa supportsSelection(), selectionModeEnabled() +*/ +void TQClipboard::setSelectionMode(bool enable) +{ inSelectionMode_obsolete = enable; } + + +/*! \obsolete + + Use the TQClipboard::data(), TQClipboard::setData() and related functions + which take a TQClipboard::Mode argument. + + Returns the selection mode. + + \sa setSelectionMode(), supportsSelection() +*/ +bool TQClipboard::selectionModeEnabled() const +{ return inSelectionMode_obsolete; } + + +// event filter function... captures interesting events while +// qt_xclb_wait_for_event is running the event loop +static int qt_xclb_event_filter(XEvent *event) +{ + if (event->xany.type == capture_event_type && + event->xany.window == capture_event_win) { + VTQDEBUG( "TQClipboard: event_filter(): caught event type %d", event->type ); + has_captured_event = TRUE; + captured_event = *event; + return 1; + } + + return 0; +} + +static Bool checkForClipboardEvents(Display *, XEvent *e, XPointer) +{ + return ((e->type == SelectionRequest && (e->xselectionrequest.selection == XA_PRIMARY + || e->xselectionrequest.selection == qt_xa_clipboard)) + || (e->type == SelectionClear && (e->xselectionclear.selection == XA_PRIMARY + || e->xselectionclear.selection == qt_xa_clipboard))); +} + +bool qt_xclb_wait_for_event( Display *dpy, Window win, int type, XEvent *event, + int timeout ) +{ + TQTime started = TQTime::currentTime(); + TQTime now = started; + + if (tqApp->eventLoop()->inherits("TQMotif")) { // yes yes, evil hack, we know + if ( waiting_for_data ) + qFatal( "TQClipboard: internal error, qt_xclb_wait_for_event recursed" ); + + waiting_for_data = TRUE; + has_captured_event = FALSE; + capture_event_win = win; + capture_event_type = type; + + QX11EventFilter old_event_filter = qt_set_x11_event_filter(qt_xclb_event_filter); + + do { + if ( XCheckTypedWindowEvent(dpy,win,type,event) ) { + waiting_for_data = FALSE; + qt_set_x11_event_filter(old_event_filter); + return TRUE; + } + + now = TQTime::currentTime(); + if ( started > now ) // crossed midnight + started = now; + + // 0x08 == ExcludeTimers for X11 only + tqApp->eventLoop()->processEvents( TQEventLoop::ExcludeUserInput | + TQEventLoop::ExcludeSocketNotifiers | + TQEventLoop::WaitForMore | 0x08 ); + + if ( has_captured_event ) { + waiting_for_data = FALSE; + *event = captured_event; + qt_set_x11_event_filter(old_event_filter); + return TRUE; + } + } while ( started.msecsTo(now) < timeout ); + + waiting_for_data = FALSE; + qt_set_x11_event_filter(old_event_filter); + + return FALSE; + } + + bool flushed = FALSE; + do { + if ( XCheckTypedWindowEvent(dpy,win,type,event) ) + return TRUE; + + // process other clipboard events, since someone is probably requesting data from us + XEvent e; + if (XCheckIfEvent(dpy, &e, checkForClipboardEvents, 0)) + tqApp->x11ProcessEvent(&e); + + now = TQTime::currentTime(); + if ( started > now ) // crossed midnight + started = now; + + if(!flushed) { + XFlush( dpy ); + flushed = TRUE; + } + + // sleep 50ms, so we don't use up CPU cycles all the time. + struct timeval usleep_tv; + usleep_tv.tv_sec = 0; + usleep_tv.tv_usec = 50000; + select(0, 0, 0, 0, &usleep_tv); + } while ( started.msecsTo(now) < timeout ); + + return FALSE; +} + + +static inline int maxSelectionIncr( Display *dpy ) +{ return XMaxRequestSize(dpy) > 65536 ? 65536*4 : XMaxRequestSize(dpy)*4 - 100; } + +// uglyhack: externed into qt_xdnd.cpp. qt is really not designed for +// single-platform, multi-purpose blocks of code... +bool qt_xclb_read_property( Display *dpy, Window win, Atom property, + bool deleteProperty, + TQByteArray *buffer, int *size, Atom *type, + int *format, bool nullterm ) +{ + int maxsize = maxSelectionIncr(dpy); + ulong bytes_left; // bytes_after + ulong length; // nitems + uchar *data; + Atom dummy_type; + int dummy_format; + int r; + + if ( !type ) // allow null args + type = &dummy_type; + if ( !format ) + format = &dummy_format; + + // Don't read anything, just get the size of the property data + r = XGetWindowProperty( dpy, win, property, 0, 0, False, + AnyPropertyType, type, format, + &length, &bytes_left, &data ); + if (r != Success || (type && *type == None)) { + buffer->resize( 0 ); + return FALSE; + } + XFree( (char*)data ); + + int offset = 0, buffer_offset = 0, format_inc = 1, proplen = bytes_left; + + VTQDEBUG("TQClipboard: read_property(): initial property length: %d", proplen); + + switch (*format) { + case 8: + default: + format_inc = sizeof(char) / 1; + break; + + case 16: + format_inc = sizeof(short) / 2; + proplen *= sizeof(short) / 2; + break; + + case 32: + format_inc = sizeof(long) / 4; + proplen *= sizeof(long) / 4; + break; + } + + bool ok = buffer->tqresize( proplen + (nullterm ? 1 : 0) ); + + VTQDEBUG("TQClipboard: read_property(): buffer resized to %d", buffer->size()); + + if ( ok ) { + // could allocate buffer + + while ( bytes_left ) { + // more to read... + + r = XGetWindowProperty( dpy, win, property, offset, maxsize/4, + False, AnyPropertyType, type, format, + &length, &bytes_left, &data ); + if (r != Success || (type && *type == None)) + break; + + offset += length / (32 / *format); + length *= format_inc * (*format) / 8; + + // Here we check if we get a buffer overflow and tries to + // recover -- this shouldn't normally happen, but it doesn't + // hurt to be defensive + if (buffer_offset + length > buffer->size()) { + length = buffer->size() - buffer_offset; + + // escape loop + bytes_left = 0; + } + + memcpy(buffer->data() + buffer_offset, data, length); + buffer_offset += length; + + XFree( (char*)data ); + } + + static Atom xa_compound_text = *qt_xdnd_str_to_atom( "COMPOUND_TEXT" ); + if ( *format == 8 && *type == xa_compound_text ) { + // convert COMPOUND_TEXT to a multibyte string + XTextProperty textprop; + textprop.encoding = *type; + textprop.format = *format; + textprop.nitems = length; + textprop.value = (unsigned char *) buffer->data(); + + char **list_ret = 0; + int count; + if ( XmbTextPropertyToTextList( dpy, &textprop, &list_ret, + &count ) == Success && + count && list_ret ) { + offset = strlen( list_ret[0] ); + buffer->resize( offset + ( nullterm ? 1 : 0 ) ); + memcpy( buffer->data(), list_ret[0], offset ); + } + if (list_ret) XFreeStringList(list_ret); + } + + // zero-terminate (for text) + if (nullterm) + buffer->tqat(buffer_offset) = '\0'; + } + + // correct size, not 0-term. + if ( size ) + *size = buffer_offset; + + VTQDEBUG("TQClipboard: read_property(): buffer size %d, buffer offset %d, offset %d", + buffer->size(), buffer_offset, offset); + + if ( deleteProperty ) + XDeleteProperty( dpy, win, property ); + + XFlush( dpy ); + + return ok; +} + + +// this is externed into qt_xdnd.cpp too. +TQByteArray qt_xclb_read_incremental_property( Display *dpy, Window win, + Atom property, int nbytes, + bool nullterm ) +{ + XEvent event; + + TQByteArray buf; + TQByteArray tmp_buf; + bool alloc_error = FALSE; + int length; + int offset = 0; + + if ( nbytes > 0 ) { + // Reserve buffer + zero-terminator (for text data) + // We want to complete the INCR transfer even if we cannot + // allocate more memory + alloc_error = !buf.tqresize(nbytes+1); + } + + for (;;) { + XFlush( dpy ); + if ( !qt_xclb_wait_for_event(dpy,win,PropertyNotify,&event,clipboard_timeout) ) + break; + if ( event.xproperty.atom != property || + event.xproperty.state != PropertyNewValue ) + continue; + if ( qt_xclb_read_property(dpy, win, property, TRUE, &tmp_buf, + &length,0, 0, FALSE) ) { + if ( length == 0 ) { // no more data, we're done + if ( nullterm ) { + buf.resize( offset+1 ); + buf.tqat( offset ) = '\0'; + } else { + buf.resize(offset); + } + return buf; + } else if ( !alloc_error ) { + if ( offset+length > (int)buf.size() ) { + if ( !buf.tqresize(offset+length+65535) ) { + alloc_error = TRUE; + length = buf.size() - offset; + } + } + memcpy( buf.data()+offset, tmp_buf.data(), length ); + tmp_buf.resize( 0 ); + offset += length; + } + } else { + break; + } + } + + // timed out ... create a new requestor window, otherwise the requestor + // could consider next request to be still part of this timed out request + delete requestor; + requestor = new TQWidget( 0, "internal clipboard requestor" ); + + return TQByteArray(); +} + +static Atom send_selection(TQClipboardData *d, Atom target, Window window, Atom property, + int format = 0, TQByteArray data = TQByteArray()); + +static Atom send_targets_selection(TQClipboardData *d, Window window, Atom property) +{ + int atoms = 0; + while (d->source()->format(atoms)) atoms++; + if (d->source()->provides("image/ppm")) atoms++; + if (d->source()->provides("image/pbm")) atoms++; + if (d->source()->provides("text/plain")) atoms+=4; + + VTQDEBUG("TQClipboard: send_targets_selection(): %d provided types", atoms); + + // for 64 bit cleanness... XChangeProperty expects long* for data with format == 32 + TQByteArray data((atoms+3) * sizeof(long)); // plus TARGETS, MULTIPLE and TIMESTAMP + long *atarget = (long *) data.data(); + + const char *fmt; + int n = 0; + while ((fmt=d->source()->format(n)) && n < atoms) + atarget[n++] = *qt_xdnd_str_to_atom(fmt); + + static Atom xa_text = *qt_xdnd_str_to_atom("TEXT"); + static Atom xa_compound_text = *qt_xdnd_str_to_atom("COMPOUND_TEXT"); + static Atom xa_targets = *qt_xdnd_str_to_atom("TARGETS"); + static Atom xa_multiple = *qt_xdnd_str_to_atom("MULTIPLE"); + static Atom xa_timestamp = *qt_xdnd_str_to_atom("TIMESTAMP"); + + if (d->source()->provides("image/ppm")) + atarget[n++] = XA_PIXMAP; + if (d->source()->provides("image/pbm")) + atarget[n++] = XA_BITMAP; + if (d->source()->provides("text/plain")) { + atarget[n++] = qt_utf8_string; + atarget[n++] = xa_text; + atarget[n++] = xa_compound_text; + atarget[n++] = XA_STRING; + } + + atarget[n++] = xa_targets; + atarget[n++] = xa_multiple; + atarget[n++] = xa_timestamp; + +#if defined(TQCLIPBOARD_DEBUG_VERBOSE) + for (int index = 0; index < n; index++) { + VTQDEBUG(" atom %d: 0x%lx (%s)", index, atarget[index], + qt_xdnd_atom_to_str(atarget[index])); + } +#endif + + XChangeProperty(TQPaintDevice::x11AppDisplay(), window, property, XA_ATOM, 32, + PropModeReplace, (uchar *) data.data(), n); + return property; +} + +static Atom send_string_selection(TQClipboardData *d, Atom target, Window window, Atom property) +{ + static Atom xa_text = *qt_xdnd_str_to_atom("TEXT"); + static Atom xa_compound_text = *qt_xdnd_str_to_atom("COMPOUND_TEXT"); + + TQDEBUG("TQClipboard: send_string_selection():\n" + " property type %lx\n" + " property name '%s'", + target, qt_xdnd_atom_to_str(target)); + + if (target == xa_text || target == xa_compound_text) { + // the ICCCM states that TEXT and COMPOUND_TEXT are in the + // encoding of choice, so we choose the encoding of the locale + TQByteArray data = d->source()->tqencodedData("text/plain"); + if(data.tqresize(data.size() + 1)) + data[int(data.size() - 1)] = '\0'; + char *list[] = { data.data(), NULL }; + + XICCEncodingStyle style = + (target == xa_compound_text) ? XCompoundTextStyle : XStdICCTextStyle; + XTextProperty textprop; + if (list[0] != NULL + && XmbTextListToTextProperty(TQPaintDevice::x11AppDisplay(), + list, 1, style, &textprop) == Success) { + TQDEBUG(" textprop type %lx\n" + " textprop name '%s'\n" + " format %d\n" + " %ld items", + textprop.encoding, qt_xdnd_atom_to_str(textprop.encoding), + textprop.format, textprop.nitems); + + int sz = sizeof_format(textprop.format); + data.duplicate((const char *) textprop.value, textprop.nitems * sz); + XFree(textprop.value); + + return send_selection(d, textprop.encoding, window, property, textprop.format, data); + } + + return None; + } + + Atom xtarget = None; + const char *fmt = 0; + if (target == XA_STRING + || (target == xa_text && TQTextCodec::codecForLocale()->mibEnum() == 4)) { + // the ICCCM states that STRING is latin1 plus newline and tab + // see section 2.6.2 + fmt = "text/plain;charset=ISO-8859-1"; + xtarget = XA_STRING; + } else if (target == qt_utf8_string) { + // proposed UTF8_STRING conversion type + fmt = "text/plain;charset=UTF-8"; + xtarget = qt_utf8_string; + } + + if (xtarget == None) // should not happen + return None; + + TQByteArray data = d->source()->tqencodedData(fmt); + + TQDEBUG(" format 8\n %d bytes", data.size()); + + return send_selection(d, xtarget, window, property, 8, data); +} + +static Atom send_pixmap_selection(TQClipboardData *d, Atom target, Window window, Atom property) +{ + TQPixmap pm; + + if ( target == XA_PIXMAP ) { + TQByteArray data = d->source()->tqencodedData("image/ppm"); + pm.loadFromData(data); + } else if ( target == XA_BITMAP ) { + TQByteArray data = d->source()->tqencodedData("image/pbm"); + TQImage img; + img.loadFromData(data); + if ( img.depth() != 1 ) + img = img.convertDepth(1); + } + + if (pm.isNull()) // should never happen + return None; + + Pixmap handle = pm.handle(); + XChangeProperty(TQPaintDevice::x11AppDisplay(), window, property, + target, 32, PropModeReplace, (uchar *) &handle, 1); + d->addTransferredPixmap(pm); + return property; + +} + +static Atom send_selection(TQClipboardData *d, Atom target, Window window, Atom property, + int format, TQByteArray data) +{ + if (format == 0) format = 8; + + if (data.isEmpty()) { + const char *fmt = qt_xdnd_atom_to_str(target); + TQDEBUG("TQClipboard: send_selection(): converting to type '%s'", fmt); + if (fmt && !d->source()->provides(fmt)) // Not a MIME type we can produce + return None; + data = d->source()->tqencodedData(fmt); + } + + TQDEBUG("TQClipboard: send_selection():\n" + " property type %lx\n" + " property name '%s'\n" + " format %d\n" + " %d bytes", + target, qt_xdnd_atom_to_str(target), format, data.size()); + + // don't allow INCR transfers when using MULTIPLE or to + // Motif clients (since Motif doesn't support INCR) + static Atom motif_clip_temporary = *qt_xdnd_str_to_atom("CLIP_TEMPORARY"); + bool allow_incr = property != motif_clip_temporary; + + // X_ChangeProperty protocol request is 24 bytes + const unsigned int increment = (XMaxRequestSize(TQPaintDevice::x11AppDisplay()) * 4) - 24; + if (data.size() > increment && allow_incr) { + long bytes = data.size(); + XChangeProperty(TQPaintDevice::x11AppDisplay(), window, property, + qt_x_incr, 32, PropModeReplace, (uchar *) &bytes, 1); + + (void)new TQClipboardINCRTransaction(window, property, target, format, data, increment); + return qt_x_incr; + } + + // make sure we can perform the XChangeProperty in a single request + if (data.size() > increment) + return None; // ### perhaps use several XChangeProperty calls w/ PropModeAppend? + + // use a single request to transfer data + XChangeProperty(TQPaintDevice::x11AppDisplay(), window, property, target, + format, PropModeReplace, (uchar *) data.data(), + data.size() / sizeof_format(format)); + return property; +} + +/*! \internal + Internal cleanup for Windows. +*/ +void TQClipboard::ownerDestroyed() +{ } + + +/*! \internal + Internal optimization for Windows. +*/ +void TQClipboard::connectNotify( const char * ) +{ } + + +/*! \reimp + */ +bool TQClipboard::event( TQEvent *e ) +{ + if ( e->type() == TQEvent::Timer ) { + TQTimerEvent *te = (TQTimerEvent *) e; + + if ( waiting_for_data ) // should never happen + return FALSE; + + if (te->timerId() == timer_id) { + killTimer(timer_id); + timer_id = 0; + + timer_event_clear = TRUE; + if ( selection_watcher ) // clear selection + selectionData()->clear(); + if ( clipboard_watcher ) // clear clipboard + clipboardData()->clear(); + timer_event_clear = FALSE; + + return TRUE; + } else if ( te->timerId() == pending_timer_id ) { + // I hate klipper + killTimer( pending_timer_id ); + pending_timer_id = 0; + + if ( pending_clipboard_changed ) { + pending_clipboard_changed = FALSE; + clipboardData()->clear(); + emit dataChanged(); + } + if ( pending_selection_changed ) { + pending_selection_changed = FALSE; + selectionData()->clear(); + emit selectionChanged(); + } + + return TRUE; + } else if (te->timerId() == incr_timer_id) { + killTimer(incr_timer_id); + incr_timer_id = 0; + + qt_xclb_incr_timeout(); + + return TRUE; + } else { + return TQObject::event( e ); + } + } else if ( e->type() != TQEvent::Clipboard ) { + return TQObject::event( e ); + } + + XEvent *xevent = (XEvent *)(((TQCustomEvent *)e)->data()); + Display *dpy = TQPaintDevice::x11AppDisplay(); + + if ( !xevent ) + return TRUE; + + switch ( xevent->type ) { + + case SelectionClear: + // new selection owner + if (xevent->xselectionclear.selection == XA_PRIMARY) { + TQClipboardData *d = selectionData(); + + // ignore the event if it was generated before we gained selection ownership + if (d->timestamp != CurrentTime && xevent->xselectionclear.time < d->timestamp) + break; + + TQDEBUG("TQClipboard: new selection owner 0x%lx at time %lx (ours %lx)", + XGetSelectionOwner(dpy, XA_PRIMARY), + xevent->xselectionclear.time, d->timestamp); + + if ( ! waiting_for_data ) { + d->clear(); + emit selectionChanged(); + } else { + pending_selection_changed = TRUE; + if ( ! pending_timer_id ) + pending_timer_id = TQApplication::clipboard()->startTimer( 0 ); + } + } else if (xevent->xselectionclear.selection == qt_xa_clipboard) { + TQClipboardData *d = clipboardData(); + + // ignore the event if it was generated before we gained selection ownership + if (d->timestamp != CurrentTime && xevent->xselectionclear.time < d->timestamp) + break; + + TQDEBUG("TQClipboard: new clipboard owner 0x%lx at time %lx (%lx)", + XGetSelectionOwner(dpy, qt_xa_clipboard), + xevent->xselectionclear.time, d->timestamp); + + if ( ! waiting_for_data ) { + d->clear(); + emit dataChanged(); + } else { + pending_clipboard_changed = TRUE; + if ( ! pending_timer_id ) + pending_timer_id = TQApplication::clipboard()->startTimer( 0 ); + } + } else { +#ifdef TQT_CHECK_STATE + qWarning("TQClipboard: Unknown SelectionClear event received."); +#endif + return FALSE; + } + break; + + case SelectionNotify: + /* + Something has delivered data to us, but this was not caught + by TQClipboardWatcher::getDataInFormat() + + Just skip the event to prevent Bad Things (tm) from + happening later on... + */ + break; + + case SelectionRequest: + { + // someone wants our data + XSelectionRequestEvent *req = &xevent->xselectionrequest; + + if (requestor && req->requestor == requestor->winId()) + break; + + XEvent event; + event.xselection.type = SelectionNotify; + event.xselection.display = req->display; + event.xselection.requestor = req->requestor; + event.xselection.selection = req->selection; + event.xselection.target = req->target; + event.xselection.property = None; + event.xselection.time = req->time; + + TQDEBUG("TQClipboard: SelectionRequest from %lx\n" + " selection 0x%lx (%s) target 0x%lx (%s)", + req->requestor, + req->selection, + qt_xdnd_atom_to_str(req->selection), + req->target, + qt_xdnd_atom_to_str(req->target)); + + TQClipboardData *d; + + if ( req->selection == XA_PRIMARY ) { + d = selectionData(); + } else if ( req->selection == qt_xa_clipboard ) { + d = clipboardData(); + } else { +#ifdef TQT_CHECK_RANGE + qWarning("TQClipboard: unknown selection '%lx'", req->selection); +#endif // TQT_CHECK_RANGE + + XSendEvent(dpy, req->requestor, False, NoEventMask, &event); + break; + } + + if (! d->source()) { +#ifdef TQT_CHECK_STATE + qWarning("TQClipboard: cannot transfer data, no data available"); +#endif // TQT_CHECK_STATE + + XSendEvent(dpy, req->requestor, False, NoEventMask, &event); + break; + } + + TQDEBUG("TQClipboard: SelectionRequest at time %lx (ours %lx)", + req->time, d->timestamp); + + if (d->timestamp == CurrentTime // we don't own the selection anymore + || (req->time != CurrentTime && req->time < d->timestamp)) { + TQDEBUG("TQClipboard: SelectionRequest too old"); + XSendEvent(dpy, req->requestor, False, NoEventMask, &event); + break; + } + + static Atom xa_text = *qt_xdnd_str_to_atom("TEXT"); + static Atom xa_compound_text = *qt_xdnd_str_to_atom("COMPOUND_TEXT"); + static Atom xa_targets = *qt_xdnd_str_to_atom("TARGETS"); + static Atom xa_multiple = *qt_xdnd_str_to_atom("MULTIPLE"); + static Atom xa_timestamp = *qt_xdnd_str_to_atom("TIMESTAMP"); + + struct AtomPair { Atom target; Atom property; } *multi = 0; + Atom multi_type = None; + int multi_format = 0; + int nmulti = 0; + int imulti = -1; + bool multi_writeback = FALSE; + + if ( req->target == xa_multiple ) { + TQByteArray multi_data; + if (req->property == None + || !qt_xclb_read_property(dpy, req->requestor, req->property, + FALSE, &multi_data, 0, &multi_type, &multi_format, 0) + || multi_format != 32) { + // MULTIPLE property not formatted correctly + XSendEvent(dpy, req->requestor, False, NoEventMask, &event); + break; + } + nmulti = multi_data.size()/sizeof(*multi); + multi = new AtomPair[nmulti]; + memcpy(multi,multi_data.data(),multi_data.size()); + imulti = 0; + } + + for (; imulti < nmulti; ++imulti) { + Atom target; + Atom property; + + if ( multi ) { + target = multi[imulti].target; + property = multi[imulti].property; + } else { + target = req->target; + property = req->property; + if (property == None) // obsolete client + property = target; + } + + Atom ret = None; + if (target == None || property == None) { + ; + } else if (target == xa_timestamp) { + if (d->timestamp != CurrentTime) { + XChangeProperty(dpy, req->requestor, property, xa_timestamp, 32, + PropModeReplace, (uchar *) &d->timestamp, 1); + ret = property; + } else { + +#ifdef TQT_CHECK_STATE + qWarning("TQClipboard: invalid data timestamp"); +#endif // TQT_CHECK_STATE + } + } else if (target == xa_targets) { + ret = send_targets_selection(d, req->requestor, property); + } else if (target == XA_STRING + || target == xa_text + || target == xa_compound_text + || target == qt_utf8_string) { + ret = send_string_selection(d, target, req->requestor, property); + } else if (target == XA_PIXMAP + || target == XA_BITMAP) { + ret = send_pixmap_selection(d, target, req->requestor, property); + } else { + ret = send_selection(d, target, req->requestor, property); + } + + if (nmulti > 0) { + if (ret == None) { + multi[imulti].property = None; + multi_writeback = TRUE; + } + } else { + event.xselection.property = ret; + break; + } + } + + if (nmulti > 0) { + if (multi_writeback) { + // according to ICCCM 2.6.2 says to put None back + // into the original property on the requestor window + XChangeProperty(dpy, req->requestor, req->property, multi_type, 32, + PropModeReplace, (uchar *) multi, nmulti * 2); + } + + delete [] multi; + event.xselection.property = req->property; + } + + // send selection notify to requestor + XSendEvent(dpy, req->requestor, False, NoEventMask, &event); + + TQDEBUG("TQClipboard: SelectionNotify to 0x%lx\n" + " property 0x%lx (%s)", + req->requestor, event.xselection.property, + qt_xdnd_atom_to_str(event.xselection.property)); + } + break; + } + + return TRUE; +} + + + + + + +TQClipboardWatcher::TQClipboardWatcher( TQClipboard::Mode mode ) +{ + switch ( mode ) { + case TQClipboard::Selection: + atom = XA_PRIMARY; + break; + + case TQClipboard::Clipboard: + atom = qt_xa_clipboard; + break; + +#ifdef TQT_CHECK_RANGE + default: + qWarning( "TQClipboardWatcher: internal error, unknown clipboard mode" ); + break; +#endif // TQT_CHECK_RANGE + } + + setupOwner(); +} + +TQClipboardWatcher::~TQClipboardWatcher() +{ + if( selection_watcher == this ) + selection_watcher = 0; + if( clipboard_watcher == this ) + clipboard_watcher = 0; +} + +bool TQClipboardWatcher::empty() const +{ + Display *dpy = TQPaintDevice::x11AppDisplay(); + Window win = XGetSelectionOwner( dpy, atom ); + +#ifdef TQT_CHECK_STATE + if( win == requestor->winId()) { + qWarning( "TQClipboardWatcher::empty: internal error, app owns the selection" ); + return TRUE; + } +#endif // TQT_CHECK_STATE + + return win == None; +} + +const char* TQClipboardWatcher::format( int n ) const +{ + if ( empty() ) + return 0; + + if (! formatList.count()) { + // get the list of targets from the current clipboard owner - we do this + // once so that multiple calls to this function don't require multiple + // server round trips... + static Atom xa_targets = *qt_xdnd_str_to_atom( "TARGETS" ); + + TQClipboardWatcher *that = (TQClipboardWatcher *) this; + TQByteArray ba = getDataInFormat(xa_targets); + if (ba.size() > 0) { + Atom *unsorted_target = (Atom *) ba.data(); + static Atom xa_text = *qt_xdnd_str_to_atom( "TEXT" ); + static Atom xa_compound_text = *qt_xdnd_str_to_atom( "COMPOUND_TEXT" ); + int i, size = ba.size() / sizeof(Atom); + + // sort TARGETS to prefer some types over others. some apps + // will report XA_STRING before COMPOUND_TEXT, and we want the + // latter, not the former (if it is present). + Atom* target = new Atom[size+4]; + memset( target, 0, (size+4) * sizeof(Atom) ); + + for ( i = 0; i < size; ++i ) { + if ( unsorted_target[i] == qt_utf8_string ) + target[0] = unsorted_target[i]; + else if ( unsorted_target[i] == xa_compound_text ) + target[1] = unsorted_target[i]; + else if ( unsorted_target[i] == xa_text ) + target[2] = unsorted_target[i]; + else if ( unsorted_target[i] == XA_STRING ) + target[3] = unsorted_target[i]; + else + target[i + 4] = unsorted_target[i]; + } + + for (i = 0; i < size + 4; ++i) { + if ( target[i] == 0 ) continue; + + VTQDEBUG(" format: %s", qt_xdnd_atom_to_str(target[i])); + + if ( target[i] == XA_PIXMAP ) + that->formatList.append("image/ppm"); + else if ( target[i] == XA_STRING ) + that->formatList.append( "text/plain;charset=ISO-8859-1" ); + else if ( target[i] == qt_utf8_string ) + that->formatList.append( "text/plain;charset=UTF-8" ); + else if ( target[i] == xa_text || + target[i] == xa_compound_text ) + that->formatList.append( "text/plain" ); + else + that->formatList.append(qt_xdnd_atom_to_str(target[i])); + } + delete []target; + + TQDEBUG("TQClipboardWatcher::format: %d formats available", + int(that->formatList.count())); + } + } + + if (n >= 0 && n < (signed) formatList.count()) + return formatList[n]; + if (n == 0) + return "text/plain"; + return 0; +} + +TQByteArray TQClipboardWatcher::tqencodedData( const char* fmt ) const +{ + if ( !fmt || empty() ) + return TQByteArray( 0 ); + + TQDEBUG("TQClipboardWatcher::tqencodedData: fetching format '%s'", fmt); + + Atom fmtatom = 0; + + if ( 0==qstricmp(fmt,"text/plain;charset=iso-8859-1") ) { + // ICCCM section 2.6.2 says STRING is latin1 text + fmtatom = XA_STRING; + } else if ( 0==qstricmp(fmt,"text/plain;charset=utf-8") ) { + // proprosed UTF8_STRING conversion type + fmtatom = *qt_xdnd_str_to_atom( "UTF8_STRING" ); + } else if ( 0==qstrcmp(fmt,"text/plain") ) { + fmtatom = *qt_xdnd_str_to_atom( "COMPOUND_TEXT" ); + } else if ( 0==qstrcmp(fmt,"image/ppm") ) { + fmtatom = XA_PIXMAP; + TQByteArray pmd = getDataInFormat(fmtatom); + if ( pmd.size() == sizeof(Pixmap) ) { + Pixmap xpm = *((Pixmap*)pmd.data()); + Display *dpy = TQPaintDevice::x11AppDisplay(); + Window r; + int x,y; + uint w,h,bw,d; + if ( ! xpm ) + return TQByteArray( 0 ); + XGetGeometry(dpy,xpm, &r,&x,&y,&w,&h,&bw,&d); + TQImageIO iio; + GC gc = XCreateGC( dpy, xpm, 0, 0 ); + if ( d == 1 ) { + TQBitmap qbm(w,h); + XCopyArea(dpy,xpm,qbm.handle(),gc,0,0,w,h,0,0); + iio.setFormat("PBMRAW"); + iio.setImage(qbm.convertToImage()); + } else { + TQPixmap qpm(w,h); + XCopyArea(dpy,xpm,qpm.handle(),gc,0,0,w,h,0,0); + iio.setFormat("PPMRAW"); + iio.setImage(qpm.convertToImage()); + } + XFreeGC(dpy,gc); + TQBuffer buf; + buf.open(IO_WriteOnly); + iio.setIODevice(TQT_TQIODEVICE(&buf)); + iio.write(); + return buf.buffer(); + } else { + fmtatom = *qt_xdnd_str_to_atom(fmt); + } + } else { + fmtatom = *qt_xdnd_str_to_atom(fmt); + } + return getDataInFormat(fmtatom); +} + +TQByteArray TQClipboardWatcher::getDataInFormat(Atom fmtatom) const +{ + TQByteArray buf; + + Display *dpy = TQPaintDevice::x11AppDisplay(); + Window win = requestor->winId(); + + TQDEBUG("TQClipboardWatcher::getDataInFormat: selection '%s' format '%s'", + qt_xdnd_atom_to_str(atom), qt_xdnd_atom_to_str(fmtatom)); + + XSelectInput(dpy, win, NoEventMask); // don't listen for any events + + XDeleteProperty(dpy, win, qt_selection_property); + XConvertSelection(dpy, atom, fmtatom, qt_selection_property, win, GET_QT_X_TIME()); + XSync(dpy, FALSE); + + VTQDEBUG("TQClipboardWatcher::getDataInFormat: waiting for SelectionNotify event"); + + XEvent xevent; + if ( !qt_xclb_wait_for_event(dpy,win,SelectionNotify,&xevent,clipboard_timeout) || + xevent.xselection.property == None ) { + TQDEBUG("TQClipboardWatcher::getDataInFormat: format not available"); + return buf; + } + + VTQDEBUG("TQClipboardWatcher::getDataInFormat: fetching data..."); + + Atom type; + XSelectInput(dpy, win, PropertyChangeMask); + + if ( qt_xclb_read_property(dpy,win,qt_selection_property,TRUE, + &buf,0,&type,0,FALSE) ) { + if ( type == qt_x_incr ) { + int nbytes = buf.size() >= 4 ? *((int*)buf.data()) : 0; + buf = qt_xclb_read_incremental_property( dpy, win, + qt_selection_property, + nbytes, FALSE ); + } + } + + XSelectInput(dpy, win, NoEventMask); + + TQDEBUG("TQClipboardWatcher::getDataInFormat: %d bytes received", buf.size()); + + return buf; +} + + +TQMimeSource* TQClipboard::data( Mode mode ) const +{ + TQClipboardData *d; + switch ( mode ) { + case Selection: d = selectionData(); break; + case Clipboard: d = clipboardData(); break; + + default: +#ifdef TQT_CHECK_RANGE + qWarning( "TQClipboard::data: invalid mode '%d'", mode ); +#endif // TQT_CHECK_RANGE + return 0; + } + + if ( ! d->source() && ! timer_event_clear ) { + if ( mode == Selection ) { + if ( ! selection_watcher ) + selection_watcher = new TQClipboardWatcher( mode ); + d->setSource( selection_watcher ); + } else { + if ( ! clipboard_watcher ) + clipboard_watcher = new TQClipboardWatcher( mode ); + d->setSource( clipboard_watcher ); + } + + if (! timer_id) { + // start a zero timer - we will clear cached data when the timer + // times out, which will be the next time we hit the event loop... + // that way, the data is cached long enough for calls within a single + // loop/function, but the data doesn't linger around in case the selection + // changes + TQClipboard *that = ((TQClipboard *) this); + timer_id = that->startTimer(0); + } + } + + return d->source(); +} + + +void TQClipboard::setData( TQMimeSource* src, Mode mode ) +{ + Atom atom, sentinel_atom; + TQClipboardData *d; + switch ( mode ) { + case Selection: + atom = XA_PRIMARY; + sentinel_atom = qt_selection_sentinel; + d = selectionData(); + break; + + case Clipboard: + atom = qt_xa_clipboard; + sentinel_atom = qt_clipboard_sentinel; + d = clipboardData(); + break; + + default: +#ifdef TQT_CHECK_RANGE + qWarning( "TQClipboard::data: invalid mode '%d'", mode ); +#endif // TQT_CHECK_RANGE + return; + } + + Display *dpy = TQPaintDevice::x11AppDisplay(); + Window newOwner; + + if (! src) { // no data, clear clipboard contents + newOwner = None; + d->clear(); + } else { + setupOwner(); + + newOwner = owner->winId(); + + d->setSource(src); + d->timestamp = GET_QT_X_TIME(); + } + + Window prevOwner = XGetSelectionOwner( dpy, atom ); + // use qt_x_time, since d->timestamp == CurrentTime when clearing + XSetSelectionOwner(dpy, atom, newOwner, GET_QT_X_TIME()); + + if ( mode == Selection ) + emit selectionChanged(); + else + emit dataChanged(); + + if (XGetSelectionOwner(dpy, atom) != newOwner) { +#ifdef TQT_CHECK_STATE + qWarning("TQClipboard::setData: Cannot set X11 selection owner for %s", + qt_xdnd_atom_to_str(atom)); +#endif // TQT_CHECK_STATE + + d->clear(); + return; + } + + // Signal to other TQt processes that the selection has changed + Window owners[2]; + owners[0] = newOwner; + owners[1] = prevOwner; + XChangeProperty( dpy, TQApplication::desktop()->screen(0)->winId(), + sentinel_atom, XA_WINDOW, 32, PropModeReplace, + (unsigned char*)&owners, 2 ); +} + + +/* + Called by the main event loop in qapplication_x11.cpp when the + _TQT_SELECTION_SENTINEL property has been changed (i.e. when some TQt + process has performed TQClipboard::setData(). If it returns TRUE, the + TQClipBoard dataChanged() signal should be emitted. +*/ + +bool qt_check_selection_sentinel() +{ + bool doIt = TRUE; + if ( owner ) { + /* + Since the X selection mechanism cannot give any signal when + the selection has changed, we emulate it (for TQt processes) here. + The notification should be ignored in case of either + a) This is the process that did setData (because setData() + then has already emitted dataChanged()) + b) This is the process that owned the selection when dataChanged() + was called (because we have then received a SelectionClear event, + and have already emitted dataChanged() as a result of that) + */ + + Window* owners; + Atom actualType; + int actualFormat; + ulong nitems; + ulong bytesLeft; + + if (XGetWindowProperty(TQPaintDevice::x11AppDisplay(), + TQApplication::desktop()->screen(0)->winId(), + qt_selection_sentinel, 0, 2, False, XA_WINDOW, + &actualType, &actualFormat, &nitems, + &bytesLeft, (unsigned char**)&owners) == Success) { + if ( actualType == XA_WINDOW && actualFormat == 32 && nitems == 2 ) { + Window win = owner->winId(); + if ( owners[0] == win || owners[1] == win ) + doIt = FALSE; + } + + XFree( owners ); + } + } + + if (doIt) { + if ( waiting_for_data ) { + pending_selection_changed = TRUE; + if ( ! pending_timer_id ) + pending_timer_id = TQApplication::clipboard()->startTimer( 0 ); + doIt = FALSE; + } else { + selectionData()->clear(); + } + } + + return doIt; +} + + +bool qt_check_clipboard_sentinel() +{ + bool doIt = TRUE; + if (owner) { + Window *owners; + Atom actualType; + int actualFormat; + unsigned long nitems, bytesLeft; + + if (XGetWindowProperty(TQPaintDevice::x11AppDisplay(), + TQApplication::desktop()->screen(0)->winId(), + qt_clipboard_sentinel, 0, 2, False, XA_WINDOW, + &actualType, &actualFormat, &nitems, &bytesLeft, + (unsigned char **) &owners) == Success) { + if (actualType == XA_WINDOW && actualFormat == 32 && nitems == 2) { + Window win = owner->winId(); + if (owners[0] == win || owners[1] == win) + doIt = FALSE; + } + + XFree(owners); + } + } + + if (doIt) { + if ( waiting_for_data ) { + pending_clipboard_changed = TRUE; + if ( ! pending_timer_id ) + pending_timer_id = TQApplication::clipboard()->startTimer( 0 ); + doIt = FALSE; + } else { + clipboardData()->clear(); + } + } + + return doIt; +} + +#endif // TQT_NO_CLIPBOARD + +#endif // USE_QT4 |