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>
30 #include <X11/Xatom.h>
35 #include <X11/extensions/Xfixes.h>
40 //#define NOISY_KLIPPER_
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 )
69 const char* names
[ 6 ]
70 = { "_QT_SELECTION_SENTINEL",
71 "_QT_CLIPBOARD_SENTINEL",
74 "KLIPPER_SELECTION_TIMESTAMP",
75 "KLIPPER_CLIPBOARD_TIMESTAMP" };
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 );
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
);
100 #ifdef NOISY_KLIPPER_
101 kDebug() << "Using XFIXES";
107 #ifdef NOISY_KLIPPER_
108 kDebug() << "Using polling";
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
;
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
;
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
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)";
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)";
167 QX11Info::setAppTime( ev
->timestamp
);
168 emit
clipboardChanged( false );
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)";
178 emit
clipboardChanged( true );
181 if ( changedTimestamp( m_clipboard
, *e
) )
183 #ifdef NOISY_KLIPPER_
184 kDebug() << "CLIPBOARD CHANGED (GOT TIMESTAMP)";
186 emit
clipboardChanged( false );
188 return true; // filter out
193 void ClipboardPoll::updateQtOwnership( SelectionData
& data
)
197 unsigned long nitems
;
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";
207 data
.owner_is_qt
= false;
212 Window owner
= reinterpret_cast< long* >( prop
)[ 0 ]; // [0] is new owner, [1] is previous
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
;
222 void ClipboardPoll::timeout()
224 Klipper::updateTimestamp();
225 if( !kapp
->clipboard()->ownsSelection() && checkTimestamp( m_selection
) ) {
226 #ifdef NOISY_KLIPPER_
227 kDebug() << "SELECTION CHANGED";
229 emit
clipboardChanged( true );
231 if( !kapp
->clipboard()->ownsClipboard() && checkTimestamp( m_clipboard
) ) {
232 #ifdef NOISY_KLIPPER_
233 kDebug() << "CLIPBOARD CHANGED";
235 emit
clipboardChanged( false );
240 bool ClipboardPoll::checkTimestamp( SelectionData
& data
)
242 Window current_owner
= XGetSelectionOwner( QX11Info::display(), data
.atom
);
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
;
251 data
.last_owner
= current_owner
;
252 data
.waiting_for_timestamp
= 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
;
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
;
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
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
);
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
)
294 data
.waiting_for_timestamp
= false;
295 if( ev
.xselection
.property
== None
)
297 #ifdef NOISY_KLIPPER_
298 kDebug() << "REFUSED:" << ( data
.atom
== XA_PRIMARY
);
304 unsigned long nitems
;
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
);
318 Time timestamp
= reinterpret_cast< long* >( prop
)[ 0 ];
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
327 if( timestamp
!= data
.last_change
|| timestamp
== CurrentTime
)
329 #ifdef NOISY_KLIPPER_
330 kDebug() << "TIMESTAMP CHANGE:" << ( data
.atom
== XA_PRIMARY
);
332 data
.last_change
= timestamp
;
335 return false; // ok, same timestamp
338 #include "clipboardpoll.moc"