not quite so much needs to be delayed to the init() function
[personal-kdebase.git] / workspace / klipper / klipper.cpp
blob9ed6fbdc5856ba3693dcadded0981e55b9986ce2
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.
24 #include <QClipboard>
25 #include <QtDBus/QDBusConnection>
27 #include <kaboutdata.h>
28 #include <klocale.h>
29 #include <kmessagebox.h>
30 #include <ksavefile.h>
31 #include <ksessionmanager.h>
32 #include <kstandarddirs.h>
33 #include <ksystemtrayicon.h>
34 #include <kdebug.h>
35 #include <kglobalsettings.h>
36 #include <kactioncollection.h>
37 #include <ktoggleaction.h>
38 #include <KConfigSkeleton>
40 #include "configdialog.h"
41 #include "klipper.h"
42 #include "urlgrabber.h"
43 #include "version.h"
44 #include "clipboardpoll.h"
45 #include "history.h"
46 #include "historyitem.h"
47 #include "historystringitem.h"
48 #include "klipperpopup.h"
50 #include <zlib.h>
52 #include <X11/Xlib.h>
53 #include <X11/Xatom.h>
55 //#define NOISY_KLIPPER
57 namespace {
58 /**
59 * Use this when manipulating the clipboard
60 * from within clipboard-related signals.
62 * This avoids issues such as mouse-selections that immediately
63 * disappear.
64 * pattern: Resource Acqusition is Initialisation (RAII)
66 * (This is not threadsafe, so don't try to use such in threaded
67 * applications).
69 struct Ignore {
70 Ignore(int& locklevel) : locklevelref(locklevel) {
71 locklevelref++;
73 ~Ignore() {
74 locklevelref--;
76 private:
77 int& locklevelref;
81 /**
82 * Helper class to save history upon session exit.
84 class KlipperSessionManager : public KSessionManager
86 public:
87 KlipperSessionManager( Klipper* k )
88 : klipper( k )
91 virtual ~KlipperSessionManager() {}
93 /**
94 * Save state upon session exit.
96 * Saving history on session save
98 virtual bool commitData( QSessionManager& ) {
99 klipper->saveSession();
100 return true;
102 private:
103 Klipper* klipper;
106 static void ensureGlobalSyncOff(KSharedConfigPtr config);
108 // config == KGlobal::config for process, otherwise applet
109 Klipper::Klipper(QObject *parent, const KSharedConfigPtr &config)
110 : QObject( parent )
111 , m_overflowCounter( 0 )
112 , m_locklevel( 0 )
113 , m_config( config )
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" );
157 m_myURLGrabber = 0L;
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 );
200 if ( !isApplet() ) {
201 popup->plugAction( m_quitAction );
205 Klipper::~Klipper()
207 delete m_poll;
208 delete m_session_managed;
209 delete m_showTimer;
210 delete m_hideTimer;
211 delete m_myURLGrabber;
214 // DCOP
215 QString Klipper::getClipboardContents()
217 return getClipboardHistoryItem(0);
220 void Klipper::showKlipperPopupMenu() {
221 slotPopupMenu();
223 void Klipper::showKlipperManuallyInvokeActionMenu() {
224 slotRepeatAction();
228 // DCOP - don't call from Klipper itself
229 void Klipper::setClipboardContents(QString s)
231 if (s.isEmpty())
232 return;
233 Ignore lock( m_locklevel );
234 updateTimestamp();
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()
243 updateTimestamp();
244 slotClearClipboard();
247 // DCOP - don't call from Klipper itself
248 void Klipper::clearClipboardHistory()
250 updateTimestamp();
251 slotClearClipboard();
252 history()->slotClear();
253 saveSession();
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()));
276 else
277 menu->popup(QPoint(g.x(), g.y()));
278 } else {
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()));
286 else
287 menu->popup(QPoint( g.x() + g.width(), g.y() + g.height()));
288 } else
289 abort();
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
303 oldfile = true;
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() ) {
310 return false;
314 if ( !history_file.open( QIODevice::ReadOnly ) ) {
315 kWarning() << failed_load_warning << ": " << history_file.errorString() ;
316 return false;
318 QDataStream file_stream( &history_file );
319 if( file_stream.atEnd()) {
320 kWarning() << failed_load_warning ;
321 return false;
323 QDataStream* history_stream = &file_stream;
324 QByteArray data;
325 if( !oldfile ) {
326 quint32 crc;
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() ;
330 return false;
332 history_stream = new QDataStream( &data, QIODevice::ReadOnly );
334 char* version;
335 *history_stream >> version;
336 delete[] 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
341 // first.
342 QList<HistoryItem*> reverseList;
343 for ( HistoryItem* item = HistoryItem::create( *history_stream );
344 item;
345 item = HistoryItem::create( *history_stream ) )
347 reverseList.prepend( item );
350 for ( QList<HistoryItem*>::const_iterator it = reverseList.constBegin();
351 it != reverseList.constEnd();
352 ++it )
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;
366 return true;
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 ;
376 return;
378 KSaveFile history_file( history_file_name );
379 if ( !history_file.open() ) {
380 kWarning() << failed_save_warning ;
381 return;
383 QByteArray data;
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 );
395 ds << crc << data;
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 );
464 kc.sync();
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
471 saveHistory();
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 );
538 delete skeleton;
539 delete dlg;
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 ) {
548 return;
551 saveSession();
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
562 return;
563 config.sync();
565 kapp->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() );
587 if ( top ) {
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;
606 m_myURLGrabber = 0L;
609 else {
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() {
626 if ( m_locklevel ) {
627 return;
630 const HistoryItem* topitem = history()->first();
631 if ( topitem ) {
632 setClipboard( *topitem, Clipboard | Selection );
634 if ( m_bReplayActionInHistory && m_bURLGrabber ) {
635 slotRepeatAction();
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" ;
653 #if 0
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;
663 selection = false;
665 else
666 selection = true;
669 if ( isSelection )
670 *isSelection = selection;
672 #endif
674 return 0;
677 void Klipper::applyClipChanges( const QMimeData* clipData )
679 if ( m_locklevel )
680 return;
681 Ignore lock( m_locklevel );
682 history()->insert( HistoryItem::create( clipData ) );
686 void Klipper::newClipData( bool selectionMode )
688 if ( m_locklevel ) {
689 return;
692 if( blockFetchingNewData())
693 return;
695 checkClipData( selectionMode );
699 void Klipper::clipboardSignalArrived( bool selectionMode )
701 if ( m_locklevel ) {
702 return;
704 if( blockFetchingNewData())
705 return;
707 updateTimestamp();
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.
730 Window root, child;
731 int root_x, root_y, win_x, win_y;
732 uint state;
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 );
739 return true;
741 m_pendingContentsCheck = false;
742 if( ++m_overflowCounter > MAX_CLIPBOARD_CHANGES )
743 return true;
744 return false;
747 void Klipper::slotCheckPending()
749 if( !m_pendingContentsCheck )
750 return;
751 m_pendingContentsCheck = false; // blockFetchingNewData() will be called again
752 updateTimestamp();
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();
765 if ( top ) {
766 setClipboard( *top, selectionMode ? Selection : Clipboard);
768 return;
771 // debug code
772 #ifdef NOISY_KLIPPER
773 kDebug() << "Checking clip data";
774 #endif
775 #if 0
776 kDebug() << "====== c h e c k C l i p D a t a ============================"
777 << kBacktrace()
778 << "====== c h e c k C l i p D a t a ============================"
779 << endl;;
780 #endif
781 #if 0
782 if ( sender() ) {
783 kDebug() << "sender=" << sender()->name();
784 } else {
785 kDebug() << "no sender";
787 #endif
788 #if 0
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;
794 #endif
795 #if 0
796 const char *format;
797 int i = 0;
798 while ( (format = clip->data()->format( i++ )) )
800 qDebug( " format: %s", format);
802 #endif
803 const QMimeData* data = m_clip->mimeData( selectionMode ? QClipboard::Selection : QClipboard::Clipboard );
804 if ( !data ) {
805 kWarning("No data in clipboard. This not not supposed to happen." );
806 return;
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();
816 if ( top ) {
817 // keep old clipboard after someone set it to null
818 #ifdef NOISY_KLIPPER
819 kDebug() << "Resetting clipboard (Prevent empty clipboard)";
820 #endif
821 setClipboard( *top, selectionMode ? Selection : Clipboard );
823 return;
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 )
830 return;
832 if( selectionMode && m_bSelectionTextOnly && !data->hasText())
833 return;
835 if( KUrl::List::canDecode( data ) )
836 ; // ok
837 else if( data->hasText() )
838 ; // ok
839 else if( data->hasImage() )
841 if( m_bIgnoreImages )
842 return;
844 else // unknown, ignore
845 return;
847 // store old contents:
848 #if 0
849 if ( selectionMode )
850 m_lastSelection = data->serialNumber();
851 else
852 m_lastClipboard = data->serialNumber();
853 #endif
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
866 // work.
867 if ( text != lastURLGrabberText )
869 lastURLGrabberText = text;
870 if ( m_myURLGrabber->checkNewData( text ) )
872 return; // don't add into the history
876 else
877 lastURLGrabberText = QString();
879 else
880 lastURLGrabberText = QString();
882 if (changed) {
883 applyClipChanges( data );
884 #ifdef NOISY_KLIPPER
885 kDebug() << "Synchronize?" << m_bSynchronize;
886 #endif
887 if ( m_bSynchronize ) {
888 const HistoryItem* topItem = history()->first();
889 if ( topItem ) {
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 ) {
903 #ifdef NOISY_KLIPPER
904 kDebug() << "Setting selection to <" << item.text() << ">";
905 #endif
906 m_clip->setMimeData( item.mimeData(), QClipboard::Selection );
907 #if 0
908 m_lastSelection = clip->data()->serialNumber();<
909 #endif
911 if ( mode & Clipboard ) {
912 #ifdef NOISY_KLIPPER
913 kDebug() << "Setting clipboard to <" << item.text() << ">";
914 #endif
915 m_clip->setMimeData( item.mimeData(), QClipboard::Clipboard );
916 #if 0
917 m_lastClipboard = clip->data()->serialNumber();
918 #endif
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()
935 QStringList menu;
937 History::iterator it = history()->youngest();
938 while (it.hasNext()) {
939 const HistoryItem *item = it.next();
940 menu << item->text();
943 return menu;
946 QString Klipper::getClipboardHistoryItem(int i)
948 History::iterator it = history()->youngest();
949 while (it.hasNext()) {
950 const HistoryItem *item = it.next();
951 if ( i == 0 ) {
952 return item->text();
954 i--;
956 return QString();
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();
968 if ( focusWidget )
970 if ( focusWidget->inherits( "QSpinBox" ) ||
971 (focusWidget->parentWidget() &&
972 focusWidget->inherits("QLineEdit") &&
973 focusWidget->parentWidget()->inherits("QSpinWidget")) )
975 return true;
979 return false;
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 )
995 return False;
996 // from qapplication_x11.cpp
997 switch ( event->type ) {
998 case ButtonPress:
999 // fallthrough intended
1000 case ButtonRelease:
1001 next_x_time = event->xbutton.time;
1002 break;
1003 case MotionNotify:
1004 next_x_time = event->xmotion.time;
1005 break;
1006 case KeyPress:
1007 // fallthrough intended
1008 case KeyRelease:
1009 next_x_time = event->xkey.time;
1010 break;
1011 case PropertyNotify:
1012 next_x_time = event->xproperty.time;
1013 break;
1014 case EnterNotify:
1015 case LeaveNotify:
1016 next_x_time = event->xcrossing.time;
1017 break;
1018 case SelectionClear:
1019 next_x_time = event->xselectionclear.time;
1020 break;
1021 default:
1022 break;
1024 return False;
1027 void Klipper::updateTimestamp()
1029 static QWidget* w = 0;
1030 if ( !w )
1031 w = new QWidget;
1032 unsigned char data[ 1 ];
1033 XChangeProperty( QX11Info::display(), w->winId(), XA_ATOM, XA_ATOM, 8, PropModeAppend, data, 1 );
1034 next_x_time = CurrentTime;
1035 XEvent dummy;
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"),
1060 ki18n("Author"),
1061 "pfeiffer@kde.org");
1063 m_about_data->addAuthor(ki18n("Andrew Stanley-Jones"),
1064 ki18n( "Original Author" ),
1065 "asj@cban.com");
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"),
1073 "l.lunak@kde.org");
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 );
1098 cg.sync();
1099 kapp->setSynchronizeClipboard(false);
1100 KGlobalSettings::self()->emitChange( KGlobalSettings::ClipboardConfigChanged, 0 );
1106 #include "klipper.moc"