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();
52 myPopupKillTimeout
= 8;
55 myActions
= new ActionList();
57 readConfiguration( m_config
.data() );
59 myPopupKillTimer
= new QTimer( this );
60 myPopupKillTimer
->setSingleShot( true );
61 connect( 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 myActions->append( action );
72 action = new ClipAction( "^mailto:", "Mail-URL" );
73 action->addCommand("kmail --composer %s", "Launch kmail", true);
74 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 myActions->append( action );
84 URLGrabber::~URLGrabber()
89 qDeleteAll(myMatches
);
93 // Called from Klipper::slotRepeatAction, i.e. by pressing Ctrl-Alt-R
94 // shortcut. I.e. never from clipboard monitoring
96 void URLGrabber::invokeAction( const QString
& clip
)
98 if ( !clip
.isEmpty() )
101 myClipData
= myClipData
.trimmed();
107 void URLGrabber::setActionList( ActionList
*list
)
114 const ActionList
& URLGrabber::matchingActions( const QString
& clipData
)
117 ClipAction
*action
= 0L;
119 ActionListIterator
it( *myActions
);
120 while (it
.hasNext()) {
122 if ( action
->matches( clipData
) )
123 myMatches
.append( action
);
130 bool URLGrabber::checkNewData( const QString
& clipData
)
132 // kDebug() << "** checking new data: " << clipData;
133 myClipData
= clipData
;
135 myClipData
= myClipData
.trimmed();
137 if ( myActions
->isEmpty() )
140 actionMenu( true ); // also creates myMatches
142 return ( !myMatches
.isEmpty() &&
143 (!m_config
->group("General").readEntry("Put Matching URLs in history", true))); //XXX i am not sure this entry exists anymore
147 void URLGrabber::actionMenu( bool wm_class_check
)
149 if ( myClipData
.isEmpty() )
152 ActionListIterator
it( matchingActions( myClipData
) );
153 ClipAction
*action
= 0L;
154 ClipCommand
*command
= 0L;
157 // don't react on konqi's/netscape's urls...
158 if ( wm_class_check
&& isAvoidedWindow() )
162 myCommandMapper
.clear();
164 myPopupKillTimer
->stop();
168 connect(myMenu
, SIGNAL(triggered(QAction
*)), SLOT(slotItemSelected(QAction
*)));
170 while (it
.hasNext()) {
172 QListIterator
<ClipCommand
*> it2( action
->commands() );
174 myMenu
->addTitle(KIcon( "klipper" ), action
->description() +
175 i18n(" - Actions For: ") +
176 KStringHandler::csqueeze(myClipData
, 45));
177 while (it2
.hasNext()) {
178 command
= it2
.next();
179 item
= command
->description
;
180 if ( item
.isEmpty() )
181 item
= command
->command
;
183 QString id
= QUuid::createUuid().toString();
184 QAction
* action
= new QAction(this);
186 action
->setText(item
);
188 if (!command
->pixmap
.isEmpty())
189 action
->setIcon(KIcon(command
->pixmap
));
191 myCommandMapper
.insert(id
, command
);
192 myMenu
->addAction(action
);
196 // only insert this when invoked via clipboard monitoring, not from an
197 // explicit Ctrl-Alt-R
198 if ( wm_class_check
)
200 myMenu
->addSeparator();
201 QAction
*disableAction
= new QAction(i18n("Disable This Popup"), this);
202 connect(disableAction
, SIGNAL(triggered()), SIGNAL(sigDisablePopup()));
203 myMenu
->addAction(disableAction
);
205 myMenu
->addSeparator();
206 // add an edit-possibility
207 QAction
*editAction
= new QAction(KIcon("document-properties"), i18n("&Edit Contents..."), this);
208 connect(editAction
, SIGNAL(triggered()), SLOT(editData()));
209 myMenu
->addAction(editAction
);
211 QAction
*cancelAction
= new QAction(KIcon("dialog-cancel"), i18n("&Cancel"), this);
212 connect(cancelAction
, SIGNAL(triggered()), myMenu
, SLOT(hide()));
213 myMenu
->addAction(cancelAction
);
215 if ( myPopupKillTimeout
> 0 )
216 myPopupKillTimer
->start( 1000 * myPopupKillTimeout
);
218 emit
sigPopup( myMenu
);
223 void URLGrabber::slotItemSelected(QAction
*action
)
225 myMenu
->hide(); // deleted by the timer or the next action
227 QString id
= action
->data().toString();
230 kDebug() << "Klipper: no command associated";
234 QHash
<QString
, ClipCommand
*>::iterator i
= myCommandMapper
.find(id
);
235 ClipCommand
*command
= i
.value();
240 kDebug() << "Klipper: cannot find associated action";
244 void URLGrabber::execute( const struct ClipCommand
*command
) const
246 if ( command
->isEnabled
) {
247 QHash
<QChar
,QString
> map
;
248 map
.insert( 's', myClipData
);
249 QString cmdLine
= KMacroExpander::expandMacrosShellQuote( command
->command
, map
);
251 if ( cmdLine
.isEmpty() )
255 proc
.setShellCommand(cmdLine
.trimmed());
256 if (!proc
.startDetached())
257 kDebug() << "Klipper: Could not start process!";
262 void URLGrabber::editData()
264 myPopupKillTimer
->stop();
265 KDialog
*dlg
= new KDialog( 0 );
266 dlg
->setModal( true );
267 dlg
->setCaption( i18n("Edit Contents") );
268 dlg
->setButtons( KDialog::Ok
| KDialog::Cancel
);
270 KTextEdit
*edit
= new KTextEdit( dlg
);
271 edit
->setText( myClipData
);
273 edit
->setMinimumSize( 300, 40 );
274 dlg
->setMainWidget( edit
);
277 if ( dlg
->exec() == KDialog::Accepted
) {
278 myClipData
= edit
->toPlainText();
279 QTimer::singleShot( 0, this, SLOT( slotActionMenu() ) );
289 void URLGrabber::readConfiguration( KConfig
*kc
)
292 KConfigGroup
cg(kc
, "General");
293 int num
= cg
.readEntry("Number of Actions", 0);
294 myAvoidWindows
= cg
.readEntry("No Actions for WM_CLASS",QStringList());
295 myPopupKillTimeout
= cg
.readEntry( "Timeout for Action popups (seconds)", 8 );
296 m_trimmed
= cg
.readEntry("Strip Whitespace before exec", true);
298 for ( int i
= 0; i
< num
; i
++ ) {
299 group
= QString("Action_%1").arg( i
);
300 myActions
->append( new ClipAction( kc
, group
) );
305 void URLGrabber::writeConfiguration( KConfig
*kc
)
307 KConfigGroup
cg(kc
, "General");
308 cg
.writeEntry( "Number of Actions", myActions
->count() );
309 cg
.writeEntry( "Timeout for Action popups (seconds)", myPopupKillTimeout
);
310 cg
.writeEntry( "No Actions for WM_CLASS", myAvoidWindows
);
311 cg
.writeEntry( "Strip Whitespace before exec", m_trimmed
);
313 ActionListIterator
it( *myActions
);
318 while (it
.hasNext()) {
320 group
= QString("Action_%1").arg( i
);
321 action
->save( kc
, group
);
326 // find out whether the active window's WM_CLASS is in our avoid-list
327 // digged a little bit in netwm.cpp
328 bool URLGrabber::isAvoidedWindow() const
330 Display
*d
= QX11Info::display();
331 static Atom wm_class
= XInternAtom( d
, "WM_CLASS", true );
332 static Atom active_window
= XInternAtom( d
, "_NET_ACTIVE_WINDOW", true );
335 unsigned long nitems_ret
, unused
;
336 unsigned char *data_ret
;
342 // get the active window
343 if (XGetWindowProperty(d
, DefaultRootWindow( d
), active_window
, 0l, 1l,
344 False
, XA_WINDOW
, &type_ret
, &format_ret
,
345 &nitems_ret
, &unused
, &data_ret
)
347 if (type_ret
== XA_WINDOW
&& format_ret
== 32 && nitems_ret
== 1) {
348 active
= *((Window
*) data_ret
);
355 // get the class of the active window
356 if ( XGetWindowProperty(d
, active
, wm_class
, 0L, BUFSIZE
, False
, XA_STRING
,
357 &type_ret
, &format_ret
, &nitems_ret
,
358 &unused
, &data_ret
) == Success
) {
359 if ( type_ret
== XA_STRING
&& format_ret
== 8 && nitems_ret
> 0 ) {
360 wmClass
= QString::fromUtf8( (const char *) data_ret
);
361 ret
= (myAvoidWindows
.indexOf( wmClass
) != -1);
371 void URLGrabber::slotKillPopupMenu()
373 if ( myMenu
&& myMenu
->isVisible() )
375 if ( myMenu
->geometry().contains( QCursor::pos() ) &&
376 myPopupKillTimeout
> 0 )
378 myPopupKillTimer
->start( 1000 * myPopupKillTimeout
);
386 ///////////////////////////////////////////////////////////////////////////
389 ClipCommand::ClipCommand(const QString
&_command
, const QString
&_description
,
390 bool _isEnabled
, const QString
&_icon
)
392 description(_description
),
393 isEnabled(_isEnabled
)
395 int len
= command
.indexOf(" ");
397 len
= command
.length();
399 if (!_icon
.isEmpty())
403 KService::Ptr service
= KService::serviceByDesktopName(command
.left(len
));
405 pixmap
= service
->icon();
412 ClipAction::ClipAction( const QString
& regExp
, const QString
& description
)
413 : myRegExp( regExp
), myDescription( description
)
418 ClipAction::ClipAction( const ClipAction
& action
)
420 myRegExp
= action
.myRegExp
;
421 myDescription
= action
.myDescription
;
423 ClipCommand
*command
= 0L;
424 QListIterator
<ClipCommand
*> it( myCommands
);
425 while (it
.hasNext()) {
427 addCommand(command
->command
, command
->description
, command
->isEnabled
);
432 ClipAction::ClipAction( KConfig
*kc
, const QString
& group
)
433 : myRegExp( kc
->group(group
).readEntry("Regexp") ),
434 myDescription (kc
->group(group
).readEntry("Description") )
436 KConfigGroup
cg(kc
, group
);
438 int num
= cg
.readEntry( "Number of commands", 0 );
441 for ( int i
= 0; i
< num
; i
++ ) {
442 QString _group
= group
+ "/Command_%1";
443 KConfigGroup
_cg(kc
, _group
.arg(i
));
445 addCommand( _cg
.readPathEntry( "Commandline", QString() ),
446 _cg
.readEntry( "Description" ), // i18n'ed
447 _cg
.readEntry( "Enabled" , false),
448 _cg
.readEntry( "Icon") );
453 ClipAction::~ClipAction()
455 qDeleteAll(myCommands
);
459 void ClipAction::addCommand( const QString
& command
,
460 const QString
& description
, bool enabled
, const QString
& icon
)
462 if ( command
.isEmpty() )
465 struct ClipCommand
*cmd
= new ClipCommand( command
, description
, enabled
, icon
);
466 // cmd->id = myCommands.count(); // superfluous, I think...
467 myCommands
.append( cmd
);
471 // precondition: we're in the correct action's group of the KConfig object
472 void ClipAction::save( KConfig
*kc
, const QString
& group
) const
474 KConfigGroup
cg(kc
, group
);
475 cg
.writeEntry( "Description", description() );
476 cg
.writeEntry( "Regexp", regExp() );
477 cg
.writeEntry( "Number of commands", myCommands
.count() );
479 struct ClipCommand
*cmd
;
480 QListIterator
<struct ClipCommand
*> it( myCommands
);
482 // now iterate over all commands of this action
484 while (it
.hasNext()) {
486 QString _group
= group
+ "/Command_%1";
487 KConfigGroup
cg(kc
, _group
.arg(i
));
489 cg
.writePathEntry( "Commandline", cmd
->command
);
490 cg
.writeEntry( "Description", cmd
->description
);
491 cg
.writeEntry( "Enabled", cmd
->isEnabled
);
497 #include "urlgrabber.moc"