<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -360,7 +360,11 @@ bail:
 
 #pragma mark Command handlers
 
-- (BOOL)processKeyExchangeCommandWithArguments:(NSAttributedString *)arguments toConnection:(MVChatConnection *)connection inView:(JVDirectChatPanel *)view;
+/// User command to initiate a key exchange
+/**
+ view can be nil, if this command was not typed in a direct chat panel.
+ */
+- (BOOL)processKeyExchangeCommandWithArguments:(NSAttributedString *)arguments toConnection:(MVChatConnection *)connection inDirectChatPanel:(JVDirectChatPanel *)view;
 {
    NSString *argumentString = [arguments string];
    // If no argument has been given, try to deduce it from the current view. This only works for queries, as key exchange for channels is not supported.
@@ -389,7 +393,11 @@ bail:
    return YES;
 }
 
-- (BOOL)processSetKeyCommandWithArguments:(NSAttributedString *)arguments toConnection:(MVChatConnection *)connection inView:(JVDirectChatPanel *)view;
+/// User command to set the key for a room/nick
+/**
+ view can be nil, if this command was not typed in a direct chat panel.
+ */
+- (BOOL)processSetKeyCommandWithArguments:(NSAttributedString *)arguments toConnection:(MVChatConnection *)connection inDirectChatPanel:(JVDirectChatPanel *)view;
 {
    NSArray *argumentList = [[arguments string] FiSH_arguments];
    NSString *secret = nil;
@@ -406,7 +414,7 @@ bail:
       if (!account)
       {
          DLog(@&quot;SetKey expects exactly 2 arguments, aborting.&quot;);
-         return NO;
+         return YES;
       }
       secret = [argumentList objectAtIndex:0];
    } else if ([argumentList count] == 2)
@@ -420,7 +428,7 @@ bail:
    if (![[FiSHSecretStore sharedSecretStore] storeSecret:secret forService:nil account:account isTemporary:NO])
    {
       [view addEventMessageToDisplay:NSLocalizedString(@&quot;Failed to save the key.&quot;, @&quot;Failed to save the key.&quot;) withName:@&quot;KeySaveError&quot; andAttributes:nil];   
-      return NO;
+      return YES;
    }
    [view addEventMessageToDisplay:NSLocalizedString(@&quot;Key saved to Keychain.&quot;, @&quot;Key saved to Keychain.&quot;) withName:@&quot;KeySavedToKeychain&quot; andAttributes:nil];   
    
@@ -430,8 +438,19 @@ bail:
    return YES;
 }
 
-- (BOOL)processSendUnecryptedCommandWithArguments:(NSAttributedString *)arguments toConnection:(MVChatConnection *)connection inView:(JVDirectChatPanel *)view;
+/// User command to send an unencrypted message to an unencrypted room/nick.
+/**
+ view can be nil, if this command was not typed in a direct chat panel.
+ */
+- (BOOL)processSendUnecryptedCommandWithArguments:(NSAttributedString *)arguments toConnection:(MVChatConnection *)connection inDirectChatPanel:(JVDirectChatPanel *)view;
 {
+   if (!view)
+   {
+      DLog(@&quot;Command only supported in chat windows.&quot;);
+      return YES;
+   }
+   
+   
    JVMutableChatMessage *msg = [[[NSClassFromString(@&quot;JVMutableChatMessage&quot;) alloc] initWithText:arguments sender:[connection localUser]] autorelease];
    [msg setAttribute:[NSNumber numberWithBool:YES] forKey:@&quot;sendUnencrypted&quot;];
    [view echoSentMessageToDisplay:msg];
@@ -440,20 +459,24 @@ bail:
    return YES;
 }
 
-- (BOOL) processEncryptionPreferenceCommandWithArguments:(NSAttributedString *)arguments toConnection:(MVChatConnection *)connection inView:(JVDirectChatPanel *)view pref:(FiSHEncPrefKey)encPref;
+/// User command to enable/disable encryption for a room/nick.
+/**
+ view can be nil, if this command was not typed in a direct chat panel.
+ */
+- (BOOL) processEncryptionPreferenceCommandWithArguments:(NSAttributedString *)arguments toConnection:(MVChatConnection *)connection inDirectChatPanel:(JVDirectChatPanel *)view pref:(FiSHEncPrefKey)encPref;
 {
    NSArray *argumentList = [[arguments string] FiSH_arguments];
    NSString *targetName = nil;
    if ([argumentList count] == 1)
    {
       targetName = [argumentList objectAtIndex:0];
-   } else if ([argumentList count] == 0)
+   } else if ([argumentList count] == 0 &amp;&amp; view)
    {
       targetName = [[view target] isKindOfClass:NSClassFromString(@&quot;MVChatUser&quot;)] ? [[view target] nickname] : [[view target] name];
    } else
    {
       DLog(@&quot;Command expects exactly 1 argument&quot;);
-      return NO;
+      return YES;
    }
    
    // TODO: Differenciate between services here.
