about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--src/command/command.c52
-rw-r--r--src/command/commands.c155
-rw-r--r--src/config/accounts.c19
-rw-r--r--src/config/accounts.h2
-rw-r--r--src/config/preferences.c37
-rw-r--r--src/config/preferences.h3
-rw-r--r--src/profanity.c150
-rw-r--r--src/ui/console.c39
-rw-r--r--src/ui/core.c30
-rw-r--r--src/ui/ui.h3
-rw-r--r--src/xmpp/presence.c4
-rw-r--r--tests/unittests/config/stub_accounts.c6
-rw-r--r--tests/unittests/ui/stub_ui.c2
13 files changed, 321 insertions, 181 deletions
diff --git a/src/command/command.c b/src/command/command.c
index 891cf4ea..d93106e9 100644
--- a/src/command/command.c
+++ b/src/command/command.c
@@ -1374,28 +1374,33 @@ static struct cmd_t command_defs[] =
     },
 
     { "/autoaway",
-        cmd_autoaway, parse_args_with_freetext, 2, 2, &cons_autoaway_setting,
+        cmd_autoaway, parse_args_with_freetext, 2, 3, &cons_autoaway_setting,
         CMD_TAGS(
             CMD_TAG_PRESENCE)
         CMD_SYN(
             "/autoaway mode idle|away|off",
-            "/autoaway time <minutes>",
-            "/autoaway message <message>|off",
+            "/autoaway time away|xa <minutes>",
+            "/autoaway message away|xa <message>|off",
             "/autoaway check on|off")
         CMD_DESC(
             "Manage autoway settings for idle time.")
         CMD_ARGS(
-            { "mode idle",         "Sends idle time, status remains online." },
-            { "mode away",         "Sends an away presence." },
-            { "mode off",          "Disabled (default)." },
-            { "time <minutes>",    "Number of minutes before the presence change is sent, default: 15." },
-            { "message <message>", "Optional message to send with the presence change, default: off (disabled)." },
-            { "message off",       "Send no message with autoaway presence." },
-            { "check on|off",      "When enabled, checks for activity and sends online presence, default: on." })
+            { "mode idle",              "Sends idle time, status remains online." },
+            { "mode away",              "Sends away and xa presence as well as idle time." },
+            { "mode off",               "Disabled (default)." },
+            { "time away <minutes>",    "Number of minutes before the away presence is sent, default: 15." },
+            { "time xa <minutes>",      "Number of minutes before the xa presence is sent, default: 0 (disabled)." },
+            { "message away <message>", "Optional message to send with the away presence, default: off (disabled)." },
+            { "message xa <message>",   "Optional message to send with the xa presence, default: off (disabled)." },
+            { "message away off",       "Send no message with away presence." },
+            { "message xa off",         "Send no message with xa presence." },
+            { "check on|off",           "When enabled, checks for activity and sends online presence, default: on." })
         CMD_EXAMPLES(
-            "/autoaway mode idle",
-            "/autoaway time 30",
-            "/autoaway message I'm not really doing much",
+            "/autoaway mode away",
+            "/autoaway time away 30",
+            "/autoaway message away Away from computer for a while",
+            "/autoaway time xa 120",
+            "/autoaway message xa Away from computer for a very long time",
             "/autoaway check off")
     },
 
@@ -1658,6 +1663,7 @@ static Autocomplete sub_ac;
 static Autocomplete log_ac;
 static Autocomplete autoaway_ac;
 static Autocomplete autoaway_mode_ac;
+static Autocomplete autoaway_presence_ac;
 static Autocomplete autoconnect_ac;
 static Autocomplete titlebar_ac;
 static Autocomplete theme_ac;
@@ -1823,6 +1829,10 @@ cmd_init(void)
     autocomplete_add(autoaway_mode_ac, "idle");
     autocomplete_add(autoaway_mode_ac, "off");
 
+    autoaway_presence_ac = autocomplete_new();
+    autocomplete_add(autoaway_presence_ac, "away");
+    autocomplete_add(autoaway_presence_ac, "xa");
+
     autoconnect_ac = autocomplete_new();
     autocomplete_add(autoconnect_ac, "set");
     autocomplete_add(autoconnect_ac, "off");
@@ -2132,6 +2142,7 @@ cmd_uninit(void)
     autocomplete_free(prefs_ac);
     autocomplete_free(autoaway_ac);
     autocomplete_free(autoaway_mode_ac);
+    autocomplete_free(autoaway_presence_ac);
     autocomplete_free(autoconnect_ac);
     autocomplete_free(theme_ac);
     autocomplete_free(theme_load_ac);
