changeset 443:d8ddb26b9c14

Merge with myself
author mikael@bandit.lilotux.net
date Thu, 22 Sep 2005 19:27:13 +0200
parents 285dea1f1937 (current diff) 3741142ed9e7 (diff)
children 5927c3bfba13
files
diffstat 13 files changed, 356 insertions(+), 93 deletions(-) [+]
line wrap: on
line diff
--- a/mcabber/ChangeLog	Thu Sep 22 19:16:12 2005 +0200
+++ b/mcabber/ChangeLog	Thu Sep 22 19:27:13 2005 +0200
@@ -1,6 +1,13 @@
 mcabber (0.6.9-dev)
 
- * 
+ * New /rawxml command
+ * Multiple resources support
+   The resources can be seen using the /info command
+ * Warn the user when a message is received, which cannot be decoded
+   (local charset unable to display some chars, for example)
+ * Fix a bug when a buddy's name cannot be converted from utf-8 for
+   being displayed in the roster (fall back to jid)
+ * Fix: Status message is not lost anymore when using /move command
 
  -- Mikael, ?
 
--- a/mcabber/TODO	Thu Sep 22 19:16:12 2005 +0200
+++ b/mcabber/TODO	Thu Sep 22 19:27:13 2005 +0200
@@ -18,23 +18,21 @@
 * Publish personal information
 * Show status changes in buddy window (if open)?
 * Options completion
-* Keep track of buddy resources.  Ex.:
-  - buddy A connects with resource r1
-  - buddy A connects with resource r2
-  - A/r2 disconnects
-  Then we see A as being offline, although we should still see A/r1 online
+* MUC: invitation
+* MUC: join password-protected room
 
 * File transfer? :)
 * MUC (Conferences...)
 
 * Commands:
 
-  - /buffer <clear|top|bottom>
+  - /buffer clear|top|bottom
     + /buffer % 50          (jump to 50 %)
     + /buffer date $date    (jump to first msg after $date)
-  - /group <expand|shrink|toggle>
+  - /group expand|shrink|toggle
     + rename
-  - /say_to <jid> blabla
+  - /say_to jid blabla
+  - /status_to jid status [message]
   - /info [jid]
     (request info to the server if the buddy is not in the roster)
   - /server register|unregister
@@ -42,6 +40,5 @@
   - /search <jid>|name
     (server search)
   - /help
-  - /room join|leave|topic|names
-  - /rawxml...
+  - /room join|leave|topic|names|nick
 
--- a/mcabber/doc/mcabber.1	Thu Sep 22 19:16:12 2005 +0200
+++ b/mcabber/doc/mcabber.1	Thu Sep 22 19:27:13 2005 +0200
@@ -156,7 +156,7 @@
 
 .TP
 \fB/info\fR
-Display info on the selected entry (user, agent, group...)\&.
+Display info on the selected entry (user, agent, group...)\&. For users, resources are displayed with the status, priority and status message (if available) of each resource\&.
 
 .TP
 \fB/move\fR [groupname]
@@ -172,6 +172,10 @@
  \fBabort\fR	leave multi\-line mode without sending the message
 
 .TP
+\fB/rawxml\fR send
+ \fBsend\fR string: send string (raw XML format) to the Jabber server\&. No check is done on the string provided\&. BEWARE! Use this only if you know what you are doing, or you could terminate the connection\&.
+
+.TP
 \fB/rename\fR nickname
 Rename current buddy to the given nickname\&. This command does not work for groups, at the moment (but you can move the buddies to another group with the /move command)\&.
 
--- a/mcabber/doc/mcabber.1.html	Thu Sep 22 19:16:12 2005 +0200
+++ b/mcabber/doc/mcabber.1.html	Thu Sep 22 19:27:13 2005 +0200
@@ -335,7 +335,9 @@
 /info
 </b></dt>
 <dd>