@@ -476,32 +499,32 @@ bail:
 
 #pragma mark MVChatPluginCommandSupport
 
+/// Process user commands.
+/**
+ Called by Colloquy whenever the user types a string which starts with a single /.
+ */
 - (BOOL)processUserCommand:(NSString *) command withArguments:(NSAttributedString *) arguments toConnection:(MVChatConnection *) connection inView:(id &lt;JVChatViewController&gt;)aView;
 {
    // We only support IRC connection.
    if (!FiSHIsIRCConnection(connection))
       return NO;
    
-   // Make sure that aView really is a direct chat panel.
+   // If aView is a direct chat panel, cast it to supply it later to the command handlers.
+   // The only other case here probably are transcript and console panels.
    JVDirectChatPanel *view = FiSHDirectChatPanelForChatViewController(aView);
-   if (!view)
-   {
-      DLog(@&quot;Ignoring unsupported chat controller type.&quot;);
-      return NO;
-   }
    
    
    // Check for correct command string. We don't care about case.
    if ([command isCaseInsensitiveEqualToString:FiSHKeyExchangeCommand])
-      return [self processKeyExchangeCommandWithArguments:arguments toConnection:connection inView:view];
+      return [self processKeyExchangeCommandWithArguments:arguments toConnection:connection inDirectChatPanel:view];
    if ([command isCaseInsensitiveEqualToString:FiSHSetKeyCommand])
-      return [self processSetKeyCommandWithArguments:arguments toConnection:connection inView:view];
+      return [self processSetKeyCommandWithArguments:arguments toConnection:connection inDirectChatPanel:view];
    if ([command isCaseInsensitiveEqualToString:FiSHOverrideEncCommand])
-      return [self processSendUnecryptedCommandWithArguments:arguments toConnection:connection inView:view];
+      return [self processSendUnecryptedCommandWithArguments:arguments toConnection:connection inDirectChatPanel:view];
    if ([command isCaseInsensitiveEqualToString:FiSHPreferEncCommand])
-      return [self processEncryptionPreferenceCommandWithArguments:arguments toConnection:connection inView:view pref:FiSHEncPrefPreferEncrypted];
+      return [self processEncryptionPreferenceCommandWithArguments:arguments toConnection:connection inDirectChatPanel:view pref:FiSHEncPrefPreferEncrypted];
    if ([command isCaseInsensitiveEqualToString:FiSHAvoidEncCommand])
-      return [self processEncryptionPreferenceCommandWithArguments:arguments toConnection:connection inView:view pref:FiSHEncPrefAvoidEncrypted];
+      return [self processEncryptionPreferenceCommandWithArguments:arguments toConnection:connection inDirectChatPanel:view pref:FiSHEncPrefAvoidEncrypted];
    
    return NO;
 }</diff>
      <filename>FiSHController.m</filename>
    </modified>
    <modified>
      <diff>@@ -40,7 +40,7 @@ const NSString *FiSHKeyExchangeInfoRemoveOldTempKeyPairTimerKey = @&quot;FiSHKeyExcha
 - (NSDictionary *)temporaryKeyExchangeInfosForNickname:(NSString *)nickname onConnection:(id)connection;
 - (void)removeTemporaryKeyExchangeInfosForNickName:(NSString *)nickname onConnection:(id)connection;
 
-- (void)handleKeyExchangeTimeoutForNickname:(NSString *)nickname onConnection:(id)connection;
+- (void)handleKeyExchangeTimeout:(NSTimer *)aTimer;
 
 - (void)handleFiSHKeyExchangeRequestFrom:(NSString *)nickname on:(id)connection withRemotePublicKeyData:(NSString *)remotePublicKeyData;
 - (void)handleFiSHKeyExchangeResponseFrom:(NSString *)nickname on:(id)connection withRemotePublicKeyData:(NSString *)remotePublicKeyData;
@@ -155,19 +155,19 @@ const NSString *FiSHKeyExchangeInfoRemoveOldTempKeyPairTimerKey = @&quot;FiSHKeyExcha
       [temporaryKeyExchangeInfos_ setObject:keyExchangeInfosForNicknames forKey:connection];
    }
    
