From d92bf1f7cf76d0c678ccbaea10a5ff6d41630e52 Mon Sep 17 00:00:00 2001 From: "B. Watson" Date: Wed, 11 Mar 2026 06:26:04 -0400 Subject: Remove server/nick prompts from client, add a separate config tool that loads as an init segment, with lots more options. --- Makefile | 5 ++- TODO | 134 ++++++++++++++++++++++++++++---------------------------- config/Makefile | 8 ++-- config/config.c | 130 ++++++++++++++++++++++++++++++++++++++++-------------- src/cmd.c | 5 ++- src/config.c | 3 ++ src/config.h | 14 ++++++ src/edbox.c | 39 ----------------- src/edbox.h | 11 ----- src/irc.c | 40 ++++++++++------- src/irc.h | 3 +- src/main.c | 36 +++------------ 12 files changed, 227 insertions(+), 201 deletions(-) create mode 100644 src/config.c create mode 100644 src/config.h diff --git a/Makefile b/Makefile index cf1378c..d016e10 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -PARTS=memsetup.xex font_dl.xex client.xex +PARTS=config/config.xex memsetup.xex font_dl.xex client.xex TESTXEX=fnchat-$(shell date +%Y%m%d-%H%M).xex @@ -16,6 +16,9 @@ client.xex: client client: $(MAKE) -f Makefile.client +config/config.xex: + $(MAKE) -C config + clean: rm -f $(PARTS) *.o $(MAKE) -f Makefile.client clean diff --git a/TODO b/TODO index 9490827..660e45d 100644 --- a/TODO +++ b/TODO @@ -1,76 +1,76 @@ -FujiChat features: +See also: ideas.txt. -- nick tab completion (FujiChat's was very lame) -- option to not show ping/pong -- hide motd -- settable real name (currently hardcoded "FujiNetChat User") +FujiChat features, we're almost at parity! + +- Nick tab completion. FujiChat's was very lame, make this one + less lame. It can't be perfect due to limited RAM. +- Real name is configurable in the UI, but not yet used in + the client! Other stuff: -- at least one keyboard macro (for ChanServ auth) -- error numerics should go to the current screen -- autojoin on startup -- add an optional 2nd parameter to /join (key). spec calls for it, +- Gracefully handle nicks/channels whose lengths are stupid long. + At least we shouldn't overflow any buffers. +- Channel tab completion for the [server] screen. +- Auto-reconnect on error, with backoff timer. Do not + reconnect if user types /quit though. +- At least one keyboard macro (for ChanServ auth). More would + be nice, if we can afford the RAM. +- Error numerics should go to the current screen (?). +- Autojoin on startup (see config file section below). +- Add an optional key parameter to /join (key). spec calls for it, I've never seen it used. -- decide whether to allow mass joins: /join #chan1,#chan2,#chan3 +- Decide whether to allow mass joins: /join #chan1,#chan2,#chan3 not sure how widely supported it is on various networks. right - now, it works with libera, but only the first channel gets a - screen. better to do it client-side, /join #chan1 #chan2 #chan3, + now, it's explicitly not allowed. + better to do it client-side, /join #chan1 #chan2 #chan3, accept lots of arguments (but don't support keys). + If someone does need channel keys, we can support that with + a 2-arg /join whose 2nd arg doesn't start with #... unless + the key starts with #! Give this further thought. - /join also supports "/join 0", which parts all channels. don't have to do anything special for it, but do document it. -- 'dead' screens (channels we've parted) should show some kind of - indicator, and not accept input. -- use GR.1 for the activity indicators, so they can be colorful. - -See also: ideas.txt - -... - -Config file. At minimum, these: - -Server -Port -SSL? -Nick -Channel/query list - -Extras: - -AltNick -Colors (BG/FG/BAK) (per-channel? or just one set?) -Screen layout (pre-create channel and query windows) -Macros (with Select key, 1-9, 0 for #10) -CTCP VERSION response (config utility can auto-detect OS type?) - -The config will be created by a separate tool, FNCHATCF. This should -have a built-in list of known IRC servers to choose from, plus the -ability to add more. Maybe store them in a file called FNCHATCF.SRV. - -Distribution disk: MyDOS, with a menu as .AR0 where you can select -whether or not to run the config tool. If there's no config file, just -run the tool. Maybe another menu option to read the README? Use MyDOS -because cc65's exec() works with it (no writing my own exec() function -this time around). - -Menu might just be: - -FujiNetChat v0.0-YYYYMMDD-githash - - config file loaded . - -[Option] Read the manual. -[Select] Connection setup. -[Start] or [Return] Connect to IRC server. -[Escape] Exit to DOS. - -...and if there's no config file on D: or in memory, the Start option -is greyed out. - -Reserve a memory area for the config, so after FNCHATCF runs FNCHAT, -there's no need to reload the config. There won't be a /server command -in the client, so connecting to a different server means re-running -the config tool. - -On disconnect: [R]econnect, [C]onfig, [Q]uit, [B]oot: [R] -Quit will exit to DOS. If that can be made to work, anyway. +- 'Dead' screens (channels we've parted, or failed to join) should + show some kind of indicator, and not accept input. Although maybe + this isn't worth doing (you already get "cannot send to channel"). +- Use GR.1 for the activity indicators, so they can be colorful. +- *Possibly* save the config from within the client... though not + all options can be changed (really only /nick and /beep). To + make this useful would probably bloat the code too much. Some + way to change the IRC server/port within the app would be very + useful though. +- Loading/title screen. Display immediately before the config init + segment is loaded, and again when it runs the client. Its DL and + data can be kept in scrollback RAM, since scr_init() clears it. + Should be small & simple (one line of GR.2 probably). Have to + not disable ANTIC DMA during the client load. +- Handle redirects better. Trying to "/join #slackware" on Libera + results in actually joining ##slackware. But a screen got created + for #slackware, and ##slackware will appear in the [server] screen. + This shouldn't be hard to do. +- Input box history. Up-arrow to recall previous command. Unfortunately + this needs a lot of RAM. Maybe just one previous command, so you + can up-arrow to re-ping someone, etc. + +Config file and initial config: + +- BUG: yn() doesn't get passed the default value, it always defaults + to Y. +- The config tool should be rewritten in asm, for size. It doesn't + need to be fast, but it needs to *load* fast. +- The config UI should be nicer. Dialog/curses style, arrows to move, and + hotkeys like [S]erver, [N]ick. Categories (as tabs? tree?). I'm not + going to get this right without some user feedback, since I'm mainly + a CLI and text editor guy. +- There should be a preset list of servers to choose from, or the + user can enter his own. +- There should be a file selector for load/save config. +- Allow user to manually read MOTD even if hide_motd is set. +More prefs: +- CTCP VERSION response? +- 5 channels/queries for screens 2-7. +- List of non-screen channels to join. +- Ignore list (maybe). +- Connect macro (log in to bot). + +Remember, the config has to be <= 512 bytes! diff --git a/config/Makefile b/config/Makefile index 98eb07f..a371135 100644 --- a/config/Makefile +++ b/config/Makefile @@ -1,10 +1,12 @@ all: config.xex -config.xex: config.c exetrailer.s ../src/config.h - cl65 -t atari -C ../src/atari.cfg -o config.xex config.c exetrailer.s +#config.xex: config.c exetrailer.s ../src/config.h ../src/config.c + +config.xex: + cl65 -t atari -C ../src/atari.cfg -o config.xex config.c exetrailer.s ../src/config.c test: - cl65 -t atari -C ../src/atari.cfg -o config.xex config.c exetrailer.s + cl65 -t atari -C ../src/atari.cfg -o config.xex config.c exetrailer.s ../src/config.c cl65 -t atari -C ../src/atari.cfg -o conftest.xex conftest.c cat config.xex conftest.xex > autorun.sys cp dos25_sd.atr test.atr diff --git a/config/config.c b/config/config.c index 212d36b..28e0f6d 100644 --- a/config/config.c +++ b/config/config.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -19,22 +20,36 @@ conf_t defaults = { 1 }; -char filename[100] = "D:FNCHAT.CFG"; -char buf[100]; -char server[100]; +char have_dos = 0; +char filename[101] = "D:FNCHAT.CFG"; +char buf[128]; +char numbuf[4]; +char server[101]; char port[6]; +void detect_dos(void) { + char i, j; + + for(i = 0; i < 11; i++) { + j = OS.hatabs[i].id; + if(j == 'D' || j == 'H') { + have_dos = 1; + break; + } + } +} + void print(const char *text) { fputs(text, stdout); } -void prompt(const char *text, char *p) { +void prompt(const char *text, char *p, char limit) { print(text); print(" ["); print(p); print("]? "); gets(buf); - if(buf[0]) strcpy(p, buf); + if(buf[0]) strncpy(p, buf, limit); } char yn(const char *text) { @@ -50,16 +65,60 @@ char yn(const char *text) { return (c == '\n' || c == 'Y' || c == 'y'); } -void save(void) { - int fh, bad; +void prompt_config_file(void) { + OS.shflok = 0x40; + prompt("Config File", filename, 100); + OS.shflok = 0x00; +} + +void prompt_color(const char *text, char which) { + itoa(conf->colors[which], numbuf, 10); + print(text); + prompt(" color", numbuf, 3); + conf->colors[which] = atoi(numbuf); +} + +void prompt_colors(void) { + char bad; + + do { + bad = 0; + prompt_color("Background", 0); + prompt_color("Text", 1); + if((conf->colors[0] & 0x0e) == (conf->colors[1] & 0x0e)) { + print("!! Unreadable, try again.\n"); + bad = 1; + } + } while(bad); + + OS.color2 = conf->colors[0]; + OS.color1 = conf->colors[1]; +} + +void prompt_alert_type(void) { + char c, bad; + print(" 0:None, 1:Beep, 2:Flash, 3:Both\n"); do { bad = 0; + itoa(conf->alert_type, numbuf, 10); + prompt("Alert type", numbuf, 2); + c = atoi(numbuf); + if(c < 0 || c > 3) { + bad = 1; + print("!! Range is 0-3, try again.\n"); + } + } while(bad); - OS.shflok = 0x40; - prompt("Config File", filename); - OS.shflok = 0x00; + conf->alert_type = c; +} +void save(void) { + int fh, bad; + + do { + bad = 0; + prompt_config_file(); if((fh = open(filename, O_WRONLY | O_CREAT)) < 0) { print("!! I/O error (open)\n"); bad = 1; @@ -79,23 +138,26 @@ void save(void) { void load(void) { int fh, bad = 0; - if(!yn("Load Config")) { - bad = 1; - print("OK"); - } else { - OS.shflok = 0x40; - prompt("Config File", filename); - OS.shflok = 0x00; - - if((fh = open(filename, O_RDONLY)) < 0) { - print("Not found"); - bad = 1; - } else if((read(fh, conf, sizeof(conf_t))) != sizeof(conf_t)) { - print("Invalid"); + if(have_dos) { + if(!yn("Load Config")) { bad = 1; + print("OK"); + } else { + prompt_config_file(); + + if((fh = open(filename, O_RDONLY)) < 0) { + print("Not found"); + bad = 1; + } else if((read(fh, conf, sizeof(conf_t))) != sizeof(conf_t)) { + print("Invalid"); + bad = 1; + } + + if(fh >= 0) close(fh); } - - if(fh >= 0) close(fh); + } else { + print("No DOS booted"); + bad = 1; } if(bad) { @@ -120,6 +182,7 @@ void main(void) { cursor(1); puts("\x7d" "FujiNetChat Setup\n"); + detect_dos(); load(); OS.color2 = conf->colors[0]; @@ -138,11 +201,12 @@ void main(void) { strcpy(port, p); while(1) { - prompt("\nServer", server); - prompt("Port ", port); - prompt("Nick ", conf->nick); - // prompt_colors(); - // prompt_alert_type(); + prompt("\nServer", server, 100); + prompt("Port ", port, 5); + prompt("Nick ", conf->nick, 25); + prompt("'Real' Name ", conf->real_name, 25); + prompt_colors(); + prompt_alert_type(); conf->show_ping = yn("Show PING/PONG"); conf->atract_away = yn("Set AWAY on ATRACT"); conf->hide_motd = yn("Hide MOTD"); @@ -156,8 +220,10 @@ void main(void) { break; } - if(yn("Save to disk")) - save(); + if(have_dos) { + if(yn("Save to disk")) + save(); + } print("\nClient loading..."); } diff --git a/src/cmd.c b/src/cmd.c index f548b74..88bf669 100644 --- a/src/cmd.c +++ b/src/cmd.c @@ -5,6 +5,7 @@ #include "irc.h" #include "addrs.h" #include "screen.h" +#include "config.h" /* A "command" is actually anything the user types, whether or not it starts with a /character. */ @@ -74,7 +75,7 @@ static void cmd_chan_text(void) { /* 0x02 = ^B = enable bold */ scr_print_active("<\x02"); - scr_print_active(usernick); + scr_print_active(conf->nick); scr_print_active("\x02"); /* @@ -305,7 +306,7 @@ static void do_me(void) { txbuf_send(); scr_print_current("\x02* "); - scr_print_current(usernick); + scr_print_current(conf->nick); scr_print_current("\x02 "); scr_print_current(arg1); scr_eol_current(); diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..f7c8982 --- /dev/null +++ b/src/config.c @@ -0,0 +1,3 @@ +#include "config.h" + +conf_t *conf = (conf_t *)0x0400; diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..f051fed --- /dev/null +++ b/src/config.h @@ -0,0 +1,14 @@ + +typedef struct { + char url[128]; + char nick[25]; + char real_name[25]; + char colors[2]; + char channels[5][33]; + char alert_type; + char show_ping; + char hide_motd; + char atract_away; +} conf_t; + +extern conf_t *conf; diff --git a/src/edbox.c b/src/edbox.c index 167bdc2..efbd19d 100644 --- a/src/edbox.c +++ b/src/edbox.c @@ -57,45 +57,6 @@ void edbox_putc(char c) { edbox_show(); } -void edbox_append(char *s) { - while(*s) - edit_box[edbox_pos++] = *s++; - edbox_len = edbox_pos; -} - -void edbox_preset(char *s) { - edbox_clear(); - edbox_append(s); -} - -static char readline_done, readline_len; -static char *readline_dest; -static void readline_callback(void) { - strncpy(readline_dest, edit_box, readline_len); - readline_done = 1; -} - -void edbox_readline(char *dest, char len) { - void (*old_callback)(void); - - edbox_clear(); - if(*dest) - edbox_preset(dest); - edbox_show(); - - old_callback = edbox_callback; - edbox_callback = readline_callback; - readline_dest = dest; - readline_len = len; - - readline_done = 0; - - while(!readline_done) - edbox_keystroke(); - - edbox_callback = old_callback; -} - static void special_keystroke(char c) { keyclick(); OS.ch = 0xff; diff --git a/src/edbox.h b/src/edbox.h index 3344353..548dc87 100644 --- a/src/edbox.h +++ b/src/edbox.h @@ -20,16 +20,5 @@ void edbox_putc(char c); is pressed, edbox_callback gets called (if it's set!) */ void edbox_keystroke(void); -/* append a string to the edit box. */ -void edbox_append(char *s); - -/* explicitly set the edit box's contents. wipes out whatever was there - before. */ -void edbox_preset(char *s); - -/* read a complete line into the edit box; doesn't return until the user - pressed Return. copies it to dest, up to len characters. */ -void edbox_readline(char *dest, char len); - /* called when the user presses Return */ extern void (*edbox_callback)(void); diff --git a/src/irc.c b/src/irc.c index 9a5b7a9..34957f1 100644 --- a/src/irc.c +++ b/src/irc.c @@ -12,6 +12,7 @@ #include #include #include "nio.h" +#include "config.h" #define MAX_MSG 512 @@ -22,7 +23,7 @@ char *msg_args[MAX_MSG_ARGS]; int msg_argcount; char irc_away = 0; -char bell_type = 3; +char bell_type; static char msgbuf[MAX_MSG] = { 0 }; static char *msg; /* with source removed */ @@ -44,7 +45,7 @@ static void join_channel(void) { */ static void send_nick(void) { - txbuf_set_str2("NICK ", usernick); + txbuf_set_str2("NICK ", conf->nick); txbuf_send(); } @@ -57,7 +58,8 @@ static void print_reason(void) { } static void do_pong(void) { - scr_print_server("PING/PONG\n"); /* make hiding this a preference, or just ditch it */ + if(conf->show_ping) + scr_print_server("PING/PONG\n"); txbuf_set_str2("PONG ", msg_args[0]); txbuf_send(); } @@ -203,7 +205,7 @@ static void do_ctcp(int is_notice) { static void do_privmsg(void) { /* TODO: this shouldn't be case-sensitive */ - if(strstr(msg_text, usernick)) + if(strstr(msg_text, conf->nick)) hilite = 1; else hilite = 0; @@ -232,7 +234,7 @@ static void do_notice(void) { } static void do_join(void) { - if(streq_i(usernick, msg_src)) { + if(streq_i(conf->nick, msg_src)) { scr_print_active("You have "); } else { scr_print_active(msg_src); @@ -244,9 +246,9 @@ static void do_join(void) { } static void do_nick(void) { - if(streq_i(usernick, msg_src)) { + if(streq_i(conf->nick, msg_src)) { scr_print_active("You are "); - strncpy(usernick, msg_dest, 32); + strncpy(conf->nick, msg_dest, 32); } else { scr_print_active(msg_src); scr_print_active(" is "); @@ -339,7 +341,7 @@ static void do_catchall(int arg) { static void permute_nick(void) { char *last; - last = usernick + strlen(usernick) - 1; + last = conf->nick + strlen(conf->nick) - 1; if((*last >= '1' && *last < '9') || (*last >= 'A' && *last < 'Z')) { (*last)++; @@ -359,7 +361,7 @@ static void do_numeric(void) { /* use the server's idea of what our nick is, in case it got truncated. */ case RPL_WELCOME: - strcpy(usernick, msg_args[0]); + strcpy(conf->nick, msg_args[0]); regged = 1; do_catchall(1); break; @@ -394,6 +396,12 @@ static void do_numeric(void) { case RPL_MOTDSTART: break; + case RPL_MOTD: + /* FIXME: this prevents the user using /MOTD on purpose, too */ + if(!conf->hide_motd) + do_catchall(0); + break; + /* don't print, but do trigger rejoin */ case RPL_ENDOFMOTD: case ERR_NOMOTD: @@ -616,7 +624,7 @@ void print_errnum(void) { int irc_read(void) { if(!trip) return 1; - err = nstatus(url); + err = nstatus(conf->url); if(err != 1) { regged = 0; @@ -638,7 +646,7 @@ int irc_read(void) { rxbuflen = MAX_MSG; if(rxbuflen > 0) { - err = nread(url, rx_buf, rxbuflen); + err = nread(conf->url, rx_buf, rxbuflen); if(err != 1) { print_errnum(); return 0; @@ -661,7 +669,7 @@ void irc_register(void) { /* 2nd arg: local (UNIX) username, just use the nick */ /* 3rd arg: "real" name (make it a pref?) */ - txbuf_set_str3("USER ", usernick, " 0 * :FujiNetChat User"); + txbuf_set_str3("USER ", conf->nick, " 0 * :FujiNetChat User"); txbuf_send(); send_nick(); @@ -781,9 +789,11 @@ static void keystroke(void) { /* only exits on error (e.g. connection closed, which might be via /QUIT). */ void irc_loop(void) { while(1) { - if(!irc_away && (OS.atract & 0x80)) { - irc_away = 1; - txbuf_send_str("AWAY :ATRACT mode"); + if(conf->atract_away) { + if(!irc_away && (OS.atract & 0x80)) { + irc_away = 1; + txbuf_send_str("AWAY :ATRACT mode"); + } } if(!irc_read()) return; keystroke(); diff --git a/src/irc.h b/src/irc.h index 4b57df3..7feeb24 100644 --- a/src/irc.h +++ b/src/irc.h @@ -5,8 +5,6 @@ #define streq_i(x,y) !strcasecmp(x,y) /**** main.c */ -extern char url[256]; -extern char usernick[32]; extern char *rx_buf; extern unsigned short rxbuflen; extern unsigned char err; @@ -42,6 +40,7 @@ void print_error(unsigned char err); /**** irc.c */ #define MAX_MSG_ARGS 8 +extern char bell_type; extern char numbuf[10]; extern char *msg_src, *msg_cmd, *msg_dest, *msg_text; extern char *msg_args[MAX_MSG_ARGS]; diff --git a/src/main.c b/src/main.c index 3bed456..094d1ef 100644 --- a/src/main.c +++ b/src/main.c @@ -4,10 +4,6 @@ #define VERSION "0.0" #define BANNER SELF " v" VERSION " (B. Watson)\n" -#define DEF_URL "N:TCP://irc.libera.chat:6667" -#define DEF_NICK "FNChatTest" -#define DEF_CHANNEL "##atari" - #include #include #include @@ -18,9 +14,8 @@ #include "irc.h" #include "screen.h" #include "edbox.h" +#include "config.h" -char url[256] = DEF_URL; // URL. -char usernick[32] = DEF_NICK; // our current nick (can be changed by the server) unsigned char err; // error code of last operation. unsigned char trip = 0; // if trip == 1, fujinet is asking us for attention. char old_enabled = 0; // were interrupts enabled for old vector @@ -33,23 +28,6 @@ char hz; /* 50 for PAL, 60 for NSTC */ extern void ih(); // defined in intr.s -void get_config(void) { - scr_print_current(BANNER); - - while(1) { - scr_print_current("URL?\n"); - edbox_readline(url, sizeof(url)); - scr_print_current("Nick?\n"); - edbox_readline(usernick, sizeof(usernick)); - - /* - scr_print_current("Are these settings OK [Y/n]?\n"); - if(tolower(cgetc()) != 'n') - */ - break; - } -} - void txbuf_init(void) { txbuflen = tx_buf[0] = 0; } @@ -94,7 +72,7 @@ void txbuf_send(void) { if(tx_buf[txbuflen - 1] != '\n') tx_buf[txbuflen++] = '\n'; - nwrite(url, tx_buf, txbuflen); + nwrite(conf->url, tx_buf, txbuflen); txbuf_init(); } @@ -106,10 +84,10 @@ void txbuf_send_str(const char *str) { int fn_connect(void) { scr_print(SCR_SERVER, "Connecting to: "); - scr_print(SCR_SERVER, url); + scr_print(SCR_SERVER, conf->url); scr_print(SCR_SERVER, "\n"); - err = nopen(url, FNET_TRANSLATION); + err = nopen(conf->url, FNET_TRANSLATION); if(err != SUCCESS) { scr_print(SCR_SERVER, "Connection failed: "); @@ -135,10 +113,11 @@ void fn_disconnect(void) { } int main(void) { + bell_type = conf->alert_type; /* TODO: have bell.s read staight from the struct */ OS.shflok = 0; // turn off shift-lock. OS.soundr = 0; // Turn off SIO beeping sound - OS.color2 = 0xc0; /* darkest green background */ - OS.color1 = 0x0c; /* bright text */ + OS.color2 = conf->colors[0]; + OS.color1 = conf->colors[1]; hz = (GTIA_READ.pal & 0x0e) ? 60 : 50; @@ -146,7 +125,6 @@ int main(void) { scr_init(); while(1) { - get_config(); edbox_callback = cmd_execute; if(fn_connect()) { irc_register(); -- cgit v1.2.3