diff --git a/termtcp.c b/termtcp.c index f30680e..78ad052 100644 --- a/termtcp.c +++ b/termtcp.c @@ -54,9 +54,10 @@ #define CP_YELLOW 4 #define CP_CYAN 5 #define CP_MAGENTA 6 -#define CP_STATUS 7 /* black on cyan */ -#define CP_INPUT 8 /* white on blue */ -#define CP_SEP 9 /* cyan on black */ +#define CP_STATUS 7 /* white on blue - status bar */ +#define CP_INPUT 8 /* black on white - input area */ +#define CP_SEP 9 /* cyan on black - separators */ +#define CP_FKEY 10 /* blue on white - fkey chip label */ /* ── Host config ─────────────────────────────────────────── */ typedef struct { @@ -141,10 +142,11 @@ static char hist_save[INPUT_MAX]; /* saved input while browsing */ /* ── ncurses windows ─────────────────────────────────────── */ WINDOW *status_win = NULL; WINDOW *out_win = NULL; -WINDOW *sep_win = NULL; /* "----- Monitor -----" between output and monitor */ +WINDOW *sep_win = NULL; /* separator between monitor and output panes */ WINDOW *mon_win = NULL; -WINDOW *sep2_win = NULL; /* "----- Input -----" above input area */ +WINDOW *sep2_win = NULL; /* separator above input area */ WINDOW *input_win = NULL; +WINDOW *fkey_win = NULL; /* function key hint bar at bottom */ /* ── Input buffer ────────────────────────────────────────── */ char input_buf[INPUT_MAX]; @@ -684,9 +686,10 @@ void init_colors(void) init_pair(CP_YELLOW, COLOR_YELLOW, COLOR_BLACK); init_pair(CP_CYAN, COLOR_CYAN, COLOR_BLACK); init_pair(CP_MAGENTA, COLOR_MAGENTA, COLOR_BLACK); - init_pair(CP_STATUS, COLOR_BLACK, COLOR_CYAN); + init_pair(CP_STATUS, COLOR_WHITE, COLOR_BLUE); init_pair(CP_INPUT, COLOR_BLACK, COLOR_WHITE); init_pair(CP_SEP, COLOR_CYAN, COLOR_BLACK); + init_pair(CP_FKEY, COLOR_BLUE, COLOR_WHITE); } @@ -734,10 +737,11 @@ void create_windows(void) if (mon_win) { delwin(mon_win); mon_win = NULL; } if (sep2_win) { delwin(sep2_win); sep2_win = NULL; } if (input_win) { delwin(input_win); input_win = NULL; } + if (fkey_win) { delwin(fkey_win); fkey_win = NULL; } - /* overhead = status(1) + sep2(1) + input(INPUT_OUTER) - * + sep(1) if monitor visible */ - int overhead = 1 + 1 + INPUT_OUTER + (mon_visible ? 1 : 0); + /* overhead = status(1) + sep2(1) + input(INPUT_OUTER) + fkey(1) + * + sep(1) if monitor visible */ + int overhead = 1 + 1 + INPUT_OUTER + 1 + (mon_visible ? 1 : 0); int content_rows = LINES - overhead; if (content_rows < 4) content_rows = 4; @@ -789,33 +793,83 @@ void create_windows(void) leaveok(input_win, FALSE); keypad(input_win, TRUE); nodelay(input_win, TRUE); + row += INPUT_OUTER; + + /* Function key hint bar */ + fkey_win = newwin(1, COLS, row, 0); + wbkgd(fkey_win, COLOR_PAIR(CP_STATUS)); + leaveok(fkey_win, TRUE); } void draw_status(void) { if (!status_win) return; werase(status_win); - wattron(status_win, COLOR_PAIR(CP_STATUS) | A_BOLD); + wbkgd(status_win, COLOR_PAIR(CP_STATUS)); + wattron(status_win, COLOR_PAIR(CP_STATUS)); + + /* Left: program name */ + mvwprintw(status_win, 0, 1, "TermTCP %s", VERSION); + /* Center: connection info */ + char center[80]; if (connected && current_host >= 0) { int elapsed = (int)(time(NULL) - connect_time); - mvwprintw(status_win, 0, 0, - " TermTCP v%s | %s | %02d:%02d | Mon:%s%s | Ctrl+A=Menu", - VERSION, - hosts[current_host].name, - elapsed / 60, elapsed % 60, - mon_visible ? "ON" : "OFF", - logging ? " | LOG" : ""); + snprintf(center, sizeof(center), "%s [%02d:%02d]", + hosts[current_host].name, elapsed / 60, elapsed % 60); } else { - mvwprintw(status_win, 0, 0, - " TermTCP v%s | Not connected | Mon:%s | Ctrl+A=Menu", - VERSION, mon_visible ? "ON" : "OFF"); + snprintf(center, sizeof(center), "Not Connected"); } - - wattroff(status_win, COLOR_PAIR(CP_STATUS) | A_BOLD); + int cx = (COLS - (int)strlen(center)) / 2; + if (cx > 0) mvwprintw(status_win, 0, cx, "%s", center); + + /* Right: UTC time + log indicator */ + time_t now = time(NULL); + struct tm *t = gmtime(&now); + char right[32]; + snprintf(right, sizeof(right), "%s%02d:%02d UTC ", + logging ? "LOG " : "", t->tm_hour, t->tm_min); + int rx = COLS - (int)strlen(right); + if (rx > 0) mvwprintw(status_win, 0, rx, "%s", right); + + wattroff(status_win, COLOR_PAIR(CP_STATUS)); wrefresh(status_win); } +static void draw_fkeys(void) +{ + if (!fkey_win) return; + werase(fkey_win); + + static const struct { const char *key; const char *desc; } fkeys[] = { + { "F1", "Help" }, + { "F3", "Connect" }, + { "F4", "Disconnect" }, + { "F5", "Forms" }, + { "F6", "Log" }, + { "F10", "Quit" }, + }; + static const int nfkeys = 6; + + int col = 0; + for (int i = 0; i < nfkeys && col < COLS - 2; i++) { + wattron(fkey_win, COLOR_PAIR(CP_FKEY) | A_BOLD); + mvwprintw(fkey_win, 0, col, "%s", fkeys[i].key); + col += (int)strlen(fkeys[i].key); + wattroff(fkey_win, COLOR_PAIR(CP_FKEY) | A_BOLD); + wattron(fkey_win, COLOR_PAIR(CP_STATUS)); + mvwprintw(fkey_win, 0, col, " %-*s", (int)strlen(fkeys[i].desc) + 1, fkeys[i].desc); + col += (int)strlen(fkeys[i].desc) + 2; + wattroff(fkey_win, COLOR_PAIR(CP_STATUS)); + } + /* Fill remainder of bar */ + wattron(fkey_win, COLOR_PAIR(CP_STATUS)); + for (int c = col; c < COLS; c++) mvwaddch(fkey_win, 0, c, ' '); + wattroff(fkey_win, COLOR_PAIR(CP_STATUS)); + + wrefresh(fkey_win); +} + /* Render stored lines from a ring buffer into an ncurses window. * scroll_off=0 shows the most recent lines (live); >0 scrolls back. */ static void scrl_draw_window(WINDOW *win, ScrlLine *buf, int count, int head, @@ -1037,6 +1091,7 @@ static void refresh_all(void) scrl_redraw_out(); scrl_redraw_mon(); draw_input(); + draw_fkeys(); } /* Full background repaint using redrawwin() — safe alternative to @@ -1049,6 +1104,7 @@ static void force_repaint(void) if (mon_win) redrawwin(mon_win); if (sep2_win) redrawwin(sep2_win); if (input_win) redrawwin(input_win); + if (fkey_win) redrawwin(fkey_win); refresh_all(); } @@ -2756,6 +2812,50 @@ static void handle_key(int ch) ctrl_a_menu(); return; + case KEY_F(1): + show_help(); + return; + + case KEY_F(3): { + int idx = pick_host("Select Host"); + if (idx >= 0) do_connect(idx); + return; + } + + case KEY_F(4): + if (connected) { + tcp_disconnect(); + append_out("*** Disconnected\n", 17, COLOR_PAIR(CP_RED)); + draw_status(); + } + return; + + case KEY_F(5): + forms_menu(); + return; + + case KEY_F(6): + if (logging) { + close_log(); + append_out("[Logging OFF]\n", 14, COLOR_PAIR(CP_YELLOW)); + } else if (connected && current_host >= 0) { + char prefix[120]; + snprintf(prefix, sizeof(prefix), "%s_%s", + hosts[current_host].host, hosts[current_host].port); + open_log(prefix); + append_out("[Logging ON]\n", 13, COLOR_PAIR(CP_YELLOW)); + } else { + append_out("Connect first to start logging.\n", 32, COLOR_PAIR(CP_YELLOW)); + } + draw_status(); + return; + + case KEY_F(10): + if (connected) tcp_disconnect(); + if (logging) close_log(); + endwin(); + exit(0); + case KEY_RESIZE: endwin(); refresh(); clear(); create_windows();