@ -1,11 +1,10 @@
/* termtcp_gui.cpp — FLTK GUI for TermTCP
/* termtcp_gui.cpp — FLTK GUI for TermTCP
* BPQTermTCP protocol client for BPQ32 packet radio nodes .
* Layout matches the ncurses CLI : status bar top , output pane , monitor pane ,
* Compile : see Makefile target ' gui '
* input line , function key bar bottom .
*/
*/
# include <FL/Fl.H>
# include <FL/Fl.H>
# include <FL/Fl_Double_Window.H>
# include <FL/Fl_Double_Window.H>
# include <FL/Fl_Menu_Bar.H>
# include <FL/Fl_Text_Display.H>
# include <FL/Fl_Text_Display.H>
# include <FL/Fl_Text_Buffer.H>
# include <FL/Fl_Text_Buffer.H>
# include <FL/Fl_Input.H>
# include <FL/Fl_Input.H>
@ -13,9 +12,11 @@
# include <FL/Fl_Return_Button.H>
# include <FL/Fl_Return_Button.H>
# include <FL/Fl_Box.H>
# include <FL/Fl_Box.H>
# include <FL/Fl_Tile.H>
# include <FL/Fl_Tile.H>
# include <FL/Fl_Group.H>
# include <FL/Fl_Hold_Browser.H>
# include <FL/Fl_Hold_Browser.H>
# include <FL/Fl_Window.H>
# include <FL/Fl_Window.H>
# include <FL/fl_ask.H>
# include <FL/fl_ask.H>
# include <FL/fl_draw.H>
extern " C " {
extern " C " {
# include <unistd.h>
# include <unistd.h>
@ -34,6 +35,12 @@ extern "C" {
# define RECV_SZ 4096
# define RECV_SZ 4096
# define MON_FBUF_SZ 4096
# define MON_FBUF_SZ 4096
/* ── Layout constants (pixels) ───────────────────────────── */
static const int STATUS_H = 22 ;
static const int SEP_H = 16 ;
static const int INPUT_H = 26 ;
static const int FKEY_H = 26 ;
/* ── Config ──────────────────────────────────────────────── */
/* ── Config ──────────────────────────────────────────────── */
struct HostEntry {
struct HostEntry {
@ -69,37 +76,50 @@ static int g_mon_flen = 0;
/* ── GUI widgets ─────────────────────────────────────────── */
/* ── GUI widgets ─────────────────────────────────────────── */
static Fl_Double_Window * main_win = nullptr ;
static Fl_Double_Window * main_win = nullptr ;
static Fl_Text_Display * out_disp = nullptr ;
static Fl_Box * status_bar = nullptr ;
static Fl_Text_Display * mon_disp = nullptr ;
static Fl_Text_Display * out_disp = nullptr ;
static Fl_Text_Buffer * out_buf = nullptr ;
static Fl_Text_Display * mon_disp = nullptr ;
static Fl_Text_Buffer * out_styl = nullptr ;
static Fl_Text_Buffer * out_buf = nullptr ;
static Fl_Text_Buffer * mon_buf = nullptr ;
static Fl_Text_Buffer * out_styl = nullptr ;
static Fl_Text_Buffer * mon_styl = nullptr ;
static Fl_Text_Buffer * mon_buf = nullptr ;
static Fl_Input * inp = nullptr ;
static Fl_Text_Buffer * mon_styl = nullptr ;
static Fl_Box * status_lbl = nullptr ;
static Fl_Input * inp = nullptr ;
static Fl_Menu_Bar * menu_bar = nullptr ;
static Fl_Button * fkey_btn [ 9 ] ; /* F1 F3 F4 F5 F6 F7 F8 F9 F10 */
/* Style table — index from 'A'.
/* Colors matching the CLI color pairs */
* A = default B = green ( monitor ) C = red ( error )
static const Fl_Color COL_STATUS_BG = fl_rgb_color ( 0 , 0 , 160 ) ;
* D = yellow ( status ) E = cyan ( info ) F = magenta */
static const Fl_Color COL_STATUS_FG = FL_WHITE ;
static const Fl_Color COL_OUT_BG = fl_rgb_color ( 10 , 10 , 10 ) ;
static const Fl_Color COL_OUT_FG = FL_WHITE ;
static const Fl_Color COL_MON_BG = fl_rgb_color ( 0 , 20 , 0 ) ;
static const Fl_Color COL_MON_FG = fl_rgb_color ( 0 , 220 , 0 ) ;
static const Fl_Color COL_SEP_BG = fl_rgb_color ( 0 , 140 , 140 ) ;
static const Fl_Color COL_SEP_FG = FL_WHITE ;
static const Fl_Color COL_INP_BG = fl_rgb_color ( 20 , 20 , 20 ) ;
static const Fl_Color COL_INP_FG = FL_WHITE ;
static const Fl_Color COL_FKEY_BG = fl_rgb_color ( 0 , 0 , 100 ) ;
static const Fl_Color COL_FKEY_CHIP = fl_rgb_color ( 200 , 200 , 255 ) ;
static const Fl_Color COL_FKEY_TEXT = fl_rgb_color ( 0 , 0 , 80 ) ;
/* Style table for Fl_Text_Display highlighting.
* Index from ' A ' : A = default B = green C = red D = yellow E = cyan F = magenta */
static const Fl_Text_Display : : Style_Table_Entry STYLES [ ] = {
static const Fl_Text_Display : : Style_Table_Entry STYLES [ ] = {
{ FL_FOREGROUND_COLOR , FL_COURIER , 13 } , /* A */
{ COL_OUT_FG, FL_COURIER , 13 } , /* A default */
{ fl_rgb_color ( 0 , 180 , 0 ) , FL_COURIER , 13 } , /* B */
{ fl_rgb_color ( 0 , 220, 0 ) , FL_COURIER , 13 } , /* B green */
{ FL_RED , FL_COURIER , 13 } , /* C */
{ fl_rgb_color( 220 , 60 , 60 ) , FL_COURIER , 13 } , /* C red */
{ fl_rgb_color ( 180 , 140 , 0 ) , FL_COURIER , 13 } , /* D */
{ fl_rgb_color ( 220, 200 , 0 ) , FL_COURIER , 13 } , /* D yellow */
{ fl_rgb_color ( 0 , 190 , 210 ) , FL_COURIER , 13 } , /* E */
{ fl_rgb_color ( 0 , 210, 220 ) , FL_COURIER , 13 } , /* E cyan */
{ FL_MAGENTA , FL_COURIER , 13 } , /* F */
{ fl_rgb_color( 200 , 80 , 200 ) , FL_COURIER , 13 } , /* F magenta */
} ;
} ;
# define N_STYLES 6
# define N_STYLES 6
/* ── Forward declarations ────────────────────────────────── */
/* ── Forward declarations ────────────────────────────────── */
static void tcp_disconnect ( void ) ;
static void tcp_disconnect ( void ) ;
static void update_status ( void ) ;
static void update_status ( void ) ;
static void cb_timer( void * ) ;
static void do_connect( int idx ) ;
/* ── Config I/O ──── ──────────────────────────────────────── */
/* ── Config loading ──────────────────────────────────────── */
static void load_config ( void )
static void load_config ( void )
{
{
@ -113,33 +133,24 @@ static void load_config(void)
char line [ 256 ] ;
char line [ 256 ] ;
int hi = - 1 ;
int hi = - 1 ;
while ( fgets ( line , sizeof ( line ) , fp ) ) {
while ( fgets ( line , sizeof ( line ) , fp ) ) {
/* strip trailing newline */
int l = ( int ) strlen ( line ) ;
int l = ( int ) strlen ( line ) ;
while ( l > 0 & & ( line [ l - 1 ] = = ' \n ' | | line [ l - 1 ] = = ' \r ' | | line [ l - 1 ] = = ' ' ) )
while ( l > 0 & & ( line [ l - 1 ] = = ' \n ' | | line [ l - 1 ] = = ' \r ' | | line [ l - 1 ] = = ' ' ) )
line [ - - l ] = ' \0 ' ;
line [ - - l ] = ' \0 ' ;
if ( line [ 0 ] = = ' [ ' ) {
if ( line [ 0 ] = = ' [ ' ) {
/* section header */
hi = ( strncmp ( line , " [Host " , 5 ) = = 0 & & num_hosts < MAX_HOSTS ) ? num_hosts + + : - 1 ;
if ( strncmp ( line , " [Host " , 5 ) = = 0 & & num_hosts < MAX_HOSTS ) {
if ( hi > = 0 ) memset ( & hosts [ hi ] , 0 , sizeof ( HostEntry ) ) ;
hi = num_hosts + + ;
memset ( & hosts [ hi ] , 0 , sizeof ( HostEntry ) ) ;
} else {
hi = - 1 ;
}
continue ;
continue ;
}
}
char * eq = strchr ( line , ' = ' ) ;
char * eq = strchr ( line , ' = ' ) ;
if ( ! eq ) continue ;
if ( ! eq ) continue ;
* eq = ' \0 ' ;
* eq = ' \0 ' ;
const char * key = line , * val = eq + 1 ;
const char * key = line , * val = eq + 1 ;
if ( hi > = 0 ) {
if ( hi > = 0 ) {
if ( ! strcmp ( key , " Host " ) ) { strncpy ( hosts [ hi ] . host , val , 99 ) ; }
if ( ! strcmp ( key , " Host " ) ) strncpy ( hosts [ hi ] . host , val , 99 ) ;
else if ( ! strcmp ( key , " Port " ) ) { strncpy ( hosts [ hi ] . port , val , 9 ) ; }
else if ( ! strcmp ( key , " Port " ) ) strncpy ( hosts [ hi ] . port , val , 9 ) ;
else if ( ! strcmp ( key , " Username " ) ) { strncpy ( hosts [ hi ] . username , val , 79 ) ; }
else if ( ! strcmp ( key , " Username " ) ) strncpy ( hosts [ hi ] . username , val , 79 ) ;
else if ( ! strcmp ( key , " Password " ) ) { strncpy ( hosts [ hi ] . password , val , 79 ) ; }
else if ( ! strcmp ( key , " Password " ) ) strncpy ( hosts [ hi ] . password , val , 79 ) ;
else if ( ! strcmp ( key , " Name " ) ) { strncpy ( hosts [ hi ] . name , val , 49 ) ; }
else if ( ! strcmp ( key , " Name " ) ) strncpy ( hosts [ hi ] . name , val , 49 ) ;
} else {
} else {
if ( ! strcmp ( key , " MonTX " ) ) mon_tx = atoi ( val ) ;
if ( ! strcmp ( key , " MonTX " ) ) mon_tx = atoi ( val ) ;
else if ( ! strcmp ( key , " MonCom " ) ) mon_com = atoi ( val ) ;
else if ( ! strcmp ( key , " MonCom " ) ) mon_com = atoi ( val ) ;
@ -149,8 +160,7 @@ static void load_config(void)
else if ( ! strcmp ( key , " PortMask " ) ) port_mask = atoi ( val ) ;
else if ( ! strcmp ( key , " PortMask " ) ) port_mask = atoi ( val ) ;
else if ( strncmp ( key , " PortName " , 8 ) = = 0 ) {
else if ( strncmp ( key , " PortName " , 8 ) = = 0 ) {
int pn = atoi ( key + 8 ) ;
int pn = atoi ( key + 8 ) ;
if ( pn > = 1 & & pn < = 32 )
if ( pn > = 1 & & pn < = 32 ) snprintf ( port_names [ pn - 1 ] , 32 , " %s " , val ) ;
snprintf ( port_names [ pn - 1 ] , 32 , " %s " , val ) ;
}
}
}
}
}
}
@ -163,50 +173,39 @@ static void append_styled(Fl_Text_Buffer *tbuf, Fl_Text_Buffer *sbuf,
const char * text , int len , char style )
const char * text , int len , char style )
{
{
if ( len < = 0 ) return ;
if ( len < = 0 ) return ;
/* Build a style string the same length as text (minus any stripped
* BPQ 0x1B colour - escape pairs which we skip ) . */
char * tmp = ( char * ) malloc ( len + 1 ) ;
char * tmp = ( char * ) malloc ( len + 1 ) ;
char * sty = ( char * ) malloc ( len + 1 ) ;
char * sty = ( char * ) malloc ( len + 1 ) ;
if ( ! tmp | | ! sty ) { free ( tmp ) ; free ( sty ) ; return ; }
if ( ! tmp | | ! sty ) { free ( tmp ) ; free ( sty ) ; return ; }
int ti = 0 ;
int ti = 0 ;
char cur = style ;
char cur = style ;
for ( int i = 0 ; i < len ; ) {
for ( int i = 0 ; i < len ; ) {
unsigned char c = ( unsigned char ) text [ i ] ;
unsigned char c = ( unsigned char ) text [ i ] ;
if ( c = = 0x1B & & i + 1 < len ) {
if ( c = = 0x1B & & i + 1 < len ) {
/* BPQ colour byte — map to style char */
/* BPQ colour byte → map to style */
unsigned char cb = ( unsigned char ) text [ i + 1 ] ;
int idx = ( int ) ( unsigned char ) text [ i + 1 ] - 10 ;
int idx = ( int ) cb - 10 ;
if ( idx < 0 ) cur = ' A ' ;
if ( idx < 0 ) cur = ' A ' ;
else if ( idx < 4 ) cur = ' E ' ; /* dark blue → cyan */
else if ( idx < 4 ) cur = ' E ' ;
else if ( idx < 20 ) cur = ' B ' ; /* green */
else if ( idx < 20 ) cur = ' B ' ;
else if ( idx < 60 ) cur = ' C ' ; /* reds */
else if ( idx < 60 ) cur = ' C ' ;
else cur = ' D ' ; /* bright → yellow */
else cur = ' D ' ;
i + = 2 ;
i + = 2 ;
} else if ( c = = ' \r ' ) {
} else if ( c = = ' \r ' ) {
i + + ; /* skip bare CR */
i + + ;
} else {
} else {
tmp [ ti ] = ( char ) c ;
tmp [ ti ] = ( char ) c ;
sty [ ti ] = cur ;
sty [ ti ] = cur ;
ti + + ;
ti + + ; i + + ;
i + + ;
}
}
}
}
tmp [ ti ] = ' \0 ' ;
tmp [ ti ] = ' \0 ' ; sty [ ti ] = ' \0 ' ;
sty [ ti ] = ' \0 ' ;
tbuf - > append ( tmp ) ;
tbuf - > append ( tmp ) ;
sbuf - > append ( sty ) ;
sbuf - > append ( sty ) ;
free ( tmp ) ; free ( sty ) ;
free ( tmp ) ;
free ( sty ) ;
}
}
static void out_append ( const char * text , int len , char style = ' A ' )
static void out_append ( const char * text , int len , char style = ' A ' )
{
{
append_styled ( out_buf , out_styl , text , len , style ) ;
append_styled ( out_buf , out_styl , text , len , style ) ;
/* scroll to bottom */
out_disp - > scroll ( out_buf - > count_lines ( 0 , out_buf - > length ( ) ) , 0 ) ;
out_disp - > scroll ( out_buf - > count_lines ( 0 , out_buf - > length ( ) ) , 0 ) ;
}
}
@ -237,86 +236,74 @@ static void send_trace_options(bool with_ports)
static void parse_portinfo ( const char * data , int len )
static void parse_portinfo ( const char * data , int len )
{
{
const char * p = data , * e = data + len ;
const char * p = data , * e = data + len ;
int nports = 0 ;
int nports = 0 ;
while ( p < e & & * p > = ' 0 ' & & * p < = ' 9 ' ) nports = nports * 10 + ( * p + + - ' 0 ' ) ;
while ( p < e & & * p > = ' 0 ' & & * p < = ' 9 ' ) nports = nports * 10 + ( * p + + - ' 0 ' ) ;
if ( p > = e | | * p ! = ' | ' ) return ;
if ( p > = e | | * p ! = ' | ' ) return ;
p + + ;
p + + ;
for ( int i = 0 ; i < nports & & p < e ; i + + ) {
for ( int i = 0 ; i < nports & & p < e ; i + + ) {
int n = 0 ;
int n = 0 ;
while ( p < e & & * p > = ' 0 ' & & * p < = ' 9 ' ) n = n * 10 + ( * p + + - ' 0 ' ) ;
while ( p < e & & * p > = ' 0 ' & & * p < = ' 9 ' ) n = n * 10 + ( * p + + - ' 0 ' ) ;
if ( p > = e | | * p ! = ' ' ) break ;
if ( p > = e | | * p ! = ' ' ) break ;
p + + ;
p + + ;
const char * desc = p ;
const char * desc = p ;
while ( p < e & & * p ! = ' | ' ) p + + ;
while ( p < e & & * p ! = ' | ' ) p + + ;
int dlen = ( int ) ( p - desc ) ;
int dlen = ( int ) ( p - desc ) ;
if ( n > = 1 & & n < = 32 & & dlen > 0 ) {
if ( n > = 1 & & n < = 32 & & dlen > 0 ) {
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 ' ;
}
}
if ( p < e & & * p = = ' | ' ) p + + ;
if ( p < e & & * p = = ' | ' ) p + + ;
}
}
}
}
static void bpq_process ( const char * buf , int len )
static void bpq_process ( const char * buf , int len )
{
{
const char * p = buf ;
const char * p = buf , * end = buf + len ;
const char * end = buf + len ;
while ( p < end ) {
while ( p < end ) {
if ( ! g_in_mon ) {
if ( ! g_in_mon ) {
const char * ff = ( const char * ) memchr ( p , 0xFF , end - p ) ;
const char * ff = ( const char * ) memchr ( p , 0xFF , end - p ) ;
if ( ! ff ) {
if ( ! ff ) { out_append ( p , ( int ) ( end - p ) ) ; break ; }
out_append ( p , ( int ) ( end - p ) ) ;
if ( ff > p ) out_append ( p , ( int ) ( ff - p ) ) ;
break ;
p = ff + 1 ;
}
if ( ff > p )
out_append ( p , ( int ) ( ff - p ) ) ;
p = ff + 1 ;
if ( p < end & & ( unsigned char ) * p = = 0xFF ) {
if ( p < end & & ( unsigned char ) * p = = 0xFF ) {
parse_portinfo ( p + 1 , ( int ) ( end - p - 1 ) ) ;
parse_portinfo ( p + 1 , ( int ) ( end - p - 1 ) ) ;
p = end ;
p = end ;
} else {
} else {
g_in_mon = 1 ;
g_in_mon = 1 ; g_mon_flen = 0 ;
g_mon_flen = 0 ;
}
}
} else {
} else {
const char * fe = ( const char * ) memchr ( p , 0xFE , end - p ) ;
const char * fe = ( const char * ) memchr ( p , 0xFE , end - p ) ;
int seg _len = fe ? ( int ) ( fe - p ) : ( int ) ( end - p ) ;
int seg = fe ? ( int ) ( fe - p ) : ( int ) ( end - p ) ;
if ( seg _len > 0 ) {
if ( seg > 0 ) {
int room = MON_FBUF_SZ - 1 - g_mon_flen ;
int room = MON_FBUF_SZ - 1 - g_mon_flen ;
int add = seg _len < room ? seg _len : room ;
int add = seg < room ? seg : room ;
memcpy ( g_mon_fbuf + g_mon_flen , p , add ) ;
memcpy ( g_mon_fbuf + g_mon_flen , p , add ) ;
g_mon_flen + = add ;
g_mon_flen + = add ;
}
}
if ( fe ) {
if ( fe ) {
if ( g_mon_flen > 0 ) {
if ( g_mon_flen > 0 ) {
/* NODES filter */
int is_nodes = 0 ;
int is_nodes = 0 ;
for ( int i = 0 ; i < = g_mon_flen - 6 & & ! is_nodes ; i + + )
for ( int i = 0 ; i < = g_mon_flen - 6 & & ! is_nodes ; i + + )
if ( memcmp ( g_mon_fbuf + i , " >NODES " , 6 ) = = 0 ) is_nodes = 1 ;
if ( memcmp ( g_mon_fbuf + i , " >NODES " , 6 ) = = 0 ) is_nodes = 1 ;
if ( mon_nodes | | ! is_nodes ) {
if ( mon_nodes | | ! is_nodes ) {
g_mon_fbuf [ g_mon_flen ] = ' \n ' ;
g_mon_fbuf [ g_mon_flen ] = ' \n ' ;
mon_append ( g_mon_fbuf , g_mon_flen + 1 ) ;
mon_append ( g_mon_fbuf , g_mon_flen + 1 ) ;
}
}
}
}
g_mon_flen = 0 ;
g_mon_flen = 0 ; g_in_mon = 0 ; p = fe + 1 ;
g_in_mon = 0 ;
} else break ;
p = fe + 1 ;
} else {
break ;
}
}
}
}
}
}
}
/* ── Socket handling ─────────────────────────────────────── */
/* ── Socket handling ─────────────────────────────────────── */
static void on_socket_ready ( int /*fd*/ , void * /*data*/ )
static void on_socket_ready ( int , void * )
{
{
char buf [ RECV_SZ ] ;
char buf [ RECV_SZ ] ;
int n = recv ( g_sock , buf , sizeof ( buf ) - 1 , 0 ) ;
int n = recv ( g_sock , buf , sizeof ( buf ) - 1 , 0 ) ;
if ( n > 0 ) {
if ( n > 0 ) {
bpq_process ( buf , n ) ;
bpq_process ( buf , n ) ;
} else if ( n = = 0 | | ( n < 0 & & errno ! = EAGAIN ) ) {
} else if ( n = = 0 | | ( n < 0 & & errno ! = EAGAIN ) ) {
@ -325,40 +312,25 @@ static void on_socket_ready(int /*fd*/, void * /*data*/)
}
}
}
}
static int tcp_connect_ to ( int idx )
static int tcp_connect_ host ( int idx )
{
{
if ( idx < 0 | | idx > = num_hosts ) return - 1 ;
struct addrinfo hints , * res = nullptr ;
struct addrinfo hints , * res = nullptr ;
memset ( & hints , 0 , sizeof ( hints ) ) ;
memset ( & hints , 0 , sizeof ( hints ) ) ;
hints . ai_family = AF_UNSPEC ;
hints . ai_family = AF_UNSPEC ;
hints . ai_socktype = SOCK_STREAM ;
hints . ai_socktype = SOCK_STREAM ;
if ( getaddrinfo ( hosts [ idx ] . host , hosts [ idx ] . port , & hints , & res ) ! = 0 | | ! res ) return - 1 ;
if ( getaddrinfo ( hosts [ idx ] . host , hosts [ idx ] . port , & hints , & res ) ! = 0 | | ! res )
return - 1 ;
int fd = socket ( res - > ai_family , res - > ai_socktype , res - > ai_protocol ) ;
int fd = socket ( res - > ai_family , res - > ai_socktype , res - > ai_protocol ) ;
if ( fd < 0 ) { freeaddrinfo ( res ) ; return - 1 ; }
if ( fd < 0 ) { freeaddrinfo ( res ) ; return - 1 ; }
if ( connect ( fd , res - > ai_addr , res - > ai_addrlen ) ! = 0 ) { close ( fd ) ; freeaddrinfo ( res ) ; return - 1 ; }
if ( connect ( fd , res - > ai_addr , res - > ai_addrlen ) ! = 0 ) {
close ( fd ) ; freeaddrinfo ( res ) ; return - 1 ;
}
freeaddrinfo ( res ) ;
freeaddrinfo ( res ) ;
return fd ;
return fd ;
}
}
static void tcp_disconnect ( void )
static void tcp_disconnect ( void )
{
{
if ( g_sock > = 0 ) {
if ( g_sock > = 0 ) { Fl : : remove_fd ( g_sock ) ; close ( g_sock ) ; g_sock = - 1 ; }
Fl : : remove_fd ( g_sock ) ;
g_connected = 0 ; g_host_idx = - 1 ; g_conn_time = 0 ;
close ( g_sock ) ;
g_in_mon = 0 ; g_mon_flen = 0 ;
g_sock = - 1 ;
}
g_connected = 0 ;
g_host_idx = - 1 ;
g_conn_time = 0 ;
g_in_mon = 0 ;
g_mon_flen = 0 ;
update_status ( ) ;
update_status ( ) ;
}
}
@ -372,25 +344,24 @@ static void tcp_send(const char *text, int len)
static void update_status ( void )
static void update_status ( void )
{
{
static char buf [ 16 0] ;
static char buf [ 20 0] ;
if ( ! g_connected | | g_host_idx < 0 ) {
if ( ! g_connected | | g_host_idx < 0 ) {
snprintf ( buf , sizeof ( buf ) , " Not connected" ) ;
snprintf ( buf , sizeof ( buf ) , " TermTCP %s — Not connected" , GUI_VERSION ) ;
} else {
} else {
time_t elapsed = time ( nullptr ) - g_conn_time ;
time_t el = time ( nullptr ) - g_conn_time ;
int h = ( int ) ( elapsed / 3600 ) ;
int h = ( int ) ( el / 3600 ) , m = ( int ) ( ( el % 3600 ) / 60 ) , s = ( int ) ( el % 60 ) ;
int m = ( int ) ( ( elapsed % 3600 ) / 60 ) ;
int s = ( int ) ( elapsed % 60 ) ;
time_t now = time ( nullptr ) ;
time_t now = time ( nullptr ) ;
struct tm * utc = gmtime ( & now ) ;
struct tm * utc = gmtime ( & now ) ;
const char * nm = hosts [ g_host_idx ] . name [ 0 ]
? hosts [ g_host_idx ] . name : hosts [ g_host_idx ] . host ;
snprintf ( buf , sizeof ( buf ) ,
snprintf ( buf , sizeof ( buf ) ,
" %s (%s:%s) %02d:%02d:%02d %02d:%02d UTC " ,
" TermTCP %s | %s (%s:%s) | %02d:%02d:%02d | %02d:%02dz " ,
hosts[ g_host_idx ] . name ,
GUI_VERSION, nm ,
hosts [ g_host_idx ] . host , hosts [ g_host_idx ] . port ,
hosts [ g_host_idx ] . host , hosts [ g_host_idx ] . port ,
h , m , s ,
h , m , s , utc - > tm_hour , utc - > tm_min ) ;
utc - > tm_hour , utc - > tm_min ) ;
}
}
status_ lbl - > copy_label ( buf ) ;
status_ bar - > copy_label ( buf ) ;
status_ lbl - > redraw ( ) ;
status_ bar - > redraw ( ) ;
}
}
static void cb_timer ( void * )
static void cb_timer ( void * )
@ -401,29 +372,21 @@ static void cb_timer(void *)
/* ── Connect dialog ──────────────────────────────────────── */
/* ── Connect dialog ──────────────────────────────────────── */
static int g_dlg_result = - 1 ;
static int g_pick = - 1 ;
static void cb_dlg_connect ( Fl_Widget * w , void * browser_ptr )
{
Fl_Hold_Browser * br = ( Fl_Hold_Browser * ) browser_ptr ;
int sel = br - > value ( ) ;
g_dlg_result = ( sel > 0 ) ? sel - 1 : - 1 ;
w - > window ( ) - > hide ( ) ;
}
static void cb_ dlg_cancel ( Fl_Widget * w , void * )
static void cb_pick_ok ( Fl_Widget * w , void * br )
{
{
g_dlg_result = - 1 ;
int v = ( ( Fl_Hold_Browser * ) br ) - > value ( ) ;
g_pick = ( v > 0 ) ? v - 1 : - 1 ;
w - > window ( ) - > hide ( ) ;
w - > window ( ) - > hide ( ) ;
}
}
static void cb_pick_cancel ( Fl_Widget * w , void * )
{ g_pick = - 1 ; w - > window ( ) - > hide ( ) ; }
static void cb_dlg_dblclick ( Fl_Widget * w , void * )
static void cb_ pick_dbl ( Fl_Widget * w , void * )
{
{
Fl_Hold_Browser * br = ( Fl_Hold_Browser * ) w ;
Fl_Hold_Browser * br = ( Fl_Hold_Browser * ) w ;
if ( br - > value ( ) > 0 ) {
if ( br - > value ( ) > 0 ) { g_pick = br - > value ( ) - 1 ; br - > window ( ) - > hide ( ) ; }
g_dlg_result = br - > value ( ) - 1 ;
br - > window ( ) - > hide ( ) ;
}
}
}
static int show_connect_dialog ( void )
static int show_connect_dialog ( void )
@ -432,255 +395,299 @@ static int show_connect_dialog(void)
fl_message ( " No hosts configured. \n Edit %s to add BPQ nodes. " , cfg_path ) ;
fl_message ( " No hosts configured. \n Edit %s to add BPQ nodes. " , cfg_path ) ;
return - 1 ;
return - 1 ;
}
}
Fl_Window * dlg = new Fl_Window ( 340 , 250 , " Connect to Host " ) ;
Fl_Window * dlg = new Fl_Window ( 320 , 240 , " Connect to Host " ) ;
dlg - > begin ( ) ;
dlg - > begin ( ) ;
Fl_Hold_Browser * br = new Fl_Hold_Browser ( 10 , 10 , 320 , 180 ) ;
Fl_Hold_Browser * br = new Fl_Hold_Browser ( 10 , 10 , 300 , 170 ) ;
for ( int i = 0 ; i < num_hosts ; i + + ) {
for ( int i = 0 ; i < num_hosts ; i + + ) {
char lbl [ 140 ] ;
char lbl [ 140 ] ;
snprintf ( lbl , sizeof ( lbl ) , " %s (%s:%s)" ,
snprintf ( lbl , sizeof ( lbl ) , " %s (%s:%s)" ,
hosts [ i ] . name [ 0 ] ? hosts [ i ] . name : hosts [ i ] . host ,
hosts [ i ] . name [ 0 ] ? hosts [ i ] . name : hosts [ i ] . host ,
hosts [ i ] . host , hosts [ i ] . port ) ;
hosts [ i ] . host , hosts [ i ] . port ) ;
br - > add ( lbl ) ;
br - > add ( lbl ) ;
}
}
br - > select ( 1 ) ;
br - > select ( 1 ) ;
br - > callback ( cb_dlg_dblclick ) ;
br - > textfont ( FL_COURIER ) ; br - > textsize ( 13 ) ;
br - > when ( FL_WHEN_CHANGED ) ; /* double-click triggers when() */
br - > callback ( cb_pick_dbl ) ;
Fl_Return_Button * ok = new Fl_Return_Button ( 150 , 205 , 90 , 28 , " Connect " ) ;
Fl_Return_Button * btn_ok = new Fl_Return_Button ( 130 , 195 , 90 , 30 , " Connect " ) ;
ok - > callback ( cb_pick_ok , br ) ;
btn_ok - > callback ( cb_dlg_connect , br ) ;
Fl_Button * cancel = new Fl_Button ( 250 , 205 , 80 , 28 , " Cancel " ) ;
cancel - > callback ( cb_pick_cancel ) ;
Fl_Button * btn_cancel = new Fl_Button ( 230 , 195 , 80 , 30 , " Cancel " ) ;
dlg - > end ( ) ; dlg - > set_modal ( ) ;
btn_cancel - > callback ( cb_dlg_cancel ) ;
g_pick = - 1 ;
dlg - > end ( ) ;
dlg - > set_modal ( ) ;
g_dlg_result = - 1 ;
dlg - > show ( ) ;
dlg - > show ( ) ;
while ( dlg - > shown ( ) ) Fl : : wait ( ) ;
while ( dlg - > shown ( ) ) Fl : : wait ( ) ;
int r esult = g_dlg_result ;
int r = g_pick ;
delete dlg ;
delete dlg ;
return r esult ;
return r ;
}
}
/* ── Menu callbacks ──────────────────────────────────────── */
/* ── Actions ─────── ──────────────────────────────────────── */
static void do_connect ( int idx )
static void do_connect ( int idx )
{
{
if ( g_connected ) tcp_disconnect ( ) ;
if ( g_connected ) tcp_disconnect ( ) ;
char msg [ 160 ] ;
char msg [ 160 ] ;
snprintf ( msg , sizeof ( msg ) , " Connecting to %s (%s:%s)... \n " ,
snprintf ( msg , sizeof ( msg ) , " Connecting to %s (%s:%s)... \n " ,
hosts [ idx ] . name [ 0 ] ? hosts [ idx ] . name : hosts [ idx ] . host ,
hosts [ idx ] . name [ 0 ] ? hosts [ idx ] . name : hosts [ idx ] . host ,
hosts [ idx ] . host , hosts [ idx ] . port ) ;
hosts [ idx ] . host , hosts [ idx ] . port ) ;
out_append ( msg , ( int ) strlen ( msg ) , ' E ' ) ;
out_append ( msg , ( int ) strlen ( msg ) , ' E ' ) ;
int fd = tcp_connect_host ( idx ) ;
int fd = tcp_connect_to ( idx ) ;
if ( fd < 0 ) {
if ( fd < 0 ) {
snprintf ( msg , sizeof ( msg ) , " Connection failed: %s \n " , strerror ( errno ) ) ;
snprintf ( msg , sizeof ( msg ) , " Connection failed: %s \n " , strerror ( errno ) ) ;
out_append ( msg , ( int ) strlen ( msg ) , ' C ' ) ;
out_append ( msg , ( int ) strlen ( msg ) , ' C ' ) ;
return ;
return ;
}
}
g_sock = fd ; g_connected = 1 ; g_host_idx = idx ; g_conn_time = time ( nullptr ) ;
g_sock = fd ;
g_connected = 1 ;
g_host_idx = idx ;
g_conn_time = time ( nullptr ) ;
Fl : : add_fd ( g_sock , FL_READ , on_socket_ready , nullptr ) ;
Fl : : add_fd ( g_sock , FL_READ , on_socket_ready , nullptr ) ;
send_trace_options ( true ) ;
send_trace_options ( true ) ;
update_status ( ) ;
update_status ( ) ;
snprintf ( msg , sizeof ( msg ) , " Connected. \n " ) ;
snprintf ( msg , sizeof ( msg ) , " Connected. \n " ) ;
out_append ( msg , ( int ) strlen ( msg ) , ' E ' ) ;
out_append ( msg , ( int ) strlen ( msg ) , ' E ' ) ;
}
}
static void cb_connect ( Fl_Widget * , void * )
/* F-key callbacks */
static void cb_f1 ( Fl_Widget * , void * ) /* Connect / Disconnect */
{
{
int idx = show_connect_dialog ( ) ;
if ( g_connected ) {
if ( idx > = 0 ) do_connect ( idx ) ;
out_append ( " *** Disconnected \n " , 17 , ' C ' ) ;
tcp_disconnect ( ) ;
} else {
int idx = show_connect_dialog ( ) ;
if ( idx > = 0 ) do_connect ( idx ) ;
}
}
}
static void cb_disconnect ( Fl_Widget * , void * )
static void cb_ f3 ( Fl_Widget * , void * ) /* Toggle monitor */
{
{
if ( ! g_connected ) { fl_message ( " Not connected. " ) ; return ; }
mon_visible = ! mon_visible ;
out_append ( " *** Disconnected \n " , 17 , ' C ' ) ;
out_append ( mon_visible ? " [Monitor ON] \n " : " [Monitor OFF] \n " , 14 , ' D ' ) ;
tcp_disconnect ( ) ;
}
}
static void cb_ quit ( Fl_Widget * , void * )
static void cb_ f4 ( Fl_Widget * , void * ) /* Config — placeholder */
{
{
if ( g_connected ) tcp_disconnect ( ) ;
fl_message ( " Host config editor coming soon. \n Edit %s manually for now. " , cfg_path ) ;
exit ( 0 ) ;
}
}
static void cb_ clear_out ( Fl_Widget * , void * )
static void cb_ f5 ( Fl_Widget * , void * ) /* Log — placeholder */
{
{
out_buf - > remove ( 0 , out_buf - > length ( ) ) ;
fl_message ( " Session logging coming soon. " ) ;
out_styl - > remove ( 0 , out_styl - > length ( ) ) ;
}
}
static void cb_ clear_mon ( Fl_Widget * , void * )
static void cb_ f6 ( Fl_Widget * , void * ) /* Forms — placeholder */
{
{
mon_buf - > remove ( 0 , mon_buf - > length ( ) ) ;
fl_message ( " Forms coming soon. " ) ;
mon_styl - > remove ( 0 , mon_styl - > length ( ) ) ;
}
}
static void cb_ toggle_mon ( Fl_Widget * , void * )
static void cb_ f7 ( Fl_Widget * , void * ) /* New session — placeholder */
{
{
mon_visible = ! mon_visible ;
fl_message ( " Multiple sessions coming soon. " ) ;
if ( mon_visible )
out_append ( " [Monitor ON] \n " , 13 , ' D ' ) ;
else
out_append ( " [Monitor OFF] \n " , 14 , ' D ' ) ;
}
}
/* ── Input send ──────────────────────────────────────────── */
static void cb_f8 ( Fl_Widget * , void * ) { } /* Prev session — future */
static void cb_f9 ( Fl_Widget * , void * ) { } /* Next session — future */
static void cb_f10 ( Fl_Widget * , void * ) /* Quit */
{
if ( g_connected ) tcp_disconnect ( ) ;
exit ( 0 ) ;
}
/* Send input line */
static void cb_send ( Fl_Widget * , void * )
static void cb_send ( Fl_Widget * , void * )
{
{
const char * text = inp - > value ( ) ;
const char * text = inp - > value ( ) ;
if ( ! text | | text [ 0 ] = = ' \0 ' ) return ;
if ( ! text | | ! text [ 0 ] ) return ;
if ( ! g_connected ) {
if ( ! g_connected ) {
out_append ( " Not connected. \n " , 15 , ' C ' ) ;
out_append ( " Not connected — use F1 to connect. \n " , 36 , ' C ' ) ;
inp - > value ( " " ) ;
inp - > value ( " " ) ; return ;
return ;
}
}
/* Echo locally */
char echo [ 514 ] ;
char echo [ 514 ] ;
int elen = snprintf ( echo , sizeof ( echo ) , " > %s \n " , text ) ;
int elen = snprintf ( echo , sizeof ( echo ) , " > %s \n " , text ) ;
out_append ( echo , elen , ' D ' ) ;
out_append ( echo , elen , ' D ' ) ;
int tlen = ( int ) strlen ( text ) ;
/* Send to server with CR */
int tlen = ( int ) strlen ( text ) ;
char * tbuf = ( char * ) malloc ( tlen + 2 ) ;
char * tbuf = ( char * ) malloc ( tlen + 2 ) ;
if ( tbuf ) {
if ( tbuf ) {
memcpy ( tbuf , text , tlen ) ;
memcpy ( tbuf , text , tlen ) ; tbuf [ tlen ] = ' \r ' ; tbuf [ tlen + 1 ] = ' \0 ' ;
tbuf [ tlen ] = ' \r ' ;
tcp_send ( tbuf , tlen + 1 ) ; free ( tbuf ) ;
tbuf [ tlen + 1 ] = ' \0 ' ;
tcp_send ( tbuf , tlen + 1 ) ;
free ( tbuf ) ;
}
}
inp - > value ( " " ) ;
inp - > value ( " " ) ;
}
}
/* ── Main window ─────────────────────────────────────────── */
/* ── Build window ────────────────────────────────────────── */
static Fl_Menu_Item menu_items [ ] = {
{ " &File " , 0 , nullptr , nullptr , FL_SUBMENU } ,
{ " &Connect... " , FL_CTRL + ' k ' , cb_connect } ,
{ " &Disconnect " , FL_CTRL + ' d ' , cb_disconnect } ,
{ " Clear output " , 0 , cb_clear_out } ,
{ " Clear monitor " , 0 , cb_clear_mon } ,
{ " E&xit " , FL_CTRL + ' q ' , cb_quit , nullptr , FL_MENU_DIVIDER } ,
{ nullptr } ,
{ " &Monitor " , 0 , nullptr , nullptr , FL_SUBMENU } ,
{ " Toggle monitor pane " , FL_CTRL + ' m ' , cb_toggle_mon } ,
{ nullptr } ,
{ " &Help " , 0 , nullptr , nullptr , FL_SUBMENU } ,
{ " About " , 0 , [ ] ( Fl_Widget * , void * ) {
fl_message ( " TermTCP GUI v " GUI_VERSION " \n "
" BPQTermTCP client for BPQ32 packet radio nodes. \n "
" Config: ~/.BPQTermTCP.ini " ) ;
} } ,
{ nullptr } ,
{ nullptr }
} ;
static void build_window ( void )
static void build_window ( void )
{
{
const int W = 900 , H = 650 ;
const int W = 920 ;
const int MENU_H = 25 ;
const int H = 650 ;
const int STATUS_H = 22 ;
const int INPUT_H = 32 ;
/* Fixed heights */
const int SEND_W = 70 ;
const int FIXED = STATUS_H + SEP_H + INPUT_H + SEP_H + FKEY_H ;
const int MON_FRAC = 3 ; /* monitor gets 1/3 of inner height */
const int TILE_H = H - FIXED ; /* height for output+sep+monitor tile */
const int OUT_H = TILE_H * 62 / 100 ; /* ~62% output */
const int MON_H = TILE_H - OUT_H ; /* ~38% monitor */
main_win = new Fl_Double_Window ( W , H , " TermTCP " GUI_VERSION ) ;
main_win = new Fl_Double_Window ( W , H , " TermTCP " GUI_VERSION ) ;
main_win - > color ( fl_rgb_color ( 10 , 10 , 10 ) ) ;
main_win - > begin ( ) ;
main_win - > begin ( ) ;
main_win - > color ( FL_WHITE ) ;
/* Menu bar */
menu_bar = new Fl_Menu_Bar ( 0 , 0 , W , MENU_H ) ;
menu_bar - > menu ( menu_items ) ;
menu_bar - > color ( fl_rgb_color ( 230 , 230 , 230 ) ) ;
/* Resizable split area */
int tile_y = MENU_H ;
int tile_h = H - MENU_H - INPUT_H - STATUS_H ;
int out_h = tile_h - tile_h / MON_FRAC ;
int mon_h = tile_h / MON_FRAC ;
Fl_Tile * tile = new Fl_Tile ( 0 , tile_y , W , tile_h ) ;
int y = 0 ;
/* Output display */
/* ── Status bar (top, blue) ── */
out_disp = new Fl_Text_Display ( 0 , tile_y , W , out_h ) ;
status_bar = new Fl_Box ( 0 , y , W , STATUS_H ) ;
status_bar - > box ( FL_FLAT_BOX ) ;
status_bar - > color ( COL_STATUS_BG ) ;
status_bar - > labelcolor ( COL_STATUS_FG ) ;
status_bar - > labelfont ( FL_HELVETICA_BOLD ) ;
status_bar - > labelsize ( 12 ) ;
status_bar - > align ( FL_ALIGN_LEFT | FL_ALIGN_INSIDE ) ;
status_bar - > copy_label ( " TermTCP " GUI_VERSION " — Not connected " ) ;
y + = STATUS_H ;
/* ── Resizable tile: output group + monitor group ── */
Fl_Tile * tile = new Fl_Tile ( 0 , y , W , TILE_H ) ;
tile - > begin ( ) ;
/* Output group: display + "Output" separator at bottom */
Fl_Group * out_grp = new Fl_Group ( 0 , y , W , OUT_H ) ;
out_grp - > begin ( ) ;
out_disp = new Fl_Text_Display ( 0 , y , W , OUT_H - SEP_H ) ;
out_disp - > box ( FL_FLAT_BOX ) ;
out_disp - > color ( COL_OUT_BG ) ;
out_disp - > textcolor ( COL_OUT_FG ) ;
out_disp - > textfont ( FL_COURIER ) ;
out_disp - > textsize ( 13 ) ;
out_disp - > cursor_color ( COL_OUT_FG ) ;
out_disp - > scrollbar_width ( 12 ) ;
out_disp - > wrap_mode ( Fl_Text_Display : : WRAP_AT_BOUNDS , 0 ) ;
out_buf = new Fl_Text_Buffer ( ) ;
out_buf = new Fl_Text_Buffer ( ) ;
out_styl = new Fl_Text_Buffer ( ) ;
out_styl = new Fl_Text_Buffer ( ) ;
out_disp - > buffer ( out_buf ) ;
out_disp - > buffer ( out_buf ) ;
out_disp - > highlight_data ( out_styl , STYLES , N_STYLES , ' A ' , nullptr , 0 ) ;
out_disp - > highlight_data ( out_styl , STYLES , N_STYLES , ' A ' , nullptr , 0 ) ;
out_disp - > textfont ( FL_COURIER ) ;
out_disp - > textsize ( 13 ) ;
out_disp - > color ( fl_rgb_color ( 20 , 20 , 20 ) ) ;
out_disp - > textcolor ( FL_WHITE ) ;
out_disp - > cursor_color ( FL_WHITE ) ;
out_disp - > scrollbar_width ( 14 ) ;
out_disp - > wrap_mode ( Fl_Text_Display : : WRAP_AT_BOUNDS , 0 ) ;
/* Monitor display */
Fl_Box * out_sep = new Fl_Box ( 0 , y + OUT_H - SEP_H , W , SEP_H , " Output " ) ;
mon_disp = new Fl_Text_Display ( 0 , tile_y + out_h , W , mon_h ) ;
out_sep - > box ( FL_FLAT_BOX ) ;
out_sep - > color ( COL_SEP_BG ) ;
out_sep - > labelcolor ( COL_SEP_FG ) ;
out_sep - > labelfont ( FL_HELVETICA_BOLD ) ;
out_sep - > labelsize ( 11 ) ;
out_grp - > resizable ( out_disp ) ;
out_grp - > end ( ) ;
/* Monitor group: display fills group, "Monitor" in its top label area */
Fl_Group * mon_grp = new Fl_Group ( 0 , y + OUT_H , W , MON_H ) ;
mon_grp - > begin ( ) ;
mon_disp = new Fl_Text_Display ( 0 , y + OUT_H , W , MON_H ) ;
mon_disp - > box ( FL_FLAT_BOX ) ;
mon_disp - > color ( COL_MON_BG ) ;
mon_disp - > textcolor ( COL_MON_FG ) ;
mon_disp - > textfont ( FL_COURIER ) ;
mon_disp - > textsize ( 12 ) ;
mon_disp - > cursor_color ( COL_MON_FG ) ;
mon_disp - > scrollbar_width ( 12 ) ;
mon_buf = new Fl_Text_Buffer ( ) ;
mon_buf = new Fl_Text_Buffer ( ) ;
mon_styl = new Fl_Text_Buffer ( ) ;
mon_styl = new Fl_Text_Buffer ( ) ;
mon_disp - > buffer ( mon_buf ) ;
mon_disp - > buffer ( mon_buf ) ;
mon_disp - > highlight_data ( mon_styl , STYLES , N_STYLES , ' B ' , nullptr , 0 ) ;
mon_disp - > highlight_data ( mon_styl , STYLES , N_STYLES , ' B ' , nullptr , 0 ) ;
mon_disp - > textfont ( FL_COURIER ) ;
mon_grp - > resizable ( mon_disp ) ;
mon_disp - > textsize ( 12 ) ;
mon_grp - > end ( ) ;
mon_disp - > color ( fl_rgb_color ( 10 , 30 , 10 ) ) ;
mon_disp - > textcolor ( fl_rgb_color ( 0 , 220 , 0 ) ) ;
mon_disp - > cursor_color ( fl_rgb_color ( 0 , 220 , 0 ) ) ;
mon_disp - > scrollbar_width ( 14 ) ;
tile - > end ( ) ;
tile - > end ( ) ;
tile - > resizable ( tile ) ;
/* Input row */
y + = TILE_H ;
int inp_y = tile_y + tile_h ;
inp = new Fl_Input ( 0 , inp_y , W - SEND_W , INPUT_H ) ;
/* ── Input separator ── */
Fl_Box * inp_sep = new Fl_Box ( 0 , y , W , SEP_H , " Input " ) ;
inp_sep - > box ( FL_FLAT_BOX ) ;
inp_sep - > color ( COL_SEP_BG ) ;
inp_sep - > labelcolor ( COL_SEP_FG ) ;
inp_sep - > labelfont ( FL_HELVETICA_BOLD ) ;
inp_sep - > labelsize ( 11 ) ;
y + = SEP_H ;
/* ── Input row: "> " prompt + input field ── */
Fl_Box * prompt = new Fl_Box ( 0 , y , 22 , INPUT_H , " > " ) ;
prompt - > box ( FL_FLAT_BOX ) ;
prompt - > color ( COL_INP_BG ) ;
prompt - > labelcolor ( COL_INP_FG ) ;
prompt - > labelfont ( FL_COURIER_BOLD ) ;
prompt - > labelsize ( 13 ) ;
inp = new Fl_Input ( 22 , y , W - 22 , INPUT_H ) ;
inp - > box ( FL_FLAT_BOX ) ;
inp - > color ( COL_INP_BG ) ;
inp - > textcolor ( COL_INP_FG ) ;
inp - > cursor_color ( COL_INP_FG ) ;
inp - > textfont ( FL_COURIER ) ;
inp - > textfont ( FL_COURIER ) ;
inp - > textsize ( 13 ) ;
inp - > textsize ( 13 ) ;
inp - > color ( fl_rgb_color ( 30 , 30 , 30 ) ) ;
inp - > textcolor ( FL_WHITE ) ;
inp - > cursor_color ( FL_WHITE ) ;
inp - > when ( FL_WHEN_ENTER_KEY_ALWAYS ) ;
inp - > when ( FL_WHEN_ENTER_KEY_ALWAYS ) ;
inp - > callback ( cb_send ) ;
inp - > callback ( cb_send ) ;
y + = INPUT_H ;
Fl_Return_Button * send_btn = new Fl_Return_Button ( W - SEND_W , inp_y , SEND_W , INPUT_H , " Send " ) ;
send_btn - > callback ( cb_send ) ;
/* ── Function key bar ── */
struct { const char * label ; Fl_Callback * cb ; } fkeys [ ] = {
/* Status bar */
{ " F1 Conn " , cb_f1 } ,
int st_y = inp_y + INPUT_H ;
{ " F3 Mon " , cb_f3 } ,
status_lbl = new Fl_Box ( 0 , st_y , W , STATUS_H , " Not connected " ) ;
{ " F4 Cfg " , cb_f4 } ,
status_lbl - > align ( FL_ALIGN_LEFT | FL_ALIGN_INSIDE ) ;
{ " F5 Log " , cb_f5 } ,
status_lbl - > box ( FL_FLAT_BOX ) ;
{ " F6 Forms " , cb_f6 } ,
status_lbl - > color ( fl_rgb_color ( 0 , 0 , 140 ) ) ;
{ " F7 New " , cb_f7 } ,
status_lbl - > labelcolor ( FL_WHITE ) ;
{ " F8 Prev " , cb_f8 } ,
status_lbl - > labelfont ( FL_HELVETICA ) ;
{ " F9 Next " , cb_f9 } ,
status_lbl - > labelsize ( 12 ) ;
{ " F10 Quit " , cb_f10 } ,
} ;
const int NKEYS = 9 ;
int btn_w = W / NKEYS ;
Fl_Group * fkey_grp = new Fl_Group ( 0 , y , W , FKEY_H ) ;
fkey_grp - > begin ( ) ;
fkey_grp - > box ( FL_FLAT_BOX ) ;
fkey_grp - > color ( COL_FKEY_BG ) ;
for ( int i = 0 ; i < NKEYS ; i + + ) {
int bx = i * btn_w ;
int bw = ( i = = NKEYS - 1 ) ? W - bx : btn_w ; /* last button fills remainder */
fkey_btn [ i ] = new Fl_Button ( bx , y , bw , FKEY_H , fkeys [ i ] . label ) ;
fkey_btn [ i ] - > box ( FL_FLAT_BOX ) ;
fkey_btn [ i ] - > color ( COL_FKEY_CHIP ) ;
fkey_btn [ i ] - > labelcolor ( COL_FKEY_TEXT ) ;
fkey_btn [ i ] - > labelfont ( FL_HELVETICA_BOLD ) ;
fkey_btn [ i ] - > labelsize ( 11 ) ;
fkey_btn [ i ] - > callback ( fkeys [ i ] . cb ) ;
}
fkey_grp - > end ( ) ;
main_win - > resizable ( tile ) ;
main_win - > resizable ( tile ) ;
main_win - > end ( ) ;
main_win - > end ( ) ;
/* Wire up keyboard F-keys on the main window */
main_win - > callback ( [ ] ( Fl_Widget * , void * ) {
main_win - > callback ( [ ] ( Fl_Widget * , void * ) {
if ( g_connected ) tcp_disconnect ( ) ;
if ( g_connected ) tcp_disconnect ( ) ;
exit ( 0 ) ;
exit ( 0 ) ;
} ) ;
} ) ;
}
}
/* Handle F-keys pressed anywhere in the window */
static int event_handler ( int event )
{
if ( event ! = FL_SHORTCUT ) return 0 ;
switch ( Fl : : event_key ( ) ) {
case FL_F + 1 : cb_f1 ( nullptr , nullptr ) ; return 1 ;
case FL_F + 3 : cb_f3 ( nullptr , nullptr ) ; return 1 ;
case FL_F + 4 : cb_f4 ( nullptr , nullptr ) ; return 1 ;
case FL_F + 5 : cb_f5 ( nullptr , nullptr ) ; return 1 ;
case FL_F + 6 : cb_f6 ( nullptr , nullptr ) ; return 1 ;
case FL_F + 7 : cb_f7 ( nullptr , nullptr ) ; return 1 ;
case FL_F + 8 : cb_f8 ( nullptr , nullptr ) ; return 1 ;
case FL_F + 9 : cb_f9 ( nullptr , nullptr ) ; return 1 ;
case FL_F + 10 : cb_f10 ( nullptr , nullptr ) ; return 1 ;
}
return 0 ;
}
/* ── main ────────────────────────────────────────────────── */
/* ── main ────────────────────────────────────────────────── */
int main ( int argc , char * * argv )
int main ( int argc , char * * argv )
{
{
/* Init port names */
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 ) ;
@ -688,25 +695,21 @@ int main(int argc, char **argv)
Fl : : scheme ( " gtk+ " ) ;
Fl : : scheme ( " gtk+ " ) ;
build_window ( ) ;
build_window ( ) ;
Fl : : add_handler ( event_handler ) ;
main_win - > show ( argc , argv ) ;
main_win - > show ( argc , argv ) ;
/* Status timer */
Fl : : add_timeout ( 1.0 , cb_timer , nullptr ) ;
Fl : : add_timeout ( 1.0 , cb_timer , nullptr ) ;
/* Welcome banner */
const char * banner = " TermTCP GUI v " GUI_VERSION
const char * banner = " TermTCP GUI v " GUI_VERSION
" — File > Connect to get started \n " ;
" — F1 to connect \n " ;
out_append ( banner , ( int ) strlen ( banner ) , ' E ' ) ;
out_append ( banner , ( int ) strlen ( banner ) , ' E ' ) ;
/* Auto-connect if exactly one host and -c flag or single host */
/* Auto-connect if only one host configured */
int auto_idx = - 1 ;
int auto_idx = - 1 ;
for ( int i = 1 ; i < argc ; i + + ) {
for ( int i = 1 ; i < argc ; i + + )
if ( ( strcmp ( argv [ i ] , " -c " ) = = 0 | | strcmp ( argv [ i ] , " --connect " ) = = 0 )
if ( ( strcmp ( argv [ i ] , " -c " ) = = 0 | | strcmp ( argv [ i ] , " --connect " ) = = 0 ) & & i + 1 < argc )
& & i + 1 < argc )
auto_idx = atoi ( argv [ + + i ] ) ;
auto_idx = atoi ( argv [ + + i ] ) ;
}
if ( auto_idx < 0 & & num_hosts = = 1 ) auto_idx = 0 ;
if ( auto_idx < 0 & & num_hosts = = 1 ) auto_idx = 0 ;
if ( auto_idx > = 0 & & auto_idx < num_hosts )
if ( auto_idx > = 0 & & auto_idx < num_hosts )
do_connect ( auto_idx ) ;
do_connect ( auto_idx ) ;