diff options
| -rw-r--r-- | TODO | 30 | ||||
| -rw-r--r-- | commands.txt | 27 | ||||
| -rw-r--r-- | src/bell.s | 45 | ||||
| -rw-r--r-- | src/cmd.c | 112 | ||||
| -rw-r--r-- | src/irc.c | 191 | ||||
| -rw-r--r-- | src/irc.h | 5 | ||||
| -rw-r--r-- | src/main.c | 4 | ||||
| -rw-r--r-- | ui_keys.txt | 12 |
8 files changed, 384 insertions, 42 deletions
@@ -1,23 +1,33 @@ FujiChat features: -- respond to incoming CTCPs - handle incoming PONG (with latency) -- visual and audible bell -- nick tab completion +- nick tab completion (FujiChat's was very lame) +- option to not show ping/pong +- hide motd +- settable real name (currently hardcoded "FujiNetChat User") Other stuff: +- at least one keyboard macro (for ChanServ auth) - error numerics should go to the current screen -- the server's spontaneous MODE at connect should go to screen 0 +- autojoin on startup +- add an optional 2nd parameter to /join (key). spec calls for it, + I've never seen it used. +- 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, + accept lots of arguments (but don't support keys). +- /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 ... -Don't create channel screen until we receive a message from the -channel. /join ##foo won't create the screen... and if there are no -screens available, we don't have to check for it. - -Don't create query windows unless user explicitly does so. - Config file. At minimum, these: Server diff --git a/commands.txt b/commands.txt index 9641d6d..cba40ef 100644 --- a/commands.txt +++ b/commands.txt @@ -16,6 +16,11 @@ Joins a channel, creates a new screen if possible. If a screen can't be created, channel text will be sent to the [server] screen, and "/m #channel" must be used, to send to the channel. +/j1 <channel> +/join1 <channel> +Joins a channel without creating a new screen. Channel test will be +sent to the [server] screen. Use "/m #channel" to send to the channel. + /m <nick|channel> <text> /msg <nick|channel> <text> PRIVMSG to nick or channel. @@ -36,11 +41,21 @@ Parts (leaves) a channel. If no #chan is given, the current screen's channel is parted (if you're in a channel screen). If there's a screen for the channel, it gets closed. +/names [<channel>] +Shows the list of users in a channel. Uses the current screen's channel, +if no <channel> given. On most networks, it's not very useful to use +/names on a channel you haven't joined. + +/topic [<channel>] +Shows the channel topic and its creator. With no <channel>, uses the +current screen's channel. + /ping [<nick>] With no argument: ping the server. With arg: CTCP ping the nick. The contents of RTCLOK are sent as the ping data, so when the PONG response is received, the round-trip time can be shown, with up to 1/60 (NTSC) or 1/50 (PAL) second accuracy. +TODO: not implemented yet. /me <action> CTCP ACTION. Only works in a channel or query screen (eventually @@ -53,6 +68,9 @@ CTCP VERSION. With <nick>, CTCP CLIENTINFO. Without <nick>, INFO command for the server. +/ctcp <nick> <command> [<text>] +Send arbitrary CTCP commands. + /list <arg(s)> This command will be sent to the server as-is. The only reason it's a local command is so the argument can be required: sending LIST without @@ -60,10 +78,17 @@ any arguments lists every channel on the server, which isn't useful. /color <bg> [<fg>] [<status-active>] [<status-highlight>] Set colors. This should be on a per-screen basis, eventually. +TODO: this only takes bg and fg arguments, currently. /chans List all channels we've joined. This will actually be limited to something like 20 (who joins more than 20 channels anyway?) +TODO: not implemented yet. /quote <cmd> -Send raw IRC protocol. +Send raw IRC protocol to the server. This bypasses local command +parsing. + +/bell <number> +Set the type of alert that happens when you're PMed or highlighted +in a channel. 0 = none, 1 = beep, 2 = flash, 3 = beep and flash. diff --git a/src/bell.s b/src/bell.s new file mode 100644 index 0000000..6a27503 --- /dev/null +++ b/src/bell.s @@ -0,0 +1,45 @@ + + .include "atari.inc" + + .import _bell_type + .export _bell + +DISTVOL = $a8 +PITCH = $40 +JIFFIES = $03 + +_bell: + lda #<bell_callback + sta CDTMA2 + lda #>bell_callback + sta CDTMA2+1 + lda _bell_type + beq done + lda #JIFFIES + sta CDTMV2 + lda _bell_type + and #1 + beq check_flash + lda #DISTVOL + sta AUDC1 + lda #PITCH + sta AUDF1 +check_flash: + lda _bell_type + and #2 + beq done + lda #$08 + sta COLOR4 + lda #JIFFIES + sta CDTMV2 +done: + rts + +bell_callback: + lda #0 + sta AUDC1 + sta AUDF1 + sta COLOR4 + lda #JIFFIES + sta CDTMV2 + rts @@ -12,13 +12,17 @@ char *command, *arg1, *arg2, *arg3; static char *target; +static void do_away(void); +static void do_bell(void); static void do_color(void); +static void do_ctcp(void); static void do_info(void); static void do_j(void); static void do_j1(void); static void do_list(void); static void do_me(void); static void do_msg(void); +static void do_names(void); static void do_part(void); static void do_ping(void); static void do_query(void); @@ -37,7 +41,10 @@ typedef struct { OP DEOP VOICE DEVOICE KICK BAN KB IGNORE UNIGNORE MODE */ cmd_t command_defs[] = { + { "AWAY", do_away, 1 }, + { "BELL", do_bell, 1 }, { "COLOR", do_color, 1 }, + { "CTCP", do_ctcp, 1 }, { "INFO", do_info, 0 }, { "J", do_j, 1 }, { "J1", do_j1, 1 }, @@ -47,6 +54,7 @@ cmd_t command_defs[] = { { "M", do_msg, 1 }, { "ME", do_me, 1 }, { "MSG", do_msg, 1 }, + { "NAMES", do_names, 0 }, { "PART", do_part, 0 }, { "PING", do_ping, 0 }, { "Q", do_query, 1 }, @@ -96,7 +104,7 @@ static void err_marker(void) { static void err_arg_req(void) { err_marker(); scr_print_current(cmd_def->cmd); - scr_print_current(" requires an argument\n"); + scr_print_current(": missing argument\n"); } static void err_no_scr_target(void) { @@ -104,12 +112,6 @@ static void err_no_scr_target(void) { scr_print_current("No channel/nick for screen\n"); } -static void err_target_req(void) { - err_marker(); - scr_print_current(cmd_def->cmd); - scr_print_current(" requires target channel/nick\n"); -} - /* arg points to something like: "part #channel I'm outta here\0" after nextarg(), arg points to "part\0" only, and ret points @@ -162,7 +164,7 @@ static void do_part(void) { } if(!target) { - err_target_req(); + err_no_scr_target(); return; } @@ -181,7 +183,7 @@ static void do_topic(void) { } if(!target) { - err_target_req(); + err_no_scr_target(); return; } @@ -191,6 +193,22 @@ static void do_topic(void) { txbuf_send(); } +static void do_names(void) { + if(arg1[0] == '#') { + target = arg1; + arg2 = nextarg(arg1); + } else { + arg2 = arg1; + } + + if(!target) { + err_no_scr_target(); + return; + } + txbuf_set_str2("NAMES ", target); + txbuf_send(); +} + static void do_server_info(void) { txbuf_send_str("INFO"); } @@ -199,27 +217,70 @@ static void do_server_ping(void) { txbuf_send_str("PING 0xdeadbeef"); } -static void do_ctcp_ping(void) { +/* arg1 = nick + arg2 = CTCP type + arg3 = optional data */ +static void send_ctcp(void) { txbuf_set_str3("PRIVMSG ", arg1, " :\x01"); - txbuf_append_str("PING 0xdeadbeef\x01"); + txbuf_append_str(arg2); + if(arg3) + txbuf_append_str2(" ", arg3); + txbuf_append_str("\x01"); txbuf_send(); } +long read_rtclok(void) { + long r; + + r = ((long)OS.rtclok[0] << 16); + r |= (OS.rtclok[1] << 8); + r |= OS.rtclok[2]; + + return r; +} + +static void rtclok_to_numbuf(void) { + long r; + r = read_rtclok(); + ltoa(r, numbuf, 10); +} + +static void do_ctcp_ping(void) { + arg2 = "PING"; + rtclok_to_numbuf(); + arg3 = numbuf; + send_ctcp(); +} + static void do_ctcp_info(void) { - txbuf_set_str3("PRIVMSG ", arg1, " :\x01"); - txbuf_append_str("CLIENTINFO\x01"); - txbuf_send(); + arg2 = "CLIENTINFO"; + arg3 = 0; + send_ctcp(); } static void do_ctcp_ver(void) { - txbuf_set_str3("PRIVMSG ", arg1, " :\x01"); - txbuf_append_str("VERSION\x01"); - txbuf_send(); + arg2 = "VERSION"; + arg3 = 0; + send_ctcp(); +} + +static void do_ctcp(void) { + if(!arg1) { + err_arg_req(); + return; + } + arg2 = nextarg(arg1); + if(!arg2) { + err_arg_req(); + return; + } + arg3 = nextarg(arg2); + send_ctcp(); } static void do_me(void) { if(!target) { - err_target_req(); + err_no_scr_target(); return; } @@ -272,6 +333,10 @@ static void do_color(void) { } static void do_query(void) { + /* don't create a query if we already got one! */ + if(scr_getbyname(arg1)) + return; + if(scr_create(arg1, 1) == 0xff) { err_marker(); scr_print_current("Can't create query, all screens in use\n"); @@ -300,6 +365,17 @@ static void do_msg(void) { } } +static void do_away(void) { + OS.atract = 0x80; + irc_away = 1; + txbuf_set_str2("AWAY :", arg1); + txbuf_send(); +} + +static void do_bell(void) { + bell_type = *arg1 - '0'; +} + static int cmd_local(void) { arg1 = nextarg(command); @@ -15,15 +15,23 @@ #define MAX_MSG 512 +extern void __fastcall__ bell(void); /* see src/bell.s */ + char *msg_src, *msg_cmd, *msg_dest, *msg_text; char *msg_args[MAX_MSG_ARGS]; int msg_argcount; +char irc_away = 0; +char bell_type = 3; + static char msgbuf[MAX_MSG] = { 0 }; static char *msg; /* with source removed */ static int msgbuf_len = 0, msg_len = 0; static char regged = 0, hilite = 0; +static char scr_prev = SCR_PRIV; + +char numbuf[10]; /* static void join_channel(void) { @@ -56,6 +64,7 @@ static void hilite_bold(void) { } static void do_chan_nick(void) { + if(hilite) bell(); hilite_bold(); scr_print_active("<"); hilite_bold(); @@ -75,6 +84,107 @@ static void do_priv_nick(void) { scr_print_active("*"); scr_print_active(msg_src); scr_print_active("* "); + bell(); +} + +static void print_ping_time(char *p) { + static long now, pingtime; + static int sec, frac; + + now = read_rtclok(); + pingtime = atol(p); + + /* correct for rtclock rollover (every 77 hours) */ + if(now < pingtime) now |= 0x01000000L; + + pingtime = now - pingtime; + + sec = pingtime / hz; + frac = pingtime % hz; + frac *= 1000; + frac /= (hz * 10); + + scr_print_active("*** "); + scr_print_active(msg_src); + scr_print_active(" ping time: "); + itoa(sec, numbuf, 10); + scr_print_active(numbuf); + scr_print_active("."); + itoa(frac, numbuf, 10); + scr_print_active(numbuf); + scr_print_active(" sec"); + + /* for debugging: + scr_print_active(" "); + ltoa(pingtime, numbuf, 10); + scr_print_active(numbuf); + */ +} + +/* FIXME: this isn't very fast */ +static void do_ctcp(int is_notice) { + static char *p, *ctcp_type, *resp; + + resp = 0; + ctcp_type = ++msg_text; /* skip leading ^A */ + + if( (p = strchr(msg_text, '\x01')) ) { + /* kill trailing ^A */ + *p = 0; + } + + if( (p = strchr(msg_text, ' ')) ) { + *p++ = 0; + } + + if(is_notice) { + /* NOTICEs are responses */ + if(p && streq_i(ctcp_type, "PING")) { + print_ping_time(p); + } else { + scr_print_active("*** CTCP "); + scr_print_active(ctcp_type); + scr_print_active(" response from "); + scr_print_active(msg_src); + if(p) { + scr_print_active(": "); + scr_print_active(p); + } + } + scr_eol_active(); + } else { + /* this is a PRIVMSG (aka a request) */ + if(streq_i(ctcp_type, "ACTION")) { + scr_print_active("* "); + scr_print_active(msg_src); + scr_print_active(" "); + scr_print_active(p); + scr_eol_active(); + return; + } + + scr_print_active("*** CTCP "); + scr_print_active(ctcp_type); + scr_print_active(" request from "); + scr_print_active(msg_src); + scr_eol_active(); + + if(streq_i(ctcp_type, "PING")) { + resp = p; + } else if(streq_i(ctcp_type, "CLIENTINFO")) { + resp = "PING VERSION CLIENTINFO"; + } else if(streq_i(ctcp_type, "VERSION")) { + resp = "FujiNetChat pre-alpha on an Atari 8-bit"; + } else { + /* unknown CTCP type, ignore */ + return; + } + + txbuf_set_str3("NOTICE ", msg_src, " :\x01"); + txbuf_append_str3(ctcp_type, " ", resp); + txbuf_append_str("\x01"); + txbuf_send(); + } } static void do_privmsg(void) { @@ -84,6 +194,11 @@ static void do_privmsg(void) { else hilite = 0; + if(*msg_text == '\x01') { + do_ctcp(0); + return; + } + if(*msg_dest == '#') do_chan_nick(); else @@ -93,6 +208,15 @@ static void do_privmsg(void) { scr_eol_active(); } +static void do_notice(void) { + if(*msg_text == '\x01') { + do_ctcp(1); + } else { + scr_print_active("NOTICE "); + do_privmsg(); + } +} + static void do_join(void) { if(streq_i(usernick, msg_src)) { scr_print_active("You have "); @@ -321,6 +445,8 @@ static void dispatch_msg(void) { if(streq_i(msg_cmd, "PRIVMSG")) { do_privmsg(); + } else if(streq_i(msg_cmd, "NOTICE")) { + do_notice(); } else if(streq_i(msg_cmd, "JOIN")) { do_join(); } else if(streq_i(msg_cmd, "NICK")) { @@ -459,10 +585,9 @@ static void irc_split_Lines(void) { /* TODO: there needs to be a scr_printnum() */ void print_errnum(void) { extern unsigned char err; - char tmp[10]; scr_print_current("Error #"); - itoa(err, tmp, 10); - scr_print_current(tmp); + itoa(err, numbuf, 10); + scr_print_current(numbuf); scr_print_current(", press any key...\n"); } @@ -472,11 +597,13 @@ int irc_read(void) { err = nstatus(url); if(err != 1) { + regged = 0; if(err == 136) { scr_print_current("Disconnected, press any key...\n"); } else { print_errnum(); } + bell(); cgetc(); scr_display(0); return 0; @@ -529,23 +656,71 @@ static void scrollback() { scr_end_scrollback(); } +static void hunt_screen(signed char dir) { + signed char s = scr_current; + + do { + s += dir; + if(s < 0) + s = MAX_SCREENS - 1; + s %= MAX_SCREENS; + } while(scr_status[s] == SCR_UNUSED); + + scr_display(s); +} + +void switch_to_active() { + char i; + + for(i = 0; i < MAX_SCREENS; i++) { + if(scr_status[i] == SCR_ACTIVE) { + scr_prev = scr_current; + scr_display(i); + return; + } + } +} + static void start_keystroke(void) { char i, s; i = cgetc(); if(i >= '1' && i <= '7') { s = i - '1'; - if(scr_status[s] != SCR_UNUSED) - scr_display(s); + if(s != scr_current) { + if(scr_status[s] != SCR_UNUSED) { + scr_prev = scr_current; + scr_display(s); + } + } } else if(i == CH_CURS_UP || i == '-') { scrollback(); - } else if(i == 0x1b) { /* escape */ + } else if(i == CH_ESC) { + scr_prev = SCR_PRIV; scr_destroy(scr_current); + } else if(i == CH_CURS_LEFT || i == '+') { + scr_prev = scr_current; + hunt_screen(-1); + } else if(i == CH_CURS_RIGHT || i == '*') { + scr_prev = scr_current; + hunt_screen(1); + } else if(i == CH_TAB) { + i = scr_current; + scr_display(scr_prev); + scr_prev = i; + } else if(i == 'a' || i == 'A') { + switch_to_active(); + } else if(i == 's' || i == 'S') { + edbox_hide(); } } static void keystroke(void) { if(OS.ch == 0xff) return; + if(irc_away) { + txbuf_send_str("AWAY"); + irc_away = 0; + } if(GTIA_READ.consol == 6) { /* start pressed */ start_keystroke(); } else { @@ -556,6 +731,10 @@ 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(!irc_read()) return; keystroke(); } @@ -11,6 +11,7 @@ extern char *rx_buf; extern unsigned short rxbuflen; extern unsigned char err; extern unsigned char trip; +extern char hz; extern unsigned int txbuflen; extern char *tx_buf; @@ -41,9 +42,12 @@ void print_error(unsigned char err); /**** irc.c */ #define MAX_MSG_ARGS 8 +extern char numbuf[10]; extern char *msg_src, *msg_cmd, *msg_dest, *msg_text; extern char *msg_args[MAX_MSG_ARGS]; extern int msg_argcount; +extern char irc_away; +extern char bell_type; /* call this once, right after TCP connection is established. */ void irc_register(void); @@ -57,3 +61,4 @@ void print_errnum(void); /**** cmd.c */ void cmd_command(char *cmd); void cmd_execute(void); +long read_rtclok(void); /* irc.c needs this one so it's not static */ @@ -23,13 +23,13 @@ char url[256] = DEF_URL; // URL char usernick[32] = DEF_NICK; -char tmp[8]; // temporary # to string unsigned char err; // error code of last operation. unsigned char trip=0; // if trip=1, fujinet is asking us for attention. bool old_enabled=false; // were interrupts enabled for old vector void* old_vprced; // old PROCEED vector, restored on exit. unsigned short rxbuflen; unsigned int txbuflen; // TX buffer length +char hz; /* 50 for PAL, 60 for NSTC */ /* TODO: user modes (default +iw), fg/bg color... */ @@ -142,6 +142,8 @@ int main(void) { OS.color2 = 0xc0; /* darkest green background */ OS.color1 = 0x0c; /* bright text */ + hz = (GTIA_READ.pal & 0x0e) ? 60 : 50; + scr_init(); while(1) { diff --git a/ui_keys.txt b/ui_keys.txt index ddc467d..a6b8dd9 100644 --- a/ui_keys.txt +++ b/ui_keys.txt @@ -1,13 +1,13 @@ Hold down Start and: 1-7 - switch screens -Up Arrow or "-" - scroll current screen up +Up Arrow (without Control) - scroll current screen up +Escape - close screen (but do not part channel) +Tab - switch to last displayed screen +Left/Right (without Control) - previous/next screen. +A - switch to active screen +S - show status (hide edit box) Future plans: -A - switch to active screen -Tab - switch to previous screen -S - show status (hide edit box) -Escape - close screen (end query, or part channel). needs confirmation! -Left/Right - previous/next screen. ? - Show help (also the Help key by itself will do this). |
