Index: perl/modules/jabber.pl
===================================================================
--- perl/modules/jabber.pl	(revision 38ffdf98eed30f38e988085b2ab7be7a65c08e33)
+++ perl/modules/jabber.pl	(revision b6a253c676d48c073dc3a401d27fe588737dd4fe)
@@ -5,6 +5,13 @@
 # owl perl jabber support
 #
-# Todo:
-# Connect command.
+# XXX Todo:
+# Rosters for MUCs
+# More user feedback
+#  * joining MUC
+#  * parting MUC
+#  * presence (Roster and MUC)
+# Implementing formatting and logging callbacks for C
+# Appropriate callbacks for presence subscription messages.
+#  * Current behavior => auto-accept (default for Net::Jabber)
 #
 ################################################################################
@@ -12,4 +19,5 @@
 our $client;
 our $jid;
+our $roster;
 
 sub onStart
@@ -18,4 +26,6 @@
     {
 	register_owl_commands();
+	push @::onMainLoop, sub { owl_jabber::onMainLoop(@_) };
+	push @::onGetBuddyList, sub { owl_jabber::onGetBuddyList(@_) };
     }
     else
@@ -26,5 +36,5 @@
     }
 }
-push @::onStartSubs, \&onStart;
+push @::onStartSubs, sub { owl_jabber::onStart(@_) };
 
 sub onMainLoop
@@ -40,4 +50,5 @@
     {
 	queue_admin_msg("Jabber disconnected.");
+	$roster = undef;
 	$client = undef;
 	return;
@@ -46,4 +57,5 @@
     if ($::shutdown)
     {
+	$roster = undef;
 	$client->Disconnect();
 	$client = undef;
@@ -51,5 +63,55 @@
     }
 }
-push @::onMainLoop, \&onMainLoop;
+
+sub blist_listBuddy
+{
+    my $buddy = shift;
+    my $blistStr .= "    ";
+    my %jq = $roster->query($buddy);
+    my $res = $roster->resource($buddy);
+
+    $blistStr .= $jq{name} ? $jq{name} : $buddy->GetJID();
+    
+    if ($res)
+    {
+	my %rq = $roster->resourceQuery($buddy, $res);
+	$blistStr .= " [".($rq{show} ? $rq{show} : 'online')."]";
+	$blistStr .= " ".$rq{status} if $rq{status};
+	$blistStr = boldify($blistStr);
+    }
+    else
+    {
+	$blistStr .= $jq{ask} ? " [pending]" : " [offline]";
+    }
+
+    return $blistStr."\n";
+}
+
+sub onGetBuddyList
+{
+    return "" if ($client == undef);
+    my $blist = "\n".boldify("Jabber Roster for ".$jid->GetJID('base'))."\n";
+
+    foreach my $group ($roster->groups())
+    {
+	$blist .= "  Group: $group\n";
+	foreach my $buddy ($roster->jids('group',$group))
+	{
+	    $blist .= blist_listBuddy($buddy);
+	}
+    }
+    
+    my @unsorted = $roster->jids('nogroup');
+    if (@unsorted)
+    {
+	$blist .= "  [unsorted]\n";
+	foreach my $buddy (@unsorted)
+	{
+	    $blist .= blist_listBuddy($buddy);
+	}
+    }
+
+    $blist .= "\n";
+}
 
 ################################################################################
@@ -69,28 +131,29 @@
         {
             summary     => "Send a Jabber Message",
-            usage       => "jwrite JID [-t thread]"
+            usage       => "jwrite JID [-g] [-t thread] [-s subject]"
         }
     );
     owl::new_command(
-        jchat => \&cmd_jwrite_gc,
+        jlist => \&cmd_jlist,
         {
-            summary => "Send a Jabber Message",
-            usage       => "jchat [room]@[server]"
+            summary     => "Show your Jabber roster.",
+            usage       => "jlist"
         }
     );
     owl::new_command(
-        jjoin => \&cmd_join_gc,
+        jmuc => \&cmd_jmuc,
         {
-            summary     => "Joins a jabber groupchat.",
-            usage       => "jjoin [room]@[server]/[nick]"
+            summary     => "Jabber MUC related commands.",
+	    description => "jmuc sends jabber commands related to muc.\n\n".
+		"The following commands are available\n\n".
+		"join {muc}  Join a muc.\n\n".
+		"part [muc]  Part a muc.".
+		"            The muc is taken from the current message if not supplied.\n\n".
+		"invite {jid} [muc]\n\n".
+		"            Invite {jid} to [muc].\n".
+		"            The muc is taken from the current message if not supplied.\n\n",
+            usage       => "jmuc {command} {args}"
         }
     );
-    owl::new_command(
-        jpart => \&cmd_part_gc,
-        {
-            summary     => "Parts a jabber groupchat.",
-            usage       => "jpart [room]@[server]/[nick]"
-        }
-    );
 }
 
