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 "urlgrabber.h"
42 // - script-interface?
44 URLGrabber::URLGrabber(const KSharedConfigPtr
&config
)
48 m_config
= KGlobal::config();
50 m_myCurrentAction
= 0L;
52 m_myPopupKillTimeout
= 8;
55 m_myActions
= new ActionList();
57 readConfiguration( m_config
.data() );
59 m_myPopupKillTimer
= new QTimer( this );
60 m_myPopupKillTimer
->setSingleShot( true );
61 connect( m_myPopupKillTimer
, SIGNAL( timeout() ),
62 SLOT( slotKillPopupMenu() ));
67 action = new ClipAction( "^http:\\/\\/", "Web-URL" );
68 action->addCommand("kfmclient exec %s", "Open with Konqi", true);
69 action->addCommand("netscape -no-about-splash -remote \"openURL(%s, new-window)\"", "Open with Netscape", true);
70 m_myActions->append( action );
72 action = new ClipAction( "^mailto:", "Mail-URL" );
73 action->addCommand("kmail --composer %s", "Launch kmail", true);
74 m_myActions->append( action );
76 action = new ClipAction( "^\\/.+\\.jpg$", "Jpeg-Image" );
77 action->addCommand("kuickshow %s", "Launch KuickShow", true);
78 action->addCommand("kview %s", "Launch KView", true);
79 m_myActions->append( action );
84 URLGrabber::~URLGrabber()
87 ActionListIterator
it( *m_myActions
);
94 // Called from Klipper::slotRepeatAction, i.e. by pressing Ctrl-Alt-R
95 // shortcut. I.e. never from clipboard monitoring
97 void URLGrabber::invokeAction( const QString
& clip
)
99 if ( !clip
.isEmpty() )
102 m_myClipData
= m_myClipData
.trimmed();
108 void URLGrabber::setActionList( ActionList
*list
)
110 ActionListIterator
it( *m_myActions
);
118 const ActionList
& URLGrabber::matchingActions( const QString
& clipData
)
121 ClipAction
*action
= 0L;
123 ActionListIterator
it( *m_myActions
);
124 while (it
.hasNext()) {
126 if ( action
->matches( clipData
) )
127 m_myMatches
.append( action
);
134 bool URLGrabber::checkNewData( const QString
& clipData
)
136 // kDebug() << "** checking new data: " << clipData;
137 m_myClipData
= clipData
;
139 m_myClipData
= m_myClipData
.trimmed();
141 if ( m_myActions
->isEmpty() )
144 actionMenu( true ); // also creates m_myMatches
146 return ( !m_myMatches
.isEmpty() &&
147 (!m_config
->group("General").readEntry("Put Matching URLs in history", true))); //XXX i am not sure this entry exists anymore
151 void URLGrabber::actionMenu( bool wm_class_check
)
153 if ( m_myClipData
.isEmpty() )
156 ActionListIterator
it( matchingActions( m_myClipData
) );
157 ClipAction
*action
= 0L;
158 ClipCommand
*command
= 0L;
161 // don't react on konqi's/netscape's urls...
162 if ( wm_class_check
&& isAvoidedWindow() )
166 m_myCommandMapper
.clear();
168 m_myPopupKillTimer
->stop();
170 m_myMenu
= new KMenu
;
172 connect(m_myMenu
, SIGNAL(triggered(QAction
*)), SLOT(slotItemSelected(QAction
*)));
174 while (it
.hasNext()) {
176 QListIterator
<ClipCommand
*> it2( action
->commands() );
178 m_myMenu
->addTitle(KIcon( "klipper" ),
179 i18n("%1 - Actions For: %2", action
->description(), KStringHandler::csqueeze(m_myClipData
, 45)));
180 while (it2
.hasNext()) {
181 command
= it2
.next();
182 item
= command
->description
;
183 if ( item
.isEmpty() )
184 item
= command
->command
;
186 QString id
= QUuid::createUuid().toString();
187 QAction
* action
= new QAction(this);
189 action
->setText(item
);
191 if (!command
->pixmap
.isEmpty())
192 action
->setIcon(KIcon(command
->pixmap
));
194 m_myCommandMapper
.insert(id
, command
);
195 m_myMenu
->addAction(action
);
199 // only insert this when invoked via clipboard monitoring, not from an
200 // explicit Ctrl-Alt-R
201 if ( wm_class_check
)
203 m_myMenu
->addSeparator();
204 QAction
*disableAction
= new QAction(i18n("Disable This Popup"), this);
205 connect(disableAction
, SIGNAL(triggered()), SIGNAL(sigDisablePopup()));
206 m_myMenu
->addAction(disableAction
);
208 m_myMenu
->addSeparator();
209 // add an edit-possibility
210 QAction
*editAction
= new QAction(KIcon("document-properties"), i18n("&Edit Contents..."), this);
211 connect(editAction
, SIGNAL(triggered()), SLOT(editData()));
212 m_myMenu
->addAction(editAction
);
214 QAction
*cancelAction
= new QAction(KIcon("dialog-cancel"), i18n("&Cancel"), this);
215 connect(cancelAction
, SIGNAL(triggered()), m_myMenu
, SLOT(hide()));
216 m_myMenu
->addAction(cancelAction
);
218 if ( m_myPopupKillTimeout
> 0 )
219 m_myPopupKillTimer
->start( 1000 * m_myPopupKillTimeout
);
221 emit
sigPopup( m_myMenu
);
226 void URLGrabber::slotItemSelected(QAction
*action
)
229 m_myMenu
->hide(); // deleted by the timer or the next action
231 QString id
= action
->data().toString();
234 kDebug() << "Klipper: no command associated";
238 QHash
<QString
, ClipCommand
*>::iterator i
= m_myCommandMapper
.find(id
);
239 ClipCommand
*command
= i
.value();
244 kDebug() << "Klipper: cannot find associated action";
248 void URLGrabber::execute( const struct ClipCommand
*command
) const
250 if ( command
->isEnabled
) {
251 QHash
<QChar
,QString
> map
;
252 map
.insert( 's', m_myClipData
);
253 // commands executed should always have a parent,
254 // but a simple check won't hurt...
255 if ( command
->parent
)
257 const QStringList matches
= command
->parent
->regExpMatches();
258 // support only %0 and the first 9 matches...
259 const int numMatches
= qMin(10, matches
.count());
260 for ( int i
= 0; i
< numMatches
; ++i
)
261 map
.insert( QChar( '0' + i
), matches
.at( i
) );
265 kDebug() << "No parent for" << command
->description
<< "(" << command
->command
<< ")";
267 QString cmdLine
= KMacroExpander::expandMacrosShellQuote( command
->command
, map
);
269 if ( cmdLine
.isEmpty() )
273 proc
.setShellCommand(cmdLine
.trimmed());
274 if (!proc
.startDetached())
275 kDebug() << "Klipper: Could not start process!";
280 void URLGrabber::editData()
282 m_myPopupKillTimer
->stop();
283 KDialog
*dlg
= new KDialog( 0 );
284 dlg
->setModal( true );
285 dlg
->setCaption( i18n("Edit Contents") );
286 dlg
->setButtons( KDialog::Ok
| KDialog::Cancel
);
288 KTextEdit
*edit
= new KTextEdit( dlg
);
289 edit
->setText( m_myClipData
);
291 edit
->setMinimumSize( 300, 40 );
292 dlg
->setMainWidget( edit
);
295 if ( dlg
->exec() == KDialog::Accepted
) {
296 m_myClipData
= edit
->toPlainText();
297 QTimer::singleShot( 0, this, SLOT( slotActionMenu() ) );
301 m_myMenu
->deleteLater();
308 void URLGrabber::readConfiguration( KConfig
*kc
)
310 ActionListIterator
it( *m_myActions
);
313 m_myActions
->clear();
314 KConfigGroup
cg(kc
, "General");
315 int num
= cg
.readEntry("Number of Actions", 0);
316 m_myAvoidWindows
= cg
.readEntry("No Actions for WM_CLASS",QStringList());
317 m_myPopupKillTimeout
= cg
.readEntry( "Timeout for Action popups (seconds)", 8 );
318 m_trimmed
= cg
.readEntry("Strip Whitespace before exec", true);
320 for ( int i
= 0; i
< num
; i
++ ) {
321 group
= QString("Action_%1").arg( i
);
322 m_myActions
->append( new ClipAction( kc
, group
) );
327 void URLGrabber::writeConfiguration( KConfig
*kc
)
329 KConfigGroup
cg(kc
, "General");
330 cg
.writeEntry( "Number of Actions", m_myActions
->count() );
331 cg
.writeEntry( "Timeout for Action popups (seconds)", m_myPopupKillTimeout
);
332 cg
.writeEntry( "No Actions for WM_CLASS", m_myAvoidWindows
);
333 cg
.writeEntry( "Strip Whitespace before exec", m_trimmed
);
335 ActionListIterator
it( *m_myActions
);
340 while (it
.hasNext()) {
342 group
= QString("Action_%1").arg( i
);
343 action
->save( kc
, group
);
348 // find out whether the active window's WM_CLASS is in our avoid-list
349 // digged a little bit in netwm.cpp
350 bool URLGrabber::isAvoidedWindow() const
352 Display
*d
= QX11Info::display();
353 static Atom wm_class
= XInternAtom( d
, "WM_CLASS", true );
354 static Atom active_window
= XInternAtom( d
, "_NET_ACTIVE_WINDOW", true );
357 unsigned long nitems_ret
, unused
;
358 unsigned char *data_ret
;
364 // get the active window
365 if (XGetWindowProperty(d
, DefaultRootWindow( d
), active_window
, 0l, 1l,
366 False
, XA_WINDOW
, &type_ret
, &format_ret
,
367 &nitems_ret
, &unused
, &data_ret
)
369 if (type_ret
== XA_WINDOW
&& format_ret
== 32 && nitems_ret
== 1) {
370 active
= *((Window
*) data_ret
);
377 // get the class of the active window
378 if ( XGetWindowProperty(d
, active
, wm_class
, 0L, BUFSIZE
, False
, XA_STRING
,
379 &type_ret
, &format_ret
, &nitems_ret
,
380 &unused
, &data_ret
) == Success
) {
381 if ( type_ret
== XA_STRING
&& format_ret
== 8 && nitems_ret
> 0 ) {
382 wmClass
= QString::fromUtf8( (const char *) data_ret
);
383 ret
= (m_myAvoidWindows
.indexOf( wmClass
) != -1);
393 void URLGrabber::slotKillPopupMenu()
395 if ( m_myMenu
&& m_myMenu
->isVisible() )
397 if ( m_myMenu
->geometry().contains( QCursor::pos() ) &&
398 m_myPopupKillTimeout
> 0 )
400 m_myPopupKillTimer
->start( 1000 * m_myPopupKillTimeout
);
406 m_myMenu
->deleteLater();
411 ///////////////////////////////////////////////////////////////////////////
414 ClipCommand::ClipCommand(ClipAction
*_parent
, const QString
&_command
, const QString
&_description
,
415 bool _isEnabled
, const QString
&_icon
)
418 description(_description
),
419 isEnabled(_isEnabled
)
421 int len
= command
.indexOf(" ");
423 len
= command
.length();
425 if (!_icon
.isEmpty())
429 KService::Ptr service
= KService::serviceByDesktopName(command
.left(len
));
431 pixmap
= service
->icon();
438 ClipAction::ClipAction( const QString
& regExp
, const QString
& description
)
439 : m_myRegExp( regExp
), m_myDescription( description
)
444 ClipAction::ClipAction( const ClipAction
& action
)
446 m_myRegExp
= action
.m_myRegExp
;
447 m_myDescription
= action
.m_myDescription
;
449 ClipCommand
*command
= 0L;
450 QListIterator
<ClipCommand
*> it( m_myCommands
);
451 while (it
.hasNext()) {
453 addCommand(command
->command
, command
->description
, command
->isEnabled
);
458 ClipAction::ClipAction( KConfig
*kc
, const QString
& group
)
459 : m_myRegExp( kc
->group(group
).readEntry("Regexp") ),
460 m_myDescription (kc
->group(group
).readEntry("Description") )
462 KConfigGroup
cg(kc
, group
);
464 int num
= cg
.readEntry( "Number of commands", 0 );
467 for ( int i
= 0; i
< num
; i
++ ) {
468 QString _group
= group
+ "/Command_%1";
469 KConfigGroup
_cg(kc
, _group
.arg(i
));
471 addCommand( _cg
.readPathEntry( "Commandline", QString() ),
472 _cg
.readEntry( "Description" ), // i18n'ed
473 _cg
.readEntry( "Enabled" , false),
474 _cg
.readEntry( "Icon") );
479 ClipAction::~ClipAction()
481 qDeleteAll(m_myCommands
);
485 void ClipAction::addCommand( const QString
& command
,
486 const QString
& description
, bool enabled
, const QString
& icon
)
488 if ( command
.isEmpty() )
491 struct ClipCommand
*cmd
= new ClipCommand( this, command
, description
, enabled
, icon
);
492 // cmd->id = m_myCommands.count(); // superfluous, I think...
493 m_myCommands
.append( cmd
);
497 // precondition: we're in the correct action's group of the KConfig object
498 void ClipAction::save( KConfig
*kc
, const QString
& group
) const
500 KConfigGroup
cg(kc
, group
);
501 cg
.writeEntry( "Description", description() );
502 cg
.writeEntry( "Regexp", regExp() );
503 cg
.writeEntry( "Number of commands", m_myCommands
.count() );
505 struct ClipCommand
*cmd
;
506 QListIterator
<struct ClipCommand
*> it( m_myCommands
);
508 // now iterate over all commands of this action
510 while (it
.hasNext()) {
512 QString _group
= group
+ "/Command_%1";
513 KConfigGroup
cg(kc
, _group
.arg(i
));
515 cg
.writePathEntry( "Commandline", cmd
->command
);
516 cg
.writeEntry( "Description", cmd
->description
);
517 cg
.writeEntry( "Enabled", cmd
->isEnabled
);
523 #include "urlgrabber.moc"