Beispiel #1
0
static void
_presence_join_room(char *room, char *nick, char * passwd)
{
    Jid *jid = jid_create_from_bare_and_resource(room, nick);

    log_debug("Sending room join presence to: %s", jid->fulljid);
    xmpp_ctx_t *ctx = connection_get_ctx();
    xmpp_conn_t *conn = connection_get_conn();
    resource_presence_t presence_type =
        accounts_get_last_presence(jabber_get_account_name());
    const char *show = stanza_get_presence_string_from_type(presence_type);
    char *status = jabber_get_presence_message();
    int pri = accounts_get_priority_for_presence_type(jabber_get_account_name(),
        presence_type);

    xmpp_stanza_t *presence = stanza_create_room_join_presence(ctx, jid->fulljid, passwd);
    stanza_attach_show(ctx, presence, show);
    stanza_attach_status(ctx, presence, status);
    stanza_attach_priority(ctx, presence, pri);
    stanza_attach_caps(ctx, presence);

    xmpp_send(conn, presence);
    xmpp_stanza_release(presence);

    jid_destroy(jid);
}
Beispiel #2
0
jabber_conn_status_t
jabber_connect_with_account(const ProfAccount * const account)
{
    assert(account != NULL);

    log_info("Connecting using account: %s", account->name);

    // save account name and password for reconnect
    if (saved_account.name) {
        free(saved_account.name);
    }
    saved_account.name = strdup(account->name);
    if (saved_account.passwd) {
        free(saved_account.passwd);
    }
    saved_account.passwd = strdup(account->password);

    // connect with fulljid
    Jid *jidp = jid_create_from_bare_and_resource(account->jid, account->resource);
    jabber_conn_status_t result =
      _jabber_connect(jidp->fulljid, account->password, account->server, account->port);
    jid_destroy(jidp);

    return result;
}
Beispiel #3
0
static char*
_session_jid(const char *const barejid)
{
    ChatSession *session = chat_session_get(barejid);
    char *jid = NULL;
    if (session) {
        Jid *jidp = jid_create_from_bare_and_resource(session->barejid, session->resource);
        jid = strdup(jidp->fulljid);
        jid_destroy(jidp);
    } else {
        jid = strdup(barejid);
    }

    return jid;
}
Beispiel #4
0
jabber_conn_status_t
jabber_connect_with_details(const char *const jid, const char *const passwd, const char *const altdomain,
    const int port, const char *const tls_policy)
{
    assert(jid != NULL);
    assert(passwd != NULL);

    // save details for reconnect, remember name for account creating on success
    saved_details.name = strdup(jid);
    saved_details.passwd = strdup(passwd);
    if (altdomain) {
        saved_details.altdomain = strdup(altdomain);
    } else {
        saved_details.altdomain = NULL;
    }
    if (port != 0) {
        saved_details.port = port;
    } else {
        saved_details.port = 0;
    }
    if (tls_policy) {
        saved_details.tls_policy = strdup(tls_policy);
    } else {
        saved_details.tls_policy = NULL;
    }

    // use 'profanity' when no resourcepart in provided jid
    Jid *jidp = jid_create(jid);
    if (jidp->resourcepart == NULL) {
        jid_destroy(jidp);
        jidp = jid_create_from_bare_and_resource(jid, "profanity");
        saved_details.jid = strdup(jidp->fulljid);
    } else {
        saved_details.jid = strdup(jid);
    }
    jid_destroy(jidp);

    // connect with fulljid
    log_info("Connecting without account, JID: %s", saved_details.jid);

    return _jabber_connect(
        saved_details.jid,
        passwd,
        saved_details.altdomain,
        saved_details.port,
        saved_details.tls_policy);
}
void
handle_contact_offline(char *contact, char *resource, char *status)
{
    gboolean updated = roster_contact_offline(contact, resource, status);

    if (resource != NULL && updated && prefs_get_boolean(PREF_STATUSES)) {
        Jid *jid = jid_create_from_bare_and_resource(contact, resource);
        PContact result = roster_get_contact(contact);
        if (p_contact_subscription(result) != NULL) {
            if (strcmp(p_contact_subscription(result), "none") != 0) {
                ui_contact_offline(jid->fulljid, "offline", status);
                ui_current_page_off();
            }
        }
        jid_destroy(jid);
    }
}
Beispiel #6
0
void
ui_contact_offline(char *barejid, char *resource, char *status)
{
    char *show_console = prefs_get_string(PREF_STATUSES_CONSOLE);
    char *show_chat_win = prefs_get_string(PREF_STATUSES_CHAT);
    Jid *jid = jid_create_from_bare_and_resource(barejid, resource);
    PContact contact = roster_get_contact(barejid);
    if (p_contact_subscription(contact)) {
        if (strcmp(p_contact_subscription(contact), "none") != 0) {

            // show in console if "all"
            if (g_strcmp0(show_console, "all") == 0) {
                cons_show_contact_offline(contact, resource, status);

            // show in console of "online"
            } else if (g_strcmp0(show_console, "online") == 0) {
                cons_show_contact_offline(contact, resource, status);
            }

            // show in chat win if "all"
            if (g_strcmp0(show_chat_win, "all") == 0) {
                ProfChatWin *chatwin = wins_get_chat(barejid);
                if (chatwin) {
                    chatwin_contact_offline(chatwin, resource, status);
                }

            // show in chat win if "online" and presence online
            } else if (g_strcmp0(show_chat_win, "online") == 0) {
                ProfChatWin *chatwin = wins_get_chat(barejid);
                if (chatwin) {
                    chatwin_contact_offline(chatwin, resource, status);
                }
            }
        }
    }

    ProfChatWin *chatwin = wins_get_chat(barejid);
    if (chatwin && chatwin->resource_override && (g_strcmp0(resource, chatwin->resource_override) == 0)) {
        FREE_SET_NULL(chatwin->resource_override);
    }

    prefs_free_string(show_console);
    prefs_free_string(show_chat_win);
    jid_destroy(jid);
}
Beispiel #7
0
void
sv_ev_room_occupent_banned(const char *const room, const char *const nick, const char *const actor,
    const char *const reason)
{
    muc_roster_remove(room, nick);
    ProfMucWin *mucwin = wins_get_muc(room);
    if (mucwin) {
        mucwin_occupant_banned(mucwin, nick, actor, reason);
    }

    Jid *jidp = jid_create_from_bare_and_resource(room, nick);
    ProfPrivateWin *privwin = wins_get_private(jidp->fulljid);
    jid_destroy(jidp);
    if (privwin != NULL) {
        privwin_occupant_banned(privwin, actor, reason);
    }

    occupantswin_occupants(room);
    rosterwin_roster();
}
Beispiel #8
0
gboolean
roster_update_presence(const char *const barejid, Resource *resource, GDateTime *last_activity)
{
    assert(barejid != NULL);
    assert(resource != NULL);

    PContact contact = roster_get_contact(barejid);
    if (contact == NULL) {
        return FALSE;
    }
    if (!_datetimes_equal(p_contact_last_activity(contact), last_activity)) {
        p_contact_set_last_activity(contact, last_activity);
    }
    p_contact_set_presence(contact, resource);
    Jid *jid = jid_create_from_bare_and_resource(barejid, resource->name);
    autocomplete_add(fulljid_ac, jid->fulljid);
    jid_destroy(jid);

    return TRUE;
}
Beispiel #9
0
void
ui_contact_online(const char * const barejid, const char * const resource,
    const char * const show, const char * const status, GDateTime *last_activity)
{
    Jid *jid = jid_create_from_bare_and_resource(barejid, resource);
    PContact contact = roster_get_contact(barejid);
    GString *display_str = g_string_new("");

    // use nickname if exists
    if (p_contact_name(contact) != NULL) {
        g_string_append(display_str, p_contact_name(contact));
    } else {
        g_string_append(display_str, barejid);
    }

    // add resource if not default provided by profanity
    if (strcmp(jid->resourcepart, "__prof_default") != 0) {
        g_string_append(display_str, " (");
        g_string_append(display_str, jid->resourcepart);
        g_string_append(display_str, ")");
    }

    ProfWin *console = wins_get_console();
    _show_status_string(console, display_str->str, show, status, last_activity,
        "++", "online");

    ProfWin *window = wins_get_by_recipient(barejid);
    if (window != NULL) {
        _show_status_string(window, display_str->str, show, status,
            last_activity, "++", "online");
    }

    jid_destroy(jid);
    g_string_free(display_str, TRUE);

    if (wins_is_current(console)) {
        wins_refresh_current();
    } else if ((window != NULL) && (wins_is_current(window))) {
        wins_refresh_current();
    }
}
Beispiel #10
0
gboolean
roster_contact_offline(const char *const barejid, const char *const resource, const char *const status)
{
    PContact contact = roster_get_contact(barejid);

    if (contact == NULL) {
        return FALSE;
    }
    if (resource == NULL) {
        return TRUE;
    } else {
        gboolean result = p_contact_remove_resource(contact, resource);
        if (result == TRUE) {
            Jid *jid = jid_create_from_bare_and_resource(barejid, resource);
            autocomplete_remove(fulljid_ac, jid->fulljid);
            jid_destroy(jid);
        }

        return result;
    }
}
Beispiel #11
0
void
handle_contact_offline(char *barejid, char *resource, char *status)
{
    gboolean updated = roster_contact_offline(barejid, resource, status);

    if (resource != NULL && updated) {
        char *show_console = prefs_get_string(PREF_STATUSES_CONSOLE);
        char *show_chat_win = prefs_get_string(PREF_STATUSES_CHAT);
        Jid *jid = jid_create_from_bare_and_resource(barejid, resource);
        PContact contact = roster_get_contact(barejid);
        if (p_contact_subscription(contact) != NULL) {
            if (strcmp(p_contact_subscription(contact), "none") != 0) {

                // show in console if "all"
                if (g_strcmp0(show_console, "all") == 0) {
                    cons_show_contact_offline(contact, resource, status);

                // show in console of "online"
                } else if (g_strcmp0(show_console, "online") == 0) {
                    cons_show_contact_offline(contact, resource, status);
                }

                // show in chat win if "all"
                if (g_strcmp0(show_chat_win, "all") == 0) {
                    ui_chat_win_contact_offline(contact, resource, status);

                // show in char win if "online" and presence online
                } else if (g_strcmp0(show_chat_win, "online") == 0) {
                    ui_chat_win_contact_offline(contact, resource, status);
                }
            }
        }
        prefs_free_string(show_console);
        prefs_free_string(show_chat_win);
        jid_destroy(jid);
    }

    ui_roster();
}
Beispiel #12
0
void
sv_ev_room_occupant_offline(const char *const room, const char *const nick,
    const char *const show, const char *const status)
{
    muc_roster_remove(room, nick);

    char *muc_status_pref = prefs_get_string(PREF_STATUSES_MUC);
    ProfMucWin *mucwin = wins_get_muc(room);
    if (mucwin && (g_strcmp0(muc_status_pref, "none") != 0)) {
        mucwin_occupant_offline(mucwin, nick);
    }
    prefs_free_string(muc_status_pref);

    Jid *jidp = jid_create_from_bare_and_resource(room, nick);
    ProfPrivateWin *privwin = wins_get_private(jidp->fulljid);
    jid_destroy(jidp);
    if (privwin != NULL) {
        privwin_occupant_offline(privwin);
    }

    occupantswin_occupants(room);
    rosterwin_roster();
}
Beispiel #13
0
void
sv_ev_muc_occupant_online(const char *const room, const char *const nick, const char *const jid,
    const char *const role, const char *const affiliation, const char *const actor, const char *const reason,
    const char *const show, const char *const status)
{
    Occupant *occupant = muc_roster_item(room, nick);

    const char *old_role = NULL;
    const char *old_affiliation = NULL;
    if (occupant) {
        old_role = muc_occupant_role_str(occupant);
        old_affiliation = muc_occupant_affiliation_str(occupant);
    }

    gboolean updated = muc_roster_add(room, nick, jid, role, affiliation, show, status);

    // not yet finished joining room
    if (!muc_roster_complete(room)) {
        return;
    }

    // handle nickname change
    char *old_nick = muc_roster_nick_change_complete(room, nick);
    if (old_nick) {
        ProfMucWin *mucwin = wins_get_muc(room);
        if (mucwin) {
            mucwin_occupant_nick_change(mucwin, old_nick, nick);
        }
        wins_private_nick_change(mucwin->roomjid, old_nick, nick);
        free(old_nick);

        occupantswin_occupants(room);
        rosterwin_roster();
        return;
    }

    // joined room
    if (!occupant) {
        char *muc_status_pref = prefs_get_string(PREF_STATUSES_MUC);
        ProfMucWin *mucwin = wins_get_muc(room);
        if (mucwin && g_strcmp0(muc_status_pref, "none") != 0) {
            mucwin_occupant_online(mucwin, nick, role, affiliation, show, status);
        }
        prefs_free_string(muc_status_pref);

        Jid *jidp = jid_create_from_bare_and_resource(mucwin->roomjid, nick);
        ProfPrivateWin *privwin = wins_get_private(jidp->fulljid);
        jid_destroy(jidp);
        if (privwin) {
            privwin_occupant_online(privwin);
        }

        occupantswin_occupants(room);
        rosterwin_roster();
        return;
    }

    // presence updated
    if (updated) {
        char *muc_status_pref = prefs_get_string(PREF_STATUSES_MUC);
        ProfMucWin *mucwin = wins_get_muc(room);
        if (mucwin && (g_strcmp0(muc_status_pref, "all") == 0)) {
            mucwin_occupant_presence(mucwin, nick, show, status);
        }
        prefs_free_string(muc_status_pref);
        occupantswin_occupants(room);

    // presence unchanged, check for role/affiliation change
    } else {
        ProfMucWin *mucwin = wins_get_muc(room);
        if (mucwin && prefs_get_boolean(PREF_MUC_PRIVILEGES)) {
            // both changed
            if ((g_strcmp0(role, old_role) != 0) && (g_strcmp0(affiliation, old_affiliation) != 0)) {
                mucwin_occupant_role_and_affiliation_change(mucwin, nick, role, affiliation, actor, reason);

            // role changed
            } else if (g_strcmp0(role, old_role) != 0) {
                mucwin_occupant_role_change(mucwin, nick, role, actor, reason);

            // affiliation changed
            } else if (g_strcmp0(affiliation, old_affiliation) != 0) {
                mucwin_occupant_affiliation_change(mucwin, nick, affiliation, actor, reason);
            }
        }
        occupantswin_occupants(room);
    }

    rosterwin_roster();
}
Beispiel #14
0
void
win_show_occupant_info(ProfWin *window, const char *const room, Occupant *occupant)
{
    const char *presence_str = string_from_resource_presence(occupant->presence);
    const char *occupant_affiliation = muc_occupant_affiliation_str(occupant);
    const char *occupant_role = muc_occupant_role_str(occupant);

    theme_item_t presence_colour = theme_main_presence_attrs(presence_str);

    win_print(window, '!', 0, NULL, NO_EOL, presence_colour, "", occupant->nick);
    win_vprint(window, '!', 0, NULL, NO_DATE | NO_EOL, presence_colour, "", " is %s", presence_str);

    if (occupant->status) {
        win_vprint(window, '!', 0, NULL, NO_DATE | NO_EOL, presence_colour, "", ", \"%s\"", occupant->status);
    }

    win_newline(window);

    if (occupant->jid) {
        win_vprint(window, '!', 0, NULL, 0, 0, "", "  Jid: %s", occupant->jid);
    }

    win_vprint(window, '!', 0, NULL, 0, 0, "", "  Affiliation: %s", occupant_affiliation);
    win_vprint(window, '!', 0, NULL, 0, 0, "", "  Role: %s", occupant_role);

    Jid *jidp = jid_create_from_bare_and_resource(room, occupant->nick);
    EntityCapabilities *caps = caps_lookup(jidp->fulljid);
    jid_destroy(jidp);

    if (caps) {
        // show identity
        if (caps->identity) {
            DiscoIdentity *identity = caps->identity;
            win_print(window, '!', 0, NULL, NO_EOL, 0, "", "  Identity: ");
            if (identity->name) {
                win_print(window, '!', 0, NULL, NO_DATE | NO_EOL, 0, "", identity->name);
                if (identity->category || identity->type) {
                    win_print(window, '-', 0, NULL, NO_DATE | NO_EOL, 0, "", " ");
                }
            }
            if (identity->type) {
                win_print(window, '!', 0, NULL, NO_DATE | NO_EOL, 0, "", identity->type);
                if (identity->category) {
                    win_print(window, '!', 0, NULL, NO_DATE | NO_EOL, 0, "", " ");
                }
            }
            if (identity->category) {
                win_print(window, '!', 0, NULL, NO_DATE | NO_EOL, 0, "", identity->category);
            }
            win_newline(window);
        }

        if (caps->software_version) {
            SoftwareVersion *software_version = caps->software_version;
            if (software_version->software) {
                win_vprint(window, '!', 0, NULL, NO_EOL, 0, "", "  Software: %s", software_version->software);
            }
            if (software_version->software_version) {
                win_vprint(window, '!', 0, NULL, NO_DATE | NO_EOL, 0, "", ", %s", software_version->software_version);
            }
            if (software_version->software || software_version->software_version) {
                win_newline(window);
            }
            if (software_version->os) {
                win_vprint(window, '!', 0, NULL, NO_EOL, 0, "", "  OS: %s", software_version->os);
            }
            if (software_version->os_version) {
                win_vprint(window, '!', 0, NULL, NO_DATE | NO_EOL, 0, "", ", %s", software_version->os_version);
            }
            if (software_version->os || software_version->os_version) {
                win_newline(window);
            }
        }

        caps_destroy(caps);
    }

    win_print(window, '-', 0, NULL, 0, 0, "", "");
}
Beispiel #15
0
static int
_bookmark_handle_result(xmpp_conn_t * const conn,
    xmpp_stanza_t * const stanza, void * const userdata)
{
    xmpp_ctx_t *ctx = connection_get_ctx();
    char *id = (char *)userdata;
    xmpp_stanza_t *ptr;
    xmpp_stanza_t *nick;
    char *name;
    char *jid;
    char *autojoin;
    gboolean autojoin_val;
    Jid *my_jid;
    Bookmark *item;

    xmpp_timed_handler_delete(conn, _bookmark_handle_delete);
    g_free(id);

    name = xmpp_stanza_get_name(stanza);
    if (!name || strcmp(name, STANZA_NAME_IQ) != 0) {
        return 0;
    }

    ptr = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_QUERY);
    if (!ptr) {
        return 0;
    }
    ptr = xmpp_stanza_get_child_by_name(ptr, STANZA_NAME_STORAGE);
    if (!ptr) {
        return 0;
    }

    if (bookmark_ac == NULL) {
        bookmark_ac = autocomplete_new();
    }
    my_jid = jid_create(jabber_get_fulljid());

    ptr = xmpp_stanza_get_children(ptr);
    while (ptr) {
        name = xmpp_stanza_get_name(ptr);
        if (!name || strcmp(name, STANZA_NAME_CONFERENCE) != 0) {
            ptr = xmpp_stanza_get_next(ptr);
            continue;
        }
        jid = xmpp_stanza_get_attribute(ptr, STANZA_ATTR_JID);
        if (!jid) {
            ptr = xmpp_stanza_get_next(ptr);
            continue;
        }

        log_debug("Handle bookmark for %s", jid);

        name = NULL;
        nick = xmpp_stanza_get_child_by_name(ptr, "nick");
        if (nick) {
            char *tmp;
            tmp = xmpp_stanza_get_text(nick);
            if (tmp) {
                name = strdup(tmp);
                xmpp_free(ctx, tmp);
            }
        }

        autojoin = xmpp_stanza_get_attribute(ptr, "autojoin");
        if (autojoin && (strcmp(autojoin, "1") == 0 || strcmp(autojoin, "true") == 0)) {
            autojoin_val = TRUE;
        } else {
            autojoin_val = FALSE;
        }

        autocomplete_add(bookmark_ac, jid);
        item = malloc(sizeof(*item));
        item->jid = strdup(jid);
        item->nick = name;
        item->autojoin = autojoin_val;
        bookmark_list = g_list_append(bookmark_list, item);


        /* TODO: preference whether autojoin */
        if (autojoin_val) {
            if (autojoin_count < BOOKMARK_AUTOJOIN_MAX) {
                Jid *room_jid;

                ++autojoin_count;

                if (name == NULL) {
                    name = my_jid->localpart;
                }

                log_debug("Autojoin %s with nick=%s", jid, name);
                room_jid = jid_create_from_bare_and_resource(jid, name);
                if (!muc_room_is_active(room_jid)) {
                    presence_join_room(room_jid);
                    /* TODO: this should be removed after fixing #195 */
                    ui_room_join(room_jid);
                }
                jid_destroy(room_jid);
            } else {
                log_debug("Rejected autojoin %s (maximum has been reached)", jid);
            }
        }

        ptr = xmpp_stanza_get_next(ptr);
    }

    jid_destroy(my_jid);

    return 0;
}
Beispiel #16
0
static int
_bookmark_handle_result(xmpp_conn_t *const conn,
    xmpp_stanza_t *const stanza, void *const userdata)
{
    xmpp_ctx_t *ctx = connection_get_ctx();
    char *id = (char *)userdata;
    xmpp_stanza_t *ptr;
    xmpp_stanza_t *nick;
    xmpp_stanza_t *password_st;
    char *name;
    char *jid;
    char *autojoin;
    char *password;
    gboolean autojoin_val;
    Jid *my_jid;
    Bookmark *item;

    xmpp_timed_handler_delete(conn, _bookmark_handle_delete);
    g_free(id);

    name = xmpp_stanza_get_name(stanza);
    if (!name || strcmp(name, STANZA_NAME_IQ) != 0) {
        return 0;
    }

    ptr = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_QUERY);
    if (!ptr) {
        return 0;
    }
    ptr = xmpp_stanza_get_child_by_name(ptr, STANZA_NAME_STORAGE);
    if (!ptr) {
        return 0;
    }

    if (bookmark_ac == NULL) {
        bookmark_ac = autocomplete_new();
    }
    my_jid = jid_create(jabber_get_fulljid());

    ptr = xmpp_stanza_get_children(ptr);
    while (ptr) {
        name = xmpp_stanza_get_name(ptr);
        if (!name || strcmp(name, STANZA_NAME_CONFERENCE) != 0) {
            ptr = xmpp_stanza_get_next(ptr);
            continue;
        }
        jid = xmpp_stanza_get_attribute(ptr, STANZA_ATTR_JID);
        if (!jid) {
            ptr = xmpp_stanza_get_next(ptr);
            continue;
        }

        log_debug("Handle bookmark for %s", jid);

        name = NULL;
        nick = xmpp_stanza_get_child_by_name(ptr, "nick");
        if (nick) {
            char *tmp;
            tmp = xmpp_stanza_get_text(nick);
            if (tmp) {
                name = strdup(tmp);
                xmpp_free(ctx, tmp);
            }
        }

        password = NULL;
        password_st = xmpp_stanza_get_child_by_name(ptr, "password");
        if (password_st) {
            char *tmp;
            tmp = xmpp_stanza_get_text(password_st);
            if (tmp) {
                password = strdup(tmp);
                xmpp_free(ctx, tmp);
            }
        }

        autojoin = xmpp_stanza_get_attribute(ptr, "autojoin");
        if (autojoin && (strcmp(autojoin, "1") == 0 || strcmp(autojoin, "true") == 0)) {
            autojoin_val = TRUE;
        } else {
            autojoin_val = FALSE;
        }

        autocomplete_add(bookmark_ac, jid);
        item = malloc(sizeof(*item));
        item->jid = strdup(jid);
        item->nick = name;
        item->password = password;
        item->autojoin = autojoin_val;
        bookmark_list = g_list_append(bookmark_list, item);

        if (autojoin_val) {
            Jid *room_jid;

            char *account_name = jabber_get_account_name();
            ProfAccount *account = accounts_get_account(account_name);
            if (name == NULL) {
                name = account->muc_nick;
            }

            log_debug("Autojoin %s with nick=%s", jid, name);
            room_jid = jid_create_from_bare_and_resource(jid, name);
            if (!muc_active(room_jid->barejid)) {
                presence_join_room(jid, name, password);
                muc_join(jid, name, password, TRUE);
            }
            jid_destroy(room_jid);
            account_free(account);
        }

        ptr = xmpp_stanza_get_next(ptr);
    }

    jid_destroy(my_jid);

    return 0;
}
Beispiel #17
0
void
win_show_info(ProfWin *window, PContact contact)
{
    const char *barejid = p_contact_barejid(contact);
    const char *name = p_contact_name(contact);
    const char *presence = p_contact_presence(contact);
    const char *sub = p_contact_subscription(contact);
    GDateTime *last_activity = p_contact_last_activity(contact);

    theme_item_t presence_colour = theme_main_presence_attrs(presence);

    win_print(window, '-', 0, NULL, 0, 0, "", "");
    win_print(window, '-', 0, NULL, NO_EOL, presence_colour, "", barejid);
    if (name) {
        win_vprint(window, '-', 0, NULL, NO_DATE | NO_EOL, presence_colour, "", " (%s)", name);
    }
    win_print(window, '-', 0, NULL, NO_DATE, 0, "", ":");

    if (sub) {
        win_vprint(window, '-', 0, NULL, 0, 0, "", "Subscription: %s", sub);
    }

    if (last_activity) {
        GDateTime *now = g_date_time_new_now_local();
        GTimeSpan span = g_date_time_difference(now, last_activity);

        int hours = span / G_TIME_SPAN_HOUR;
        span = span - hours * G_TIME_SPAN_HOUR;
        int minutes = span / G_TIME_SPAN_MINUTE;
        span = span - minutes * G_TIME_SPAN_MINUTE;
        int seconds = span / G_TIME_SPAN_SECOND;

        if (hours > 0) {
          win_vprint(window, '-', 0, NULL, 0, 0, "", "Last activity: %dh%dm%ds", hours, minutes, seconds);
        }
        else {
          win_vprint(window, '-', 0, NULL, 0, 0, "", "Last activity: %dm%ds", minutes, seconds);
        }

        g_date_time_unref(now);
    }

    GList *resources = p_contact_get_available_resources(contact);
    GList *ordered_resources = NULL;
    if (resources) {
        win_print(window, '-', 0, NULL, 0, 0, "", "Resources:");

        // sort in order of availability
        GList *curr = resources;
        while (curr) {
            Resource *resource = curr->data;
            ordered_resources = g_list_insert_sorted(ordered_resources,
                resource, (GCompareFunc)resource_compare_availability);
            curr = g_list_next(curr);
        }
    }
    g_list_free(resources);

    GList *curr = ordered_resources;
    while (curr) {
        Resource *resource = curr->data;
        const char *resource_presence = string_from_resource_presence(resource->presence);
        theme_item_t presence_colour = theme_main_presence_attrs(resource_presence);
        win_vprint(window, '-', 0, NULL, NO_EOL, presence_colour, "", "  %s (%d), %s", resource->name, resource->priority, resource_presence);
        if (resource->status) {
            win_vprint(window, '-', 0, NULL, NO_DATE | NO_EOL, presence_colour, "", ", \"%s\"", resource->status);
        }
        win_newline(window);

        Jid *jidp = jid_create_from_bare_and_resource(barejid, resource->name);
        EntityCapabilities *caps = caps_lookup(jidp->fulljid);
        jid_destroy(jidp);

        if (caps) {
            // show identity
            if (caps->identity) {
                DiscoIdentity *identity = caps->identity;
                win_print(window, '-', 0, NULL, NO_EOL, 0, "", "    Identity: ");
                if (identity->name) {
                    win_print(window, '-', 0, NULL, NO_DATE | NO_EOL, 0, "", identity->name);
                    if (identity->category || identity->type) {
                        win_print(window, '-', 0, NULL, NO_DATE | NO_EOL, 0, "", " ");
                    }
                }
                if (identity->type) {
                    win_print(window, '-', 0, NULL, NO_DATE | NO_EOL, 0, "", identity->type);
                    if (identity->category) {
                        win_print(window, '-', 0, NULL, NO_DATE | NO_EOL, 0, "", " ");
                    }
                }
                if (identity->category) {
                    win_print(window, '-', 0, NULL, NO_DATE | NO_EOL, 0, "", identity->category);
                }
                win_newline(window);
            }

            if (caps->software_version) {
                SoftwareVersion *software_version = caps->software_version;
                if (software_version->software) {
                    win_vprint(window, '-', 0, NULL, NO_EOL, 0, "", "    Software: %s", software_version->software);
                }
                if (software_version->software_version) {
                    win_vprint(window, '-', 0, NULL, NO_DATE | NO_EOL, 0, "", ", %s", software_version->software_version);
                }
                if (software_version->software || software_version->software_version) {
                    win_newline(window);
                }
                if (software_version->os) {
                    win_vprint(window, '-', 0, NULL, NO_EOL, 0, "", "    OS: %s", software_version->os);
                }
                if (software_version->os_version) {
                    win_vprint(window, '-', 0, NULL, NO_DATE | NO_EOL, 0, "", ", %s", software_version->os_version);
                }
                if (software_version->os || software_version->os_version) {
                    win_newline(window);
                }
            }

            caps_destroy(caps);
        }

        curr = g_list_next(curr);
    }
    g_list_free(ordered_resources);
}