@@ -102,7 +165,11 @@
 	return;
     }
-    
-    # These strings should not be hard-coded here.
+
+    %muc_roster = ();
     $client = Net::Jabber::Client->new();
+    $roster = $client->Roster();
+
+    #XXX Todo: Add more callbacks.
+    # MUC presence handlers
     $client->SetMessageCallBacks(chat => sub { owl_jabber::process_incoming_chat_message(@_) },
 				 error => sub { owl_jabber::process_incoming_error_message(@_) },
@@ -110,4 +177,6 @@
 				 headline => sub { owl_jabber::process_incoming_headline_message(@_) },
 				 normal => sub { owl_jabber::process_incoming_normal_message(@_) });
+
+    #XXX Todo: Parameterize the arguments to Connect()
     my $status = $client->Connect(hostname => 'jabber.mit.edu',
 				  tls => 1,
@@ -118,10 +187,13 @@
     {
 	owl::error("We failed to connect");
-	return;
-    }
+	$client = undef;
+	return;
+    }
+
 
     my @result = $client->AuthSend(username => $ENV{USER}, resource => 'owl', password => '');
     if($result[0] ne 'ok') {
 	owl::error("Error in connect: " . join(" ", $result[1..$#result]));
+	$roster = undef;
 	$client->Disconnect();
 	$client = undef;
@@ -135,4 +207,5 @@
 		 resource => 'owl');
     
+    $roster->fetch();
     $client->PresenceSend(priority => 1);
     queue_admin_msg("Connected to jabber as ".$jid->GetJID('full'));
@@ -145,4 +218,5 @@
     if ($client)
     {
+	$roster = undef;
 	$client->Disconnect();
 	$client = undef;
@@ -150,4 +224,14 @@
     }
     return "";
+}
+
+sub cmd_jlist
+{
+    if (!$client)
+    {
+	owl::error("You are not logged in to Jabber.");
+	return;
+    }
+    owl::popless_ztext(onGetBuddyList());
 }
 
@@ -160,5 +244,5 @@
     if (!$client)
     {
-	# Error here
+	owl::error("You are not logged in to Jabber.");
 	return;
     }
@@ -173,6 +257,8 @@
   JW_ARG: for (my $i = 1; $i < $argsLen; $i++)
     {
-	$args[$i] =~ /^-t$/ && ($jwrite_thread = $args[++$i]  && next JW_ARG);
-	$args[$i] =~ /^-s$/ && ($jwrite_subject = $args[++$i] && next JW_ARG);
+	$args[$i] =~ /^-t$/ && ($jwrite_thread = $args[++$i]  and next JW_ARG);
+	$args[$i] =~ /^-s$/ && ($jwrite_subject = $args[++$i] and next JW_ARG);
+	$args[$i] =~ /^-g$/ && ($jwrite_type = "groupchat" and next JW_ARG);
+
 	if ($jwrite_to ne '')
 	{
@@ -189,73 +275,115 @@
     }
 
-    if(!$jwrite_to) {
+    if(!$jwrite_to)
+    {
         owl::error("Usage: jwrite JID [-t thread] [-s 'subject']");
         return;
     }
-    
+
+
     owl::message("Type your message below.  End with a dot on a line by itself.  ^C will quit.");
     owl::start_edit_win(join(' ', @args), \&process_owl_jwrite);
 }
 
-sub cmd_join_gc
+sub cmd_jmuc
 {
     if (!$client)
     {
-	# Error here
-	return;
-    }
-    if(!$_[1])
-    {
-	owl::error("Usage: jchat [room]@[server]/[nick]");
-	return;
-    }
-
-    my $x = new XML::Stream::Node('x');
-    $x->put_attrib(xmlns => 'http://jabber.org/protocol/muc');
-    $x->add_child('history')->put_attrib(maxchars => '0');
-
-
-    my $presence = new Net::Jabber::Presence;
-    $presence->SetPresence(to => $_[1]);
-    $presence->AddX($x);
-
-    $client->Send($presence);
+	owl::error("You are not logged in to Jabber.");
+	return;
+    }
+    
+    if (!$_[1])
+    {
+	#XXX TODO: Write general usage for jmuc command.
+	return;
+    }
+
+    my $cmd = $_[1];
+
+    if ($cmd eq 'join')
+    {
+	if (!$_[2])
+	{
+	    owl::error('Usage: jmuc join {muc} [password]');
+	    return;
+	}
+	my $muc = $_[2];
+	my $x = new XML::Stream::Node('x');
+	$x->put_attrib(xmlns => 'http://jabber.org/protocol/muc');
+	$x->add_child('history')->put_attrib(maxchars => '0');
+	
+	if ($_[3]) #password
+	{
+	    $x->add_child('password')->add_cdata($_[3]);
+	}
+
+	my $presence = new Net::Jabber::Presence;
+	$presence->SetPresence(to => $muc);
+	$presence->AddX($x);
+	$client->Send($presence);
+    }
+    elsif ($cmd eq 'part')
+    {
+	my $muc;
+	if (!$_[2])
+	{
+	    my $m = owl::getcurmsg();
+	    if ($m->is_jabber && $m->{jtype} eq 'groupchat')
+	    {
+		$muc = $m->{muc};
+	    }
+	    else
+	    {
+		owl::error('Usage: "jmuc part [muc]"');
+	        return;
+	    }
+	}
+	else
+	{
+	    $muc = $_[2];
+	}
+	$client->PresenceSend(to => $muc, type => 'unavailable');
+    }
+    elsif ($cmd eq 'invite')
+    {
+	my $jid;
+	my $muc;
+
+	owl::error('Usage: jmuc invite {jid} [muc]') if (!$_[2]);
+	
+	if (!@_[3])
+	{  	
+	    my $m = owl::getcurmsg();
+	    if ($m->is_jabber && $m->{jtype} eq 'groupchat')
+	    {
+		$muc = $m->{muc};
+	    }
+	    else
+	    {
+		owl::error('Usage: jmuc invite {jid} [muc]');
+	        return;
+	    }
+	}
+	else
+	{
+	    $muc = $_[3];
+	}
+	
+	my $x = new XML::Stream::Node('x');
+	$x->put_attrib(xmlns => 'http://jabber.org/protocol/muc#user');
+	$x->add_child('invite')->put_attrib(to => $_[2]);
+	
+	my $message = new Net::Jabber::Message;
+	$message->SetTo($muc);
+	$message->AddX($x);
+	
+	$client->Send($message);
+    }
+    else
+    {
+	owl::error('jmuc: unrecognized command.');
+    }
     return "";
-}
-
-sub cmd_part_gc
-{
-    if (!$client)
-    {
-	# Error here
-	return;
-    }
-    if(!$_[1])
-    {
-	owl::error("Usage: jchat [room]@[server]/[nick]");
-	return;
-    }
-
-    $client->PresenceSend(to=>$_[1], type=>'unavailable');
-    return "";
-}
-
-sub cmd_jwrite_gc
-{
-    if (!$client)
-    {
-	# Error here
-	return;
-    }
-
-    $jwrite_to = $_[1];
-    $jwrite_thread = "";
-    $jwrite_subject = "";
-    $jwrite_type = "groupchat";
-    my @args = @_;
-    my $argsLen = @args;
-
-    owl::message("Type your message below.  End with a dot on a line by itself.  ^C will quit.");
-    owl::start_edit_win(join(' ', @args), \&process_owl_jwrite);
 }
 
@@ -296,5 +424,7 @@
 {
     my ($session, $j) = @_;
-    queue_admin_msg("Error ".$j->GetErrorCode()." sending to ".$j->GetFrom('jid')->GetJID('base'));
+    my %jhash = j2hash($j, 'in');
+    $jhash{type} = 'admin';
+    owl::queue_message(owl::Message->new(%jhash));
 }
 
@@ -316,5 +446,25 @@
 {
     my ($session, $j) = @_;
-    owl::queue_message(j2o($j, 'in'));
+    my %props = j2hash($j, 'in');
+
+    # XXX TODO: handle things such as MUC invites here.
+
+#    if ($j->HasX('http://jabber.org/protocol/muc#user'))
+#    {
+#	my $x = $j->GetX('http://jabber.org/protocol/muc#user');
+#	if ($x->HasChild('invite'))
+#	{
+#	    $props	    
+#	}
+#    }
+#    
+    owl::queue_message(owl::Message->new(%props));
+}
+
+sub process_muc_presence
+{
+    my ($session, $p) = @_;
+    return unless ($p->HasX('http://jabber.org/protocol/muc#user'));
+    
 }
 
@@ -322,5 +472,5 @@
 ### Helper functions
 
-sub j2o
+sub j2hash
 {
     my $j = shift;
@@ -330,51 +480,61 @@
 		 direction => $dir);
 
-
-    $props{replycmd} = "jwrite";
-
-    $props{jtype} = $j->GetType();
-    $props{jtype} =~ /^(?:headline|error)$/ && {$props{replycmd} = undef};
-    $props{jtype} =~ /^groupchat$/ && {$props{replycmd} = "jchat"};
-
-    $props{isprivate} = $props{jtype} =~ /^(?:normal|chat)$/;
-
-    my $reply_to;
-    if ($j->DefinedTo())
-    {
-	my $jid = $j->GetTo('jid');
-	$props{recipient} = $jid->GetJID('base');
-	$props{to_jid} = $jid->GetJID('full');
-	if ($dir eq 'out')
-	{
-	    $reply_to = $props{to_jid};
-	    $props{jtype} =~ /^groupchat$/ && {$reply_to = $jid->GetJID('base')};
-	}
-    }
-    if ($j->DefinedFrom())
-    {
-	my $jid = $j->GetFrom('jid');
-	$props{sender} = $jid->GetJID('base');
-	$props{from_jid} = $jid->GetJID('full');
-	$reply_to = $props{from_jid} if ($dir eq 'in');
-	if ($dir eq 'in')
-	{
-	    $reply_to = $props{from_jid};
-	    $props{jtype} =~ /^groupchat$/ && {$reply_to = $jid->GetJID('base')};
-	}
-    }
-
-    $props{subject} = $j->GetSubject() if ($j->DefinedSubject());
-    $props{body} = $j->GetBody() if ($j->DefinedBody());
-#    if ($j->DefinedThread())
-#    {
-#	$props{thread} = $j->GetThread() if ($j->DefinedThread());
-#	$props{replycmd} .= " -t $props{thread}";
-#    }
-    $props{error} = $j->GetError() if ($j->DefinedError());
+    my $jtype = $props{jtype} = $j->GetType();
+    my $from  = $j->GetFrom('jid');
+    my $to    = $j->GetTo('jid');
+
+    $props{from} = $from->GetJID('full');
+    $props{to}   = $to->GetJID('full');
+
+    $props{recipient}  = $to->GetJID('base');
+    $props{sender}     = $from->GetJID('base');
+    $props{subject}    = $j->GetSubject() if ($j->DefinedSubject());
+    $props{thread}     = $j->GetThread() if ($j->DefinedThread());
+    $props{body}       = $j->GetBody() if ($j->DefinedBody());
+    $props{error}      = $j->GetError() if ($j->DefinedError());
     $props{error_code} = $j->GetErrorCode() if ($j->DefinedErrorCode());
-    $props{replycmd} .= " $reply_to";
+    $props{xml}        = $j->GetXML();
+
+    if ($jtype eq 'chat')
+    {
+	$props{replycmd} = "jwrite ".(($dir eq 'in') ? $props{from} : $props{to});
+	$props{isprivate} = 1;
+    }
+    elsif ($jtype eq 'groupchat')
+    {
+	my $nick = $props{nick} = $from->GetResource();
+	my $room = $props{room} = $from->GetJID('base');
+	$props{replycmd} = "jwrite -g $room";
+	
+	$props{sender} = $nick;
+	$props{recipient} = $room;
+
+	if ($props{subject} && !$props{body})
+	{
+	    $props{body} = '['.$nick." has set the topic to: ".$props{subject}."]"
+	}
+    }
+    elsif ($jtype eq 'normal')
+    {
+	$props{replycmd} = undef;
+	$props{isprivate} = 1;
+    }
+    elsif ($jtype eq 'headline')
+    {
+	$props{replycmd} = undef;
+    }
+    elsif ($jtype eq 'error')
+    {
+	$props{replycmd} = undef;
+	$props{body} = "Error ".$props{error_code}." sending to ".$props{from}."\n".$props{error};
+    }
+    
     $props{replysendercmd} = $props{replycmd};
-
-    return owl::Message->new(%props);
+    return %props;
+}
+
+sub j2o
+{
+    return owl::Message->new(j2hash(@_));
 }
 
@@ -387,2 +547,17 @@
     owl::queue_message($m);
 }
