tui: dedicated MOTD renderer (M7-5)
Some checks failed
CI / build-and-test (macos-latest) (push) Has been cancelled
CI / build-and-test (ubuntu-latest) (push) Has been cancelled
Deploy / test (push) Has been cancelled
Deploy / deploy (push) Has been cancelled

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]
This commit is contained in:
m1ngsama 2026-05-17 13:16:37 +08:00
parent 2610bba76d
commit d3ebe25973
4 changed files with 104 additions and 5 deletions

View file

@ -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;

View file

@ -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);

View file

@ -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 {

View file

@ -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"