-        Display info on the selected entry (user, agent, group&#8230;).
+        Display info on the selected entry (user, agent, group&#8230;).<br />
+        For users, resources are displayed with the status, priority and
+        status message (if available) of each resource.
 </dd>
 <dt><b>
 /move [groupname]
@@ -401,6 +403,12 @@
         Disconnect and leave <tt>mcabber(1)</tt>.
 </dd>
 <dt><b>
+/rawxml send
+</b></dt>
+<dd>
+        <b>send</b> string: send string (raw XML format) to the Jabber server.  No check is done on the string provided.  BEWARE! Use this only if you know what you are doing, or you could terminate the connection.
+</dd>
+<dt><b>
 /rename nickname
 </b></dt>
 <dd>
@@ -549,8 +557,8 @@
 License (GPL).</p>
 <div id="footer">
 <p>
-Version 0.6.8<br />
-Last updated 12-Sep-2005 19:31:21 CEST
+Version 0.6.9-dev<br />
+Last updated 20-Sep-2005 23:15:49 CEST
 </p>
 </div>
 </div>
--- a/mcabber/doc/mcabber.1.txt	Thu Sep 22 19:16:12 2005 +0200
+++ b/mcabber/doc/mcabber.1.txt	Thu Sep 22 19:27:13 2005 +0200
@@ -136,7 +136,9 @@
         'toggle';; toggle the state (fold/unfold) of the current tree
 
 /info::
-        Display info on the selected entry (user, agent, group...).
+        Display info on the selected entry (user, agent, group...). +
+        For users, resources are displayed with the status, priority and
+        status message (if available) of each resource.
 
 /move [groupname]::
         Move the current buddy to the requested group.  If no group is
@@ -163,6 +165,9 @@
 /quit::
         Disconnect and leave `mcabber(1)`.
 
+/rawxml send::
+        'send' string: send string (raw XML format) to the Jabber server.  No check is done on the string provided.  BEWARE! Use this only if you know what you are doing, or you could terminate the connection.
+
 /rename nickname::
         Rename current buddy to the given nickname.
         This command does not work for groups, at the moment (but you can move
--- a/mcabber/src/commands.c	Thu Sep 22 19:16:12 2005 +0200
+++ b/mcabber/src/commands.c	Thu Sep 22 19:27:13 2005 +0200
@@ -49,6 +49,7 @@
 static void do_bind(char *arg);
 static void do_connect(char *arg);
 static void do_disconnect(char *arg);
+static void do_rawxml(char *arg);
 
 // Global variable for the commands list
 static GSList *Commands;
@@ -92,6 +93,7 @@
           COMPL_MULTILINE, 0, &do_msay);
   //cmd_add("nick");
   cmd_add("quit", "Exit the software", 0, 0, NULL);
+  cmd_add("rawxml", "Send a raw XML string", 0, 0, &do_rawxml);
   cmd_add("rename", "Rename the current buddy", 0, 0, &do_rename);
   //cmd_add("request_auth");
   cmd_add("roster", "Manipulate the roster/buddylist", COMPL_ROSTER, 0,
@@ -614,9 +616,8 @@
 static void do_info(char *arg)
 {
   gpointer bud;
-  const char *jid, *name, *st_msg;
+  const char *jid, *name;
   guint type;
-  enum imstatus status;
   char *buffer;
 
   if (!current_buddy) return;
@@ -625,12 +626,11 @@
   jid    = buddy_getjid(bud);
   name   = buddy_getname(bud);
   type   = buddy_gettype(bud);
-  status = buddy_getstatus(bud);
-  st_msg = buddy_getstatusmsg(bud);
 
   buffer = g_new(char, 128);
 
   if (jid) {
+    GSList *resources;
     char *typestr = "unknown";
 
     snprintf(buffer, 127, "jid:  <%s>", jid);
@@ -639,20 +639,34 @@
       snprintf(buffer, 127, "Name: %s", name);
       scr_WriteIncomingMessage(jid, buffer, 0, HBB_PREFIX_INFO);
     }
-    if (st_msg) {
-      snprintf(buffer, 127, "Status message: %s", st_msg);
-      scr_WriteIncomingMessage(jid, buffer, 0, HBB_PREFIX_INFO);
-    }
 
-    if (type == ROSTER_TYPE_USER) typestr = "user";
+    if (type == ROSTER_TYPE_USER)       typestr = "user";
     else if (type == ROSTER_TYPE_AGENT) typestr = "agent";
-
     snprintf(buffer, 127, "Type: %s", typestr);
     scr_WriteIncomingMessage(jid, buffer, 0, HBB_PREFIX_INFO);
+
+    resources = buddy_getresources(bud);
+    for ( ; resources ; resources = g_slist_next(resources) ) {
+      gchar rprio;
+      enum imstatus rstatus;
+      const char *rst_msg;
+
+      rprio   = buddy_getresourceprio(bud, resources->data);
+      rstatus = buddy_getstatus(bud, resources->data);
+      rst_msg = buddy_getstatusmsg(bud, resources->data);
+
+      snprintf(buffer, 127, "Resource: [%c] (%d) %s", imstatus2char[rstatus],
+               rprio, (char*)resources->data);
+      scr_WriteIncomingMessage(jid, buffer, 0, HBB_PREFIX_INFO);
+      if (rst_msg) {
+        snprintf(buffer, 127, "Status message: %s", rst_msg);
+        scr_WriteIncomingMessage(jid, buffer, 0, HBB_PREFIX_INFO);
+      }
+    }
   } else {
     if (name) scr_LogPrint(LPRINT_NORMAL, "Name: %s", name);
     scr_LogPrint(LPRINT_NORMAL, "Type: %s",
-            ((type == ROSTER_TYPE_GROUP) ? "group" : "unknown"));
+                 ((type == ROSTER_TYPE_GROUP) ? "group" : "unknown"));
   }
 
   g_free(buffer);
@@ -823,6 +837,19 @@
     settings_set(SETTINGS_TYPE_BINDING, keycode, value);
 }
 
+static void do_rawxml(char *arg)
+{
+  if (!strncasecmp(arg, "send ", 5))  {
+    for (arg += 5; *arg && *arg == ' '; arg++)
+      ;
+    scr_LogPrint(LPRINT_NORMAL, "Sending XML string");
+    jb_send_raw(arg);
+  } else {
+    scr_LogPrint(LPRINT_NORMAL, "Please read the manual page"
+                 " before using /rawxml :-)");
+  }
+}
+
 static void do_connect(char *arg)
 {
   mcabber_connect();
--- a/mcabber/src/hooks.c	Thu Sep 22 19:16:12 2005 +0200
+++ b/mcabber/src/hooks.c	Thu Sep 22 19:27:13 2005 +0200
@@ -60,7 +60,8 @@
   // We need to rebuild the list if the sender is unknown or
   // if the sender is offline/invisible and hide_offline_buddies is set
   if (new_guy ||
-     (roster_getstatus(jid) == offline && buddylist_get_hide_offline_buddies()))
+      (roster_getstatus(jid, NULL) == offline &&
+       buddylist_get_hide_offline_buddies()))
   {
     buddylist_build();
     update_roster = TRUE;
@@ -75,13 +76,16 @@
   hk_ext_cmd(jid, 'M', 'S', NULL);
 }
 
-inline void hk_statuschange(const char *jid, time_t timestamp,
-        enum imstatus status, const char *status_msg)
+inline void hk_statuschange(const char *jid, const char *resname, gchar prio,
+                            time_t timestamp, enum imstatus status,
+                            const char *status_msg)
 {
-  scr_LogPrint(LPRINT_LOGNORM, "Buddy status has changed: [%c>%c] <%s> %s",
-          imstatus2char[roster_getstatus(jid)], imstatus2char[status], jid,
-          ((status_msg) ? status_msg : ""));
-  roster_setstatus(jid, status, status_msg);
+  const char *rn = (resname ? resname : "default");
+  scr_LogPrint(LPRINT_LOGNORM, "Buddy status has changed: [%c>%c] <%s/%s> %s",
+               imstatus2char[roster_getstatus(jid, resname)],
+               imstatus2char[status], jid, rn,
+               ((status_msg) ? status_msg : ""));
+  roster_setstatus(jid, rn, prio, status, status_msg);
   buddylist_build();
   scr_DrawRoster();
   hlog_write_status(jid, 0, status, status_msg);
--- a/mcabber/src/hooks.h	Thu Sep 22 19:16:12 2005 +0200
+++ b/mcabber/src/hooks.h	Thu Sep 22 19:27:13 2005 +0200
@@ -8,10 +8,12 @@
 inline void hk_message_in(const char *jid, time_t timestamp, const char *msg,
                           const char *type);
 inline void hk_message_out(const char *jid, time_t timestamp, const char *msg);
-inline void hk_statuschange(const char *jid, time_t timestamp,
-        enum imstatus status, char const *status_msg);
+inline void hk_statuschange(const char *jid, const char *resname, gchar prio,
+                            time_t timestamp, enum imstatus status,
+                            char const *status_msg);
 inline void hk_mystatuschange(time_t timestamp,
-        enum imstatus old_status, enum imstatus new_status, const char *msg);
+                              enum imstatus old_status,
+                              enum imstatus new_status, const char *msg);
 
 void hk_ext_cmd_init(const char *command);
 void hk_ext_cmd(const char *jid, guchar type, guchar info, const char *data);
--- a/mcabber/src/jabglue.c	Thu Sep 22 19:16:12 2005 +0200
+++ b/mcabber/src/jabglue.c	Thu Sep 22 19:27:13 2005 +0200
@@ -29,6 +29,7 @@
 #include "hooks.h"
 #include "utils.h"
 #include "settings.h"
+#include "hbuf.h"
 
 #define JABBERPORT      5222
 #define JABBERSSLPORT   5223
@@ -99,8 +100,7 @@
   char *ptr;
   char *alias;
 
-  while ((alias = g_strdup(jid)) == NULL)
-    safe_usleep(100);
+  alias = g_strdup(jid);
 
   if ((ptr = strchr(alias, '/')) != NULL) {
     *ptr = 0;
@@ -173,6 +173,12 @@
   time(&LastPingTime);
 }
 
+void jb_send_raw(const char *str)
+{
+  if (jc && online)
+    jab_send_raw(jc, str);
+}
+
 void jb_keepalive()
 {
   if (jc && online)
@@ -535,11 +541,11 @@
       gchar *name_noutf8 = NULL;
       gchar *group_noutf8 = NULL;
 
+      buddyname = cleanalias;
       if (name) {
         name_noutf8 = from_utf8(name);
-        buddyname = name_noutf8;
-      } else
-        buddyname = cleanalias;
+        if (name_noutf8) buddyname = name_noutf8;
+      }
 
       if (group)
         group_noutf8 = from_utf8(group);
@@ -560,6 +566,18 @@
   char *jid;
   gchar *buffer = from_utf8(body);
 
+  jid = jidtodisp(from);
+
+  if (!buffer && body) {
+    scr_LogPrint(LPRINT_LOGNORM, "Decoding of message from <%s> has failed",
+                 from);
+    scr_WriteIncomingMessage(jid, "Cannot display message: "
+                             "UTF-8 conversion failure",
+                             0, HBB_PREFIX_ERR | HBB_PREFIX_IN);
+    g_free(jid);
+    return;
+  }
+
   /*
   //char *u, *h, *r;
   //jidsplit(from, &u, &h, &r);
@@ -571,7 +589,6 @@
   //             jidtodisp(from), type);
   */
 
-  jid = jidtodisp(from);
   hk_message_in(jid, timestamp, buffer, type);
   g_free(jid);
   g_free(buffer);
@@ -704,12 +721,13 @@
 void packethandler(jconn conn, jpacket packet)
 {
   char *p, *r;
-  const char *m;
+  const char *m, *rname;
   xmlnode x, y;
   char *from=NULL, *type=NULL, *body=NULL, *enc=NULL;
   char *ns=NULL;
   char *id=NULL;
   enum imstatus ust;
+  char bpprio;
 
   jb_reset_keepalive(); // reset keepalive delay
   jpacket_reset(packet);
@@ -897,18 +915,18 @@
           g_free(r);
           break;
         }
-        x = xmlnode_get_tag(packet->x, "show");
-        ust = available;
 
-        if (x) {
-          p = xmlnode_get_data(x); if (p) ns = p;
+        p = xmlnode_get_tag_data(packet->x, "priority");
+        if (p && *p) bpprio = (gchar)atoi(p);
+        else         bpprio = 0;
 
-          if (ns) {
-            if (!strcmp(ns, "away"))      ust = away;
-            else if (!strcmp(ns, "dnd"))  ust = dontdisturb;
-            else if (!strcmp(ns, "xa"))   ust = notavail;
-            else if (!strcmp(ns, "chat")) ust = freeforchat;
-          }
+        ust = available;
+        p = xmlnode_get_tag_data(packet->x, "show");
+        if (p) {
+          if (!strcmp(p, "away"))      ust = away;
+          else if (!strcmp(p, "dnd"))  ust = dontdisturb;
+          else if (!strcmp(p, "xa"))   ust = notavail;
+          else if (!strcmp(p, "chat")) ust = freeforchat;
         }
 
         if (type && !strcmp(type, "unavailable"))
@@ -921,9 +939,11 @@
 
         // Call hk_statuschange() if status has changed or if the
         // status message is different
-        m = roster_getstatusmsg(r);
-        if ((ust != roster_getstatus(r)) || (p && (!m || strcmp(p, m))))
-          hk_statuschange(r, 0, ust, p);
+        rname = strchr(from, '/');
+        if (rname) rname++;
+        m = roster_getstatusmsg(r, rname);
+        if ((ust != roster_getstatus(r, rname)) || (p && (!m || strcmp(p, m))))
+          hk_statuschange(r, rname, bpprio, 0, ust, p);
         g_free(r);
         if (p) g_free(p);
         break;
--- a/mcabber/src/jabglue.h	Thu Sep 22 19:16:12 2005 +0200
+++ b/mcabber/src/jabglue.h	Thu Sep 22 19:27:13 2005 +0200
@@ -48,6 +48,7 @@
 inline enum imstatus jb_getstatus();
 void jb_setstatus(enum imstatus st, const char *msg);
 void jb_send_msg(const char *, const char *);
+void jb_send_raw(const char *str);
 void jb_keepalive();
 inline void jb_reset_keepalive();
 void jb_set_keepalive_delay(unsigned int delay);
--- a/mcabber/src/roster.c	Thu Sep 22 19:16:12 2005 +0200
+++ b/mcabber/src/roster.c	Thu Sep 22 19:27:13 2005 +0200
@@ -25,14 +25,26 @@
 #include "roster.h"
 
 
+/* Resource structure */
+
+typedef struct {
+  gchar *name;
+  gchar prio;
+  enum imstatus status;
+  gchar *status_msg;
+  enum imrole role;
+  gchar *realjid;       /* for chatrooms, if buddy's real jid is known */
+} res;
+
 /* This is a private structure type for the roster */
 
 typedef struct {
-  const gchar *name;
-  const gchar *jid;
-  const gchar *status_msg;
+  gchar *name;
+  gchar *jid;
   guint type;
-  enum imstatus status;
+  enum subscr subscription;
+  GSList *resource;
+  res *cur_res;
   guint flags;
   // list: user -> points to his group; group -> points to its users list
   GSList *list;
@@ -49,6 +61,115 @@
 GList *alternate_buddy;
 
 
+/* ### Resources functions ### */
+
+static void free_all_resources(GSList **reslist)
+{
+  GSList *lip;
+  res *p_res;
+
+  for ( lip = *reslist; lip ; lip = g_slist_next(lip)) {
+    p_res = (res*)lip->data;
+    if (p_res->status_msg) {
+      g_free((gchar*)p_res->status_msg);
+    }
+    if (p_res->name) {
+      g_free((gchar*)p_res->name);
+    }
+    if (p_res->realjid) {
+      g_free((gchar*)p_res->realjid);
+    }
+  }
+  // Free all nodes but the first (which is static)
+  g_slist_free(*reslist);
+  *reslist = NULL;
+}
+
+// Resources are sorted in ascending order
+static gint resource_compare_prio(res *a, res *b) {
+  //return (a->prio - b->prio);
+  if (a->prio < b->prio) return -1;
+  else                   return 1;
+}
+
+//  get_resource(rost, resname)
+// Return a pointer to the resource with name resname, in rost's resources list
+// - if rost has no resources, return NULL
+// - if resname is defined, return the match or NULL
+// - if resname is NULL, the last resource is returned, currently
+//   This could change in the future, because we should return the best one
+//   (priority? last used? and fall back to the first resource)
+//
+static res *get_resource(roster *rost, const char *resname)
+{
+  GSList *p;
+  res *r = NULL;
+
+  for (p = rost->resource; p; p = g_slist_next(p)) {
+    r = p->data;
+    if (resname && !strcmp(r->name, resname))
+      return r;
+  }
+
+  // The last resource is one of the resources with the highest priority,
+  // however, we don't know if it is the more-recently-used.
+  if (!resname) return r;
+  return NULL;
+}
+
+//  get_or_add_resource(rost, resname, priority)
+// - if there is a "resname" resource in rost's resources, return a pointer
+//   on this resource
+// - if not, add the resource, set the name, and return a pointer on this
+//   new resource
+static res *get_or_add_resource(roster *rost, const char *resname, gchar prio)
+{
+  GSList *p;
+  res *nres;
+
+  if (!resname) return NULL;
+
+  for (p = rost->resource; p; p = g_slist_next(p)) {
+    res *r = p->data;
+    if (!strcmp(r->name, resname))
+      return r;
+  }
+
+  // Resource not found
+  nres = g_new0(res, 1);
+  nres->name = g_strdup(resname);
+  nres->prio = prio;
+  rost->resource = g_slist_insert_sorted(rost->resource, nres,
+                                         (GCompareFunc)&resource_compare_prio);
+  return nres;
+}
+
+static void del_resource(roster *rost, const char *resname)
+{
+  GSList *p;
+  GSList *p_res_elt = NULL;
+  res *p_res;
+
+  if (!resname) return;
+
+  for (p = rost->resource; p; p = g_slist_next(p)) {
+    res *r = p->data;
+    if (!strcmp(r->name, resname))
+      p_res_elt = p;
+  }
+
+  if (!p_res_elt) return;   // Resource not found
+
+  p_res = p_res_elt->data;
+  // Free allocations and delete resource node
+  if (p_res->name)        g_free(p_res->name);
+  if (p_res->status_msg)  g_free(p_res->status_msg);
+  if (p_res->realjid)     g_free(p_res->realjid);
+  rost->resource = g_slist_delete_link(rost->resource, p_res_elt);
+  return;
+}
+
+
 /* ### Roster functions ### */
 
 // Comparison function used to search in the roster (compares jids and types)
@@ -80,10 +201,10 @@
 
   sample.type = roster_type;
   if (type == jidsearch) {
-    sample.jid = jidname;
+    sample.jid = (gchar*)jidname;
     comp = (GCompareFunc)&roster_compare_jid_type;
   } else if (type == namesearch) {
-    sample.name = jidname;
+    sample.name = (gchar*)jidname;
     comp = (GCompareFunc)&roster_compare_name;
   } else
     return NULL;    // should not happen
@@ -183,7 +304,7 @@
   // Let's free memory (jid, name, status message)
   if (roster_usr->jid)        g_free((gchar*)roster_usr->jid);
   if (roster_usr->name)       g_free((gchar*)roster_usr->name);
-  if (roster_usr->status_msg) g_free((gchar*)roster_usr->status_msg);
+  free_all_resources(&roster_usr->resource);
   g_free(roster_usr);
 
   // That's a little complex, we need to dereference twice
@@ -220,7 +341,7 @@
       // Free name and jid
       if (roster_usr->jid)        g_free((gchar*)roster_usr->jid);
       if (roster_usr->name)       g_free((gchar*)roster_usr->name);
-      if (roster_usr->status_msg) g_free((gchar*)roster_usr->status_msg);
+      free_all_resources(&roster_usr->resource);
       g_free(roster_usr);
       sl_usr = g_slist_next(sl_usr);
     }
@@ -243,25 +364,38 @@
   }
 }
 
