Make a branch to make krunner Good Enough For Aaron™.
[kdebase/uwolfer.git] / workspace / klipper / urlgrabber.cpp
blob4e6decab5e47895b45e81e45a0c27a0d1d49d218
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.
21 #include <netwm.h>
23 #include <QTimer>
24 #include <QX11Info>
25 #include <QUuid>
27 #include <kconfig.h>
28 #include <kdialog.h>
29 #include <ktextedit.h>
30 #include <klocale.h>
31 #include <kmenu.h>
32 #include <kprocess.h>
33 #include <kservice.h>
34 #include <kdebug.h>
35 #include <kstringhandler.h>
36 #include <kmacroexpander.h>
37 #include <kglobal.h>
39 #include "urlgrabber.h"
41 // TODO:
42 // - script-interface?
44 URLGrabber::URLGrabber(const KSharedConfigPtr &config)
45 : m_config( config )
47 if(!m_config) {
48 m_config = KGlobal::config();
50 myCurrentAction = 0L;
51 myMenu = 0L;
52 myPopupKillTimeout = 8;
53 m_trimmed = true;
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() ));
64 // testing
66 ClipAction *action;
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()
86 if (myMenu)
87 delete myMenu;
88 delete myActions;
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() )
99 myClipData = clip;
100 if ( m_trimmed )
101 myClipData = myClipData.trimmed();
103 actionMenu( false );
107 void URLGrabber::setActionList( ActionList *list )
109 delete myActions;
110 myActions = list;
114 const ActionList& URLGrabber::matchingActions( const QString& clipData )
116 myMatches.clear();
117 ClipAction *action = 0L;
119 ActionListIterator it( *myActions );
120 while (it.hasNext()) {
121 action = it.next();
122 if ( action->matches( clipData ) )
123 myMatches.append( action );
126 return myMatches;
130 bool URLGrabber::checkNewData( const QString& clipData )
132 // kDebug() << "** checking new data: " << clipData;
133 myClipData = clipData;
134 if ( m_trimmed )
135 myClipData = myClipData.trimmed();
137 if ( myActions->isEmpty() )
138 return false;
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() )
150 return;
152 ActionListIterator it( matchingActions( myClipData ) );
153 ClipAction *action = 0L;
154 ClipCommand *command = 0L;
156 if (it.hasNext()) {
157 // don't react on konqi's/netscape's urls...
158 if ( wm_class_check && isAvoidedWindow() )
159 return;
161 QString item;
162 myCommandMapper.clear();
164 myPopupKillTimer->stop();
166 myMenu = new KMenu;
168 connect(myMenu, SIGNAL(triggered(QAction*)), SLOT(slotItemSelected(QAction*)));
170 while (it.hasNext()) {
171 action = it.next();
172 QListIterator<ClipCommand*> it2( action->commands() );
173 if ( it2.hasNext() )
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);
185 action->setData(id);
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();
229 if (id.isEmpty()) {
230 kDebug() << "Klipper: no command associated";
231 return;
234 QHash<QString, ClipCommand*>::iterator i = myCommandMapper.find(id);
235 ClipCommand *command = i.value();
237 if (command)
238 execute(command);
239 else
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() )
252 return;
254 KProcess proc;
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 );
272 edit->setFocus();
273 edit->setMinimumSize( 300, 40 );
274 dlg->setMainWidget( edit );
275 dlg->adjustSize();
277 if ( dlg->exec() == KDialog::Accepted ) {
278 myClipData = edit->toPlainText();
279 QTimer::singleShot( 0, this, SLOT( slotActionMenu() ) );
281 else
283 delete myMenu;
285 delete dlg;
289 void URLGrabber::readConfiguration( KConfig *kc )
291 myActions->clear();
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);
297 QString group;
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 );
314 ClipAction *action;
316 int i = 0;
317 QString group;
318 while (it.hasNext()) {
319 action = it.next();
320 group = QString("Action_%1").arg( i );
321 action->save( kc, group );
322 ++i;
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 );
333 Atom type_ret;
334 int format_ret;
335 unsigned long nitems_ret, unused;
336 unsigned char *data_ret;
337 long BUFSIZE = 2048;
338 bool ret = false;
339 Window active = 0L;
340 QString wmClass;
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)
346 == Success) {
347 if (type_ret == XA_WINDOW && format_ret == 32 && nitems_ret == 1) {
348 active = *((Window *) data_ret);
350 XFree(data_ret);
352 if ( !active )
353 return false;
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);
364 XFree( data_ret );
367 return ret;
371 void URLGrabber::slotKillPopupMenu()
373 if ( myMenu && myMenu->isVisible() )
375 if ( myMenu->geometry().contains( QCursor::pos() ) &&
376 myPopupKillTimeout > 0 )
378 myPopupKillTimer->start( 1000 * myPopupKillTimeout );
379 return;
383 delete myMenu;
386 ///////////////////////////////////////////////////////////////////////////
387 ////////
389 ClipCommand::ClipCommand(const QString &_command, const QString &_description,
390 bool _isEnabled, const QString &_icon)
391 : command(_command),
392 description(_description),
393 isEnabled(_isEnabled)
395 int len = command.indexOf(" ");
396 if (len == -1)
397 len = command.length();
399 if (!_icon.isEmpty())
400 pixmap = _icon;
401 else
403 KService::Ptr service= KService::serviceByDesktopName(command.left(len));
404 if (service)
405 pixmap = service->icon();
406 else
407 pixmap.clear();
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()) {
426 command = it.next();
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 );
440 // read the commands
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() )
463 return;
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
483 int i = 0;
484 while (it.hasNext()) {
485 cmd = it.next();
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 );
493 ++i;
497 #include "urlgrabber.moc"