@ -27,14 +27,17 @@
# include <fcntl.h>
# include <fcntl.h>
# include <signal.h>
# include <signal.h>
# include <sys/select.h>
# include <sys/select.h>
# include <sys/time.h>
# include <time.h>
# include <time.h>
# include <locale.h>
# include <wchar.h>
# include <ncurses.h>
# include <ncurses.h>
# ifndef PATH_MAX
# ifndef PATH_MAX
# define PATH_MAX 4096
# define PATH_MAX 4096
# endif
# endif
# define VERSION "0.0. 49 "
# define VERSION "0.0. 52 "
# define MAX_HOSTS 4
# define MAX_HOSTS 4
# define INPUT_MAX 512
# define INPUT_MAX 512
# define CONF_FILE ".termtcprc"
# define CONF_FILE ".termtcprc"
@ -88,6 +91,8 @@ 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 ;
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 */
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 */
char port_names [ 32 ] [ 32 ] ; /* names for ports 1-32, loaded from config or defaulted */
@ -96,6 +101,7 @@ char port_names[32][32]; /* names for ports 1-32, loaded from config or defa
static char ports_line_buf [ 256 ] ;
static char ports_line_buf [ 256 ] ;
static int ports_line_len = 0 ;
static int ports_line_len = 0 ;
static int ports_parse_mode = 0 ; /* 1 while inside "} Ports" response */
static int ports_parse_mode = 0 ; /* 1 while inside "} Ports" response */
static int ports_loaded = 0 ; /* set when PORTS list fully received */
/* ── Scrollback buffers ──────────────────────────────────── */
/* ── Scrollback buffers ──────────────────────────────────── */
static ScrlLine out_sbuf [ SCRL_MAX ] ;
static ScrlLine out_sbuf [ SCRL_MAX ] ;
@ -361,8 +367,12 @@ void tcp_disconnect(void)
connected = 0 ;
connected = 0 ;
connect_time = 0 ;
connect_time = 0 ;
in_mon_frame = 0 ;
in_mon_frame = 0 ;
mon_flen = 0 ;
ports_parse_mode = 0 ;
ports_parse_mode = 0 ;
ports_line_len = 0 ;
ports_line_len = 0 ;
ports_loaded = 0 ;
port_seen = 0 ;
for ( int i = 0 ; i < 32 ; i + + ) snprintf ( port_names [ i ] , 32 , " Port %d " , i + 1 ) ;
draw_status ( ) ;
draw_status ( ) ;
}
}
@ -381,6 +391,48 @@ void send_trace_options(void)
send ( sock , buf , len , 0 ) ;
send ( 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 ;
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 ) ;
}
/* Parse the 0xFF 0xFF port-info packet from SendPortsForMonitor.
* Format ( after both 0xFF bytes ) : " N|portnum desc|portnum desc|... "
* N = total entries ; portnum 0 = Mail Monitor ( skipped ) . */
static void parse_portinfo ( const char * data , int len )
{
const char * p = data , * e = data + len ;
int nports = 0 ;
while ( p < e & & * p > = ' 0 ' & & * p < = ' 9 ' ) nports = nports * 10 + ( * p + + - ' 0 ' ) ;
if ( p > = e | | * p ! = ' | ' ) return ;
p + + ;
for ( int i = 0 ; i < nports & & p < e ; i + + ) {
int n = 0 ;
while ( p < e & & * p > = ' 0 ' & & * p < = ' 9 ' ) n = n * 10 + ( * p + + - ' 0 ' ) ;
if ( p > = e | | * p ! = ' ' ) break ;
p + + ;
const char * desc = p ;
while ( p < e & & * p ! = ' | ' ) p + + ;
int dlen = ( int ) ( p - desc ) ;
if ( n > = 1 & & n < = 32 & & dlen > 0 ) {
int copy = dlen < 31 ? dlen : 31 ;
memcpy ( port_names [ n - 1 ] , desc , copy ) ;
port_names [ n - 1 ] [ copy ] = ' \0 ' ;
port_seen | = ( 1u < < ( n - 1 ) ) ;
}
if ( p < e & & * p = = ' | ' ) p + + ;
}
ports_loaded = 1 ;
}
static int bpq_color_attr ( unsigned char color_byte )
static int bpq_color_attr ( unsigned char color_byte )
{
{
/* Map BPQ Colours[] index (color_byte - 10) to nearest terminal color.
/* Map BPQ Colours[] index (color_byte - 10) to nearest terminal color.
@ -459,7 +511,8 @@ static void parse_session_line(const char *line, int len)
port_seen | = ( 1 < < ( n - 1 ) ) ;
port_seen | = ( 1 < < ( n - 1 ) ) ;
}
}
} else {
} else {
ports_parse_mode = 0 ; /* Non-matching line ends the list */
if ( ports_parse_mode ) ports_loaded = 1 ; /* list ended cleanly */
ports_parse_mode = 0 ;
}
}
}
}
@ -497,17 +550,46 @@ void bpq_process(const char *buf, int len)
feed_session_byte ( ( unsigned char ) * q ) ;
feed_session_byte ( ( unsigned char ) * q ) ;
append_out ( p , ( int ) ( ff - p ) , COLOR_PAIR ( CP_DEFAULT ) ) ;
append_out ( p , ( int ) ( ff - p ) , COLOR_PAIR ( CP_DEFAULT ) ) ;
}
}
in_mon_frame = 1 ;
/* 0xFF 0xFF = SendPortsForMonitor response; 0xFF 0x1B = monitor frame */
p = ff + 1 ;
p = ff + 1 ;
if ( p < end & & ( unsigned char ) * p = = 0xFF ) {
/* Port info packet — parse in place, consume to end of buffer */
parse_portinfo ( p + 1 , ( int ) ( end - p - 1 ) ) ;
p = end ;
} else {
in_mon_frame = 1 ;
mon_flen = 0 ;
}
} else {
} else {
const char * fe = memchr ( p , 0xFE , end - p ) ;
const char * fe = memchr ( p , 0xFE , end - p ) ;
int seg_len = fe ? ( int ) ( fe - p ) : ( int ) ( end - p ) ;
int seg_len = fe ? ( int ) ( fe - p ) : ( int ) ( end - p ) ;
if ( seg_len > 0 ) {
if ( seg_len > 0 ) {
detect_port_number ( p , seg_len ) ;
detect_port_number ( p , seg_len ) ;
append_mon ( p , seg_len , COLOR_PAIR ( CP_GREEN ) ) ;
/* Accumulate into the per-frame buffer */
int room = ( int ) sizeof ( mon_fbuf ) - 1 - mon_flen ;
if ( room > 0 ) {
int add = seg_len < room ? seg_len : room ;
memcpy ( mon_fbuf + mon_flen , p , add ) ;
mon_flen + = add ;
}
}
if ( fe ) {
/* Frame complete: apply client-side NODES filter then display */
if ( mon_flen > 0 ) {
mon_fbuf [ 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 ;
if ( mon_nodes | | ! is_nodes )
append_mon ( mon_fbuf , mon_flen , COLOR_PAIR ( CP_GREEN ) ) ;
}
mon_flen = 0 ;
in_mon_frame = 0 ;
p = fe + 1 ;
} else {
break ;
}
}
if ( fe ) { in_mon_frame = 0 ; p = fe + 1 ; }
else { break ; }
}
}
}
}
@ -534,26 +616,19 @@ void init_colors(void)
init_pair ( CP_SEP , COLOR_CYAN , COLOR_BLACK ) ;
init_pair ( CP_SEP , COLOR_CYAN , COLOR_BLACK ) ;
}
}
/* Draw a plain ASCII border (+, -, |) around window w.
* Title ( if any ) is written over the top border by the caller after this . */
/* Draw a box using Unicode cchar_t — bypasses ACS so it works on any UTF-8 terminal */
static void ascii _box( WINDOW * w )
static void draw _box( WINDOW * w )
{
{
int rows , cols ;
cchar_t vt , hz , ul , ur , ll , lr ;
getmaxyx ( w , rows , cols ) ;
setcchar ( & vt , L " │ " , 0 , 0 , NULL ) ; /* │ */
mvwaddch ( w , 0 , 0 , ' + ' ) ;
setcchar ( & hz , L " ─ " , 0 , 0 , NULL ) ; /* ─ */
mvwaddch ( w , 0 , cols - 1 , ' + ' ) ;
setcchar ( & ul , L " ┌ " , 0 , 0 , NULL ) ; /* ┌ */
mvwaddch ( w , rows - 1 , 0 , ' + ' ) ;
setcchar ( & ur , L " ┐ " , 0 , 0 , NULL ) ; /* ┐ */
mvwaddch ( w , rows - 1 , cols - 1 , ' + ' ) ;
setcchar ( & ll , L " └ " , 0 , 0 , NULL ) ; /* └ */
for ( int c = 1 ; c < cols - 1 ; c + + ) {
setcchar ( & lr , L " ┘ " , 0 , 0 , NULL ) ; /* ┘ */
mvwaddch ( w , 0 , c , ' - ' ) ;
wborder_set ( w , & vt , & vt , & hz , & hz , & ul , & ur , & ll , & lr ) ;
mvwaddch ( w , rows - 1 , c , ' - ' ) ;
}
for ( int r = 1 ; r < rows - 1 ; r + + ) {
mvwaddch ( w , r , 0 , ' | ' ) ;
mvwaddch ( w , r , cols - 1 , ' | ' ) ;
}
}
}
/* Fill a 1-row window with plain '-' dashes and an optional centred label.
/* Fill a 1-row window with plain '-' dashes and an optional centred label.
* No ACS — works on any terminal . */
* No ACS — works on any terminal . */
@ -563,7 +638,9 @@ static void fill_sep(WINDOW *w, const char *label)
werase ( w ) ;
werase ( w ) ;
wbkgd ( w , COLOR_PAIR ( CP_SEP ) ) ;
wbkgd ( w , COLOR_PAIR ( CP_SEP ) ) ;
wattron ( w , COLOR_PAIR ( CP_SEP ) ) ;
wattron ( w , COLOR_PAIR ( CP_SEP ) ) ;
whline ( w , ' - ' , COLS ) ;
cchar_t hz ;
setcchar ( & hz , L " ─ " , 0 , 0 , NULL ) ;
whline_set ( w , & hz , COLS ) ;
if ( label & & COLS > 4 ) {
if ( label & & COLS > 4 ) {
int llen = ( int ) strlen ( label ) ;
int llen = ( int ) strlen ( label ) ;
int col = ( COLS - llen ) / 2 ;
int col = ( COLS - llen ) / 2 ;
@ -672,8 +749,9 @@ static void scrl_draw_window(WINDOW *win, ScrlLine *buf, int count, int head,
int scroll_off )
int scroll_off )
{
{
if ( ! win ) return ;
if ( ! win ) return ;
scrollok ( win , FALSE ) ; /* disable scroll during batch redraw — prevents losing lines */
werase ( win ) ;
werase ( win ) ;
if ( count = = 0 ) { wrefresh( win ) ; return ; }
if ( count = = 0 ) { scrollok( win , TRUE ) ; wrefresh( win ) ; return ; }
int win_h , win_w ;
int win_h , win_w ;
getmaxyx ( win , win_h , win_w ) ;
getmaxyx ( win , win_h , win_w ) ;
@ -702,8 +780,12 @@ static void scrl_draw_window(WINDOW *win, ScrlLine *buf, int count, int head,
else { p + + ; }
else { p + + ; }
}
}
wattroff ( win , cur ) ;
wattroff ( win , cur ) ;
waddch ( win , ' \n ' ) ;
if ( i < last ) waddch ( win , ' \n ' ) ;
}
}
/* Move cursor to start of next blank row so live appends begin on a fresh line */
int drawn = last - first + 1 ;
if ( drawn < win_h ) wmove ( win , drawn , 0 ) ;
scrollok ( win , TRUE ) ; /* re-enable for live appends */
wrefresh ( win ) ;
wrefresh ( win ) ;
}
}
@ -884,6 +966,19 @@ static void refresh_all(void)
draw_input ( ) ;
draw_input ( ) ;
}
}
/* Full background repaint using redrawwin() — safe alternative to
* clearok ( curscr ) which resets ACS mode and breaks box ( ) borders . */
static void force_repaint ( void )
{
if ( status_win ) redrawwin ( status_win ) ;
if ( sep_win ) redrawwin ( sep_win ) ;
if ( out_win ) redrawwin ( out_win ) ;
if ( mon_win ) redrawwin ( mon_win ) ;
if ( sep2_win ) redrawwin ( sep2_win ) ;
if ( input_win ) redrawwin ( input_win ) ;
refresh_all ( ) ;
}
static void config_menu ( void )
static void config_menu ( void )
{
{
int mh = 18 , mw = 64 ;
int mh = 18 , mw = 64 ;
@ -926,7 +1021,7 @@ static void config_menu(void)
strncpy ( fields [ 3 ] , hosts [ cur_host ] . password , 79 ) ; fields [ 3 ] [ 79 ] = 0 ;
strncpy ( fields [ 3 ] , hosts [ cur_host ] . password , 79 ) ; fields [ 3 ] [ 79 ] = 0 ;
strncpy ( fields [ 4 ] , hosts [ cur_host ] . name , 49 ) ; fields [ 4 ] [ 49 ] = 0 ;
strncpy ( fields [ 4 ] , hosts [ cur_host ] . name , 49 ) ; fields [ 4 ] [ 49 ] = 0 ;
werase ( w ) ; ascii _box( w ) ;
werase ( w ) ; draw _box( w ) ;
mvwprintw ( w , 0 , ( mw - 14 ) / 2 , " Config Hosts " ) ;
mvwprintw ( w , 0 , ( mw - 14 ) / 2 , " Config Hosts " ) ;
mvwprintw ( w , 2 , 2 , " Host %d of %d PgUp/PgDn=switch N=new D=delete " ,
mvwprintw ( w , 2 , 2 , " Host %d of %d PgUp/PgDn=switch N=new D=delete " ,
cur_host + 1 , num_hosts ) ;
cur_host + 1 , num_hosts ) ;
@ -1002,18 +1097,17 @@ static void config_menu(void)
# undef CF_COMMIT
# undef CF_COMMIT
delwin ( w ) ;
delwin ( w ) ;
touchwin ( stdscr ) ; refresh ( ) ;
force_repaint ( ) ;
refresh_all ( ) ;
}
}
/* ═══════════════════════════════════════════════════════════
/* ═══════════════════════════════════════════════════════════
* Monitor options ( Ctrl + A - > M )
* Monitor options ( Ctrl + A - > M )
* ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ */
* ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ */
static void monitor_options_menu ( void )
/* Returns 'P' to switch to port page, 0 to close */
static int mon_options_page ( void )
{
{
/* Two pages: 0 = options, 1 = ports. Tab switches between them. */
static const char * labels [ ] = {
static const char * opt_labels [ ] = {
" Monitor TX " ,
" Monitor TX " ,
" Monitor Supervisor " ,
" Monitor Supervisor " ,
" Monitor Nodes " ,
" Monitor Nodes " ,
@ -1021,120 +1115,125 @@ static void monitor_options_menu(void)
" Enable Colour " ,
" Enable Colour " ,
" Show Monitor Pane " ,
" Show Monitor Pane " ,
} ;
} ;
int * opt_vals [ ] = {
int * vals [ ] = { & mon_tx , & mon_com , & mon_nodes , & mon_ui_only , & mon_colour , & mon_visible } ;
& mon_tx , & mon_com , & mon_nodes , & mon_ui_only , & mon_colour , & mon_visible
const int N = 6 ;
} ;
int mh = N + 5 , mw = 46 ;
const int N_OPTS = 6 ;
int mh = 14 , mw = 46 ;
int my = ( LINES - mh ) / 2 ; if ( my < 0 ) my = 0 ;
int my = ( LINES - mh ) / 2 ; if ( my < 0 ) my = 0 ;
int mx = ( COLS - mw ) / 2 ; if ( mx < 0 ) mx = 0 ;
int mx = ( COLS - mw ) / 2 ; if ( mx < 0 ) mx = 0 ;
force_repaint ( ) ;
WINDOW * w = newwin ( mh , mw , my , mx ) ;
WINDOW * w = newwin ( mh , mw , my , mx ) ;
wbkgd ( w , COLOR_PAIR ( CP_DEFAULT ) ) ;
wbkgd ( w , COLOR_PAIR ( CP_DEFAULT ) ) ;
keypad ( w , TRUE ) ;
keypad ( w , TRUE ) ;
nodelay ( w , FALSE ) ;
nodelay ( w , FALSE ) ;
int page = 0 ; /* 0=options 1=ports */
int cur = 0 ;
int cur = 0 ;
int running = 1 ;
for ( ; ; ) {
werase ( w ) ;
while ( running ) {
draw_box ( w ) ;
werase ( w ) ; ascii_box ( w ) ;
if ( page = = 0 ) {
mvwprintw ( w , 0 , ( mw - 18 ) / 2 , " Monitor Options " ) ;
mvwprintw ( w , 0 , ( mw - 18 ) / 2 , " Monitor Options " ) ;
mvwprintw ( w , 1 , 2 , " [Options] Ports Tab=switch " ) ;
for ( int i = 0 ; i < N ; i + + ) {
for ( int i = 0 ; i < N_OPTS ; i + + ) {
if ( i = = cur ) wattron ( w , A_REVERSE ) ;
if ( i = = cur ) wattron ( w , A_REVERSE ) ;
mvwprintw ( w , 3 + i , 3 , " [%c] %s " ,
mvwprintw ( w , 2 + i , 3 , " [%c] %s " , * vals [ i ] ? ' X ' : ' ' , labels [ i ] ) ;
* opt_vals [ i ] ? ' X ' : ' ' , opt_labels [ i ] ) ;
if ( i = = cur ) wattroff ( w , A_REVERSE ) ;
if ( i = = cur ) wattroff ( w , A_REVERSE ) ;
}
}
mvwprintw ( w , mh - 3 , 3 , " Up/Down = navigate " ) ;
mvwprintw ( w , mh - 3 , 3 , " Up/Down=move Space=toggle " ) ;
mvwprintw ( w , mh - 2 , 3 , " Space = toggle Esc/Q = close " ) ;
mvwprintw ( w , mh - 2 , 3 , " Tab=Port Select Esc/Q=close " ) ;
} else {
wrefresh ( w ) ;
/* Build list of ports to show: detected ones, or 1-8 if none yet */
int ch = wgetch ( w ) ;
switch ( ch ) {
case KEY_UP : cur = ( cur - 1 + N ) % N ; break ;
case KEY_DOWN : cur = ( cur + 1 ) % N ; break ;
case ' \t ' :
delwin ( w ) ;
return ' P ' ;
case ' ' : case ' \r ' : case ' \n ' : case KEY_ENTER :
if ( cur = = 5 ) {
mon_visible = ! mon_visible ;
delwin ( w ) ;
create_windows ( ) ;
force_repaint ( ) ;
return 0 ;
}
* vals [ cur ] = ! ( * vals [ cur ] ) ;
send_trace_options ( ) ;
break ;
case 27 : case ' Q ' : case ' q ' :
delwin ( w ) ;
force_repaint ( ) ;
return 0 ;
}
}
}
/* Returns 'O' to switch to options page, 0 to close */
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 ( 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 ;
/* Resize window if needed */
int mh = n_ports + 5 ; if ( mh < 10 ) mh = 10 ;
int need_h = 4 + n_ports + 1 ;
int mw = 54 ;
if ( need_h < 8 ) need_h = 8 ;
int my = ( LINES - mh ) / 2 ; if ( my < 0 ) my = 0 ;
if ( need_h ! = mh ) {
int mx = ( COLS - mw ) / 2 ; if ( mx < 0 ) mx = 0 ;
mh = need_h ;
delwin ( w ) ;
force_repaint ( ) ;
my = ( LINES - mh ) / 2 ; if ( my < 0 ) my = 0 ;
mx = ( COLS - mw ) / 2 ; if ( mx < 0 ) mx = 0 ;
WINDOW * w = newwin ( mh , mw , my , mx ) ;
w = newwin ( mh , mw , my , mx ) ;
wbkgd ( w , COLOR_PAIR ( CP_DEFAULT ) ) ;
wbkgd ( w , COLOR_PAIR ( CP_DEFAULT ) ) ;
keypad ( w , TRUE ) ;
keypad ( w , TRUE ) ;
nodelay ( w , FALSE ) ;
nodelay ( w , FALSE ) ;
werase ( w ) ; ascii_box ( w ) ;
}
int cur = 0 ;
for ( ; ; ) {
werase ( w ) ;
draw_box ( w ) ;
mvwprintw ( w , 0 , ( mw - 16 ) / 2 , " Port Selection " ) ;
mvwprintw ( w , 0 , ( mw - 16 ) / 2 , " Port Selection " ) ;
mvwprintw ( w , 1 , 2 , " Options [Ports] Tab=switch " ) ;
if ( port_seen = = 0 )
if ( 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 ] ;
if ( i = = cur ) wattron ( w , A_REVERSE ) ;
if ( i = = cur ) wattron ( w , A_REVERSE ) ;
mvwprintw ( w , 3 + i , 3 , " [%c] %2d %-27s " ,
mvwprintw ( w , 2 + i , 3 , " [%c] %2d %- 3 2s" ,
( port_mask > > ( pn - 1 ) ) & 1 ? ' X ' : ' ' ,
( port_mask > > ( pn - 1 ) ) & 1 ? ' X ' : ' ' ,
pn , port_names [ pn - 1 ] ) ;
pn , port_names [ pn - 1 ] ) ;
if ( i = = cur ) wattroff ( w , A_REVERSE ) ;
if ( i = = cur ) wattroff ( w , A_REVERSE ) ;
}
}
mvwprintw ( w , mh - 2 , 3 , " Space = toggle Esc/Q = clos e" ) ;
mvwprintw ( w , mh - 3 , 3 , " Up/Down=move Space=toggle" ) ;
}
mvwprintw ( w , mh - 2 , 3 , " Tab=Monitor Opts Esc/Q=close " ) ;
wrefresh ( w ) ;
wrefresh ( w ) ;
/* Build current port list for key handling */
int port_list2 [ 32 ] , n_ports2 = 0 ;
if ( page = = 1 ) {
for ( int b = 0 ; b < 32 ; b + + )
if ( port_seen & ( 1 < < b ) ) port_list2 [ n_ports2 + + ] = b + 1 ;
if ( n_ports2 = = 0 )
for ( int b = 0 ; b < 8 ; b + + ) port_list2 [ n_ports2 + + ] = b + 1 ;
}
int max_items = ( page = = 0 ) ? N_OPTS : n_ports2 ;
int ch = wgetch ( w ) ;
int ch = wgetch ( w ) ;
switch ( ch ) {
switch ( ch ) {
case KEY_UP :
case KEY_UP : cur = ( cur - 1 + n_ports ) % n_ports ; break ;
cur = ( cur - 1 + max_items ) % max_items ; break ;
case KEY_DOWN : cur = ( cur + 1 ) % n_ports ; break ;
case KEY_DOWN :
cur = ( cur + 1 ) % max_items ; break ;
case ' \t ' :
case ' \t ' :
page = 1 - page ; cur = 0 ; break ;
case ' ' : case ' \r ' : case ' \n ' : case KEY_ENTER :
if ( page = = 0 ) {
if ( cur = = 5 ) { /* Show Monitor Pane — needs window rebuild */
mon_visible = ! mon_visible ;
delwin ( w ) ;
delwin ( w ) ;
touchwin ( stdscr ) ; refresh ( ) ;
return ' O ' ;
create_windows ( ) ;
case ' ' : case ' \r ' : case ' \n ' : case KEY_ENTER :
refresh_all ( ) ;
port_mask ^ = ( 1 < < ( port_list [ cur ] - 1 ) ) ;
return ;
}
* opt_vals [ cur ] = ! ( * opt_vals [ cur ] ) ;
send_trace_options ( ) ;
} else {
int pn = ( cur < n_ports2 ) ? port_list2 [ cur ] : cur + 1 ;
port_mask ^ = ( 1 < < ( pn - 1 ) ) ;
send_trace_options ( ) ;
send_trace_options ( ) ;
}
break ;
break ;
case 27 : case ' Q ' : case ' q ' :
case 27 : case ' Q ' : case ' q ' :
running = 0 ; break ;
delwin ( w ) ;
force_repaint ( ) ;
return 0 ;
}
}
}
}
}
delwin ( w ) ;
static void monitor_options_menu ( void )
touchwin ( stdscr ) ; refresh ( ) ;
{
refresh_all ( ) ;
int next = ' O ' ;
while ( next ) {
if ( next = = ' O ' ) next = mon_options_page ( ) ;
else next = mon_ports_page ( ) ;
}
}
}
/* ═══════════════════════════════════════════════════════════
/* ═══════════════════════════════════════════════════════════
@ -1143,10 +1242,10 @@ static void monitor_options_menu(void)
static void show_help ( void )
static void show_help ( void )
{
{
int mh = 1 8 , mw = 52 ;
int mh = 1 9 , mw = 52 ;
WINDOW * w = newwin ( mh , mw , ( LINES - mh ) / 2 , ( COLS - mw ) / 2 ) ;
WINDOW * w = newwin ( mh , mw , ( LINES - mh ) / 2 , ( COLS - mw ) / 2 ) ;
wbkgd ( w , COLOR_PAIR ( CP_DEFAULT ) ) ;
wbkgd ( w , COLOR_PAIR ( CP_DEFAULT ) ) ;
ascii _box( w ) ;
draw _box( w ) ;
mvwprintw ( w , 0 , ( mw - 8 ) / 2 , " Help " ) ;
mvwprintw ( w , 0 , ( mw - 8 ) / 2 , " Help " ) ;
mvwprintw ( w , 2 , 3 , " Ctrl+A Open menu " ) ;
mvwprintw ( w , 2 , 3 , " Ctrl+A Open menu " ) ;
@ -1160,15 +1259,15 @@ static void show_help(void)
mvwprintw ( w , 11 , 3 , " Enter Send line to server " ) ;
mvwprintw ( w , 11 , 3 , " Enter Send line to server " ) ;
mvwprintw ( w , 12 , 3 , " Up / Down Input history " ) ;
mvwprintw ( w , 12 , 3 , " Up / Down Input history " ) ;
mvwprintw ( w , 13 , 3 , " PgUp / PgDn Scroll output window " ) ;
mvwprintw ( w , 13 , 3 , " PgUp / PgDn Scroll output window " ) ;
mvwprintw ( w , 14 , 3 , " Backspace Delete char " ) ;
mvwprintw ( w , 14 , 3 , " Home / End Scroll monitor window " ) ;
mvwprintw ( w , 15 , 3 , " Backspace Delete char " ) ;
mvwprintw ( w , mh - 2 , 3 , " Press any key to close " ) ;
mvwprintw ( w , mh - 2 , 3 , " Press any key to close " ) ;
wrefresh ( w ) ;
wrefresh ( w ) ;
nodelay ( w , FALSE ) ;
nodelay ( w , FALSE ) ;
wgetch ( w ) ;
wgetch ( w ) ;
delwin ( w ) ;
delwin ( w ) ;
touchwin ( stdscr ) ; refresh ( ) ;
force_repaint ( ) ;
refresh_all ( ) ;
}
}
/* ═══════════════════════════════════════════════════════════
/* ═══════════════════════════════════════════════════════════
@ -1212,9 +1311,33 @@ static void do_connect(int idx)
COLOR_PAIR ( CP_YELLOW ) ) ;
COLOR_PAIR ( CP_YELLOW ) ) ;
}
}
send_trace_options ( ) ;
/* P8=1 triggers BPQ's SendPortsForMonitor → arrives as 0xFF 0xFF packet */
/* Ask the node for its port list; response is parsed in parse_session_line() */
send_trace_options_with_ports ( ) ;
tcp_send ( " PORTS \r " , 6 ) ;
/* 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 . */
{
char rbuf [ 4096 ] ;
struct timeval t0 , now ;
gettimeofday ( & t0 , NULL ) ;
for ( ; ; ) {
gettimeofday ( & now , NULL ) ;
long elapsed = ( now . tv_sec - t0 . tv_sec ) * 1000000L
+ ( now . tv_usec - t0 . tv_usec ) ;
if ( elapsed > = 2000000L ) break ;
if ( 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 ) ;
if ( n < = 0 ) break ;
bpq_process ( rbuf , n ) ;
}
}
draw_status ( ) ;
draw_status ( ) ;
}
}
@ -1230,7 +1353,7 @@ static void ctrl_a_menu(void)
WINDOW * w = newwin ( mh , mw , my , mx ) ;
WINDOW * w = newwin ( mh , mw , my , mx ) ;
wbkgd ( w , COLOR_PAIR ( CP_DEFAULT ) ) ;
wbkgd ( w , COLOR_PAIR ( CP_DEFAULT ) ) ;
ascii _box( w ) ;
draw _box( w ) ;
mvwprintw ( w , 0 , ( mw - 14 ) / 2 , " TermTCP Menu " ) ;
mvwprintw ( w , 0 , ( mw - 14 ) / 2 , " TermTCP Menu " ) ;
mvwprintw ( w , 2 , 4 , " C - Connect " ) ;
mvwprintw ( w , 2 , 4 , " C - Connect " ) ;
mvwprintw ( w , 3 , 4 , " D - Disconnect " ) ;
mvwprintw ( w , 3 , 4 , " D - Disconnect " ) ;
@ -1245,18 +1368,27 @@ static void ctrl_a_menu(void)
nodelay ( w , FALSE ) ;
nodelay ( w , FALSE ) ;
int ch = wgetch ( w ) ;
int ch = wgetch ( w ) ;
delwin ( w ) ;
delwin ( w ) ;
touchwin ( stdscr ) ; refresh ( ) ;
force_repaint ( ) ;
refresh_all ( ) ;
switch ( ch | 0x20 ) {
switch ( ch | 0x20 ) {
case ' c ' :
case ' c ' :
if ( num_hosts = = 1 ) {
if ( num_hosts = = 1 ) {
do_connect ( 0 ) ;
do_connect ( 0 ) ;
} else if ( num_hosts > 1 ) {
} else if ( num_hosts > 1 ) {
int ph = num_hosts + 5 , pw = 38 ;
/* Auto-size width to fit the longest entry */
int pw = 20 ;
for ( int i = 0 ; i < num_hosts ; i + + ) {
int len = snprintf ( NULL , 0 , " %d - %s (%s:%s) " , i ,
hosts [ i ] . name , hosts [ i ] . host , hosts [ i ] . port ) + 6 ;
if ( len > pw ) pw = len ;
}
int hint_w = snprintf ( NULL , 0 , " 0-%d = pick Esc = cancel " , num_hosts - 1 ) + 6 ;
if ( hint_w > pw ) pw = hint_w ;
if ( pw > COLS ) pw = COLS ;
int ph = num_hosts + 5 ;
WINDOW * hw = newwin ( ph , pw , ( LINES - ph ) / 2 , ( COLS - pw ) / 2 ) ;
WINDOW * hw = newwin ( ph , pw , ( LINES - ph ) / 2 , ( COLS - pw ) / 2 ) ;
wbkgd ( hw , COLOR_PAIR ( CP_DEFAULT ) ) ;
wbkgd ( hw , COLOR_PAIR ( CP_DEFAULT ) ) ;
box ( hw , 0 , 0 ) ;
draw_ box( hw ) ;
mvwprintw ( hw , 0 , ( pw - 14 ) / 2 , " Select Host " ) ;
mvwprintw ( hw , 0 , ( pw - 14 ) / 2 , " Select Host " ) ;
for ( int i = 0 ; i < num_hosts ; i + + )
for ( int i = 0 ; i < num_hosts ; i + + )
mvwprintw ( hw , 2 + i , 3 , " %d - %s (%s:%s) " , i ,
mvwprintw ( hw , 2 + i , 3 , " %d - %s (%s:%s) " , i ,
@ -1266,8 +1398,7 @@ static void ctrl_a_menu(void)
nodelay ( hw , FALSE ) ;
nodelay ( hw , FALSE ) ;
int hch = wgetch ( hw ) ;
int hch = wgetch ( hw ) ;
delwin ( hw ) ;
delwin ( hw ) ;
touchwin ( stdscr ) ; refresh ( ) ;
force_repaint ( ) ;
refresh_all ( ) ;
if ( hch > = ' 0 ' & & hch < ' 0 ' + num_hosts )
if ( hch > = ' 0 ' & & hch < ' 0 ' + num_hosts )
do_connect ( hch - ' 0 ' ) ;
do_connect ( hch - ' 0 ' ) ;
}
}
@ -1358,6 +1489,27 @@ static void handle_key(int ch)
}
}
return ;
return ;
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 ;
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 ) {
int page = getmaxy ( mon_win ) ;
mon_scroll - = page ;
if ( mon_scroll < 0 ) mon_scroll = 0 ;
scrl_redraw_mon ( ) ;
if ( input_win ) wrefresh ( input_win ) ;
}
return ;
case ' \r ' : case ' \n ' : case KEY_ENTER :
case ' \r ' : case ' \n ' : case KEY_ENTER :
if ( input_len = = 0 ) return ;
if ( input_len = = 0 ) return ;
input_buf [ input_len ] = ' \0 ' ;
input_buf [ input_len ] = ' \0 ' ;
@ -1401,8 +1553,7 @@ static void handle_key(int ch)
if ( hist_pos < 0 ) break ;
if ( hist_pos < 0 ) break ;
hist_pos - - ;
hist_pos - - ;
if ( hist_pos < 0 ) {
if ( hist_pos < 0 ) {
strncpy ( input_buf , hist_save , INPUT_MAX - 1 ) ;
input_buf [ 0 ] = ' \0 ' ; /* always end with blank line */
input_buf [ INPUT_MAX - 1 ] = ' \0 ' ;
} else {
} else {
int idx = ( hist_head - 1 - hist_pos + HIST_MAX * 2 ) % HIST_MAX ;
int idx = ( hist_head - 1 - hist_pos + HIST_MAX * 2 ) % HIST_MAX ;
strncpy ( input_buf , hist_buf [ idx ] , INPUT_MAX - 1 ) ;
strncpy ( input_buf , hist_buf [ idx ] , INPUT_MAX - 1 ) ;
@ -1495,6 +1646,7 @@ int main(int argc, char *argv[])
load_config ( ) ;
load_config ( ) ;
setlocale ( LC_ALL , " " ) ; /* enable UTF-8 so ncursesw renders box chars correctly */
initscr ( ) ;
initscr ( ) ;
cbreak ( ) ;
cbreak ( ) ;
noecho ( ) ;
noecho ( ) ;