--- lbbs/src/screen.c 2025/07/02 06:25:02 1.105 +++ lbbs/src/screen.c 2025/12/18 10:43:48 1.136 @@ -1,18 +1,14 @@ -/*************************************************************************** - screen.c - description - ------------------- - Copyright : (C) 2004-2025 by Leaflet - Email : leaflet@leafok.com - ***************************************************************************/ - -/*************************************************************************** - * * - * This program 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. * - * * - ***************************************************************************/ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * screen + * - advanced telnet-based user interactive input / output features + * + * Copyright (C) 2004-2025 Leaflet + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif #include "bbs.h" #include "common.h" @@ -29,16 +25,30 @@ #include #include #include +#include #include #include -#include #include -#define ACTIVE_BOARD_HEIGHT 8 +const char CTRL_SEQ_CLR_LINE[] = "\033[K"; -#define STR_TOP_LEFT_MAX_LEN 80 -#define STR_TOP_MIDDLE_MAX_LEN 40 -#define STR_TOP_RIGHT_MAX_LEN 40 +static const int ACTIVE_BOARD_HEIGHT = 8; + +static const int STR_TOP_LEFT_MAX_LEN = 80; +static const int STR_TOP_MIDDLE_MAX_LEN = 40; +static const int STR_TOP_RIGHT_MAX_LEN = 80; + +static size_t get_time_str(char *s, size_t len) +{ + time_t curtime; + struct tm local_tm; + + time(&curtime); + localtime_r(&curtime, &local_tm); + size_t j = strftime(s, len, "%m/%d %H:%M %Z", &local_tm); + + return j; +} void moveto(int row, int col) { @@ -78,18 +88,35 @@ void clrtobot(int line_begin) void clearscr() { prints("\033[2J"); - moveto(0, 0); + moveto(1, 1); +} + +inline int press_any_key() +{ + return press_any_key_ex(" \033[1;33m按任意键继续...\033[m", 60); } -int press_any_key() +int press_any_key_ex(const char *msg, int sec) { + int ch = 0; + int duration = 0; + time_t t_begin = time(NULL); + moveto(SCREEN_ROWS, 0); clrtoeol(); - prints(" \033[1;33m按任意键继续...\033[0;37m"); + prints(msg); iflush(); - return igetch_t(MIN(MAX_DELAY_TIME, 60)); + igetch_reset(); + + do + { + ch = igetch_t(sec - duration); + duration = (int)(time(NULL) - t_begin); + } while (!SYS_server_exit && ch == 0 && duration < 60); + + return ch; } void set_input_echo(int echo) @@ -97,26 +124,24 @@ void set_input_echo(int echo) if (echo) { outc('\x83'); // ASCII code 131 - iflush(); } else { - // outc ('\x85'); // ASCII code 133 + // outc ('\x85'); // ASCII code 133 prints("\xff\xfb\x01\xff\xfb\x03"); - iflush(); - igetch(0); - igetch_reset(); } + iflush(); } -static int _str_input(char *buffer, int buf_size, int max_display_len, int echo_mode) +static int _str_input(char *buffer, int buf_size, int max_display_len, enum io_echo_t echo_mode) { int ch; int offset = 0; int eol; int display_len; - char input_str[4]; + char input_str[5]; int str_len = 0; + wchar_t wcs[2]; char c; buffer[buf_size - 1] = '\0'; @@ -126,11 +151,10 @@ static int _str_input(char *buffer, int while (!SYS_server_exit) { - ch = igetch_t(MIN(MAX_DELAY_TIME, 60)); + ch = igetch_t(MIN(BBS_max_user_idle_time, 60)); if (ch == CR) { - igetch_reset(); break; } else if (ch == KEY_TIMEOUT || ch == KEY_NULL) // timeout or broken pipe @@ -148,7 +172,7 @@ static int _str_input(char *buffer, int offset--; if (buffer[offset] < 0 || buffer[offset] > 127) // UTF8 { - while (offset > 0 && (buffer[offset] & 0b11000000) != 0b11000000) + while (offset > 0 && (buffer[offset] & 0xc0) != 0xc0) { offset--; } @@ -169,14 +193,14 @@ static int _str_input(char *buffer, int else if ((ch & 0xff80) == 0x80) // head of multi-byte character { str_len = 0; - c = (char)(ch & 0b11110000); - while (c & 0b10000000) + c = (char)(ch & 0xf0); + while (c & 0x80) { input_str[str_len] = (char)(ch - 256); str_len++; - c = (c & 0b01111111) << 1; + c = (c & 0x7f) << 1; - if ((c & 0b10000000) == 0) // Input completed + if ((c & 0x80) == 0) // Input completed { break; } @@ -185,18 +209,23 @@ static int _str_input(char *buffer, int ch = igetch(100); // 0.1 second if (ch == KEY_NULL || ch == KEY_TIMEOUT) // Ignore received bytes if no futher input { - log_error("Ignore %d bytes of incomplete UTF8 character\n", str_len); + log_debug("Ignore %d bytes of incomplete UTF8 character\n", str_len); str_len = 0; break; } } + input_str[str_len] = '\0'; if (str_len == 0) // Incomplete input { continue; } - if (offset + str_len > buf_size - 1 || display_len + 2 > max_display_len) // No enough space for Chinese character + if (mbstowcs(wcs, input_str, 1) == (size_t)-1) + { + log_error("mbstowcs() error\n"); + } + if (offset + str_len > buf_size - 1 || display_len + (UTF8_fixed_width ? 2 : wcwidth(wcs[0])) > max_display_len) // No enough space for Chinese character { outc('\a'); iflush(); @@ -252,7 +281,7 @@ static int _str_input(char *buffer, int return offset; } -int str_input(char *buffer, int buf_size, int echo_mode) +int str_input(char *buffer, int buf_size, enum io_echo_t echo_mode) { int len; @@ -264,18 +293,318 @@ int str_input(char *buffer, int buf_size iflush(); return len; -}; +} -int get_data(int row, int col, char *prompt, char *buffer, int buf_size, int max_display_len, int echo_mode) +int get_data(int row, int col, char *prompt, char *buffer, int buf_size, int max_display_len) { - int len; + int len = 0; + int col_cur = 0; + int ch; + int offset = 0; + int eol; + int display_len; + char input_str[5]; + int str_len = 0; + wchar_t wcs[2]; + int wc_len; + char c; + + buffer[buf_size - 1] = '\0'; + offset = split_line(buffer, max_display_len, &eol, &display_len, 0); + buffer[offset] = '\0'; + len = offset; + col_cur = col + str_length(prompt, 1) + display_len; moveto(row, col); prints("%s", prompt); prints("%s", buffer); + prints("%*s", max_display_len - display_len, ""); + moveto(row, col_cur); iflush(); - len = _str_input(buffer, buf_size, max_display_len, echo_mode); + igetch_reset(); + + while (!SYS_server_exit) + { + ch = igetch_t(MIN(BBS_max_user_idle_time, 60)); + + if (ch == CR) + { + break; + } + else if (ch == KEY_TIMEOUT || ch == KEY_NULL) // timeout or broken pipe + { + return -1; + } + else if (ch == LF || ch == '\0') + { + continue; + } + else if (ch == BACKSPACE) + { + if (offset > 0) + { + str_len = 1; + offset--; + if (buffer[offset] < 0 || buffer[offset] > 127) // UTF8 + { + while (offset > 0 && (buffer[offset] & 0xc0) != 0xc0) + { + str_len++; + offset--; + } + + if (mbstowcs(wcs, buffer + offset, 1) == (size_t)-1) + { + log_error("mbstowcs() error\n"); + } + wc_len = (UTF8_fixed_width ? 2 : wcwidth(wcs[0])); + + if (wc_len == 2) + { + display_len--; + col_cur--; + } + } + + memmove(buffer + offset, buffer + offset + str_len, (size_t)(len - offset - str_len)); + len -= str_len; + buffer[len] = '\0'; + display_len--; + col_cur--; + + moveto(row, col_cur); + prints("%s", buffer + offset); + prints("%*s", max_display_len - display_len, ""); + moveto(row, col_cur); + iflush(); + } + continue; + } + else if (ch == KEY_DEL) + { + if (offset < len) + { + if ((buffer[offset] & 0x80) == 0x80) // head of multi-byte character + { + str_len = 0; + c = (char)(buffer[offset] & 0xf0); + while (c & 0x80) + { + str_len++; + c = (c & 0x7f) << 1; + } + display_len--; + } + else + { + str_len = 1; + } + + memmove(buffer + offset, buffer + offset + str_len, (size_t)(len - offset - str_len)); + len -= str_len; + buffer[len] = '\0'; + display_len--; + + moveto(row, col_cur); + prints("%s", buffer + offset); + prints("%*s", max_display_len - display_len, ""); + moveto(row, col_cur); + iflush(); + } + continue; + } + else if (ch == KEY_LEFT) + { + if (offset > 0) + { + str_len = 1; + offset--; + if (buffer[offset] < 0 || buffer[offset] > 127) // UTF8 + { + while (offset > 0 && (buffer[offset] & 0xc0) != 0xc0) + { + str_len++; + offset--; + } + + if (mbstowcs(wcs, buffer + offset, 1) == (size_t)-1) + { + log_error("mbstowcs() error\n"); + } + wc_len = (UTF8_fixed_width ? 2 : wcwidth(wcs[0])); + + if (wc_len == 2) + { + col_cur--; + } + } + col_cur--; + + moveto(row, col_cur); + iflush(); + } + continue; + } + else if (ch == KEY_RIGHT) + { + if (offset < len) + { + str_len = 0; + if ((buffer[offset] & 0x80) == 0x80) // head of multi-byte character + { + c = (char)(buffer[offset] & 0xf0); + while (c & 0x80) + { + str_len++; + c = (c & 0x7f) << 1; + } + + if (mbstowcs(wcs, buffer + offset, 1) == (size_t)-1) + { + log_error("mbstowcs() error\n"); + } + wc_len = (UTF8_fixed_width ? 2 : wcwidth(wcs[0])); + + if (wc_len == 2) + { + col_cur++; + } + } + else + { + str_len = 1; + } + + col_cur++; + offset += str_len; + + moveto(row, col_cur); + iflush(); + } + continue; + } + else if (ch == KEY_HOME || ch == KEY_UP) + { + if (offset > 0) + { + offset = 0; + col_cur = col + str_length(prompt, 1); + + moveto(row, col_cur); + iflush(); + } + continue; + } + else if (ch == KEY_END || ch == KEY_DOWN) + { + if (offset < len) + { + offset = len; + col_cur = col + str_length(prompt, 1) + display_len; + + moveto(row, col_cur); + iflush(); + } + continue; + } + else if (ch > 255 || iscntrl(ch)) + { + continue; + } + else if ((ch & 0xff80) == 0x80) // head of multi-byte character + { + str_len = 0; + c = (char)(ch & 0xf0); + while (c & 0x80) + { + input_str[str_len] = (char)(ch - 256); + str_len++; + c = (c & 0x7f) << 1; + + if ((c & 0x80) == 0) // Input completed + { + break; + } + + // Expect additional bytes of input + ch = igetch(100); // 0.1 second + if (ch == KEY_NULL || ch == KEY_TIMEOUT) // Ignore received bytes if no futher input + { + log_debug("Ignore %d bytes of incomplete UTF8 character\n", str_len); + str_len = 0; + break; + } + } + input_str[str_len] = '\0'; + + if (str_len == 0) // Incomplete input + { + continue; + } + + if (mbstowcs(wcs, input_str, 1) == (size_t)-1) + { + log_error("mbstowcs() error\n"); + } + wc_len = (UTF8_fixed_width ? 2 : wcwidth(wcs[0])); + + if (len + str_len > buf_size - 1 || + display_len + wc_len > max_display_len) // No enough space for Chinese character + { + outc('\a'); + iflush(); + continue; + } + + memmove(buffer + offset + str_len, buffer + offset, (size_t)(len - offset)); + memcpy(buffer + offset, input_str, (size_t)str_len); + len += str_len; + buffer[len] = '\0'; + display_len += wc_len; + + moveto(row, col_cur); + prints("%s", buffer + offset); + prints("%*s", max_display_len - display_len, ""); + + col_cur += wc_len; + + moveto(row, col_cur); + iflush(); + + offset += str_len; + } + else if (ch >= 32 && ch < 127) // Printable character + { + if (len + 1 > buf_size - 1 || display_len + 1 > max_display_len) + { + outc('\a'); + iflush(); + continue; + } + + memmove(buffer + offset + 1, buffer + offset, (size_t)(len - offset)); + buffer[offset] = (char)ch; + len++; + buffer[len] = '\0'; + display_len++; + + moveto(row, col_cur); + prints("%s", buffer + offset); + prints("%*s", max_display_len - display_len, ""); + + col_cur++; + + moveto(row, col_cur); + iflush(); + + offset++; + } + else // Invalid character + { + continue; + } + } return len; } @@ -309,7 +638,7 @@ int display_data(const void *p_data, lon loop = 1; while (!SYS_server_exit && loop) { - if (eof_exit > 0 && line_current >= display_line_total && display_line_total <= screen_row_total) + if (eof_exit > 0 && line_current >= display_line_total) { if (eof_exit == 1) { @@ -364,9 +693,20 @@ int display_data(const void *p_data, lon input_ok = 0; while (!SYS_server_exit && !input_ok) { - ch = igetch_t(MAX_DELAY_TIME); + ch = igetch_t(BBS_max_user_idle_time); input_ok = 1; + if (ch != KEY_NULL && ch != KEY_TIMEOUT) + { + BBS_last_access_tm = time(NULL); + + // Refresh current action + if (user_online_update(NULL) < 0) + { + log_error("user_online_update(NULL) error\n"); + } + } + // extended key handler if (key_handler(&ch, &ctx) != 0) { @@ -376,7 +716,10 @@ int display_data(const void *p_data, lon switch (ch) { case KEY_NULL: + log_debug("KEY_NULL\n"); + goto cleanup; case KEY_TIMEOUT: + log_debug("User input timeout\n"); goto cleanup; case KEY_HOME: if (line_current - output_current_row < 0) // Reach begin @@ -410,8 +753,6 @@ int display_data(const void *p_data, lon output_end_row = SCREEN_ROWS - 1; // Legacy Fterm only works with this line break; case CR: - igetch_reset(); - case KEY_SPACE: case KEY_DOWN: if (line_current + (screen_row_total - (output_current_row - screen_begin_row)) >= display_line_total) // Reach end { @@ -439,6 +780,7 @@ int display_data(const void *p_data, lon output_end_row = SCREEN_ROWS - 1; clrline(output_current_row, SCREEN_ROWS); break; + case KEY_SPACE: case KEY_PGDN: if (line_current + screen_row_total - (output_current_row - screen_begin_row) >= display_line_total) // Reach end { @@ -479,8 +821,6 @@ int display_data(const void *p_data, lon input_ok = 0; break; } - - BBS_last_access_tm = time(NULL); } continue; @@ -514,7 +854,7 @@ cleanup: return ch; } -static int display_file_key_handler(int *p_key, DISPLAY_CTX *p_ctx) +int display_file_key_handler(int *p_key, DISPLAY_CTX *p_ctx) { switch (*p_key) { @@ -532,7 +872,7 @@ static int display_file_key_handler(int int display_file(const char *filename, int eof_exit) { int ret; - const void *p_shm; + void *p_shm; size_t data_len; long line_total; const void *p_data; @@ -597,24 +937,26 @@ int show_top(const char *str_left, const int show_bottom(const char *msg) { char str_time[LINE_BUFFER_LEN]; + int len_str_time; time_t time_online; struct tm *tm_online; char msg_f[LINE_BUFFER_LEN]; int eol; - int msg_len; + int len_msg; int len; int len_username; char str_tm_online[LINE_BUFFER_LEN]; + int len_str_tm_online; - get_time_str(str_time, sizeof(str_time)); + len_str_time = (int)get_time_str(str_time, sizeof(str_time)); msg_f[0] = '\0'; - msg_len = 0; + len_msg = 0; if (msg != NULL) { strncpy(msg_f, msg, sizeof(msg_f) - 1); msg_f[sizeof(msg_f) - 1] = '\0'; - len = split_line(msg_f, 23, &eol, &msg_len, 1); + len = split_line(msg_f, 23, &eol, &len_msg, 1); msg_f[len] = '\0'; } @@ -625,20 +967,22 @@ int show_bottom(const char *msg) if (tm_online->tm_mday > 1) { snprintf(str_tm_online, sizeof(str_tm_online), - "\033[36m%2d\033[33m天\033[36m%2d\033[33m时", - tm_online->tm_mday - 1, tm_online->tm_hour); + "\033[36m%d\033[33md \033[36m%d\033[33m:\033[36m%.2d", + tm_online->tm_mday - 1, tm_online->tm_hour, tm_online->tm_min); } else { snprintf(str_tm_online, sizeof(str_tm_online), - "\033[36m%2d\033[33m时\033[36m%2d\033[33m分", + "\033[36m%d\033[33m:\033[36m%.2d", tm_online->tm_hour, tm_online->tm_min); } + len_str_tm_online = str_length(str_tm_online, 1); moveto(SCREEN_ROWS, 0); clrtoeol(); - prints("\033[1;44;33m时间[\033[36m%s\033[33m]%s%*s \033[33m帐号[\033[36m%s\033[33m][%s\033[33m]\033[m", - str_time, msg_f, 38 - msg_len - len_username, "", BBS_username, str_tm_online); + prints("\033[1;44;33m时间[\033[36m%s\033[33m]%s%*s \033[33m用户[\033[36m%s\033[33m][%s\033[33m]\033[m", + str_time, msg_f, 65 - len_str_time - len_msg - len_username - len_str_tm_online, + "", BBS_username, str_tm_online); return 0; }