2 kopetecommandhandler.cpp - Command Handler
4 Copyright (c) 2003 by Jason Keirstead <jason@keirstead.org>
5 Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org>
7 *************************************************************************
9 * This library is free software; you can redistribute it and/or *
10 * modify it under the terms of the GNU Lesser General Public *
11 * License as published by the Free Software Foundation; either *
12 * version 2 of the License, or (at your option) any later version. *
14 *************************************************************************
19 #include <QApplication>
22 #include <kdeversion.h>
23 #include <kxmlguiclient.h>
26 #include <kauthorized.h>
27 #include <kactioncollection.h>
29 #include <k3process.h>
31 class K3Process
: public QObject
{
40 K3Process(QObject
*) {}
42 K3Process
&operator<< (const QString
&arg
) { return *this; }
43 K3Process
&operator<< (const char *arg
) { return *this; }
44 K3Process
&operator<< (const QByteArray
&arg
) { return *this; }
45 K3Process
&operator<< (const QStringList
&args
) { return *this; }
46 virtual bool start (RunMode runmode
=NotifyOnExit
, Communication comm
=NoCommunication
) { return true; }
50 #include "kopetechatsessionmanager.h"
51 #include "kopeteprotocol.h"
52 #include "kopetepluginmanager.h"
53 #include "kopeteview.h"
54 #include "kopeteaccountmanager.h"
55 #include "kopeteaccount.h"
56 #include "kopetecommandhandler.h"
57 #include "kopetecontact.h"
58 #include "kopetecommand.h"
59 #include "kopeteonlinestatusmanager.h"
61 using Kopete::CommandList
;
63 typedef QMap
<QObject
*, CommandList
> PluginCommandMap
;
64 typedef QMap
<QString
,QString
> CommandMap
;
65 typedef QPair
<Kopete::ChatSession
*, Kopete::Message::MessageDirection
> ManagerPair
;
67 class KopeteCommandGUIClient
: public QObject
, public KXMLGUIClient
70 KopeteCommandGUIClient( Kopete::ChatSession
*manager
) : QObject(manager
), KXMLGUIClient(manager
)
72 setXMLFile( QString::fromLatin1("kopetecommandui.rc") );
74 QDomDocument doc
= domDocument();
75 QDomNode menu
= doc
.documentElement().firstChild().firstChild().firstChild();
76 CommandList mCommands
= Kopete::CommandHandler::commandHandler()->commands(
80 CommandList::Iterator it
, itEnd
= mCommands
.end();
81 for( it
= mCommands
.begin(); it
!= itEnd
; ++it
)
83 KAction
*a
= static_cast<KAction
*>( it
.value() );
84 actionCollection()->addAction( a
->objectName(), a
);
85 QDomElement newNode
= doc
.createElement( QString::fromLatin1("Action") );
86 newNode
.setAttribute( QString::fromLatin1("name"), a
->objectName() );
89 for( QDomElement n
= menu
.firstChild().toElement();
90 !n
.isNull(); n
= n
.nextSibling().toElement() )
92 if( a
->objectName() < n
.attribute(QString::fromLatin1("name")))
94 menu
.insertBefore( newNode
, n
);
102 menu
.appendChild( newNode
);
106 setDOMDocument( doc
);
110 struct CommandHandlerPrivate
112 PluginCommandMap pluginCommands
;
113 Kopete::CommandHandler
*s_handler
;
114 QMap
<K3Process
*,ManagerPair
> processMap
;
116 QList
<KAction
*> m_commands
;
119 CommandHandlerPrivate
*Kopete::CommandHandler::p
= 0L;
121 Kopete::CommandHandler::CommandHandler() : QObject( qApp
)
124 p
->inCommand
= false;
126 CommandList mCommands
;
127 mCommands
.reserve(31);
128 p
->pluginCommands
.insert( this, mCommands
);
130 registerCommand( this, QString::fromLatin1("help"), SLOT( slotHelpCommand( const QString
&, Kopete::ChatSession
* ) ),
131 i18n( "USAGE: /help [<command>] - Used to list available commands, or show help for a specified command." ), 0, 1 );
133 registerCommand( this, QString::fromLatin1("close"), SLOT( slotCloseCommand( const QString
&, Kopete::ChatSession
* ) ),
134 i18n( "USAGE: /close - Closes the current view." ) );
136 // FIXME: What's the difference with /close? The help doesn't explain it - Martijn
137 registerCommand( this, QString::fromLatin1("part"), SLOT( slotPartCommand( const QString
&, Kopete::ChatSession
* ) ),
138 i18n( "USAGE: /part - Closes the current view." ) );
140 registerCommand( this, QString::fromLatin1("clear"), SLOT( slotClearCommand( const QString
&, Kopete::ChatSession
* ) ),
141 i18n( "USAGE: /clear - Clears the active view's chat buffer." ) );
143 //registerCommand( this, QString::fromLatin1("me"), SLOT( slotMeCommand( const QString &, Kopete::ChatSession * ) ),
144 // i18n( "USAGE: /me <text> - Formats message as in '<nickname> went to the store'." ) );
146 registerCommand( this, QString::fromLatin1("away"), SLOT( slotAwayCommand( const QString
&, Kopete::ChatSession
* ) ),
147 i18n( "USAGE: /away [<reason>] - Marks you as away/back for the current account only." ) );
149 registerCommand( this, QString::fromLatin1("awayall"), SLOT( slotAwayAllCommand( const QString
&, Kopete::ChatSession
* ) ),
150 i18n( "USAGE: /awayall [<reason>] - Marks you as away/back for all accounts." ) );
152 registerCommand( this, QString::fromLatin1("say"), SLOT( slotSayCommand( const QString
&, Kopete::ChatSession
* ) ),
153 i18n( "USAGE: /say <text> - Say text in this chat. This is the same as just typing a message, but is very "
154 "useful for scripts." ), 1 );
156 registerCommand( this, QString::fromLatin1("exec"), SLOT( slotExecCommand( const QString
&, Kopete::ChatSession
* ) ),
157 i18n( "USAGE: /exec [-o] <command> - Executes the specified command and displays the output in the chat buffer. "
158 "If -o is specified, the output is sent to all members of the chat."), 1 );
160 connect( Kopete::PluginManager::self(), SIGNAL( pluginLoaded( Kopete::Plugin
*) ),
161 this, SLOT(slotPluginLoaded(Kopete::Plugin
*) ) );
163 connect( Kopete::ChatSessionManager::self(), SIGNAL( viewCreated( KopeteView
* ) ),
164 this, SLOT( slotViewCreated( KopeteView
* ) ) );
167 Kopete::CommandHandler::~CommandHandler()
169 CommandList commandList
= p
->pluginCommands
[this];
170 while (!commandList
.isEmpty())
172 Kopete::Command
*value
= *commandList
.begin();
173 commandList
.erase(commandList
.begin());
180 Kopete::CommandHandler
*Kopete::CommandHandler::commandHandler()
184 p
= new CommandHandlerPrivate
;
185 p
->s_handler
= new Kopete::CommandHandler();
191 void Kopete::CommandHandler::registerCommand( QObject
*parent
, const QString
&command
, const char* handlerSlot
,
192 const QString
&help
, uint minArgs
, int maxArgs
, const KShortcut
&cut
, const QString
&pix
)
194 QString lowerCommand
= command
.toLower();
196 Kopete::Command
*mCommand
= new Kopete::Command( parent
, lowerCommand
, handlerSlot
, help
,
197 Normal
, QString::null
, minArgs
, maxArgs
, cut
, pix
); //krazy:exclude=nullstrassign for old broken gcc
198 p
->pluginCommands
[ parent
].insert( lowerCommand
, mCommand
);
201 void Kopete::CommandHandler::unregisterCommand( QObject
*parent
, const QString
&command
)
203 if( p
->pluginCommands
[ parent
].contains(command
) )
204 p
->pluginCommands
[ parent
].remove( command
);
207 void Kopete::CommandHandler::registerAlias( QObject
*parent
, const QString
&alias
, const QString
&formatString
,
208 const QString
&help
, CommandType type
, uint minArgs
, int maxArgs
, const KShortcut
&cut
, const QString
&pix
)
210 QString lowerAlias
= alias
.toLower();
212 Kopete::Command
*mCommand
= new Kopete::Command( parent
, lowerAlias
, 0L, help
, type
,
213 formatString
, minArgs
, maxArgs
, cut
, pix
);
214 p
->pluginCommands
[ parent
].insert( lowerAlias
, mCommand
);
217 void Kopete::CommandHandler::unregisterAlias( QObject
*parent
, const QString
&alias
)
219 if( p
->pluginCommands
[ parent
].contains(alias
) )
220 p
->pluginCommands
[ parent
].remove( alias
);
223 bool Kopete::CommandHandler::processMessage( const QString
&msg
, Kopete::ChatSession
*manager
)
227 QRegExp
splitRx( QString::fromLatin1("^/([\\S]+)(.*)") );
230 if(splitRx
.indexIn(msg
) != -1)
232 command
= splitRx
.cap(1);
233 args
= splitRx
.cap(2).mid(1);
238 CommandList mCommands
= commands( manager
->protocol() );
239 Kopete::Command
*c
= mCommands
.value(command
);
242 kDebug(14010) << "Handled Command";
243 if( c
->type() != SystemAlias
&& c
->type() != UserAlias
)
246 c
->processCommand( args
, manager
);
247 p
->inCommand
= false;
255 bool Kopete::CommandHandler::processMessage( Kopete::Message
&msg
, Kopete::ChatSession
*manager
)
257 QString messageBody
= msg
.plainBody();
259 return processMessage( messageBody
, manager
);
262 void Kopete::CommandHandler::slotHelpCommand( const QString
&args
, Kopete::ChatSession
*manager
)
267 int commandCount
= 0;
268 output
= i18n( "Available Commands:\n" );
270 CommandList mCommands
= commands( manager
->myself()->protocol() );
271 CommandList::Iterator it
, itEnd
= mCommands
.end();
272 for( it
= mCommands
.begin(); it
!= itEnd
; ++it
)
274 output
.append( it
.value()->command().toUpper() + '\t' );
275 if( commandCount
++ == 5 )
278 output
.append( '\n' );
281 output
.append( i18n( "\nType /help <command> for more information." ) );
285 QString command
= parseArguments( args
).front().toLower();
286 Kopete::Command
*c
= commands( manager
->myself()->protocol() ).value( command
);
287 if( c
&& !c
->help().isNull() )
290 output
= i18n("There is no help available for '%1'.", command
);
293 Kopete::Message
msg(manager
->myself(), manager
->members());
294 msg
.setDirection( Kopete::Message::Internal
);
295 msg
.setPlainBody( output
);
297 manager
->appendMessage(msg
);
300 void Kopete::CommandHandler::slotSayCommand( const QString
&args
, Kopete::ChatSession
*manager
)
302 //Just say whatever is passed
303 Kopete::Message
msg(manager
->myself(), manager
->members());
304 msg
.setPlainBody( args
);
305 msg
.setDirection( Kopete::Message::Outbound
);
307 manager
->sendMessage(msg
);
310 void Kopete::CommandHandler::slotExecCommand( const QString
&args
, Kopete::ChatSession
*manager
)
312 if( !args
.isEmpty() )
314 K3Process
*proc
= 0L;
315 if ( KAuthorized::authorizeKAction( "shell_access" ) )
316 proc
= new K3Process(manager
);
319 *proc
<< QString::fromLatin1("sh") << QString::fromLatin1("-c");
321 QStringList argsList
= parseArguments( args
);
322 if( argsList
.front() == QString::fromLatin1("-o") )
324 p
->processMap
.insert( proc
, ManagerPair(manager
, Kopete::Message::Outbound
) );
325 *proc
<< args
.section(QRegExp(QString::fromLatin1("\\s+")), 1);
329 p
->processMap
.insert( proc
, ManagerPair(manager
, Kopete::Message::Internal
) );
333 connect(proc
, SIGNAL(receivedStdout(K3Process
*, char *, int)), this, SLOT(slotExecReturnedData(K3Process
*, char *, int)));
334 connect(proc
, SIGNAL(receivedStderr(K3Process
*, char *, int)), this, SLOT(slotExecReturnedData(K3Process
*, char *, int)));
335 proc
->start( K3Process::NotifyOnExit
, K3Process::AllOutput
);
339 Kopete::Message
msg(manager
->myself(), manager
->members() );
340 msg
.setDirection( Kopete::Message::Internal
);
341 msg
.setPlainBody( i18n( "ERROR: Shell access has been restricted on your system. The /exec command will not function." ) );
342 manager
->sendMessage( msg
);
347 void Kopete::CommandHandler::slotClearCommand( const QString
&, Kopete::ChatSession
*manager
)
349 if( manager
->view() )
350 manager
->view()->clear();
353 void Kopete::CommandHandler::slotPartCommand( const QString
&, Kopete::ChatSession
*manager
)
355 if( manager
->view() )
356 manager
->view()->closeView();
359 void Kopete::CommandHandler::slotAwayCommand( const QString
&args
, Kopete::ChatSession
*manager
)
361 bool goAway
= !manager
->account()->isAway();
364 manager
->account()->setOnlineStatus( OnlineStatusManager::self()->onlineStatus(manager
->account()->protocol() , goAway
? OnlineStatusManager::Away
: OnlineStatusManager::Online
) );
366 manager
->account()->setOnlineStatus( OnlineStatusManager::self()->onlineStatus(manager
->account()->protocol() , goAway
? OnlineStatusManager::Away
: OnlineStatusManager::Online
) , args
);
369 void Kopete::CommandHandler::slotAwayAllCommand( const QString
&args
, Kopete::ChatSession
*manager
)
371 if( manager
->account()->isAway() ) {
372 Kopete::AccountManager::self()->setOnlineStatus( Kopete::OnlineStatusManager::Online
);
375 Kopete::AccountManager::self()->setOnlineStatus( Kopete::OnlineStatusManager::Away
, args
);
379 void Kopete::CommandHandler::slotCloseCommand( const QString
&, Kopete::ChatSession
*manager
)
381 if( manager
->view() )
382 manager
->view()->closeView();
385 void Kopete::CommandHandler::slotExecReturnedData(K3Process
*proc
, char *buff
, int bufflen
)
388 QString buffer
= QString::fromLocal8Bit( buff
, bufflen
);
389 ManagerPair mgrPair
= p
->processMap
[ proc
];
390 Kopete::Message
msg( mgrPair
.first
->myself(), mgrPair
.first
->members() );
391 msg
.setDirection( mgrPair
.second
);
392 msg
.setPlainBody( buffer
);
394 if( mgrPair
.second
== Kopete::Message::Outbound
)
395 mgrPair
.first
->sendMessage( msg
);
397 mgrPair
.first
->appendMessage( msg
);
400 void Kopete::CommandHandler::slotExecFinished(K3Process
*proc
)
403 p
->processMap
.remove( proc
);
406 QStringList
Kopete::CommandHandler::parseArguments( const QString
&args
)
408 QStringList arguments
;
409 QRegExp
quotedArgs( QString::fromLatin1("\"(.*)\"") );
410 quotedArgs
.setMinimal( true );
412 if ( quotedArgs
.indexIn( args
) != -1 )
414 for( int i
= 0; i
< quotedArgs
.numCaptures(); i
++ )
415 arguments
.append( quotedArgs
.cap(i
) );
418 QStringList otherArgs
= args
.section( quotedArgs
, 0 ).split( QRegExp(QString::fromLatin1("\\s+")), QString::SkipEmptyParts
);
419 for( QStringList::Iterator it
= otherArgs
.begin(); it
!= otherArgs
.end(); ++it
)
420 arguments
.append( *it
);
425 bool Kopete::CommandHandler::commandHandled( const QString
&command
)
427 for( PluginCommandMap::Iterator it
= p
->pluginCommands
.begin(); it
!= p
->pluginCommands
.end(); ++it
)
429 if( it
.value().value( command
) )
436 bool Kopete::CommandHandler::commandHandledByProtocol( const QString
&command
, Kopete::Protocol
*protocol
)
438 // Make sure the protocol is not NULL
442 // Fetch the commands for the protocol
443 CommandList commandList
= commands( protocol
);
444 CommandList::Iterator it
, itEnd
= commandList
.end();
446 // Loop through commands and check if they match the supplied command
447 for( it
= commandList
.begin(); it
!= itEnd
; ++it
)
449 if( it
.value()->command().toLower() == command
)
457 CommandList
Kopete::CommandHandler::commands( Kopete::Protocol
*protocol
)
459 CommandList commandList
;
460 commandList
.reserve(63);
462 //Add plugin user aliases first
463 addCommands( p
->pluginCommands
[protocol
], commandList
, UserAlias
);
465 //Add plugin system aliases next
466 addCommands( p
->pluginCommands
[protocol
], commandList
, SystemAlias
);
468 //Add the commands for this protocol next
469 addCommands( p
->pluginCommands
[protocol
], commandList
);
471 //Add plugin commands
472 for( PluginCommandMap::Iterator it
= p
->pluginCommands
.begin(); it
!= p
->pluginCommands
.end(); ++it
)
474 if( !it
.key()->inherits("Kopete::Protocol") && it
.key()->inherits("Kopete::Plugin") )
475 addCommands( it
.value(), commandList
);
478 //Add global user aliases first
479 addCommands( p
->pluginCommands
[this], commandList
, UserAlias
);
481 //Add global system aliases next
482 addCommands( p
->pluginCommands
[this], commandList
, SystemAlias
);
484 //Add the internal commands *last*
485 addCommands( p
->pluginCommands
[this], commandList
);
490 void Kopete::CommandHandler::addCommands( CommandList
&from
, CommandList
&to
, CommandType type
)
492 CommandList::Iterator itDict
, itDictEnd
= from
.end();
493 for( itDict
= from
.begin(); itDict
!= itDictEnd
; ++itDict
)
495 if( !to
.value( itDict
.key() ) &&
496 ( type
== Undefined
|| itDict
.value()->type() == type
) )
497 to
.insert( itDict
.key(), itDict
.value() );
501 void Kopete::CommandHandler::slotViewCreated( KopeteView
*view
)
503 new KopeteCommandGUIClient( view
->msgManager() );
506 void Kopete::CommandHandler::slotPluginLoaded( Kopete::Plugin
*plugin
)
508 connect( plugin
, SIGNAL( destroyed( QObject
* ) ), this, SLOT( slotPluginDestroyed( QObject
* ) ) );
509 if( !p
->pluginCommands
.contains( plugin
) )
511 //Create a QDict optomized for a larger # of commands, and case insensitive
512 CommandList mCommands
;
513 mCommands
.reserve(31);
514 p
->pluginCommands
.insert( plugin
, mCommands
);
518 void Kopete::CommandHandler::slotPluginDestroyed( QObject
*plugin
)
520 p
->pluginCommands
.remove( static_cast<Kopete::Plugin
*>(plugin
) );
523 #include "kopetecommandhandler.moc"
525 // vim: set noet ts=4 sts=4 sw=4: