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 , m_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 m_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 m_collection
= new KActionCollection( this );
136 m_toggleURLGrabAction
= new KToggleAction( this );
137 m_collection
->addAction( "toggleUrlGrabAction", m_toggleURLGrabAction
);
138 m_toggleURLGrabAction
->setEnabled( true );
139 m_toggleURLGrabAction
->setText(i18n("Enable &Actions"));
140 //m_toggleURLGrabAction->setGroup( defaultGroup );
141 m_clearHistoryAction
= m_collection
->addAction( "clearHistoryAction" );
142 m_clearHistoryAction
->setIcon( KIcon("edit-clear-history") );
143 m_clearHistoryAction
->setText( i18n("C&lear Clipboard History") );
144 connect(m_clearHistoryAction
, SIGNAL(triggered() ), history(), SLOT( slotClear() ));
145 connect( m_clearHistoryAction
, SIGNAL( triggered() ), SLOT( slotClearClipboard() ) );
146 //m_clearHistoryAction->setGroup( defaultGroup );
147 m_configureAction
= m_collection
->addAction( "configureAction" );
148 m_configureAction
->setIcon( KIcon("configure") );
149 m_configureAction
->setText( i18n("&Configure Klipper...") );
150 connect(m_configureAction
, SIGNAL(triggered(bool) ), SLOT( slotConfigure() ));
151 //m_configureAction->setGroup( defaultGroup );
152 m_quitAction
= m_collection
->addAction( "quitAction" );
153 m_quitAction
->setIcon( KIcon("application-exit") );
154 m_quitAction
->setText( i18n("&Quit") );
155 connect(m_quitAction
, SIGNAL(triggered(bool) ), SLOT( slotQuit() ));
156 //quitAction->setGroup( "exit" );
158 KConfig
*kc
= m_config
.data();
159 readConfiguration( kc
);
160 setURLGrabberEnabled( m_bURLGrabber
);
162 m_hideTimer
= new QTime();
163 m_showTimer
= new QTime();
165 readProperties(m_config
.data());
166 connect(KGlobalSettings::self(), SIGNAL(settingsChanged(int)), SLOT(slotSettingsChanged(int)));
168 m_poll
= new ClipboardPoll
;
169 connect( m_poll
, SIGNAL( clipboardChanged( bool ) ),
170 this, SLOT( newClipData( bool ) ) );
172 QAction
*a
= m_collection
->addAction("show_klipper_popup");
173 a
->setText(i18n("Show Klipper Popup-Menu"));
174 qobject_cast
<KAction
*>(a
)->setGlobalShortcut(KShortcut(Qt::ALT
+Qt::CTRL
+Qt::Key_V
));
175 connect(a
, SIGNAL(triggered()), SLOT(slotPopupMenu()));
177 a
= m_collection
->addAction("repeat_action");
178 a
->setText(i18n("Manually Invoke Action on Current Clipboard"));
179 qobject_cast
<KAction
*>(a
)->setGlobalShortcut(KShortcut(Qt::ALT
+Qt::CTRL
+Qt::Key_R
));
180 connect(a
, SIGNAL(triggered()), SLOT(slotRepeatAction()));
182 a
= m_collection
->addAction("clipboard_action");
183 a
->setText(i18n("Enable/Disable Clipboard Actions"));
184 qobject_cast
<KAction
*>(a
)->setGlobalShortcut(KShortcut(Qt::ALT
+Qt::CTRL
+Qt::Key_X
));
185 connect(a
, SIGNAL(triggered()), SLOT(toggleURLGrabber()));
187 m_toggleURLGrabAction
->setShortcut(qobject_cast
<KAction
*>(m_collection
->action("clipboard_action"))->globalShortcut());
189 connect( m_toggleURLGrabAction
, SIGNAL( toggled( bool )),
190 this, SLOT( setURLGrabberEnabled( bool )));
192 KlipperPopup
* popup
= history()->popup();
193 connect ( history(), SIGNAL( topChanged() ), SLOT( slotHistoryTopChanged() ) );
194 connect( popup
, SIGNAL( aboutToHide() ), SLOT( slotStartHideTimer() ) );
195 connect( popup
, SIGNAL( aboutToShow() ), SLOT( slotStartShowTimer() ) );
197 popup
->plugAction( m_toggleURLGrabAction
);
198 popup
->plugAction( m_clearHistoryAction
);
199 popup
->plugAction( m_configureAction
);
201 popup
->plugAction( m_quitAction
);
208 delete m_session_managed
;
211 delete m_myURLGrabber
;
215 QString
Klipper::getClipboardContents()
217 return getClipboardHistoryItem(0);
220 void Klipper::showKlipperPopupMenu() {
223 void Klipper::showKlipperManuallyInvokeActionMenu() {
228 // DCOP - don't call from Klipper itself
229 void Klipper::setClipboardContents(QString s
)
233 Ignore
lock( m_locklevel
);
235 HistoryStringItem
* item
= new HistoryStringItem( s
);
236 setClipboard( *item
, Clipboard
| Selection
);
237 history()->insert( item
);
240 // DCOP - don't call from Klipper itself
241 void Klipper::clearClipboardContents()
244 slotClearClipboard();
247 // DCOP - don't call from Klipper itself
248 void Klipper::clearClipboardHistory()
251 slotClearClipboard();
252 history()->slotClear();
257 void Klipper::slotStartHideTimer()
259 m_hideTimer
->start();
262 void Klipper::slotStartShowTimer()
264 m_showTimer
->start();
267 void Klipper::showPopupMenu( QMenu
*menu
)
269 Q_ASSERT( menu
!= 0L );
271 QSize size
= menu
->sizeHint(); // geometry is not valid until it's shown
272 if (m_bPopupAtMouse
) {
273 QPoint g
= QCursor::pos();
274 if ( size
.height() < g
.y() )
275 menu
->popup(QPoint( g
.x(), g
.y() - size
.height()));
277 menu
->popup(QPoint(g
.x(), g
.y()));
279 if( KSystemTrayIcon
* tray
= dynamic_cast< KSystemTrayIcon
* >( parent())) {
280 QRect g
= tray
->geometry();
281 QRect screen
= KGlobalSettings::desktopGeometry(g
.center());
283 if ( g
.x()-screen
.x() > screen
.width()/2 &&
284 g
.y()-screen
.y() + size
.height() > screen
.height() )
285 menu
->popup(QPoint( g
.x(), g
.y() - size
.height()));
287 menu
->popup(QPoint( g
.x() + g
.width(), g
.y() + g
.height()));
291 // menu->exec(mapToGlobal(QPoint( width()/2, height()/2 )));
295 bool Klipper::loadHistory() {
296 static const char* const failed_load_warning
=
297 "Failed to load history resource. Clipboard history cannot be read.";
298 // don't use "appdata", klipper is also a kicker applet
299 QString history_file_name
= KStandardDirs::locateLocal( "data", "klipper/history2.lst" );
300 QFile
history_file( history_file_name
);
301 bool oldfile
= false;
302 if ( !history_file
.exists() ) { // backwards compatibility
304 history_file_name
= KStandardDirs::locateLocal( "data", "klipper/history.lst" );
305 history_file
.setFileName( history_file_name
);
306 if ( !history_file
.exists() ) {
307 history_file_name
= KStandardDirs::locateLocal( "data", "kicker/history.lst" );
308 history_file
.setFileName( history_file_name
);
309 if ( !history_file
.exists() ) {
314 if ( !history_file
.open( QIODevice::ReadOnly
) ) {
315 kWarning() << failed_load_warning
<< ": " << history_file
.errorString() ;
318 QDataStream
file_stream( &history_file
);
319 if( file_stream
.atEnd()) {
320 kWarning() << failed_load_warning
;
323 QDataStream
* history_stream
= &file_stream
;
327 file_stream
>> crc
>> data
;
328 if( crc32( 0, reinterpret_cast<unsigned char *>( data
.data() ), data
.size() ) != crc
) {
329 kWarning() << failed_load_warning
<< ": " << history_file
.errorString() ;
332 history_stream
= new QDataStream( &data
, QIODevice::ReadOnly
);
335 *history_stream
>> version
;
338 // The list needs to be reversed, as it is saved
339 // youngest-first to keep the most important clipboard
340 // items at the top, but the history is created oldest
342 QList
<HistoryItem
*> reverseList
;
343 for ( HistoryItem
* item
= HistoryItem::create( *history_stream
);
345 item
= HistoryItem::create( *history_stream
) )
347 reverseList
.prepend( item
);
350 for ( QList
<HistoryItem
*>::const_iterator it
= reverseList
.constBegin();
351 it
!= reverseList
.constEnd();
354 history()->forceInsert( *it
);
357 if ( !history()->empty() ) {
358 m_lastSelection
= -1;
359 m_lastClipboard
= -1;
360 setClipboard( *history()->first(), Clipboard
| Selection
);
363 if( history_stream
!= &file_stream
)
364 delete history_stream
;
369 void Klipper::saveHistory() {
370 static const char* const failed_save_warning
=
371 "Failed to save history. Clipboard history cannot be saved.";
372 // don't use "appdata", klipper is also a kicker applet
373 QString
history_file_name( KStandardDirs::locateLocal( "data", "klipper/history2.lst" ) );
374 if ( history_file_name
.isNull() || history_file_name
.isEmpty() ) {
375 kWarning() << failed_save_warning
;
378 KSaveFile
history_file( history_file_name
);
379 if ( !history_file
.open() ) {
380 kWarning() << failed_save_warning
;
384 QDataStream
history_stream( &data
, QIODevice::WriteOnly
);
385 history_stream
<< klipper_version
; // const char*
387 History::iterator it
= history()->youngest();
388 while (it
.hasNext()) {
389 const HistoryItem
*item
= it
.next();
390 history_stream
<< item
;
393 quint32 crc
= crc32( 0, reinterpret_cast<unsigned char *>( data
.data() ), data
.size() );
394 QDataStream
ds ( &history_file
);
398 void Klipper::readProperties(KConfig
*kc
)
400 QStringList dataList
;
402 history()->slotClear();
404 if (m_bKeepContents
) { // load old clipboard if configured
405 if ( !loadHistory() ) {
406 // Try to load from the old config file.
407 // Remove this at some point.
408 KConfigGroup
configGroup(kc
, "General");
409 dataList
= configGroup
.readEntry("ClipboardData",QStringList());
411 for (QStringList::ConstIterator it
= dataList
.constEnd();
412 it
!= dataList
.constBegin();
415 history()->forceInsert( new HistoryStringItem( *( --it
) ) );
418 if ( !dataList
.isEmpty() )
420 m_lastSelection
= -1;
421 m_lastClipboard
= -1;
422 setClipboard( *history()->first(), Clipboard
| Selection
);
430 void Klipper::readConfiguration( KConfig
*_kc
)
432 KConfigGroup
kc( _kc
, "General");
433 m_bPopupAtMouse
= kc
.readEntry("PopupAtMousePosition", false);
434 m_bKeepContents
= kc
.readEntry("KeepClipboardContents", true);
435 m_bURLGrabber
= kc
.readEntry("URLGrabberEnabled", false);
436 m_bReplayActionInHistory
= kc
.readEntry("ReplayActionInHistory", false);
437 m_bNoNullClipboard
= kc
.readEntry("NoEmptyClipboard", true);
438 m_bUseGUIRegExpEditor
= kc
.readEntry("UseGUIRegExpEditor", true);
439 history()->max_size( kc
.readEntry("MaxClipItems", 7) );
440 m_bIgnoreSelection
= kc
.readEntry("IgnoreSelection", false);
441 m_bSynchronize
= kc
.readEntry("Synchronize", false);
442 m_bSelectionTextOnly
= kc
.readEntry("SelectionTextOnly",true);
443 m_bIgnoreImages
= kc
.readEntry("IgnoreImages", true);
446 void Klipper::writeConfiguration( KConfig
*_kc
)
448 KConfigGroup
kc( _kc
, "General");
449 kc
.writeEntry("PopupAtMousePosition", m_bPopupAtMouse
);
450 kc
.writeEntry("KeepClipboardContents", m_bKeepContents
);
451 kc
.writeEntry("ReplayActionInHistory", m_bReplayActionInHistory
);
452 kc
.writeEntry("NoEmptyClipboard", m_bNoNullClipboard
);
453 kc
.writeEntry("UseGUIRegExpEditor", m_bUseGUIRegExpEditor
);
454 kc
.writeEntry("MaxClipItems", history()->max_size() );
455 kc
.writeEntry("IgnoreSelection", m_bIgnoreSelection
);
456 kc
.writeEntry("Synchronize", m_bSynchronize
);
457 kc
.writeEntry("SelectionTextOnly", m_bSelectionTextOnly
);
458 kc
.writeEntry("IgnoreImages", m_bIgnoreImages
);
459 kc
.writeEntry("Version", klipper_version
);
461 if ( m_myURLGrabber
)
462 m_myURLGrabber
->writeConfiguration( _kc
);
467 // save session on shutdown. Don't simply use the c'tor, as that may not be called.
468 void Klipper::saveSession()
470 if ( m_bKeepContents
) { // save the clipboard eventually
475 void Klipper::slotSettingsChanged( int category
)
477 if ( category
== (int) KGlobalSettings::SETTINGS_SHORTCUTS
) {
478 m_toggleURLGrabAction
->setShortcut(qobject_cast
<KAction
*>(m_collection
->action("clipboard_action"))->globalShortcut());
482 void Klipper::disableURLGrabber()
484 KMessageBox::information( 0L,
485 i18n( "You can enable URL actions later by right-clicking on the "
486 "Klipper icon and selecting 'Enable Actions'" ) );
488 setURLGrabberEnabled( false );
491 void Klipper::slotConfigure()
493 bool haveURLGrabber
= m_bURLGrabber
;
494 if ( !m_myURLGrabber
) { // temporary, for the config-dialog
495 setURLGrabberEnabled( true );
496 readConfiguration( m_config
.data() );
498 KConfigSkeleton
*skeleton
= new KConfigSkeleton();
499 ConfigDialog
*dlg
= new ConfigDialog( 0, skeleton
, m_myURLGrabber
->actionList(), m_collection
, isApplet() );
500 dlg
->setKeepContents( m_bKeepContents
);
501 dlg
->setPopupAtMousePos( m_bPopupAtMouse
);
502 dlg
->setStripWhiteSpace( m_myURLGrabber
->trimmed() );
503 dlg
->setReplayActionInHistory( m_bReplayActionInHistory
);
504 dlg
->setNoNullClipboard( m_bNoNullClipboard
);
505 dlg
->setUseGUIRegExpEditor( m_bUseGUIRegExpEditor
);
506 dlg
->setPopupTimeout( m_myURLGrabber
->popupTimeout() );
507 dlg
->setMaxItems( history()->max_size() );
508 dlg
->setIgnoreSelection( m_bIgnoreSelection
);
509 dlg
->setIgnoreImages( m_bIgnoreImages
);
510 dlg
->setSynchronize( m_bSynchronize
);
511 dlg
->setNoActionsFor( m_myURLGrabber
->avoidWindows() );
513 if ( dlg
->exec() == QDialog::Accepted
) {
514 m_bKeepContents
= dlg
->keepContents();
515 m_bPopupAtMouse
= dlg
->popupAtMousePos();
516 m_bReplayActionInHistory
= dlg
->replayActionInHistory();
517 m_bNoNullClipboard
= dlg
->noNullClipboard();
518 m_bIgnoreSelection
= dlg
->ignoreSelection();
519 m_bIgnoreImages
= dlg
->ignoreImages();
520 m_bSynchronize
= dlg
->synchronize();
521 m_bUseGUIRegExpEditor
= dlg
->useGUIRegExpEditor();
522 dlg
->commitShortcuts();
524 m_toggleURLGrabAction
->setShortcut(qobject_cast
<KAction
*>(m_collection
->action("clipboard_action"))->globalShortcut());
526 m_myURLGrabber
->setActionList( dlg
->actionList() );
527 m_myURLGrabber
->setPopupTimeout( dlg
->popupTimeout() );
528 m_myURLGrabber
->setStripWhiteSpace( dlg
->trimmed() );
529 m_myURLGrabber
->setAvoidWindows( dlg
->noActionsFor() );
531 history()->max_size( dlg
->maxItems() );
533 writeConfiguration( m_config
.data() );
536 setURLGrabberEnabled( haveURLGrabber
);
542 void Klipper::slotQuit()
544 // If the menu was just opened, likely the user
545 // selected quit by accident while attempting to
546 // click the Klipper icon.
547 if ( m_showTimer
->elapsed() < 300 ) {
552 int autoStart
= KMessageBox::questionYesNoCancel(0, i18n("Should Klipper start automatically when you login?"),
553 i18n("Automatically Start Klipper?"), KGuiItem(i18n("Start")),
554 KGuiItem(i18n("Do Not Start")), KStandardGuiItem::cancel(), "StartAutomatically");
556 KConfigGroup
config( KGlobal::config(), "General");
557 if ( autoStart
== KMessageBox::Yes
) {
558 config
.writeEntry("AutoStart", true);
559 } else if ( autoStart
== KMessageBox::No
) {
560 config
.writeEntry("AutoStart", false);
561 } else // cancel chosen don't quit
569 void Klipper::slotPopupMenu() {
570 KlipperPopup
* popup
= history()->popup();
571 popup
->ensureClean();
572 showPopupMenu( popup
);
576 void Klipper::slotRepeatAction()
578 if ( !m_myURLGrabber
) {
579 m_myURLGrabber
= new URLGrabber( m_config
);
580 connect( m_myURLGrabber
, SIGNAL( sigPopup( QMenu
* )),
581 SLOT( showPopupMenu( QMenu
* )) );
582 connect( m_myURLGrabber
, SIGNAL( sigDisablePopup() ),
583 this, SLOT( disableURLGrabber() ) );
586 const HistoryStringItem
* top
= dynamic_cast<const HistoryStringItem
*>( history()->first() );
588 m_myURLGrabber
->invokeAction( top
->text() );
592 void Klipper::setURLGrabberEnabled( bool enable
)
594 if (enable
!= m_bURLGrabber
) {
595 m_bURLGrabber
= enable
;
596 KConfigGroup
kc(m_config
.data(), "General");
597 kc
.writeEntry("URLGrabberEnabled", m_bURLGrabber
);
598 m_lastURLGrabberTextSelection
= QString();
599 m_lastURLGrabberTextClipboard
= QString();
602 m_toggleURLGrabAction
->setChecked( enable
);
604 if ( !m_bURLGrabber
) {
605 delete m_myURLGrabber
;
610 if ( !m_myURLGrabber
) {
611 m_myURLGrabber
= new URLGrabber( m_config
);
612 connect( m_myURLGrabber
, SIGNAL( sigPopup( QMenu
* )),
613 SLOT( showPopupMenu( QMenu
* )) );
614 connect( m_myURLGrabber
, SIGNAL( sigDisablePopup() ),
615 this, SLOT( disableURLGrabber() ) );
620 void Klipper::toggleURLGrabber()
622 setURLGrabberEnabled( !m_bURLGrabber
);
625 void Klipper::slotHistoryTopChanged() {
630 const HistoryItem
* topitem
= history()->first();
632 setClipboard( *topitem
, Clipboard
| Selection
);
634 if ( m_bReplayActionInHistory
&& m_bURLGrabber
) {
639 void Klipper::slotClearClipboard()
641 Ignore
lock( m_locklevel
);
643 m_clip
->clear(QClipboard::Selection
);
644 m_clip
->clear(QClipboard::Clipboard
);
648 //XXX: Should die, and the DCOP signal handled sensible.
649 QString
Klipper::clipboardContents( bool * /*isSelection*/ )
651 kWarning() << "Obsolete function called. Please fix" ;
654 bool selection
= true;
655 QMimeSource
* data
= clip
->data(QClipboard::Selection
);
657 if ( data
->serialNumber() == m_lastSelection
)
659 QString clipContents
= clip
->text(QClipboard::Clipboard
);
660 if ( clipContents
!= m_lastClipboard
)
662 contents
= clipContents
;
670 *isSelection
= selection
;
677 void Klipper::applyClipChanges( const QMimeData
* clipData
)
681 Ignore
lock( m_locklevel
);
682 history()->insert( HistoryItem::create( clipData
) );
686 void Klipper::newClipData( bool selectionMode
)
692 if( blockFetchingNewData())
695 checkClipData( selectionMode
);
699 void Klipper::clipboardSignalArrived( bool selectionMode
)
704 if( blockFetchingNewData())
708 checkClipData( selectionMode
);
711 // Protection against too many clipboard data changes. Lyx responds to clipboard data
712 // requests with setting new clipboard data, so if Lyx takes over clipboard,
713 // Klipper notices, requests this data, this triggers "new" clipboard contents
714 // from Lyx, so Klipper notices again, requests this data, ... you get the idea.
715 const int MAX_CLIPBOARD_CHANGES
= 10; // max changes per second
717 bool Klipper::blockFetchingNewData()
719 // Hacks for #85198 and #80302.
720 // #85198 - block fetching new clipboard contents if Shift is pressed and mouse is not,
721 // this may mean the user is doing selection using the keyboard, in which case
722 // it's possible the app sets new clipboard contents after every change - Klipper's
723 // history would list them all.
724 // #80302 - OOo (v1.1.3 at least) has a bug that if Klipper requests its clipboard contents
725 // while the user is doing a selection using the mouse, OOo stops updating the clipboard
726 // contents, so in practice it's like the user has selected only the part which was
727 // selected when Klipper asked first.
728 // Use XQueryPointer rather than QApplication::mouseButtons()/keyboardModifiers(), because
729 // Klipper needs the very current state.
731 int root_x
, root_y
, win_x
, win_y
;
733 XQueryPointer( QX11Info::display(), QX11Info::appRootWindow(), &root
, &child
,
734 &root_x
, &root_y
, &win_x
, &win_y
, &state
);
735 if( ( state
& ( ShiftMask
| Button1Mask
)) == ShiftMask
// #85198
736 || ( state
& Button1Mask
) == Button1Mask
) { // #80302
737 m_pendingContentsCheck
= true;
738 m_pendingCheckTimer
.start( 100 );
741 m_pendingContentsCheck
= false;
742 if( ++m_overflowCounter
> MAX_CLIPBOARD_CHANGES
)
747 void Klipper::slotCheckPending()
749 if( !m_pendingContentsCheck
)
751 m_pendingContentsCheck
= false; // blockFetchingNewData() will be called again
753 newClipData( true ); // always selection
756 void Klipper::checkClipData( bool selectionMode
)
758 if ( ignoreClipboardChanges() ) // internal to klipper, ignoring QSpinBox selections
760 // keep our old clipboard, thanks
761 // This won't quite work, but it's close enough for now.
762 // The trouble is that the top selection =! top clipboard
763 // but we don't track that yet. We will....
764 const HistoryItem
* top
= history()->first();
766 setClipboard( *top
, selectionMode
? Selection
: Clipboard
);
773 kDebug() << "Checking clip data";
776 kDebug() << "====== c h e c k C l i p D a t a ============================"
778 << "====== c h e c k C l i p D a t a ============================"
783 kDebug() << "sender=" << sender()->name();
785 kDebug() << "no sender";
789 kDebug() << "\nselectionMode=" << selectionMode
790 << "\nserialNo=" << clip
->data()->serialNumber() << " (sel,cli)=(" << m_lastSelection
<< "," << m_lastClipboard
<< ")"
791 << "\nowning (sel,cli)=(" << clip
->ownsSelection() << "," << clip
->ownsClipboard() << ")"
792 << "\ntext=" << clip
->text( selectionMode
? QClipboard::Selection
: QClipboard::Clipboard
) << endl
;
798 while ( (format
= clip
->data()->format( i
++ )) )
800 qDebug( " format: %s", format
);
803 const QMimeData
* data
= m_clip
->mimeData( selectionMode
? QClipboard::Selection
: QClipboard::Clipboard
);
805 kWarning("No data in clipboard. This not not supposed to happen." );
808 // TODO: Rewrite to Qt4 !!!
809 //int lastSerialNo = selectionMode ? m_lastSelection : m_lastClipboard;
810 //bool changed = data->serialNumber() != lastSerialNo;
811 bool changed
= true; // ### FIXME
812 bool clipEmpty
= data
->formats().isEmpty();
814 if ( changed
&& clipEmpty
&& m_bNoNullClipboard
) {
815 const HistoryItem
* top
= history()->first();
817 // keep old clipboard after someone set it to null
819 kDebug() << "Resetting clipboard (Prevent empty clipboard)";
821 setClipboard( *top
, selectionMode
? Selection
: Clipboard
);
826 // this must be below the "bNoNullClipboard" handling code!
827 // XXX: I want a better handling of selection/clipboard in general.
828 // XXX: Order sensitive code. Must die.
829 if ( selectionMode
&& m_bIgnoreSelection
)
832 if( selectionMode
&& m_bSelectionTextOnly
&& !data
->hasText())
835 if( KUrl::List::canDecode( data
) )
837 else if( data
->hasText() )
839 else if( data
->hasImage() )
841 if( m_bIgnoreImages
)
844 else // unknown, ignore
847 // store old contents:
850 m_lastSelection
= data
->serialNumber();
852 m_lastClipboard
= data
->serialNumber();
855 QString
& lastURLGrabberText
= selectionMode
856 ? m_lastURLGrabberTextSelection
: m_lastURLGrabberTextClipboard
;
857 if( data
->hasText() )
859 if ( m_bURLGrabber
&& m_myURLGrabber
)
861 QString text
= data
->text();
863 // Make sure URLGrabber doesn't repeat all the time if klipper reads the same
864 // text all the time (e.g. because XFixes is not available and the application
865 // has broken TIMESTAMP target). Using most recent history item may not always
867 if ( text
!= lastURLGrabberText
)
869 lastURLGrabberText
= text
;
870 if ( m_myURLGrabber
->checkNewData( text
) )
872 return; // don't add into the history
877 lastURLGrabberText
= QString();
880 lastURLGrabberText
= QString();
883 applyClipChanges( data
);
885 kDebug() << "Synchronize?" << m_bSynchronize
;
887 if ( m_bSynchronize
) {
888 const HistoryItem
* topItem
= history()->first();
890 setClipboard( *topItem
, selectionMode
? Clipboard
: Selection
);
896 void Klipper::setClipboard( const HistoryItem
& item
, int mode
)
898 Ignore
lock( m_locklevel
);
900 Q_ASSERT( ( mode
& 1 ) == 0 ); // Warn if trying to pass a boolean as a mode.
902 if ( mode
& Selection
) {
904 kDebug() << "Setting selection to <" << item
.text() << ">";
906 m_clip
->setMimeData( item
.mimeData(), QClipboard::Selection
);
908 m_lastSelection
= clip
->data()->serialNumber();<
911 if ( mode
& Clipboard
) {
913 kDebug() << "Setting clipboard to <" << item
.text() << ">";
915 m_clip
->setMimeData( item
.mimeData(), QClipboard::Clipboard
);
917 m_lastClipboard
= clip
->data()->serialNumber();
923 void Klipper::slotClearOverflow()
925 if( m_overflowCounter
> MAX_CLIPBOARD_CHANGES
) {
926 kDebug() << "App owning the clipboard/selection is lame";
927 // update to the latest data - this unfortunately may trigger the problem again
928 newClipData( true ); // Always the selection.
930 m_overflowCounter
= 0;
933 QStringList
Klipper::getClipboardHistoryMenu()
937 History::iterator it
= history()->youngest();
938 while (it
.hasNext()) {
939 const HistoryItem
*item
= it
.next();
940 menu
<< item
->text();
946 QString
Klipper::getClipboardHistoryItem(int i
)
948 History::iterator it
= history()->youngest();
949 while (it
.hasNext()) {
950 const HistoryItem
*item
= it
.next();
961 // changing a spinbox in klipper's config-dialog causes the lineedit-contents
962 // of the spinbox to be selected and hence the clipboard changes. But we don't
963 // want all those items in klipper's history. See #41917
965 bool Klipper::ignoreClipboardChanges() const
967 QWidget
*focusWidget
= qApp
->focusWidget();
970 if ( focusWidget
->inherits( "QSpinBox" ) ||
971 (focusWidget
->parentWidget() &&
972 focusWidget
->inherits("QLineEdit") &&
973 focusWidget
->parentWidget()->inherits("QSpinWidget")) )
982 // QClipboard uses qt_x_time as the timestamp for selection operations.
983 // It is updated mainly from user actions, but Klipper polls the clipboard
984 // without any user action triggering it, so qt_x_time may be old,
985 // which could possibly lead to QClipboard reporting empty clipboard.
986 // Therefore, qt_x_time needs to be updated to current X server timestamp.
988 // Call KApplication::updateUserTime() only from functions that are
989 // called from outside (DCOP), or from QTimer timeout !
991 static Time next_x_time
;
992 static Bool
update_x_time_predicate( Display
*, XEvent
* event
, XPointer
)
994 if( next_x_time
!= CurrentTime
)
996 // from qapplication_x11.cpp
997 switch ( event
->type
) {
999 // fallthrough intended
1001 next_x_time
= event
->xbutton
.time
;
1004 next_x_time
= event
->xmotion
.time
;
1007 // fallthrough intended
1009 next_x_time
= event
->xkey
.time
;
1011 case PropertyNotify
:
1012 next_x_time
= event
->xproperty
.time
;
1016 next_x_time
= event
->xcrossing
.time
;
1018 case SelectionClear
:
1019 next_x_time
= event
->xselectionclear
.time
;
1027 void Klipper::updateTimestamp()
1029 static QWidget
* w
= 0;
1032 unsigned char data
[ 1 ];
1033 XChangeProperty( QX11Info::display(), w
->winId(), XA_ATOM
, XA_ATOM
, 8, PropModeAppend
, data
, 1 );
1034 next_x_time
= CurrentTime
;
1036 XCheckIfEvent( QX11Info::display(), &dummy
, update_x_time_predicate
, NULL
);
1037 if( next_x_time
== CurrentTime
)
1039 XSync( QX11Info::display(), False
);
1040 XCheckIfEvent( QX11Info::display(), &dummy
, update_x_time_predicate
, NULL
);
1042 Q_ASSERT( next_x_time
!= CurrentTime
);
1043 QX11Info::setAppTime( next_x_time
);
1044 XEvent ev
; // remove the PropertyNotify event from the events queue
1045 XWindowEvent( QX11Info::display(), w
->winId(), PropertyChangeMask
, &ev
);
1048 static const char * const description
=
1049 I18N_NOOP("KDE cut & paste history utility");
1051 void Klipper::createAboutData()
1053 m_about_data
= new KAboutData("klipper", 0, ki18n("Klipper"),
1054 klipper_version
, ki18n(description
), KAboutData::License_GPL
,
1055 ki18n("(c) 1998, Andrew Stanley-Jones\n"
1056 "1998-2002, Carsten Pfeiffer\n"
1057 "2001, Patrick Dubroy"));
1059 m_about_data
->addAuthor(ki18n("Carsten Pfeiffer"),
1061 "pfeiffer@kde.org");
1063 m_about_data
->addAuthor(ki18n("Andrew Stanley-Jones"),
1064 ki18n( "Original Author" ),
1067 m_about_data
->addAuthor(ki18n("Patrick Dubroy"),
1068 ki18n("Contributor"),
1069 "patrickdu@corel.com");
1071 m_about_data
->addAuthor( ki18n("Luboš Luňák"),
1072 ki18n("Bugfixes and optimizations"),
1075 m_about_data
->addAuthor( ki18n("Esben Mose Hansen"),
1076 ki18n("Maintainer"),
1077 "kde@mosehansen.dk");
1080 void Klipper::destroyAboutData()
1082 delete m_about_data
;
1083 m_about_data
= NULL
;
1086 KAboutData
* Klipper::m_about_data
;
1088 KAboutData
* Klipper::aboutData()
1090 return m_about_data
;
1093 static void ensureGlobalSyncOff(KSharedConfigPtr config
) {
1094 KConfigGroup
cg(config
, "General");
1095 if ( cg
.readEntry( "SynchronizeClipboardAndSelection" , false) ) {
1096 kDebug() << "Shutting off global synchronization";
1097 cg
.writeEntry("SynchronizeClipboardAndSelection", false, KConfig::Normal
| KConfig::Global
);
1099 kapp
->setSynchronizeClipboard(false);
1100 KGlobalSettings::self()->emitChange( KGlobalSettings::ClipboardConfigChanged
, 0 );
1106 #include "klipper.moc"