AmendHub

Download:

jcs

/

subtext

/

amendments

/

97

user_settings: Add user settings menu

Currently allows changing passwords and renegotiating the terminal

jcs made amendment 97 over 2 years ago
--- ansi.c Mon Feb 21 15:24:00 2022 +++ ansi.c Thu Apr 28 14:03:36 2022 @@ -248,4 +248,50 @@ ansi_strip(char *inp, char **outp) } return olen; -} +} + +void +ansi_probe_screen_size(struct session *s) +{ + short cols, rows; + size_t count = 0; + char c[2] = { 0 }; + + if (!s->vt100) + return; + + /* save cursor position */ + session_clear_input(s); + session_output_string(s, "\33[s"); + session_flush(s); + + /* go to the (hopefully) edge of the screen */ + session_output_string(s, "\33[200B\33[200C"); + session_flush(s); + + /* report cursor position */ + session_clear_input(s); + session_output_string(s, "\33[6n"); + session_flush(s); + + session_wait_for_chars(s, 1500, 100); + + /* \e[12;34R */ + if (s->ibuf[0] == ESC && s->ibuf[1] == '[' && + sscanf((char *)&s->ibuf + 2, "%d;%d%[R]", &rows, &cols, &c) == 3 && + c[0] == 'R') { + if (cols >= MIN_TERMINAL_COLUMNS && rows >= MIN_TERMINAL_LINES) { + s->terminal_columns = cols; + s->terminal_lines = rows; + session_log(s, "Probed terminal size of %dx%d", cols, rows); + } else { + session_log(s, "Bogus terminal size probe response: %dx%d", + cols, rows); + } + } + + /* restore saved cursor position */ + session_clear_input(s); + session_output_string(s, "\33[u"); + session_flush(s); +} --- ansi.h Mon Feb 21 15:24:45 2022 +++ ansi.h Thu Apr 28 13:22:14 2022 @@ -17,6 +17,8 @@ #ifndef __ANSI_H__ #define __ANSI_H__ +#define ESC 0x1b + enum { ANSI_RESET = 1, ANSI_BOLD, @@ -45,5 +47,6 @@ enum { char *ansi(struct session *s, ...); size_t ansi_strip(char *inp, char **outp); +void ansi_probe_screen_size(struct session *s); #endif /* __ANSI_H__ */ --- console.c Thu Apr 21 17:04:17 2022 +++ console.c Thu Apr 28 13:40:14 2022 @@ -18,6 +18,7 @@ #include <string.h> #include "subtext.h" +#include "ansi.h" #include "console.h" #include "focusable.h" #include "session.h" @@ -514,9 +515,9 @@ console_shift_chars(struct console *console, short sta count); if (start % console->ncolumns != 0) - panic("TODO partial console_shift_chars"); + warn("TODO partial console_shift_chars"); if (offset % console->ncolumns != 0) - panic("TODO partial console_shift_chars"); + warn("TODO partial console_shift_chars"); rgn = NewRgn(); mover.top = console->win->portRect.top + CONSOLE_PADDING + @@ -539,6 +540,7 @@ console_parse_csi(struct console *console) short param1 = -1, param2 = -1; short parambuflen; short start, count, offset; + struct session *session; char parambuf[4]; unsigned char c; @@ -567,6 +569,7 @@ console_parse_csi(struct console *console) case 'T': case 'd': case 'g': + case 'n': case 's': case 'u': case '7': @@ -846,11 +849,42 @@ console_parse_csi(struct console *console) case 'n': /* DSR - device status report */ switch (param1) { case 5: /* terminal is ready */ - session_output(console->session, "\33[0n", 4); + session = console->session; + if (session->ibuflen >= sizeof(session->ibuf) - 4) + break; + session->ibuf[session->ibuflen++] = ESC; + session->ibuf[session->ibuflen++] = '['; + session->ibuf[session->ibuflen++] = '0'; + session->ibuf[session->ibuflen++] = 'n'; break; case 6: /* CPR - report cursor position */ - session_printf(console->session, "\33[%d;%dR", - console->cursor_line + 1, console->cursor_column + 1); + session = console->session; + if (session->ibuflen >= sizeof(session->ibuf) - 10) + break; + session->ibuf[session->ibuflen++] = ESC; + session->ibuf[session->ibuflen++] = '['; + + if (console->cursor_line >= 99) + session->ibuf[session->ibuflen++] = '0' + + ((console->cursor_line + 1) / 100); + if (console->cursor_line >= 9) + session->ibuf[session->ibuflen++] = '0' + + (((console->cursor_line + 1) % 100) / 10); + session->ibuf[session->ibuflen++] = '0' + + ((console->cursor_line + 1) % 10); + + session->ibuf[session->ibuflen++] = ';'; + + if (console->cursor_column >= 99) + session->ibuf[session->ibuflen++] = '0' + + ((console->cursor_column + 1) / 100); + if (console->cursor_column >= 9) + session->ibuf[session->ibuflen++] = '0' + + (((console->cursor_column + 1) % 100) / 10); + session->ibuf[session->ibuflen++] = '0' + + ((console->cursor_column + 1) % 10); + + session->ibuf[session->ibuflen++] = 'R'; break; } break; --- mail.c Mon Feb 21 13:50:29 2022 +++ mail.c Tue Apr 26 20:55:15 2022 @@ -75,11 +75,12 @@ mail_free_message_strings(struct private_message *msg) void mail_menu(struct session *s) { - char arg[32]; + char l[2], arg[32]; size_t count, nmsgs, id; bool done = false; char *field; unsigned long *mail_ids = NULL; + short ret; if (!s->user) { session_output_string(s, "Mail is not available to guests.\r\n" @@ -105,15 +106,18 @@ mail_menu(struct session *s) session_output(s, "\r\n", 2); session_flush(s); - if (sscanf(field, "c %s%n", &arg, &count) == 1 && count > 1) + if (sscanf(field, "%[Cc] %s", &l, &arg) == 2) mail_compose(s, arg, NULL, NULL); - else if (strcmp(field, "c") == 0) + else if (sscanf(field, "%[Cc]", &l) == 1 && field[1] == '\0') mail_compose(s, NULL, NULL, NULL); - else if (sscanf(field, "d %ld%n", &id, &count) == 1 && count > 1) + else if (sscanf(field, "%[Dd] %ld%n", &l, &id, &count) == 2 && + field[count] == '\0') mail_delete(s, id); - else if (sscanf(field, "u %ld%n", &id, &count) == 1 && count > 1) + else if (sscanf(field, "%[Uu] %ld%n", &l, &id, &count) == 2 && + field[count] == '\0') mail_mark_unread(s, id); - else if (sscanf(field, "%ld%n", &id, &count) == 1 && count > 1) { + else if (sscanf(field, "%ld%n", &id, &count) == 1 && + field[count] == '\0') { if (id < 1 || id > nmsgs) { session_output_string(s, "Invalid message id\r\n"); session_flush(s); @@ -121,11 +125,11 @@ mail_menu(struct session *s) } mail_read(s, mail_ids[id - 1]); } - else if (strcmp(field, "l") == 0) + else if (sscanf(field, "%[Ll]", &l) == 1 && field[1] == '\0') mail_list(s, false, &mail_ids, &nmsgs); - else if (strcmp(field, "q") == 0 || strcmp(field, "x") == 0) + else if (sscanf(field, "%[QqXx]", &l) == 1 && field[1] == '\0') done = true; - else if (strcmp(field, "?") == 0 || strcmp(field, "h") == 0) + else if (sscanf(field, "%[?Hh]", &l) == 1 && field[1] == '\0') mail_help(s); else if (field[0] != '\0') { session_output_template(s, @@ -141,12 +145,12 @@ void mail_help(struct session *s) { session_output_template(s, - "{{B}}l{{/}}: List mail messages\r\n" + "{{B}}L{{/}}: List mail messages\r\n" "{{B}}#{{/}}: Read mail message [#]\r\n" - "{{B}}d #{{/}}: Delete mail message [#]\r\n" - "{{B}}u #{{/}}: Mark mail message [#] unread\r\n" - "{{B}}c{{/}}: Compose new mail message\r\n" - "{{B}}q{{/}}: Return to main menu\r\n"); + "{{B}}D #{{/}}: Delete mail message [#]\r\n" + "{{B}}U #{{/}}: Mark mail message [#] unread\r\n" + "{{B}}C{{/}}: Compose new mail message\r\n" + "{{B}}Q{{/}}: Return to main menu\r\n"); session_flush(s); } --- session.c Tue Apr 26 15:46:59 2022 +++ session.c Wed Apr 27 17:41:47 2022 @@ -187,8 +187,11 @@ get_another_char: break; case '?': /* short menu */ - if (!session_output_view(s, DB_TEXT_SHORTMENU_ID)) - session_output_string(s, "\r\n[ Short menu missing! ]\r\n\r\n"); + if (!session_output_view(s, DB_TEXT_SHORTMENU_ID)) { + if (!session_output_view(s, DB_TEXT_MENU_ID)) + session_output_string(s, "\r\n[ Short and long menu " + "missing! ]\r\n\r\n"); + } session_flush(s); break; default: @@ -376,32 +379,54 @@ session_output_template(struct session *session, const return size; } -unsigned short -session_input_char(struct session *session) +bool +session_wait_for_chars(struct session *session, unsigned short timeout_ms, + unsigned short num_chars) { - unsigned short ret; - short waiting_for = 1; - short consumed = 0; - + unsigned long expire = 0; + + if (timeout_ms) + expire = Ticks + (timeout_ms / ((double)1000 / (double)60)); + wait_for_char: - while (session->ibuflen < waiting_for) { + while (session->ibuflen < num_chars) { session->node_funcs->input(session); if (session->ending) - return 0; + return false; if (session->abort_input) - return 0; - if (session->ibuflen >= waiting_for) - break; + return false; if (session->obuflen != 0 && session->chatting) - return 0; + return false; + if (expire && Ticks > expire) + return false; if (Time - session->last_input_at > (db->config.max_idle_minutes * 60)) - goto idled_out; + return false; + if (session->ibuflen >= num_chars) + break; uthread_yield(); } session->last_input_at = Time; + return true; +} + +unsigned short +session_input_char(struct session *session) +{ + unsigned short ret; + short waiting_for = 1; + short consumed = 0; +wait_for_char: + if (!session_wait_for_chars(session, 0, waiting_for)) { + if (Time - session->last_input_at > + (db->config.max_idle_minutes * 60)) + goto idled_out; + + return 0; + } + if (session->ibuf[0] == '\33') { /* look for a VT100 escape sequence */ if (session->ibuflen == 1) { @@ -465,6 +490,17 @@ idled_out: session_flush(session); session->ending = 1; return 0; +} + +void +session_clear_input(struct session *session) +{ + session->ibuflen = 0; + + /* + * TODO: provide a node-specific way to do this since nodes might + * have their own input buffers + */ } /* --- session.h Tue Apr 26 15:45:15 2022 +++ session.h Thu Apr 28 14:02:58 2022 @@ -30,6 +30,8 @@ enum session_input_state { #define DEFAULT_TERMINAL_COLUMNS 80 #define DEFAULT_TERMINAL_LINES 24 +#define MIN_TERMINAL_COLUMNS 20 +#define MIN_TERMINAL_LINES 4 #define CONTROL_C 3 #define CONTROL_D 4 @@ -99,7 +101,10 @@ struct session * session_create(char *node, char *via, struct node_funcs *node_funcs); void session_close(struct session *session); void session_idle(struct session *session); +bool session_wait_for_chars(struct session *session, + unsigned short timeout_ms, unsigned short num_chars); unsigned short session_input_char(struct session *session); +void session_clear_input(struct session *session); void session_flush(struct session *session); size_t session_log(struct session *session, const char *format, ...); size_t session_output(struct session *session, const char *str, size_t len); --- user_settings.c Thu Apr 28 14:13:17 2022 +++ user_settings.c Thu Apr 28 14:13:17 2022 @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2022 joshua stein <jcs@jcs.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "subtext.h" +#include "ansi.h" +#include "session.h" +#include "user.h" +#include "user_settings.h" +#include "util.h" + +void user_settings_help(struct session *s); +void user_settings_renegotiate(struct session *s); +void user_settings_password(struct session *s); + +void +user_settings_help(struct session *s) +{ + session_output_template(s, + "{{B}}R{{/}}: Renegotiate terminal\r\n"); + + if (s->user) + session_output_template(s, "{{B}}P{{/}}: Change password\r\n"); + + session_output_template(s, + "{{B}}Q{{/}}: Return to main menu\r\n"); + session_flush(s); +} + +void +user_settings_renegotiate(struct session *s) +{ + unsigned short c; + short cols, lines; + char *tsize; + + session_output_template(s, + "Does this terminal support VT100 escape sequences? [Y/n] "); + session_flush(s); + +try_again: + c = session_input_char(s); + if (s->ending) + return; + if (c == 0 || c > 255) + goto try_again; + if (c == 'n' || c == 'N') { + session_printf(s, "%c\r\n", c == 'N' ? 'N' : 'n'); + session_flush(s); + s->vt100 = false; + } else if (c == '\r' || c == 'Y' || c == 'y') { + session_printf(s, "%c\r\n", c == 'Y' ? 'Y' : 'y'); + session_flush(s); + s->vt100 = true; + } + + if (!s->vt100) { + session_output_template(s, + "Does this terminal support VT52 escape sequences? [Y/n] "); + session_flush(s); + +try_again2: + c = session_input_char(s); + if (s->ending) + return; + if (c == 0 || c > 255) + goto try_again2; + if (c == 'n' || c == 'N') { + session_printf(s, "%c\r\n", c == 'N' ? 'N' : 'n'); + session_flush(s); + s->vt52 = false; + } else if (c == '\r' || c == 'Y' || c == 'y') { + session_printf(s, "%c\r\n", c == 'Y' ? 'Y' : 'y'); + session_flush(s); + s->vt52 = true; + } + } + + if (s->vt100) + ansi_probe_screen_size(s); + +get_terminal_size: + session_printf(s, + "Terminal size (columns x rows) [%dx%d] ", s->terminal_columns, + s->terminal_lines); + session_flush(s); + + tsize = session_field_input(s, 10, 10, NULL, 0); + if (tsize == NULL) + return; + session_output(s, "\r\n", 2); + session_flush(s); + if (tsize[0] == '\0') + return; + + if (sscanf(tsize, "%dx%d", &cols, &lines) != 2) { + session_printf(s, "Invalid response, must be in format %dx%d\r\n", + s->terminal_columns, s->terminal_lines); + session_flush(s); + goto get_terminal_size; + } + + if (cols < MIN_TERMINAL_COLUMNS || lines < MIN_TERMINAL_LINES) { + session_printf(s, "Terminal too small, must be at least " + "%dx%d\r\n", MIN_TERMINAL_COLUMNS, MIN_TERMINAL_LINES); + session_flush(s); + goto get_terminal_size; + } + + s->terminal_columns = cols; + s->terminal_lines = lines; + session_log(s, "Changed terminal size to %dx%d", cols, lines); +} + +void +user_settings_password(struct session *s) +{ + char *password = NULL, *password_confirm = NULL; + + if (!s->user) { + session_output_template(s, "{{B}}Error{{/}}: Guest accounts " + "cannot change passwords\r\n"); + return; + } + + for (;;) { + session_output_template(s, "{{B}}New Password:{{/}} "); + session_flush(s); + password = session_field_input(s, 64, 64, NULL, '*'); + session_output(s, "\r\n", 2); + session_flush(s); + + if (password == NULL || s->ending) + break; + + if (password[0] == '\0') { + session_output_template(s, "{{B}}Error:{{/}} " + "Password cannot be blank\r\n"); + free(password); + continue; + } + + session_output_template(s, "{{B}}New Password (again):{{/}} "); + session_flush(s); + password_confirm = session_field_input(s, 64, 64, NULL, '*'); + session_output(s, "\r\n", 2); + session_flush(s); + + if (password_confirm == NULL || s->ending) + break; + + if (strcmp(password_confirm, password) != 0) { + session_output_template(s, "{{B}}Error:{{/}} " + "Passwords do not match\r\n"); + free(password); + free(password_confirm); + continue; + } + + user_set_password(s->user, password); + user_save(s->user); + session_output_template(s, "{{B}}Your password has been " + "changed{{/}}\r\n"); + break; + } + + if (password) + free(password); + if (password_confirm) + free(password_confirm); +} + +void +user_settings_menu(struct session *s) +{ + bool done = false; + unsigned short c; + + session_log(s, "User settings menu"); + + session_output_template(s, "{{B}}Settings{{/}}\r\n" + "{{B}}--------{{/}}\r\n"); + user_settings_help(s); + + while (!done) { + session_output_template(s, "{{B}}Settings>{{/}} "); + session_flush(s); + +get_another_char: + c = session_input_char(s); + if (s->ending) + break; + if (c == '\r' || c == 0 || c > 255) + goto get_another_char; + + session_printf(s, "%c\r\n", c); + session_flush(s); + + switch (c) { + case 'p': + case 'P': + user_settings_password(s); + break; + case 'q': + case 'Q': + case 'x': + case 'X': + done = true; + break; + case 'r': + case 'R': + user_settings_renegotiate(s); + break; + case '?': + case 'H': + case 'h': + user_settings_help(s); + break; + default: + session_output_string(s, "Invalid option\r\n"); + session_flush(s); + break; + } + } +} --- user_settings.h Fri Apr 22 12:28:20 2022 +++ user_settings.h Fri Apr 22 12:28:20 2022 @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 joshua stein <jcs@jcs.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __USER_SETTINGS_H__ +#define __USER_SETTINGS_H__ + +#include "session.h" +#include "user.h" + +void user_settings_menu(struct session *s); + +#endif