summaryrefslogtreecommitdiffstats
path: root/klipper/urlgrabber.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'klipper/urlgrabber.cpp')
-rw-r--r--klipper/urlgrabber.cpp497
1 files changed, 497 insertions, 0 deletions
diff --git a/klipper/urlgrabber.cpp b/klipper/urlgrabber.cpp
new file mode 100644
index 000000000..352f3ab74
--- /dev/null
+++ b/klipper/urlgrabber.cpp
@@ -0,0 +1,497 @@
+// -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*-
+/* This file is part of the KDE project
+ Copyright (C) (C) 2000,2001,2002 by Carsten Pfeiffer <pfeiffer@kde.org>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include <qcursor.h>
+#include <qtimer.h>
+
+#include <kapplication.h>
+#include <kconfig.h>
+#include <kdialogbase.h>
+#include <ktextedit.h>
+#include <klocale.h>
+#include <kpopupmenu.h>
+#include <kservice.h>
+#include <kiconloader.h>
+#include <kdebug.h>
+#include <netwm.h>
+#include <kstringhandler.h>
+#include <kmacroexpander.h>
+
+#include <stdlib.h> // For getenv()
+
+#include "urlgrabber.h"
+
+// TODO:
+// - script-interface?
+// - Bug in KPopupMenu::clear() (insertTitle() doesn't go away sometimes)
+
+#define URL_EDIT_ITEM 10
+#define DO_NOTHING_ITEM 11
+#define DISABLE_POPUP 12
+
+URLGrabber::URLGrabber( KConfig* config )
+ : m_config( config )
+{
+ if( m_config == NULL )
+ m_config = kapp->config();
+ myCurrentAction = 0L;
+ myMenu = 0L;
+ myPopupKillTimeout = 8;
+ m_stripWhiteSpace = true;
+
+ myActions = new ActionList();
+ myActions->setAutoDelete( true );
+ myMatches.setAutoDelete( false );
+
+ readConfiguration( m_config );
+
+ myPopupKillTimer = new QTimer( this );
+ connect( myPopupKillTimer, SIGNAL( timeout() ),
+ SLOT( slotKillPopupMenu() ));
+
+ // testing
+ /*
+ ClipAction *action;
+ action = new ClipAction( "^http:\\/\\/", "Web-URL" );
+ action->addCommand("kfmclient exec %s", "Open with Konqi", true);
+ action->addCommand("netscape -no-about-splash -remote \"openURL(%s, new-window)\"", "Open with Netscape", true);
+ myActions->append( action );
+
+ action = new ClipAction( "^mailto:", "Mail-URL" );
+ action->addCommand("kmail --composer %s", "Launch kmail", true);
+ myActions->append( action );
+
+ action = new ClipAction( "^\\/.+\\.jpg$", "Jpeg-Image" );
+ action->addCommand("kuickshow %s", "Launch KuickShow", true);
+ action->addCommand("kview %s", "Launch KView", true);
+ myActions->append( action );
+ */
+}
+
+
+URLGrabber::~URLGrabber()
+{
+ delete myActions;
+}
+
+//
+// Called from Klipper::slotRepeatAction, i.e. by pressing Ctrl-Alt-R
+// shortcut. I.e. never from clipboard monitoring
+//
+void URLGrabber::invokeAction( const QString& clip )
+{
+ if ( !clip.isEmpty() )
+ myClipData = clip;
+ if ( m_stripWhiteSpace )
+ myClipData = myClipData.stripWhiteSpace();
+
+ actionMenu( false );
+}
+
+
+void URLGrabber::setActionList( ActionList *list )
+{
+ delete myActions;
+ myActions = list;
+}
+
+
+const ActionList& URLGrabber::matchingActions( const QString& clipData )
+{
+ myMatches.clear();
+ ClipAction *action = 0L;
+ ActionListIterator it( *myActions );
+ for ( action = it.current(); action; action = ++it ) {
+ if ( action->matches( clipData ) )
+ myMatches.append( action );
+ }
+
+ return myMatches;
+}
+
+
+bool URLGrabber::checkNewData( const QString& clipData )
+{
+ // kdDebug() << "** checking new data: " << clipData << endl;
+ myClipData = clipData;
+ if ( m_stripWhiteSpace )
+ myClipData = myClipData.stripWhiteSpace();
+
+ if ( myActions->isEmpty() )
+ return false;
+
+ actionMenu( true ); // also creates myMatches
+
+ return ( !myMatches.isEmpty() &&
+ (!m_config->readBoolEntry("Put Matching URLs in history", true)));
+}
+
+
+void URLGrabber::actionMenu( bool wm_class_check )
+{
+ if ( myClipData.isEmpty() )
+ return;
+
+ ActionListIterator it( matchingActions( myClipData ) );
+ ClipAction *action = 0L;
+ ClipCommand *command = 0L;
+
+ if ( it.count() > 0 ) {
+ // don't react on konqi's/netscape's urls...
+ if ( wm_class_check && isAvoidedWindow() )
+ return;
+
+ QString item;
+ myCommandMapper.clear();
+
+ myPopupKillTimer->stop();
+ delete myMenu;
+ myMenu = new KPopupMenu;
+ connect( myMenu, SIGNAL( activated( int )),
+ SLOT( slotItemSelected( int )));
+
+ for ( action = it.current(); action; action = ++it ) {
+ QPtrListIterator<ClipCommand> it2( action->commands() );
+ if ( it2.count() > 0 )
+ myMenu->insertTitle( SmallIcon( "klipper" ), action->description() +
+ i18n(" - Actions For: ") +
+ KStringHandler::csqueeze(myClipData, 45));
+ for ( command = it2.current(); command; command = ++it2 ) {
+ item = command->description;
+ if ( item.isEmpty() )
+ item = command->command;
+
+ int id;
+ if ( command->pixmap.isEmpty() )
+ id = myMenu->insertItem( item );
+ else
+ id = myMenu->insertItem( SmallIcon(command->pixmap), item);
+ myCommandMapper.insert( id, command );
+ }
+ }
+
+ // only insert this when invoked via clipboard monitoring, not from an
+ // explicit Ctrl-Alt-R
+ if ( wm_class_check )
+ {
+ myMenu->insertSeparator();
+ myMenu->insertItem( i18n( "Disable This Popup" ), DISABLE_POPUP );
+ }
+ myMenu->insertSeparator();
+ // add an edit-possibility
+ myMenu->insertItem( SmallIcon("edit"), i18n("&Edit Contents..."),
+ URL_EDIT_ITEM );
+ myMenu->insertItem( SmallIconSet("cancel"), i18n("&Cancel"), DO_NOTHING_ITEM );
+
+ if ( myPopupKillTimeout > 0 )
+ myPopupKillTimer->start( 1000 * myPopupKillTimeout, true );
+
+ emit sigPopup( myMenu );
+ }
+}
+
+
+void URLGrabber::slotItemSelected( int id )
+{
+ myMenu->hide(); // deleted by the timer or the next action
+
+ switch ( id ) {
+ case -1:
+ case DO_NOTHING_ITEM:
+ break;
+ case URL_EDIT_ITEM:
+ editData();
+ break;
+ case DISABLE_POPUP:
+ emit sigDisablePopup();
+ break;
+ default:
+ ClipCommand *command = myCommandMapper.find( id );
+ if ( !command )
+ qWarning("Klipper: can't find associated action");
+ else
+ execute( command );
+ }
+}
+
+
+void URLGrabber::execute( const struct ClipCommand *command ) const
+{
+ if ( command->isEnabled ) {
+ QMap<QChar,QString> map;
+ map.insert( 's', myClipData );
+ QString cmdLine = KMacroExpander::expandMacrosShellQuote( command->command, map );
+
+ if ( cmdLine.isEmpty() )
+ return;
+
+ KProcess proc;
+ const char *shell = getenv("KLIPPER_SHELL");
+ if (shell==NULL) shell = getenv("SHELL");
+ proc.setUseShell(true,shell);
+
+ proc << cmdLine.stripWhiteSpace();
+
+ if ( !proc.start(KProcess::DontCare, KProcess::NoCommunication ))
+ qWarning("Klipper: Couldn't start process!");
+ }
+}
+
+
+void URLGrabber::editData()
+{
+ myPopupKillTimer->stop();
+ KDialogBase *dlg = new KDialogBase( 0, 0, true,
+ i18n("Edit Contents"),
+ KDialogBase::Ok | KDialogBase::Cancel);
+ KTextEdit *edit = new KTextEdit( dlg );
+ edit->setText( myClipData );
+ edit->setFocus();
+ edit->setMinimumSize( 300, 40 );
+ dlg->setMainWidget( edit );
+ dlg->adjustSize();
+
+ if ( dlg->exec() == QDialog::Accepted ) {
+ myClipData = edit->text();
+ delete dlg;
+ QTimer::singleShot( 0, this, SLOT( slotActionMenu() ) );
+ }
+ else
+ {
+ delete dlg;
+ myMenu->deleteLater();
+ myMenu = 0L;
+ }
+}
+
+
+void URLGrabber::readConfiguration( KConfig *kc )
+{
+ myActions->clear();
+ kc->setGroup( "General" );
+ int num = kc->readNumEntry("Number of Actions", 0);
+ myAvoidWindows = kc->readListEntry("No Actions for WM_CLASS");
+ myPopupKillTimeout = kc->readNumEntry( "Timeout for Action popups (seconds)", 8 );
+ m_stripWhiteSpace = kc->readBoolEntry("Strip Whitespace before exec", true);
+ QString group;
+ for ( int i = 0; i < num; i++ ) {
+ group = QString("Action_%1").arg( i );
+ kc->setGroup( group );
+ myActions->append( new ClipAction( kc ) );
+ }
+}
+
+
+void URLGrabber::writeConfiguration( KConfig *kc )
+{
+ kc->setGroup( "General" );
+ kc->writeEntry( "Number of Actions", myActions->count() );
+ kc->writeEntry( "Timeout for Action popups (seconds)", myPopupKillTimeout);
+ kc->writeEntry( "No Actions for WM_CLASS", myAvoidWindows );
+ kc->writeEntry( "Strip Whitespace before exec", m_stripWhiteSpace );
+
+ ActionListIterator it( *myActions );
+ ClipAction *action;
+
+ int i = 0;
+ QString group;
+ while ( (action = it.current()) ) {
+ group = QString("Action_%1").arg( i );
+ kc->setGroup( group );
+ action->save( kc );
+ ++i;
+ ++it;
+ }
+}
+
+// find out whether the active window's WM_CLASS is in our avoid-list
+// digged a little bit in netwm.cpp
+bool URLGrabber::isAvoidedWindow() const
+{
+ Display *d = qt_xdisplay();
+ static Atom wm_class = XInternAtom( d, "WM_CLASS", true );
+ static Atom active_window = XInternAtom( d, "_NET_ACTIVE_WINDOW", true );
+ Atom type_ret;
+ int format_ret;
+ unsigned long nitems_ret, unused;
+ unsigned char *data_ret;
+ long BUFSIZE = 2048;
+ bool ret = false;
+ Window active = 0L;
+ QString wmClass;
+
+ // get the active window
+ if (XGetWindowProperty(d, DefaultRootWindow( d ), active_window, 0l, 1l,
+ False, XA_WINDOW, &type_ret, &format_ret,
+ &nitems_ret, &unused, &data_ret)
+ == Success) {
+ if (type_ret == XA_WINDOW && format_ret == 32 && nitems_ret == 1) {
+ active = *((Window *) data_ret);
+ }
+ XFree(data_ret);
+ }
+ if ( !active )
+ return false;
+
+ // get the class of the active window
+ if ( XGetWindowProperty(d, active, wm_class, 0L, BUFSIZE, False, XA_STRING,
+ &type_ret, &format_ret, &nitems_ret,
+ &unused, &data_ret ) == Success) {
+ if ( type_ret == XA_STRING && format_ret == 8 && nitems_ret > 0 ) {
+ wmClass = QString::fromUtf8( (const char *) data_ret );
+ ret = (myAvoidWindows.find( wmClass ) != myAvoidWindows.end());
+ }
+
+ XFree( data_ret );
+ }
+
+ return ret;
+}
+
+
+void URLGrabber::slotKillPopupMenu()
+{
+ if ( myMenu && myMenu->isVisible() )
+ {
+ if ( myMenu->geometry().contains( QCursor::pos() ) &&
+ myPopupKillTimeout > 0 )
+ {
+ myPopupKillTimer->start( 1000 * myPopupKillTimeout, true );
+ return;
+ }
+ }
+
+ delete myMenu;
+ myMenu = 0L;
+}
+
+///////////////////////////////////////////////////////////////////////////
+////////
+
+ClipCommand::ClipCommand(const QString &_command, const QString &_description,
+ bool _isEnabled, const QString &_icon)
+ : command(_command),
+ description(_description),
+ isEnabled(_isEnabled)
+{
+ int len = command.find(" ");
+ if (len == -1)
+ len = command.length();
+
+ if (!_icon.isEmpty())
+ pixmap = _icon;
+ else
+ {
+ KService::Ptr service= KService::serviceByDesktopName(command.left(len));
+ if (service)
+ pixmap = service->icon();
+ else
+ pixmap = QString::null;
+ }
+}
+
+
+ClipAction::ClipAction( const QString& regExp, const QString& description )
+ : myRegExp( regExp ), myDescription( description )
+{
+ myCommands.setAutoDelete( true );
+}
+
+
+ClipAction::ClipAction( const ClipAction& action )
+{
+ myCommands.setAutoDelete( true );
+ myRegExp = action.myRegExp;
+ myDescription = action.myDescription;
+
+ ClipCommand *command = 0L;
+ QPtrListIterator<ClipCommand> it( myCommands );
+ for ( ; it.current(); ++it ) {
+ command = it.current();
+ addCommand(command->command, command->description, command->isEnabled);
+ }
+}
+
+
+ClipAction::ClipAction( KConfig *kc )
+ : myRegExp( kc->readEntry( "Regexp" ) ),
+ myDescription( kc->readEntry( "Description" ) )
+{
+ myCommands.setAutoDelete( true );
+ int num = kc->readNumEntry( "Number of commands" );
+
+ // read the commands
+ QString actionGroup = kc->group();
+ for ( int i = 0; i < num; i++ ) {
+ QString group = actionGroup + "/Command_%1";
+ kc->setGroup( group.arg( i ) );
+
+ addCommand( kc->readPathEntry( "Commandline" ),
+ kc->readEntry( "Description" ), // i18n'ed
+ kc->readBoolEntry( "Enabled" ),
+ kc->readEntry( "Icon") );
+ }
+}
+
+
+ClipAction::~ClipAction()
+{
+}
+
+
+void ClipAction::addCommand( const QString& command,
+ const QString& description, bool enabled, const QString& icon )
+{
+ if ( command.isEmpty() )
+ return;
+
+ struct ClipCommand *cmd = new ClipCommand( command, description, enabled, icon );
+ // cmd->id = myCommands.count(); // superfluous, I think...
+ myCommands.append( cmd );
+}
+
+
+// precondition: we're in the correct action's group of the KConfig object
+void ClipAction::save( KConfig *kc ) const
+{
+ kc->writeEntry( "Description", description() );
+ kc->writeEntry( "Regexp", regExp() );
+ kc->writeEntry( "Number of commands", myCommands.count() );
+
+ QString actionGroup = kc->group();
+ struct ClipCommand *cmd;
+ QPtrListIterator<struct ClipCommand> it( myCommands );
+
+ // now iterate over all commands of this action
+ int i = 0;
+ while ( (cmd = it.current()) ) {
+ QString group = actionGroup + "/Command_%1";
+ kc->setGroup( group.arg( i ) );
+
+ kc->writePathEntry( "Commandline", cmd->command );
+ kc->writeEntry( "Description", cmd->description );
+ kc->writeEntry( "Enabled", cmd->isEnabled );
+
+ ++i;
+ ++it;
+ }
+}
+
+#include "urlgrabber.moc"