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) by Andrew Stanley-Jones
5 Copyright (C) 2000 by Carsten Pfeiffer <pfeiffer@kde.org>
6 Copyright (C) 2004 Esben Mose Hansen <kde@mosehansen.dk>
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public
10 License as published by the Free Software Foundation; either
11 version 2 of the License, or (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; see the file COPYING. If not, write to
20 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 Boston, MA 02110-1301, USA.
25 #include <QtDBus/QDBusConnection>
27 #include <kaboutdata.h>
29 #include <kmessagebox.h>
30 #include <ksavefile.h>
31 #include <ksessionmanager.h>
32 #include <kstandarddirs.h>
33 #include <ksystemtrayicon.h>
35 #include <kglobalsettings.h>
36 #include <kactioncollection.h>
37 #include <ktoggleaction.h>
38 #include <KConfigSkeleton>
40 #include "configdialog.h"
42 #include "urlgrabber.h"
44 #include "clipboardpoll.h"
46 #include "historyitem.h"
47 #include "historystringitem.h"
48 #include "klipperpopup.h"
53 #include <X11/Xatom.h>
55 //#define NOISY_KLIPPER
59 * Use this when manipulating the clipboard
60 * from within clipboard-related signals.
62 * This avoids issues such as mouse-selections that immediately
64 * pattern: Resource Acqusition is Initialisation (RAII)
66 * (This is not threadsafe, so don't try to use such in threaded
70 Ignore(int& locklevel
) : locklevelref(locklevel
) {
82 * Helper class to save history upon session exit.
84 class KlipperSessionManager
: public KSessionManager
87 KlipperSessionManager( Klipper
* k
)
91 virtual ~KlipperSessionManager() {}
94 * Save state upon session exit.
96 * Saving history on session save
98 virtual bool commitData( QSessionManager
& ) {
99 klipper
->saveSession();
106 static void ensureGlobalSyncOff(KSharedConfigPtr config
);
108 // config == KGlobal::config for process, otherwise applet
109 Klipper::Klipper(QObject
*parent
, const KSharedConfigPtr
&config
)
111 , m_overflowCounter( 0 )
114 , m_pendingContentsCheck( false )
115 , session_managed( new KlipperSessionManager( this ))
117 QDBusConnection::sessionBus().registerObject("/klipper", this, QDBusConnection::ExportScriptableSlots
);
119 // We don't use the clipboardsynchronizer anymore, and it confuses Klipper
120 ensureGlobalSyncOff(m_config
);
122 updateTimestamp(); // read initial X user time
123 clip
= kapp
->clipboard();
125 connect( &m_overflowClearTimer
, SIGNAL( timeout()), SLOT( slotClearOverflow()));
126 m_overflowClearTimer
.start( 1000 );
128 m_pendingCheckTimer
.setSingleShot( true );
129 connect( &m_pendingCheckTimer
, SIGNAL( timeout()), SLOT( slotCheckPending()));
131 m_history
= new History( this );
133 // we need that collection, otherwise KToggleAction is not happy :}
134 //QString defaultGroup( "default" );
135 collection
= new KActionCollection( this );
136 toggleURLGrabAction
= new KToggleAction( this );
137 collection
->addAction( "toggleUrlGrabAction", toggleURLGrabAction
);
138 toggleURLGrabAction
->setEnabled( true );
139 //toggleURLGrabAction->setGroup( defaultGroup );
140 clearHistoryAction
= collection
->addAction( "clearHistoryAction" );
141 clearHistoryAction
->setIcon( KIcon("edit-clear-history") );
142 clearHistoryAction
->setText( i18n("C&lear Clipboard History") );
143 connect(clearHistoryAction
, SIGNAL(triggered() ), history(), SLOT( slotClear() ));
144 connect( clearHistoryAction
, SIGNAL( triggered() ), SLOT( slotClearClipboard() ) );
145 //clearHistoryAction->setGroup( defaultGroup );
146 configureAction
= collection
->addAction( "configureAction" );
147 configureAction
->setIcon( KIcon("configure") );
148 configureAction
->setText( i18n("&Configure Klipper...") );
149 connect(configureAction
, SIGNAL(triggered(bool) ), SLOT( slotConfigure() ));
150 //configureAction->setGroup( defaultGroup );
151 quitAction
= collection
->addAction( "quitAction" );
152 quitAction
->setIcon( KIcon("application-exit") );
153 quitAction
->setText( i18n("&Quit") );
154 connect(quitAction
, SIGNAL(triggered(bool) ), SLOT( slotQuit() ));
155 //quitAction->setGroup( "exit" );
157 KConfig
*kc
= m_config
.data();
158 readConfiguration( kc
);
159 setURLGrabberEnabled( bURLGrabber
);
161 hideTimer
= new QTime();
162 showTimer
= new QTime();
164 readProperties(m_config
.data());
165 connect(KGlobalSettings::self(), SIGNAL(settingsChanged(int)), SLOT(slotSettingsChanged(int)));
167 poll
= new ClipboardPoll
;
168 connect( poll
, SIGNAL( clipboardChanged( bool ) ),
169 this, SLOT( newClipData( bool ) ) );
171 QAction
*a
= collection
->addAction("show_klipper_popup");
172 a
->setText(i18n("Show Klipper Popup-Menu"));
173 qobject_cast
<KAction
*>(a
)->setGlobalShortcut(KShortcut(Qt::ALT
+Qt::CTRL
+Qt::Key_V
));
174 connect(a
, SIGNAL(triggered()), SLOT(slotPopupMenu()));
176 a
= collection
->addAction("repeat_action");
177 a
->setText(i18n("Manually Invoke Action on Current Clipboard"));
178 qobject_cast
<KAction
*>(a
)->setGlobalShortcut(KShortcut(Qt::ALT
+Qt::CTRL
+Qt::Key_R
));
179 connect(a
, SIGNAL(triggered()), SLOT(slotRepeatAction()));
181 a
= collection
->addAction("clipboard_action");
182 a
->setText(i18n("Enable/Disable Clipboard Actions"));
183 qobject_cast
<KAction
*>(a
)->setGlobalShortcut(KShortcut(Qt::ALT
+Qt::CTRL
+Qt::Key_X
));
184 connect(a
, SIGNAL(triggered()), SLOT(toggleURLGrabber()));
186 toggleURLGrabAction
->setShortcut(qobject_cast
<KAction
*>(collection
->action("clipboard_action"))->globalShortcut());
188 connect( toggleURLGrabAction
, SIGNAL( toggled( bool )),
189 this, SLOT( setURLGrabberEnabled( bool )));
191 KlipperPopup
* popup
= history()->popup();
192 connect ( history(), SIGNAL( topChanged() ), SLOT( slotHistoryTopChanged() ) );
193 connect( popup
, SIGNAL( aboutToHide() ), SLOT( slotStartHideTimer() ) );
194 connect( popup
, SIGNAL( aboutToShow() ), SLOT( slotStartShowTimer() ) );
196 popup
->plugAction( toggleURLGrabAction
);
197 popup
->plugAction( clearHistoryAction
);
198 popup
->plugAction( configureAction
);
200 popup
->plugAction( quitAction
);
207 delete session_managed
;
214 QString
Klipper::getClipboardContents()
216 return getClipboardHistoryItem(0);
219 // DCOP - don't call from Klipper itself
220 void Klipper::setClipboardContents(QString s
)
222 Ignore
lock( locklevel
);
224 HistoryStringItem
* item
= new HistoryStringItem( s
);
225 setClipboard( *item
, Clipboard
| Selection
);
226 history()->insert( item
);
229 // DCOP - don't call from Klipper itself
230 void Klipper::clearClipboardContents()
233 slotClearClipboard();
236 // DCOP - don't call from Klipper itself
237 void Klipper::clearClipboardHistory()
240 slotClearClipboard();
241 history()->slotClear();
246 void Klipper::slotStartHideTimer()
251 void Klipper::slotStartShowTimer()
256 void Klipper::showPopupMenu( QMenu
*menu
)
258 Q_ASSERT( menu
!= 0L );
260 QSize size
= menu
->sizeHint(); // geometry is not valid until it's shown
262 QPoint g
= QCursor::pos();
263 if ( size
.height() < g
.y() )
264 menu
->popup(QPoint( g
.x(), g
.y() - size
.height()));
266 menu
->popup(QPoint(g
.x(), g
.y()));
268 if( KSystemTrayIcon
* tray
= dynamic_cast< KSystemTrayIcon
* >( parent())) {
269 QRect g
= tray
->geometry();
270 QRect screen
= KGlobalSettings::desktopGeometry(g
.center());
272 if ( g
.x()-screen
.x() > screen
.width()/2 &&
273 g
.y()-screen
.y() + size
.height() > screen
.height() )
274 menu
->popup(QPoint( g
.x(), g
.y() - size
.height()));
276 menu
->popup(QPoint( g
.x() + g
.width(), g
.y() + g
.height()));
280 // menu->exec(mapToGlobal(QPoint( width()/2, height()/2 )));
284 bool Klipper::loadHistory() {
285 static const char* const failed_load_warning
=
286 "Failed to load history resource. Clipboard history cannot be read.";
287 // don't use "appdata", klipper is also a kicker applet
288 QString history_file_name
= KStandardDirs::locateLocal( "data", "klipper/history2.lst" );
289 QFile
history_file( history_file_name
);
290 bool oldfile
= false;
291 if ( !history_file
.exists() ) { // backwards compatibility
293 history_file_name
= KStandardDirs::locateLocal( "data", "klipper/history.lst" );
294 history_file
.setFileName( history_file_name
);
295 if ( !history_file
.exists() ) {
296 history_file_name
= KStandardDirs::locateLocal( "data", "kicker/history.lst" );
297 history_file
.setFileName( history_file_name
);
298 if ( !history_file
.exists() ) {
303 if ( !history_file
.open( QIODevice::ReadOnly
) ) {
304 kWarning() << failed_load_warning
<< ": " << history_file
.errorString() ;
307 QDataStream
file_stream( &history_file
);
308 if( file_stream
.atEnd()) {
309 kWarning() << failed_load_warning
;
312 QDataStream
* history_stream
= &file_stream
;
316 file_stream
>> crc
>> data
;
317 if( crc32( 0, reinterpret_cast<unsigned char *>( data
.data() ), data
.size() ) != crc
) {
318 kWarning() << failed_load_warning
<< ": " << history_file
.errorString() ;
321 history_stream
= new QDataStream( &data
, QIODevice::ReadOnly
);
324 *history_stream
>> version
;
327 // The list needs to be reversed, as it is saved
328 // youngest-first to keep the most important clipboard
329 // items at the top, but the history is created oldest
331 QList
<HistoryItem
*> reverseList
;
332 for ( HistoryItem
* item
= HistoryItem::create( *history_stream
);
334 item
= HistoryItem::create( *history_stream
) )
336 reverseList
.prepend( item
);
339 for ( QList
<HistoryItem
*>::const_iterator it
= reverseList
.begin();
340 it
!= reverseList
.end();
343 history()->forceInsert( *it
);
346 if ( !history()->empty() ) {
347 m_lastSelection
= -1;
348 m_lastClipboard
= -1;
349 setClipboard( *history()->first(), Clipboard
| Selection
);
352 if( history_stream
!= &file_stream
)
353 delete history_stream
;
358 void Klipper::saveHistory() {
359 static const char* const failed_save_warning
=
360 "Failed to save history. Clipboard history cannot be saved.";
361 // don't use "appdata", klipper is also a kicker applet
362 QString
history_file_name( KStandardDirs::locateLocal( "data", "klipper/history2.lst" ) );
363 if ( history_file_name
.isNull() || history_file_name
.isEmpty() ) {
364 kWarning() << failed_save_warning
;
367 KSaveFile
history_file( history_file_name
);
368 if ( !history_file
.open() ) {
369 kWarning() << failed_save_warning
;
373 QDataStream
history_stream( &data
, QIODevice::WriteOnly
);
374 history_stream
<< klipper_version
; // const char*
376 History::iterator it
= history()->youngest();
377 while (it
.hasNext()) {
378 const HistoryItem
*item
= it
.next();
379 history_stream
<< item
;
382 quint32 crc
= crc32( 0, reinterpret_cast<unsigned char *>( data
.data() ), data
.size() );
383 QDataStream
ds ( &history_file
);
387 void Klipper::readProperties(KConfig
*kc
)
389 QStringList dataList
;
391 history()->slotClear();
393 if (bKeepContents
) { // load old clipboard if configured
394 if ( !loadHistory() ) {
395 // Try to load from the old config file.
396 // Remove this at some point.
397 KConfigGroup
configGroup(kc
, "General");
398 dataList
= configGroup
.readEntry("ClipboardData",QStringList());
400 for (QStringList::ConstIterator it
= dataList
.end();
401 it
!= dataList
.begin();
404 history()->forceInsert( new HistoryStringItem( *( --it
) ) );
407 if ( !dataList
.isEmpty() )
409 m_lastSelection
= -1;
410 m_lastClipboard
= -1;
411 setClipboard( *history()->first(), Clipboard
| Selection
);
419 void Klipper::readConfiguration( KConfig
*_kc
)
421 KConfigGroup
kc( _kc
, "General");
422 bPopupAtMouse
= kc
.readEntry("PopupAtMousePosition", false);
423 bKeepContents
= kc
.readEntry("KeepClipboardContents", true);
424 bURLGrabber
= kc
.readEntry("URLGrabberEnabled", false);
425 bReplayActionInHistory
= kc
.readEntry("ReplayActionInHistory", false);
426 bNoNullClipboard
= kc
.readEntry("NoEmptyClipboard", true);
427 bUseGUIRegExpEditor
= kc
.readEntry("UseGUIRegExpEditor", true);
428 history()->max_size( kc
.readEntry("MaxClipItems", 7) );
429 bIgnoreSelection
= kc
.readEntry("IgnoreSelection", false);
430 bSynchronize
= kc
.readEntry("Synchronize", false);
431 bSelectionTextOnly
= kc
.readEntry("SelectionTextOnly",true);
432 bIgnoreImages
= kc
.readEntry("IgnoreImages",true);
435 void Klipper::writeConfiguration( KConfig
*_kc
)
437 KConfigGroup
kc( _kc
, "General");
438 kc
.writeEntry("PopupAtMousePosition", bPopupAtMouse
);
439 kc
.writeEntry("KeepClipboardContents", bKeepContents
);
440 kc
.writeEntry("ReplayActionInHistory", bReplayActionInHistory
);
441 kc
.writeEntry("NoEmptyClipboard", bNoNullClipboard
);
442 kc
.writeEntry("UseGUIRegExpEditor", bUseGUIRegExpEditor
);
443 kc
.writeEntry("MaxClipItems", history()->max_size() );
444 kc
.writeEntry("IgnoreSelection", bIgnoreSelection
);
445 kc
.writeEntry("Synchronize", bSynchronize
);
446 kc
.writeEntry("SelectionTextOnly", bSelectionTextOnly
);
447 kc
.writeEntry("TrackImages", bIgnoreImages
);
448 kc
.writeEntry("Version", klipper_version
);
451 myURLGrabber
->writeConfiguration( _kc
);
456 // save session on shutdown. Don't simply use the c'tor, as that may not be called.
457 void Klipper::saveSession()
459 if ( bKeepContents
) { // save the clipboard eventually
464 void Klipper::slotSettingsChanged( int category
)
466 if ( category
== (int) KGlobalSettings::SETTINGS_SHORTCUTS
) {
467 toggleURLGrabAction
->setShortcut(qobject_cast
<KAction
*>(collection
->action("clipboard_action"))->globalShortcut());
471 void Klipper::disableURLGrabber()
473 KMessageBox::information( 0L,
474 i18n( "You can enable URL actions later by right-clicking on the "
475 "Klipper icon and selecting 'Enable Actions'" ) );
477 setURLGrabberEnabled( false );
480 void Klipper::slotConfigure()
482 bool haveURLGrabber
= bURLGrabber
;
483 if ( !myURLGrabber
) { // temporary, for the config-dialog
484 setURLGrabberEnabled( true );
485 readConfiguration( m_config
.data() );
488 ConfigDialog
*dlg
= new ConfigDialog( 0, new KConfigSkeleton(), myURLGrabber
->actionList(), collection
, isApplet() );
489 dlg
->setKeepContents( bKeepContents
);
490 dlg
->setPopupAtMousePos( bPopupAtMouse
);
491 dlg
->setStripWhiteSpace( myURLGrabber
->trimmed() );
492 dlg
->setReplayActionInHistory( bReplayActionInHistory
);
493 dlg
->setNoNullClipboard( bNoNullClipboard
);
494 dlg
->setUseGUIRegExpEditor( bUseGUIRegExpEditor
);
495 dlg
->setPopupTimeout( myURLGrabber
->popupTimeout() );
496 dlg
->setMaxItems( history()->max_size() );
497 dlg
->setIgnoreSelection( bIgnoreSelection
);
498 dlg
->setSynchronize( bSynchronize
);
499 dlg
->setNoActionsFor( myURLGrabber
->avoidWindows() );
501 if ( dlg
->exec() == QDialog::Accepted
) {
502 bKeepContents
= dlg
->keepContents();
503 bPopupAtMouse
= dlg
->popupAtMousePos();
504 bReplayActionInHistory
= dlg
->replayActionInHistory();
505 bNoNullClipboard
= dlg
->noNullClipboard();
506 bIgnoreSelection
= dlg
->ignoreSelection();
507 bSynchronize
= dlg
->synchronize();
508 bUseGUIRegExpEditor
= dlg
->useGUIRegExpEditor();
509 dlg
->commitShortcuts();
511 toggleURLGrabAction
->setShortcut(qobject_cast
<KAction
*>(collection
->action("clipboard_action"))->globalShortcut());
513 myURLGrabber
->setActionList( dlg
->actionList() );
514 myURLGrabber
->setPopupTimeout( dlg
->popupTimeout() );
515 myURLGrabber
->setStripWhiteSpace( dlg
->trimmed() );
516 myURLGrabber
->setAvoidWindows( dlg
->noActionsFor() );
518 history()->max_size( dlg
->maxItems() );
520 writeConfiguration( m_config
.data() );
523 setURLGrabberEnabled( haveURLGrabber
);
528 void Klipper::slotQuit()
530 // If the menu was just opened, likely the user
531 // selected quit by accident while attempting to
532 // click the Klipper icon.
533 if ( showTimer
->elapsed() < 300 ) {
538 int autoStart
= KMessageBox::questionYesNoCancel(0, i18n("Should Klipper start automatically when you login?"),
539 i18n("Automatically Start Klipper?"), KGuiItem(i18n("Start")),
540 KGuiItem(i18n("Do Not Start")), KStandardGuiItem::cancel(), "StartAutomatically");
542 KConfigGroup
config( KGlobal::config(), "General");
543 if ( autoStart
== KMessageBox::Yes
) {
544 config
.writeEntry("AutoStart", true);
545 } else if ( autoStart
== KMessageBox::No
) {
546 config
.writeEntry("AutoStart", false);
547 } else // cancel chosen don't quit
555 void Klipper::slotPopupMenu() {
556 KlipperPopup
* popup
= history()->popup();
557 popup
->ensureClean();
558 showPopupMenu( popup
);
562 void Klipper::slotRepeatAction()
564 if ( !myURLGrabber
) {
565 myURLGrabber
= new URLGrabber( m_config
);
566 connect( myURLGrabber
, SIGNAL( sigPopup( QMenu
* )),
567 SLOT( showPopupMenu( QMenu
* )) );
568 connect( myURLGrabber
, SIGNAL( sigDisablePopup() ),
569 this, SLOT( disableURLGrabber() ) );
572 const HistoryStringItem
* top
= dynamic_cast<const HistoryStringItem
*>( history()->first() );
574 myURLGrabber
->invokeAction( top
->text() );
578 void Klipper::setURLGrabberEnabled( bool enable
)
580 if (enable
!= bURLGrabber
) {
581 bURLGrabber
= enable
;
582 KConfigGroup
kc(m_config
.data(), "General");
583 kc
.writeEntry("URLGrabberEnabled", bURLGrabber
);
584 m_lastURLGrabberTextSelection
= QString();
585 m_lastURLGrabberTextClipboard
= QString();
588 toggleURLGrabAction
->setChecked( enable
);
590 if ( !bURLGrabber
) {
593 toggleURLGrabAction
->setText(i18n("Enable &Actions"));
597 toggleURLGrabAction
->setText(i18n("&Actions Enabled"));
598 if ( !myURLGrabber
) {
599 myURLGrabber
= new URLGrabber( m_config
);
600 connect( myURLGrabber
, SIGNAL( sigPopup( QMenu
* )),
601 SLOT( showPopupMenu( QMenu
* )) );
602 connect( myURLGrabber
, SIGNAL( sigDisablePopup() ),
603 this, SLOT( disableURLGrabber() ) );
608 void Klipper::toggleURLGrabber()
610 setURLGrabberEnabled( !bURLGrabber
);
613 void Klipper::slotHistoryTopChanged() {
618 const HistoryItem
* topitem
= history()->first();
620 setClipboard( *topitem
, Clipboard
| Selection
);
622 if ( bReplayActionInHistory
&& bURLGrabber
) {
628 void Klipper::slotClearClipboard()
630 Ignore
lock( locklevel
);
632 clip
->clear(QClipboard::Selection
);
633 clip
->clear(QClipboard::Clipboard
);
639 //XXX: Should die, and the DCOP signal handled sensible.
640 QString
Klipper::clipboardContents( bool * /*isSelection*/ )
642 kWarning() << "Obsolete function called. Please fix" ;
645 bool selection
= true;
646 QMimeSource
* data
= clip
->data(QClipboard::Selection
);
648 if ( data
->serialNumber() == m_lastSelection
)
650 QString clipContents
= clip
->text(QClipboard::Clipboard
);
651 if ( clipContents
!= m_lastClipboard
)
653 contents
= clipContents
;
661 *isSelection
= selection
;
668 void Klipper::applyClipChanges( const QMimeData
* clipData
)
672 Ignore
lock( locklevel
);
673 history()->insert( HistoryItem::create( clipData
) );
677 void Klipper::newClipData( bool selectionMode
)
683 if( blockFetchingNewData())
686 checkClipData( selectionMode
);
690 void Klipper::clipboardSignalArrived( bool selectionMode
)
695 if( blockFetchingNewData())
699 checkClipData( selectionMode
);
702 // Protection against too many clipboard data changes. Lyx responds to clipboard data
703 // requests with setting new clipboard data, so if Lyx takes over clipboard,
704 // Klipper notices, requests this data, this triggers "new" clipboard contents
705 // from Lyx, so Klipper notices again, requests this data, ... you get the idea.
706 const int MAX_CLIPBOARD_CHANGES
= 10; // max changes per second
708 bool Klipper::blockFetchingNewData()
710 // Hacks for #85198 and #80302.
711 // #85198 - block fetching new clipboard contents if Shift is pressed and mouse is not,
712 // this may mean the user is doing selection using the keyboard, in which case
713 // it's possible the app sets new clipboard contents after every change - Klipper's
714 // history would list them all.
715 // #80302 - OOo (v1.1.3 at least) has a bug that if Klipper requests its clipboard contents
716 // while the user is doing a selection using the mouse, OOo stops updating the clipboard
717 // contents, so in practice it's like the user has selected only the part which was
718 // selected when Klipper asked first.
719 // Use XQueryPointer rather than QApplication::mouseButtons()/keyboardModifiers(), because
720 // Klipper needs the very current state.
722 int root_x
, root_y
, win_x
, win_y
;
724 XQueryPointer( QX11Info::display(), QX11Info::appRootWindow(), &root
, &child
,
725 &root_x
, &root_y
, &win_x
, &win_y
, &state
);
726 if( ( state
& ( ShiftMask
| Button1Mask
)) == ShiftMask
// #85198
727 || ( state
& Button1Mask
) == Button1Mask
) { // #80302
728 m_pendingContentsCheck
= true;
729 m_pendingCheckTimer
.start( 100 );
732 m_pendingContentsCheck
= false;
733 if( ++m_overflowCounter
> MAX_CLIPBOARD_CHANGES
)
738 void Klipper::slotCheckPending()
740 if( !m_pendingContentsCheck
)
742 m_pendingContentsCheck
= false; // blockFetchingNewData() will be called again
744 newClipData( true ); // always selection
747 void Klipper::checkClipData( bool selectionMode
)
749 if ( ignoreClipboardChanges() ) // internal to klipper, ignoring QSpinBox selections
751 // keep our old clipboard, thanks
752 // This won't quite work, but it's close enough for now.
753 // The trouble is that the top selection =! top clipboard
754 // but we don't track that yet. We will....
755 const HistoryItem
* top
= history()->first();
757 setClipboard( *top
, selectionMode
? Selection
: Clipboard
);
764 kDebug() << "Checking clip data";
767 kDebug() << "====== c h e c k C l i p D a t a ============================"
769 << "====== c h e c k C l i p D a t a ============================"
774 kDebug() << "sender=" << sender()->name();
776 kDebug() << "no sender";
780 kDebug() << "\nselectionMode=" << selectionMode
781 << "\nserialNo=" << clip
->data()->serialNumber() << " (sel,cli)=(" << m_lastSelection
<< "," << m_lastClipboard
<< ")"
782 << "\nowning (sel,cli)=(" << clip
->ownsSelection() << "," << clip
->ownsClipboard() << ")"
783 << "\ntext=" << clip
->text( selectionMode
? QClipboard::Selection
: QClipboard::Clipboard
) << endl
;
789 while ( (format
= clip
->data()->format( i
++ )) )
791 qDebug( " format: %s", format
);
794 const QMimeData
* data
= clip
->mimeData( selectionMode
? QClipboard::Selection
: QClipboard::Clipboard
);
796 kWarning("No data in clipboard. This not not supposed to happen." );
799 // TODO: Rewrite to Qt4 !!!
800 //int lastSerialNo = selectionMode ? m_lastSelection : m_lastClipboard;
801 //bool changed = data->serialNumber() != lastSerialNo;
802 bool changed
= true; // ### FIXME
803 bool clipEmpty
= data
->formats().isEmpty();
805 if ( changed
&& clipEmpty
&& bNoNullClipboard
) {
806 const HistoryItem
* top
= history()->first();
808 // keep old clipboard after someone set it to null
810 kDebug() << "Resetting clipboard (Prevent empty clipboard)";
812 setClipboard( *top
, selectionMode
? Selection
: Clipboard
);
817 // this must be below the "bNoNullClipboard" handling code!
818 // XXX: I want a better handling of selection/clipboard in general.
819 // XXX: Order sensitive code. Must die.
820 if ( selectionMode
&& bIgnoreSelection
)
823 if( selectionMode
&& bSelectionTextOnly
&& !data
->hasText())
826 // TODO: This should be maybe extended for KDE4 or at least get a checkbox somewhere in UI
827 if( KUrl::List::canDecode( data
) )
829 else if( data
->hasText() )
831 else if( data
->hasImage() )
833 // Limit mimetypes that are tracked by Klipper (this is basically a workaround
834 // for #109032). Can't add UI in 3.5 because of string freeze, and I'm not sure
835 // if this actually needs to be more configurable than only text vs all klipper knows.
839 else // unknown, ignore
842 // store old contents:
845 m_lastSelection
= data
->serialNumber();
847 m_lastClipboard
= data
->serialNumber();
850 QString
& lastURLGrabberText
= selectionMode
851 ? m_lastURLGrabberTextSelection
: m_lastURLGrabberTextClipboard
;
852 if( data
->hasText() )
854 if ( bURLGrabber
&& myURLGrabber
)
856 QString text
= data
->text();
858 // Make sure URLGrabber doesn't repeat all the time if klipper reads the same
859 // text all the time (e.g. because XFixes is not available and the application
860 // has broken TIMESTAMP target). Using most recent history item may not always
862 if ( text
!= lastURLGrabberText
)
864 lastURLGrabberText
= text
;
865 if ( myURLGrabber
->checkNewData( text
) )
867 return; // don't add into the history
872 lastURLGrabberText
= QString();
875 lastURLGrabberText
= QString();
878 applyClipChanges( data
);
880 kDebug() << "Synchronize?" << ( bSynchronize
? "yes" : "no" );
882 if ( bSynchronize
) {
883 const HistoryItem
* topItem
= history()->first();
885 setClipboard( *topItem
, selectionMode
? Clipboard
: Selection
);
891 void Klipper::setClipboard( const HistoryItem
& item
, int mode
)
893 Ignore
lock( locklevel
);
895 Q_ASSERT( ( mode
& 1 ) == 0 ); // Warn if trying to pass a boolean as a mode.
897 if ( mode
& Selection
) {
899 kDebug() << "Setting selection to <" << item
.text() << ">";
901 clip
->setMimeData( item
.mimeData(), QClipboard::Selection
);
903 m_lastSelection
= clip
->data()->serialNumber();<
906 if ( mode
& Clipboard
) {
908 kDebug() << "Setting clipboard to <" << item
.text() << ">";
910 clip
->setMimeData( item
.mimeData(), QClipboard::Clipboard
);
912 m_lastClipboard
= clip
->data()->serialNumber();
918 void Klipper::slotClearOverflow()
920 if( m_overflowCounter
> MAX_CLIPBOARD_CHANGES
) {
921 kDebug() << "App owning the clipboard/selection is lame";
922 // update to the latest data - this unfortunately may trigger the problem again
923 newClipData( true ); // Always the selection.
925 m_overflowCounter
= 0;
928 QStringList
Klipper::getClipboardHistoryMenu()
932 History::iterator it
= history()->youngest();
933 while (it
.hasNext()) {
934 const HistoryItem
*item
= it
.next();
935 menu
<< item
->text();
941 QString
Klipper::getClipboardHistoryItem(int i
)
943 History::iterator it
= history()->youngest();
944 while (it
.hasNext()) {
945 const HistoryItem
*item
= it
.next();
956 // changing a spinbox in klipper's config-dialog causes the lineedit-contents
957 // of the spinbox to be selected and hence the clipboard changes. But we don't
958 // want all those items in klipper's history. See #41917
960 bool Klipper::ignoreClipboardChanges() const
962 QWidget
*focusWidget
= qApp
->focusWidget();
965 if ( focusWidget
->inherits( "QSpinBox" ) ||
966 (focusWidget
->parentWidget() &&
967 focusWidget
->inherits("QLineEdit") &&
968 focusWidget
->parentWidget()->inherits("QSpinWidget")) )
977 // QClipboard uses qt_x_time as the timestamp for selection operations.
978 // It is updated mainly from user actions, but Klipper polls the clipboard
979 // without any user action triggering it, so qt_x_time may be old,
980 // which could possibly lead to QClipboard reporting empty clipboard.
981 // Therefore, qt_x_time needs to be updated to current X server timestamp.
983 // Call KApplication::updateUserTime() only from functions that are
984 // called from outside (DCOP), or from QTimer timeout !
986 static Time next_x_time
;
987 static Bool
update_x_time_predicate( Display
*, XEvent
* event
, XPointer
)
989 if( next_x_time
!= CurrentTime
)
991 // from qapplication_x11.cpp
992 switch ( event
->type
) {
994 // fallthrough intended
996 next_x_time
= event
->xbutton
.time
;
999 next_x_time
= event
->xmotion
.time
;
1002 // fallthrough intended
1004 next_x_time
= event
->xkey
.time
;
1006 case PropertyNotify
:
1007 next_x_time
= event
->xproperty
.time
;
1011 next_x_time
= event
->xcrossing
.time
;
1013 case SelectionClear
:
1014 next_x_time
= event
->xselectionclear
.time
;
1022 void Klipper::updateTimestamp()
1024 static QWidget
* w
= 0;
1027 unsigned char data
[ 1 ];
1028 XChangeProperty( QX11Info::display(), w
->winId(), XA_ATOM
, XA_ATOM
, 8, PropModeAppend
, data
, 1 );
1029 next_x_time
= CurrentTime
;
1031 XCheckIfEvent( QX11Info::display(), &dummy
, update_x_time_predicate
, NULL
);
1032 if( next_x_time
== CurrentTime
)
1034 XSync( QX11Info::display(), False
);
1035 XCheckIfEvent( QX11Info::display(), &dummy
, update_x_time_predicate
, NULL
);
1037 Q_ASSERT( next_x_time
!= CurrentTime
);
1038 QX11Info::setAppTime( next_x_time
);
1039 XEvent ev
; // remove the PropertyNotify event from the events queue
1040 XWindowEvent( QX11Info::display(), w
->winId(), PropertyChangeMask
, &ev
);
1043 static const char * const description
=
1044 I18N_NOOP("KDE cut & paste history utility");
1046 void Klipper::createAboutData()
1048 about_data
= new KAboutData("klipper", 0, ki18n("Klipper"),
1049 klipper_version
, ki18n(description
), KAboutData::License_GPL
,
1050 ki18n("(c) 1998, Andrew Stanley-Jones\n"
1051 "1998-2002, Carsten Pfeiffer\n"
1052 "2001, Patrick Dubroy"));
1054 about_data
->addAuthor(ki18n("Carsten Pfeiffer"),
1056 "pfeiffer@kde.org");
1058 about_data
->addAuthor(ki18n("Andrew Stanley-Jones"),
1059 ki18n( "Original Author" ),
1062 about_data
->addAuthor(ki18n("Patrick Dubroy"),
1063 ki18n("Contributor"),
1064 "patrickdu@corel.com");
1066 about_data
->addAuthor( ki18n("Luboš Luňák"),
1067 ki18n("Bugfixes and optimizations"),
1070 about_data
->addAuthor( ki18n("Esben Mose Hansen"),
1071 ki18n("Maintainer"),
1072 "kde@mosehansen.dk");
1075 void Klipper::destroyAboutData()
1081 KAboutData
* Klipper::about_data
;
1083 KAboutData
* Klipper::aboutData()
1088 static void ensureGlobalSyncOff(KSharedConfigPtr config
) {
1089 KConfigGroup
cg(config
, "General");
1090 if ( cg
.readEntry( "SynchronizeClipboardAndSelection" , false) ) {
1091 kDebug() << "Shutting off global synchronization";
1092 cg
.writeEntry("SynchronizeClipboardAndSelection", false, KConfig::Normal
| KConfig::Global
);
1094 kapp
->setSynchronizeClipboard(false);
1095 KGlobalSettings::self()->emitChange( KGlobalSettings::ClipboardConfigChanged
, 0 );
1101 #include "klipper.moc"