not quite so much needs to be delayed to the init() function
[personal-kdebase.git] / workspace / klipper / urlgrabber.cpp
blobfee297266f2b8f0028f05fea72603b4197e4fea3
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 m_myCurrentAction = 0L;
51 m_myMenu = 0L;
52 m_myPopupKillTimeout = 8;
53 m_trimmed = true;
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() ));
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 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()
86 delete m_myMenu;
87 ActionListIterator it( *m_myActions );
88 while (it.hasNext())
89 delete it.next();
90 delete 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() )
100 m_myClipData = clip;
101 if ( m_trimmed )
102 m_myClipData = m_myClipData.trimmed();
104 actionMenu( false );
108 void URLGrabber::setActionList( ActionList *list )
110 ActionListIterator it( *m_myActions );
111 while (it.hasNext())
112 delete it.next();
113 delete m_myActions;
114 m_myActions = list;
118 const ActionList& URLGrabber::matchingActions( const QString& clipData )
120 m_myMatches.clear();
121 ClipAction *action = 0L;
123 ActionListIterator it( *m_myActions );
124 while (it.hasNext()) {
125 action = it.next();
126 if ( action->matches( clipData ) )
127 m_myMatches.append( action );
130 return m_myMatches;
134 bool URLGrabber::checkNewData( const QString& clipData )
136 // kDebug() << "** checking new data: " << clipData;
137 m_myClipData = clipData;
138 if ( m_trimmed )
139 m_myClipData = m_myClipData.trimmed();
141 if ( m_myActions->isEmpty() )
142 return false;
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() )
154 return;
156 ActionListIterator it( matchingActions( m_myClipData ) );
157 ClipAction *action = 0L;
158 ClipCommand *command = 0L;
160 if (it.hasNext()) {
161 // don't react on konqi's/netscape's urls...
162 if ( wm_class_check && isAvoidedWindow() )
163 return;
165 QString item;
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()) {
175 action = it.next();
176 QListIterator<ClipCommand*> it2( action->commands() );
177 if ( it2.hasNext() )
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);
188 action->setData(id);
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)
228 if (m_myMenu)
229 m_myMenu->hide(); // deleted by the timer or the next action
231 QString id = action->data().toString();
233 if (id.isEmpty()) {
234 kDebug() << "Klipper: no command associated";
235 return;
238 QHash<QString, ClipCommand*>::iterator i = m_myCommandMapper.find(id);
239 ClipCommand *command = i.value();
241 if (command)
242 execute(command);
243 else
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 ) );
263 else
265 kDebug() << "No parent for" << command->description << "(" << command->command << ")";
267 QString cmdLine = KMacroExpander::expandMacrosShellQuote( command->command, map );
269 if ( cmdLine.isEmpty() )
270 return;
272 KProcess proc;
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 );
290 edit->setFocus();
291 edit->setMinimumSize( 300, 40 );
292 dlg->setMainWidget( edit );
293 dlg->adjustSize();
295 if ( dlg->exec() == KDialog::Accepted ) {
296 m_myClipData = edit->toPlainText();
297 QTimer::singleShot( 0, this, SLOT( slotActionMenu() ) );
299 else
301 m_myMenu->deleteLater();
302 m_myMenu = 0;
304 delete dlg;
308 void URLGrabber::readConfiguration( KConfig *kc )
310 ActionListIterator it( *m_myActions );
311 while (it.hasNext())
312 delete it.next();
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);
319 QString group;
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 );
336 ClipAction *action;
338 int i = 0;
339 QString group;
340 while (it.hasNext()) {
341 action = it.next();
342 group = QString("Action_%1").arg( i );
343 action->save( kc, group );
344 ++i;
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 );
355 Atom type_ret;
356 int format_ret;
357 unsigned long nitems_ret, unused;
358 unsigned char *data_ret;
359 long BUFSIZE = 2048;
360 bool ret = false;
361 Window active = 0L;
362 QString wmClass;
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)
368 == Success) {
369 if (type_ret == XA_WINDOW && format_ret == 32 && nitems_ret == 1) {
370 active = *((Window *) data_ret);
372 XFree(data_ret);
374 if ( !active )
375 return false;
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);
386 XFree( data_ret );
389 return ret;
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 );
401 return;
405 if ( m_myMenu ) {
406 m_myMenu->deleteLater();
407 m_myMenu = 0;
411 ///////////////////////////////////////////////////////////////////////////
412 ////////
414 ClipCommand::ClipCommand(ClipAction *_parent, const QString &_command, const QString &_description,
415 bool _isEnabled, const QString &_icon)
416 : parent(_parent),
417 command(_command),
418 description(_description),
419 isEnabled(_isEnabled)
421 int len = command.indexOf(" ");
422 if (len == -1)
423 len = command.length();
425 if (!_icon.isEmpty())
426 pixmap = _icon;
427 else
429 KService::Ptr service= KService::serviceByDesktopName(command.left(len));
430 if (service)
431 pixmap = service->icon();
432 else
433 pixmap.clear();
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()) {
452 command = it.next();
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 );
466 // read the commands
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() )
489 return;
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
509 int i = 0;
510 while (it.hasNext()) {
511 cmd = it.next();
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 );
519 ++i;
523 #include "urlgrabber.moc"