-void roster_setstatus(const char *jid, enum imstatus bstat,
-        const char *status_msg)
+void roster_setstatus(const char *jid, const char *resname, gchar prio,
+                      enum imstatus bstat, const char *status_msg)
 {
   GSList *sl_user;
   roster *roster_usr;
+  res *p_res;
 
   sl_user = roster_find(jid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_AGENT);
   // If we can't find it, we add it
   if (sl_user == NULL)
     sl_user = roster_add_user(jid, NULL, NULL, ROSTER_TYPE_USER);
 
+  // If there is no resource name, we can leave now
+  if (!resname) return;
+
   roster_usr = (roster*)sl_user->data;
-  roster_usr->status = bstat;
-  if (roster_usr->status_msg) {
-    g_free((gchar*)roster_usr->status_msg);
-    roster_usr->status_msg = NULL;
+
+  // If bstat is offline, we MUST delete the resource, actually
+  if (bstat == offline) {
+    del_resource(roster_usr, resname);
+    return;
+  }
+
+  // New or updated resource
+  p_res = get_or_add_resource(roster_usr, resname, prio);
+  p_res->status = bstat;
+  if (p_res->status_msg) {
+    g_free((gchar*)p_res->status_msg);
+    p_res->status_msg = NULL;
   }
   if (status_msg)
-    roster_usr->status_msg = g_strdup(status_msg);
+    p_res->status_msg = g_strdup(status_msg);
 }
 
 //  roster_setflags()
@@ -347,30 +481,38 @@
   roster_usr->type = type;
 }
 
