diff options
Diffstat (limited to 'klipper/clipboardpoll.cpp')
-rw-r--r-- | klipper/clipboardpoll.cpp | 336 |
1 files changed, 336 insertions, 0 deletions
diff --git a/klipper/clipboardpoll.cpp b/klipper/clipboardpoll.cpp new file mode 100644 index 000000000..20157a1a2 --- /dev/null +++ b/klipper/clipboardpoll.cpp @@ -0,0 +1,336 @@ +// -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*- +/* This file is part of the KDE project + + Copyright (C) 2003 by Lubos Lunak <[email protected]> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#include "config.h" + +#include "clipboardpoll.h" + +#include <kapplication.h> +#include <qclipboard.h> +#include <kdebug.h> +#include <X11/Xatom.h> +#include <time.h> + +#ifdef HAVE_XFIXES +#include <X11/extensions/Xfixes.h> +#endif + +#include "toplevel.h" + +//#define NOISY_KLIPPER_ + +/* + + The polling magic: + + There's no way with X11 how to find out if the selection has changed (unless its ownership + is taken away from the current client). In the future, there will be hopefully such notification, + which will make this whole file more or less obsolete. But for now, Klipper has to poll. + In order to avoid transferring all the data on every time pulse, this file implements two + optimizations: The first one is checking whether the selection owner is Qt application (using + the _QT_SELECTION/CLIPBOARD_SENTINEL atoms on the root window of screen 0), and if yes, + Klipper can rely on QClipboard's signals. If the owner is not Qt app, and the ownership has changed, + it means the selection has changed as well. Otherwise, first only the timestamp + of the last selection change is requested using the TIMESTAMP selection target, and if it's + the same, it's assumed the contents haven't changed. Note that some applications (like XEmacs) does + not provide this information, so Klipper has to assume that the clipboard might have changed in this + case --- this is what is meant by REFUSED below. + + Update: Now there's also support for XFixes, so in case XFixes support is detected, only XFixes is + used for detecting changes, everything else is ignored, even Qt's clipboard signals. + +*/ + +extern Time qt_x_time; + +ClipboardPoll::ClipboardPoll( QWidget* parent ) + : QWidget( parent ) + , xfixes_event_base( -1 ) +{ + hide(); + const char* names[ 6 ] + = { "_QT_SELECTION_SENTINEL", + "_QT_CLIPBOARD_SENTINEL", + "CLIPBOARD", + "TIMESTAMP", + "KLIPPER_SELECTION_TIMESTAMP", + "KLIPPER_CLIPBOARD_TIMESTAMP" }; + Atom atoms[ 6 ]; + XInternAtoms( qt_xdisplay(), const_cast< char** >( names ), 6, False, atoms ); + selection.sentinel_atom = atoms[ 0 ]; + clipboard.sentinel_atom = atoms[ 1 ]; + xa_clipboard = atoms[ 2 ]; + xa_timestamp = atoms[ 3 ]; + selection.timestamp_atom = atoms[ 4 ]; + clipboard.timestamp_atom = atoms[ 5 ]; + bool use_polling = true; + kapp->installX11EventFilter( this ); +#ifdef HAVE_XFIXES + int dummy; + if( XFixesQueryExtension( qt_xdisplay(), &xfixes_event_base, &dummy )) + { + XFixesSelectSelectionInput( qt_xdisplay(), qt_xrootwin( 0 ), XA_PRIMARY, + XFixesSetSelectionOwnerNotifyMask | + XFixesSelectionWindowDestroyNotifyMask | + XFixesSelectionClientCloseNotifyMask ); + XFixesSelectSelectionInput( qt_xdisplay(), qt_xrootwin( 0 ), xa_clipboard, + XFixesSetSelectionOwnerNotifyMask | + XFixesSelectionWindowDestroyNotifyMask | + XFixesSelectionClientCloseNotifyMask ); + use_polling = false; +#ifdef NOISY_KLIPPER_ + kdDebug() << "Using XFIXES" << endl; +#endif + } +#endif + if( use_polling ) + { +#ifdef NOISY_KLIPPER_ + kdDebug() << "Using polling" << endl; +#endif + initPolling(); + } +} + +void ClipboardPoll::initPolling() +{ + connect( kapp->clipboard(), SIGNAL( selectionChanged() ), SLOT(qtSelectionChanged())); + connect( kapp->clipboard(), SIGNAL( dataChanged() ), SLOT( qtClipboardChanged() )); + connect( &timer, SIGNAL( timeout()), SLOT( timeout())); + timer.start( 1000, false ); + selection.atom = XA_PRIMARY; + clipboard.atom = xa_clipboard; + selection.last_change = clipboard.last_change = qt_x_time; // don't trigger right after startup + selection.last_owner = XGetSelectionOwner( qt_xdisplay(), XA_PRIMARY ); +#ifdef NOISY_KLIPPER_ + kdDebug() << "(1) Setting last_owner for =" << "selection" << ":" << selection.last_owner << endl; +#endif + clipboard.last_owner = XGetSelectionOwner( qt_xdisplay(), xa_clipboard ); +#ifdef NOISY_KLIPPER_ + kdDebug() << "(2) Setting last_owner for =" << "clipboard" << ":" << clipboard.last_owner << endl; +#endif + selection.waiting_for_timestamp = false; + clipboard.waiting_for_timestamp = false; + updateQtOwnership( selection ); + updateQtOwnership( clipboard ); +} + +void ClipboardPoll::qtSelectionChanged() +{ + emit clipboardChanged( true ); +} + +void ClipboardPoll::qtClipboardChanged() +{ + emit clipboardChanged( false ); +} + +bool ClipboardPoll::x11Event( XEvent* e ) +{ +// note that this is also installed as app-wide filter +#ifdef HAVE_XFIXES + if( xfixes_event_base != -1 && e->type == xfixes_event_base + XFixesSelectionNotify ) + { + XFixesSelectionNotifyEvent* ev = reinterpret_cast< XFixesSelectionNotifyEvent* >( e ); + if( ev->selection == XA_PRIMARY && !kapp->clipboard()->ownsSelection()) + { +#ifdef NOISY_KLIPPER_ + kdDebug() << "SELECTION CHANGED (XFIXES)" << endl; +#endif + qt_x_time = ev->timestamp; + emit clipboardChanged( true ); + } + else if( ev->selection == xa_clipboard && !kapp->clipboard()->ownsClipboard()) + { +#ifdef NOISY_KLIPPER_ + kdDebug() << "CLIPBOARD CHANGED (XFIXES)" << endl; +#endif + qt_x_time = ev->timestamp; + emit clipboardChanged( false ); + } + } +#endif + if( e->type == SelectionNotify && e->xselection.requestor == winId()) + { + if( changedTimestamp( selection, *e ) ) { +#ifdef NOISY_KLIPPER_ + kdDebug() << "SELECTION CHANGED (GOT TIMESTAMP)" << endl; +#endif + emit clipboardChanged( true ); + } + + if ( changedTimestamp( clipboard, *e ) ) + { +#ifdef NOISY_KLIPPER_ + kdDebug() << "CLIPBOARD CHANGED (GOT TIMESTAMP)" << endl; +#endif + emit clipboardChanged( false ); + } + return true; // filter out + } + return false; +} + +void ClipboardPoll::updateQtOwnership( SelectionData& data ) +{ + Atom type; + int format; + unsigned long nitems; + unsigned long after; + unsigned char* prop = NULL; + if( XGetWindowProperty( qt_xdisplay(), qt_xrootwin( 0 ), data.sentinel_atom, 0, 2, False, + XA_WINDOW, &type, &format, &nitems, &after, &prop ) != Success + || type != XA_WINDOW || format != 32 || nitems != 2 || prop == NULL ) + { +#ifdef REALLY_NOISY_KLIPPER_ + kdDebug() << "UPDATEQT BAD PROPERTY" << endl; +#endif + data.owner_is_qt = false; + if( prop != NULL ) + XFree( prop ); + return; + } + Window owner = reinterpret_cast< long* >( prop )[ 0 ]; // [0] is new owner, [1] is previous + XFree( prop ); + Window current_owner = XGetSelectionOwner( qt_xdisplay(), data.atom ); + data.owner_is_qt = ( owner == current_owner ); +#ifdef REALLY_NOISY_KLIPPER_ + kdDebug() << "owner=" << owner << "; current_owner=" << current_owner << endl; + kdDebug() << "UPDATEQT:" << ( &data == &selection ? "selection" : "clipboard" ) << ":" << data.owner_is_qt << endl; +#endif +} + +void ClipboardPoll::timeout() +{ + KlipperWidget::updateTimestamp(); + if( !kapp->clipboard()->ownsSelection() && checkTimestamp( selection ) ) { +#ifdef NOISY_KLIPPER_ + kdDebug() << "SELECTION CHANGED" << endl; +#endif + emit clipboardChanged( true ); + } + if( !kapp->clipboard()->ownsClipboard() && checkTimestamp( clipboard ) ) { +#ifdef NOISY_KLIPPER_ + kdDebug() << "CLIPBOARD CHANGED" << endl; +#endif + emit clipboardChanged( false ); + } + +} + +bool ClipboardPoll::checkTimestamp( SelectionData& data ) +{ + Window current_owner = XGetSelectionOwner( qt_xdisplay(), data.atom ); + bool signal = false; + updateQtOwnership( data ); + if( data.owner_is_qt ) + { + data.last_change = CurrentTime; +#ifdef REALLY_NOISY_KLIPPER_ + kdDebug() << "(3) Setting last_owner for =" << ( &data==&selection ?"selection":"clipboard" ) << ":" << current_owner << endl; +#endif + data.last_owner = current_owner; + data.waiting_for_timestamp = false; + return false; + } + if( current_owner != data.last_owner ) + { + signal = true; // owner has changed + data.last_owner = current_owner; +#ifdef REALLY_NOISY_KLIPPER_ + kdDebug() << "(4) Setting last_owner for =" << ( &data==&selection ?"selection":"clipboard" ) << ":" << current_owner << endl; +#endif + data.waiting_for_timestamp = false; + data.last_change = CurrentTime; +#ifdef REALLY_NOISY_KLIPPER_ + kdDebug() << "OWNER CHANGE:" << ( data.atom == XA_PRIMARY ) << ":" << current_owner << endl; +#endif + return true; + } + if( current_owner == None ) { + return false; // None also last_owner... + } + if( data.waiting_for_timestamp ) { + // We're already waiting for the timestamp of the last check + return false; + } + XDeleteProperty( qt_xdisplay(), winId(), data.timestamp_atom ); + XConvertSelection( qt_xdisplay(), data.atom, xa_timestamp, data.timestamp_atom, winId(), qt_x_time ); + data.waiting_for_timestamp = true; + data.waiting_x_time = qt_x_time; +#ifdef REALLY_NOISY_KLIPPER_ + kdDebug() << "WAITING TIMESTAMP:" << ( data.atom == XA_PRIMARY ) << endl; +#endif + return false; +} + +bool ClipboardPoll::changedTimestamp( SelectionData& data, const XEvent& ev ) +{ + if( ev.xselection.requestor != winId() + || ev.xselection.selection != data.atom + || ev.xselection.time != data.waiting_x_time ) + { + return false; + } + data.waiting_for_timestamp = false; + if( ev.xselection.property == None ) + { +#ifdef NOISY_KLIPPER_ + kdDebug() << "REFUSED:" << ( data.atom == XA_PRIMARY ) << endl; +#endif + return true; + } + Atom type; + int format; + unsigned long nitems; + unsigned long after; + unsigned char* prop = NULL; + if( XGetWindowProperty( qt_xdisplay(), winId(), ev.xselection.property, 0, 1, False, + AnyPropertyType, &type, &format, &nitems, &after, &prop ) != Success + || format != 32 || nitems != 1 || prop == NULL ) + { +#ifdef NOISY_KLIPPER_ + kdDebug() << "BAD PROPERTY:" << ( data.atom == XA_PRIMARY ) << endl; +#endif + if( prop != NULL ) + XFree( prop ); + return true; + } + Time timestamp = reinterpret_cast< long* >( prop )[ 0 ]; + XFree( prop ); +#ifdef NOISY_KLIPPER_ + kdDebug() << "GOT TIMESTAMP:" << ( data.atom == XA_PRIMARY ) << endl; + kdDebug() << "timestamp=" << timestamp + << "; CurrentTime=" << CurrentTime + << "; last_change=" << data.last_change + << endl; +#endif + if( timestamp != data.last_change || timestamp == CurrentTime ) + { +#ifdef NOISY_KLIPPER_ + kdDebug() << "TIMESTAMP CHANGE:" << ( data.atom == XA_PRIMARY ) << endl; +#endif + data.last_change = timestamp; + return true; + } + return false; // ok, same timestamp +} + +#include "clipboardpoll.moc" |