~ubuntu-branches/ubuntu/utopic/kde-workspace/utopic-proposed

« back to all changes in this revision

Viewing changes to klipper/clipboardpoll.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Michał Zając
  • Date: 2011-07-09 08:31:15 UTC
  • Revision ID: james.westby@ubuntu.com-20110709083115-ohyxn6z93mily9fc
Tags: upstream-4.6.90
Import upstream version 4.6.90

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*-
 
2
/* This file is part of the KDE project
 
3
 
 
4
   Copyright (C) 2003 by Lubos Lunak <l.lunak@kde.org>
 
5
 
 
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.
 
10
 
 
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.
 
15
 
 
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.
 
20
*/
 
21
#ifdef Q_WS_X11
 
22
 
 
23
#include <config-workspace.h>
 
24
#include <config-X11.h>
 
25
 
 
26
#include "clipboardpoll.h"
 
27
 
 
28
#include <kapplication.h>
 
29
#include <QClipboard>
 
30
#include <kdebug.h>
 
31
#include <X11/Xatom.h>
 
32
#include <time.h>
 
33
#include <QX11Info>
 
34
 
 
35
#ifdef HAVE_XFIXES
 
36
#include <X11/extensions/Xfixes.h>
 
37
#endif
 
38
 
 
39
#include "klipper.h"
 
40
  
 
41
//#define NOISY_KLIPPER_
 
42
 
 
43
 
 
44
/*
 
45
 
 
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)
 
49
 
 
50
 The polling magic:
 
51
 
 
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.
 
64
 
 
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.
 
67
 
 
68
*/
 
69
 
 
70
ClipboardPoll::ClipboardPoll()
 
71
    : m_xfixes_event_base( -1 )
 
72
{
 
73
    hide();
 
74
    const char* names[ 6 ]
 
75
        = { "_QT_SELECTION_SENTINEL",
 
76
            "_QT_CLIPBOARD_SENTINEL",
 
77
            "CLIPBOARD",
 
78
            "TIMESTAMP",
 
79
            "KLIPPER_SELECTION_TIMESTAMP",
 
80
            "KLIPPER_CLIPBOARD_TIMESTAMP" };
 
81
    Atom atoms[ 6 ];
 
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 );
 
92
#ifdef HAVE_XFIXES
 
93
    int dummy;
 
94
    if( XFixesQueryExtension( QX11Info::display(), &m_xfixes_event_base, &dummy ))
 
95
    {
 
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 );
 
104
        use_polling = false;
 
105
#ifdef NOISY_KLIPPER_
 
106
            kDebug() << "Using XFIXES";
 
107
#endif
 
108
    }
 
109
#endif
 
110
    if( use_polling )
 
111
        {
 
112
#ifdef NOISY_KLIPPER_
 
113
        kDebug() << "Using polling";
 
114
#endif
 
115
        initPolling();
 
116
        }
 
117
}
 
118
    
 
119
void ClipboardPoll::initPolling()
 
120
{
 
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;
 
131
#endif
 
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;
 
135
#endif
 
136
    m_selection.waiting_for_timestamp = false;
 
137
    m_clipboard.waiting_for_timestamp = false;
 
138
    updateQtOwnership( m_selection );
 
139
    updateQtOwnership( m_clipboard );
 
140
}
 
141
 
 
142
void ClipboardPoll::qtSelectionChanged()
 
143
{
 
144
    emit clipboardChanged( true );
 
145
}
 
146
 
 
147
void ClipboardPoll::qtClipboardChanged()
 
148
{
 
149
    emit clipboardChanged( false );
 
150
}
 
151
 
 
152
bool ClipboardPoll::x11Event( XEvent* e )
 
153
{
 
154
// note that this is also installed as app-wide filter
 
155
#ifdef HAVE_XFIXES
 
156
    if( m_xfixes_event_base != -1 && e->type == m_xfixes_event_base + XFixesSelectionNotify )
 
157
    {
 
158
        XFixesSelectionNotifyEvent* ev = reinterpret_cast< XFixesSelectionNotifyEvent* >( e );
 
159
        if( ev->selection == XA_PRIMARY && !kapp->clipboard()->ownsSelection())
 
160
        {
 
161
#ifdef NOISY_KLIPPER_
 
162
            kDebug() << "SELECTION CHANGED (XFIXES)";
 
163
#endif
 
164
            QX11Info::setAppTime( ev->timestamp );
 
165
            emit clipboardChanged( true );
 
166
        }
 
167
        else if( ev->selection == m_xa_clipboard && !kapp->clipboard()->ownsClipboard())
 
168
        {
 
169
#ifdef NOISY_KLIPPER_
 
170
            kDebug() << "CLIPBOARD CHANGED (XFIXES)";
 
171
#endif
 
172
            QX11Info::setAppTime( ev->timestamp );
 
173
            emit clipboardChanged( false );
 
174
        }
 
175
    }
 
176
#endif
 
177
    if( e->type == SelectionNotify && e->xselection.requestor == winId())
 
178
    {
 
179
        if( changedTimestamp( m_selection, *e ) ) {
 
180
#ifdef NOISY_KLIPPER_
 
181
            kDebug() << "SELECTION CHANGED (GOT TIMESTAMP)";
 
182
#endif
 
183
            emit clipboardChanged( true );
 
184
        }
 
185
 
 
186
        if ( changedTimestamp( m_clipboard, *e ) )
 
187
        {
 
188
#ifdef NOISY_KLIPPER_
 
189
            kDebug() << "CLIPBOARD CHANGED (GOT TIMESTAMP)";
 
190
#endif
 
191
            emit clipboardChanged( false );
 
192
        }
 
193
        return true; // filter out
 
194
    }
 
195
    return false;
 
196
}
 