-enum imstatus roster_getstatus(const char *jid)
+enum imstatus roster_getstatus(const char *jid, const char *resname)
 {
   GSList *sl_user;
   roster *roster_usr;
+  res *p_res;
 
   sl_user = roster_find(jid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_AGENT);
   if (sl_user == NULL)
     return offline; // Not in the roster, anyway...
 
   roster_usr = (roster*)sl_user->data;
-  return roster_usr->status;
+  p_res = get_resource(roster_usr, resname);
+  if (p_res)
+    return p_res->status;
+  return offline;
 }
 
-const char *roster_getstatusmsg(const char *jid)
+const char *roster_getstatusmsg(const char *jid, const char *resname)
 {
   GSList *sl_user;
   roster *roster_usr;
+  res *p_res;
 
   sl_user = roster_find(jid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_AGENT);
   if (sl_user == NULL)
     return NULL; // Not in the roster, anyway...
 
   roster_usr = (roster*)sl_user->data;
-  return roster_usr->status_msg;
+  p_res = get_resource(roster_usr, resname);
+  if (p_res)
+    return p_res->status_msg;
+  return NULL;
 }
 
 guint roster_gettype(const char *jid)
@@ -466,7 +608,7 @@
       // - group isn't hidden (shrunk)
       // - this is the current_buddy
       if (!hide_offline_buddies || roster_usrelt == roster_current_buddy ||
-          (buddy_getstatus((gpointer)roster_usrelt) != offline) ||
+          (buddy_getstatus((gpointer)roster_usrelt, NULL) != offline) ||
           (buddy_getflags((gpointer)roster_usrelt) &
                (ROSTER_FLAG_LOCK | ROSTER_FLAG_MSG))) {
         // This user should be added.  Maybe the group hasn't been added yet?
@@ -544,13 +686,14 @@
   sl_clone = roster_add_user(roster_usr->jid, roster_usr->name,
           newgroupname, roster_usr->type);
   roster_clone = (roster*)sl_clone->data;
-  roster_clone->status = roster_usr->status;
-  roster_clone->flags  = roster_usr->flags;
+  roster_clone->flags = roster_usr->flags;
+  roster_clone->resource = roster_usr->resource;
+  roster_usr->resource = NULL;
 
   // Free old buddy
   if (roster_usr->jid)        g_free((gchar*)roster_usr->jid);
   if (roster_usr->name)       g_free((gchar*)roster_usr->name);
-  if (roster_usr->status_msg) g_free((gchar*)roster_usr->status_msg);
+  free_all_resources(&roster_usr->resource);
   g_free(roster_usr);
 
   // If new new group is folded, the curren_buddy will be lost, and the
@@ -629,16 +772,45 @@
   return roster_usr->type;
 }
 