@@ -2311,6 +2322,7 @@ cmd_reset_autocomplete(ProfWin *window)
     autocomplete_reset(commands_ac);
     autocomplete_reset(autoaway_ac);
     autocomplete_reset(autoaway_mode_ac);
+    autocomplete_reset(autoaway_presence_ac);
     autocomplete_reset(autoconnect_ac);
     autocomplete_reset(theme_ac);
     if (theme_load_ac) {
@@ -2925,8 +2937,18 @@ _autoaway_autocomplete(ProfWin *window, const char * const input)
     if (result) {
         return result;
     }
-    result = autocomplete_param_with_func(input, "/autoaway check",
-        prefs_autocomplete_boolean_choice);
+
+    result = autocomplete_param_with_ac(input, "/autoaway time", autoaway_presence_ac, TRUE);
+    if (result) {
+        return result;
+    }
+
+    result = autocomplete_param_with_ac(input, "/autoaway message", autoaway_presence_ac, TRUE);
+    if (result) {
+        return result;
+    }
+
+    result = autocomplete_param_with_func(input, "/autoaway check", prefs_autocomplete_boolean_choice);
     if (result) {
         return result;
     }
diff --git a/src/command/commands.c b/src/command/commands.c
index 3f695cc1..e6d1e277 100644
--- a/src/command/commands.c
+++ b/src/command/commands.c
@@ -3973,56 +3973,101 @@ cmd_ping(ProfWin *window, const char * const command, gchar **args)
 gboolean
 cmd_autoaway(ProfWin *window, const char * const command, gchar **args)
 {
-    char *setting = args[0];
-    char *value = args[1];
-
-    if ((strcmp(setting, "mode") != 0) && (strcmp(setting, "time") != 0) &&
-            (strcmp(setting, "message") != 0) && (strcmp(setting, "check") != 0)) {
+    if ((strcmp(args[0], "mode") != 0) && (strcmp(args[0], "time") != 0) &&
+            (strcmp(args[0], "message") != 0) && (strcmp(args[0], "check") != 0)) {
         cons_show("Setting must be one of 'mode', 'time', 'message' or 'check'");
         return TRUE;
     }
 
-    if (strcmp(setting, "mode") == 0) {
-        if ((strcmp(value, "idle") != 0) && (strcmp(value, "away") != 0) &&
-                (strcmp(value, "off") != 0)) {
+    if (strcmp(args[0], "mode") == 0) {
+        if ((strcmp(args[1], "idle") != 0) && (strcmp(args[1], "away") != 0) &&
+                (strcmp(args[1], "off") != 0)) {
             cons_show("Mode must be one of 'idle', 'away' or 'off'");
         } else {
-            prefs_set_string(PREF_AUTOAWAY_MODE, value);
-            cons_show("Auto away mode set to: %s.", value);
+            prefs_set_string(PREF_AUTOAWAY_MODE, args[1]);
+            cons_show("Auto away mode set to: %s.", args[1]);
         }
 
         return TRUE;
     }
 
-    if (strcmp(setting, "time") == 0) {
-        int minutesval = 0;
-        char *err_msg = NULL;
-        gboolean res = strtoi_range(value, &minutesval, 1, INT_MAX, &err_msg);
-        if (res) {
-            prefs_set_autoaway_time(minutesval);
-            cons_show("Auto away time set to: %d minutes.", minutesval);
+    if (strcmp(args[0], "time") == 0) {
+        if (g_strcmp0(args[1], "away") == 0) {
+            int minutesval = 0;
+            char *err_msg = NULL;
+            gboolean res = strtoi_range(args[2], &minutesval, 1, INT_MAX, &err_msg);
+            if (res) {
+                prefs_set_autoaway_time(minutesval);
+                if (minutesval == 1) {
+                    cons_show("Auto away time set to: 1 minute.");
+                } else {
+                    cons_show("Auto away time set to: %d minutes.", minutesval);
+                }
+            } else {
+                cons_show(err_msg);
+                free(err_msg);
+            }
+
+            return TRUE;
+        } else if (g_strcmp0(args[1], "xa") == 0) {
+            int minutesval = 0;
+            char *err_msg = NULL;
+            gboolean res = strtoi_range(args[2], &minutesval, 0, INT_MAX, &err_msg);
+            if (res) {
+                int away_time = prefs_get_autoaway_time();
+                if (minutesval != 0 && minutesval <= away_time) {
+                    cons_show("Auto xa time must be larger than auto away time.");
+                } else {
+                    prefs_set_autoxa_time(minutesval);
+                    if (minutesval == 0) {
+                        cons_show("Auto xa time disabled.");
+                    } else if (minutesval == 1) {
+                        cons_show("Auto xa time set to: 1 minute.");
+                    } else {
+                        cons_show("Auto xa time set to: %d minutes.", minutesval);
+                    }
+                }
+            } else {
+                cons_show(err_msg);
+                free(err_msg);
+            }
+
+            return TRUE;
         } else {
-            cons_show(err_msg);
-            free(err_msg);
+            cons_bad_cmd_usage(command);
+            return TRUE;
         }
-
-        return TRUE;
     }
 
-    if (strcmp(setting, "message") == 0) {
-        if (strcmp(value, "off") == 0) {
-            prefs_set_string(PREF_AUTOAWAY_MESSAGE, NULL);
-            cons_show("Auto away message cleared.");
+    if (strcmp(args[0], "message") == 0) {
+        if (g_strcmp0(args[1], "away") == 0) {
+            if (strcmp(args[2], "off") == 0) {
+                prefs_set_string(PREF_AUTOAWAY_MESSAGE, NULL);
+                cons_show("Auto away message cleared.");
+            } else {
+                prefs_set_string(PREF_AUTOAWAY_MESSAGE, args[2]);
+                cons_show("Auto away message set to: \"%s\".", args[2]);
+            }
+
+            return TRUE;
+        } else if (g_strcmp0(args[1], "xa") == 0) {
+            if (strcmp(args[2], "off") == 0) {
+                prefs_set_string(PREF_AUTOXA_MESSAGE, NULL);
+                cons_show("Auto xa message cleared.");
+            } else {
+                prefs_set_string(PREF_AUTOXA_MESSAGE, args[2]);
+                cons_show("Auto xa message set to: \"%s\".", args[2]);
+            }
+
+            return TRUE;
         } else {
-            prefs_set_string(PREF_AUTOAWAY_MESSAGE, value);
-            cons_show("Auto away message set to: \"%s\".", value);
+            cons_bad_cmd_usage(command);
+            return TRUE;
         }
-
-        return TRUE;
     }
 
-    if (strcmp(setting, "check") == 0) {
-        return _cmd_set_boolean_preference(value, command, "Online check", PREF_AUTOAWAY_CHECK);
+    if (strcmp(args[0], "check") == 0) {
+        return _cmd_set_boolean_preference(args[1], command, "Online check", PREF_AUTOAWAY_CHECK);
     }
 
     return TRUE;
@@ -4879,51 +4924,3 @@ _cmd_set_boolean_preference(gchar *arg, const char * const command,
 
     return TRUE;
 }
-
-//static void
-//_cmd_show_filtered_help(char *heading, gchar *cmd_filter[], int filter_size)
-//{
-//    ProfWin *console = wins_get_console();
-//    cons_show("");
-//    win_print(console, '-', NULL, 0, THEME_WHITE_BOLD, "", heading);
-//
-//    GList *ordered_commands = NULL;
-//    int i;
-//    for (i = 0; i < filter_size; i++) {
-//        Command *pcmd = g_hash_table_lookup(commands, cmd_filter[i]);
-//        ordered_commands = g_list_insert_sorted(ordered_commands, pcmd->cmd, (GCompareFunc)g_strcmp0);
-//    }
-//
-//    int maxlen = 0;
-//    GList *curr = ordered_commands;
-//    while (curr) {
-//        gchar *cmd = curr->data;
-//        int len = strlen(cmd);
-//        if (len > maxlen) maxlen = len;
-//        curr = g_list_next(curr);
-//    }
-//
-//    GString *cmds = g_string_new("");
-//    curr = ordered_commands;
-//    int count = 0;
-//    while (curr) {
-//        gchar *cmd = curr->data;
-//        if (count == 5) {
-//            cons_show(cmds->str);
-//            g_string_free(cmds, TRUE);
-//            cmds = g_string_new("");
-//            count = 0;
-//        }
-//        g_string_append_printf(cmds, "%-*s", maxlen + 1, cmd);
-//        curr = g_list_next(curr);
-//        count++;
-//    }
-//    cons_show(cmds->str);
-//    g_string_free(cmds, TRUE);
-//    g_list_free(ordered_commands);
-//    g_list_free(curr);
-//
-//    cons_show("");
-//    cons_show("Use /help [command] without the leading slash, for help on a specific command");
-//    cons_show("");
-//}
diff --git a/src/config/accounts.c b/src/config/accounts.c
index d43cc3a3..9b838f59 100644
--- a/src/config/accounts.c
+++ b/src/config/accounts.c
@@ -762,6 +762,19 @@ accounts_set_last_presence(const char * const account_name, const char * const v
 }
 
 void
+accounts_set_last_status(const char * const account_name, const char * const value)
+{
+    if (accounts_account_exists(account_name)) {
+        if (value) {
+            g_key_file_set_string(accounts, account_name, "presence.laststatus", value);
+        } else {
+            g_key_file_remove_key(accounts, account_name, "presence.laststatus", NULL);
+        }
+        _save_accounts();
+    }
+}
+
+void
 accounts_set_last_activity(const char * const account_name)
 {
     if (accounts_account_exists(account_name)) {
@@ -815,6 +828,12 @@ accounts_get_last_presence(const char * const account_name)
     return result;
 }
 
+char *
+accounts_get_last_status(const char * const account_name)
+{
+    return g_key_file_get_string(accounts, account_name, "presence.laststatus", NULL);
+}
+
 resource_presence_t
 accounts_get_login_presence(const char * const account_name)
 {
diff --git a/src/config/accounts.h b/src/config/accounts.h
index 65106365..14bdbf97 100644
--- a/src/config/accounts.h
+++ b/src/config/accounts.h
@@ -66,9 +66,11 @@ void accounts_set_muc_service(const char * const account_name, const char * cons
 void accounts_set_muc_nick(const char * const account_name, const char * const value);
 void accounts_set_otr_policy(const char * const account_name, const char * const value);
 void accounts_set_last_presence(const char * const account_name, const char * const value);
+void accounts_set_last_status(const char * const account_name, const char * const value);
 void accounts_set_last_activity(const char * const account_name);
 void accounts_set_login_presence(const char * const account_name, const char * const value);
 resource_presence_t accounts_get_login_presence(const char * const account_name);
+char * accounts_get_last_status(const char * const account_name);
 resource_presence_t accounts_get_last_presence(const char * const account_name);
 void accounts_set_priority_online(const char * const account_name, const gint value);
 void accounts_set_priority_chat(const char * const account_name, const gint value);
diff --git a/src/config/preferences.c b/src/config/preferences.c
index 7a198ac7..e594e9f6 100644
--- a/src/config/preferences.c
+++ b/src/config/preferences.c
@@ -93,6 +93,21 @@ prefs_load(void)
         g_error_free(err);
     }
 
+    // move pre 0.4.8 autoaway.time to autoaway.awaytime
+    if (g_key_file_has_key(prefs, PREF_GROUP_PRESENCE, "autoaway.time", NULL)) {
+        gint time = g_key_file_get_integer(prefs, PREF_GROUP_PRESENCE, "autoaway.time", NULL);
+        g_key_file_set_integer(prefs, PREF_GROUP_PRESENCE, "autoaway.awaytime", time);
+        g_key_file_remove_key(prefs, PREF_GROUP_PRESENCE, "autoaway.time", NULL);
+    }
+
+    // move pre 0.4.8 autoaway.message to autoaway.awaymessage
+    if (g_key_file_has_key(prefs, PREF_GROUP_PRESENCE, "autoaway.message", NULL)) {
+        char *message = g_key_file_get_string(prefs, PREF_GROUP_PRESENCE, "autoaway.message", NULL);
+        g_key_file_set_string(prefs, PREF_GROUP_PRESENCE, "autoaway.awaymessage", message);
+        g_key_file_remove_key(prefs, PREF_GROUP_PRESENCE, "autoaway.message", NULL);
+        prefs_free_string(message);
+    }
+
     // move pre 0.4.7 otr.warn to enc.warn
     err = NULL;
     gboolean otr_warn = g_key_file_get_boolean(prefs, PREF_GROUP_UI, "otr.warn", &err);
@@ -322,7 +337,7 @@ prefs_set_autoping(gint value)
 gint
 prefs_get_autoaway_time(void)
 {
-    gint result = g_key_file_get_integer(prefs, PREF_GROUP_PRESENCE, "autoaway.time", NULL);
+    gint result = g_key_file_get_integer(prefs, PREF_GROUP_PRESENCE, "autoaway.awaytime", NULL);
 
     if (result == 0) {
         return 15;
@@ -331,10 +346,23 @@ prefs_get_autoaway_time(void)
     }
 }
 
+gint
+prefs_get_autoxa_time(void)
+{
+    return g_key_file_get_integer(prefs, PREF_GROUP_PRESENCE, "autoaway.xatime", NULL);
+}
+
 void
 prefs_set_autoaway_time(gint value)
 {
-    g_key_file_set_integer(prefs, PREF_GROUP_PRESENCE, "autoaway.time", value);
+    g_key_file_set_integer(prefs, PREF_GROUP_PRESENCE, "autoaway.awaytime", value);
+    _save_prefs();
+}
+
+void
+prefs_set_autoxa_time(gint value)
+{
+    g_key_file_set_integer(prefs, PREF_GROUP_PRESENCE, "autoaway.xatime", value);
     _save_prefs();
 }
 
@@ -606,6 +634,7 @@ _get_group(preference_t pref)
         case PREF_AUTOAWAY_CHECK:
         case PREF_AUTOAWAY_MODE:
         case PREF_AUTOAWAY_MESSAGE:
+        case PREF_AUTOXA_MESSAGE:
             return PREF_GROUP_PRESENCE;
         case PREF_CONNECT_ACCOUNT:
         case PREF_DEFAULT_ACCOUNT:
@@ -702,7 +731,9 @@ _get_key(preference_t pref)
         case PREF_AUTOAWAY_MODE:
             return "autoaway.mode";
         case PREF_AUTOAWAY_MESSAGE:
-            return "autoaway.message";
+            return "autoaway.awaymessage";
+        case PREF_AUTOXA_MESSAGE:
+            return "autoaway.xamessage";
         case PREF_CONNECT_ACCOUNT:
             return "account";
         case PREF_DEFAULT_ACCOUNT:
diff --git a/src/config/preferences.h b/src/config/preferences.h
index 89b3fe24..1879c7d3 100644
--- a/src/config/preferences.h
+++ b/src/config/preferences.h
@@ -93,6 +93,7 @@ typedef enum {
     PREF_AUTOAWAY_CHECK,
     PREF_AUTOAWAY_MODE,
     PREF_AUTOAWAY_MESSAGE,
+    PREF_AUTOXA_MESSAGE,
     PREF_CONNECT_ACCOUNT,
     PREF_DEFAULT_ACCOUNT,
     PREF_LOG_ROTATE,
@@ -143,6 +144,8 @@ gint prefs_get_roster_size(void);
 
 gint prefs_get_autoaway_time(void);
 void prefs_set_autoaway_time(gint value);
+gint prefs_get_autoxa_time(void);
+void prefs_set_autoxa_time(gint value);
 
 char prefs_get_otr_char(void);
 void prefs_set_otr_char(char ch);
diff --git a/src/profanity.c b/src/profanity.c
index 4fcf045f..bcbd8c2e 100644
--- a/src/profanity.c
+++ b/src/profanity.c
@@ -77,8 +77,16 @@ static void _shutdown(void);
 static void _create_directories(void);
 static void _connect_default(const char * const account);
 
-static gboolean idle = FALSE;
-resource_presence_t autoaway_pre_presence;
+typedef enum {
+    ACTIVITY_ST_ACTIVE,
+    ACTIVITY_ST_IDLE,
+    ACTIVITY_ST_AWAY,
+    ACTIVITY_ST_XA,
+} activity_state_t;
+
+activity_state_t activity_state;
+resource_presence_t saved_presence;
+char *saved_status;
 
 static gboolean cont = TRUE;
 
@@ -91,6 +99,9 @@ prof_run(const int disable_tls, char *log_level, char *account_name)
 
     log_info("Starting main event loop");
 
+    activity_state = ACTIVITY_ST_ACTIVE;
+    saved_status = NULL;
+
     char *line = NULL;
     while(cont) {
         log_stderr_handler();
@@ -172,52 +183,108 @@ _check_autoaway()
         return;
     }
 
-    gint autoaway_time = prefs_get_autoaway_time() * 60000;
+    char *mode = prefs_get_string(PREF_AUTOAWAY_MODE);
+    gboolean check = prefs_get_boolean(PREF_AUTOAWAY_CHECK);
+    gint away_time = prefs_get_autoaway_time();
+    gint xa_time = prefs_get_autoxa_time();
+    int away_time_ms = away_time * 60000;
+    int xa_time_ms = xa_time * 60000;
+
+    char *account = jabber_get_account_name();
+    resource_presence_t curr_presence = accounts_get_last_presence(account);
+    char *curr_status = accounts_get_last_status(account);
+
     unsigned long idle_ms = ui_get_idle_time();
-    char *pref_autoaway_mode = prefs_get_string(PREF_AUTOAWAY_MODE);
-
-    if (idle) {
-        if (idle_ms < autoaway_time) {
-            idle = FALSE;
-
-            // handle check
-            if (prefs_get_boolean(PREF_AUTOAWAY_CHECK)) {
-                if (strcmp(pref_autoaway_mode, "away") == 0) {
-                    cl_ev_presence_send(autoaway_pre_presence, NULL, 0);
-                    ui_end_auto_away(autoaway_pre_presence);
-                } else if (strcmp(pref_autoaway_mode, "idle") == 0) {
-                    cl_ev_presence_send(autoaway_pre_presence, NULL, 0);
-                    contact_presence_t contact_presence = contact_presence_from_resource_presence(autoaway_pre_presence);
-                    ui_titlebar_presence(contact_presence);
+
+    switch (activity_state) {
+    case ACTIVITY_ST_ACTIVE:
+        if (idle_ms >= away_time_ms) {
+            if (g_strcmp0(mode, "away") == 0) {
+                if ((curr_presence == RESOURCE_ONLINE) || (curr_presence == RESOURCE_CHAT) || (curr_presence == RESOURCE_DND)) {
+                    activity_state = ACTIVITY_ST_AWAY;
+
+                    // save current presence
+                    saved_presence = curr_presence;
+                    if (saved_status) {
+                        free(saved_status);
+                    }
+                    saved_status = curr_status;
+
+                    // send away presence with last activity
+                    char *message = prefs_get_string(PREF_AUTOAWAY_MESSAGE);
+                    cl_ev_presence_send(RESOURCE_AWAY, message, idle_ms / 1000);
+
+                    int pri = accounts_get_priority_for_presence_type(account, RESOURCE_AWAY);
+                    if (message) {
+                        cons_show("Idle for %d minutes, status set to away (priority %d), \"%s\".", away_time, pri, message);
+                    } else {
+                        cons_show("Idle for %d minutes, status set to away (priority %d).", away_time, pri);
+                    }
+                    prefs_free_string(message);
+
+                    ui_titlebar_presence(CONTACT_AWAY);
                 }
+            } else if (g_strcmp0(mode, "idle") == 0) {
+                activity_state = ACTIVITY_ST_IDLE;
+
+                // send current presence with last activity
+                cl_ev_presence_send(curr_presence, curr_status, idle_ms / 1000);
             }
         }
-    } else {
-        char *account_name = jabber_get_account_name();
-        resource_presence_t current_presence = accounts_get_last_presence(account_name);
-        autoaway_pre_presence = current_presence;
-        if ((current_presence == RESOURCE_ONLINE)
-                || (current_presence == RESOURCE_CHAT)
-                || (current_presence == RESOURCE_DND)) {
-
-            if (idle_ms >= autoaway_time) {
-                idle = TRUE;
-                char *pref_autoaway_message = prefs_get_string(PREF_AUTOAWAY_MESSAGE);
-
-                // handle away mode
-                if (strcmp(pref_autoaway_mode, "away") == 0) {
-                    cl_ev_presence_send(RESOURCE_AWAY, pref_autoaway_message, idle_ms / 1000);
-                    ui_auto_away();
-
-                // handle idle mode
-                } else if (strcmp(pref_autoaway_mode, "idle") == 0) {
-                    cl_ev_presence_send(current_presence, pref_autoaway_message, idle_ms / 1000);
-                }
+        break;
+    case ACTIVITY_ST_IDLE:
+        if (check && (idle_ms < away_time_ms)) {
+            activity_state = ACTIVITY_ST_ACTIVE;
+
+            cons_show("No longer idle.");
+
+            // send current presence without last activity
+            cl_ev_presence_send(curr_presence, curr_status, 0);
+        }
+        break;
+    case ACTIVITY_ST_AWAY:
+        if (xa_time_ms > 0 && (idle_ms >= xa_time_ms)) {
+            activity_state = ACTIVITY_ST_XA;
+
+            // send extended away presence with last activity
+            char *message = prefs_get_string(PREF_AUTOXA_MESSAGE);
+            cl_ev_presence_send(RESOURCE_XA, message, idle_ms / 1000);
+
+            int pri = accounts_get_priority_for_presence_type(account, RESOURCE_XA);
+            if (message) {
+                cons_show("Idle for %d minutes, status set to xa (priority %d), \"%s\".", xa_time, pri, message);
+            } else {
+                cons_show("Idle for %d minutes, status set to xa (priority %d).", xa_time, pri);
             }
+            prefs_free_string(message);
+
+            ui_titlebar_presence(CONTACT_XA);
+        } else if (check && (idle_ms < away_time_ms)) {
+            activity_state = ACTIVITY_ST_ACTIVE;
+
+            cons_show("No longer idle.");
+
+            // send saved presence without last activity
+            cl_ev_presence_send(saved_presence, saved_status, 0);
+            contact_presence_t contact_pres = contact_presence_from_resource_presence(saved_presence);
+            ui_titlebar_presence(contact_pres);
         }
+        break;
+    case ACTIVITY_ST_XA:
+        if (check && (idle_ms < away_time_ms)) {
+            activity_state = ACTIVITY_ST_ACTIVE;
+
+            cons_show("No longer idle.");
+
+            // send saved presence without last activity
+            cl_ev_presence_send(saved_presence, saved_status, 0);
+            contact_presence_t contact_pres = contact_presence_from_resource_presence(saved_presence);
+            ui_titlebar_presence(contact_pres);
+        }
+        break;
     }
 
-    prefs_free_string(pref_autoaway_mode);
+    prefs_free_string(mode);
 }
 
 static void
@@ -297,6 +364,9 @@ _shutdown(void)
     log_stderr_close();
     log_close();
     prefs_close();
+    if (saved_status) {
+        free(saved_status);
+    }
 }
 
 static void
diff --git a/src/ui/console.c b/src/ui/console.c
index 80bfe9f5..666f7015 100644
--- a/src/ui/console.c
+++ b/src/ui/console.c
@@ -1354,27 +1354,48 @@ cons_autoaway_setting(void)
 {
     char *pref_autoaway_mode = prefs_get_string(PREF_AUTOAWAY_MODE);
     if (strcmp(pref_autoaway_mode, "off") == 0) {
-        cons_show("Autoaway (/autoaway mode)            : OFF");
+        cons_show("Autoaway (/autoaway mode)                 : OFF");
     } else {
-        cons_show("Autoaway (/autoaway mode)            : %s", pref_autoaway_mode);
+        cons_show("Autoaway (/autoaway mode)                 : %s", pref_autoaway_mode);
     }
     prefs_free_string(pref_autoaway_mode);
 
-    cons_show("Autoaway minutes (/autoaway time)    : %d minutes", prefs_get_autoaway_time());
+    int away_time = prefs_get_autoaway_time();
+    if (away_time == 1) {
+        cons_show("Autoaway away minutes (/autoaway time)    : 1 minute");
+    } else {
+        cons_show("Autoaway away minutes (/autoaway time)    : %d minutes", away_time);
+    }
+
+    int xa_time = prefs_get_autoxa_time();
+    if (xa_time == 0) {
+        cons_show("Autoaway xa minutes (/autoaway time)      : OFF");
+    } else if (xa_time == 1) {
+        cons_show("Autoaway xa minutes (/autoaway time)      : 1 minute");
+    } else {
+        cons_show("Autoaway xa minutes (/autoaway time)      : %d minutes", xa_time);
+    }
 
     char *pref_autoaway_message = prefs_get_string(PREF_AUTOAWAY_MESSAGE);
-    if ((pref_autoaway_message == NULL) ||
-            (strcmp(pref_autoaway_message, "") == 0)) {
-        cons_show("Autoaway message (/autoaway message) : OFF");
+    if ((pref_autoaway_message == NULL) || (strcmp(pref_autoaway_message, "") == 0)) {
+        cons_show("Autoaway away message (/autoaway message) : OFF");
     } else {
-        cons_show("Autoaway message (/autoaway message) : \"%s\"", pref_autoaway_message);
+        cons_show("Autoaway away message (/autoaway message) : \"%s\"", pref_autoaway_message);
     }
     prefs_free_string(pref_autoaway_message);
 
+    char *pref_autoxa_message = prefs_get_string(PREF_AUTOXA_MESSAGE);
+    if ((pref_autoxa_message == NULL) || (strcmp(pref_autoxa_message, "") == 0)) {
+        cons_show("Autoaway xa message (/autoaway message)   : OFF");
+    } else {
+        cons_show("Autoaway xa message (/autoaway message)   : \"%s\"", pref_autoxa_message);
+    }
+    prefs_free_string(pref_autoxa_message);
+
     if (prefs_get_boolean(PREF_AUTOAWAY_CHECK)) {
-        cons_show("Autoaway check (/autoaway check)     : ON");
+        cons_show("Autoaway check (/autoaway check)          : ON");
     } else {
-        cons_show("Autoaway check (/autoaway check)     : OFF");
+        cons_show("Autoaway check (/autoaway check)          : OFF");
     }
 }
 
diff --git a/src/ui/core.c b/src/ui/core.c
index 3950ecea..ff6d90ce 100644
--- a/src/ui/core.c
+++ b/src/ui/core.c
@@ -552,36 +552,6 @@ ui_group_removed(const char * const contact, const char * const group)
 }
 
 void
-ui_auto_away(void)
-{
-    char *pref_autoaway_message = prefs_get_string(PREF_AUTOAWAY_MESSAGE);
-    if (pref_autoaway_message) {
-        int pri =
-            accounts_get_priority_for_presence_type(jabber_get_account_name(),
-                RESOURCE_AWAY);
-        cons_show("Idle for %d minutes, status set to away (priority %d), \"%s\".",
-            prefs_get_autoaway_time(), pri, pref_autoaway_message);
-        title_bar_set_presence(CONTACT_AWAY);
-    } else {
-        int pri =
-            accounts_get_priority_for_presence_type(jabber_get_account_name(),
-                RESOURCE_AWAY);
-        cons_show("Idle for %d minutes, status set to away (priority %d).",
-            prefs_get_autoaway_time(), pri);
-        title_bar_set_presence(CONTACT_AWAY);
-    }
-    prefs_free_string(pref_autoaway_message);
-}
-
-void
-ui_end_auto_away(resource_presence_t presence)
-{
-    int pri = accounts_get_priority_for_presence_type(jabber_get_account_name(), presence);
-    cons_show("No longer idle, status set to online (priority %d).", pri);
-    title_bar_set_presence(contact_presence_from_resource_presence(presence));
-}
-
-void
 ui_titlebar_presence(contact_presence_t presence)
 {
     title_bar_set_presence(presence);
diff --git a/src/ui/ui.h b/src/ui/ui.h
index fd3caf70..3aae7643 100644
--- a/src/ui/ui.h
+++ b/src/ui/ui.h
@@ -225,8 +225,7 @@ void ui_subwin_page_up(void);
 void ui_subwin_page_down(void);
 void ui_clear_win(ProfWin *window);
 
-void ui_auto_away(void);
-void ui_end_auto_away(resource_presence_t presence);
+void ui_auto_away(char *message, gint time, resource_presence_t res_presence);
 void ui_titlebar_presence(contact_presence_t presence);
 void ui_handle_login_account_success(ProfAccount *account);
 void ui_update_presence(const resource_presence_t resource_presence,
diff --git a/src/xmpp/presence.c b/src/xmpp/presence.c
index ff34cc50..7ccc20a4 100644
--- a/src/xmpp/presence.c
+++ b/src/xmpp/presence.c
@@ -254,7 +254,9 @@ presence_send(const resource_presence_t presence_type, const char * const msg, c
     if (last == NULL) {
         last = STANZA_TEXT_ONLINE;
     }
-    accounts_set_last_presence(jabber_get_account_name(), last);
+    char *account = jabber_get_account_name();
+    accounts_set_last_presence(account, last);
+    accounts_set_last_status(account, msg);
     free(id);
 }
 
diff --git a/tests/unittests/config/stub_accounts.c b/tests/unittests/config/stub_accounts.c
index 89b6dfc2..e13df7e7 100644
--- a/tests/unittests/config/stub_accounts.c
+++ b/tests/unittests/config/stub_accounts.c
@@ -122,6 +122,7 @@ void accounts_set_otr_policy(const char * const account_name, const char * const
 }
 
 void accounts_set_last_presence(const char * const account_name, const char * const value) {}
+void accounts_set_last_status(const char * const account_name, const char * const value) {}
 void accounts_set_last_activity(const char * const account_name) {}
 void accounts_set_pgp_keyid(const char * const account_name, const char * const value) {}
 
@@ -136,6 +137,11 @@ resource_presence_t accounts_get_login_presence(const char * const account_name)
     return RESOURCE_ONLINE;
 }
 
+char * accounts_get_last_status(const char * const account_name)
+{
+    return NULL;
+}
+
 resource_presence_t accounts_get_last_presence(const char * const account_name)
 {
     check_expected(account_name);
diff --git a/tests/unittests/ui/stub_ui.c b/tests/unittests/ui/stub_ui.c
index c22265b7..30afa087 100644
--- a/tests/unittests/ui/stub_ui.c
+++ b/tests/unittests/ui/stub_ui.c
@@ -315,8 +315,6 @@ gboolean ui_swap_wins(int source_win, int target_win)
     return FALSE;
 }
 
-void ui_auto_away(void) {}
-void ui_end_auto_away(resource_presence_t presence) {}
 void ui_titlebar_presence(contact_presence_t presence) {}
 void ui_handle_login_account_success(ProfAccount *account) {}
 void ui_update_presence(const resource_presence_t resource_presence,