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.
23
#include <config-workspace.h>
24
#include <config-X11.h>
26
#include "clipboardpoll.h"
28
#include <kapplication.h>
31
#include <X11/Xatom.h>
36
#include <X11/extensions/Xfixes.h>
41
//#define NOISY_KLIPPER_
46
After Qt 4.something, this part worked poorly. Meanwhile, XFixes was integrated into QClipboard,
47
and has become pretty ubiquitous, so I'm risking retiring this code. The alternative is essentially
48
reimplementation of QClipboard, and the duplication would help noone. (Esben Mose Hansen, 2009-11-28 11:44:43)
52
There's no way with X11 how to find out if the selection has changed (unless its ownership
53
is taken away from the current client). In the future, there will be hopefully such notification,
54
which will make this whole file more or less obsolete. But for now, Klipper has to poll.
55
In order to avoid transferring all the data on every time pulse, this file implements two
56
optimizations: The first one is checking whether the selection owner is Qt application (using
57
the _QT_SELECTION/CLIPBOARD_SENTINEL atoms on the root window of screen 0), and if yes,
58
Klipper can rely on QClipboard's signals. If the owner is not Qt app, and the ownership has changed,
59
it means the selection has changed as well. Otherwise, first only the timestamp
60
of the last selection change is requested using the TIMESTAMP selection target, and if it's
61
the same, it's assumed the contents haven't changed. Note that some applications (like XEmacs) does
62
not provide this information, so Klipper has to assume that the clipboard might have changed in this
63
case --- this is what is meant by REFUSED below.
65
Update: Now there's also support for XFixes, so in case XFixes support is detected, only XFixes is
66
used for detecting changes, everything else is ignored, even Qt's clipboard signals.
70
ClipboardPoll::ClipboardPoll()
71
: m_xfixes_event_base( -1 )
74
const char* names[ 6 ]
75
= { "_QT_SELECTION_SENTINEL",
76
"_QT_CLIPBOARD_SENTINEL",
79
"KLIPPER_SELECTION_TIMESTAMP",
80
"KLIPPER_CLIPBOARD_TIMESTAMP" };
82
XInternAtoms( QX11Info::display(), const_cast< char** >( names ), 6, False, atoms );
83
m_selection.sentinel_atom = atoms[ 0 ];
84
m_clipboard.sentinel_atom = atoms[ 1 ];
85
m_xa_clipboard = atoms[ 2 ];
86
m_xa_timestamp = atoms[ 3 ];
87
m_selection.timestamp_atom = atoms[ 4 ];
88
m_clipboard.timestamp_atom = atoms[ 5 ];
89
bool use_polling = true;
90
kapp->installX11EventFilter( this );
91
m_timer.setSingleShot( false );
94
if( XFixesQueryExtension( QX11Info::display(), &m_xfixes_event_base, &dummy ))
96
XFixesSelectSelectionInput( QX11Info::display(), QX11Info::appRootWindow( 0 ), XA_PRIMARY,
97
XFixesSetSelectionOwnerNotifyMask |
98
XFixesSelectionWindowDestroyNotifyMask |
99
XFixesSelectionClientCloseNotifyMask );
100
XFixesSelectSelectionInput( QX11Info::display(), QX11Info::appRootWindow( 0 ), m_xa_clipboard,
101
XFixesSetSelectionOwnerNotifyMask |
102
XFixesSelectionWindowDestroyNotifyMask |
103
XFixesSelectionClientCloseNotifyMask );
105
#ifdef NOISY_KLIPPER_
106
kDebug() << "Using XFIXES";
112
#ifdef NOISY_KLIPPER_
113
kDebug() << "Using polling";
119
void ClipboardPoll::initPolling()
121
connect( kapp->clipboard(), SIGNAL( selectionChanged() ), SLOT(qtSelectionChanged()));
122
connect( kapp->clipboard(), SIGNAL( dataChanged() ), SLOT( qtClipboardChanged() ));
123
connect( &m_timer, SIGNAL( timeout()), SLOT( timeout()));
124
m_timer.start( 1000 );
125
m_selection.atom = XA_PRIMARY;
126
m_clipboard.atom = m_xa_clipboard;
127
m_selection.last_change = m_clipboard.last_change = QX11Info::appTime(); // don't trigger right after startup
128
m_selection.last_owner = XGetSelectionOwner( QX11Info::display(), XA_PRIMARY );
129
#ifdef NOISY_KLIPPER_
130
kDebug() << "(1) Setting last_owner for =" << "selection" << ":" << m_selection.last_owner;
132
m_clipboard.last_owner = XGetSelectionOwner( QX11Info::display(), m_xa_clipboard );
133
#ifdef NOISY_KLIPPER_
134
kDebug() << "(2) Setting last_owner for =" << "clipboard" << ":" << m_clipboard.last_owner;
136
m_selection.waiting_for_timestamp = false;
137
m_clipboard.waiting_for_timestamp = false;
138
updateQtOwnership( m_selection );
139
updateQtOwnership( m_clipboard );
142
void ClipboardPoll::qtSelectionChanged()
144
emit clipboardChanged( true );
147
void ClipboardPoll::qtClipboardChanged()
149
emit clipboardChanged( false );
152
bool ClipboardPoll::x11Event( XEvent* e )
154
// note that this is also installed as app-wide filter
156
if( m_xfixes_event_base != -1 && e->type == m_xfixes_event_base + XFixesSelectionNotify )
158
XFixesSelectionNotifyEvent* ev = reinterpret_cast< XFixesSelectionNotifyEvent* >( e );
159
if( ev->selection == XA_PRIMARY && !kapp->clipboard()->ownsSelection())
161
#ifdef NOISY_KLIPPER_
162
kDebug() << "SELECTION CHANGED (XFIXES)";
164
QX11Info::setAppTime( ev->timestamp );
165
emit clipboardChanged( true );
167
else if( ev->selection == m_xa_clipboard && !kapp->clipboard()->ownsClipboard())
169
#ifdef NOISY_KLIPPER_
170
kDebug() << "CLIPBOARD CHANGED (XFIXES)";
172
QX11Info::setAppTime( ev->timestamp );
173
emit clipboardChanged( false );
177
if( e->type == SelectionNotify && e->xselection.requestor == winId())
179
if( changedTimestamp( m_selection, *e ) ) {
180
#ifdef NOISY_KLIPPER_
181
kDebug() << "SELECTION CHANGED (GOT TIMESTAMP)";
183
emit clipboardChanged( true );
186
if ( changedTimestamp( m_clipboard, *e ) )
188
#ifdef NOISY_KLIPPER_
189
kDebug() << "CLIPBOARD CHANGED (GOT TIMESTAMP)";
191
emit clipboardChanged( false );
193
return true; // filter out
198
void ClipboardPoll::updateQtOwnership( SelectionData& data )
202
unsigned long nitems;
204
unsigned char* prop = NULL;
205
if( XGetWindowProperty( QX11Info::display(), QX11Info::appRootWindow( 0 ), data.sentinel_atom, 0, 2, False,
206
XA_WINDOW, &type, &format, &nitems, &after, &prop ) != Success
207
|| type != XA_WINDOW || format != 32 || nitems != 2 || prop == NULL )
209
#ifdef REALLY_NOISY_KLIPPER_
210
kDebug() << "UPDATEQT BAD PROPERTY";
212
data.owner_is_qt = false;
217
Window owner = reinterpret_cast< long* >( prop )[ 0 ]; // [0] is new owner, [1] is previous
219
Window current_owner = XGetSelectionOwner( QX11Info::display(), data.atom );
220
data.owner_is_qt = ( owner == current_owner );
221
#ifdef REALLY_NOISY_KLIPPER_
222
kDebug() << "owner=" << owner << "; current_owner=" << current_owner;
223
kDebug() << "UPDATEQT:" << ( &data == &m_selection ? "selection" : "clipboard" ) << ":" << data.owner_is_qt;
227
void ClipboardPoll::timeout()
229
Klipper::updateTimestamp();
230
if( !kapp->clipboard()->ownsSelection() && checkTimestamp( m_selection ) ) {
231
#ifdef NOISY_KLIPPER_
232
kDebug() << "SELECTION CHANGED";
234
emit clipboardChanged( true );
236
if( !kapp->clipboard()->ownsClipboard() && checkTimestamp( m_clipboard ) ) {
237
#ifdef NOISY_KLIPPER_
238
kDebug() << "CLIPBOARD CHANGED";
240
emit clipboardChanged( false );
245
bool ClipboardPoll::checkTimestamp( SelectionData& data )
247
Window current_owner = XGetSelectionOwner( QX11Info::display(), data.atom );
249
updateQtOwnership( data );
250
if( data.owner_is_qt )
252
data.last_change = CurrentTime;
253
#ifdef REALLY_NOISY_KLIPPER_
254
kDebug() << "(3) Setting last_owner for =" << ( &data==&m_selection ?"selection":"clipboard" ) << ":" << current_owner;
256
data.last_owner = current_owner;
257
data.waiting_for_timestamp = false;
260
if( current_owner != data.last_owner )
262
signal = true; // owner has changed
263
data.last_owner = current_owner;
264
#ifdef REALLY_NOISY_KLIPPER_
265
kDebug() << "(4) Setting last_owner for =" << ( &data==&m_selection ?"selection":"clipboard" ) << ":" << current_owner;
267
data.waiting_for_timestamp = false;
268
data.last_change = CurrentTime;
269
#ifdef REALLY_NOISY_KLIPPER_
270
kDebug() << "OWNER CHANGE:" << ( data.atom == XA_PRIMARY ) << ":" << current_owner;
274
if( current_owner == None ) {
275
return false; // None also last_owner...
277
if( data.waiting_for_timestamp ) {
278
// We're already waiting for the timestamp of the last check
281
XDeleteProperty( QX11Info::display(), winId(), data.timestamp_atom );
282
XConvertSelection( QX11Info::display(), data.atom, m_xa_timestamp, data.timestamp_atom, winId(), QX11Info::appTime() );
283
data.waiting_for_timestamp = true;
284
data.waiting_x_time = QX11Info::appTime();
285
#ifdef REALLY_NOISY_KLIPPER_
286
kDebug() << "WAITING TIMESTAMP:" << ( data.atom == XA_PRIMARY );
291
bool ClipboardPoll::changedTimestamp( SelectionData& data, const XEvent& ev )
293
if( ev.xselection.requestor != winId()
294
|| ev.xselection.selection != data.atom
295
|| ev.xselection.time != data.waiting_x_time )
299
data.waiting_for_timestamp = false;
300
if( ev.xselection.property == None )
302
#ifdef NOISY_KLIPPER_
303
kDebug() << "REFUSED:" << ( data.atom == XA_PRIMARY );
309
unsigned long nitems;
311
unsigned char* prop = NULL;
312
if( XGetWindowProperty( QX11Info::display(), winId(), ev.xselection.property, 0, 1, False,
313
AnyPropertyType, &type, &format, &nitems, &after, &prop ) != Success
314
|| format != 32 || nitems != 1 || prop == NULL )
316
#ifdef NOISY_KLIPPER_
317
kDebug() << "BAD PROPERTY:" << ( data.atom == XA_PRIMARY );
323
Time timestamp = reinterpret_cast< long* >( prop )[ 0 ];
325
#ifdef NOISY_KLIPPER_
326
kDebug() << "GOT TIMESTAMP:" << ( data.atom == XA_PRIMARY );
327
kDebug() << "timestamp=" << timestamp
328
<< "; CurrentTime=" << CurrentTime
329
<< "; last_change=" << data.last_change
332
if( timestamp != data.last_change || timestamp == CurrentTime )
334
#ifdef NOISY_KLIPPER_
335
kDebug() << "TIMESTAMP CHANGE:" << ( data.atom == XA_PRIMARY );
337
data.last_change = timestamp;
340
return false; // ok, same timestamp
343
#include "clipboardpoll.moc"