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;
/* ── 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];
int num_hosts = 0;
int current_host = -1;
/* ── Network ─────────────────────────────────────────────── */
int sock = -1;
int connected = 0;
time_t connect_time = 0;
/* ── Signal handling ─────────────────────────────────────── *
* SIGINT (Ctrl+C) is trapped rather than left at its default
@ -94,11 +149,7 @@ static void on_sigint(int signum)
sigint_pending = 1;
}
/* ── Logging ─────────────────────────────────────────────── */
int logging = 0;
FILE *log_file = NULL;
/* ── BPQ / monitor state ─────────────────────────────────── */
/* ── BPQ / monitor state (shared across all sessions) ─────── */
int mon_visible = 1;
int mon_tx = 1;
int mon_com = 1;
@ -106,38 +157,8 @@ int mon_nodes = 0;
int mon_colour = 1;
int mon_ui_only = 1;
int port_mask = 0xFFFF;
int in_mon_frame = 0;
static char mon_fbuf[4096]; /* accumulates one complete 0xFF…0xFE monitor frame */
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 */
char port_names[32][32]; /* port 1-32 names, loaded from config */
/* ── ncurses windows ─────────────────────────────────────── */
WINDOW *status_win = NULL;
@ -148,10 +169,6 @@ 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];
int input_len = 0;
/* ── ICS-213 General Message ─────────────────────────────── */
#define ICS_SBLINE_MAX 120 /* "SB <addressee>@<distribution>" line */
#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",
prefix, t->tm_year+1900, t->tm_mon+1, t->tm_mday,
t->tm_hour, t->tm_min, t->tm_sec);
log_file = fopen(filename, "w");
if (log_file) logging = 1;
S->log_file = fopen(filename, "w");
if (S->log_file) S->logging = 1;
}
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)
{
if (sock < 0 || len <= 0) return -1;
if (logging && log_file) { fwrite(data, 1, len, log_file); fflush(log_file); }
return send(sock, data, len, 0);
if (S->sock < 0 || len <= 0) return -1;
if (S->logging && S->log_file) { fwrite(data, 1, len, S->log_file); fflush(S->log_file); }
return send(S->sock, data, len, 0);
}
void tcp_disconnect(void)
{
if (sock >= 0) { close(sock); sock = -1; }
connected = 0;
connect_time = 0;
in_mon_frame = 0;
mon_flen = 0;
ports_parse_mode = 0;
ports_line_len = 0;
ports_loaded = 0;
port_seen = 0;
if (S->sock >= 0) { close(S->sock); S->sock = -1; }
S->connected = 0;
S->connect_time = 0;
S->in_mon_frame = 0;
S->mon_flen = 0;
S->ports_parse_mode = 0;
S->ports_line_len = 0;
S->ports_loaded = 0;
S->port_seen = 0;
for (int i = 0; i < 32; i++) snprintf(port_names[i], 32, "Port %d", i + 1);
draw_status();
}
@ -457,26 +474,26 @@ void tcp_disconnect(void)
void send_trace_options(void)
{
if (sock < 0) return;
if (S->sock < 0) return;
char buf[80];
int len = snprintf(buf, sizeof(buf),
"\\\\\\\\%x %x %x %x %x %x\r",
port_mask, mon_tx, mon_com,
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
* SendPortsForMonitor() returns port list as 0xFF 0xFF N|num desc|... */
static void send_trace_options_with_ports(void)
{
if (sock < 0) return;
if (S->sock < 0) return;
char buf[80];
int len = snprintf(buf, sizeof(buf),
"\\\\\\\\%x %x %x %x %x %x 0 1\r",
port_mask, mon_tx, mon_com,
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.
@ -501,11 +518,11 @@ static void parse_portinfo(const char *data, int len)
int copy = dlen < 31 ? dlen : 31;
memcpy(port_names[n - 1], desc, copy);
port_names[n - 1][copy] = '\0';
port_seen |= (1u << (n - 1));
S->port_seen |= (1u << (n - 1));
}
if (p < e && *p == '|') p++;
}
ports_loaded = 1;
S->ports_loaded = 1;
}
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);
}
/* 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>").
* Port names come from the PORTS session command, not from monitor frames. */
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')
n = n * 10 + (p[j++] - '0');
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--;
if (len == 0) return;
if (!ports_parse_mode) {
if (!S->ports_parse_mode) {
/* Detect "} Ports" header (case-insensitive) */
for (int i = 0; i + 6 < len; i++) {
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+5]=='t'||line[i+5]=='T') &&
(line[i+6]=='s'||line[i+6]=='S')) {
ports_parse_mode = 1;
S->ports_parse_mode = 1;
return;
}
}
@ -583,11 +600,11 @@ static void parse_session_line(const char *line, int len)
int copy = nlen < 31 ? nlen : 31;
memcpy(port_names[n-1], line + j, copy);
port_names[n-1][copy] = '\0';
port_seen |= (1 << (n-1));
S->port_seen |= (1 << (n-1));
}
} else {
if (ports_parse_mode) ports_loaded = 1; /* list ended cleanly */
ports_parse_mode = 0;
if (S->ports_parse_mode) S->ports_loaded = 1; /* list ended cleanly */
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)
{
if (c == '\r' || c == '\n') {
if (ports_line_len > 0) {
parse_session_line(ports_line_buf, ports_line_len);
ports_line_len = 0;
if (S->ports_line_len > 0) {
parse_session_line(S->ports_line_buf, S->ports_line_len);
S->ports_line_len = 0;
}
} else if (c >= 32 && c < 128 && ports_line_len < 255) {
ports_line_buf[ports_line_len++] = (char)c;
} else if (c >= 32 && c < 128 && S->ports_line_len < 255) {
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;
while (p < end) {
if (!in_mon_frame) {
if (!S->in_mon_frame) {
const char *ff = memchr(p, 0xFF, end - p);
if (!ff) {
/* 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));
p = end;
} else {
in_mon_frame = 1;
mon_flen = 0;
S->in_mon_frame = 1;
S->mon_flen = 0;
}
} else {
const char *fe = memchr(p, 0xFE, end - p);
@ -641,26 +658,26 @@ void bpq_process(const char *buf, int len)
if (seg_len > 0) {
detect_port_number(p, seg_len);
/* 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) {
int add = seg_len < room ? seg_len : room;
memcpy(mon_fbuf + mon_flen, p, add);
mon_flen += add;
memcpy(S->mon_fbuf + S->mon_flen, p, add);
S->mon_flen += add;
}
}
if (fe) {
/* Frame complete: apply client-side NODES filter then display */
if (mon_flen > 0) {
mon_fbuf[mon_flen] = '\0';
if (S->mon_flen > 0) {
S->mon_fbuf[S->mon_flen] = '\0';
/* Detect NODES broadcast by AX.25 destination ">NODES" in header */
int is_nodes = 0;
for (int i = 0; i <= mon_flen - 6 && !is_nodes; i++)
if (memcmp(mon_fbuf + i, ">NODES", 6) == 0) is_nodes = 1;
for (int i = 0; i <= S->mon_flen - 6 && !is_nodes; i++)
if (memcmp(S->mon_fbuf + i, ">NODES", 6) == 0) is_nodes = 1;
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;
in_mon_frame = 0;
S->mon_flen = 0;
S->in_mon_frame = 0;
p = fe + 1;
} else {
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 */
char center[80];
if (connected && current_host >= 0) {
int elapsed = (int)(time(NULL) - connect_time);
if (S->connected && S->host_idx >= 0) {
int elapsed = (int)(time(NULL) - S->connect_time);
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 {
snprintf(center, sizeof(center), "Not Connected");
}
@ -828,7 +845,7 @@ void draw_status(void)
struct tm *t = gmtime(&now);
char right[32];
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);
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 *end = text + len;
mon_part_cur = attr;
if (mon_part_len == 0) mon_part_base = attr;
S->mon_part_cur = attr;
if (S->mon_part_len == 0) S->mon_part_base = attr;
if (mon_win && mon_scroll == 0)
wattron(mon_win, mon_part_cur);
if (mon_win && S->mon_scroll == 0)
wattron(mon_win, S->mon_part_cur);
while (p < end) {
unsigned char c = (unsigned char)*p;
if (c == 0x1B && (p + 1) < end) {
int na = bpq_color_attr((unsigned char)p[1]);
if (mon_part_len + 2 < SCRL_W) {
mon_part[mon_part_len++] = (char)0x1B;
mon_part[mon_part_len++] = p[1];
if (S->mon_part_len + 2 < SCRL_W) {
S->mon_part[S->mon_part_len++] = (char)0x1B;
S->mon_part[S->mon_part_len++] = p[1];
}
if (mon_win && mon_scroll == 0) {
wattroff(mon_win, mon_part_cur);
if (mon_win && S->mon_scroll == 0) {
wattroff(mon_win, S->mon_part_cur);
wattron(mon_win, na);
}
mon_part_cur = na;
S->mon_part_cur = na;
p += 2;
} else if (c == '\r' || c == '\n') {
ScrlLine *sl = &mon_sbuf[mon_sbuf_head];
memcpy(sl->data, mon_part, mon_part_len);
sl->len = mon_part_len;
sl->base_attr = mon_part_base;
mon_sbuf_head = (mon_sbuf_head + 1) % SCRL_MAX;
if (mon_sbuf_count < SCRL_MAX) mon_sbuf_count++;
mon_part_len = 0;
mon_part_base = mon_part_cur;
if (mon_win && mon_scroll == 0)
ScrlLine *sl = &S->mon_sbuf[S->mon_sbuf_head];
memcpy(sl->data, S->mon_part, S->mon_part_len);
sl->len = S->mon_part_len;
sl->base_attr = S->mon_part_base;
S->mon_sbuf_head = (S->mon_sbuf_head + 1) % SCRL_MAX;
if (S->mon_sbuf_count < SCRL_MAX) S->mon_sbuf_count++;
S->mon_part_len = 0;
S->mon_part_base = S->mon_part_cur;
if (mon_win && S->mon_scroll == 0)
waddch(mon_win, '\n');
p++;
} else if (c >= 32 && c < 127) {
if (mon_part_len < SCRL_W - 1)
mon_part[mon_part_len++] = (char)c;
if (mon_win && mon_scroll == 0)
if (S->mon_part_len < SCRL_W - 1)
S->mon_part[S->mon_part_len++] = (char)c;
if (mon_win && S->mon_scroll == 0)
waddch(mon_win, c);
p++;
} else { p++; }
}
if (mon_win && mon_scroll == 0) {
wattroff(mon_win, mon_part_cur);
if (mon_win && S->mon_scroll == 0) {
wattroff(mon_win, S->mon_part_cur);
wrefresh(mon_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 *end = text + len;
out_part_cur = attr;
if (out_part_len == 0) out_part_base = attr;
S->out_part_cur = attr;
if (S->out_part_len == 0) S->out_part_base = attr;
if (out_win && out_scroll == 0)
wattron(out_win, out_part_cur);
if (out_win && S->out_scroll == 0)
wattron(out_win, S->out_part_cur);
while (p < end) {
unsigned char c = (unsigned char)*p;
if (c == 0x1B && (p + 1) < end) {
int na = bpq_color_attr((unsigned char)p[1]);
if (out_part_len + 2 < SCRL_W) {
out_part[out_part_len++] = (char)0x1B;
out_part[out_part_len++] = p[1];
if (S->out_part_len + 2 < SCRL_W) {
S->out_part[S->out_part_len++] = (char)0x1B;
S->out_part[S->out_part_len++] = p[1];
}
if (out_win && out_scroll == 0) {
wattroff(out_win, out_part_cur);
if (out_win && S->out_scroll == 0) {
wattroff(out_win, S->out_part_cur);
wattron(out_win, na);
}
out_part_cur = na;
S->out_part_cur = na;
p += 2;
} else if (c == '\r' || c == '\n') {
ScrlLine *sl = &out_sbuf[out_sbuf_head];
memcpy(sl->data, out_part, out_part_len);
sl->len = out_part_len;
sl->base_attr = out_part_base;
out_sbuf_head = (out_sbuf_head + 1) % SCRL_MAX;
if (out_sbuf_count < SCRL_MAX) out_sbuf_count++;
out_part_len = 0;
out_part_base = out_part_cur;
if (out_win && out_scroll == 0)
ScrlLine *sl = &S->out_sbuf[S->out_sbuf_head];
memcpy(sl->data, S->out_part, S->out_part_len);
sl->len = S->out_part_len;
sl->base_attr = S->out_part_base;
S->out_sbuf_head = (S->out_sbuf_head + 1) % SCRL_MAX;
if (S->out_sbuf_count < SCRL_MAX) S->out_sbuf_count++;
S->out_part_len = 0;
S->out_part_base = S->out_part_cur;
if (out_win && S->out_scroll == 0)
waddch(out_win, '\n');
p++;
} else if (c >= 32 && c < 127) {
if (out_part_len < SCRL_W - 1)
out_part[out_part_len++] = (char)c;
if (out_win && out_scroll == 0)
if (S->out_part_len < SCRL_W - 1)
S->out_part[S->out_part_len++] = (char)c;
if (out_win && S->out_scroll == 0)
waddch(out_win, c);
p++;
} else { p++; }
}
if (out_win && out_scroll == 0) {
wattroff(out_win, out_part_cur);
if (out_win && S->out_scroll == 0) {
wattroff(out_win, S->out_part_cur);
wrefresh(out_win);
if (input_win) wrefresh(input_win);
}
@ -1040,16 +1057,16 @@ void draw_input(void)
mvwaddstr(input_win, 0, 0, "> ");
/* 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 r = pos / COLS;
int c = pos % COLS;
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 */
int cpos = 2 + input_len;
int cpos = 2 + S->input_len;
int cr = cpos / COLS;
int cc = cpos % COLS;
if (cr < INPUT_OUTER) wmove(input_win, cr, cc);
@ -1064,19 +1081,19 @@ void draw_input(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)
{
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)
{
if (sep_win) {
if (out_scroll > 0)
if (S->out_scroll > 0)
fill_sep(sep_win, " Output [scrolled - PgDn=live] ");
else
fill_sep(sep_win, " Output ");
@ -1126,7 +1143,7 @@ static void config_menu(void)
const int max_len[] = { 99, 9, 79, 79, 49 };
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;
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;
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)
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);
draw_box(w);
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)");
for (int i = 0; i < n_ports; i++) {
int pn = port_list[i];
@ -1605,14 +1622,14 @@ static void show_help(void)
/* ── Background send connection ──────────────────────────────
* 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
* main window - no shared connection state, no risk of interleaving
* a send with whatever you're typing or watching scroll by.
*
* Deliberately does NOT call bpq_process(): that function's monitor
* frame parser keeps state in globals (in_mon_frame, mon_flen,
* ports_parse_mode, etc.) shared with the main session. Running two
* frame parser keeps state in globals (S->in_mon_frame, S->mon_flen,
* S->ports_parse_mode, etc.) shared with the main session. Running two
* sockets' data through one shared parser would corrupt both. The
* background connection instead does a much simpler raw drain: BPQ
* 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,
* 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
* 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. */
static int bg_connect_and_login(int idx)
{
@ -2393,8 +2410,8 @@ static void pktnet_checkin_form(void)
memset(&f, 0, sizeof(f));
strncpy(f.sb_line, "SB PKTNET@USA", ICS_SBLINE_MAX - 1);
strncpy(f.to, "PKTNET@USA", CHKIN_FIELD_MAX - 1);
if (current_host >= 0 && hosts[current_host].username[0])
strncpy(f.from, hosts[current_host].username, CHKIN_FIELD_MAX - 1);
if (S->host_idx >= 0 && hosts[S->host_idx].username[0])
strncpy(f.from, hosts[S->host_idx].username, CHKIN_FIELD_MAX - 1);
f.type_idx = 0; /* EXERCISE */
f.band_idx = 2; /* VHF */
f.session_idx = 1; /* AX25 Packet */
@ -2597,22 +2614,22 @@ static void pktnet_checkin_form(void)
static void do_connect(int idx)
{
if (idx < 0 || idx >= num_hosts) return;
if (connected) tcp_disconnect();
if (S->connected) tcp_disconnect();
char msg[160];
snprintf(msg, sizeof(msg), "Connecting to %s:%s...\n",
hosts[idx].host, hosts[idx].port);
append_out(msg, (int)strlen(msg), COLOR_PAIR(CP_YELLOW));
sock = tcp_connect(hosts[idx].host, hosts[idx].port);
if (sock < 0) {
S->sock = tcp_connect(hosts[idx].host, hosts[idx].port);
if (S->sock < 0) {
append_out("Connection failed.\n", 19, COLOR_PAIR(CP_RED));
return;
}
connected = 1;
current_host = idx;
connect_time = time(NULL);
S->connected = 1;
S->host_idx = idx;
S->connect_time = time(NULL);
snprintf(msg, sizeof(msg), "Connected to %s\n", hosts[idx].name);
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.
* 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];
struct timeval t0, now;
@ -2646,13 +2663,13 @@ static void do_connect(int idx)
long elapsed = (now.tv_sec - t0.tv_sec) * 1000000L
+ (now.tv_usec - t0.tv_usec);
if (elapsed >= 2000000L) break;
if (ports_loaded) break;
if (S->ports_loaded) break;
long remain = 2000000L - elapsed;
fd_set rfds;
struct timeval tv = { remain / 1000000L, remain % 1000000L };
FD_ZERO(&rfds); FD_SET(sock, &rfds);
if (select(sock + 1, &rfds, NULL, NULL, &tv) <= 0) break;
int n = recv(sock, rbuf, sizeof(rbuf) - 1, 0);
FD_ZERO(&rfds); FD_SET(S->sock, &rfds);
if (select(S->sock + 1, &rfds, NULL, NULL, &tv) <= 0) break;
int n = recv(S->sock, rbuf, sizeof(rbuf) - 1, 0);
if (n <= 0) break;
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").
* Re-sending once the initial drain has settled costs one extra
* 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();
char rbuf[4096];
struct timeval tv2 = { 1, 0 };
fd_set rfds2;
FD_ZERO(&rfds2); FD_SET(sock, &rfds2);
if (select(sock + 1, &rfds2, NULL, NULL, &tv2) > 0) {
int n = recv(sock, rbuf, sizeof(rbuf) - 1, 0);
FD_ZERO(&rfds2); FD_SET(S->sock, &rfds2);
if (select(S->sock + 1, &rfds2, NULL, NULL, &tv2) > 0) {
int n = recv(S->sock, rbuf, sizeof(rbuf) - 1, 0);
if (n > 0) bpq_process(rbuf, n);
}
}
@ -2753,7 +2770,7 @@ static void ctrl_a_menu(void)
break;
case 'd':
if (connected) {
if (S->connected) {
tcp_disconnect();
append_out("*** Disconnected\n", 17, COLOR_PAIR(CP_RED));
}
@ -2768,17 +2785,17 @@ static void ctrl_a_menu(void)
break;
case 'l':
if (logging) {
if (S->logging) {
close_log();
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];
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);
append_out("[Logging ON]\n", 13, COLOR_PAIR(CP_YELLOW));
} 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();
break;
@ -2792,8 +2809,8 @@ static void ctrl_a_menu(void)
break;
case 'q':
if (connected) tcp_disconnect();
if (logging) close_log();
if (S->connected) tcp_disconnect();
if (S->logging) close_log();
endwin();
exit(0);
}
@ -2823,7 +2840,7 @@ static void handle_key(int ch)
}
case KEY_F(4):
if (connected) {
if (S->connected) {
tcp_disconnect();
append_out("*** Disconnected\n", 17, COLOR_PAIR(CP_RED));
draw_status();
@ -2835,24 +2852,24 @@ static void handle_key(int ch)
return;
case KEY_F(6):
if (logging) {
if (S->logging) {
close_log();
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];
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);
append_out("[Logging ON]\n", 13, COLOR_PAIR(CP_YELLOW));
} 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();
return;
case KEY_F(10):
if (connected) tcp_disconnect();
if (logging) close_log();
if (S->connected) tcp_disconnect();
if (S->logging) close_log();
endwin();
exit(0);
@ -2865,9 +2882,9 @@ static void handle_key(int ch)
case KEY_PPAGE: /* PgUp — scroll output back */
if (out_win) {
int page = getmaxy(out_win);
out_scroll += page;
int max_scroll = out_sbuf_count > 0 ? out_sbuf_count - 1 : 0;
if (out_scroll > max_scroll) out_scroll = max_scroll;
S->out_scroll += page;
int max_scroll = S->out_sbuf_count > 0 ? S->out_sbuf_count - 1 : 0;
if (S->out_scroll > max_scroll) S->out_scroll = max_scroll;
scrl_redraw_out();
update_sep_labels();
if (input_win) wrefresh(input_win);
@ -2875,10 +2892,10 @@ static void handle_key(int ch)
return;
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);
out_scroll -= page;
if (out_scroll < 0) out_scroll = 0;
S->out_scroll -= page;
if (S->out_scroll < 0) S->out_scroll = 0;
scrl_redraw_out();
update_sep_labels();
if (input_win) wrefresh(input_win);
@ -2888,83 +2905,83 @@ static void handle_key(int ch)
case KEY_HOME: /* Home — scroll monitor back */
if (mon_win && mon_visible) {
int page = getmaxy(mon_win);
mon_scroll += page;
int max_scroll = mon_sbuf_count > 0 ? mon_sbuf_count - 1 : 0;
if (mon_scroll > max_scroll) mon_scroll = max_scroll;
S->mon_scroll += page;
int max_scroll = S->mon_sbuf_count > 0 ? S->mon_sbuf_count - 1 : 0;
if (S->mon_scroll > max_scroll) S->mon_scroll = max_scroll;
scrl_redraw_mon();
if (input_win) wrefresh(input_win);
}
return;
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);
mon_scroll -= page;
if (mon_scroll < 0) mon_scroll = 0;
S->mon_scroll -= page;
if (S->mon_scroll < 0) S->mon_scroll = 0;
scrl_redraw_mon();
if (input_win) wrefresh(input_win);
}
return;
case '\r': case '\n': case KEY_ENTER:
if (input_len == 0) return;
input_buf[input_len] = '\0';
if (S->input_len == 0) return;
S->input_buf[S->input_len] = '\0';
/* Save to history */
strncpy(hist_buf[hist_head], input_buf, INPUT_MAX - 1);
hist_buf[hist_head][INPUT_MAX - 1] = '\0';
hist_head = (hist_head + 1) % HIST_MAX;
if (hist_count < HIST_MAX) hist_count++;
hist_pos = -1;
if (connected) {
tcp_send(input_buf, input_len);
strncpy(S->hist_buf[S->hist_head], S->input_buf, INPUT_MAX - 1);
S->hist_buf[S->hist_head][INPUT_MAX - 1] = '\0';
S->hist_head = (S->hist_head + 1) % HIST_MAX;
if (S->hist_count < HIST_MAX) S->hist_count++;
S->hist_pos = -1;
if (S->connected) {
tcp_send(S->input_buf, S->input_len);
tcp_send("\r", 1);
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);
} 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));
}
input_len = 0;
S->input_len = 0;
break;
case KEY_UP: /* history: go back */
if (hist_count == 0) break;
if (hist_pos == -1) {
strncpy(hist_save, input_buf, INPUT_MAX - 1);
hist_save[INPUT_MAX - 1] = '\0';
hist_pos = 0;
} else if (hist_pos < hist_count - 1) {
hist_pos++;
if (S->hist_count == 0) break;
if (S->hist_pos == -1) {
strncpy(S->hist_save, S->input_buf, INPUT_MAX - 1);
S->hist_save[INPUT_MAX - 1] = '\0';
S->hist_pos = 0;
} else if (S->hist_pos < S->hist_count - 1) {
S->hist_pos++;
}
{
int idx = (hist_head - 1 - hist_pos + HIST_MAX * 2) % HIST_MAX;
strncpy(input_buf, hist_buf[idx], INPUT_MAX - 1);
input_buf[INPUT_MAX - 1] = '\0';
input_len = (int)strlen(input_buf);
int idx = (S->hist_head - 1 - S->hist_pos + HIST_MAX * 2) % HIST_MAX;
strncpy(S->input_buf, S->hist_buf[idx], INPUT_MAX - 1);
S->input_buf[INPUT_MAX - 1] = '\0';
S->input_len = (int)strlen(S->input_buf);
}
break;
case KEY_DOWN: /* history: go forward */
if (hist_pos < 0) break;
hist_pos--;
if (hist_pos < 0) {
input_buf[0] = '\0'; /* always end with blank line */
if (S->hist_pos < 0) break;
S->hist_pos--;
if (S->hist_pos < 0) {
S->input_buf[0] = '\0'; /* always end with blank line */
} else {
int idx = (hist_head - 1 - hist_pos + HIST_MAX * 2) % HIST_MAX;
strncpy(input_buf, hist_buf[idx], INPUT_MAX - 1);
input_buf[INPUT_MAX - 1] = '\0';
int idx = (S->hist_head - 1 - S->hist_pos + HIST_MAX * 2) % HIST_MAX;
strncpy(S->input_buf, S->hist_buf[idx], INPUT_MAX - 1);
S->input_buf[INPUT_MAX - 1] = '\0';
}
input_len = (int)strlen(input_buf);
S->input_len = (int)strlen(S->input_buf);
break;
case KEY_BACKSPACE: case 127: case '\b':
if (input_len > 0) input_len--;
if (S->input_len > 0) S->input_len--;
break;
default:
if (ch >= 32 && ch < 256 && input_len < INPUT_MAX - 1)
input_buf[input_len++] = (char)ch;
if (ch >= 32 && ch < 256 && S->input_len < INPUT_MAX - 1)
S->input_buf[S->input_len++] = (char)ch;
break;
}
@ -2988,7 +3005,7 @@ static int confirm_quit_popup(void)
draw_box(w);
mvwprintw(w, 0, (mw - 8) / 2, " Quit? ");
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");
wrefresh(w);
@ -3013,8 +3030,8 @@ void interactive_loop(void)
if (sigint_pending) {
sigint_pending = 0;
if (confirm_quit_popup()) {
if (connected) tcp_disconnect();
if (logging) close_log();
if (S->connected) tcp_disconnect();
if (S->logging) close_log();
endwin();
exit(0);
}
@ -3025,17 +3042,17 @@ void interactive_loop(void)
FD_ZERO(&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_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);
if (ready < 0) continue;
/* Network data */
if (connected && sock >= 0 && FD_ISSET(sock, &readfds)) {
int n = recv(sock, recv_buf, sizeof(recv_buf)-1, 0);
if (S->connected && S->sock >= 0 && FD_ISSET(S->sock, &readfds)) {
int n = recv(S->sock, recv_buf, sizeof(recv_buf)-1, 0);
if (n > 0) {
bpq_process(recv_buf, n);
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 */
for (int i = 0; i < 32; i++)
snprintf(port_names[i], 32, "Port %d", i + 1);
@ -3122,8 +3148,8 @@ int main(int argc, char *argv[])
interactive_loop();
if (connected) tcp_disconnect();
if (logging) close_log();
if (S->connected) tcp_disconnect();
if (S->logging) close_log();
endwin();
return 0;
}

Loading…
Cancel
Save

Powered by TurnKey Linux.