Session architecture: bundle per-connection state into Session struct

All per-connection state (socket, buffers, logging, port tracking,
input history, scrollback) moved from global variables into a Session
struct.  sessions[MAX_SESSIONS=4] holds the pool; S points to the
active one.  CLI mode runs with S=&sessions[0] unchanged.

This is the foundation for multiple simultaneous sessions in the GUI.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
gui-redesign
Ryan Collier 3 weeks ago
parent c0cf254034
commit 8bf8ae80ec

@ -70,14 +70,69 @@ typedef struct {
typedef struct { char data[SCRL_W]; int len; int base_attr; } ScrlLine; typedef struct { char data[SCRL_W]; int len; int base_attr; } ScrlLine;
/* ── Session ──────────────────────────────────────────────────
* All per-connection state in one struct so the program can run
* multiple simultaneous sessions (one per tab in the GUI, or one
* in the CLI). S always points to the currently active session. */
#define MAX_SESSIONS 4
#define SESSION_LABEL 32
typedef struct {
int active; /* slot is in use */
int host_idx; /* index into hosts[] */
char label[SESSION_LABEL]; /* display name / tab title */
/* Connection */
int sock;
int connected;
time_t connect_time;
/* Logging */
int logging;
FILE *log_file;
/* BPQ monitor frame accumulator */
int in_mon_frame;
char mon_fbuf[4096];
int mon_flen;
/* Port tracking */
int port_seen;
char ports_line_buf[256];
int ports_line_len;
int ports_parse_mode;
int ports_loaded;
/* Output scrollback */
ScrlLine out_sbuf[SCRL_MAX];
int out_sbuf_head, out_sbuf_count;
int out_scroll;
char out_part[SCRL_W];
int out_part_len, out_part_base, out_part_cur;
/* Monitor scrollback */
ScrlLine mon_sbuf[SCRL_MAX];
int mon_sbuf_head, mon_sbuf_count;
int mon_scroll;
char mon_part[SCRL_W];
int mon_part_len, mon_part_base, mon_part_cur;
/* Input */
char input_buf[INPUT_MAX];
int input_len;
/* Input history */
char hist_buf[HIST_MAX][INPUT_MAX];
int hist_head, hist_count, hist_pos;
char hist_save[INPUT_MAX];
} Session;
static Session sessions[MAX_SESSIONS];
static Session *S; /* active (displayed) session */
HostEntry hosts[MAX_HOSTS]; HostEntry hosts[MAX_HOSTS];
int num_hosts = 0; int num_hosts = 0;
int current_host = -1;
/* ── Network ─────────────────────────────────────────────── */
int sock = -1;
int connected = 0;
time_t connect_time = 0;
/* ── Signal handling ─────────────────────────────────────── * /* ── Signal handling ─────────────────────────────────────── *
* SIGINT (Ctrl+C) is trapped rather than left at its default * SIGINT (Ctrl+C) is trapped rather than left at its default
@ -94,11 +149,7 @@ static void on_sigint(int signum)
sigint_pending = 1; sigint_pending = 1;
} }
/* ── Logging ─────────────────────────────────────────────── */ /* ── BPQ / monitor state (shared across all sessions) ─────── */
int logging = 0;
FILE *log_file = NULL;
/* ── BPQ / monitor state ─────────────────────────────────── */
int mon_visible = 1; int mon_visible = 1;
int mon_tx = 1; int mon_tx = 1;
int mon_com = 1; int mon_com = 1;
@ -106,38 +157,8 @@ int mon_nodes = 0;
int mon_colour = 1; int mon_colour = 1;
int mon_ui_only = 1; int mon_ui_only = 1;
int port_mask = 0xFFFF; int port_mask = 0xFFFF;
int in_mon_frame = 0;
static char mon_fbuf[4096]; /* accumulates one complete 0xFF…0xFE monitor frame */ char port_names[32][32]; /* port 1-32 names, loaded from config */
static int mon_flen = 0;
int port_seen = 0; /* bitmask: bit N-1 set when port N seen in monitor data */
char port_names[32][32]; /* names for ports 1-32, loaded from config or defaulted */
/* ── Session line parser (for PORTS response) ────────────── */
static char ports_line_buf[256];
static int ports_line_len = 0;
static int ports_parse_mode = 0; /* 1 while inside "} Ports" response */
static int ports_loaded = 0; /* set when PORTS list fully received */
/* ── Scrollback buffers ──────────────────────────────────── */
static ScrlLine out_sbuf[SCRL_MAX];
static int out_sbuf_head = 0, out_sbuf_count = 0;
static int out_scroll = 0; /* 0=live, >0=lines from bottom */
static char out_part[SCRL_W];
static int out_part_len = 0, out_part_base = 0, out_part_cur = 0;
static ScrlLine mon_sbuf[SCRL_MAX];
static int mon_sbuf_head = 0, mon_sbuf_count = 0;
static int mon_scroll = 0;
static char mon_part[SCRL_W];
static int mon_part_len = 0, mon_part_base = 0, mon_part_cur = 0;
/* ── Input history ───────────────────────────────────────── */
static char hist_buf[HIST_MAX][INPUT_MAX];
static int hist_head = 0; /* next write slot (ring) */
static int hist_count = 0; /* valid entries */
static int hist_pos = -1; /* -1=live, 0=most recent ... */
static char hist_save[INPUT_MAX]; /* saved input while browsing */
/* ── ncurses windows ─────────────────────────────────────── */ /* ── ncurses windows ─────────────────────────────────────── */
WINDOW *status_win = NULL; WINDOW *status_win = NULL;
@ -148,10 +169,6 @@ WINDOW *sep2_win = NULL; /* separator above input area */
WINDOW *input_win = NULL; WINDOW *input_win = NULL;
WINDOW *fkey_win = NULL; /* function key hint bar at bottom */ WINDOW *fkey_win = NULL; /* function key hint bar at bottom */
/* ── Input buffer ────────────────────────────────────────── */
char input_buf[INPUT_MAX];
int input_len = 0;
/* ── ICS-213 General Message ─────────────────────────────── */ /* ── ICS-213 General Message ─────────────────────────────── */
#define ICS_SBLINE_MAX 120 /* "SB <addressee>@<distribution>" line */ #define ICS_SBLINE_MAX 120 /* "SB <addressee>@<distribution>" line */
#define ICS_FIELD_MAX 80 /* To / From / Subject single-line fields */ #define ICS_FIELD_MAX 80 /* To / From / Subject single-line fields */
@ -385,13 +402,13 @@ void open_log(const char *prefix)
snprintf(filename, PATH_MAX, "%s_%04d%02d%02d_%02d%02d%02d.log", snprintf(filename, PATH_MAX, "%s_%04d%02d%02d_%02d%02d%02d.log",
prefix, t->tm_year+1900, t->tm_mon+1, t->tm_mday, prefix, t->tm_year+1900, t->tm_mon+1, t->tm_mday,
t->tm_hour, t->tm_min, t->tm_sec); t->tm_hour, t->tm_min, t->tm_sec);
log_file = fopen(filename, "w"); S->log_file = fopen(filename, "w");
if (log_file) logging = 1; if (S->log_file) S->logging = 1;
} }
void close_log(void) void close_log(void)
{ {
if (log_file) { fclose(log_file); log_file = NULL; logging = 0; } if (S->log_file) { fclose(S->log_file); S->log_file = NULL; S->logging = 0; }
} }
/* ═══════════════════════════════════════════════════════════ /* ═══════════════════════════════════════════════════════════
@ -431,22 +448,22 @@ int tcp_connect(const char *host, const char *port)
int tcp_send(const char *data, int len) int tcp_send(const char *data, int len)
{ {
if (sock < 0 || len <= 0) return -1; if (S->sock < 0 || len <= 0) return -1;
if (logging && log_file) { fwrite(data, 1, len, log_file); fflush(log_file); } if (S->logging && S->log_file) { fwrite(data, 1, len, S->log_file); fflush(S->log_file); }
return send(sock, data, len, 0); return send(S->sock, data, len, 0);
} }
void tcp_disconnect(void) void tcp_disconnect(void)
{ {
if (sock >= 0) { close(sock); sock = -1; } if (S->sock >= 0) { close(S->sock); S->sock = -1; }
connected = 0; S->connected = 0;
connect_time = 0; S->connect_time = 0;
in_mon_frame = 0; S->in_mon_frame = 0;
mon_flen = 0; S->mon_flen = 0;
ports_parse_mode = 0; S->ports_parse_mode = 0;
ports_line_len = 0; S->ports_line_len = 0;
ports_loaded = 0; S->ports_loaded = 0;
port_seen = 0; S->port_seen = 0;
for (int i = 0; i < 32; i++) snprintf(port_names[i], 32, "Port %d", i + 1); for (int i = 0; i < 32; i++) snprintf(port_names[i], 32, "Port %d", i + 1);
draw_status(); draw_status();
} }
@ -457,26 +474,26 @@ void tcp_disconnect(void)
void send_trace_options(void) void send_trace_options(void)
{ {
if (sock < 0) return; if (S->sock < 0) return;
char buf[80]; char buf[80];
int len = snprintf(buf, sizeof(buf), int len = snprintf(buf, sizeof(buf),
"\\\\\\\\%x %x %x %x %x %x\r", "\\\\\\\\%x %x %x %x %x %x\r",
port_mask, mon_tx, mon_com, port_mask, mon_tx, mon_com,
mon_nodes, mon_colour, mon_ui_only); mon_nodes, mon_colour, mon_ui_only);
send(sock, buf, len, 0); send(S->sock, buf, len, 0);
} }
/* Same as send_trace_options but appends P8=1, which tells BPQ to call /* Same as send_trace_options but appends P8=1, which tells BPQ to call
* SendPortsForMonitor() returns port list as 0xFF 0xFF N|num desc|... */ * SendPortsForMonitor() returns port list as 0xFF 0xFF N|num desc|... */
static void send_trace_options_with_ports(void) static void send_trace_options_with_ports(void)
{ {
if (sock < 0) return; if (S->sock < 0) return;
char buf[80]; char buf[80];
int len = snprintf(buf, sizeof(buf), int len = snprintf(buf, sizeof(buf),
"\\\\\\\\%x %x %x %x %x %x 0 1\r", "\\\\\\\\%x %x %x %x %x %x 0 1\r",
port_mask, mon_tx, mon_com, port_mask, mon_tx, mon_com,
mon_nodes, mon_colour, mon_ui_only); mon_nodes, mon_colour, mon_ui_only);
send(sock, buf, len, 0); send(S->sock, buf, len, 0);
} }
/* Parse the 0xFF 0xFF port-info packet from SendPortsForMonitor. /* Parse the 0xFF 0xFF port-info packet from SendPortsForMonitor.
@ -501,11 +518,11 @@ static void parse_portinfo(const char *data, int len)
int copy = dlen < 31 ? dlen : 31; int copy = dlen < 31 ? dlen : 31;
memcpy(port_names[n - 1], desc, copy); memcpy(port_names[n - 1], desc, copy);
port_names[n - 1][copy] = '\0'; port_names[n - 1][copy] = '\0';
port_seen |= (1u << (n - 1)); S->port_seen |= (1u << (n - 1));
} }
if (p < e && *p == '|') p++; if (p < e && *p == '|') p++;
} }
ports_loaded = 1; S->ports_loaded = 1;
} }
static int bpq_color_attr(unsigned char color_byte) static int bpq_color_attr(unsigned char color_byte)
@ -522,7 +539,7 @@ static int bpq_color_attr(unsigned char color_byte)
return COLOR_PAIR(CP_MAGENTA); return COLOR_PAIR(CP_MAGENTA);
} }
/* Scan monitor frame text for "Port=N" and record N in port_seen bitmask. /* Scan monitor frame text for "Port=N" and record N in S->port_seen bitmask.
* BPQ monitor frames use "Port=N" format (e.g. "KB8PMY>APRS Port=2 <UI>"). * BPQ monitor frames use "Port=N" format (e.g. "KB8PMY>APRS Port=2 <UI>").
* Port names come from the PORTS session command, not from monitor frames. */ * Port names come from the PORTS session command, not from monitor frames. */
static void detect_port_number(const char *text, int len) static void detect_port_number(const char *text, int len)
@ -539,7 +556,7 @@ static void detect_port_number(const char *text, int len)
while (j < len && p[j] >= '0' && p[j] <= '9') while (j < len && p[j] >= '0' && p[j] <= '9')
n = n * 10 + (p[j++] - '0'); n = n * 10 + (p[j++] - '0');
if (n >= 1 && n <= 32) if (n >= 1 && n <= 32)
port_seen |= (1 << (n-1)); S->port_seen |= (1 << (n-1));
} }
} }
} }
@ -553,7 +570,7 @@ static void parse_session_line(const char *line, int len)
while (len > 0 && (unsigned char)line[len-1] <= ' ') len--; while (len > 0 && (unsigned char)line[len-1] <= ' ') len--;
if (len == 0) return; if (len == 0) return;
if (!ports_parse_mode) { if (!S->ports_parse_mode) {
/* Detect "} Ports" header (case-insensitive) */ /* Detect "} Ports" header (case-insensitive) */
for (int i = 0; i + 6 < len; i++) { for (int i = 0; i + 6 < len; i++) {
if (line[i] == '}' && line[i+1] == ' ' && if (line[i] == '}' && line[i+1] == ' ' &&
@ -562,7 +579,7 @@ static void parse_session_line(const char *line, int len)
(line[i+4]=='r'||line[i+4]=='R') && (line[i+4]=='r'||line[i+4]=='R') &&
(line[i+5]=='t'||line[i+5]=='T') && (line[i+5]=='t'||line[i+5]=='T') &&
(line[i+6]=='s'||line[i+6]=='S')) { (line[i+6]=='s'||line[i+6]=='S')) {
ports_parse_mode = 1; S->ports_parse_mode = 1;
return; return;
} }
} }
@ -583,11 +600,11 @@ static void parse_session_line(const char *line, int len)
int copy = nlen < 31 ? nlen : 31; int copy = nlen < 31 ? nlen : 31;
memcpy(port_names[n-1], line + j, copy); memcpy(port_names[n-1], line + j, copy);
port_names[n-1][copy] = '\0'; port_names[n-1][copy] = '\0';
port_seen |= (1 << (n-1)); S->port_seen |= (1 << (n-1));
} }
} else { } else {
if (ports_parse_mode) ports_loaded = 1; /* list ended cleanly */ if (S->ports_parse_mode) S->ports_loaded = 1; /* list ended cleanly */
ports_parse_mode = 0; S->ports_parse_mode = 0;
} }
} }
@ -595,12 +612,12 @@ static void parse_session_line(const char *line, int len)
static void feed_session_byte(unsigned char c) static void feed_session_byte(unsigned char c)
{ {
if (c == '\r' || c == '\n') { if (c == '\r' || c == '\n') {
if (ports_line_len > 0) { if (S->ports_line_len > 0) {
parse_session_line(ports_line_buf, ports_line_len); parse_session_line(S->ports_line_buf, S->ports_line_len);
ports_line_len = 0; S->ports_line_len = 0;
} }
} else if (c >= 32 && c < 128 && ports_line_len < 255) { } else if (c >= 32 && c < 128 && S->ports_line_len < 255) {
ports_line_buf[ports_line_len++] = (char)c; S->ports_line_buf[S->ports_line_len++] = (char)c;
} }
} }
@ -610,7 +627,7 @@ void bpq_process(const char *buf, int len)
const char *end = buf + len; const char *end = buf + len;
while (p < end) { while (p < end) {
if (!in_mon_frame) { if (!S->in_mon_frame) {
const char *ff = memchr(p, 0xFF, end - p); const char *ff = memchr(p, 0xFF, end - p);
if (!ff) { if (!ff) {
/* All remaining bytes are session output */ /* All remaining bytes are session output */
@ -632,8 +649,8 @@ void bpq_process(const char *buf, int len)
parse_portinfo(p + 1, (int)(end - p - 1)); parse_portinfo(p + 1, (int)(end - p - 1));
p = end; p = end;
} else { } else {
in_mon_frame = 1; S->in_mon_frame = 1;
mon_flen = 0; S->mon_flen = 0;
} }
} else { } else {
const char *fe = memchr(p, 0xFE, end - p); const char *fe = memchr(p, 0xFE, end - p);
@ -641,26 +658,26 @@ void bpq_process(const char *buf, int len)
if (seg_len > 0) { if (seg_len > 0) {
detect_port_number(p, seg_len); detect_port_number(p, seg_len);
/* Accumulate into the per-frame buffer */ /* Accumulate into the per-frame buffer */
int room = (int)sizeof(mon_fbuf) - 1 - mon_flen; int room = (int)sizeof(S->mon_fbuf) - 1 - S->mon_flen;
if (room > 0) { if (room > 0) {
int add = seg_len < room ? seg_len : room; int add = seg_len < room ? seg_len : room;
memcpy(mon_fbuf + mon_flen, p, add); memcpy(S->mon_fbuf + S->mon_flen, p, add);
mon_flen += add; S->mon_flen += add;
} }
} }
if (fe) { if (fe) {
/* Frame complete: apply client-side NODES filter then display */ /* Frame complete: apply client-side NODES filter then display */
if (mon_flen > 0) { if (S->mon_flen > 0) {
mon_fbuf[mon_flen] = '\0'; S->mon_fbuf[S->mon_flen] = '\0';
/* Detect NODES broadcast by AX.25 destination ">NODES" in header */ /* Detect NODES broadcast by AX.25 destination ">NODES" in header */
int is_nodes = 0; int is_nodes = 0;
for (int i = 0; i <= mon_flen - 6 && !is_nodes; i++) for (int i = 0; i <= S->mon_flen - 6 && !is_nodes; i++)
if (memcmp(mon_fbuf + i, ">NODES", 6) == 0) is_nodes = 1; if (memcmp(S->mon_fbuf + i, ">NODES", 6) == 0) is_nodes = 1;
if (mon_nodes || !is_nodes) if (mon_nodes || !is_nodes)
append_mon(mon_fbuf, mon_flen, COLOR_PAIR(CP_GREEN)); append_mon(S->mon_fbuf, S->mon_flen, COLOR_PAIR(CP_GREEN));
} }
mon_flen = 0; S->mon_flen = 0;
in_mon_frame = 0; S->in_mon_frame = 0;
p = fe + 1; p = fe + 1;
} else { } else {
break; break;
@ -668,7 +685,7 @@ void bpq_process(const char *buf, int len)
} }
} }
if (logging && log_file) { fwrite(buf, 1, len, log_file); fflush(log_file); } if (S->logging && S->log_file) { fwrite(buf, 1, len, S->log_file); fflush(S->log_file); }
} }
/* ═══════════════════════════════════════════════════════════ /* ═══════════════════════════════════════════════════════════
@ -813,10 +830,10 @@ void draw_status(void)
/* Center: connection info */ /* Center: connection info */
char center[80]; char center[80];
if (connected && current_host >= 0) { if (S->connected && S->host_idx >= 0) {
int elapsed = (int)(time(NULL) - connect_time); int elapsed = (int)(time(NULL) - S->connect_time);
snprintf(center, sizeof(center), "%s [%02d:%02d]", snprintf(center, sizeof(center), "%s [%02d:%02d]",
hosts[current_host].name, elapsed / 60, elapsed % 60); hosts[S->host_idx].name, elapsed / 60, elapsed % 60);
} else { } else {
snprintf(center, sizeof(center), "Not Connected"); snprintf(center, sizeof(center), "Not Connected");
} }
@ -828,7 +845,7 @@ void draw_status(void)
struct tm *t = gmtime(&now); struct tm *t = gmtime(&now);
char right[32]; char right[32];
snprintf(right, sizeof(right), "%s%02d:%02d UTC ", snprintf(right, sizeof(right), "%s%02d:%02d UTC ",
logging ? "LOG " : "", t->tm_hour, t->tm_min); S->logging ? "LOG " : "", t->tm_hour, t->tm_min);
int rx = COLS - (int)strlen(right); int rx = COLS - (int)strlen(right);
if (rx > 0) mvwprintw(status_win, 0, rx, "%s", right); if (rx > 0) mvwprintw(status_win, 0, rx, "%s", right);
@ -923,49 +940,49 @@ void append_mon(const char *text, int len, int attr)
const char *p = text; const char *p = text;
const char *end = text + len; const char *end = text + len;
mon_part_cur = attr; S->mon_part_cur = attr;
if (mon_part_len == 0) mon_part_base = attr; if (S->mon_part_len == 0) S->mon_part_base = attr;
if (mon_win && mon_scroll == 0) if (mon_win && S->mon_scroll == 0)
wattron(mon_win, mon_part_cur); wattron(mon_win, S->mon_part_cur);
while (p < end) { while (p < end) {
unsigned char c = (unsigned char)*p; unsigned char c = (unsigned char)*p;
if (c == 0x1B && (p + 1) < end) { if (c == 0x1B && (p + 1) < end) {
int na = bpq_color_attr((unsigned char)p[1]); int na = bpq_color_attr((unsigned char)p[1]);
if (mon_part_len + 2 < SCRL_W) { if (S->mon_part_len + 2 < SCRL_W) {
mon_part[mon_part_len++] = (char)0x1B; S->mon_part[S->mon_part_len++] = (char)0x1B;
mon_part[mon_part_len++] = p[1]; S->mon_part[S->mon_part_len++] = p[1];
} }
if (mon_win && mon_scroll == 0) { if (mon_win && S->mon_scroll == 0) {
wattroff(mon_win, mon_part_cur); wattroff(mon_win, S->mon_part_cur);
wattron(mon_win, na); wattron(mon_win, na);
} }
mon_part_cur = na; S->mon_part_cur = na;
p += 2; p += 2;
} else if (c == '\r' || c == '\n') { } else if (c == '\r' || c == '\n') {
ScrlLine *sl = &mon_sbuf[mon_sbuf_head]; ScrlLine *sl = &S->mon_sbuf[S->mon_sbuf_head];
memcpy(sl->data, mon_part, mon_part_len); memcpy(sl->data, S->mon_part, S->mon_part_len);
sl->len = mon_part_len; sl->len = S->mon_part_len;
sl->base_attr = mon_part_base; sl->base_attr = S->mon_part_base;
mon_sbuf_head = (mon_sbuf_head + 1) % SCRL_MAX; S->mon_sbuf_head = (S->mon_sbuf_head + 1) % SCRL_MAX;
if (mon_sbuf_count < SCRL_MAX) mon_sbuf_count++; if (S->mon_sbuf_count < SCRL_MAX) S->mon_sbuf_count++;
mon_part_len = 0; S->mon_part_len = 0;
mon_part_base = mon_part_cur; S->mon_part_base = S->mon_part_cur;
if (mon_win && mon_scroll == 0) if (mon_win && S->mon_scroll == 0)
waddch(mon_win, '\n'); waddch(mon_win, '\n');
p++; p++;
} else if (c >= 32 && c < 127) { } else if (c >= 32 && c < 127) {
if (mon_part_len < SCRL_W - 1) if (S->mon_part_len < SCRL_W - 1)
mon_part[mon_part_len++] = (char)c; S->mon_part[S->mon_part_len++] = (char)c;
if (mon_win && mon_scroll == 0) if (mon_win && S->mon_scroll == 0)
waddch(mon_win, c); waddch(mon_win, c);
p++; p++;
} else { p++; } } else { p++; }
} }
if (mon_win && mon_scroll == 0) { if (mon_win && S->mon_scroll == 0) {
wattroff(mon_win, mon_part_cur); wattroff(mon_win, S->mon_part_cur);
wrefresh(mon_win); wrefresh(mon_win);
if (input_win) wrefresh(input_win); if (input_win) wrefresh(input_win);
} }
@ -978,49 +995,49 @@ void append_out(const char *text, int len, int attr)
const char *p = text; const char *p = text;
const char *end = text + len; const char *end = text + len;
out_part_cur = attr; S->out_part_cur = attr;
if (out_part_len == 0) out_part_base = attr; if (S->out_part_len == 0) S->out_part_base = attr;
if (out_win && out_scroll == 0) if (out_win && S->out_scroll == 0)
wattron(out_win, out_part_cur); wattron(out_win, S->out_part_cur);
while (p < end) { while (p < end) {
unsigned char c = (unsigned char)*p; unsigned char c = (unsigned char)*p;
if (c == 0x1B && (p + 1) < end) { if (c == 0x1B && (p + 1) < end) {
int na = bpq_color_attr((unsigned char)p[1]); int na = bpq_color_attr((unsigned char)p[1]);
if (out_part_len + 2 < SCRL_W) { if (S->out_part_len + 2 < SCRL_W) {
out_part[out_part_len++] = (char)0x1B; S->out_part[S->out_part_len++] = (char)0x1B;
out_part[out_part_len++] = p[1]; S->out_part[S->out_part_len++] = p[1];
} }
if (out_win && out_scroll == 0) { if (out_win && S->out_scroll == 0) {
wattroff(out_win, out_part_cur); wattroff(out_win, S->out_part_cur);
wattron(out_win, na); wattron(out_win, na);
} }
out_part_cur = na; S->out_part_cur = na;
p += 2; p += 2;
} else if (c == '\r' || c == '\n') { } else if (c == '\r' || c == '\n') {
ScrlLine *sl = &out_sbuf[out_sbuf_head]; ScrlLine *sl = &S->out_sbuf[S->out_sbuf_head];
memcpy(sl->data, out_part, out_part_len); memcpy(sl->data, S->out_part, S->out_part_len);
sl->len = out_part_len; sl->len = S->out_part_len;
sl->base_attr = out_part_base; sl->base_attr = S->out_part_base;
out_sbuf_head = (out_sbuf_head + 1) % SCRL_MAX; S->out_sbuf_head = (S->out_sbuf_head + 1) % SCRL_MAX;
if (out_sbuf_count < SCRL_MAX) out_sbuf_count++; if (S->out_sbuf_count < SCRL_MAX) S->out_sbuf_count++;
out_part_len = 0; S->out_part_len = 0;
out_part_base = out_part_cur; S->out_part_base = S->out_part_cur;
if (out_win && out_scroll == 0) if (out_win && S->out_scroll == 0)
waddch(out_win, '\n'); waddch(out_win, '\n');
p++; p++;
} else if (c >= 32 && c < 127) { } else if (c >= 32 && c < 127) {
if (out_part_len < SCRL_W - 1) if (S->out_part_len < SCRL_W - 1)
out_part[out_part_len++] = (char)c; S->out_part[S->out_part_len++] = (char)c;
if (out_win && out_scroll == 0) if (out_win && S->out_scroll == 0)
waddch(out_win, c); waddch(out_win, c);
p++; p++;
} else { p++; } } else { p++; }
} }
if (out_win && out_scroll == 0) { if (out_win && S->out_scroll == 0) {
wattroff(out_win, out_part_cur); wattroff(out_win, S->out_part_cur);
wrefresh(out_win); wrefresh(out_win);
if (input_win) wrefresh(input_win); if (input_win) wrefresh(input_win);
} }
@ -1040,16 +1057,16 @@ void draw_input(void)
mvwaddstr(input_win, 0, 0, "> "); mvwaddstr(input_win, 0, 0, "> ");
/* Buffer content — wraps at COLS, '> ' prefix is 2 chars */ /* Buffer content — wraps at COLS, '> ' prefix is 2 chars */
for (int i = 0; i < input_len; i++) { for (int i = 0; i < S->input_len; i++) {
int pos = 2 + i; int pos = 2 + i;
int r = pos / COLS; int r = pos / COLS;
int c = pos % COLS; int c = pos % COLS;
if (r >= INPUT_OUTER) break; if (r >= INPUT_OUTER) break;
mvwaddch(input_win, r, c, (unsigned char)input_buf[i]); mvwaddch(input_win, r, c, (unsigned char)S->input_buf[i]);
} }
/* Cursor position */ /* Cursor position */
int cpos = 2 + input_len; int cpos = 2 + S->input_len;
int cr = cpos / COLS; int cr = cpos / COLS;
int cc = cpos % COLS; int cc = cpos % COLS;
if (cr < INPUT_OUTER) wmove(input_win, cr, cc); if (cr < INPUT_OUTER) wmove(input_win, cr, cc);
@ -1064,19 +1081,19 @@ void draw_input(void)
static void scrl_redraw_out(void) static void scrl_redraw_out(void)
{ {
scrl_draw_window(out_win, out_sbuf, out_sbuf_count, out_sbuf_head, out_scroll); scrl_draw_window(out_win, S->out_sbuf, S->out_sbuf_count, S->out_sbuf_head, S->out_scroll);
} }
static void scrl_redraw_mon(void) static void scrl_redraw_mon(void)
{ {
if (!mon_visible) return; if (!mon_visible) return;
scrl_draw_window(mon_win, mon_sbuf, mon_sbuf_count, mon_sbuf_head, mon_scroll); scrl_draw_window(mon_win, S->mon_sbuf, S->mon_sbuf_count, S->mon_sbuf_head, S->mon_scroll);
} }
static void update_sep_labels(void) static void update_sep_labels(void)
{ {
if (sep_win) { if (sep_win) {
if (out_scroll > 0) if (S->out_scroll > 0)
fill_sep(sep_win, " Output [scrolled - PgDn=live] "); fill_sep(sep_win, " Output [scrolled - PgDn=live] ");
else else
fill_sep(sep_win, " Output "); fill_sep(sep_win, " Output ");
@ -1126,7 +1143,7 @@ static void config_menu(void)
const int max_len[] = { 99, 9, 79, 79, 49 }; const int max_len[] = { 99, 9, 79, 79, 49 };
char fields[5][100]; char fields[5][100];
int cur_host = (current_host >= 0 && current_host < num_hosts) ? current_host : 0; int cur_host = (S->host_idx >= 0 && S->host_idx < num_hosts) ? S->host_idx : 0;
int cur_field = 0; int cur_field = 0;
if (num_hosts == 0) { memset(&hosts[0], 0, sizeof(HostEntry)); num_hosts = 1; } if (num_hosts == 0) { memset(&hosts[0], 0, sizeof(HostEntry)); num_hosts = 1; }
@ -1492,7 +1509,7 @@ static int mon_ports_page(void)
{ {
int port_list[32], n_ports = 0; int port_list[32], n_ports = 0;
for (int b = 0; b < 32; b++) for (int b = 0; b < 32; b++)
if (port_seen & (1 << b)) port_list[n_ports++] = b + 1; if (S->port_seen & (1 << b)) port_list[n_ports++] = b + 1;
if (n_ports == 0) if (n_ports == 0)
for (int b = 0; b < 8; b++) port_list[n_ports++] = b + 1; for (int b = 0; b < 8; b++) port_list[n_ports++] = b + 1;
@ -1513,7 +1530,7 @@ static int mon_ports_page(void)
werase(w); werase(w);
draw_box(w); draw_box(w);
mvwprintw(w, 0, (mw - 16) / 2, " Port Selection "); mvwprintw(w, 0, (mw - 16) / 2, " Port Selection ");
if (port_seen == 0) if (S->port_seen == 0)
mvwprintw(w, 2, 3, "(connect to auto-detect ports)"); mvwprintw(w, 2, 3, "(connect to auto-detect ports)");
for (int i = 0; i < n_ports; i++) { for (int i = 0; i < n_ports; i++) {
int pn = port_list[i]; int pn = port_list[i];
@ -1605,14 +1622,14 @@ static void show_help(void)
/* ── Background send connection ────────────────────────────── /* ── Background send connection ──────────────────────────────
* F2 always opens its OWN socket for sending, completely separate * F2 always opens its OWN socket for sending, completely separate
* from the main interactive session's global `sock`/`connected`. * from the main interactive session's global `S->sock`/`S->connected`.
* This means a send never disturbs whatever you're doing in the * This means a send never disturbs whatever you're doing in the
* main window - no shared connection state, no risk of interleaving * main window - no shared connection state, no risk of interleaving
* a send with whatever you're typing or watching scroll by. * a send with whatever you're typing or watching scroll by.
* *
* Deliberately does NOT call bpq_process(): that function's monitor * Deliberately does NOT call bpq_process(): that function's monitor
* frame parser keeps state in globals (in_mon_frame, mon_flen, * frame parser keeps state in globals (S->in_mon_frame, S->mon_flen,
* ports_parse_mode, etc.) shared with the main session. Running two * S->ports_parse_mode, etc.) shared with the main session. Running two
* sockets' data through one shared parser would corrupt both. The * sockets' data through one shared parser would corrupt both. The
* background connection instead does a much simpler raw drain: BPQ * background connection instead does a much simpler raw drain: BPQ
* monitor frames are bounded by 0xFF/0xFE bytes, so they're just * monitor frames are bounded by 0xFF/0xFE bytes, so they're just
@ -1733,9 +1750,9 @@ static void bg_send_block(int fd, char lines[][ICS_MSG_LINELEN], int n, int *abo
} }
/* Opens a new socket to hosts[idx], logs in if credentials are saved, /* Opens a new socket to hosts[idx], logs in if credentials are saved,
* and drains the login banner briefly. Returns the connected fd, or * and drains the login banner briefly. Returns the S->connected fd, or
* -1 on failure (with a message already echoed to out_win). This is * -1 on failure (with a message already echoed to out_win). This is
* intentionally separate from do_connect()/sock/connected - it never * intentionally separate from do_connect()/S->sock/S->connected - it never
* touches the main session's connection state. */ * touches the main session's connection state. */
static int bg_connect_and_login(int idx) static int bg_connect_and_login(int idx)
{ {
@ -2393,8 +2410,8 @@ static void pktnet_checkin_form(void)
memset(&f, 0, sizeof(f)); memset(&f, 0, sizeof(f));
strncpy(f.sb_line, "SB PKTNET@USA", ICS_SBLINE_MAX - 1); strncpy(f.sb_line, "SB PKTNET@USA", ICS_SBLINE_MAX - 1);
strncpy(f.to, "PKTNET@USA", CHKIN_FIELD_MAX - 1); strncpy(f.to, "PKTNET@USA", CHKIN_FIELD_MAX - 1);
if (current_host >= 0 && hosts[current_host].username[0]) if (S->host_idx >= 0 && hosts[S->host_idx].username[0])
strncpy(f.from, hosts[current_host].username, CHKIN_FIELD_MAX - 1); strncpy(f.from, hosts[S->host_idx].username, CHKIN_FIELD_MAX - 1);
f.type_idx = 0; /* EXERCISE */ f.type_idx = 0; /* EXERCISE */
f.band_idx = 2; /* VHF */ f.band_idx = 2; /* VHF */
f.session_idx = 1; /* AX25 Packet */ f.session_idx = 1; /* AX25 Packet */
@ -2597,22 +2614,22 @@ static void pktnet_checkin_form(void)
static void do_connect(int idx) static void do_connect(int idx)
{ {
if (idx < 0 || idx >= num_hosts) return; if (idx < 0 || idx >= num_hosts) return;
if (connected) tcp_disconnect(); if (S->connected) tcp_disconnect();
char msg[160]; char msg[160];
snprintf(msg, sizeof(msg), "Connecting to %s:%s...\n", snprintf(msg, sizeof(msg), "Connecting to %s:%s...\n",
hosts[idx].host, hosts[idx].port); hosts[idx].host, hosts[idx].port);
append_out(msg, (int)strlen(msg), COLOR_PAIR(CP_YELLOW)); append_out(msg, (int)strlen(msg), COLOR_PAIR(CP_YELLOW));
sock = tcp_connect(hosts[idx].host, hosts[idx].port); S->sock = tcp_connect(hosts[idx].host, hosts[idx].port);
if (sock < 0) { if (S->sock < 0) {
append_out("Connection failed.\n", 19, COLOR_PAIR(CP_RED)); append_out("Connection failed.\n", 19, COLOR_PAIR(CP_RED));
return; return;
} }
connected = 1; S->connected = 1;
current_host = idx; S->host_idx = idx;
connect_time = time(NULL); S->connect_time = time(NULL);
snprintf(msg, sizeof(msg), "Connected to %s\n", hosts[idx].name); snprintf(msg, sizeof(msg), "Connected to %s\n", hosts[idx].name);
append_out(msg, (int)strlen(msg), COLOR_PAIR(CP_GREEN) | A_BOLD); append_out(msg, (int)strlen(msg), COLOR_PAIR(CP_GREEN) | A_BOLD);
@ -2636,7 +2653,7 @@ static void do_connect(int idx)
/* Drain incoming data for up to 2 seconds waiting for PORTS response. /* Drain incoming data for up to 2 seconds waiting for PORTS response.
* A single recv() often gets only the login banner; loop until we have * A single recv() often gets only the login banner; loop until we have
* the full port list (ports_loaded) or the deadline passes. */ * the full port list (S->ports_loaded) or the deadline passes. */
{ {
char rbuf[4096]; char rbuf[4096];
struct timeval t0, now; struct timeval t0, now;
@ -2646,13 +2663,13 @@ static void do_connect(int idx)
long elapsed = (now.tv_sec - t0.tv_sec) * 1000000L long elapsed = (now.tv_sec - t0.tv_sec) * 1000000L
+ (now.tv_usec - t0.tv_usec); + (now.tv_usec - t0.tv_usec);
if (elapsed >= 2000000L) break; if (elapsed >= 2000000L) break;
if (ports_loaded) break; if (S->ports_loaded) break;
long remain = 2000000L - elapsed; long remain = 2000000L - elapsed;
fd_set rfds; fd_set rfds;
struct timeval tv = { remain / 1000000L, remain % 1000000L }; struct timeval tv = { remain / 1000000L, remain % 1000000L };
FD_ZERO(&rfds); FD_SET(sock, &rfds); FD_ZERO(&rfds); FD_SET(S->sock, &rfds);
if (select(sock + 1, &rfds, NULL, NULL, &tv) <= 0) break; if (select(S->sock + 1, &rfds, NULL, NULL, &tv) <= 0) break;
int n = recv(sock, rbuf, sizeof(rbuf) - 1, 0); int n = recv(S->sock, rbuf, sizeof(rbuf) - 1, 0);
if (n <= 0) break; if (n <= 0) break;
bpq_process(rbuf, n); bpq_process(rbuf, n);
} }
@ -2665,14 +2682,14 @@ static void do_connect(int idx)
* "port shows enabled here but no traffic flows until re-toggled"). * "port shows enabled here but no traffic flows until re-toggled").
* Re-sending once the initial drain has settled costs one extra * Re-sending once the initial drain has settled costs one extra
* trace command and catches that race without needing to detect it. */ * trace command and catches that race without needing to detect it. */
if (connected && sock >= 0) { if (S->connected && S->sock >= 0) {
send_trace_options_with_ports(); send_trace_options_with_ports();
char rbuf[4096]; char rbuf[4096];
struct timeval tv2 = { 1, 0 }; struct timeval tv2 = { 1, 0 };
fd_set rfds2; fd_set rfds2;
FD_ZERO(&rfds2); FD_SET(sock, &rfds2); FD_ZERO(&rfds2); FD_SET(S->sock, &rfds2);
if (select(sock + 1, &rfds2, NULL, NULL, &tv2) > 0) { if (select(S->sock + 1, &rfds2, NULL, NULL, &tv2) > 0) {
int n = recv(sock, rbuf, sizeof(rbuf) - 1, 0); int n = recv(S->sock, rbuf, sizeof(rbuf) - 1, 0);
if (n > 0) bpq_process(rbuf, n); if (n > 0) bpq_process(rbuf, n);
} }
} }
@ -2753,7 +2770,7 @@ static void ctrl_a_menu(void)
break; break;
case 'd': case 'd':
if (connected) { if (S->connected) {
tcp_disconnect(); tcp_disconnect();
append_out("*** Disconnected\n", 17, COLOR_PAIR(CP_RED)); append_out("*** Disconnected\n", 17, COLOR_PAIR(CP_RED));
} }
@ -2768,17 +2785,17 @@ static void ctrl_a_menu(void)
break; break;
case 'l': case 'l':
if (logging) { if (S->logging) {
close_log(); close_log();
append_out("[Logging OFF]\n", 14, COLOR_PAIR(CP_YELLOW)); append_out("[Logging OFF]\n", 14, COLOR_PAIR(CP_YELLOW));
} else if (connected && current_host >= 0) { } else if (S->connected && S->host_idx >= 0) {
char prefix[120]; char prefix[120];
snprintf(prefix, sizeof(prefix), "%s_%s", snprintf(prefix, sizeof(prefix), "%s_%s",
hosts[current_host].host, hosts[current_host].port); hosts[S->host_idx].host, hosts[S->host_idx].port);
open_log(prefix); open_log(prefix);
append_out("[Logging ON]\n", 13, COLOR_PAIR(CP_YELLOW)); append_out("[Logging ON]\n", 13, COLOR_PAIR(CP_YELLOW));
} else { } else {
append_out("Connect first to start logging.\n", 32, COLOR_PAIR(CP_YELLOW)); append_out("Connect first to start S->logging.\n", 32, COLOR_PAIR(CP_YELLOW));
} }
draw_status(); draw_status();
break; break;
@ -2792,8 +2809,8 @@ static void ctrl_a_menu(void)
break; break;
case 'q': case 'q':
if (connected) tcp_disconnect(); if (S->connected) tcp_disconnect();
if (logging) close_log(); if (S->logging) close_log();
endwin(); endwin();
exit(0); exit(0);
} }
@ -2823,7 +2840,7 @@ static void handle_key(int ch)
} }
case KEY_F(4): case KEY_F(4):
if (connected) { if (S->connected) {
tcp_disconnect(); tcp_disconnect();
append_out("*** Disconnected\n", 17, COLOR_PAIR(CP_RED)); append_out("*** Disconnected\n", 17, COLOR_PAIR(CP_RED));
draw_status(); draw_status();
@ -2835,24 +2852,24 @@ static void handle_key(int ch)
return; return;
case KEY_F(6): case KEY_F(6):
if (logging) { if (S->logging) {
close_log(); close_log();
append_out("[Logging OFF]\n", 14, COLOR_PAIR(CP_YELLOW)); append_out("[Logging OFF]\n", 14, COLOR_PAIR(CP_YELLOW));
} else if (connected && current_host >= 0) { } else if (S->connected && S->host_idx >= 0) {
char prefix[120]; char prefix[120];
snprintf(prefix, sizeof(prefix), "%s_%s", snprintf(prefix, sizeof(prefix), "%s_%s",
hosts[current_host].host, hosts[current_host].port); hosts[S->host_idx].host, hosts[S->host_idx].port);
open_log(prefix); open_log(prefix);
append_out("[Logging ON]\n", 13, COLOR_PAIR(CP_YELLOW)); append_out("[Logging ON]\n", 13, COLOR_PAIR(CP_YELLOW));
} else { } else {
append_out("Connect first to start logging.\n", 32, COLOR_PAIR(CP_YELLOW)); append_out("Connect first to start S->logging.\n", 32, COLOR_PAIR(CP_YELLOW));
} }
draw_status(); draw_status();
return; return;
case KEY_F(10): case KEY_F(10):
if (connected) tcp_disconnect(); if (S->connected) tcp_disconnect();
if (logging) close_log(); if (S->logging) close_log();
endwin(); endwin();
exit(0); exit(0);
@ -2865,9 +2882,9 @@ static void handle_key(int ch)
case KEY_PPAGE: /* PgUp — scroll output back */ case KEY_PPAGE: /* PgUp — scroll output back */
if (out_win) { if (out_win) {
int page = getmaxy(out_win); int page = getmaxy(out_win);
out_scroll += page; S->out_scroll += page;
int max_scroll = out_sbuf_count > 0 ? out_sbuf_count - 1 : 0; int max_scroll = S->out_sbuf_count > 0 ? S->out_sbuf_count - 1 : 0;
if (out_scroll > max_scroll) out_scroll = max_scroll; if (S->out_scroll > max_scroll) S->out_scroll = max_scroll;
scrl_redraw_out(); scrl_redraw_out();
update_sep_labels(); update_sep_labels();
if (input_win) wrefresh(input_win); if (input_win) wrefresh(input_win);
@ -2875,10 +2892,10 @@ static void handle_key(int ch)
return; return;
case KEY_NPAGE: /* PgDn — scroll output forward / return to live */ case KEY_NPAGE: /* PgDn — scroll output forward / return to live */
if (out_win && out_scroll > 0) { if (out_win && S->out_scroll > 0) {
int page = getmaxy(out_win); int page = getmaxy(out_win);
out_scroll -= page; S->out_scroll -= page;
if (out_scroll < 0) out_scroll = 0; if (S->out_scroll < 0) S->out_scroll = 0;
scrl_redraw_out(); scrl_redraw_out();
update_sep_labels(); update_sep_labels();
if (input_win) wrefresh(input_win); if (input_win) wrefresh(input_win);
@ -2888,83 +2905,83 @@ static void handle_key(int ch)
case KEY_HOME: /* Home — scroll monitor back */ case KEY_HOME: /* Home — scroll monitor back */
if (mon_win && mon_visible) { if (mon_win && mon_visible) {
int page = getmaxy(mon_win); int page = getmaxy(mon_win);
mon_scroll += page; S->mon_scroll += page;
int max_scroll = mon_sbuf_count > 0 ? mon_sbuf_count - 1 : 0; int max_scroll = S->mon_sbuf_count > 0 ? S->mon_sbuf_count - 1 : 0;
if (mon_scroll > max_scroll) mon_scroll = max_scroll; if (S->mon_scroll > max_scroll) S->mon_scroll = max_scroll;
scrl_redraw_mon(); scrl_redraw_mon();
if (input_win) wrefresh(input_win); if (input_win) wrefresh(input_win);
} }
return; return;
case KEY_END: /* End — scroll monitor forward / return to live */ case KEY_END: /* End — scroll monitor forward / return to live */
if (mon_win && mon_visible && mon_scroll > 0) { if (mon_win && mon_visible && S->mon_scroll > 0) {
int page = getmaxy(mon_win); int page = getmaxy(mon_win);
mon_scroll -= page; S->mon_scroll -= page;
if (mon_scroll < 0) mon_scroll = 0; if (S->mon_scroll < 0) S->mon_scroll = 0;
scrl_redraw_mon(); scrl_redraw_mon();
if (input_win) wrefresh(input_win); if (input_win) wrefresh(input_win);
} }
return; return;
case '\r': case '\n': case KEY_ENTER: case '\r': case '\n': case KEY_ENTER:
if (input_len == 0) return; if (S->input_len == 0) return;
input_buf[input_len] = '\0'; S->input_buf[S->input_len] = '\0';
/* Save to history */ /* Save to history */
strncpy(hist_buf[hist_head], input_buf, INPUT_MAX - 1); strncpy(S->hist_buf[S->hist_head], S->input_buf, INPUT_MAX - 1);
hist_buf[hist_head][INPUT_MAX - 1] = '\0'; S->hist_buf[S->hist_head][INPUT_MAX - 1] = '\0';
hist_head = (hist_head + 1) % HIST_MAX; S->hist_head = (S->hist_head + 1) % HIST_MAX;
if (hist_count < HIST_MAX) hist_count++; if (S->hist_count < HIST_MAX) S->hist_count++;
hist_pos = -1; S->hist_pos = -1;
if (connected) { if (S->connected) {
tcp_send(input_buf, input_len); tcp_send(S->input_buf, S->input_len);
tcp_send("\r", 1); tcp_send("\r", 1);
char echo[INPUT_MAX + 4]; char echo[INPUT_MAX + 4];
int elen = snprintf(echo, sizeof(echo), "> %s\n", input_buf); int elen = snprintf(echo, sizeof(echo), "> %s\n", S->input_buf);
append_out(echo, elen, COLOR_PAIR(CP_CYAN) | A_BOLD); append_out(echo, elen, COLOR_PAIR(CP_CYAN) | A_BOLD);
} else { } else {
append_out("Not connected -- Ctrl+A to open menu.\n", 38, append_out("Not S->connected -- Ctrl+A to open menu.\n", 38,
COLOR_PAIR(CP_YELLOW)); COLOR_PAIR(CP_YELLOW));
} }
input_len = 0; S->input_len = 0;
break; break;
case KEY_UP: /* history: go back */ case KEY_UP: /* history: go back */
if (hist_count == 0) break; if (S->hist_count == 0) break;
if (hist_pos == -1) { if (S->hist_pos == -1) {
strncpy(hist_save, input_buf, INPUT_MAX - 1); strncpy(S->hist_save, S->input_buf, INPUT_MAX - 1);
hist_save[INPUT_MAX - 1] = '\0'; S->hist_save[INPUT_MAX - 1] = '\0';
hist_pos = 0; S->hist_pos = 0;
} else if (hist_pos < hist_count - 1) { } else if (S->hist_pos < S->hist_count - 1) {
hist_pos++; S->hist_pos++;
} }
{ {
int idx = (hist_head - 1 - hist_pos + HIST_MAX * 2) % HIST_MAX; int idx = (S->hist_head - 1 - S->hist_pos + HIST_MAX * 2) % HIST_MAX;
strncpy(input_buf, hist_buf[idx], INPUT_MAX - 1); strncpy(S->input_buf, S->hist_buf[idx], INPUT_MAX - 1);
input_buf[INPUT_MAX - 1] = '\0'; S->input_buf[INPUT_MAX - 1] = '\0';
input_len = (int)strlen(input_buf); S->input_len = (int)strlen(S->input_buf);
} }
break; break;
case KEY_DOWN: /* history: go forward */ case KEY_DOWN: /* history: go forward */
if (hist_pos < 0) break; if (S->hist_pos < 0) break;
hist_pos--; S->hist_pos--;
if (hist_pos < 0) { if (S->hist_pos < 0) {
input_buf[0] = '\0'; /* always end with blank line */ S->input_buf[0] = '\0'; /* always end with blank line */
} else { } else {
int idx = (hist_head - 1 - hist_pos + HIST_MAX * 2) % HIST_MAX; int idx = (S->hist_head - 1 - S->hist_pos + HIST_MAX * 2) % HIST_MAX;
strncpy(input_buf, hist_buf[idx], INPUT_MAX - 1); strncpy(S->input_buf, S->hist_buf[idx], INPUT_MAX - 1);
input_buf[INPUT_MAX - 1] = '\0'; S->input_buf[INPUT_MAX - 1] = '\0';
} }
input_len = (int)strlen(input_buf); S->input_len = (int)strlen(S->input_buf);
break; break;
case KEY_BACKSPACE: case 127: case '\b': case KEY_BACKSPACE: case 127: case '\b':
if (input_len > 0) input_len--; if (S->input_len > 0) S->input_len--;
break; break;
default: default:
if (ch >= 32 && ch < 256 && input_len < INPUT_MAX - 1) if (ch >= 32 && ch < 256 && S->input_len < INPUT_MAX - 1)
input_buf[input_len++] = (char)ch; S->input_buf[S->input_len++] = (char)ch;
break; break;
} }
@ -2988,7 +3005,7 @@ static int confirm_quit_popup(void)
draw_box(w); draw_box(w);
mvwprintw(w, 0, (mw - 8) / 2, " Quit? "); mvwprintw(w, 0, (mw - 8) / 2, " Quit? ");
mvwprintw(w, 2, 3, "Ctrl+C pressed."); mvwprintw(w, 2, 3, "Ctrl+C pressed.");
if (connected) mvwprintw(w, 3, 3, "This will close the current session."); if (S->connected) mvwprintw(w, 3, 3, "This will close the current session.");
mvwprintw(w, mh - 2, 3, "Y = quit N / Esc = resume"); mvwprintw(w, mh - 2, 3, "Y = quit N / Esc = resume");
wrefresh(w); wrefresh(w);
@ -3013,8 +3030,8 @@ void interactive_loop(void)
if (sigint_pending) { if (sigint_pending) {
sigint_pending = 0; sigint_pending = 0;
if (confirm_quit_popup()) { if (confirm_quit_popup()) {
if (connected) tcp_disconnect(); if (S->connected) tcp_disconnect();
if (logging) close_log(); if (S->logging) close_log();
endwin(); endwin();
exit(0); exit(0);
} }
@ -3025,17 +3042,17 @@ void interactive_loop(void)
FD_ZERO(&readfds); FD_ZERO(&readfds);
FD_SET(STDIN_FILENO, &readfds); FD_SET(STDIN_FILENO, &readfds);
if (connected && sock >= 0) FD_SET(sock, &readfds); if (S->connected && S->sock >= 0) FD_SET(S->sock, &readfds);
tv.tv_sec = 1; tv.tv_sec = 1;
tv.tv_usec = 0; tv.tv_usec = 0;
int maxfd = (connected && sock > STDIN_FILENO) ? sock : STDIN_FILENO; int maxfd = (S->connected && S->sock > STDIN_FILENO) ? S->sock : STDIN_FILENO;
int ready = select(maxfd + 1, &readfds, NULL, NULL, &tv); int ready = select(maxfd + 1, &readfds, NULL, NULL, &tv);
if (ready < 0) continue; if (ready < 0) continue;
/* Network data */ /* Network data */
if (connected && sock >= 0 && FD_ISSET(sock, &readfds)) { if (S->connected && S->sock >= 0 && FD_ISSET(S->sock, &readfds)) {
int n = recv(sock, recv_buf, sizeof(recv_buf)-1, 0); int n = recv(S->sock, recv_buf, sizeof(recv_buf)-1, 0);
if (n > 0) { if (n > 0) {
bpq_process(recv_buf, n); bpq_process(recv_buf, n);
draw_input(); draw_input();
@ -3073,6 +3090,15 @@ int main(int argc, char *argv[])
} }
} }
/* Initialise session 0 — the one and only session in CLI mode */
memset(sessions, 0, sizeof(sessions));
sessions[0].active = 1;
sessions[0].sock = -1;
sessions[0].host_idx = -1;
sessions[0].hist_pos = -1;
snprintf(sessions[0].label, SESSION_LABEL, "Session 1");
S = &sessions[0];
/* Set default port names before load_config so INI PortName keys override them */ /* Set default port names before load_config so INI PortName keys override them */
for (int i = 0; i < 32; i++) for (int i = 0; i < 32; i++)
snprintf(port_names[i], 32, "Port %d", i + 1); snprintf(port_names[i], 32, "Port %d", i + 1);
@ -3122,8 +3148,8 @@ int main(int argc, char *argv[])
interactive_loop(); interactive_loop();
if (connected) tcp_disconnect(); if (S->connected) tcp_disconnect();
if (logging) close_log(); if (S->logging) close_log();
endwin(); endwin();
return 0; return 0;
} }

Loading…
Cancel
Save

Powered by TurnKey Linux.