-enum imstatus buddy_getstatus(gpointer rosterdata)
+enum imstatus buddy_getstatus(gpointer rosterdata, const char *resname)
+{
+  roster *roster_usr = rosterdata;
+  res *p_res = get_resource(roster_usr, resname);
+  if (p_res)
+    return p_res->status;
+  return offline;
+}
+
+const char *buddy_getstatusmsg(gpointer rosterdata, const char *resname)
 {
   roster *roster_usr = rosterdata;
-  return roster_usr->status;
+  res *p_res = get_resource(roster_usr, resname);
+  if (p_res)
+    return p_res->status_msg;
+  return NULL;
 }
 
-const char *buddy_getstatusmsg(gpointer rosterdata)
+gchar buddy_getresourceprio(gpointer rosterdata, const char *resname)
 {
   roster *roster_usr = rosterdata;
-  return roster_usr->status_msg;
+  res *p_res = get_resource(roster_usr, resname);
+  if (p_res)
+    return p_res->prio;
+  return 0;
+}
+
+//  buddy_getresources(roster_data)
+// Return a singly-linked-list of resource names
+// Note: the caller should free the list (and data) after use
+GSList *buddy_getresources(gpointer rosterdata)
+{
+  roster *roster_usr = rosterdata;
+  GSList *reslist = NULL, *lp;
+
+  for (lp = roster_usr->resource; lp; lp = g_slist_next(lp))
+    reslist = g_slist_append(reslist, g_strdup(((res*)lp->data)->name));
+
+  return reslist;
 }
 
 //  buddy_setflags()
