exclude the two obviously unwanted categories from the screensaver. none of the other...
[kdebase.git] / workspace / klipper / urlgrabber.cpp
blobdeac6a87c9480d382d1ee9a388dda099dfc16eb1
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 "klippersettings.h"
40 #include "urlgrabber.h"
42 // TODO:
43 // - script-interface?
45 URLGrabber::URLGrabber()
47 m_myCurrentAction = 0L;
48 m_myMenu = 0L;
49 m_myPopupKillTimeout = 8;
50 m_trimmed = true;
52 m_myPopupKillTimer = new QTimer( this );
53 m_myPopupKillTimer->setSingleShot( true );
54 connect( m_myPopupKillTimer, SIGNAL( timeout() ),
55 SLOT( slotKillPopupMenu() ));
57 // testing
59 ClipAction *action;
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);
80 m_myActions.clear();
81 delete m_myMenu;
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() )
91 m_myClipData = clip;
92 if ( m_trimmed )
93 m_myClipData = m_myClipData.trimmed();
95 actionMenu( false );
99 void URLGrabber::setActionList( const ActionList& list )
101 qDeleteAll(m_myActions);
102 m_myActions.clear();
103 m_myActions = list;
107 const ActionList& URLGrabber::matchingActions( const QString& clipData )
109 m_myMatches.clear();
111 foreach (ClipAction* action, m_myActions) {
112 if ( action->matches( clipData ) )
113 m_myMatches.append( action );
116 return m_myMatches;
120 bool URLGrabber::checkNewData( const QString& clipData )
122 // kDebug() << "** checking new data: " << clipData;
123 m_myClipData = clipData;
124 if ( m_trimmed )
125 m_myClipData = m_myClipData.trimmed();
127 if ( m_myActions.isEmpty() )
128 return false;
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() )
139 return;
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() )
147 return;
149 QString item;
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() );
160 if ( it.hasNext() )
161 m_myMenu->addTitle(KIcon( "klipper" ),
162 i18n("%1 - Actions For: %2", action->description(), KStringHandler::csqueeze(m_myClipData, 45)));
163 while (it.hasNext()) {
164 command = it.next();
165 item = command->description;
166 if ( item.isEmpty() )
167 item = command->command;
169 QString id = QUuid::createUuid().toString();
170 QAction * action = new QAction(this);
171 action->setData(id);
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)
211 if (m_myMenu)
212 m_myMenu->hide(); // deleted by the timer or the next action
214 QString id = action->data().toString();
216 if (id.isEmpty()) {
217 kDebug() << "Klipper: no command associated";
218 return;
221 QHash<QString, ClipCommand*>::iterator i = m_myCommandMapper.find(id);
222 ClipCommand *command = i.value();
224 if (command)
225 execute(command);
226 else
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 ) );
246 else
248 kDebug() << "No parent for" << command->description << "(" << command->command << ")";
250 QString cmdLine = KMacroExpander::expandMacrosShellQuote( command->command, map );
252 if ( cmdLine.isEmpty() )
253 return;
255 KProcess proc;
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 );
273 edit->setFocus();
274 edit->setMinimumSize( 300, 40 );
275 dlg->setMainWidget( edit );
276 dlg->adjustSize();
278 if ( dlg->exec() == KDialog::Accepted ) {
279 m_myClipData = edit->toPlainText();
280 QTimer::singleShot( 0, this, SLOT( slotActionMenu() ) );
282 else
284 m_myMenu->deleteLater();
285 m_myMenu = 0;
287 delete dlg;
291 void URLGrabber::loadSettings()
293 m_trimmed = KlipperSettings::stripWhiteSpace();
294 m_myAvoidWindows = KlipperSettings::noActionsForWM_CLASS();
295 m_myPopupKillTimeout = KlipperSettings::timeoutForActionPopups();
297 qDeleteAll(m_myActions);
298 m_myActions.clear();
300 KConfigGroup cg(KGlobal::config(), "General");
301 int num = cg.readEntry("Number of Actions", 0);
302 QString group;
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() );
314 int i = 0;
315 QString group;
316 foreach (ClipAction* action, m_myActions) {
317 group = QString("Action_%1").arg( i );
318 action->save( KGlobal::config(), group );
319 ++i;
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 );
332 Atom type_ret;
333 int format_ret;
334 unsigned long nitems_ret, unused;
335 unsigned char *data_ret;
336 long BUFSIZE = 2048;
337 bool ret = false;
338 Window active = 0L;
339 QString wmClass;
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)
345 == Success) {
346 if (type_ret == XA_WINDOW && format_ret == 32 && nitems_ret == 1) {
347 active = *((Window *) data_ret);
349 XFree(data_ret);
351 if ( !active )
352 return false;
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);
363 XFree( data_ret );
366 return ret;
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 );
378 return;
382 if ( m_myMenu ) {
383 m_myMenu->deleteLater();
384 m_myMenu = 0;
388 ///////////////////////////////////////////////////////////////////////////
389 ////////
391 ClipCommand::ClipCommand(ClipAction *_parent, const QString &_command, const QString &_description,
392 bool _isEnabled, const QString &_icon)
393 : parent(_parent),
394 command(_command),
395 description(_description),
396 isEnabled(_isEnabled)
398 int len = command.indexOf(" ");
399 if (len == -1)
400 len = command.length();
402 if (!_icon.isEmpty())
403 pixmap = _icon;
404 else
406 KService::Ptr service= KService::serviceByDesktopName(command.left(len));
407 if (service)
408 pixmap = service->icon();
409 else
410 pixmap.clear();
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()) {
429 command = it.next();
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 );
443 // read the commands
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() )
466 return;
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
486 int i = 0;
487 while (it.hasNext()) {
488 cmd = it.next();
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 );
496 ++i;
500 #include "urlgrabber.moc"