about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/command/cmd_ac.c7
-rw-r--r--src/command/cmd_defs.c4
-rw-r--r--src/command/cmd_funcs.c104
-rw-r--r--src/common.c36
-rw-r--r--src/common.h3
-rw-r--r--src/config/files.c58
-rw-r--r--src/config/files.h3
-rw-r--r--src/database.c22
-rw-r--r--src/log.c76
-rw-r--r--src/omemo/omemo.c17
-rw-r--r--src/otr/otr.c36
-rw-r--r--src/pgp/gpg.c24
-rw-r--r--src/tools/editor.c125
-rw-r--r--src/tools/editor.h44
-rw-r--r--src/ui/inputwin.c57
-rw-r--r--src/xmpp/connection.c12
16 files changed, 324 insertions, 304 deletions
diff --git a/src/command/cmd_ac.c b/src/command/cmd_ac.c
index 9635211d..bb703cfb 100644
--- a/src/command/cmd_ac.c
+++ b/src/command/cmd_ac.c
@@ -780,6 +780,7 @@ cmd_ac_init(void)
     subject_ac = autocomplete_new();
     autocomplete_add(subject_ac, "set");
     autocomplete_add(subject_ac, "edit");
+    autocomplete_add(subject_ac, "editor");
     autocomplete_add(subject_ac, "prepend");
     autocomplete_add(subject_ac, "append");
     autocomplete_add(subject_ac, "clear");