--- a/mcabber/src/roster.h	Thu Sep 22 19:16:12 2005 +0200
+++ b/mcabber/src/roster.h	Thu Sep 22 19:27:13 2005 +0200
@@ -5,6 +5,20 @@
 
 # include "jabglue.h"
 
+enum imrole {
+  role_none,
+  role_moderator,
+  role_participant,
+  role_visitor
+};
+
+enum subscr {
+  sub_none,
+  sub_to,
+  sub_from,
+  sub_both
+};
+
 enum findwhat {
   jidsearch,
   namesearch
@@ -36,16 +50,16 @@
         guint type);
 void    roster_del_user(const char *jid);
 void    roster_free(void);
-void    roster_setstatus(const char *jid, enum imstatus bstat,
-        const char *status_msg);
+void    roster_setstatus(const char *jid, const char *resname, gchar prio,
+                         enum imstatus bstat, const char *status_msg);
 void    roster_setflags(const char *jid, guint flags, guint value);
 void    roster_msg_setflag(const char *jid, guint value);
 void    roster_settype(const char *jid, guint type);
-enum imstatus roster_getstatus(const char *jid);
-const char   *roster_getstatusmsg(const char *jid);
+enum imstatus roster_getstatus(const char *jid, const char *resname);
+const char   *roster_getstatusmsg(const char *jid, const char *resname);
 guint   roster_gettype(const char *jid);
 inline guint roster_exists(const char *jidname, enum findwhat type,
-        guint roster_type);
+                           guint roster_type);
 
 void    buddylist_build(void);
 void    buddy_hide_group(gpointer rosterdata, int hide);