197
 
 
198
void ClipboardPoll::updateQtOwnership( SelectionData& data )
 
199
{
 
200
    Atom type;
 
201
    int format;
 
202
    unsigned long nitems;
 
203
    unsigned long after;
 
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 )
 
208
    {
 
209
#ifdef REALLY_NOISY_KLIPPER_
 
210
        kDebug() << "UPDATEQT BAD PROPERTY";
 
211
#endif
 
212
        data.owner_is_qt = false;
 
213
        if( prop != NULL )
 
214
            XFree( prop );
 
215
        return;
 
216
    }
 
217
    Window owner = reinterpret_cast< long* >( prop )[ 0 ]; // [0] is new owner, [1] is previous
 
218
    XFree( prop );
 
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;
 
224
#endif
 
225
}
 
226
 
 
227
void ClipboardPoll::timeout()
 
228
{
 
229
    Klipper::updateTimestamp();
 
230
    if( !kapp->clipboard()->ownsSelection() && checkTimestamp( m_selection ) ) {
 
231
#ifdef NOISY_KLIPPER_
 
232
        kDebug() << "SELECTION CHANGED";
 
233
#endif
 
234
        emit clipboardChanged( true );
 
235
    }
 
236
    if( !kapp->clipboard()->ownsClipboard() && checkTimestamp( m_clipboard ) ) {
 
237
#ifdef NOISY_KLIPPER_
 
238
        kDebug() << "CLIPBOARD CHANGED";
 
239
#endif
 
240
        emit clipboardChanged( false );
 
241
    }
 
242
 
 
243
}
 
244
 
 
245
bool ClipboardPoll::checkTimestamp( SelectionData& data )
 
246
{
 
247
    Window current_owner = XGetSelectionOwner( QX11Info::display(), data.atom );
 
248
    bool signal = false;
 
249
    updateQtOwnership( data );
 
250
    if( data.owner_is_qt )
 
251
    {
 
252
        data.last_change = CurrentTime;
 
253
#ifdef REALLY_NOISY_KLIPPER_
 
254
        kDebug() << "(3) Setting last_owner for =" << ( &data==&m_selection ?"selection":"clipboard" ) << ":" << current_owner;
 
255
#endif
 
256
        data.last_owner = current_owner;
 
257
        data.waiting_for_timestamp = false;
 
258
        return false;
 
259
    }
 
260
    if( current_owner != data.last_owner )
 
261
    {
 
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;
 
266
#endif
 
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;
 
271
#endif
 
272
        return true;
 
273
    }
 
274
    if( current_owner == None ) {
 
275
        return false; // None also last_owner...
 
276
    }
 
277
    if( data.waiting_for_timestamp ) {
 
278
        // We're already waiting for the timestamp of the last check
 
279
        return false;
 
280
    }
 
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 );
 
287
#endif
 
288
    return false;
 
289
}
 
290
 
 
291
bool ClipboardPoll::changedTimestamp( SelectionData& data, const XEvent& ev )
 
292
{
 
293
    if( ev.xselection.requestor != winId()
 
294
        || ev.xselection.selection != data.atom
 
295
        || ev.xselection.time != data.waiting_x_time )
 
296
    {
 
297
        return false;
 
298
    }
 
299
    data.waiting_for_timestamp = false;
 
300
    if( ev.xselection.property == None )
 
301
    {
 
302
#ifdef NOISY_KLIPPER_
 
303
        kDebug() << "REFUSED:" << ( data.atom == XA_PRIMARY );
 
304
#endif
 
305
        return true;
 
306
    }
 
307
    Atom type;
 
308
    int format;
 
309
    unsigned long nitems;
 
310
    unsigned long after;
 
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 )
 
315
    {
 
316
#ifdef NOISY_KLIPPER_
 
317
        kDebug() << "BAD PROPERTY:" << ( data.atom == XA_PRIMARY );
 
318
#endif
 
319
        if( prop != NULL )
 
320
            XFree( prop );
 
321
        return true;
 
322
    }
 
323
    Time timestamp = reinterpret_cast< long* >( prop )[ 0 ];
 
324
    XFree( prop );
 
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
 
330
              << endl;
 
331
#endif
 
332
    if( timestamp != data.last_change || timestamp == CurrentTime )
 
333
    {
 
334
#ifdef NOISY_KLIPPER_
 
335
        kDebug() << "TIMESTAMP CHANGE:" << ( data.atom == XA_PRIMARY );
 
336
#endif
 
337
        data.last_change = timestamp;
 
338
        return true;
 
339
    }
 
340
    return false; // ok, same timestamp
 
341
}
 
342
 
 
343
#include "clipboardpoll.moc"
 
344
 
 
345
#endif