-   // Build an invocation, which deletes the to be added key pair.
-   NSMethodSignature *methodSig = [self methodSignatureForSelector:@selector(handleKeyExchangeTimeoutForNickname:onConnection:)];
-   NSInvocation *removeOldTempKeyExchangeInfoInvocation = [NSInvocation invocationWithMethodSignature:methodSig];
-   [removeOldTempKeyExchangeInfoInvocation setTarget:self];
-   [removeOldTempKeyExchangeInfoInvocation setSelector:@selector(handleKeyExchangeTimeoutForNickname:onConnection:)];
-   [removeOldTempKeyExchangeInfoInvocation setArgument:&amp;nickname atIndex:2];
-   [removeOldTempKeyExchangeInfoInvocation setArgument:&amp;connection atIndex:3];
+   // Build a dictionary, which we pass to the timer, containing the necessary info to delete a request which timed out.
+   NSDictionary *timerInfoDict = [NSDictionary dictionaryWithObjectsAndKeys:
+      nickname, @&quot;nickname&quot;,
+      connection, @&quot;connection&quot;,
+      nil];
    
    // Create the dictionary containing the keypair, and the timer used to delete the key pair if we don't use it during a certain amount of time.
    NSDictionary *keyPair = [NSDictionary dictionaryWithObjectsAndKeys:
       dhInfos, FiSHKeyExchangeInfoDH1080Key,
-      [NSTimer scheduledTimerWithTimeInterval:HCKMaxTimeToWaitForDH1080Response 
-                                   invocation:removeOldTempKeyExchangeInfoInvocation 
+      [NSTimer scheduledTimerWithTimeInterval:HCKMaxTimeToWaitForDH1080Response
+                                       target:self
+                                     selector:@selector(handleKeyExchangeTimeout:)
+                                     userInfo:timerInfoDict
                                       repeats:NO], FiSHKeyExchangeInfoRemoveOldTempKeyPairTimerKey,
       nil];
    
@@ -198,13 +198,22 @@ const NSString *FiSHKeyExchangeInfoRemoveOldTempKeyPairTimerKey = @&quot;FiSHKeyExcha
    [temporaryKeyExchangeInfosLock_ unlock];
 }
 
-- (void)handleKeyExchangeTimeoutForNickname:(NSString *)nickname onConnection:(id)connection;
+- (void)handleKeyExchangeTimeout:(NSTimer *)aTimer;
 {
-   [self removeTemporaryKeyExchangeInfosForNickName:nickname onConnection:connection];
+   // Retain the user info, as it would be released later when we invalidate the timer.
+   NSDictionary *timerInfoDict = [[aTimer userInfo] retain];
+   NSString *nickname = [timerInfoDict objectForKey:@&quot;nickname&quot;];
+   id connection = [timerInfoDict objectForKey:@&quot;connection&quot;];
+   
+   DLog(@&quot;Key exchange for %@ on %@ timed out&quot;, nickname, connection);
    
+   [self removeTemporaryKeyExchangeInfosForNickName:nickname onConnection:connection];
+
    [delegate_ outputStatusInformation:NSLocalizedString(@&quot;Timed out waiting for key-exchange reply.&quot;, &quot;Timed out waiting for key-exchange reply&quot;)
                            forContext:nickname
                                    on:connection];
+
+   [timerInfoDict release];
 }
 
 - (void)handleFiSHKeyExchangeRequestFrom:(NSString *)nickname on:(id)connection withRemotePublicKeyData:(NSString *)remotePublicKeyData;</diff>
      <filename>FiSHKeyExchanger.mm</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>18908ee674c6faa5ea156b4267f2494244b12b05</id>
    </parent>
  </parents>
  <author>
    <name>hkiel</name>
    <email>hkiel</email>
  </author>
  <url>http://github.com/hennk/fishy/commit/a012e749f8355aad5c7384ee214aa9f8ad3612c3</url>
  <id>a012e749f8355aad5c7384ee214aa9f8ad3612c3</id>
  <committed-date>2007-03-14T15:58:41-07:00</committed-date>
  <authored-date>2007-03-14T15:58:41-07:00</authored-date>
  <message>Fixed a crash when a key exchange times out.

FiSHy commands now work non-chat windows, too (for example console windows). Fixes #3</message>
  <tree>8d7a8eb815f26b05e44a19a3bd0cb962ee33fbed</tree>
  <committer>
    <name>hkiel</name>
    <email>hkiel</email>
  </committer>
</commit>