@@ -3637,11 +3638,7 @@ _subject_autocomplete(ProfWin* window, const char* const input, gboolean previou
     char* result = NULL;
 
     if (window->type == WIN_MUC) {
-        if ((g_strcmp0(input, "/subject e") == 0)
-            || (g_strcmp0(input, "/subject ed") == 0)
-            || (g_strcmp0(input, "/subject edi") == 0)
-            || (g_strcmp0(input, "/subject edit") == 0)
-            || (g_strcmp0(input, "/subject edit ") == 0)
+        if ((g_strcmp0(input, "/subject edit ") == 0)
             || (g_strcmp0(input, "/subject edit \"") == 0)) {
             ProfMucWin* mucwin = (ProfMucWin*)window;
             assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
diff --git a/src/command/cmd_defs.c b/src/command/cmd_defs.c
index 359f47d0..750e533d 100644
--- a/src/command/cmd_defs.c
+++ b/src/command/cmd_defs.c
@@ -676,6 +676,7 @@ static struct cmd_t command_defs[] = {
       CMD_SYN(
               "/subject set <subject>",
               "/subject edit <subject>",
+              "/subject editor",
               "/subject prepend <text>",
               "/subject append <text>",
               "/subject clear")
@@ -684,6 +685,7 @@ static struct cmd_t command_defs[] = {
       CMD_ARGS(
               { "set <subject>", "Set the room subject." },
               { "edit <subject>", "Edit the current room subject, tab autocompletion will display the subject to edit." },
+              { "editor", "Edit the current room subject in external editor." },
               { "prepend <text>", "Prepend text to the current room subject, use double quotes if a trailing space is needed." },
               { "append <text>", "Append text to the current room subject, use double quotes if a preceding space is needed." },
               { "clear", "Clear the room subject." })
@@ -3002,7 +3004,7 @@ command_mangen(void)
         cmds = g_list_insert_sorted(cmds, pcmd, (GCompareFunc)_cmp_command);
     }
 
-    mkdir_recursive("docs");
+    create_dir("docs");
 
     GDateTime* now = g_date_time_new_now_local();
     gchar* date = g_date_time_format(now, "%F");
diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c
index be469c28..b1aadbf0 100644
--- a/src/command/cmd_funcs.c
+++ b/src/command/cmd_funcs.c
@@ -77,6 +77,7 @@
 #include "tools/autocomplete.h"
 #include "tools/parser.h"
 #include "tools/bookmark_ignore.h"
+#include "tools/editor.h"
 #include "plugins/plugins.h"
 #include "ui/ui.h"
 #include "ui/window_list.h"
@@ -4095,6 +4096,22 @@ cmd_subject(ProfWin* window, const char* const command, gchar** args)
         return TRUE;
     }
 
+    if (g_strcmp0(args[0], "editor") == 0) {
+        gchar* message = NULL;
+        char* subject = muc_subject(mucwin->roomjid);
+
+        if (get_message_from_editor(subject, &message)) {
+            return TRUE;
+        }
+
+        if (message) {
+            message_send_groupchat_subject(mucwin->roomjid, message);
+        } else {
+            cons_bad_cmd_usage(command);
+        }
+        return TRUE;
+    }
+
     if (g_strcmp0(args[0], "prepend") == 0) {
         if (args[1]) {
             char* old_subject = muc_subject(mucwin->roomjid);
@@ -9452,89 +9469,6 @@ cmd_change_password(ProfWin* window, const char* const command, gchar** args)
     return TRUE;
 }
 
-// Returns true if an error occurred
-gboolean
-_get_message_from_editor(gchar* message, gchar** returned_message)
-{
-    // create editor dir if not present
-    char* jid = connection_get_barejid();
-    gchar* path = files_get_account_data_path(DIR_EDITOR, jid);
-    free(jid);
-    if (g_mkdir_with_parents(path, S_IRWXU) != 0) {
-        cons_show_error("Failed to create directory at '%s' with error '%s'", path, strerror(errno));
-        g_free(path);
-        return TRUE;
-    }
-
-    // build temp file name. Example: /home/user/.local/share/profanity/editor/jid/compose.md
-    char* filename = g_strdup_printf("%s/compose.md", path);
-    g_free(path);
-
-    GError* creation_error = NULL;
-    GFile* file = g_file_new_for_path(filename);
-    GFileOutputStream* fos = g_file_create(file, G_FILE_CREATE_PRIVATE, NULL, &creation_error);
-
-    free(filename);
-
-    if (message != NULL && strlen(message) > 0) {
-        int fd_output_file = open(g_file_get_path(file), O_WRONLY);
-        if (fd_output_file < 0) {
-            cons_show_error("Editor: Could not open file '%s': %s", file, strerror(errno));
-            return TRUE;
-        }
-        if (-1 == write(fd_output_file, message, strlen(message))) {
-            cons_show_error("Editor: failed to write '%s' to file: %s", message, strerror(errno));
-            return TRUE;
-        }
-        close(fd_output_file);
-    }
-
-    if (creation_error) {
-        cons_show_error("Editor: could not create temp file");
-        return TRUE;
-    }
-    g_object_unref(fos);
-
-    char* editor = prefs_get_string(PREF_COMPOSE_EDITOR);
-
-    // Fork / exec
-    pid_t pid = fork();
-    if (pid == 0) {
-        int x = execlp(editor, editor, g_file_get_path(file), (char*)NULL);
-        if (x == -1) {
-            cons_show_error("Editor:Failed to exec %s", editor);
-        }
-        _exit(EXIT_FAILURE);
-    } else {
-        if (pid == -1) {
-            return TRUE;
-        }
-        int status = 0;
-        waitpid(pid, &status, 0);
-        int fd_input_file = open(g_file_get_path(file), O_RDONLY);
-        const size_t COUNT = 8192;
-        char buf[COUNT];
-        ssize_t size_read = read(fd_input_file, buf, COUNT);
-        if (size_read > 0 && size_read <= COUNT) {
-            buf[size_read - 1] = '\0';
-            GString* text = g_string_new(buf);
-            *returned_message = g_strdup(text->str);
-            g_string_free(text, TRUE);
-        }
-        close(fd_input_file);
-
-        GError* deletion_error = NULL;
-        g_file_delete(file, NULL, &deletion_error);
-        if (deletion_error) {
-            cons_show("Editor: error during file deletion");
-            return TRUE;
-        }
-        g_object_unref(file);
-    }
-
-    return FALSE;
-}
-
 gboolean
 cmd_editor(ProfWin* window, const char* const command, gchar** args)
 {
@@ -9547,7 +9481,7 @@ cmd_editor(ProfWin* window, const char* const command, gchar** args)
 
     gchar* message = NULL;
 
-    if (_get_message_from_editor(NULL, &message)) {
+    if (get_message_from_editor(NULL, &message)) {
         return TRUE;
     }
 
@@ -9570,7 +9504,7 @@ cmd_correct_editor(ProfWin* window, const char* const command, gchar** args)
     gchar* initial_message = win_get_last_sent_message(window);
 
     gchar* message = NULL;
-    if (_get_message_from_editor(initial_message, &message)) {
+    if (get_message_from_editor(initial_message, &message)) {
         return TRUE;
     }
 
diff --git a/src/common.c b/src/common.c
index 6020b8c3..5e0fabf6 100644
--- a/src/common.c
+++ b/src/common.c
@@ -71,44 +71,16 @@ struct curl_data_t
 static size_t _data_callback(void* ptr, size_t size, size_t nmemb, void* data);
 
 gboolean
-create_dir(char* name)
+create_dir(const char* name)
 {
-    struct stat sb;
-
-    if (stat(name, &sb) != 0) {
-        if (errno != ENOENT || mkdir(name, S_IRWXU) != 0) {
-            return FALSE;
-        }
-    } else {
-        if ((sb.st_mode & S_IFDIR) != S_IFDIR) {
-            log_debug("create_dir: %s exists and is not a directory!", name);
-            return FALSE;
-        }
+    if (g_mkdir_with_parents(name, S_IRWXU) != 0) {
+        log_error("Failed to create directory at '%s' with error '%s'", name, strerror(errno));
+        return FALSE;
     }
-
     return TRUE;
 }
 
 gboolean
-mkdir_recursive(const char* dir)
-{
-    gboolean result = TRUE;
-
-    for (int i = 1; i <= strlen(dir); i++) {
-        if (dir[i] == '/' || dir[i] == '\0') {
-            gchar* next_dir = g_strndup(dir, i);
-            result = create_dir(next_dir);
-            g_free(next_dir);
-            if (!result) {
-                break;
-            }
-        }
-    }
-
-    return result;
-}
-
-gboolean
 copy_file(const char* const sourcepath, const char* const targetpath, const gboolean overwrite_existing)
 {
     GFile* source = g_file_new_for_path(sourcepath);
diff --git a/src/common.h b/src/common.h
index bd33bf90..b97ef401 100644
--- a/src/common.h
+++ b/src/common.h
@@ -80,8 +80,7 @@ typedef enum {
     RESOURCE_XA
 } resource_presence_t;
 
-gboolean create_dir(char* name);
-gboolean mkdir_recursive(const char* dir);
+gboolean create_dir(const char* name);
 gboolean copy_file(const char* const src, const char* const target, const gboolean overwrite_existing);
 char* str_replace(const char* string, const char* substr, const char* replacement);
 gboolean strtoi_range(char* str, int* saveptr, int min, int max, char** err_msg);
diff --git a/src/config/files.c b/src/config/files.c
index ce3f1196..7e24dee9 100644
--- a/src/config/files.c
+++ b/src/config/files.c
@@ -38,6 +38,8 @@
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
 #include <glib.h>
 
 #include "common.h"
@@ -65,19 +67,19 @@ files_create_directories(void)
     GString* plugins_dir = g_string_new(xdg_data);
     g_string_append(plugins_dir, "/profanity/plugins");
 
-    if (!mkdir_recursive(themes_dir->str)) {
+    if (!create_dir(themes_dir->str)) {
         log_error("Error while creating directory %s", themes_dir->str);
     }
-    if (!mkdir_recursive(icons_dir->str)) {
+    if (!create_dir(icons_dir->str)) {
         log_error("Error while creating directory %s", icons_dir->str);
     }
-    if (!mkdir_recursive(chatlogs_dir->str)) {
+    if (!create_dir(chatlogs_dir->str)) {
         log_error("Error while creating directory %s", chatlogs_dir->str);
     }
-    if (!mkdir_recursive(logs_dir->str)) {
+    if (!create_dir(logs_dir->str)) {
         log_error("Error while creating directory %s", logs_dir->str);
     }
-    if (!mkdir_recursive(plugins_dir->str)) {
+    if (!create_dir(plugins_dir->str)) {
         log_error("Error while creating directory %s", plugins_dir->str);
     }
 
@@ -120,7 +122,7 @@ files_get_log_file(const char* const log_file)
 
     if (log_file) {
         gchar* log_path = g_path_get_dirname(log_file);
-        if (!mkdir_recursive(log_path)) {
+        if (!create_dir(log_path)) {
             log_error("Error while creating directory %s", log_path);
         }
         g_free(log_path);
@@ -149,13 +151,8 @@ gchar*
 files_get_config_path(const char* const config_base)
 {
     gchar* xdg_config = _files_get_xdg_config_home();
-    GString* file_str = g_string_new(xdg_config);
-    g_string_append(file_str, "/profanity/");
-    g_string_append(file_str, config_base);
-    gchar* result = g_strdup(file_str->str);
+    gchar* result = g_strdup_printf("%s/profanity/%s", xdg_config, config_base);
     g_free(xdg_config);
-    g_string_free(file_str, TRUE);
-
     return result;
 }
 
@@ -163,13 +160,8 @@ gchar*
 files_get_data_path(const char* const data_base)
 {
     gchar* xdg_data = _files_get_xdg_data_home();
-    GString* file_str = g_string_new(xdg_data);
-    g_string_append(file_str, "/profanity/");
-    g_string_append(file_str, data_base);
-    gchar* result = g_strdup(file_str->str);
+    gchar* result = g_strdup_printf("%s/profanity/%s", xdg_data, data_base);
     g_free(xdg_data);
-    g_string_free(file_str, TRUE);
-
     return result;
 }
 
@@ -177,20 +169,34 @@ gchar*
 files_get_account_data_path(const char* const specific_dir, const char* const jid)
 {
     gchar* data_dir = files_get_data_path(specific_dir);
-    GString* result_dir = g_string_new(data_dir);
-    g_free(data_dir);
-
     gchar* account_dir = str_replace(jid, "@", "_at_");
-    g_string_append(result_dir, "/");
-    g_string_append(result_dir, account_dir);
-    g_free(account_dir);
 
-    gchar* result = g_strdup(result_dir->str);
-    g_string_free(result_dir, TRUE);
+    gchar* result = g_strdup_printf("%s/%s", data_dir, account_dir);
+
+    g_free(account_dir);
+    g_free(data_dir);
 
     return result;
 }
 
+gchar*
+files_file_in_account_data_path(const char* const specific_dir, const char* const jid, const char* const file_name)
+{
+    gchar* data_path = files_get_account_data_path(specific_dir, jid);
+
+    if (!create_dir(data_path)) {
+        g_free(data_path);
+        return NULL;
+    }
+
+    if (!file_name) {
+        return data_path;
+    }
+    gchar* filename = g_strdup_printf("%s/%s", data_path, file_name);
+    g_free(data_path);
+    return filename;
+}
+
 static char*
 _files_get_xdg_config_home(void)
 {
diff --git a/src/config/files.h b/src/config/files.h
index a6b5a730..c0000ce1 100644
--- a/src/config/files.h
+++ b/src/config/files.h
@@ -69,4 +69,7 @@ gchar* files_get_account_data_path(const char* const specific_dir, const char* c
 gchar* files_get_log_file(const char* const log_file);
 gchar* files_get_inputrc_file(void);
 
+gchar*
+files_file_in_account_data_path(const char* const specific_dir, const char* const jid, const char* const file_name);
+
 #endif
diff --git a/src/database.c b/src/database.c
index c04dcff9..4656ae79 100644
--- a/src/database.c
+++ b/src/database.c
@@ -56,27 +56,7 @@ static prof_msg_type_t _get_message_type_type(const char* const type);
 static char*
 _get_db_filename(ProfAccount* account)
 {
-    gchar* database_dir = files_get_account_data_path(DIR_DATABASE, account->jid);
-
-    int res = g_mkdir_with_parents(database_dir, S_IRWXU);
-    if (res == -1) {
-        const char* errmsg = strerror(errno);
-        if (errmsg) {
-            log_error("DATABASE: error creating directory: %s, %s", database_dir, errmsg);
-        } else {
-            log_error("DATABASE: creating directory: %s", database_dir);
-        }
-        g_free(database_dir);
-        return NULL;
-    }
-
-    GString* chatlog_filename = g_string_new(database_dir);
-    g_string_append(chatlog_filename, "/chatlog.db");
-    gchar* result = g_strdup(chatlog_filename->str);
-    g_string_free(chatlog_filename, TRUE);
-    g_free(database_dir);
-
-    return result;
+    return files_file_in_account_data_path(DIR_DATABASE, account->jid, "chatlog.db");
 }
 
 gboolean
diff --git a/src/log.c b/src/log.c
index 8249d70a..546f7804 100644
--- a/src/log.c
+++ b/src/log.c
@@ -90,9 +90,7 @@ static struct dated_chat_log* _create_log(const char* const other, const char* c
 static struct dated_chat_log* _create_groupchat_log(const char* const room, const char* const login);
 static void _free_chat_log(struct dated_chat_log* dated_log);
 static gboolean _key_equals(void* key1, void* key2);
-static char* _get_log_filename(const char* const other, const char* const login, GDateTime* dt, gboolean create);
-static char* _get_groupchat_log_filename(const char* const room, const char* const login, GDateTime* dt,
-                                         gboolean create);
+static char* _get_log_filename(const char* const other, const char* const login, GDateTime* dt, gboolean is_room);
 static void _rotate_log_file(void);
 static char* _log_string_from_level(log_level_t level);
 static void _chat_log_chat(const char* const login, const char* const other, const gchar* const msg,
@@ -612,7 +610,7 @@ static struct dated_chat_log*
 _create_log(const char* const other, const char* const login)
 {
     GDateTime* now = g_date_time_new_now_local();
-    char* filename = _get_log_filename(other, login, now, TRUE);
+    char* filename = _get_log_filename(other, login, now, FALSE);
 
     struct dated_chat_log* new_log = malloc(sizeof(struct dated_chat_log));
     new_log->filename = strdup(filename);
@@ -627,7 +625,7 @@ static struct dated_chat_log*
 _create_groupchat_log(const char* const room, const char* const login)
 {
     GDateTime* now = g_date_time_new_now_local();
-    char* filename = _get_groupchat_log_filename(room, login, now, TRUE);
+    char* filename = _get_log_filename(room, login, now, TRUE);
 
     struct dated_chat_log* new_log = malloc(sizeof(struct dated_chat_log));
     new_log->filename = strdup(filename);
@@ -677,70 +675,16 @@ _key_equals(void* key1, void* key2)
 }
 
 static char*
-_get_log_filename(const char* const other, const char* const login, GDateTime* dt, gboolean create)
+_get_log_filename(const char* const other, const char* const login, GDateTime* dt, gboolean is_room)
 {
-    char* chatlogs_dir = files_get_data_path(DIR_CHATLOGS);
-    GString* log_file = g_string_new(chatlogs_dir);
-    free(chatlogs_dir);
+    gchar* chatlogs_dir = files_file_in_account_data_path(DIR_CHATLOGS, login, is_room ? "rooms" : NULL);
+    gchar* logfile_name = g_date_time_format(dt, "%Y_%m_%d.log");
+    gchar* logfile_path = files_file_in_account_data_path(chatlogs_dir, other, logfile_name);
 
-    gchar* login_dir = str_replace(login, "@", "_at_");
-    g_string_append_printf(log_file, "/%s", login_dir);
-    if (create) {
-        create_dir(log_file->str);
-    }
-    free(login_dir);
-
-    gchar* other_file = str_replace(other, "@", "_at_");
-    g_string_append_printf(log_file, "/%s", other_file);
-    if (create) {
-        create_dir(log_file->str);
-    }
-    free(other_file);
-
-    gchar* date = g_date_time_format(dt, "/%Y_%m_%d.log");
-    g_string_append(log_file, date);
-    g_free(date);
-
-    char* result = strdup(log_file->str);
-    g_string_free(log_file, TRUE);
-
-    return result;
-}
-
-static char*
-_get_groupchat_log_filename(const char* const room, const char* const login, GDateTime* dt, gboolean create)
-{
-    char* chatlogs_dir = files_get_data_path(DIR_CHATLOGS);
-    GString* log_file = g_string_new(chatlogs_dir);
-    free(chatlogs_dir);
-
-    gchar* login_dir = str_replace(login, "@", "_at_");
-    g_string_append_printf(log_file, "/%s", login_dir);
-    if (create) {
-        create_dir(log_file->str);
-    }
-    free(login_dir);
-
-    g_string_append(log_file, "/rooms");
-    if (create) {
-        create_dir(log_file->str);
-    }
+    g_free(logfile_name);
+    g_free(chatlogs_dir);
 
-    gchar* room_file = str_replace(room, "@", "_at_");
-    g_string_append_printf(log_file, "/%s", room_file);
-    if (create) {
-        create_dir(log_file->str);
-    }
-    free(room_file);
-
-    gchar* date = g_date_time_format(dt, "/%Y_%m_%d.log");
-    g_string_append(log_file, date);
-    g_free(date);
-
-    char* result = strdup(log_file->str);
-    g_string_free(log_file, TRUE);
-
-    return result;
+    return logfile_path;
 }
 
 static char*
diff --git a/src/omemo/omemo.c b/src/omemo/omemo.c
index 87599de0..c46714d6 100644
--- a/src/omemo/omemo.c
+++ b/src/omemo/omemo.c
@@ -231,7 +231,11 @@ omemo_on_connect(ProfAccount* account)
     omemo_ctx.device_list_handler = g_hash_table_new_full(g_str_hash, g_str_equal, free, NULL);
     omemo_ctx.known_devices = g_hash_table_new_full(g_str_hash, g_str_equal, free, (GDestroyNotify)_g_hash_table_free);
 
-    gchar* omemo_dir = files_get_account_data_path(DIR_OMEMO, account->jid);
+    gchar* omemo_dir = files_file_in_account_data_path(DIR_OMEMO, account->jid, NULL);
+    if (!omemo_dir) {
+        log_error("[OMEMO] failed creating directory");
+        return;
+    }
 
     omemo_ctx.identity_filename = g_string_new(omemo_dir);
     g_string_append(omemo_ctx.identity_filename, "/identity.txt");
@@ -242,17 +246,6 @@ omemo_on_connect(ProfAccount* account)
     omemo_ctx.known_devices_filename = g_string_new(omemo_dir);
     g_string_append(omemo_ctx.known_devices_filename, "/known_devices.txt");
 
-    errno = 0;
-    int res = g_mkdir_with_parents(omemo_dir, S_IRWXU);
-    if (res == -1) {
-        const char* errmsg = strerror(errno);
-        if (errmsg) {
-            log_error("[OMEMO] error creating directory: %s, %s", omemo_dir, errmsg);
-        } else {
-            log_error("[OMEMO] creating directory: %s", omemo_dir);
-        }
-    }
-
     g_free(omemo_dir);
 
     omemo_devicelist_subscribe();
diff --git a/src/otr/otr.c b/src/otr/otr.c
index 5f45e23c..562914a2 100644
--- a/src/otr/otr.c
+++ b/src/otr/otr.c
@@ -129,19 +129,20 @@ static void
 cb_write_fingerprints(void* opdata)
 {
     gcry_error_t err = 0;
-    gchar* otr_dir = files_get_account_data_path(DIR_OTR, jid);
-
-    GString* fpsfilename = g_string_new(otr_dir);
-    g_string_append(fpsfilename, "/fingerprints.txt");
+    gchar* fpsfilename = files_file_in_account_data_path(DIR_OTR, jid, "fingerprints.txt");
+    if (!fpsfilename) {
+        log_error("Failed to create fingerprints file");
+        cons_show_error("Failed to create fingerprints file");
+        return;
+    }
 
-    err = otrl_privkey_write_fingerprints(user_state, fpsfilename->str);
+    err = otrl_privkey_write_fingerprints(user_state, fpsfilename);
     if (err != GPG_ERR_NO_ERROR) {
         log_error("Failed to write fingerprints file");
-        cons_show_error("Failed to create fingerprints file");
+        cons_show_error("Failed to write fingerprints file");
     }
 
-    g_free(otr_dir);
-    g_string_free(fpsfilename, TRUE);
+    g_free(fpsfilename);
 }
 
 static void
@@ -212,12 +213,10 @@ otr_on_connect(ProfAccount* account)
     jid = strdup(account->jid);
     log_info("Loading OTR key for %s", jid);
 
-    gchar* otr_dir = files_get_account_data_path(DIR_OTR, jid);
-
-    if (!mkdir_recursive(otr_dir)) {
-        log_error("Could not create %s for account %s.", otr_dir, jid);
-        cons_show_error("Could not create %s for account %s.", otr_dir, jid);
-        g_free(otr_dir);
+    gchar* otr_dir = files_file_in_account_data_path(DIR_OTR, jid, NULL);
+    if (!otr_dir) {
+        log_error("Could not create directory for account %s.", jid);
+        cons_show_error("Could not create directory for account %s.", jid);
         return;
     }
 
@@ -381,12 +380,11 @@ otr_keygen(ProfAccount* account)
     jid = strdup(account->jid);
     log_info("Generating OTR key for %s", jid);
 
-    gchar* otr_dir = files_get_account_data_path(DIR_OTR, jid);
+    gchar* otr_dir = files_file_in_account_data_path(DIR_OTR, jid, NULL);
 
-    if (!mkdir_recursive(otr_dir)) {
-        log_error("Could not create %s for account %s.", otr_dir, jid);
-        cons_show_error("Could not create %s for account %s.", otr_dir, jid);
-        g_free(otr_dir);
+    if (!otr_dir) {
+        log_error("Could not create directory for account %s.", jid);
+        cons_show_error("Could not create directory for account %s.", jid);
         return;
     }
 
diff --git a/src/pgp/gpg.c b/src/pgp/gpg.c
index a56b34e3..99d37c64 100644
--- a/src/pgp/gpg.c
+++ b/src/pgp/gpg.c
@@ -161,27 +161,13 @@ p_gpg_close(void)
 void
 p_gpg_on_connect(const char* const barejid)
 {
-    gchar* pubsfile = files_get_account_data_path(DIR_PGP, barejid);
-
-    // mkdir if doesn't exist for account
-    errno = 0;
-    int res = g_mkdir_with_parents(pubsfile, S_IRWXU);
-    if (res == -1) {
-        const char* errmsg = strerror(errno);
-        if (errmsg) {
-            log_error("Error creating directory: %s, %s", pubsfile, errmsg);
-        } else {
-            log_error("Error creating directory: %s", pubsfile);
-        }
+    pubsloc = files_file_in_account_data_path(DIR_PGP, barejid, "pubkeys");
+    if (!pubsloc) {
+        log_error("Could not create directory for account %s.", barejid);
+        cons_show_error("Could not create directory for account %s.", barejid);
+        return;
     }
 
-    // create or read publickeys
-    GString* pubtmp = g_string_new(pubsfile);
-    g_string_append(pubtmp, "/pubkeys");
-    pubsloc = pubtmp->str;
-    g_string_free(pubtmp, FALSE);
-    g_free(pubsfile);
-
     if (g_file_test(pubsloc, G_FILE_TEST_EXISTS)) {
         g_chmod(pubsloc, S_IRUSR | S_IWUSR);
     }
diff --git a/src/tools/editor.c b/src/tools/editor.c
new file mode 100644
index 00000000..f8dc5b22
--- /dev/null
+++ b/src/tools/editor.c
@@ -0,0 +1,125 @@
+/*
+ * editor.c
+ * vim: expandtab:ts=4:sts=4:sw=4
+ *
+ * Copyright (C) 2022 Michael Vetter <jubalh@iodoru.org>
+ * Copyright (C) 2022 MarcoPolo PasTonMolo <marcopolopastonmolo@protonmail.com>
+ * Copyright (C) 2022 Steffen Jaeckel <jaeckel-floss@eyet-services.de>
+ *
+ * This file is part of Profanity.
+ *
+ * Profanity is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Profanity is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Profanity.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ * In addition, as a special exception, the copyright holders give permission to
+ * link the code of portions of this program with the OpenSSL library under
+ * certain conditions as described in each individual source file, and
+ * distribute linked combinations including the two.
+ *
+ * You must obey the GNU General Public License in all respects for all of the
+ * code used other than OpenSSL. If you modify file(s) with this exception, you
+ * may extend this exception to your version of the file(s), but you are not
+ * obligated to do so. If you do not wish to do so, delete this exception
+ * statement from your version. If you delete this exception statement from all
+ * source files in the program, then also delete it here.
+ *
+ */
+
+#include <fcntl.h>
+#include <glib.h>
+#include <errno.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "config/files.h"
+#include "config/preferences.h"
+#include "log.h"
+
+// Returns true if an error occurred
+gboolean
+get_message_from_editor(gchar* message, gchar** returned_message)
+{
+    /* Make sure that there's no junk in the return-pointer in error cases */
+    *returned_message = NULL;
+
+    gchar* filename = NULL;
+    GError* glib_error = NULL;
+    char* jid = connection_get_barejid();
+    if (jid) {
+        filename = files_file_in_account_data_path(DIR_EDITOR, jid, "compose.md");
+    } else {
+        log_debug("[Editor] could not get JID");
+        gchar* data_dir = files_get_data_path(DIR_EDITOR);
+        if (!create_dir(data_dir)) {
+            return TRUE;
+        }
+        filename = g_strdup_printf("%s/compose.md", data_dir);
+        g_free(data_dir);
+    }
+    if (!filename) {
+        log_error("[Editor] something went wrong while creating compose file");
+        return TRUE;
+    }
+
+    gsize messagelen = strlen(message);
+    if (!g_file_set_contents(filename, message, messagelen, &glib_error)) {
+        log_error("[Editor] could not write to %s: %s", filename, glib_error ? glib_error->message : "No GLib error given");
+        if (glib_error) {
+            g_error_free(glib_error);
+        }
+        g_free(filename);
+        return TRUE;
+    }
+
+    char* editor = prefs_get_string(PREF_COMPOSE_EDITOR);
+
+    // Fork / exec
+    pid_t pid = fork();
+    if (pid == 0) {
+        int x = execlp(editor, editor, filename, (char*)NULL);
+        if (x == -1) {
+            log_error("[Editor] Failed to exec %s", editor);
+        }
+        _exit(EXIT_FAILURE);
+    } else {
+        if (pid == -1) {
+            return TRUE;
+        }
+        waitpid(pid, NULL, 0);
+
+        gchar* contents;
+        gsize length;
+        if (!g_file_get_contents(filename, &contents, &length, &glib_error)) {
+            log_error("[Editor] could not read from %s: %s", filename, glib_error ? glib_error->message : "No GLib error given");
+            if (glib_error) {
+                g_error_free(glib_error);
+            }
+            g_free(filename);
+            g_free(editor);
+            return TRUE;
+        }
+        /* Remove all trailing new-line characters */
+        g_strchomp(contents);
+        *returned_message = contents;
+        if (remove(filename) != 0) {
+            log_error("[Editor] error during file deletion of %s", filename);
+        } else {
+            log_debug("[Editor] deleted file: %s", filename);
+        }
+        g_free(filename);
+    }
+
+    g_free(editor);
+
+    return FALSE;
+}
diff --git a/src/tools/editor.h b/src/tools/editor.h
new file mode 100644
index 00000000..4b239a5b
--- /dev/null
+++ b/src/tools/editor.h
@@ -0,0 +1,44 @@
+/*
+ * editor.h
+ * vim: expandtab:ts=4:sts=4:sw=4
+ *
+ * Copyright (C) 2022 Michael Vetter <jubalh@iodoru.org>
+ * Copyright (C) 2022 MarcoPolo PasTonMolo  <marcopolopastonmolo@protonmail.com>
+ *
+ * This file is part of Profanity.
+ *
+ * Profanity is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Profanity is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Profanity.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ * In addition, as a special exception, the copyright holders give permission to
+ * link the code of portions of this program with the OpenSSL library under
+ * certain conditions as described in each individual source file, and
+ * distribute linked combinations including the two.
+ *
+ * You must obey the GNU General Public License in all respects for all of the
+ * code used other than OpenSSL. If you modify file(s) with this exception, you
+ * may extend this exception to your version of the file(s), but you are not
+ * obligated to do so. If you do not wish to do so, delete this exception
+ * statement from your version. If you delete this exception statement from all
+ * source files in the program, then also delete it here.
+ *
+ */
+
+#ifndef TOOLS_EDITOR_H
+#define TOOLS_EDITOR_H
+
+#include <glib.h>
+
+gboolean get_message_from_editor(gchar* message, gchar** returned_message);
+
+#endif
diff --git a/src/ui/inputwin.c b/src/ui/inputwin.c
index 4ea773c8..9e34c0b6 100644
--- a/src/ui/inputwin.c
+++ b/src/ui/inputwin.c
@@ -74,6 +74,7 @@
 #include "xmpp/muc.h"
 #include "xmpp/roster_list.h"
 #include "xmpp/chat_state.h"
+#include "tools/editor.h"
 
 static WINDOW* inp_win;
 static int pad_start = 0;
@@ -133,6 +134,7 @@ static int _inp_rl_subwin_pageup_handler(int count, int key);
 static int _inp_rl_subwin_pagedown_handler(int count, int key);
 static int _inp_rl_startup_hook(void);
 static int _inp_rl_down_arrow_handler(int count, int key);
+static int _inp_rl_send_to_editor(int count, int key);
 
 void
 create_input_window(void)
@@ -434,6 +436,7 @@ _inp_rl_addfuncs(void)
     rl_add_funmap_entry("prof_subwin_pagedown", _inp_rl_subwin_pagedown_handler);
     rl_add_funmap_entry("prof_win_clear", _inp_rl_win_clear_handler);
     rl_add_funmap_entry("prof_win_close", _inp_rl_win_close_handler);
+    rl_add_funmap_entry("prof_send_to_editor", _inp_rl_send_to_editor);
 }
 
 // Readline callbacks
@@ -484,6 +487,7 @@ _inp_rl_startup_hook(void)
     rl_bind_keyseq("\\ea", _inp_rl_win_next_unread_handler);
     rl_bind_keyseq("\\ev", _inp_rl_win_attention_handler);
     rl_bind_keyseq("\\em", _inp_rl_win_attention_next_handler);
+    rl_bind_keyseq("\\ec", _inp_rl_send_to_editor);
 
     rl_bind_keyseq("\\e\\e[5~", _inp_rl_subwin_pageup_handler);
     rl_bind_keyseq("\\e[5;3~", _inp_rl_subwin_pageup_handler);
@@ -582,17 +586,20 @@ _inp_rl_tab_handler(int count, int key)
         return 0;
     }
 
-    ProfWin* current = wins_get_current();
-    if ((strncmp(rl_line_buffer, "/", 1) != 0) && (current->type == WIN_MUC)) {
-        char* result = muc_autocomplete(current, rl_line_buffer, FALSE);
+    if (strncmp(rl_line_buffer, "/", 1) == 0) {
+        ProfWin* window = wins_get_current();
+        char* result = cmd_ac_complete(window, rl_line_buffer, FALSE);
         if (result) {
             rl_replace_line(result, 1);
             rl_point = rl_end;
             free(result);
+            return 0;
         }
-    } else if (strncmp(rl_line_buffer, "/", 1) == 0) {
-        ProfWin* window = wins_get_current();
-        char* result = cmd_ac_complete(window, rl_line_buffer, FALSE);
+    }
+
+    ProfWin* current = wins_get_current();
+    if (current->type == WIN_MUC) {
+        char* result = muc_autocomplete(current, rl_line_buffer, FALSE);
         if (result) {
             rl_replace_line(result, 1);
             rl_point = rl_end;
@@ -600,6 +607,7 @@ _inp_rl_tab_handler(int count, int key)
         }
     }
 
+
     return 0;
 }
 
@@ -610,17 +618,20 @@ _inp_rl_shift_tab_handler(int count, int key)
         return 0;
     }
 
-    ProfWin* current = wins_get_current();
-    if ((strncmp(rl_line_buffer, "/", 1) != 0) && (current->type == WIN_MUC)) {
-        char* result = muc_autocomplete(current, rl_line_buffer, TRUE);
+    if (strncmp(rl_line_buffer, "/", 1) == 0) {
+        ProfWin* window = wins_get_current();
+        char* result = cmd_ac_complete(window, rl_line_buffer, TRUE);
         if (result) {
             rl_replace_line(result, 1);
             rl_point = rl_end;
             free(result);
+            return 0;
         }
-    } else if (strncmp(rl_line_buffer, "/", 1) == 0) {
-        ProfWin* window = wins_get_current();
-        char* result = cmd_ac_complete(window, rl_line_buffer, TRUE);
+    }
+
+    ProfWin* current = wins_get_current();
+    if (current->type == WIN_MUC) {
+        char* result = muc_autocomplete(current, rl_line_buffer, TRUE);
         if (result) {
             rl_replace_line(result, 1);
             rl_point = rl_end;
@@ -877,3 +888,25 @@ _inp_rl_down_arrow_handler(int count, int key)
     rl_redisplay();
     return 0;
 }
+
+static int
+_inp_rl_send_to_editor(int count, int key)
+{
+    if (!rl_line_buffer) {
+        return 0;
+    }
+
+    gchar* message = NULL;
+
+    if (get_message_from_editor(rl_line_buffer, &message)) {
+        return 0;
+    }
+
+    rl_replace_line(message, 0);
+    ui_resize();
+    rl_point = rl_end;
+    rl_forced_update_display();
+    g_free(message);
+
+    return 0;
+}
diff --git a/src/xmpp/connection.c b/src/xmpp/connection.c
index c5475258..d601de22 100644
--- a/src/xmpp/connection.c
+++ b/src/xmpp/connection.c
@@ -747,6 +747,8 @@ connection_get_ctx(void)
 const char*
 connection_get_fulljid(void)
 {
+    if (!conn.xmpp_conn)
+        return NULL;
     const char* jid = xmpp_conn_get_bound_jid(conn.xmpp_conn);
     if (jid) {
         return jid;
@@ -759,10 +761,11 @@ char*
 connection_get_barejid(void)
 {
     const char* jid = connection_get_fulljid();
-    char* result;
+    if (!jid)
+        return NULL;
 
     Jid* jidp = jid_create(jid);
-    result = strdup(jidp->barejid);
+    char* result = strdup(jidp->barejid);
     jid_destroy(jidp);
 
     return result;
@@ -772,8 +775,9 @@ char*
 connection_get_user(void)
 {
     const char* jid = connection_get_fulljid();
-    char* result;
-    result = strdup(jid);
+    if (!jid)
+        return NULL;
+    char* result = strdup(jid);
 
     char* split = strchr(result, '@');
     *split = '\0';