not quite so much needs to be delayed to the init() function
[personal-kdebase.git] / workspace / klipper / clipboardpoll.cpp
blob0a71eb1e4a4e97551a28a19028a67b0a1d240a4d
1 // -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*-
2 /* This file is part of the KDE project
4 Copyright (C) 2003 by Lubos Lunak <l.lunak@kde.org>
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public
8 License as published by the Free Software Foundation; either
9 version 2 of the License, or (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; see the file COPYING. If not, write to
18 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 Boston, MA 02110-1301, USA.
22 #include <config-workspace.h>
23 #include <config-X11.h>
25 #include "clipboardpoll.h"
27 #include <kapplication.h>
28 #include <QClipboard>
29 #include <kdebug.h>
30 #include <X11/Xatom.h>
31 #include <time.h>
32 #include <QX11Info>
34 #ifdef HAVE_XFIXES
35 #include <X11/extensions/Xfixes.h>
36 #endif
38 #include "klipper.h"
40 //#define NOISY_KLIPPER_
45 The polling magic:
47 There's no way with X11 how to find out if the selection has changed (unless its ownership
48 is taken away from the current client). In the future, there will be hopefully such notification,
49 which will make this whole file more or less obsolete. But for now, Klipper has to poll.
50 In order to avoid transferring all the data on every time pulse, this file implements two
51 optimizations: The first one is checking whether the selection owner is Qt application (using
52 the _QT_SELECTION/CLIPBOARD_SENTINEL atoms on the root window of screen 0), and if yes,
53 Klipper can rely on QClipboard's signals. If the owner is not Qt app, and the ownership has changed,
54 it means the selection has changed as well. Otherwise, first only the timestamp
55 of the last selection change is requested using the TIMESTAMP selection target, and if it's
56 the same, it's assumed the contents haven't changed. Note that some applications (like XEmacs) does
57 not provide this information, so Klipper has to assume that the clipboard might have changed in this
58 case --- this is what is meant by REFUSED below.
60 Update: Now there's also support for XFixes, so in case XFixes support is detected, only XFixes is
61 used for detecting changes, everything else is ignored, even Qt's clipboard signals.
65 ClipboardPoll::ClipboardPoll()
66 : m_xfixes_event_base( -1 )
68 hide();
69 const char* names[ 6 ]
70 = { "_QT_SELECTION_SENTINEL",
71 "_QT_CLIPBOARD_SENTINEL",
72 "CLIPBOARD",
73 "TIMESTAMP",
74 "KLIPPER_SELECTION_TIMESTAMP",
75 "KLIPPER_CLIPBOARD_TIMESTAMP" };
76 Atom atoms[ 6 ];
77 XInternAtoms( QX11Info::display(), const_cast< char** >( names ), 6, False, atoms );
78 m_selection.sentinel_atom = atoms[ 0 ];
79 m_clipboard.sentinel_atom = atoms[ 1 ];
80 m_xa_clipboard = atoms[ 2 ];
81 m_xa_timestamp = atoms[ 3 ];
82 m_selection.timestamp_atom = atoms[ 4 ];
83 m_clipboard.timestamp_atom = atoms[ 5 ];
84 bool use_polling = true;
85 kapp->installX11EventFilter( this );
86 m_timer.setSingleShot( false );
87 #ifdef HAVE_XFIXES
88 int dummy;
89 if( XFixesQueryExtension( QX11Info::display(), &m_xfixes_event_base, &dummy ))
91 XFixesSelectSelectionInput( QX11Info::display(), QX11Info::appRootWindow( 0 ), XA_PRIMARY,
92 XFixesSetSelectionOwnerNotifyMask |
93 XFixesSelectionWindowDestroyNotifyMask |
94 XFixesSelectionClientCloseNotifyMask );
95 XFixesSelectSelectionInput( QX11Info::display(), QX11Info::appRootWindow( 0 ), m_xa_clipboard,
96 XFixesSetSelectionOwnerNotifyMask |
97 XFixesSelectionWindowDestroyNotifyMask |
98 XFixesSelectionClientCloseNotifyMask );
99 use_polling = false;
100 #ifdef NOISY_KLIPPER_
101 kDebug() << "Using XFIXES";
102 #endif
104 #endif
105 if( use_polling )
107 #ifdef NOISY_KLIPPER_
108 kDebug() << "Using polling";
109 #endif
110 initPolling();
114 void ClipboardPoll::initPolling()
116 connect( kapp->clipboard(), SIGNAL( selectionChanged() ), SLOT(qtSelectionChanged()));
117 connect( kapp->clipboard(), SIGNAL( dataChanged() ), SLOT( qtClipboardChanged() ));
118 connect( &m_timer, SIGNAL( timeout()), SLOT( timeout()));
119 m_timer.start( 1000 );
120 m_selection.atom = XA_PRIMARY;
121 m_clipboard.atom = m_xa_clipboard;
122 m_selection.last_change = m_clipboard.last_change = QX11Info::appTime(); // don't trigger right after startup
123 m_selection.last_owner = XGetSelectionOwner( QX11Info::display(), XA_PRIMARY );
124 #ifdef NOISY_KLIPPER_
125 kDebug() << "(1) Setting last_owner for =" << "selection" << ":" << m_selection.last_owner;
126 #endif
127 m_clipboard.last_owner = XGetSelectionOwner( QX11Info::display(), m_xa_clipboard );
128 #ifdef NOISY_KLIPPER_
129 kDebug() << "(2) Setting last_owner for =" << "clipboard" << ":" << m_clipboard.last_owner;
130 #endif
131 m_selection.waiting_for_timestamp = false;
132 m_clipboard.waiting_for_timestamp = false;
133 updateQtOwnership( m_selection );
134 updateQtOwnership( m_clipboard );
137 void ClipboardPoll::qtSelectionChanged()
139 emit clipboardChanged( true );
142 void ClipboardPoll::qtClipboardChanged()
144 emit clipboardChanged( false );
147 bool ClipboardPoll::x11Event( XEvent* e )
149 // note that this is also installed as app-wide filter
150 #ifdef HAVE_XFIXES
151 if( m_xfixes_event_base != -1 && e->type == m_xfixes_event_base + XFixesSelectionNotify )
153 XFixesSelectionNotifyEvent* ev = reinterpret_cast< XFixesSelectionNotifyEvent* >( e );
154 if( ev->selection == XA_PRIMARY && !kapp->clipboard()->ownsSelection())
156 #ifdef NOISY_KLIPPER_
157 kDebug() << "SELECTION CHANGED (XFIXES)";
158 #endif
159 QX11Info::setAppTime( ev->timestamp );
160 emit clipboardChanged( true );
162 else if( ev->selection == m_xa_clipboard && !kapp->clipboard()->ownsClipboard())
164 #ifdef NOISY_KLIPPER_
165 kDebug() << "CLIPBOARD CHANGED (XFIXES)";
166 #endif
167 QX11Info::setAppTime( ev->timestamp );
168 emit clipboardChanged( false );
171 #endif
172 if( e->type == SelectionNotify && e->xselection.requestor == winId())
174 if( changedTimestamp( m_selection, *e ) ) {
175 #ifdef NOISY_KLIPPER_
176 kDebug() << "SELECTION CHANGED (GOT TIMESTAMP)";
177 #endif
178 emit clipboardChanged( true );
181 if ( changedTimestamp( m_clipboard, *e ) )
183 #ifdef NOISY_KLIPPER_
184 kDebug() << "CLIPBOARD CHANGED (GOT TIMESTAMP)";
185 #endif
186 emit clipboardChanged( false );
188 return true; // filter out
190 return false;
193 void ClipboardPoll::updateQtOwnership( SelectionData& data )
195 Atom type;
196 int format;
197 unsigned long nitems;
198 unsigned long after;
199 unsigned char* prop = NULL;
200 if( XGetWindowProperty( QX11Info::display(), QX11Info::appRootWindow( 0 ), data.sentinel_atom, 0, 2, False,
201 XA_WINDOW, &type, &format, &nitems, &after, &prop ) != Success
202 || type != XA_WINDOW || format != 32 || nitems != 2 || prop == NULL )
204 #ifdef REALLY_NOISY_KLIPPER_
205 kDebug() << "UPDATEQT BAD PROPERTY";
206 #endif
207 data.owner_is_qt = false;
208 if( prop != NULL )
209 XFree( prop );
210 return;
212 Window owner = reinterpret_cast< long* >( prop )[ 0 ]; // [0] is new owner, [1] is previous
213 XFree( prop );
214 Window current_owner = XGetSelectionOwner( QX11Info::display(), data.atom );
215 data.owner_is_qt = ( owner == current_owner );
216 #ifdef REALLY_NOISY_KLIPPER_
217 kDebug() << "owner=" << owner << "; current_owner=" << current_owner;
218 kDebug() << "UPDATEQT:" << ( &data == &m_selection ? "selection" : "clipboard" ) << ":" << data.owner_is_qt;
219 #endif
222 void ClipboardPoll::timeout()
224 Klipper::updateTimestamp();
225 if( !kapp->clipboard()->ownsSelection() && checkTimestamp( m_selection ) ) {
226 #ifdef NOISY_KLIPPER_
227 kDebug() << "SELECTION CHANGED";
228 #endif
229 emit clipboardChanged( true );
231 if( !kapp->clipboard()->ownsClipboard() && checkTimestamp( m_clipboard ) ) {
232 #ifdef NOISY_KLIPPER_
233 kDebug() << "CLIPBOARD CHANGED";
234 #endif
235 emit clipboardChanged( false );
240 bool ClipboardPoll::checkTimestamp( SelectionData& data )
242 Window current_owner = XGetSelectionOwner( QX11Info::display(), data.atom );
243 bool signal = false;
244 updateQtOwnership( data );
245 if( data.owner_is_qt )
247 data.last_change = CurrentTime;
248 #ifdef REALLY_NOISY_KLIPPER_
249 kDebug() << "(3) Setting last_owner for =" << ( &data==&m_selection ?"selection":"clipboard" ) << ":" << current_owner;
250 #endif
251 data.last_owner = current_owner;
252 data.waiting_for_timestamp = false;
253 return false;
255 if( current_owner != data.last_owner )
257 signal = true; // owner has changed
258 data.last_owner = current_owner;
259 #ifdef REALLY_NOISY_KLIPPER_
260 kDebug() << "(4) Setting last_owner for =" << ( &data==&m_selection ?"selection":"clipboard" ) << ":" << current_owner;
261 #endif
262 data.waiting_for_timestamp = false;
263 data.last_change = CurrentTime;
264 #ifdef REALLY_NOISY_KLIPPER_
265 kDebug() << "OWNER CHANGE:" << ( data.atom == XA_PRIMARY ) << ":" << current_owner;
266 #endif
267 return true;
269 if( current_owner == None ) {
270 return false; // None also last_owner...
272 if( data.waiting_for_timestamp ) {
273 // We're already waiting for the timestamp of the last check
274 return false;
276 XDeleteProperty( QX11Info::display(), winId(), data.timestamp_atom );
277 XConvertSelection( QX11Info::display(), data.atom, m_xa_timestamp, data.timestamp_atom, winId(), QX11Info::appTime() );
278 data.waiting_for_timestamp = true;
279 data.waiting_x_time = QX11Info::appTime();
280 #ifdef REALLY_NOISY_KLIPPER_
281 kDebug() << "WAITING TIMESTAMP:" << ( data.atom == XA_PRIMARY );
282 #endif
283 return false;
286 bool ClipboardPoll::changedTimestamp( SelectionData& data, const XEvent& ev )
288 if( ev.xselection.requestor != winId()
289 || ev.xselection.selection != data.atom
290 || ev.xselection.time != data.waiting_x_time )
292 return false;
294 data.waiting_for_timestamp = false;
295 if( ev.xselection.property == None )
297 #ifdef NOISY_KLIPPER_
298 kDebug() << "REFUSED:" << ( data.atom == XA_PRIMARY );
299 #endif
300 return true;
302 Atom type;
303 int format;
304 unsigned long nitems;
305 unsigned long after;
306 unsigned char* prop = NULL;
307 if( XGetWindowProperty( QX11Info::display(), winId(), ev.xselection.property, 0, 1, False,
308 AnyPropertyType, &type, &format, &nitems, &after, &prop ) != Success
309 || format != 32 || nitems != 1 || prop == NULL )
311 #ifdef NOISY_KLIPPER_
312 kDebug() << "BAD PROPERTY:" << ( data.atom == XA_PRIMARY );
313 #endif
314 if( prop != NULL )
315 XFree( prop );
316 return true;
318 Time timestamp = reinterpret_cast< long* >( prop )[ 0 ];
319 XFree( prop );
320 #ifdef NOISY_KLIPPER_
321 kDebug() << "GOT TIMESTAMP:" << ( data.atom == XA_PRIMARY );
322 kDebug() << "timestamp=" << timestamp
323 << "; CurrentTime=" << CurrentTime
324 << "; last_change=" << data.last_change
325 << endl;
326 #endif
327 if( timestamp != data.last_change || timestamp == CurrentTime )
329 #ifdef NOISY_KLIPPER_
330 kDebug() << "TIMESTAMP CHANGE:" << ( data.atom == XA_PRIMARY );
331 #endif
332 data.last_change = timestamp;
333 return true;
335 return false; // ok, same timestamp
338 #include "clipboardpoll.moc"