diff --git a/doc/toxic.conf.5 b/doc/toxic.conf.5 index 24a2f5d..a3698ec 100644 --- a/doc/toxic.conf.5 +++ b/doc/toxic.conf.5 @@ -313,6 +313,11 @@ Key combination to scroll contacts list down\&. .RS 4 Toggle the peer list on and off\&. .RE +.PP +\fBtoggle_paste_mode\fR +.RS 4 +Toggle treating linebreaks as enter key press\&. +.RE .RE .SH "FILES" .PP diff --git a/doc/toxic.conf.5.asc b/doc/toxic.conf.5.asc index a90e964..376a992 100644 --- a/doc/toxic.conf.5.asc +++ b/doc/toxic.conf.5.asc @@ -201,6 +201,9 @@ OPTIONS *toggle_peerlist*;; Toggle the peer list on and off. + *toggle_paste_mode*;; + Toggle treating linebreaks as enter key press. + FILES ----- diff --git a/misc/toxic.conf.example b/misc/toxic.conf.example index fa2f8b7..a6419d0 100644 --- a/misc/toxic.conf.example +++ b/misc/toxic.conf.example @@ -101,5 +101,6 @@ keys = { peer_list_up="Ctrl+["; peer_list_down="Ctrl+]"; toggle_peerlist="Ctrl+b"; + toggle_paste_mode="Ctrl+T"; }; diff --git a/src/chat.c b/src/chat.c index dbf8949..fd31c19 100644 --- a/src/chat.c +++ b/src/chat.c @@ -882,7 +882,6 @@ static void send_action(ToxWindow *self, ChatContext *ctx, Tox *m, char *action) static void chat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) { - ChatContext *ctx = self->chatwin; StatusBar *statusbar = self->stb; @@ -893,12 +892,15 @@ static void chat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) if (y2 <= 0 || x2 <= 0) return; + if (ctx->pastemode && key == '\r') + key = '\n'; + if (self->help->active) { help_onKey(self, key); return; } - if (ltr) { /* char is printable */ + if (ltr || key == '\n') { /* char is printable */ input_new_char(self, key, x, y, x2, y2); if (ctx->line[0] != '/' && !ctx->self_is_typing && statusbar->connection != TOX_CONNECTION_NONE) @@ -940,38 +942,42 @@ static void chat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) sound_notify(self, notif_error, 0, NULL); } - } else if (key == '\n') { + } else if (key == '\r') { rm_trailing_spaces_buf(ctx); - char line[MAX_STR_SIZE]; - - if (wcs_to_mbs_buf(line, ctx->line, MAX_STR_SIZE) == -1) - memset(&line, 0, sizeof(line)); - - if (!string_is_empty(line)) + if (!wstring_is_empty(ctx->line)) + { add_line_to_hist(ctx); - if (line[0] == '/') { - if (strcmp(line, "/close") == 0) { - kill_chat_window(self, m); - return; - } else if (strncmp(line, "/me ", strlen("/me ")) == 0) { - send_action(self, ctx, m, line + strlen("/me ")); + wstrsubst(ctx->line, L'¶', L'\n'); + + char line[MAX_STR_SIZE] = {0}; + + if (wcs_to_mbs_buf(line, ctx->line, MAX_STR_SIZE) == -1) + memset(&line, 0, sizeof(line)); + + if (line[0] == '/') { + if (strcmp(line, "/close") == 0) { + kill_chat_window(self, m); + return; + } else if (strncmp(line, "/me ", strlen("/me ")) == 0) { + send_action(self, ctx, m, line + strlen("/me ")); + } else { + execute(ctx->history, self, m, line, CHAT_COMMAND_MODE); + } } else { - execute(ctx->history, self, m, line, CHAT_COMMAND_MODE); + char selfname[TOX_MAX_NAME_LENGTH]; + tox_self_get_name(m, (uint8_t *) selfname); + + size_t len = tox_self_get_name_size(m); + selfname[len] = '\0'; + + char timefrmt[TIME_STR_SIZE]; + get_time_str(timefrmt, sizeof(timefrmt)); + + line_info_add(self, timefrmt, selfname, NULL, OUT_MSG, 0, 0, "%s", line); + cqueue_add(ctx->cqueue, line, strlen(line), OUT_MSG, ctx->hst->line_end->id + 1); } - } else if (!string_is_empty(line)) { - char selfname[TOX_MAX_NAME_LENGTH]; - tox_self_get_name(m, (uint8_t *) selfname); - - size_t len = tox_self_get_name_size(m); - selfname[len] = '\0'; - - char timefrmt[TIME_STR_SIZE]; - get_time_str(timefrmt, sizeof(timefrmt)); - - line_info_add(self, timefrmt, selfname, NULL, OUT_MSG, 0, 0, "%s", line); - cqueue_add(ctx->cqueue, line, strlen(line), OUT_MSG, ctx->hst->line_end->id + 1); } wclear(ctx->linewin); diff --git a/src/friendlist.c b/src/friendlist.c index a254750..08b06de 100644 --- a/src/friendlist.c +++ b/src/friendlist.c @@ -757,7 +757,7 @@ static void friendlist_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) return; switch (key) { - case '\n': + case '\r': if (blocklist_view) break; diff --git a/src/global_commands.c b/src/global_commands.c index 1cc2340..29ba6e4 100644 --- a/src/global_commands.c +++ b/src/global_commands.c @@ -36,6 +36,7 @@ #include "avatars.h" #include "name_lookup.h" #include "qr_code.h" +#include "toxic_strings.h" extern char *DATA_FILE; extern ToxWindow *prompt; @@ -513,11 +514,12 @@ void cmd_note(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv)[MA return; } - /* remove opening and closing quotes */ + /* remove opening and closing quotes and replace linebreaks with spaces */ char msg[MAX_STR_SIZE]; snprintf(msg, sizeof(msg), "%s", &argv[1][1]); int len = strlen(msg) - 1; msg[len] = '\0'; + strsubst(msg, '\n', ' '); prompt_update_statusmessage(prompt, m, msg); } diff --git a/src/groupchat.c b/src/groupchat.c index 1e9bcb4..1c07981 100644 --- a/src/groupchat.c +++ b/src/groupchat.c @@ -571,7 +571,10 @@ static void groupchat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) return; } - if (ltr) { /* char is printable */ + if (ctx->pastemode && key == '\r') + key = '\n'; + + if (ltr || key == '\n') { /* char is printable */ input_new_char(self, key, x, y, x2, y2); return; } @@ -615,30 +618,34 @@ static void groupchat_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) } else if (key == user_settings->key_peer_list_up) { if (groupchats[self->num].side_pos > 0) --groupchats[self->num].side_pos; - } else if (key == '\n') { + } else if (key == '\r') { rm_trailing_spaces_buf(ctx); - char line[MAX_STR_SIZE]; - - if (wcs_to_mbs_buf(line, ctx->line, MAX_STR_SIZE) == -1) - memset(&line, 0, sizeof(line)); - - if (!string_is_empty(line)) + if (!wstring_is_empty(ctx->line)) + { add_line_to_hist(ctx); - if (line[0] == '/') { - if (strcmp(line, "/close") == 0) { - close_groupchat(self, m, self->num); - return; - } else if (strncmp(line, "/me ", strlen("/me ")) == 0) { - send_group_action(self, ctx, m, line + strlen("/me ")); + wstrsubst(ctx->line, L'¶', L'\n'); + + char line[MAX_STR_SIZE]; + + if (wcs_to_mbs_buf(line, ctx->line, MAX_STR_SIZE) == -1) + memset(&line, 0, sizeof(line)); + + if (line[0] == '/') { + if (strcmp(line, "/close") == 0) { + close_groupchat(self, m, self->num); + return; + } else if (strncmp(line, "/me ", strlen("/me ")) == 0) { + send_group_action(self, ctx, m, line + strlen("/me ")); + } else { + execute(ctx->history, self, m, line, GROUPCHAT_COMMAND_MODE); + } } else { - execute(ctx->history, self, m, line, GROUPCHAT_COMMAND_MODE); - } - } else if (!string_is_empty(line)) { - if (tox_group_message_send(m, self->num, (uint8_t *) line, strlen(line)) == -1) { - const char *errmsg = " * Failed to send message."; - line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, RED, errmsg); + if (tox_group_message_send(m, self->num, (uint8_t *) line, strlen(line)) == -1) { + const char *errmsg = " * Failed to send message."; + line_info_add(self, NULL, NULL, NULL, SYS_MSG, 0, RED, errmsg); + } } } diff --git a/src/help.c b/src/help.c index d986ee7..86d48b1 100644 --- a/src/help.c +++ b/src/help.c @@ -245,7 +245,9 @@ static void help_draw_keys(ToxWindow *self) wprintw(win, " Ctrl+F and Ctrl+V : Scroll window history half a page\n"); wprintw(win, " Ctrl+H : Move to the bottom of window history\n"); wprintw(win, " Ctrl+[ and Ctrl+] : Scroll peer list in groupchats\n"); - wprintw(win, " Ctrl+B : Toggle the groupchat peerlist\n\n"); + wprintw(win, " Ctrl+B : Toggle the groupchat peerlist\n"); + wprintw(win, " Ctrl+J : Insert new line\n"); + wprintw(win, " Ctrl+T : Toggle paste mode\n\n"); wprintw(win, " (Note: Custom keybindings override these defaults.)\n\n"); help_draw_bottom_menu(win); @@ -335,7 +337,7 @@ void help_onKey(ToxWindow *self, wint_t key) break; case 'k': - help_init_window(self, 13, 80); + help_init_window(self, 15, 80); self->help->type = HELP_KEYS; break; diff --git a/src/input.c b/src/input.c index 4186c4d..649dad9 100644 --- a/src/input.c +++ b/src/input.c @@ -42,9 +42,12 @@ void input_new_char(ToxWindow *self, wint_t key, int x, int y, int mx_x, int mx_ { ChatContext *ctx = self->chatwin; + /* this is the only place we need to do this check */ + if (key == '\n') + key = L'¶'; + int cur_len = wcwidth(key); - /* this is the only place we need to do this check */ if (cur_len == -1) { sound_notify(self, notif_error, 0, NULL); return; @@ -266,15 +269,19 @@ bool input_handle(ToxWindow *self, wint_t key, int x, int y, int mx_x, int mx_y) /* TODO: this special case is ugly. maybe convert entire function to if/else and make them all customizable keys? */ - if (!match && key == user_settings->key_toggle_peerlist) { - if (self->is_groupchat) { - self->show_peerlist ^= 1; - redraw_groupchat_win(self); + if (!match) { + if (key == user_settings->key_toggle_peerlist) { + if (self->is_groupchat) { + self->show_peerlist ^= 1; + redraw_groupchat_win(self); + } + match = true; + } + else if (key == user_settings->key_toggle_pastemode) { + self->chatwin->pastemode ^= 1; + match = true; } - - match = true; } - return match; } diff --git a/src/line_info.c b/src/line_info.c index 6a84282..0ad2c6c 100644 --- a/src/line_info.c +++ b/src/line_info.c @@ -323,17 +323,27 @@ void line_info_print(ToxWindow *self) wprintw(win, "%s %s: ", user_settings->line_normal, line->name1); wattroff(win, COLOR_PAIR(nameclr)); - if (line->msg[0] == '>') - wattron(win, COLOR_PAIR(GREEN)); - else if (line->msg[0] == '<') - wattron(win, COLOR_PAIR(RED)); + char* msg = line->msg; + while (msg) + { + char* line = strsep(&msg, "\n"); - wprintw(win, "%s", line->msg); + if (line[0] == '>') + wattron(win, COLOR_PAIR(GREEN)); + else if (line[0] == '<') + wattron(win, COLOR_PAIR(RED)); - if (line->msg[0] == '>') - wattroff(win, COLOR_PAIR(GREEN)); - else if (line->msg[0] == '<') - wattroff(win, COLOR_PAIR(RED)); + wprintw(win, "%s%c", line, msg ? '\n' : '\0'); + + if (line[0] == '>') + wattroff(win, COLOR_PAIR(GREEN)); + else if (line[0] == '<') + wattroff(win, COLOR_PAIR(RED)); + + // change the \0 set by strsep back to \n + if (msg) + msg[-1] = '\n'; + } if (type == OUT_MSG && timed_out(line->timestamp, NOREAD_FLAG_TIMEOUT)) { wattron(win, COLOR_PAIR(RED)); diff --git a/src/misc_tools.c b/src/misc_tools.c index d86c338..b737a7c 100644 --- a/src/misc_tools.c +++ b/src/misc_tools.c @@ -176,6 +176,15 @@ int string_is_empty(const char *string) return string[0] == '\0'; } +/* Returns 1 if the string is empty, 0 otherwise */ +int wstring_is_empty(const wchar_t *string) +{ + if (!string) + return true; + + return string[0] == L'\0'; +} + /* convert a multibyte string to a wide character string and puts in buf. */ int mbs_to_wcs_buf(wchar_t *buf, const char *string, size_t n) { diff --git a/src/misc_tools.h b/src/misc_tools.h index d49a288..f4875b8 100644 --- a/src/misc_tools.h +++ b/src/misc_tools.h @@ -78,6 +78,9 @@ void update_unix_time(void); /* Returns 1 if the string is empty, 0 otherwise */ int string_is_empty(const char *string); +/* Same as above but for wide character strings */ +int wstring_is_empty(const wchar_t *string); + /* convert a multibyte string to a wide character string (must provide buffer) */ int char_to_wcs_buf(wchar_t *buf, const char *string, size_t n); diff --git a/src/prompt.c b/src/prompt.c index d921a57..4fbc8c6 100644 --- a/src/prompt.c +++ b/src/prompt.c @@ -189,13 +189,16 @@ static void prompt_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) if (x2 <= 0 || y2 <= 0) return; + if (ctx->pastemode && key == '\r') + key = '\n'; + /* ignore non-menu related input if active */ if (self->help->active) { help_onKey(self, key); return; } - if (ltr) { /* char is printable */ + if (ltr || key == '\n') { /* char is printable */ input_new_char(self, key, x, y, x2, y2); return; } @@ -232,19 +235,22 @@ static void prompt_onKey(ToxWindow *self, Tox *m, wint_t key, bool ltr) } else { sound_notify(self, notif_error, 0, NULL); } - } else if (key == '\n') { + } else if (key == '\r') { rm_trailing_spaces_buf(ctx); - char line[MAX_STR_SIZE] = {0}; - - if (wcs_to_mbs_buf(line, ctx->line, MAX_STR_SIZE) == -1) - memset(&line, 0, sizeof(line)); - - if (!string_is_empty(line)) + if (!wstring_is_empty(ctx->line)) + { add_line_to_hist(ctx); + wstrsubst(ctx->line, L'¶', L'\n'); - line_info_add(self, NULL, NULL, NULL, PROMPT, 0, 0, "%s", line); - execute(ctx->history, self, m, line, GLOBAL_COMMAND_MODE); + char line[MAX_STR_SIZE] = {0}; + + if (wcs_to_mbs_buf(line, ctx->line, MAX_STR_SIZE) == -1) + memset(&line, 0, sizeof(line)); + + line_info_add(self, NULL, NULL, NULL, PROMPT, 0, 0, "%s", line); + execute(ctx->history, self, m, line, GLOBAL_COMMAND_MODE); + } wclear(ctx->linewin); wmove(self->window, y2 - CURS_Y_OFFSET, 0); diff --git a/src/settings.c b/src/settings.c index b6d896c..1cbd6e2 100644 --- a/src/settings.c +++ b/src/settings.c @@ -127,6 +127,7 @@ static const struct keys_strings { const char* peer_list_up; const char* peer_list_down; const char* toggle_peerlist; + const char* toggle_pastemode; } key_strings = { "keys", "next_tab", @@ -139,6 +140,7 @@ static const struct keys_strings { "peer_list_up", "peer_list_down", "toggle_peerlist", + "toggle_paste_mode", }; /* defines from toxic.h */ @@ -154,6 +156,7 @@ static void key_defaults(struct user_settings* settings) settings->key_peer_list_up = T_KEY_C_LB; settings->key_peer_list_down = T_KEY_C_RB; settings->key_toggle_peerlist = T_KEY_C_B; + settings->key_toggle_pastemode = T_KEY_C_T; } static const struct tox_strings { @@ -399,6 +402,8 @@ int settings_load(struct user_settings *s, const char *patharg) s->key_peer_list_down = key_parse(&tmp); if (config_setting_lookup_string(setting, key_strings.toggle_peerlist, &tmp)) s->key_toggle_peerlist = key_parse(&tmp); + if (config_setting_lookup_string(setting, key_strings.toggle_pastemode, &tmp)) + s->key_toggle_pastemode = key_parse(&tmp); } #ifdef AUDIO diff --git a/src/settings.h b/src/settings.h index 696eb01..53418af 100644 --- a/src/settings.h +++ b/src/settings.h @@ -68,6 +68,7 @@ struct user_settings { int key_peer_list_up; int key_peer_list_down; int key_toggle_peerlist; + int key_toggle_pastemode; int mplex_away; /* boolean (1 for reaction to terminal attach/detach) */ char mplex_away_note [TOX_MAX_STATUS_MESSAGE_LENGTH]; diff --git a/src/toxic.c b/src/toxic.c index 4fb533a..07a4ed8 100644 --- a/src/toxic.c +++ b/src/toxic.c @@ -200,6 +200,7 @@ static void init_term(void) cbreak(); keypad(stdscr, 1); noecho(); + nonl(); timeout(100); if (has_colors()) { diff --git a/src/toxic.h b/src/toxic.h index 5f7bd88..affa748 100644 --- a/src/toxic.h +++ b/src/toxic.h @@ -70,6 +70,7 @@ #define T_KEY_C_L 0x0C /* ctrl-l */ #define T_KEY_C_W 0x17 /* ctrl-w */ #define T_KEY_C_B 0x02 /* ctrl-b */ +#define T_KEY_C_T 0x14 /* ctrl-t */ #define T_KEY_TAB 0x09 /* TAB key */ #define ONLINE_CHAR "*" diff --git a/src/toxic_strings.c b/src/toxic_strings.c index e2417ba..e17c5bb 100644 --- a/src/toxic_strings.c +++ b/src/toxic_strings.c @@ -166,19 +166,19 @@ void reset_buf(ChatContext *ctx) ctx->start = 0; } -/* Removes trailing spaces from line. */ +/* Removes trailing spaces and newlines from line. */ void rm_trailing_spaces_buf(ChatContext *ctx) { if (ctx->len <= 0) return; - if (ctx->line[ctx->len - 1] != ' ') + if (ctx->line[ctx->len - 1] != ' ' && ctx->line[ctx->len - 1] != L'¶') return; int i; for (i = ctx->len - 1; i >= 0; --i) { - if (ctx->line[i] != ' ') + if (ctx->line[i] != ' ' && ctx->line[i] != L'¶') break; } @@ -242,3 +242,19 @@ void fetch_hist_item(ChatContext *ctx, int key_dir) ctx->pos = h_len; ctx->len = h_len; } + +void strsubst(char* str, char old, char new) +{ + int i; + for (i = 0; str[i] != '\0'; ++i) + if (str[i] == old) + str[i] = new; +} + +void wstrsubst(wchar_t* str, wchar_t old, wchar_t new) +{ + int i; + for (i = 0; str[i] != L'\0'; ++i) + if (str[i] == old) + str[i] = new; +} diff --git a/src/toxic_strings.h b/src/toxic_strings.h index 3c957ef..c9e402c 100644 --- a/src/toxic_strings.h +++ b/src/toxic_strings.h @@ -66,4 +66,8 @@ void add_line_to_hist(ChatContext *ctx); resets line if at end of history */ void fetch_hist_item(ChatContext *ctx, int key_dir); +/* Substitutes all occurrences of old with new. */ +void strsubst(char *str, char old, char new); +void wstrsubst(wchar_t *str, wchar_t old, wchar_t new); + #endif /* #define TOXIC_STRINGS_H */ diff --git a/src/windows.h b/src/windows.h index 8c576a5..c2a0760 100644 --- a/src/windows.h +++ b/src/windows.h @@ -237,6 +237,7 @@ struct ChatContext { #endif uint8_t self_is_typing; + uint8_t pastemode; /* whether to translate \r to \n */ WINDOW *history; WINDOW *linewin;