@@ -58,8 +72,10 @@
 void    buddy_setgroup(gpointer rosterdata, char *newgroupname);
 const char *buddy_getgroupname(gpointer rosterdata);
 gpointer buddy_getgroup(gpointer rosterdata);
-enum imstatus buddy_getstatus(gpointer rosterdata);
-const char *buddy_getstatusmsg(gpointer rosterdata);
+enum imstatus buddy_getstatus(gpointer rosterdata, const char *resname);
+const char *buddy_getstatusmsg(gpointer rosterdata, const char *resname);
+gchar   buddy_getresourceprio(gpointer rosterdata, const char *resname);
+GSList *buddy_getresources(gpointer rosterdata);
 void    buddy_setflags(gpointer rosterdata, guint flags, guint value);
 guint   buddy_getflags(gpointer rosterdata);
 GList  *buddy_search(char *string);
--- a/mcabber/src/screen.c	Thu Sep 22 19:16:12 2005 +0200
+++ b/mcabber/src/screen.c	Thu Sep 22 19:27:13 2005 +0200
@@ -733,7 +733,7 @@
       pending = '#';
     }
 
-    budstate = buddy_getstatus(BUDDATA(buddy));
+    budstate = buddy_getstatus(BUDDATA(buddy), NULL);
     if (budstate >= 0 && budstate < imstatus_size && currentstatus != offline)
       status = imstatus2char[budstate];
     if (buddy == current_buddy) {
@@ -849,7 +849,7 @@
   if (!current_buddy || !newbuddy)  return;
   if (newbuddy == current_buddy)    return;
 
-  prev_st = buddy_getstatus(BUDDATA(current_buddy));
+  prev_st = buddy_getstatus(BUDDATA(current_buddy), NULL);
   buddy_setflags(BUDDATA(current_buddy), ROSTER_FLAG_LOCK, FALSE);
   if (chatmode)
     alternate_buddy = current_buddy;