+
+sub boldify($)
+{
+    $str = shift;
+
+    return '@b('.$str.')' if ( $str !~ /\)/ );
+    return '@b<'.$str.'>' if ( $str !~ /\>/ );
+    return '@b{'.$str.'}' if ( $str !~ /\}/ );
+    return '@b['.$str.']' if ( $str !~ /\]/ );
+
+    my $txt = "\@b($str";
+    $txt =~ s/\)/\)\@b\[\)\]\@b\(/g;
+    return $txt.')';
+}
+
Index: perlglue.xs
===================================================================
--- perlglue.xs	(revision 6b3f7976162fbf9b730f913e316e477e2c6c6c32)
+++ perlglue.xs	(revision b6a253c676d48c073dc3a401d27fe588737dd4fe)
@@ -217,2 +217,22 @@
 	OUTPUT:
 	RETVAL
+
+void
+popless_text(text) 
+	char *text
+	CODE:
+	{
+		owl_function_popless_text(text);
+	}
+
+void
+popless_ztext(text) 
+	char *text
+	CODE:
+	{
+		owl_fmtext fm;
+		owl_fmtext_init_null(&fm);
+		owl_fmtext_append_ztext(&fm, text);
+		owl_function_popless_fmtext(&fm);
+		owl_fmtext_free(&fm);
+	}
