Make a branch to make krunner Good Enough For Aaron™.
[kdebase/uwolfer.git] / workspace / klipper / klipper.cpp
blobb7729b9ddc14a7a85a1fa1c46fc462993224b97a
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 , locklevel( 0 )
113 , m_config( config )
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" );
156 myURLGrabber = 0L;
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 );
199 if ( !isApplet() ) {
200 popup->plugAction( quitAction );
204 Klipper::~Klipper()
206 delete poll;
207 delete session_managed;
208 delete showTimer;
209 delete hideTimer;
210 delete myURLGrabber;
213 // DCOP
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 );
223 updateTimestamp();
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()
232 updateTimestamp();
233 slotClearClipboard();
236 // DCOP - don't call from Klipper itself
237 void Klipper::clearClipboardHistory()
239 updateTimestamp();
240 slotClearClipboard();
241 history()->slotClear();
242 saveSession();
246 void Klipper::slotStartHideTimer()
248 hideTimer->start();
251 void Klipper::slotStartShowTimer()
253 showTimer->start();
256 void Klipper::showPopupMenu( QMenu *menu )
258 Q_ASSERT( menu != 0L );
260 QSize size = menu->sizeHint(); // geometry is not valid until it's shown
261 if (bPopupAtMouse) {
262 QPoint g = QCursor::pos();
263 if ( size.height() < g.y() )
264 menu->popup(QPoint( g.x(), g.y() - size.height()));
265 else
266 menu->popup(QPoint(g.x(), g.y()));
267 } else {
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()));
275 else
276 menu->popup(QPoint( g.x() + g.width(), g.y() + g.height()));
277 } else
278 abort();
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
292 oldfile = true;
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() ) {
299 return false;
303 if ( !history_file.open( QIODevice::ReadOnly ) ) {
304 kWarning() << failed_load_warning << ": " << history_file.errorString() ;
305 return false;
307 QDataStream file_stream( &history_file );
308 if( file_stream.atEnd()) {
309 kWarning() << failed_load_warning ;
310 return false;
312 QDataStream* history_stream = &file_stream;
313 QByteArray data;
314 if( !oldfile ) {
315 quint32 crc;
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() ;
319 return false;
321 history_stream = new QDataStream( &data, QIODevice::ReadOnly );
323 char* version;
324 *history_stream >> version;
325 delete[] 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
330 // first.
331 QList<HistoryItem*> reverseList;
332 for ( HistoryItem* item = HistoryItem::create( *history_stream );
333 item;
334 item = HistoryItem::create( *history_stream ) )
336 reverseList.prepend( item );
339 for ( QList<HistoryItem*>::const_iterator it = reverseList.begin();
340 it != reverseList.end();
341 ++it )
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;
355 return true;
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 ;
365 return;
367 KSaveFile history_file( history_file_name );
368 if ( !history_file.open() ) {
369 kWarning() << failed_save_warning ;
370 return;
372 QByteArray data;
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 );
384 ds << crc << data;
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 );
450 if ( myURLGrabber )
451 myURLGrabber->writeConfiguration( _kc );
453 kc.sync();
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
460 saveHistory();
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 );
525 delete dlg;
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 ) {
534 return;
537 saveSession();
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
548 return;
549 config.sync();
551 kapp->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() );
573 if ( top ) {
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 ) {
591 delete myURLGrabber;
592 myURLGrabber = 0L;
593 toggleURLGrabAction->setText(i18n("Enable &Actions"));
596 else {
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() {
614 if ( locklevel ) {
615 return;
618 const HistoryItem* topitem = history()->first();
619 if ( topitem ) {
620 setClipboard( *topitem, Clipboard | Selection );
622 if ( bReplayActionInHistory && bURLGrabber ) {
623 slotRepeatAction();
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" ;
644 #if 0
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;
654 selection = false;
656 else
657 selection = true;
660 if ( isSelection )
661 *isSelection = selection;
663 #endif
665 return 0;
668 void Klipper::applyClipChanges( const QMimeData* clipData )
670 if ( locklevel )
671 return;
672 Ignore lock( locklevel );
673 history()->insert( HistoryItem::create( clipData ) );
677 void Klipper::newClipData( bool selectionMode )
679 if ( locklevel ) {
680 return;
683 if( blockFetchingNewData())
684 return;
686 checkClipData( selectionMode );
690 void Klipper::clipboardSignalArrived( bool selectionMode )
692 if ( locklevel ) {
693 return;
695 if( blockFetchingNewData())
696 return;
698 updateTimestamp();
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.
721 Window root, child;
722 int root_x, root_y, win_x, win_y;
723 uint state;
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 );
730 return true;
732 m_pendingContentsCheck = false;
733 if( ++m_overflowCounter > MAX_CLIPBOARD_CHANGES )
734 return true;
735 return false;
738 void Klipper::slotCheckPending()
740 if( !m_pendingContentsCheck )
741 return;
742 m_pendingContentsCheck = false; // blockFetchingNewData() will be called again
743 updateTimestamp();
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();
756 if ( top ) {
757 setClipboard( *top, selectionMode ? Selection : Clipboard);
759 return;
762 // debug code
763 #ifdef NOISY_KLIPPER
764 kDebug() << "Checking clip data";
765 #endif
766 #if 0
767 kDebug() << "====== c h e c k C l i p D a t a ============================"
768 << kBacktrace()
769 << "====== c h e c k C l i p D a t a ============================"
770 << endl;;
771 #endif
772 #if 0
773 if ( sender() ) {
774 kDebug() << "sender=" << sender()->name();
775 } else {
776 kDebug() << "no sender";
778 #endif
779 #if 0
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;
785 #endif
786 #if 0
787 const char *format;
788 int i = 0;
789 while ( (format = clip->data()->format( i++ )) )
791 qDebug( " format: %s", format);
793 #endif
794 const QMimeData* data = clip->mimeData( selectionMode ? QClipboard::Selection : QClipboard::Clipboard );
795 if ( !data ) {
796 kWarning("No data in clipboard. This not not supposed to happen." );
797 return;
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();
807 if ( top ) {
808 // keep old clipboard after someone set it to null
809 #ifdef NOISY_KLIPPER
810 kDebug() << "Resetting clipboard (Prevent empty clipboard)";
811 #endif
812 setClipboard( *top, selectionMode ? Selection : Clipboard );
814 return;
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 )
821 return;
823 if( selectionMode && bSelectionTextOnly && !data->hasText())
824 return;
826 // TODO: This should be maybe extended for KDE4 or at least get a checkbox somewhere in UI
827 if( KUrl::List::canDecode( data ) )
828 ; // ok
829 else if( data->hasText() )
830 ; // ok
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.
836 if( bIgnoreImages )
837 return;
839 else // unknown, ignore
840 return;
842 // store old contents:
843 #if 0
844 if ( selectionMode )
845 m_lastSelection = data->serialNumber();
846 else
847 m_lastClipboard = data->serialNumber();
848 #endif
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
861 // work.
862 if ( text != lastURLGrabberText )
864 lastURLGrabberText = text;
865 if ( myURLGrabber->checkNewData( text ) )
867 return; // don't add into the history
871 else
872 lastURLGrabberText = QString();
874 else
875 lastURLGrabberText = QString();
877 if (changed) {
878 applyClipChanges( data );
879 #ifdef NOISY_KLIPPER
880 kDebug() << "Synchronize?" << ( bSynchronize ? "yes" : "no" );
881 #endif
882 if ( bSynchronize ) {
883 const HistoryItem* topItem = history()->first();
884 if ( topItem ) {
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 ) {
898 #ifdef NOSIY_KLIPPER
899 kDebug() << "Setting selection to <" << item.text() << ">";
900 #endif
901 clip->setMimeData( item.mimeData(), QClipboard::Selection );
902 #if 0
903 m_lastSelection = clip->data()->serialNumber();<
904 #endif
906 if ( mode & Clipboard ) {
907 #ifdef NOSIY_KLIPPER
908 kDebug() << "Setting clipboard to <" << item.text() << ">";
909 #endif
910 clip->setMimeData( item.mimeData(), QClipboard::Clipboard );
911 #if 0
912 m_lastClipboard = clip->data()->serialNumber();
913 #endif
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()
930 QStringList menu;
932 History::iterator it = history()->youngest();
933 while (it.hasNext()) {
934 const HistoryItem *item = it.next();
935 menu << item->text();
938 return menu;
941 QString Klipper::getClipboardHistoryItem(int i)
943 History::iterator it = history()->youngest();
944 while (it.hasNext()) {
945 const HistoryItem *item = it.next();
946 if ( i == 0 ) {
947 return item->text();
949 i--;
951 return QString();
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();
963 if ( focusWidget )
965 if ( focusWidget->inherits( "QSpinBox" ) ||
966 (focusWidget->parentWidget() &&
967 focusWidget->inherits("QLineEdit") &&
968 focusWidget->parentWidget()->inherits("QSpinWidget")) )
970 return true;
974 return false;
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 )
990 return False;
991 // from qapplication_x11.cpp
992 switch ( event->type ) {
993 case ButtonPress:
994 // fallthrough intended
995 case ButtonRelease:
996 next_x_time = event->xbutton.time;
997 break;
998 case MotionNotify:
999 next_x_time = event->xmotion.time;
1000 break;
1001 case KeyPress:
1002 // fallthrough intended
1003 case KeyRelease:
1004 next_x_time = event->xkey.time;
1005 break;
1006 case PropertyNotify:
1007 next_x_time = event->xproperty.time;
1008 break;
1009 case EnterNotify:
1010 case LeaveNotify:
1011 next_x_time = event->xcrossing.time;
1012 break;
1013 case SelectionClear:
1014 next_x_time = event->xselectionclear.time;
1015 break;
1016 default:
1017 break;
1019 return False;
1022 void Klipper::updateTimestamp()
1024 static QWidget* w = 0;
1025 if ( !w )
1026 w = new QWidget;
1027 unsigned char data[ 1 ];
1028 XChangeProperty( QX11Info::display(), w->winId(), XA_ATOM, XA_ATOM, 8, PropModeAppend, data, 1 );
1029 next_x_time = CurrentTime;
1030 XEvent dummy;
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"),
1055 ki18n("Author"),
1056 "pfeiffer@kde.org");
1058 about_data->addAuthor(ki18n("Andrew Stanley-Jones"),
1059 ki18n( "Original Author" ),
1060 "asj@cban.com");
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"),
1068 "l.lunak@kde.org");
1070 about_data->addAuthor( ki18n("Esben Mose Hansen"),
1071 ki18n("Maintainer"),
1072 "kde@mosehansen.dk");
1075 void Klipper::destroyAboutData()
1077 delete about_data;
1078 about_data = NULL;
1081 KAboutData* Klipper::about_data;
1083 KAboutData* Klipper::aboutData()
1085 return about_data;
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 );
1093 cg.sync();
1094 kapp->setSynchronizeClipboard(false);
1095 KGlobalSettings::self()->emitChange( KGlobalSettings::ClipboardConfigChanged, 0 );
1101 #include "klipper.moc"