From d3ebe25973e36e20a67ab96235cf4a6501cfc8bf Mon Sep 17 00:00:00 2001 From: m1ngsama Date: Sun, 17 May 2026 13:16:37 +0800 Subject: [PATCH] tui: dedicated MOTD renderer (M7-5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The MOTD used to ride on tui_render_command_output's coattails — text got stuffed into client->command_output prefixed by "=== 公告 / MOTD ===" and rendered under a reverse-video " COMMAND OUTPUT " title bar. Two different things (a transient command result vs. a one-shot service notice) wearing the same chrome. Now MOTD has its own renderer with its own aesthetic: ╭─ 公告 / MOTD ────────────────────────────────╮ 欢迎来到 TNT 公共聊天室。 请互相尊重,不要刷屏。 管理员:m1ng ╰─ 按任意键继续 / press any key ────────────────╯ - title chip embedded in the top border (bright cyan) - footer hint embedded in the bottom border (dim grey) - 2-column left padding on body lines, blank top/bottom pad rows - dim cyan borders, no full-line reverse anywhere Wiring: - new tui_render_motd() declared in tui.h - new client_t.show_motd flag selects the renderer; command_output remains the text storage (no extra buffer needed) - input.c MOTD path sets show_motd = true and calls tui_render_motd() - handle_key's dismiss path clears show_motd alongside command_output - main-loop redraw dispatch checks show_motd before command_output[0] --- include/ssh_server.h | 1 + include/tui.h | 5 +++ src/input.c | 13 +++++-- src/tui.c | 90 +++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 104 insertions(+), 5 deletions(-) diff --git a/include/ssh_server.h b/include/ssh_server.h index a9cb80c..f13d97e 100644 --- a/include/ssh_server.h +++ b/include/ssh_server.h @@ -25,6 +25,7 @@ typedef struct client { int command_history_count; int command_history_pos; char command_output[2048]; + bool show_motd; /* command_output holds MOTD text */ char exec_command[MAX_EXEC_COMMAND_LEN]; char ssh_login[MAX_USERNAME_LEN]; time_t connect_time; diff --git a/include/tui.h b/include/tui.h index 592c88f..b656971 100644 --- a/include/tui.h +++ b/include/tui.h @@ -16,6 +16,11 @@ void tui_render_help(struct client *client); /* Render the command output screen */ void tui_render_command_output(struct client *client); +/* Render the MOTD screen. Reads the message text from + * client->command_output (shared storage); the show_motd flag selects + * this renderer over tui_render_command_output. */ +void tui_render_motd(struct client *client); + /* Render the input line */ void tui_render_input(struct client *client, const char *input); diff --git a/src/input.c b/src/input.c index 11720e4..fda2c98 100644 --- a/src/input.c +++ b/src/input.c @@ -194,9 +194,10 @@ static bool handle_key(client_t *client, unsigned char key, char *input) { return true; /* Key consumed */ } - /* Handle command output display */ + /* Handle command output / MOTD display: any key dismisses */ if (client->command_output[0] != '\0') { client->command_output[0] = '\0'; + client->show_motd = false; client->mode = MODE_NORMAL; tui_render_screen(client); return true; /* Key consumed */ @@ -447,9 +448,11 @@ void input_run_session(client_t *client) { fclose(motd_fp); if (motd_len > 0) { motd_buf[motd_len] = '\0'; - snprintf(client->command_output, sizeof(client->command_output), - "=== 公告 / MOTD ===\n%s", motd_buf); - tui_render_command_output(client); + snprintf(client->command_output, + sizeof(client->command_output), + "%s", motd_buf); + client->show_motd = true; + tui_render_motd(client); seen_update_seq = room_get_update_seq(g_room); goto main_loop; } @@ -491,6 +494,8 @@ main_loop: if (client->show_help) { tui_render_help(client); + } else if (client->show_motd) { + tui_render_motd(client); } else if (client->command_output[0] != '\0') { tui_render_command_output(client); } else { diff --git a/src/tui.c b/src/tui.c index 0d3cb3a..ea56468 100644 --- a/src/tui.c +++ b/src/tui.c @@ -543,7 +543,95 @@ void tui_render_command_output(client_t *client) { client_send(client, buffer, pos); } -/* Get help text based on language */ +/* Render the MOTD screen. + * + * A framed banner with a title chip embedded in the top border and an + * "any key to continue" hint embedded in the bottom border, MOTD body + * left-padded inside. Dismissed by handle_key like any other modal + * (sets command_output[0]='\0' and show_motd=false). + * + * Lighter aesthetic than tui_render_command_output: no full-line reverse, + * dim borders, two blank lines of breathing room above and below the + * body so the announcement reads as a notice rather than a console dump. */ +void tui_render_motd(client_t *client) { + if (!client || !client->connected) return; + + int rw = client->width; + int rh = client->height; + if (rw < 10) rw = 10; + if (rh < 4) rh = 4; + + char buffer[4096]; + size_t pos = 0; + buffer_appendf(buffer, sizeof(buffer), &pos, ANSI_CLEAR ANSI_HOME); + + /* Top border: ╭─ 公告 / MOTD ──...──╮ */ + const char *title = " 公告 / MOTD "; + int title_w = utf8_string_width(title); + int top_dash_fill = rw - 2 - title_w - 1; /* 2 corners, 1 leading ─ */ + if (top_dash_fill < 0) top_dash_fill = 0; + + buffer_appendf(buffer, sizeof(buffer), &pos, "\033[2;36m╭─"); + buffer_appendf(buffer, sizeof(buffer), &pos, "\033[0;1;36m%s\033[2;36m", title); + for (int i = 0; i < top_dash_fill; i++) { + buffer_append_bytes(buffer, sizeof(buffer), &pos, "─", strlen("─")); + } + buffer_appendf(buffer, sizeof(buffer), &pos, "╮\033[0m\r\n"); + + /* Top breathing-room line */ + buffer_appendf(buffer, sizeof(buffer), &pos, "\r\n"); + + /* Body lines (left-pad 2 cols, truncate to inner width) */ + char body_copy[2048]; + strncpy(body_copy, client->command_output, sizeof(body_copy) - 1); + body_copy[sizeof(body_copy) - 1] = '\0'; + + int body_lines = 0; + int max_body_lines = rh - 4; /* top border + top pad + bottom pad + bottom border */ + if (max_body_lines < 1) max_body_lines = 1; + + char *line = strtok(body_copy, "\n"); + while (line && body_lines < max_body_lines) { + char truncated[1024]; + strncpy(truncated, line, sizeof(truncated) - 1); + truncated[sizeof(truncated) - 1] = '\0'; + + int avail = rw - 4; /* 2 cols padding each side */ + if (avail < 4) avail = 4; + if (utf8_string_width(truncated) > avail) { + utf8_truncate(truncated, avail); + } + buffer_appendf(buffer, sizeof(buffer), &pos, " %s\r\n", truncated); + body_lines++; + line = strtok(NULL, "\n"); + } + + /* Fill empty space up to the bottom border */ + int used_rows = 1 /*top*/ + 1 /*pad*/ + body_lines + 1 /*pad*/ + 1 /*bottom*/; + int filler_rows = rh - used_rows; + if (filler_rows < 0) filler_rows = 0; + for (int i = 0; i < filler_rows; i++) { + buffer_appendf(buffer, sizeof(buffer), &pos, "\r\n"); + } + + /* Bottom breathing-room line */ + buffer_appendf(buffer, sizeof(buffer), &pos, "\r\n"); + + /* Bottom border: ╰─ 按任意键继续 ─...─╯ */ + const char *footer = " 按任意键继续 / press any key "; + int footer_w = utf8_string_width(footer); + int bot_dash_fill = rw - 2 - footer_w - 1; + if (bot_dash_fill < 0) bot_dash_fill = 0; + + buffer_appendf(buffer, sizeof(buffer), &pos, "\033[2;36m╰─"); + buffer_appendf(buffer, sizeof(buffer), &pos, "\033[0;2;37m%s\033[2;36m", footer); + for (int i = 0; i < bot_dash_fill; i++) { + buffer_append_bytes(buffer, sizeof(buffer), &pos, "─", strlen("─")); + } + buffer_appendf(buffer, sizeof(buffer), &pos, "╯\033[0m"); + + client_send(client, buffer, pos); +} const char* tui_get_help_text(help_lang_t lang) { if (lang == LANG_EN) { return "TERMINAL CHAT ROOM - HELP\n"