1 // -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*-
2 /* This file is part of the KDE project
3 Copyright (C) (C) 2000,2001,2002 by Carsten Pfeiffer <pfeiffer@kde.org>
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; see the file COPYING. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
29 #include <ktextedit.h>
35 #include <kstringhandler.h>
36 #include <kmacroexpander.h>
39 #include "klippersettings.h"
40 #include "urlgrabber.h"
43 // - script-interface?
45 URLGrabber::URLGrabber()
47 m_myCurrentAction
= 0L;
49 m_myPopupKillTimeout
= 8;
52 m_myPopupKillTimer
= new QTimer( this );
53 m_myPopupKillTimer
->setSingleShot( true );
54 connect( m_myPopupKillTimer
, SIGNAL( timeout() ),
55 SLOT( slotKillPopupMenu() ));
60 action = new ClipAction( "^http:\\/\\/", "Web-URL" );
61 action->addCommand("kfmclient exec %s", "Open with Konqi", true);
62 action->addCommand("netscape -no-about-splash -remote \"openURL(%s, new-window)\"", "Open with Netscape", true);
63 m_myActions->append( action );
65 action = new ClipAction( "^mailto:", "Mail-URL" );
66 action->addCommand("kmail --composer %s", "Launch kmail", true);
67 m_myActions->append( action );
69 action = new ClipAction( "^\\/.+\\.jpg$", "Jpeg-Image" );
70 action->addCommand("kuickshow %s", "Launch KuickShow", true);
71 action->addCommand("kview %s", "Launch KView", true);
72 m_myActions->append( action );
77 URLGrabber::~URLGrabber()
79 qDeleteAll(m_myActions
);
85 // Called from Klipper::slotRepeatAction, i.e. by pressing Ctrl-Alt-R
86 // shortcut. I.e. never from clipboard monitoring
88 void URLGrabber::invokeAction( const QString
& clip
)
90 if ( !clip
.isEmpty() )
93 m_myClipData
= m_myClipData
.trimmed();
99 void URLGrabber::setActionList( const ActionList
& list
)
101 qDeleteAll(m_myActions
);
107 const ActionList
& URLGrabber::matchingActions( const QString
& clipData
)
111 foreach (ClipAction
* action
, m_myActions
) {
112 if ( action
->matches( clipData
) )
113 m_myMatches
.append( action
);
120 bool URLGrabber::checkNewData( const QString
& clipData
)
122 // kDebug() << "** checking new data: " << clipData;
123 m_myClipData
= clipData
;
125 m_myClipData
= m_myClipData
.trimmed();
127 if ( m_myActions
.isEmpty() )
130 actionMenu( true ); // also creates m_myMatches
132 return !m_myMatches
.isEmpty();
136 void URLGrabber::actionMenu( bool wm_class_check
)
138 if ( m_myClipData
.isEmpty() )
141 ActionList matchingActionsList
= matchingActions( m_myClipData
);
142 ClipCommand
*command
= 0L;
144 if (!matchingActionsList
.isEmpty()) {
145 // don't react on konqi's/netscape's urls...
146 if ( wm_class_check
&& isAvoidedWindow() )
150 m_myCommandMapper
.clear();
152 m_myPopupKillTimer
->stop();
154 m_myMenu
= new KMenu
;
156 connect(m_myMenu
, SIGNAL(triggered(QAction
*)), SLOT(slotItemSelected(QAction
*)));
158 foreach (ClipAction
* action
, matchingActionsList
) {
159 QListIterator
<ClipCommand
*> it( action
->commands() );
161 m_myMenu
->addTitle(KIcon( "klipper" ),
162 i18n("%1 - Actions For: %2", action
->description(), KStringHandler::csqueeze(m_myClipData
, 45)));
163 while (it
.hasNext()) {
165 item
= command
->description
;
166 if ( item
.isEmpty() )
167 item
= command
->command
;
169 QString id
= QUuid::createUuid().toString();
170 QAction
* action
= new QAction(this);
172 action
->setText(item
);
174 if (!command
->pixmap
.isEmpty())
175 action
->setIcon(KIcon(command
->pixmap
));
177 m_myCommandMapper
.insert(id
, command
);
178 m_myMenu
->addAction(action
);
182 // only insert this when invoked via clipboard monitoring, not from an
183 // explicit Ctrl-Alt-R
184 if ( wm_class_check
)
186 m_myMenu
->addSeparator();
187 QAction
*disableAction
= new QAction(i18n("Disable This Popup"), this);
188 connect(disableAction
, SIGNAL(triggered()), SIGNAL(sigDisablePopup()));
189 m_myMenu
->addAction(disableAction
);
191 m_myMenu
->addSeparator();
192 // add an edit-possibility
193 QAction
*editAction
= new QAction(KIcon("document-properties"), i18n("&Edit Contents..."), this);
194 connect(editAction
, SIGNAL(triggered()), SLOT(editData()));
195 m_myMenu
->addAction(editAction
);
197 QAction
*cancelAction
= new QAction(KIcon("dialog-cancel"), i18n("&Cancel"), this);
198 connect(cancelAction
, SIGNAL(triggered()), m_myMenu
, SLOT(hide()));
199 m_myMenu
->addAction(cancelAction
);
201 if ( m_myPopupKillTimeout
> 0 )
202 m_myPopupKillTimer
->start( 1000 * m_myPopupKillTimeout
);
204 emit
sigPopup( m_myMenu
);
209 void URLGrabber::slotItemSelected(QAction
*action
)
212 m_myMenu
->hide(); // deleted by the timer or the next action
214 QString id
= action
->data().toString();
217 kDebug() << "Klipper: no command associated";
221 QHash
<QString
, ClipCommand
*>::iterator i
= m_myCommandMapper
.find(id
);
222 ClipCommand
*command
= i
.value();
227 kDebug() << "Klipper: cannot find associated action";
231 void URLGrabber::execute( const struct ClipCommand
*command
) const
233 if ( command
->isEnabled
) {
234 QHash
<QChar
,QString
> map
;
235 map
.insert( 's', m_myClipData
);
236 // commands executed should always have a parent,
237 // but a simple check won't hurt...
238 if ( command
->parent
)
240 const QStringList matches
= command
->parent
->regExpMatches();
241 // support only %0 and the first 9 matches...
242 const int numMatches
= qMin(10, matches
.count());
243 for ( int i
= 0; i
< numMatches
; ++i
)
244 map
.insert( QChar( '0' + i
), matches
.at( i
) );
248 kDebug() << "No parent for" << command
->description
<< "(" << command
->command
<< ")";
250 QString cmdLine
= KMacroExpander::expandMacrosShellQuote( command
->command
, map
);
252 if ( cmdLine
.isEmpty() )
256 proc
.setShellCommand(cmdLine
.trimmed());
257 if (!proc
.startDetached())
258 kDebug() << "Klipper: Could not start process!";
263 void URLGrabber::editData()
265 m_myPopupKillTimer
->stop();
266 KDialog
*dlg
= new KDialog( 0 );
267 dlg
->setModal( true );
268 dlg
->setCaption( i18n("Edit Contents") );
269 dlg
->setButtons( KDialog::Ok
| KDialog::Cancel
);
271 KTextEdit
*edit
= new KTextEdit( dlg
);
272 edit
->setText( m_myClipData
);
274 edit
->setMinimumSize( 300, 40 );
275 dlg
->setMainWidget( edit
);
278 if ( dlg
->exec() == KDialog::Accepted
) {
279 m_myClipData
= edit
->toPlainText();
280 QTimer::singleShot( 0, this, SLOT( slotActionMenu() ) );
284 m_myMenu
->deleteLater();
291 void URLGrabber::loadSettings()
293 m_trimmed
= KlipperSettings::stripWhiteSpace();
294 m_myAvoidWindows
= KlipperSettings::noActionsForWM_CLASS();
295 m_myPopupKillTimeout
= KlipperSettings::timeoutForActionPopups();
297 qDeleteAll(m_myActions
);
300 KConfigGroup
cg(KGlobal::config(), "General");
301 int num
= cg
.readEntry("Number of Actions", 0);
303 for ( int i
= 0; i
< num
; i
++ ) {
304 group
= QString("Action_%1").arg( i
);
305 m_myActions
.append( new ClipAction( KGlobal::config(), group
) );
309 void URLGrabber::saveSettings() const
311 KConfigGroup
cg(KGlobal::config(), "General");
312 cg
.writeEntry( "Number of Actions", m_myActions
.count() );
316 foreach (ClipAction
* action
, m_myActions
) {
317 group
= QString("Action_%1").arg( i
);
318 action
->save( KGlobal::config(), group
);
322 KlipperSettings::setNoActionsForWM_CLASS(m_myAvoidWindows
);
325 // find out whether the active window's WM_CLASS is in our avoid-list
326 // digged a little bit in netwm.cpp
327 bool URLGrabber::isAvoidedWindow() const
329 Display
*d
= QX11Info::display();
330 static Atom wm_class
= XInternAtom( d
, "WM_CLASS", true );
331 static Atom active_window
= XInternAtom( d
, "_NET_ACTIVE_WINDOW", true );
334 unsigned long nitems_ret
, unused
;
335 unsigned char *data_ret
;
341 // get the active window
342 if (XGetWindowProperty(d
, DefaultRootWindow( d
), active_window
, 0l, 1l,
343 False
, XA_WINDOW
, &type_ret
, &format_ret
,
344 &nitems_ret
, &unused
, &data_ret
)
346 if (type_ret
== XA_WINDOW
&& format_ret
== 32 && nitems_ret
== 1) {
347 active
= *((Window
*) data_ret
);
354 // get the class of the active window
355 if ( XGetWindowProperty(d
, active
, wm_class
, 0L, BUFSIZE
, False
, XA_STRING
,
356 &type_ret
, &format_ret
, &nitems_ret
,
357 &unused
, &data_ret
) == Success
) {
358 if ( type_ret
== XA_STRING
&& format_ret
== 8 && nitems_ret
> 0 ) {
359 wmClass
= QString::fromUtf8( (const char *) data_ret
);
360 ret
= (m_myAvoidWindows
.indexOf( wmClass
) != -1);
370 void URLGrabber::slotKillPopupMenu()
372 if ( m_myMenu
&& m_myMenu
->isVisible() )
374 if ( m_myMenu
->geometry().contains( QCursor::pos() ) &&
375 m_myPopupKillTimeout
> 0 )
377 m_myPopupKillTimer
->start( 1000 * m_myPopupKillTimeout
);
383 m_myMenu
->deleteLater();
388 ///////////////////////////////////////////////////////////////////////////
391 ClipCommand::ClipCommand(ClipAction
*_parent
, const QString
&_command
, const QString
&_description
,
392 bool _isEnabled
, const QString
&_icon
)
395 description(_description
),
396 isEnabled(_isEnabled
)
398 int len
= command
.indexOf(" ");
400 len
= command
.length();
402 if (!_icon
.isEmpty())
406 KService::Ptr service
= KService::serviceByDesktopName(command
.left(len
));
408 pixmap
= service
->icon();
415 ClipAction::ClipAction( const QString
& regExp
, const QString
& description
)
416 : m_myRegExp( regExp
), m_myDescription( description
)
421 ClipAction::ClipAction( const ClipAction
& action
)
423 m_myRegExp
= action
.m_myRegExp
;
424 m_myDescription
= action
.m_myDescription
;
426 ClipCommand
*command
= 0L;
427 QListIterator
<ClipCommand
*> it( m_myCommands
);
428 while (it
.hasNext()) {
430 addCommand(command
->command
, command
->description
, command
->isEnabled
);
435 ClipAction::ClipAction( KSharedConfigPtr kc
, const QString
& group
)
436 : m_myRegExp( kc
->group(group
).readEntry("Regexp") ),
437 m_myDescription (kc
->group(group
).readEntry("Description") )
439 KConfigGroup
cg(kc
, group
);
441 int num
= cg
.readEntry( "Number of commands", 0 );
444 for ( int i
= 0; i
< num
; i
++ ) {
445 QString _group
= group
+ "/Command_%1";
446 KConfigGroup
_cg(kc
, _group
.arg(i
));
448 addCommand( _cg
.readPathEntry( "Commandline", QString() ),
449 _cg
.readEntry( "Description" ), // i18n'ed
450 _cg
.readEntry( "Enabled" , false),
451 _cg
.readEntry( "Icon") );
456 ClipAction::~ClipAction()
458 qDeleteAll(m_myCommands
);
462 void ClipAction::addCommand( const QString
& command
,
463 const QString
& description
, bool enabled
, const QString
& icon
)
465 if ( command
.isEmpty() )
468 struct ClipCommand
*cmd
= new ClipCommand( this, command
, description
, enabled
, icon
);
469 // cmd->id = m_myCommands.count(); // superfluous, I think...
470 m_myCommands
.append( cmd
);
474 // precondition: we're in the correct action's group of the KConfig object
475 void ClipAction::save( KSharedConfigPtr kc
, const QString
& group
) const
477 KConfigGroup
cg(kc
, group
);
478 cg
.writeEntry( "Description", description() );
479 cg
.writeEntry( "Regexp", regExp() );
480 cg
.writeEntry( "Number of commands", m_myCommands
.count() );
482 struct ClipCommand
*cmd
;
483 QListIterator
<struct ClipCommand
*> it( m_myCommands
);
485 // now iterate over all commands of this action
487 while (it
.hasNext()) {
489 QString _group
= group
+ "/Command_%1";
490 KConfigGroup
cg(kc
, _group
.arg(i
));
492 cg
.writePathEntry( "Commandline", cmd
->command
);
493 cg
.writeEntry( "Description", cmd
->description
);
494 cg
.writeEntry( "Enabled", cmd
->isEnabled
);
500 #include "urlgrabber.moc"