diff options
| -rw-r--r-- | TODO | 53 | ||||
| -rw-r--r-- | config/Makefile | 10 | ||||
| -rw-r--r-- | config/config.c | 68 | ||||
| -rw-r--r-- | doc/dynamic-screens.txt | 396 | ||||
| -rw-r--r-- | doc/editing_keys.txt | 76 | ||||
| -rw-r--r-- | doc/ideas.txt | 18 | ||||
| -rw-r--r-- | doc/scrollback.txt | 6 | ||||
| -rw-r--r-- | doc/statusbar.txt | 16 | ||||
| -rw-r--r-- | doc/ui_keys.txt | 37 | ||||
| -rw-r--r-- | font_dl.asm | 3 | ||||
| -rw-r--r-- | loading.asm | 4 | ||||
| -rw-r--r-- | src/a2uint.s | 65 | ||||
| -rw-r--r-- | src/addrs.c | 11 | ||||
| -rw-r--r-- | src/addrs.h | 10 | ||||
| -rw-r--r-- | src/bell.s | 7 | ||||
| -rw-r--r-- | src/cgetc.s | 24 | ||||
| -rw-r--r-- | src/cio.s | 8 | ||||
| -rw-r--r-- | src/cmd.c | 138 | ||||
| -rw-r--r-- | src/complete.c | 10 | ||||
| -rw-r--r-- | src/config.c | 3 | ||||
| -rw-r--r-- | src/config.h | 2 | ||||
| -rw-r--r-- | src/edbox.c | 295 | ||||
| -rw-r--r-- | src/edbox.h | 20 | ||||
| -rw-r--r-- | src/edboxutl.s | 203 | ||||
| -rw-r--r-- | src/exehdr.s | 11 | ||||
| -rw-r--r-- | src/indic8.c | 46 | ||||
| -rw-r--r-- | src/indic8.h | 29 | ||||
| -rw-r--r-- | src/irc.c | 471 | ||||
| -rw-r--r-- | src/irc.h | 36 | ||||
| -rw-r--r-- | src/isnum.s | 18 | ||||
| -rw-r--r-- | src/keyclick.c | 8 | ||||
| -rw-r--r-- | src/keyclick.h | 1 | ||||
| -rw-r--r-- | src/keytab.c | 274 | ||||
| -rw-r--r-- | src/keytab.h | 34 | ||||
| -rw-r--r-- | src/kgetc.h | 2 | ||||
| -rw-r--r-- | src/kgetc.s | 127 | ||||
| -rw-r--r-- | src/main.c | 64 | ||||
| -rw-r--r-- | src/nextarg.s | 78 | ||||
| -rw-r--r-- | src/nio.c | 268 | ||||
| -rw-r--r-- | src/nio.h | 59 | ||||
| -rw-r--r-- | src/permute.s | 78 | ||||
| -rw-r--r-- | src/pollkbd.s | 93 | ||||
| -rw-r--r-- | src/printnum.s | 80 | ||||
| -rw-r--r-- | src/rxtxbuf.h | 5 | ||||
| -rw-r--r-- | src/screen.c | 49 | ||||
| -rw-r--r-- | src/screen.h | 14 | ||||
| -rw-r--r-- | src/sio.s | 16 | ||||
| -rw-r--r-- | src/streq.h | 5 | ||||
| -rw-r--r-- | src/streq.s | 134 | ||||
| -rw-r--r-- | src/timers.h | 8 | ||||
| -rw-r--r-- | src/txbuf.s | 50 |
51 files changed, 2479 insertions, 1062 deletions
@@ -1,9 +1,14 @@ See also: ideas.txt. +Stuff marked with [*] means, things that must be done before Alpha-2. +Once they're all finished, I'll tag alpha2 and post it on AA. + FujiChat features, we're almost at parity! -- Configurable ctcp version response. Does it matter? -- Keyboard buffer, if possible. Right now we miss the occasional +X Configurable ctcp version response. Not going to do for now. Having + the git commit and date in the version response is more important, + during the alpha release cycle. +- [*] Keyboard buffer, if possible. Right now we miss the occasional keystroke when typing fast & furious during network I/O. Not sure how to accomplish this with FujiNet (the old FujiChat key buffer actually hooked into the SLIP code). Use a VBI I @@ -12,35 +17,33 @@ FujiChat features, we're almost at parity! Other stuff: -- Stop using ASCII translation on the FujiNet. It's turning a outgoing - ~ into a ^H (since ATASCII's backspace is DEL in regular ASCII). In - theory IRC servers always send us \r\n, we can support non-conforming - servers that just use \n without much trouble, too. -- Load/save config files to N:SD//.FujiNetChat or such. Since we *have* +- [*] sending someone "/me" in a query should highlight red, not white. +- [*] edit box issues with the 240th character. currently everything seems + to work properly, but you can't type 240 chars (only 239). out of + patience for it right now... +- Maybe: get rid of the Hide MOTD and Show PING/PONG preferences. People + can always type /motd if they're interested, and the ping/pong stuff has + been tested thoroughly by now. +- [*] Start+A should *always* switch windows, even if all are inactive. + It can act like Start+Left in that case. Or (I'm told) Start+Tab might + be better? Not sure about that one. +- [*] Auto-pinging the server seems to work, but needs more testing. +- If you "/m #channel message" or "/m nick message" from another + screen besides the one for #channel or nick, the message should be + printed locally in the correct screen, not the current one. Maybe + or maybe not: switch to that screen. +- Load/save config files to N:SD///.FujiNetChat or such. Since we *have* to have a FujiNet anyway, might as well make better use of it. - Rewrite the incoming message parser! It needs to work more like the command parser in cmd.c: know how many arguments to expect, and not blindly assume they're present. Also, replace strtok() and strstr() with nextarg(). -- Make scr_activate() *not* set scr_status[scr_active]. Also make - printing to a screen aware of the proper SCR_* status to set. - This will matter even more once there's another status (4th statusbar - color). -- Status bar needs another color (COLOR2). It will mean "new non-chat - text" (e.g. join/quit/part, numerics). The existing SCR_ACTIVE (COLOR1) - will mean *only* new text (that doesn't hilite you), and COLOR3 (red) - will stay the same as it now. -- Filtered-out keystrokes (ctrl-3 and friends) shouldn't even bring up - the editbox, if it's not already showing. -- Auto-pinging the server seems to work, but needs more testing. - *Thoroughly* test the nick and channel tab completion for the [private] and [server] screens. - Fix the nick completion in channel screens. It works, but needs some polishing up. - More scrollback. Of course it needs more memory... see doc/scrollback.txt for my ideas on this. -- ^U works, but is slow (calls backspace() in a loop, which does a - memmove() each time). - Server /ping command is iffy (see do_server_pong() in irc.c). - "User has kicked <yourname> from #channel", the name should be replaced by "you". @@ -48,20 +51,13 @@ Other stuff: At least we shouldn't overflow any buffers. - 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 (?). - Add an optional key parameter to /join (key). spec calls for it, I've never seen it used. -- '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"). - *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. -- 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: @@ -71,11 +67,8 @@ Config file and initial config: 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. More prefs: -- CTCP VERSION response? Do we need it? - Ignore list (maybe). - Connect macro (log in to bot). diff --git a/config/Makefile b/config/Makefile index 2e0fd05..9ce3ffe 100644 --- a/config/Makefile +++ b/config/Makefile @@ -3,11 +3,7 @@ all: config.xex #config.xex: config.c exetrailer.s ../src/config.h ../src/config.c config.xex: - cl65 -DVERSION='"$(VERSION)"' -Oris -t atari -C ../src/atari.cfg -o config.xex config.c exetrailer.s ../src/config.c + cl65 -DVERSION='"$(VERSION)"' -m config.map -Oris -t atari -C ../src/atari.cfg -o config.xex config.c os2ram.c ../src/exehdr.s exetrailer.s -test: - cl65 -Oris -t atari -C ../src/atari.cfg -o config.xex config.c exetrailer.s ../src/config.c - cl65 -Oris -t atari -C ../src/atari.cfg -o conftest.xex conftest.c - cat config.xex conftest.xex > autorun.sys - cp dos25_sd.atr test.atr - axe -w autorun.sys test.atr +clean: + rm -f config.xex diff --git a/config/config.c b/config/config.c index 64924f0..0fb2fab 100644 --- a/config/config.c +++ b/config/config.c @@ -10,6 +10,8 @@ #include <peekpoke.h> #include "../src/config.h" +#include "../src/timers.h" +#include "os2ram.h" #ifndef VERSION #define VERSION "?????" @@ -38,18 +40,31 @@ char *servers[] = { 0 }; +conf_t *conf = (conf_t *)0x0400; + char have_dos = 0; char filename[101] = "D:FNCHAT.CFG"; char buf[128]; char numbuf[4]; char server[101]; char port[6]; +char use_xl_ram = 1; + +unsigned int *bonus_addrs = (unsigned int *)0xd4; /* aka FR0 */ + +void setup_timers_and_exit(void) { + timers.hz = (GTIA_READ.pal & 0x0e) ? 60 : 50; + timers.one_tenth_sec = timers.hz / 10; + timers.one_sec = timers.hz * 60; + timers.net_ind_time = (timers.hz / 10) * 37; + exit(0); +} char lcgetc(void) { char c; c = cgetc(); - if(c == 0x03) exit(0); + if(c == 0x03) setup_timers_and_exit(); return c; } @@ -183,7 +198,14 @@ void prompt_alert_type(void) { } void no_dos(void) { - print("No DOS booted\n"); + print("No DOS booted (using $0700 area for\nextra scrollback)\n"); + bonus_addrs[0] = 0x0700; + bonus_addrs[1] = 0x0ae8; + bonus_addrs[2] = 0x1000; /* start on a 4K boundary */ + bonus_addrs[3] = 0x13e8; + bonus_addrs[4] = 0x17d0; + bonus_addrs[5] = 0x1bb8; + bonus_addrs[6] = 0; /* screen #7 doesn't have this */ } char want_overwrite() { @@ -331,7 +353,11 @@ char prompt_main(void) { print("[E]dit [C]onnect [C]? "); c = tolower(lcgetc()); - if(c == 0x15) { + if(c == 0x14) { + strcpy(conf->nick, "Urch600XL"); + conf->channels[1][0] = conf->channels[2][0] = 0; + c = 'c'; + } else if(c == 0x15) { /* super-secret Urchlay mode... */ strcpy(conf->nick, "Urch600XL"); c = 'c'; @@ -509,6 +535,20 @@ void prompt_server() { } } +void detect_xl(void) { + if(!is_xl()) { + print("400/800 detected, no extra RAM\n"); + use_xl_ram = 0; + return; + } + + use_xl_ram = yn("XL/XE detected, use extra RAM", use_xl_ram); + if(!use_xl_ram) return; + + os_to_ram(); +} + + void set_default_config(void) { memcpy(conf, &defaults, sizeof(conf_t)); } @@ -516,8 +556,15 @@ void set_default_config(void) { void main(void) { char bad; + /* disable Break key. don't have to do it again in the client! */ + POKE(0x10, PEEK(0x10) & 0x7f); + POKE(0xd20e, PEEK(0x10)); + /* loading1.xex left the original DL address at $fe */ - POKEW(0x0230, PEEKW(0xfe)); + if(PEEKW(0xfe)) POKEW(0x0230, PEEKW(0xfe)); + + /* clear the bonus screen addresses in case something left junk there */ + memset(bonus_addrs, 0, 14); set_default_config(); @@ -529,6 +576,8 @@ void main(void) { print("Version " VERSION "\n\n"); detect_dos(); + // detect_xl(); // not yet + if(!load(0)) print("Using built-in default config.\n"); @@ -540,7 +589,7 @@ void main(void) { print("remaining questions.\n\n"); if(memcmp(&defaults, conf, sizeof(defaults)) != 0) - if(yn("\nReset to defaults", 0)) + if(yn("Reset to defaults", 0)) set_default_config(); parse_url(); @@ -548,6 +597,7 @@ void main(void) { prompt("Nick", conf->nick, 25); prompt_server(); + make_url(); do { bad = 0; @@ -561,15 +611,13 @@ void main(void) { prompt("Name ", conf->real_name, 25); prompt_colors(); prompt_alert_type(); - conf->show_ping = yn("Show PING/PONG", conf->show_ping); + // conf->show_ping = yn("Show PING/PONG", conf->show_ping); conf->atract_away = yn("Set AWAY on ATRACT", conf->atract_away); conf->hide_motd = yn("Hide MOTD", conf->hide_motd); - conf->disable_keyclick = yn("Disable keyclick (XL/XE)", conf->disable_keyclick); + conf->disable_keyclick = yn("Disable keyclick", conf->disable_keyclick); prompt_channels(); prompt_extra_channels(); - make_url(); - print("\nURL: "); print(conf->url); putchar('\n'); @@ -580,4 +628,6 @@ void main(void) { Clearing the screen here band-aids it: a byte of the client's screen memory is getting zeroed out, but it's a byte that was already zero... */ print("\x7d"); + + setup_timers_and_exit(); } diff --git a/doc/dynamic-screens.txt b/doc/dynamic-screens.txt new file mode 100644 index 0000000..63d2f7c --- /dev/null +++ b/doc/dynamic-screens.txt @@ -0,0 +1,396 @@ +Dynamic screen memory for FujiNetChat +------------------------------------- + +This *doesn't exist* yet, even if I speak of it in the present tense +in this document! + +Goals: + +- Maximize use of available memory for scrollback. This means variable + sized screens, not fixed to a given address (though they *will* be + fixed to a given bank in extended RAM). + +- Support at least 15 screens. Maybe more. There will be *some* limit, + anyway. + +- Don't waste memory: if you only use a few screens, you shouldn't have + a bunch of memory reserved for the screens you don't use. You'll only + pay for what you use. + +- Compatible with 48K, 64K (using RAM under OS), 130XE, and at least + 256K or 320K upgraded memory. It will be able to use as much RAM as + you have, up to some limit (U1MB?). + +- *Not* require the 130XE's separate ANTIC access mode. While this + might be helpful, there's a large installed base of expanded 800XLs + that don't support it. Plus, the ANTIC bit in PORTB is one of the + bits that might get repurposed as a bank bit, for machines with + loads of RAM. + +- Able to use the extra 6.4K you get if you boot without DOS (straight + from the FujiNet), or any space between MEMLO and $2000 (if you boot + a DOS with a small memory footprint). *Maybe* also include whatever + space there is between the end of the client's code/data and the + start of cc65's stack (basically, the entire "heap" area). Whether + or not this happens depends on how large the client code gets: if + it's less than 1K free, it's not really worth the trouble. + +- Able to use 4K of the RAM under the OS in an XL/XE machine. 3K of this + would be at $D800-$E3FF: the floating point pack and the font. Another + 1K is at $CC00 (the international font). + +- A *single* executable that works on all of the above (no special + fnchatxe.xex for extended RAM), meaning it has to detect the + amount of RAM and which extended banks exist. + +- Adding new text to a screen won't require scrolling all the existing + text up by moving it in memory, so it'll be *fast*. + +- Config options to disable some of the memory. If you're running + SpartaDOS, you need a way to tell FujiNetChat not to use the extra + RAM under the OS. If you use a ramdisk that only uses some of your + extended memory banks, you need a way to tell FujiNetChat to leave + those banks alone. + +Terms: + +Bank - Hopefully you already know the concept of bankswitching. In + this document, I number the banks 0 (for the base 64K) through... + I suppose up to 255, since I'll use a byte to store the bank + number. This means we might support up to 4MB of memory, if + such a thing exists for the Atari. An unexpanded 130XE has + 5 banks, which I number 0 through 4. A Rambo 800XL has 13 + banks, numbered 0 to 12. + +Chunk - a 40x23 (or smaller) piece of a screen (can be thought of as a + "display window"). At any given time, the screen can only be + displaying one chunk. Normally, this is the bottom-most one, where + new text is printed as it comes in. The top-most chunk of a screen + can be fewer than 23 lines (e.g. if there are 30 lines, you get one + 23-line chunk and one 7-line one). + +End Marker - a "special" line whose pointer is set to point to itself, + and whose data is all spaces. This line is shared by all screens, and + actually be displayed (e.g. if the screen is less than 23 lines, + they are displayed at the bottom, then the rest of the GR.0 lines + are all end markers). + +Screen - a scrollable (backwards and forwards) area that displays text, + like FNChat already uses. In this scheme, each screen will have a + pool number, a line count, a scrollback line count (0 = not scrolled + up) and a pointer to the first (bottom-most) line. If the pointer + points to the End Marker line, that means no lines are assigned to + the screen yet (it was just created and hasn't been written to yet). + Otherwise, it points to the address of the *bottom-most* line. + A null pointer (0) would be an error, and should never exist. + Also the screens will have a title and a status, like the current + ones do. + +Scrollback - as a noun: the part of the screen that's not normally + visible. As a verb, the act of making that part visible. Scrolling + will generally be done one chunk at a time, though there's no + reason there couldn't be a "one line at a time" scrolling mode. + +Screen height - the total number of lines in a screen (includes all + its chunks). The minimum height of a screen, upon creation, is + actually 0: it has no lines until it's written to. + +Line - 42 bytes of memory that store 40 characters (one GR.0 line) of + text, plus a 2-byte pointer to the next line (in the screen, or in + the free line list). Lines in a screen are stored in a linked list + (each points to the next), in reverse order of how they're displayed + (bottom-most points to the 2nd-to-bottom, etc, and the top one in + a screen points to the End Marker). Lines in the free list are also + stored as a linked list, associated with the pool, not any screen. + A single line cannot cross a 4K boundary, because ANTIC wouldn't + be able to display it properly. + +Free Line - a line that isn't being used by any screen. All the + free lines in a pool are a linked list: initializing the pool + sets up the pointers in all the lines. Closing a screen releases + all its used lines into the pool's free line list. + +Pool - A (possibly non-contiguous) region of memory available for lines. + Each pool has a bank number, a count of unused lines, and a linked + list of the unused lines in the pool. Pool 0 is in main memory, + is always at least 16K, and can be up to 26880 bytes in size (using + 4K of under-the-OS RAM for an XL/XE, plus any space from MEMLO + to $2000 if DOS is not booted, or if a DOS with low MEMLO is + booted). The other pools consist of entire banks, 16K apiece, one + per bank. Each screen is created in one pool and cannot be moved to + another pool. + +Initialization: + +At startup, FujiNetChat detects the amount of memory (number of +extended banks) and creates a pool for each bank. Bank 0's pool can +include extra memory beyond 16K: whatever's not in use by the client, +or by DOS (if you booted with one, even). Also pool 0 might have some +of the RAM under the OS on XL/XE (because FujiNetChat doesn't use the +two 1K ROM fonts or the 2K math pack, so we get 4K "for free"). All other +pools (1 and up) will be 16K. + +At startup, the [server] and [private] screens will be created. Also +autojoin channels/queries will each get a screen created. + +When creating screens, they're assigned to pools in round-robin +style. Suppose we have 5 banks of memory (0 through 4), with one pool +each. The first screen is created in bank 0. The second screen will be +created in bank 1, 3rd in bank 3, etc. After all pools have one screen +in them, the next screen creation will use pools 0 again (so now +we have two screens in one pools). This can continue until we reach +whatever the limit is: 15 screens? 20? Maybe calculated based on the +number of banks, so we can guarantee that when all memory is in use, +each screen will have a minimum of 23 lines. With a 130XE, this would +be a stupid amount of screens: 17 per bank for the 4 extended banks, +and at least 17 for bank 0 (so 85 of them, that's too many). Maybe +limit it to 28, which guarantees each screen can be 3 chunks (69 +lines) tall? + +The reason for the round-robin creation: Suppose you're only going to +use 3 screens (server, private, and one channel). It makes more sense +for each of those 3 to be in its own bank, so each one can grow to +16K (around 390 lines, or ~17 chunks). If we created them all in bank 0, +they'd compete with each other for memory, which is silly when there's +plenty of free RAM in the other (unused) banks. + +Writing text to a screen consists of... + +- Find a free line in the screen's pool (see below). +- Fill the line with the new text. +- Make the line's 'next' pointer point to the screen's 'head' pointed to. +- Make the screen's 'head' point to the new line. + +The lines in a screen are stored as a textbook example of a linked list. + +What happens if we're displaying a screen in one bank, and need +to add text to a screen that's in a different bank? Well, we have +to bankswitch to write to the new bank. But doing so will make that +bank replace the screen memory for the screen we were looking at. So, +bankswitching and writing has to take place during the vertical blank +interval, when ANTIC is done displaying the screen and no longer +reading from RAM. + +*Careful*, without writing the code I don't yet know if there's enough +time in one VBLANK to write a huge (up to 510 bytes) IRC message in +one go. It'll be OK if it takes more than one frame, but not more than +maybe 4 or 5 (that'll make the app feel sluggish). Assembly optimization +is a must for this. Also, we don't have to wait for the VBLANK interrupt +to happen: we can start after the last visible scanline and work through +until just before the start of the first visible scanline on the next +frame. + +Finding a free line: + +- See if there's a free line in the pool (if the head of the free lines + list is not null, and/or if the free lines count is not 0). +- If you find one, add it to the screen (see above), and remove it + from the free list (make the pool's free_list point to whatever + the line's 'next' pointed to). Also decrement the free lines count for + the pool. +- If there isn't a free line, we have to 'steal' one from another + screen in this pool. For now, just take the one with the most + lines and steal its top line, and add it to the screen we're writing to. + This means the screens "compete" and eat each other :) + +'Stealing' means the screens will automatically balance, to some degree. +If you have 3 active channels in one bank, during busy periods the 3 +screens will tend to be around the same size. If one channel goes quiet, +the other 2 will steal lines from it until it gets down to 23 lines, +then they'll start stealing from each other instead. Maybe the minimum +should be 46 or 69 lines (2 or 3 chunks), to avoid the scenario where +you leave the Atari connected while you sleep, and 2 busy channels ate +all the 6+ hour old text in the other, that came in an hour after you +went to bed? + +Closing a screen: + +When a screen is closed, its lines are returned to the free lines list +in the pool. Since they're already a linked list, all that's needed is +to add the screen's 'head' to the end of the pool's free lines list, +and add the screen's line count to the pool's free line count. + +Displaying the screen: + +All the screens share the same display list, which lives in main memory. + +The display list has an LMS for every line. The top 23 lines are for +the screen, the bottom two are the status bar or edit box (always the +same address; stored in main menory). + +The LMS operands get set like so: + +Switch to the screen's bank, then... + +Starting at the screen's 'head' line, and the last LMS (bottom of +23-line area of the DL), walk the linked list of lines (which are in +bottom-first order) and the display list (backwards). + +If we're scrolled up, just keep walking as many lines as we're +scrolled up (e.g. 23 for one chunk). + +When we've walked to the first (bottom-most) line to display (which will +be the 'head' one, if we weren't scrolled back), write its address as +the current LMS's 16-bit operand, then move on to the next line +and the next LMS... + +Repeat until we hit the end of the screen (the line count), or we +hit 23 LMSes. At this point, we're done. + +Ideally, we'll double-buffer the display list (2 of them, one +displaying while the other's being rewritten), and switch to the newly +modified one during the next VBLANK (just update SDLSTL/H and let +the OS do it). Note that we *don't* have to deal with banking in the +display list: we can only show one screen at a time, so we don't need +to bankswitch. + +Switching screens, or scrolling back the current screen, will require +rebuilding the display list. There's no need to rebuild it every +frame (there'll be a 'dirty' flag that gets set when switching or +scrolling). + +Scrolling the screen up (or down) is just a matter of setting the +screen's scroll height. It should *never* be set higher than the +screen's height, and probably the UI will increase by 23 for each +press of Start+Up. So if we have 30 lines, counting from 1 (top) to 30 +(bottom), we're normally looking at lines 8 to 30. Scrolling up by +one chunk will show lines 1 to 7 at the bottom, then the rest of the +display (the top 2/3s or so) will all be the End Marker line, which +appears blank. At that point, it won't be possible to scroll again: +We're at 23, adding another 23 would exceed the height of 30 lines, +so the attempt is just ignored). + + +Memory layouts for typical machine sizes... + +- A 48K 800 will only have pool 0, which will be either 16384 bytes +or ~390 lines (if DOS is booted) or 22784 bytes or ~542 lines without +DOS. This is enough to have 7 or 8 screens without about 3 chunks +(69 lines) apiece, which is better than the exising fixed-buffer code +manages. + +- A 64K 800XL/1200XL/65XE/XEGS will only have pool 0, which will be +either 20480 bytes (~487 lines) with DOS, or 26880 bytes (~640 lines) +without DOS. + +- A 128K 130XE will have pool 0 as the XL does, plus another 4 pools +of 16K each. That's 2048 lines (with DOS), which could be organized +as e.g. 10 screens of 200 lines each, or 16 with 128 lines each, or 20 +with 100 lines each. + +- A 256K upgraded XL with DOS will have 132K (135168), or about 3200 +lines. For 512K, roughly twice that. To really take advantage of 256K, +you'll actually have to create enough screens so that all the banks +are used (16 screens with 16K each except the one in pool 0 gets +more). 512K will allow 32 screens, each in its own pool, with close +to 400 lines of scrollback in each (~17 chunks per screen!) + + +Milestones: things that will have to happen to make this a reality. + +1. First and foremost, FNChat needs to go on a diet! Lots of stuff + to rewrite in asm, to shrink it down. Currently, it's right at + 21K, but it's really more because rx_buf, tx_buf, and the font eat + another 2K (in the screen memory area, in lieu of an 8th screen), + plus all 3 display lists. No point optimizing the existing screen + code for size, though, it's going to be replaced. See doc/diet.txt + for details/progress on this. + +2. Split the code/BSS/etc into high and low segments, so it lives from + $2000 to $3FFF (low, 8K) and $8000 to $BFFF (high, 16K). This + puts the primary screen memory area right where the XE bankswitching + needs it to be, and gives a *total* size of 24K for the client, + including the font, display lists, and all buffers (except ones + located in very low memory, $400-$6FF; these are the config and the + editbox, and can stay where they are). + + The font and buffers have to be moved out of the new screen address + space (the banking window at $4000). I'm thinking the font will be + just below the bank area at $3A00, 2 512-byte buffers below that + at $3600, display lists below that, etc. Leave about 6K for 'low + code', the cc65 stack, and the BSS. Everything else will be in a + 'high code' (and possibly 'high BSS') segment, from $8000 to $BFFF. + +3. Rip out all the existing screen code and replace it with a simplified + form of the new scheme. To start with, only 1-5 pools in bank 0, but + the display list modification code can be completed. The rest of + the code (especially irc.c) is gonna need changes, because it + "knows" that there are "always" 7 screens. This stage includes + defining new hotkeys for screen numbers above 7, and making the + status bar variable sized (show only the number of screens we + have enough RAM for, based on the minimum size being 23 or 46 + lines). + +4. Learn more than I currently know about bankswitching (I know the + 130XE, but what's the difference between a Rambo and a Compy Shop + upgrade? I *think* I know how to detect all the available banks, + but what about when the self-test and/or BASIC bits are being + used for bank bits instead? Do I want to even try to support + the Axlon and Mosaic upgrades for the 800?) + +5. Do a version that supports a 130XE (4 extra banks only). Get it + well tested, fix the inevitable issues that are going to happen. + This version should also still be usable on 48K/64K. This will + probably involve adding the memory detection to the config + segment (along with copying the OS to RAM if possible). It'll + deposit the pools array somewhere in screen memory, and the + client will memcpy() that to its pools[] (before scr_init() + is called). No point keeping all that startup code in memory + the whole time the client runs. + +6. Add support for more banks (detection and use). + +Rest of the file is C structs that define the stuff above. This is +just hypothetical code (final implementation may look different). + +/* if each pool is 16K, that's 512K, not bad. however, overhead. maybe + limit this to something like 20 (128K extended = 16, plus the big + pool in main bank, plus the potential smaller pools at MEMLO and + $d800. */ +#define MAX_POOLS 32 + +/* with 512K, we get one screen per pool. + with 256K, up to 2 screens per pool. + with 128K, up to 4. + with 64K, we only get 1 large pool and a couple small ones. +*/ +#define MAX_SCREENS 32 + +/* 42 bytes per line */ +typedef struct line_s { + struct line_s *next; + char data[40]; +} line_t; + +/* the end marker line is a line_t, but it lives outside of any pool and + has a 'next' pointer that points to itself. */ + +/* sizeof(screen_t) is 33 bytes... */ +typedef struct { + char title[25]; + char status; + char pool; + int line_count; /* can be above 255 */ + int scrollback_pos; + line_t *line_list; /* head of a linked list */ +} screen_t; + +screen_t screens[MAX_SCREENS]; /* array is 1023 bytes */ + +typedef struct { + u16 start; /* 0 = not in use */ + u16 end; + u8 bank; /* probably this is just the PORTB value */ + line_t *free_list; /* when this is null, the pool has no free lines */ +} pool_t; + +/* this array is sizeof(pool) * 9, so 288 bytes */ +/* the code that builds this array (detects extended ram too), + will live in the config segment. */ +pool_t pools[MAX_POOLS]; + +/* this function is responsible for counting the usable lines (the ones + that don't cross a 4K boundary) and arranging them in a linked list + that includes all the usable ones. I suppose it should bzero() the + memory first. */ +void add_pool(u8 bank, u16 start, u16 end); diff --git a/doc/editing_keys.txt b/doc/editing_keys.txt index b6a2cbb..49be8bf 100644 --- a/doc/editing_keys.txt +++ b/doc/editing_keys.txt @@ -1,35 +1,59 @@ This is kind of a weird mix of UNIX/Emacs/bash and traditional Atari. -^A - move to start of buffer -^E - move to end of buffer -^U, Shift-Del - delete (clear) buffer -Shift-Clear or Ctrl-Clear: clear buffer and hide input box (show status) -^W - delete word to left of cursor -Left/Right arrows - move cursor -Backspace - delete the character to the left of the cursor -Ctrl-Del - delete the character under the cursor -Atari key - insert a ^B (meaning, toggle bold) -^U - delete to start of buffer -^K - kill (delete) to end of buffer. -^F or ctrl-shift-Up - move right by one word. -^B or ctrl-shift-Down - move left by one word. -Tab - in [private], pressing Tab on an empty input box inserts the - last nick that PMed you (outside of a query). +ASCII characters: -Future plans: +Shift-Escape - backtick: ` +Ctrl-Escape - tilde: ~ +Ctrl-comma - left curly brace: { +Ctrl-period - right curly brace: } + +Cursor movement: -History (if we can spare the RAM), maybe Start+Up/Down? +Left/Right arrows - move cursor left/right +Up arrow - if the inputbox has anything in it, move up 1 line, + aka 40 characters, or to the start if the text is less than + 40 characters. Up arrow *in an empty inputbox* - bring up last entered command. - Can coexist with regular use of Up for movement. + If this doesn't seem to work, make sure your input box really is + empty (spaces are invisible, but count as "not empty"). If in + doubt, press Shift-Del. +Down arrow - moves down 1 line, aka 40 characters. If this would + move past the end of the text, moves to the end. +Ctrl-A - Move to start of buffer. +Ctrl-E - Move to end of buffer. +Ctrl-F or Ctrl-Shift-Up - move right by one word. +Ctrl-B or Ctrl-Shift-Down - move left by one word. + +Deleting text: + +Backspace - Delete the character to the left of the cursor. In insert + mode, it closes up the gap. In typeover mode, it leaves a space + where the deleted character was (like BASIC does). +Shift-Del - Delete (clear) buffer. +Shift-Clear or Ctrl-Clear: clear buffer and hide input box (show status). +Ctrl-Del or Ctrl-X - delete the character under the cursor. +Ctrl-U - Delete to start of buffer. +Ctrl-W - Delete word to left of cursor +Ctrl-U - Delete to start of buffer +Ctrl-K - Kill (delete) to end of buffer. + +Formatting: + +Atari/Inverse key - Toggle bold (displays as inverse B). +Ctrl-I - Toggle italic (displays as down-arrow). +Ctrl-shift-U - Toggle underline (displays as right-arrow). + +Other: + +Tab - Nick/channel completion (only at the start of an empty input box!) +Ctrl-Insert - Toggle insert/typeover. +Escape - Treats next keystroke as though Start were held down. + Pressing Escape again gets out of this mode. + + +Future plans: + ^Y, Shift-Insert - paste (^K, ^U, ^W fill a paste buffer; need RAM) -Ctrl-Insert - toggle insert/typeover (does anyone care about this?) -Tab - For [server], complete channels. For channels, complete channel -nicks (we'll never have enough RAM to have full lists; search back -through screen memory is how it'll work) Shift-Return: Maybe... send buffer but do not clear it. -The glyphs for these will appear as inverse letters, but will actually -be the appropriate low ASCII characters: -^I - toggle italic -^L - toggle underline ^S - toggle strikethrough diff --git a/doc/ideas.txt b/doc/ideas.txt index f70e47f..15d44dc 100644 --- a/doc/ideas.txt +++ b/doc/ideas.txt @@ -2,6 +2,10 @@ These are not really TODOs, they're "might do" or "hope to do". Features that should be implemented: +- /r to reply to the last person/channel who msg'd you, /a to msg the + last person/channel you msg'ed (server and private screens). Idea + comes from phigan (thanks!) + - Support for (some of) the extra RAM in 64K machines. Can get an extra 4K because we don't need the international font or the floating point routines, and we can plop our custom font directly @@ -39,3 +43,17 @@ Features that should be implemented: - Cut buffer for ^U ^K ^W, like bash/emacs. LImited size (like, 240 bytes, same as the input box). + +- Support for the XEP80 (and its modern clones). This really should be + done as dual-head: Keep the regular Atari display for screen 1, + the edit box, and the status bar. The rest of the screens can + display on the XEP80. One problem with this is, scrollback memory. + Each of screens 2-7 will be ~2000 bytes per 80x25 chunk, so each + needs 4K to be able to scroll back 1 screens' worth. So XEP80 + support might end up being a 128K-and-up-only thing. + reifsnyderb has some code that deals with the XEP, written for use + with CP/M, that may be useful here. + +- R: support. Compile-time switch, separate executable. Use with + FujiNet, Lantronix, or anything else where we can go like + ATDThostname:port. diff --git a/doc/scrollback.txt b/doc/scrollback.txt index 7aa2b54..93e1117 100644 --- a/doc/scrollback.txt +++ b/doc/scrollback.txt @@ -15,8 +15,10 @@ $0700 (MEMLO) to the start of the code ($2000). This would be enough for 6 of the screens to have another 25 lines (73 each). Of course the user would have no place to save his config file (can we use the FujiNet for that, without bloating up the code?)... also, the -load address could be bumped up to $2300 or so, which would give -us all 7 screens. +load address could be bumped up to $2400, which would give +us all 7 screens. **Partially implemented: screens 1-6 get an +extra (bonus) 25 lines of scrollback if DOS is not booted. Screen +7 is still 48 lines tall. 1.5. Same as 1, but we convert the binary to a boot disk, so we just load at $0700. Still can use the disk (image?) to write the config to diff --git a/doc/statusbar.txt b/doc/statusbar.txt index f6f76b6..40fc0b3 100644 --- a/doc/statusbar.txt +++ b/doc/statusbar.txt @@ -11,7 +11,12 @@ as one of: - Grey number: Screen has no activity since you last looked at it. -- Green number: Screen has activity that you haven't seen yet. +- Dark green number: Screen has new activity, but not channel text. + Examples: join, part, nick change messages. Also server messages + (numerics) in the [server] screen. + +- White number: Screen has new channel text in it (but your name + was not mentioned). - Red number: Screen has activity that's intended for you: a private message, or your nick has been mentioned in a channel (AKA you've @@ -27,17 +32,18 @@ do anything. When switching screens with Start+A, FujiNetChat goes to the right-most (highest numbered) highlighted screen (red). If there are no highlighted screens, it goes to the right-most active screen -(green). If there are no screens with activity, it doesn't change +(white). If there are none, it looks for the right-most dark green +one. If there are no screens with activity, it doesn't change screens. -Note that the current screen (the one you're looking at) will never be -red or green. - This works about like irssi's Meta-A key, except that irssi actually keeps track of when each of its windows became active or highlighted, and goes to the oldest one first. FujiNetChat just starts at the last screen and works its way down. +Note that the current screen (the one you're looking at) will always +be grey. + Activity -------- diff --git a/doc/ui_keys.txt b/doc/ui_keys.txt index d26a171..46d42ec 100644 --- a/doc/ui_keys.txt +++ b/doc/ui_keys.txt @@ -1,23 +1,36 @@ -Hold down Start and: +Without Start: + +Ctrl+numbers (1 to 7) - switch to screen. Same as Start+numbers, + but more convenient for XEGS and 1200XL users. +UNTESTED: 1200XL F1-F4: without Shift, switch to screens 1-4. With + shift, F1-F3 = screens 5-7 and F4 is same as Start+A. +Help (XL/XE only) - same as Start+A. + +Hold down Start, *or* press and release Escape, and: 1-7 - switch screens -Up Arrow (without Control) - scroll current screen up -Escape - close screen (but do not part channel) +Up Arrow (without Control) - scroll current screen up. Pressing + any key while scrolled up will scroll back down. Tab - switch to last displayed screen -Left/Right (without Control) - previous/next screen. -A - switch to active screen +Left/Right Arrows (without Control) - previous/next screen. +A - switch to active screen. [TODO: if no active screen, move + left one screen... maybe? would this be helpful or annoying?] +E - show full editbox (instead of screen). You can edit in this + mode, but pressing Enter, switching screens, or anything else + that normally hides the edit box will switch back to normal + screen + status bar display. S - show status (hide edit box) -Q - create screen for last PM (in [private]) or last channel - message (in [server]). Does nothing in screens 2-7. L - list all screens (number and name). -W - in query, /whois the user. in PM, last user who PMed. - in a channel or [server], no effect. N - in channel screen, do a /names #channel. in [server], last channel with traffic. no effect elsewhere. P - in a query screen, do a /ping <nick>. in [private], last nick who PMed. no effect elsewhere. +Q - create screen for last PM (in [private]) or last channel + message (in [server]). Does nothing in screens 2-7. T - /topic #channel -E - show full editbox (instead of screen). +W - in query, /whois the user. in PM, last user who PMed. + in a channel or [server], no effect. +X - close screen (but do not part channel). Ctrl-X - in a channel screen, /part the channel and close the screen. no effect elsewhere. Ctrl-D - disconnect from server (without sending a /quit). This @@ -25,7 +38,9 @@ Ctrl-D - disconnect from server (without sending a /quit). This debugging aid (so I can test the automatic server ping code). This will likely be removed before the first beta. +If you press Escape by accident, pressing it again gets you out of +"start key toggle" mode. + Future plans: J - join last channel you were invited to. -? - Show help (also the Help key by itself will do this). diff --git a/font_dl.asm b/font_dl.asm index 3cd43e5..a48699a 100644 --- a/font_dl.asm +++ b/font_dl.asm @@ -83,7 +83,8 @@ dl_status_lms .byte $70, $70, $70, $70, $70, $70, $70, $70 .byte $42 .word $0600 ; edit_box from addrs.c - .byte $02, $02, $02, $02 + .byte $02, $02, $02, $02, $02 + .byte $00 .byte $42 .word $a798 ; status_box from addrs.c .byte $06 ; GR.1 diff --git a/loading.asm b/loading.asm index ad584f3..9c93258 100644 --- a/loading.asm +++ b/loading.asm @@ -28,9 +28,9 @@ wloop rts init - lda #$c0 + lda #$c2 sta $02c6 ; COLOR2 - lda #$0c + lda #$ce sta $02c5 ; COLOR1 lda $0230 sta save_dlist_loc diff --git a/src/a2uint.s b/src/a2uint.s new file mode 100644 index 0000000..ac8a7ed --- /dev/null +++ b/src/a2uint.s @@ -0,0 +1,65 @@ + + ; replacement for atoi() that doesn't use cc65's bloated ctype. + ; also, doesn't handle signed results (not needed). + + .export _a2uint + .import _isnum + .importzp ptr1, sreg + + result = sreg + temp16 = sreg+2 + +_a2uint: + sta ptr1 + stx ptr1+1 + + lda #0 + sta result + sta result+1 + tay + +@chrloop: + lda (ptr1),y ; get next character + jsr _isnum ; is is a digit? + beq @done ; Z set = non-digit + + ; multiply result by 10 + ; { + lda result + asl + sta temp16 + lda result+1 + rol + sta temp16+1 ; temp16 now result * 2 + + ldx #3 +@x8loop: + asl result + rol result+1 + dex + bne @x8loop + + ; result now result * 8 + clc + lda result + adc temp16 + sta result + lda result+1 + adc temp16+1 + sta result+1 ; result now original result * 10 + ; } + + lda (ptr1),y ; get character again + iny ; point to next char + and #$0f ; de-ASCIIfy + clc + adc result + sta result + bcc @chrloop + inc result+1 + bne @chrloop + +@done: + lda result + ldx result+1 + rts diff --git a/src/addrs.c b/src/addrs.c index b9f0921..c7af1f2 100644 --- a/src/addrs.c +++ b/src/addrs.c @@ -8,10 +8,9 @@ u16 *dlist_bot_lms = u16p(0xbfd4); u16 *dlist_status_lms = u16p(0xbfee); u8 *dlist_last_line = u8p(0xbff0); -u8 *edit_box = u8p(0x0600); /* up to 256 bytes (page 6) */ u8 *edbox_only_dlist = u8p(0x8fb8); -u8 *rx_buf = u8p(0xa000); /* 512 bytes */ -u8 *tx_buf = u8p(0xa200); /* 512 bytes */ +// u8 *rx_buf = u8p(0xa000); /* 512 bytes */ +// u8 *tx_buf = u8p(0xa200); /* 512 bytes */ /* 1000 bytes (25 40-char lines) apiece. */ u8 *screen_top_addrs[7] = { @@ -52,3 +51,9 @@ u8 *screen_lastlines[7] = { at the bottom (2 unused lines), for now at least. addresses: 0xab80 0xaf68 0xb398 0xb780 0xbb68 0xbf50 */ u8 *status_box = (u8 *)0xa798; + +/* 1 byte, leftmost column of GR.1 line at bottom */ +u8 *ind_net_status = (u8 *)0xa7c0; + +/* 1 byte, 2nd column of GR.1 line at bottom */ +u8 *ind_act_status = (u8 *)0xa7c1; diff --git a/src/addrs.h b/src/addrs.h index 67a59c8..4099765 100644 --- a/src/addrs.h +++ b/src/addrs.h @@ -1,6 +1,7 @@ #define FONT_ADDR_HI 0x80 #define DLIST_TOP_ADDR 0xbfa0 #define DLIST_BOT_ADDR 0xbfd0 +#define EDBOX_ADDR 0x0600 #define u8 unsigned char #define u8p(x) ((unsigned char *)x) @@ -21,9 +22,6 @@ extern u16 *dlist_status_lms; or 6 for GR.1 (status box showing) */ extern u8 *dlist_last_line; -/* only one of these (not one per screen) */ -extern u8 *edit_box; - /* display list for Start+E (show only edbox) mode. */ extern u8 *edbox_only_dlist; @@ -38,3 +36,9 @@ extern u8 *screen_lastlines[7]; /* bottom 2 lines, shared by all screens */ extern u8 *status_box; + +/* network status indicator */ +extern u8 *ind_net_status; + +/* activity indicator */ +extern u8 *ind_act_status; @@ -7,6 +7,7 @@ DISTVOL = $a8 PITCH = $40 JIFFIES = $03 +FLASH_COLOR = $08 _bell: lda #<bell_callback @@ -28,10 +29,8 @@ check_flash: lda _bell_type and #2 beq done - lda #$08 + lda #FLASH_COLOR sta COLOR4 - lda #JIFFIES - sta CDTMV2 done: rts @@ -40,6 +39,4 @@ bell_callback: sta AUDC1 sta AUDF1 sta COLOR4 - lda #JIFFIES - sta CDTMV2 rts diff --git a/src/cgetc.s b/src/cgetc.s deleted file mode 100644 index dd9fe26..0000000 --- a/src/cgetc.s +++ /dev/null @@ -1,24 +0,0 @@ -; -; Christian Groessler, November-2002 -; -; get a char from the keyboard -; char cgetc(void) -; - -; Modified version for FujiNetChat. - .include "atari.inc" - .export _cgetc - -_cgetc: -;;; jsr setcursor ; this is unneeded and causes a hole in screen 7 - lda #12 - sta ICAX1Z ; fix problems with direct call to KEYBDV - jsr @1 - ldx #0 - rts - -@1: lda KEYBDV+5 - pha - lda KEYBDV+4 - pha - rts diff --git a/src/cio.s b/src/cio.s deleted file mode 100644 index 69a789a..0000000 --- a/src/cio.s +++ /dev/null @@ -1,8 +0,0 @@ - ;; Call CIO - - .export _ciov - -_ciov: LDX #$00 - JSR $E456 - RTS - @@ -7,9 +7,13 @@ #include "screen.h" #include "edbox.h" #include "config.h" +#include "streq.h" +#include "timers.h" /* A "command" is actually anything the user types, whether or - not it starts with a /character. */ + not it starts with a / character. */ + +extern void num_to_numbuf(unsigned int n); /* printnum.s */ char *command, *arg1, *arg2, *arg3; static char *target; @@ -25,16 +29,14 @@ 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); static void do_quit(void); static void do_quote(void); -static void do_topic(void); static void do_ver(void); static void do_reset(void); static void do_reboot(void); +static void do_optional_chan(void); typedef struct { char *cmd; @@ -46,6 +48,7 @@ typedef struct { OP DEOP VOICE DEVOICE KICK BAN KB IGNORE UNIGNORE MODE INVITE */ cmd_t command_defs[] = { + { "MSG", do_msg, 1 }, /* this must come first, do not move! */ { "AWAY", do_away, 1 }, { "ALERT", do_bell, 1 }, { "CLICK", do_click, 0 }, @@ -59,15 +62,14 @@ cmd_t command_defs[] = { { "LIST", do_list, 1 }, { "M", do_msg, 1 }, { "ME", do_me, 1 }, - { "MSG", do_msg, 1 }, - { "NAMES", do_names, 0 }, - { "PART", do_part, 0 }, + { "NAMES", do_optional_chan, 0 }, + { "PART", do_optional_chan, 0 }, { "PING", do_ping, 0 }, { "Q", do_query, 1 }, { "QUERY", do_query, 1 }, { "QUIT", do_quit, 0 }, { "QUOTE", do_quote, 1 }, - { "TOPIC", do_topic, 0 }, + { "TOPIC", do_optional_chan, 0 }, { "REBOOT", do_reboot, 0 }, { "RESET", do_reset, 0 }, { "VER", do_ver, 0 }, @@ -87,15 +89,8 @@ static void cmd_chan_text(void) { /* 0x02 = ^B = enable bold */ scr_print_active("<\x02"); - scr_print_active(conf->nick); - scr_print_active("\x02"); - - /* - if(!scr_current) { - scr_print_active("/"); - scr_print_active(target); - } - */ + scr_print_active(config.nick); + scr_putc_active('\x02'); scr_print_active("> "); scr_print_active(command); @@ -125,31 +120,6 @@ static void err_no_scr_target(void) { scr_print_current("No channel/nick for screen\n"); } -/* arg points to something like: - "part #channel I'm outta here\0" - after nextarg(), arg points to "part\0" only, and ret points - to "#channel I'm outta here\0". */ -static char *nextarg(char *arg) { - /* iterate over the first word */ - while(*arg && *arg != ' ') - arg++; - - /* if we found a space, replace it with a null terminator */ - if(*arg) - *arg++ = 0; - else - return 0; /* found no space, there's no next arg! */ - - /* skip space(s) */ - while(*arg && *arg == ' ') - arg++; - - if(*arg) - return arg; - - return 0; -} - static char have_commas(void) { if(strchr(arg1, ',')) { err_marker(); @@ -164,7 +134,7 @@ static void join_arg1(void) { txbuf_send(); } -static void mass_join(int scr) { +static void mass_join(char scr) { if(have_commas()) return; do { @@ -204,7 +174,7 @@ static void do_quit(void) { static void pause(void) { OS.rtclok[2] = 0; - while(OS.rtclok[2] < hz) + while(OS.rtclok[2] < timers.hz) /* NOP */; } @@ -217,26 +187,7 @@ static void do_reboot(void) { asm("jmp $e477"); } -static void do_part(void) { - if(arg1[0] == '#') { - target = arg1; - arg2 = nextarg(arg1); - } else { - arg2 = arg1; - } - - if(!target) { - err_no_scr_target(); - return; - } - - txbuf_set_str2("PART ", target); - if(arg2) - txbuf_append_str2(" :", arg2); - txbuf_send(); -} - -static void do_topic(void) { +static void do_optional_chan(void) { if(arg1[0] == '#') { target = arg1; arg2 = nextarg(arg1); @@ -249,28 +200,12 @@ static void do_topic(void) { return; } - txbuf_set_str2("TOPIC ", target); + txbuf_set_str3(cmd_def->cmd, " ", target); if(arg2) txbuf_append_str2(" :", arg2); 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"); } @@ -293,7 +228,7 @@ unsigned int read_rtclok(void) { } static void rtclok_to_numbuf(void) { - itoa(read_rtclok(), numbuf, 10); + num_to_numbuf(read_rtclok()); } void cmd_server_ping(void) { @@ -314,16 +249,18 @@ void cmd_ctcp_ping(char *nick) { do_ctcp_ping(); } -static void do_ctcp_info(void) { - arg2 = "CLIENTINFO"; +static void do_no_arg_ctcp(char *type) { + arg2 = type; arg3 = 0; send_ctcp(); } +static void do_ctcp_info(void) { + do_no_arg_ctcp("CLIENTINFO"); +} + static void do_ctcp_ver(void) { - arg2 = "VERSION"; - arg3 = 0; - send_ctcp(); + do_no_arg_ctcp("VERSION"); } static void do_ctcp(void) { @@ -357,7 +294,7 @@ static void do_me(void) { txbuf_send(); scr_print_current("\x02* "); - scr_print_current(conf->nick); + scr_print_current(config.nick); scr_print_current("\x02 "); scr_print_current(arg1); scr_eol_current(); @@ -395,9 +332,9 @@ static void do_ver(void) { static void do_color(void) { arg2 = nextarg(arg1); - OS.color2 = atoi(arg1); + OS.color2 = a2uint(arg1); if(arg2) - OS.color1 = atoi(arg2); + OS.color1 = a2uint(arg2); } static void do_query(void) { @@ -444,7 +381,7 @@ static void do_click(void) { OS.noclik ^= 1; } -static int cmd_local(void) { +static char cmd_local(void) { arg1 = nextarg(command); /* @@ -486,29 +423,25 @@ static void cmd_slash(void) { cmd_remote(); } -void cmd_command(char *cmd) { - command = cmd; +void cmd_execute(void) { + if(!*edit_box) return; + command = edit_box; if(scr_current > 1) target = scr_get_cur_name(); else target = 0; - if(cmd[0] == '/' && cmd[1] && cmd[1] != '/') + if(command[0] == '/' && command[1] && command[1] != '/') cmd_slash(); else if(target) cmd_chan_text(); else if(scr_current == SCR_PRIV || scr_current == SCR_SERVER) - cmd_send_pm(cmd); + cmd_send_pm(command); else err_no_scr_target(); } -void cmd_execute(void) { - if(!*edit_box) return; - cmd_command(edit_box); -} - void cmd_rejoin_chans(void) { char i; @@ -519,10 +452,10 @@ void cmd_rejoin_chans(void) { } } - if(!*(conf->extra_channels)) + if(!*(config.extra_channels)) return; - strncpy(edit_box, conf->extra_channels, 128); + strncpy(edit_box, config.extra_channels, 128); arg1 = edit_box; mass_join(0); edbox_clear(); @@ -530,6 +463,7 @@ void cmd_rejoin_chans(void) { /* args contains the destination, space, the msg */ void cmd_send_pm(char *args) { + cmd_def = command_defs; /* element 0 of command_defs must be MSG! */ arg1 = args; do_msg(); } diff --git a/src/complete.c b/src/complete.c index 108bc90..e76def1 100644 --- a/src/complete.c +++ b/src/complete.c @@ -1,10 +1,10 @@ #include <atari.h> #include <string.h> -#include <ctype.h> #include "addrs.h" #include "edbox.h" #include "screen.h" #include "irc.h" +#include "streq.h" #define COMP_S1 1 #define COMP_PM 2 @@ -24,10 +24,10 @@ char (*add_to)[25] = comp_pm_nicks; static char pm_nick_pos = 0; /* insertion point for _add() */ static void add_list(const char *n) { - int i; + char i; for(i = 0; i < 25; i++) - if(strncmp(n, add_to[i], 24) == 0) + if(strneq_i(n, add_to[i], 24)) return; /* we already got this one */ strncpy(add_to[pm_nick_pos], n, 24); @@ -46,7 +46,7 @@ void comp_add_pm_chan(const char *n) { } char match(const char *p, const char *q) { - int len; + char len; while(*p == '#') p++; while(*q == '#') q++; @@ -55,7 +55,7 @@ char match(const char *p, const char *q) { if(!len) return 0; while(len--) { - if(tolower(*p) != tolower(*q)) + if(lcase(*p) != lcase(*q)) return 0; p++, q++; } diff --git a/src/config.c b/src/config.c deleted file mode 100644 index f7c8982..0000000 --- a/src/config.c +++ /dev/null @@ -1,3 +0,0 @@ -#include "config.h" - -conf_t *conf = (conf_t *)0x0400; diff --git a/src/config.h b/src/config.h index 6a4bfa9..ff25ea1 100644 --- a/src/config.h +++ b/src/config.h @@ -14,3 +14,5 @@ typedef struct { } conf_t; extern conf_t *conf; + +#define config (*(conf_t *)0x0400) diff --git a/src/edbox.c b/src/edbox.c index d5728c2..0725379 100644 --- a/src/edbox.c +++ b/src/edbox.c @@ -4,34 +4,29 @@ #include "addrs.h" #include "screen.h" #include "edbox.h" -#include "keyclick.h" #include "complete.h" - -/* TODO: tab completion */ - -char *old_edbox[EDBOX_SIZE]; -static u16 old_len; - -int edbox_visible = 0; -static u16 edbox_pos; /* range 0 to EDBOX_SIZE - 1 */ -u16 edbox_len; /* idem */ +#include "keytab.h" +#include "irc.h" + +/* private API stuff (not in edbox.h) that's been rewritten in asm. */ +void hide_cursor(void); +void show_cursor(void); +void storechr(char c); +void copy_to_old(void); +void restore_old(void); +void backspace(void); +void inschr(char c); + +char old_edbox[EDBOX_SIZE]; +char old_len; +char typeover; + +char edbox_visible = 0; +char edbox_pos; /* range 0 to EDBOX_SIZE - 1 */ +char edbox_len; /* idem */ void (*edbox_callback)(void); -static void hide_cursor(void) { - edit_box[edbox_pos] &= 0x7f; -} - -static void show_cursor(void) { - edit_box[edbox_pos] |= 0x80; -} - -void edbox_clear(void) { - memset(edit_box, 0, EDBOX_SIZE); - edbox_pos = edbox_len = 0; - show_cursor(); // not needed? seems it is.. -} - void edbox_show(void) { u16 addr; @@ -40,7 +35,7 @@ void edbox_show(void) { else addr = (u16)edit_box + edbox_pos - 79; - scr_waitvcount(116); + scr_waitvcount_116(); *dlist_status_lms = addr; *dlist_last_line = 0x02; /* ANTIC GR.0 */ @@ -54,50 +49,37 @@ void edbox_hide(void) { scr_refresh(); } +void move_right(void) { + memmove(edit_box + edbox_pos + 1, edit_box + edbox_pos, EDBOX_MAXPOS - edbox_pos); +} + /* note: c will never be an EOL. idea: when the edbox is completely full (240 chars), go ahead and pretend the user pressed Return (call edbox_callback, etc). not sure if this is more or less annoying than just refusing to accept more input until Return is pressed. */ void edbox_putc(char c) { - extern void __fastcall__ bell(void); - if(!c) return; /* no inserting nulls */ - memmove(edit_box + edbox_pos + 1, edit_box + edbox_pos, EDBOX_SIZE - edbox_pos - 1); - edit_box[edbox_pos] = c; - if(edbox_pos < EDBOX_SIZE - 1) { - edbox_pos++; - edbox_len++; - } else { + /* cannot insert *or* typeover at end of buffer! */ + if(edbox_pos == EDBOX_MAXPOS) { bell(); + return; } -} -static void copy_to_old(void) { - if(!edbox_len) return; - memcpy(old_edbox, edit_box, edbox_len); - memset(old_edbox + edbox_len, 0, (EDBOX_SIZE - 1) - edbox_len); - old_len = edbox_len; -} - -static void restore_old(void) { - edbox_clear(); - hide_cursor(); - memcpy(edit_box, old_edbox, old_len); - edbox_pos = edbox_len = old_len; + if(typeover) { + storechr(c); + edbox_pos++; + } else { + inschr(c); + } } -static void fake_keystroke(char c) { - keyclick(); - OS.ch = 0xff; - edbox_putc(c); -} -static void del_char(void) { +void del_char(void) { if(!edbox_len) return; - memmove(edit_box + edbox_pos, edit_box + edbox_pos + 1, EDBOX_SIZE - edbox_pos - 1); + memmove(edit_box + edbox_pos, edit_box + edbox_pos + 1, EDBOX_MAXPOS - edbox_pos); edbox_len--; } @@ -106,12 +88,6 @@ static void del_to_end(void) { del_char(); } -static void backspace(void) { - if(!edbox_pos) return; - edbox_pos--; - del_char(); -} - static void up(void) { if(!edbox_len) { restore_old(); @@ -145,14 +121,6 @@ static void word_left(char del) { if(del && !edbox_pos) edit_box[edbox_pos] = /* ' ' */ 0; } -static void del_word(void) { - word_left(1); -} - -static void back_word(void) { - word_left(0); -} - static void forward_word(void) { while(edbox_pos < edbox_len && edit_box[edbox_pos] == ' ') edbox_pos++; @@ -161,64 +129,101 @@ static void forward_word(void) { } static void del_to_start(void) { - while(edbox_pos) backspace(); + if(!edbox_pos) return; + memmove(edit_box, edit_box + edbox_pos, EDBOX_MAXPOS - edbox_pos); + edbox_len -= edbox_pos; + bzero(edit_box + edbox_len, EDBOX_MAXPOS - edbox_len); + edbox_pos = 0; } -static void normal_keystroke(void) { - char c; +void left(void) { + if(edbox_pos) edbox_pos--; +} + +void right(void) { + if(edbox_pos < edbox_len) edbox_pos++; +} - c = cgetc(); +void edbox_keystroke(char c) { + if(c == CH_ESC) { + start_latch = 1; + return; + } - if(c != CH_TAB) + if(c != XCH_TAB) comp_complete_done(); + if(c >= XCH_SCR1 && c <= XCH_SCR7) { + start_keystroke(c & 0x7f); + return; + } else if(c == XCH_ACTIVE) { + start_keystroke('a'); + return; + } + + edbox_show(); + hide_cursor(); + switch(c) { case CH_EOL: - // hide_cursor(); // already done by the caller copy_to_old(); edbox_hide(); if(edbox_callback) (*edbox_callback)(); edbox_clear(); break; - case CH_CLR: - edbox_hide(); - /* fall thru */ - case CH_DELLINE: - edbox_clear(); + case XCH_TAB: + comp_complete(); break; - case 0x15: /* ^U */ - del_to_start(); + case XCH_UP: + up(); break; - case 0x0b: /* ^K */ - del_to_end(); + case XCH_DOWN: + down(); + break; + case XCH_LEFT: + left(); break; - case CH_DEL: - backspace(); + case XCH_RIGHT: + right(); break; - case 0x02: /* ^B */ - back_word(); + case XCH_LWORD: + word_left(0); break; - case 0x06: /* ^F */ + case XCH_RWORD: forward_word(); break; - case 0x17: /* ^W */ - del_word(); + case XCH_CLS: + edbox_clear(); + edbox_hide(); + return; + case CH_DELCHR: + case 0x18: /* ^X */ + del_char(); break; - case CH_CURS_LEFT: - if(edbox_pos) edbox_pos--; + case CH_DELLINE: + edbox_clear(); break; - case CH_CURS_RIGHT: - if(edbox_pos < edbox_len) edbox_pos++; + case XCH_INSCHR: + inschr(' '); break; - case CH_CURS_UP: - up(); + case CH_INSLINE: + typeover = !typeover; break; - case CH_CURS_DOWN: - down(); + case 0x15: /* ^U */ + del_to_start(); break; - case CH_DELCHR: - del_char(); + case 0x0b: /* ^K */ + del_to_end(); + break; + case XCH_BS: + if(!edbox_len) + edbox_hide(); + else + backspace(); + break; + case 0x17: /* ^W */ + word_left(1); break; case 0x01: /* ^A */ edbox_pos = 0; @@ -226,101 +231,11 @@ static void normal_keystroke(void) { case 0x05: /* ^E */ edbox_pos = edbox_len; break; - case CH_TAB: - comp_complete(); - break; default: edbox_putc(c); break; } -} - -void edbox_keystroke(void) { - char c; - - while(OS.ch == 0xff) - ; - - /* filter out all ctrl-shift key combos except the ones - we actually support */ - if(OS.ch == 0xce) { - /* ctrl-shift-up, same as ^B = back 1 word */ - OS.ch = 0x95; - } else if(OS.ch == 0xcf) { - /* ctrl-shift-down, same as ^F = forward 1 word */ - OS.ch = 0xb8; - } else if(OS.ch > 0xbf) { - OS.ch = 0xff; - return; - } - - edbox_show(); - - c = 0; - - switch(OS.ch) { - case 0xa0: /* key: ctrl [ */ - c = 0x7b; /* ascii: { */ - break; - case 0xa2: /* key: ctrl ] */ - c = 0x7d; /* ascii: } */ - break; - case 0x1c: /* key: ESC */ - c = 0x60; /* ascii: ` */ - break; - case 0x5c: /* key: shift ESC */ - case 0x9c: /* key: ctrl ESC */ - c = 0x7e; /* ascii: ~ */ - break; - case 0x3c: /* caps */ - case 0x7c: /* shift-caps */ - case 0xbc: /* ctrl-caps */ - OS.shflok ^= 0x40; - keyclick(); - OS.ch = 0xff; - return; - break; - case 0x27: /* atari key */ - case 0x67: /* ...w/shift */ - case 0xa7: /* ...w/ctrl */ - c = 0x02; /* ^B = IRC bold formatting char */ - break; - case 0x9a: /* ctrl-3 (crash if cgetc() reads it!) */ - case 0x9e: /* ctrl-2 */ - case 0x6c: /* shift-tab */ - case 0xac: /* ctrl-tab */ - OS.ch = 0xff; /* ignore it! */ - return; - break; - default: - break; - } - hide_cursor(); - - if(c) { - fake_keystroke(c); - } else { - normal_keystroke(); - } - - if(edbox_visible) edbox_show(); show_cursor(); -} - -void edbox_addchr(char c) { - edit_box[edbox_len++] = c; - edbox_pos = edbox_len; -} - -void edbox_space(void) { - edbox_addchr(' '); -} - -void edbox_set(char *contents) { - edbox_clear(); - while(*contents) { - edit_box[edbox_len++] = *contents++; - } - edbox_pos = edbox_len; + if(edbox_visible) edbox_show(); } diff --git a/src/edbox.h b/src/edbox.h index 14dad03..e4069a0 100644 --- a/src/edbox.h +++ b/src/edbox.h @@ -3,12 +3,16 @@ /**** public API ****/ #define EDBOX_SIZE 240 +#define EDBOX_MAXPOS 239 -extern int edbox_visible; -extern u16 edbox_len; +typedef char edbox_t[EDBOX_SIZE]; +#define edit_box (*(edbox_t *)EDBOX_ADDR) + +extern char edbox_visible; +extern char edbox_len; /* clear the contents of the edit box (whether it's visible or not) */ -void edbox_clear(void); +void __fastcall__ edbox_clear(void); /* make the edit box visible */ void edbox_show(void); @@ -19,18 +23,18 @@ void edbox_hide(void); /* put one character into the edit box. */ void edbox_putc(char c); -/* wait for a keystroke, insert its character into the edit box. if Return +/* pass a keystroke, insert its character into the edit box. if Return is pressed, edbox_callback gets called (if it's set!) */ -void edbox_keystroke(void); +void edbox_keystroke(char c); /* called when the user presses Return */ extern void (*edbox_callback)(void); /* set edit box contents (clears out whatever was there) */ -void edbox_set(char *contents); +void __fastcall__ edbox_set(char *contents); /* append a space to the edit box */ -void edbox_addchr(char c); +void __fastcall__ edbox_addchr(char c); /* append a space to the edit box */ -void edbox_space(void); +void __fastcall__ edbox_space(void); diff --git a/src/edboxutl.s b/src/edboxutl.s new file mode 100644 index 0000000..dc89798 --- /dev/null +++ b/src/edboxutl.s @@ -0,0 +1,203 @@ +; asm rewrites of functions originally written in C, for edbox.c. +; old C code here as comments, for reference. + + .export _edbox_addchr, _edbox_space, _edbox_set, _edbox_clear + .export _hide_cursor, _show_cursor, _storechr + .export _copy_to_old, _restore_old, _backspace, _inschr + .import _edbox_len, _edbox_pos, _old_edbox, _old_len, _typeover + .import _del_char, _bell, _move_right + .importzp ptr1 + + ; EDBOX_SIZE + 1, see edbox.h + EDBOX_SIZE_PLUS_1 = $f1 + EDBOX_SIZE = $f0 + EDBOX_MAXPOS = $ef + + EDBOX_ADDR = $0600 ; MUST agree with EDBOX_ADDR from addrs.h! + +; this compiled to like 31 bytes of code... +; this version is 7 bytes. +;; void storechr(char c) { +;; edit_box[edbox_pos] = c; +;; } +_storechr: + ldx _edbox_pos + sta EDBOX_ADDR,x + rts + +;; void hide_cursor(void) { +;; edit_box[edbox_pos] &= 0x7f; +;; } +_hide_cursor: + ldx _edbox_pos + lda EDBOX_ADDR,x + and #$7f +store_curs: + sta EDBOX_ADDR,x + rts + +;; void show_cursor(void) { +;; edit_box[edbox_pos] |= 0x80; +;; } +_show_cursor: + ldx _edbox_pos + lda EDBOX_ADDR,x + ora #$80 + bne store_curs + +;; static void copy_to_old(void) { +;; if(!edbox_len) return; +;; memcpy(old_edbox, edit_box, edbox_len); +;; bzero(old_edbox + edbox_len, EDBOX_MAXPOS - edbox_len); +;; old_len = edbox_len; +;; } +_copy_to_old: + lda _edbox_len + beq nope + ldx #0 +@l: + lda EDBOX_ADDR,x + sta _old_edbox,x + inx + cpx #EDBOX_SIZE + bne @l + lda _edbox_len + sta _old_len +nope: + rts + +;; static void restore_old(void) { +;; edbox_clear(); +;; hide_cursor(); +;; memcpy(edit_box, old_edbox, old_len); +;; edbox_pos = edbox_len = old_len; +;; } +_restore_old: + lda _old_len + beq nope + ldx #0 +@l: + lda _old_edbox,x + sta EDBOX_ADDR,x + inx + cpx #EDBOX_SIZE + bne @l + lda _old_len + sta _edbox_len + sta _edbox_pos + rts + +;; static void backspace(void) { +;; if(!edbox_pos) return; +;; edbox_pos--; +;; if(typeover) +;; edit_box[edbox_pos] = ' '; +;; else +;; del_char(); +;; } +_backspace: + lda _edbox_pos + beq @nope + dec _edbox_pos + lda _typeover + bne @spc + jmp _del_char +@spc: + lda #$20 + ldx _edbox_pos + sta EDBOX_ADDR,x +@nope: + rts + +;; void inschr(char c) { +;; if(edbox_len == EDBOX_MAXPOS) { +;; /* buffer full, can't insert */ +;; bell(); +;; return; +;; } +;; move_right(); +;; edbox_len++; +;; storechr(c); +;; edbox_pos++; +;; } +_inschr: + pha + lda _edbox_len + cmp #EDBOX_MAXPOS + bne @ok + pla + jmp _bell +@ok: + jsr _move_right + inc _edbox_len + pla + jsr _storechr + inc _edbox_pos + rts + +;; void edbox_clear(void) { +;; bzero(edit_box, EDBOX_SIZE + 1); +;; edbox_pos = edbox_len = 0; +;; show_cursor(); // not needed? seems it is.. +;; } + +_edbox_clear: + ldx #0 + txa +@l: + sta EDBOX_ADDR,x + inx + cpx #EDBOX_SIZE_PLUS_1 + bne @l + sta _edbox_pos + sta _edbox_len + jmp _show_cursor + +;; void edbox_space(void) { +;; edbox_addchr(' '); +;; } +_edbox_space: + lda #$20 + ; fall thru... + +;; void edbox_addchr(char c) { +;; edit_box[edbox_len++] = c; +;; edbox_pos = edbox_len; +;; } + +_edbox_addchr: + ldx _edbox_len + sta EDBOX_ADDR,x + inx + stx _edbox_len + stx _edbox_pos + rts + +;; void edbox_set(char *contents) { +;; edbox_clear(); +;; while(*contents) { +;; edit_box[edbox_len++] = *contents++; +;; } +;; edbox_pos = edbox_len; +;; } + +; notice the asm version does bounds checking, which the C version +; didn't do. +_edbox_set: + sta ptr1 + stx ptr1+1 + jsr _edbox_clear + ldy #0 +@l: + lda (ptr1),y + sta EDBOX_ADDR,y + beq @done ; if we hit a 0 byte in the input, we're done + inx + iny + cpy #EDBOX_SIZE_PLUS_1 ; if we hit the edbox size limit, we're done + bne @l + dey +@done: + sty _edbox_len + sty _edbox_pos + rts diff --git a/src/exehdr.s b/src/exehdr.s new file mode 100644 index 0000000..7abb7c1 --- /dev/null +++ b/src/exehdr.s @@ -0,0 +1,11 @@ +; This file defines the EXE header and main chunk load header for Atari executables + + .export __EXEHDR__: absolute = 1 + .import __MAIN_START__, __BSS_LOAD__ + +.segment "EXEHDR" + .word $FFFF + +.segment "MAINHDR" + .word __MAIN_START__ + .word __BSS_LOAD__ - 1 diff --git a/src/indic8.c b/src/indic8.c index 33d7d31..260994d 100644 --- a/src/indic8.c +++ b/src/indic8.c @@ -2,25 +2,11 @@ #include <atari.h> #include "addrs.h" - -extern char hz; - -#define NET_TX_CHR 0x5c /* COLOR1 up-arrow */ -#define NET_RX_CHR 0x5d /* COLOR1 down-arrow */ -#define NET_ERR_CHR 0xe1 /* COLOR3 exclamation mark */ -#define NET_IDLE_CHR 0 /* null (renders as a space) */ - -#define J_CHR 0x4a /* COLOR1 J */ -#define P_CHR 0x50 /* COLOR1 P */ -#define Q_CHR 0x51 /* COLOR1 Q */ -#define M_CHR 0x4d /* COLOR1 M */ -#define CHAN_CHR 0x63 /* COLOR1 # */ - -char *ind_net_status; /* initialized in screen.c, scr_init() */ +#include "indic8.h" +#include "timers.h" static void ind_start_timer(void) { - /* 3.7 sec is just what The_Doctor__ ordered! */ - OS.cdtmv5 = (hz / 10) * 37; + OS.cdtmv5 = timers.net_ind_time; } void ind_net_rx(void) { @@ -39,32 +25,12 @@ void ind_net_idle(void) { *ind_net_status = NET_IDLE_CHR; } -static void ind_act(char c) { - ind_net_status[1] = c; +void ind_act(char c) { + *ind_act_status = c; ind_start_timer(); } -void ind_act_pm(void) { - ind_act(M_CHR); -} - -void ind_act_chantext(void) { - ind_act(CHAN_CHR); -} - -void ind_act_join(void) { - ind_act(J_CHR); -} - -void ind_act_part(void) { - ind_act(P_CHR); -} - -void ind_act_quit(void) { - ind_act(Q_CHR); -} - void ind_check_timer(void) { if(!OS.cdtmv5) - ind_net_status[1] = 0; + *ind_act_status = 0; } diff --git a/src/indic8.h b/src/indic8.h index 4a28d41..5524bb7 100644 --- a/src/indic8.h +++ b/src/indic8.h @@ -1,18 +1,27 @@ -extern char *ind_net_status; +#define NET_TX_CHR 0x5c /* COLOR1 up-arrow */ +#define NET_RX_CHR 0x5d /* COLOR1 down-arrow */ +#define NET_ERR_CHR 0xe1 /* COLOR3 exclamation mark */ +#define NET_IDLE_CHR 0 /* null (renders as a space) */ + +#define J_CHR 0x4a /* COLOR1 J */ +#define P_CHR 0x50 /* COLOR1 P */ +#define Q_CHR 0x51 /* COLOR1 Q */ +#define M_CHR 0x4d /* COLOR1 M */ +#define N_CHR 0x4e /* COLOR1 N */ +#define CHAN_CHR 0x63 /* COLOR1 # */ + +void ind_act(char c); void ind_net_rx(void); void ind_net_tx(void); void ind_net_down(void); void ind_net_idle(void); -void ind_act_pm(void); -void ind_act_chantext(void); -void ind_act_join(void); -void ind_act_part(void); -void ind_act_quit(void); +#define ind_act_pm() ind_act(M_CHR) +#define ind_act_notice() ind_act(N_CHR) +#define ind_act_chantext() ind_act(CHAN_CHR) +#define ind_act_join() ind_act(J_CHR) +#define ind_act_part() ind_act(P_CHR) +#define ind_act_quit() ind_act(Q_CHR) void ind_check_timer(void); - -/* -void ind_clear(void); -*/ @@ -1,13 +1,11 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <ctype.h> #include "irc.h" #include "screen.h" #include "edbox.h" #include "numerics.h" -#include "keyclick.h" #include <atari.h> #include <conio.h> @@ -15,6 +13,10 @@ #include "config.h" #include "indic8.h" #include "complete.h" +#include "keytab.h" +#include "kgetc.h" +#include "streq.h" +#include "timers.h" #ifndef VERSION #define VERSION "?????" @@ -24,14 +26,17 @@ char *msg_src, *msg_cmd, *msg_dest, *msg_text; char *msg_args[MAX_MSG_ARGS]; -int msg_argcount; +char msg_argcount; char irc_away = 0; char bell_type; char hide_motd; +char start_latch = 0; +char new_scr_status; +char need_rejoin; +char self_src; static char msgbuf[MAX_MSG] = { 0 }; -static char *msg; /* with source removed */ static int msgbuf_len = 0; static char regged = 0, hilite = 0; @@ -44,6 +49,9 @@ char last_chan[33]; /* without a screen */ static int minutes, last_read_min; +/* see pollkbd.s */ +extern void poll_keyboard(void); + /* static void join_channel(void) { txbuf_set_str2("JOIN ", channel); @@ -52,7 +60,7 @@ static void join_channel(void) { */ static void send_nick(void) { - txbuf_set_str2("NICK ", conf->nick); + txbuf_set_str2("NICK ", config.nick); txbuf_send(); } @@ -64,15 +72,8 @@ static void print_reason(void) { scr_eol_active(); } -static void do_pong(void) { - if(conf->show_ping) - scr_print_server("PING/PONG\n"); - txbuf_set_str2("PONG ", msg_args[0]); - txbuf_send(); -} - static void bold(void) { - scr_print_active("\x02"); + scr_putc_active('\x02'); } static void hilite_bold(void) { @@ -82,29 +83,29 @@ static void hilite_bold(void) { static void do_chan_nick(void) { if(hilite) { bell(); - scr_hilite_active(); + new_scr_status = SCR_HILITE; } hilite_bold(); - scr_print_active("<"); + scr_putc_active('<'); hilite_bold(); scr_print_active(msg_src); if(scr_active == SCR_SERVER) { /* if we don't have a window for it */ - scr_print_active("/"); + scr_putc_active('/'); scr_print_active(msg_dest); } hilite_bold(); - scr_print_active(">"); + scr_putc_active('>'); hilite_bold(); - scr_print_active(" "); + scr_putc_active(' '); } static void do_priv_nick(void) { if(msg_src) { - scr_print_active("*"); + scr_putc_active('*'); scr_print_active(msg_src); scr_print_active("* "); - scr_hilite_active(); + new_scr_status = SCR_HILITE; bell(); } } @@ -113,43 +114,37 @@ static void do_priv_nick(void) { nobody's ping time will ever be more than 9 minutes. anyone that lagged will have been disconnected from the server already. if this assumption turns out to be false, the ping time displayed - will be wrong (module ~9 mins). I don't think it's ever going to be + will be wrong (modulo ~9 mins). I don't think it's ever going to be a real problem. Why do it this way? Because using longs instead of ints bloats the - code by 778 bytes! */ + code by 778 bytes! + note to self: using a div_t and div() seemed like it might make + the compiled code smaller, but it grew by ~50 bytes. avoid. + */ static void print_ping_time(char *p) { static unsigned int now, pingtime; static unsigned int sec, frac; now = read_rtclok(); - pingtime = (unsigned int)atoi(p); + pingtime = (unsigned int)a2uint(p); /* correct for rtclock rollover (every ~9 mins) */ if(now < pingtime) now |= 0x8000; pingtime = now - pingtime; - sec = pingtime / hz; - frac = pingtime % hz; - frac *= 1000; - frac /= (hz * 10); + sec = pingtime / timers.hz; + frac = pingtime % timers.hz; + frac *= 100; + frac /= timers.hz; scr_print_active("*** "); scr_print_active(msg_src); scr_print_active(" lag: "); - 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(" "); - itoa(pingtime, numbuf, 10); - scr_print_active(numbuf); - */ + scr_act_printnum(sec); + scr_putc_active('.'); + scr_act_printnum(frac); + scr_putc_active('s'); } static void do_server_pong(void) { @@ -159,8 +154,12 @@ static void do_server_pong(void) { scr_eol_active(); } +static void print_ctcp(void) { + scr_print_active("*** CTCP "); +} + /* FIXME: this isn't very fast */ -static void do_ctcp(int is_notice) { +static void do_ctcp(char is_notice) { static char *p, *ctcp_type, *resp; resp = 0; @@ -180,9 +179,9 @@ static void do_ctcp(int is_notice) { if(p && streq_i(ctcp_type, "PING")) { print_ping_time(p); } else { - scr_print_active("*** CTCP "); + print_ctcp(); scr_print_active(ctcp_type); - scr_print_active(" response from "); + scr_print_active(" resp from "); scr_print_active(msg_src); if(p) { scr_print_active(": "); @@ -195,15 +194,15 @@ static void do_ctcp(int is_notice) { if(streq_i(ctcp_type, "ACTION")) { scr_print_active("* "); scr_print_active(msg_src); - scr_print_active(" "); + scr_putc_active(' '); scr_print_active(p); scr_eol_active(); return; } - scr_print_active("*** CTCP "); + print_ctcp(); scr_print_active(ctcp_type); - scr_print_active(" request from "); + scr_print_active(" req from "); scr_print_active(msg_src); scr_eol_active(); @@ -219,18 +218,23 @@ static void do_ctcp(int is_notice) { } txbuf_set_str3("NOTICE ", msg_src, " :\x01"); - txbuf_append_str3(ctcp_type, " ", resp); - txbuf_append_str("\x01"); + txbuf_append_str(ctcp_type); + txbuf_append_chr(' '); + txbuf_append_str(resp); + txbuf_append_chr('\x01'); txbuf_send(); } } static void do_privmsg(void) { /* TODO: this shouldn't be case-sensitive */ - if(strstr(msg_text, conf->nick)) + /* + if(strstr(msg_text, config.nick)) hilite = 1; else hilite = 0; + */ + hilite = find_nick(); if(*msg_text == '\x01') { do_ctcp(0); @@ -242,7 +246,10 @@ static void do_privmsg(void) { ind_act_chantext(); } else { do_priv_nick(); - ind_act_pm(); + if(*msg_cmd == 'N') + ind_act_notice(); + else + ind_act_pm(); } scr_print_active(msg_text); @@ -260,23 +267,22 @@ static void do_notice(void) { static void do_join(void) { ind_act_join(); - if(streq_i(conf->nick, msg_src)) { - scr_print_active("You have "); + if(self_src) { + scr_print_active("You"); } else { scr_print_active("\x02=\x02"); scr_print_active(msg_src); - scr_print_active(" has "); } - scr_print_active("joined "); + scr_print_active(" joined "); scr_print_active(msg_dest); scr_eol_active(); } static void do_nick(void) { - /* Do not overwrite conf->nick with bogus data! sometimes when + /* Do not overwrite config.nick with bogus data! sometimes when we get disconnected, upon reconnect we get a partial message. if it's a NICK, missing its destination argument, we end up - blowing away conf->nick, and subsequent reconnect attempts + blowing away config.nick, and subsequent reconnect attempts fail with "USER: not enough parameters". This is purely a band-aid; a proper solution involves rewriting parse_msg() so it knows how many args each msg type needs, and refuses to @@ -285,9 +291,9 @@ static void do_nick(void) { return; // ind_act_none(); - if(streq_i(conf->nick, msg_src)) { + if(self_src) { scr_print_active("You are "); - strncpy(conf->nick, msg_dest, 32); + strncpy(config.nick, msg_dest, 32); } else { scr_print_active(msg_src); scr_print_active(" is "); @@ -309,95 +315,66 @@ static void do_part(void) { scr_print_active(msg_src); scr_print_active(" has left "); scr_print_active(msg_dest); - if(msg_text) { - scr_print_active(": "); - scr_print_active(msg_text); - } - scr_eol_active(); + print_reason(); } static void do_topic(void) { scr_print_active(msg_src); - scr_print_active(" has set the topic of "); + scr_print_active(" sets the "); scr_print_active(msg_dest); - scr_print_active(" to: "); + scr_print_active(" topic to: "); scr_print_active(msg_text); scr_eol_active(); - /* TODO: set topic in the screen! */ } static void do_kick(void) { scr_print_active(msg_src); - scr_print_active(" has kicked "); + scr_print_active(" kicks "); scr_print_active(msg_args[1]); scr_print_active(" from "); scr_print_active(msg_dest); print_reason(); } -static void do_mode(void) { - int i; - if(msg_src) - scr_print_active(msg_src); - else - scr_print_active("Server"); - scr_print_active(" sets mode: "); - for(i = 0; i < msg_argcount; i++) { - scr_print_active(msg_args[i]); - scr_print_active(" "); - } - if(msg_text) scr_print_active(msg_text); - scr_eol_active(); -} - /* numerics call this with arg==1, since arg 0 is always our nick. other commands call with arg==0 to print everything. */ -static void do_catchall(int arg) { +static void do_catchall(char arg) { if(msg_src) { scr_print_active(msg_src); - scr_print_active(" "); + scr_putc_active(' '); } scr_print_active(msg_cmd); for(; arg < msg_argcount; arg++) { - scr_print_active(" "); + scr_putc_active(' '); scr_print_active(msg_args[arg]); } if(msg_text) { - scr_print_active(" "); + scr_putc_active(' '); scr_print_active(msg_text); } scr_eol_active(); } -/* permutes last character (doesn't add one), so for "Bob" you get: - Bo_, Bo1 through Bo9, BoA through BoZ - Gives a total of 36 replacement nicks to try. - Eventually we run out and start repeating, but by then the IRC - server will have disconnected us. - */ -static void permute_nick(void) { - char *last; - - last = conf->nick + strlen(conf->nick) - 1; - - if((*last >= '1' && *last < '9') || (*last >= 'A' && *last < 'Z')) { - (*last)++; +static void do_mode(void) { + if(msg_src) { + scr_print_active(msg_src); + msg_src = 0; /* don't let do_catchall() print it again */ } else { - switch(*last) { - case '_': *last = '1'; break; - case '9': *last = 'A'; break; - default: *last = '_'; break; - } + scr_print_active("Server"); } + scr_print_active(" sets mode:"); + do_catchall(0); } +extern void permute_nick(void); + /* see: https://defs.ircdocs.horse/ */ static void do_forward_chan(void) { - char s; + static char s; if(msg_argcount > 2 && msg_args[1][0] == '#' && msg_args[2][0] == '#') { s = scr_getbyname(msg_args[1]); @@ -408,13 +385,15 @@ static void do_forward_chan(void) { } static void do_numeric(void) { - unsigned int num = atoi(msg_cmd); + static unsigned int num; + + num = a2uint(msg_cmd); switch(num) { /* use the server's idea of what our nick is, in case it got truncated. */ case RPL_WELCOME: - strcpy(conf->nick, msg_args[0]); + strcpy(config.nick, msg_args[0]); regged = 1; do_catchall(1); break; @@ -438,11 +417,13 @@ static void do_numeric(void) { */ case ERR_NICKNAMEINUSE: - do_catchall(0); - if(!regged) { + if(regged) { + scr_activate(scr_current); + } else { permute_nick(); send_nick(); } + do_catchall(1); break; /* don't print these, just noise */ @@ -450,18 +431,20 @@ static void do_numeric(void) { break; case RPL_MOTD: - /* FIXME: this prevents the user using /MOTD on purpose, too */ if(!hide_motd) - do_catchall(0); + do_catchall(1); break; /* don't print, but do trigger rejoin */ case RPL_ENDOFMOTD: case ERR_NOMOTD: hide_motd = 0; - cmd_rejoin_chans(); - if(scr_names[2][0] == '#') - scr_display(2); + if(need_rejoin) { + cmd_rejoin_chans(); + if(scr_names[2][0] == '#') + scr_display(2); + need_rejoin = 0; + } break; case RPL_NAMREPLY: @@ -493,17 +476,32 @@ static void do_numeric(void) { do_forward_chan(); break; + case RPL_AWAY: + scr_print_active(msg_args[1]); + scr_print_active(" is away: "); + scr_print_active(msg_text); + scr_eol_active(); + break; + default: + if(num >= 400 && num < 600) + scr_activate(scr_current); do_catchall(1); break; } } +#if 0 static void invalid_msg(char type) { scr_print(SCR_SERVER, "??? unknown, type "); scr_putc(SCR_SERVER, type); scr_putc(SCR_SERVER, '\n'); } +#endif + +static char cmd_is(char *cmd) { + return streq_i(msg_cmd, cmd); +} void select_screen(void) { char s; @@ -519,7 +517,7 @@ void select_screen(void) { } else { s = scr_getbyname(msg_src); if(!s) { - if(streq_i(msg_cmd, "PRIVMSG")) { /* or maybe NOTICE? */ + if(cmd_is("PRIVMSG")) { /* or maybe NOTICE? */ strncpy(last_pm_nick, msg_src, 32); comp_add_pm_nick(last_pm_nick); s = SCR_PRIV; @@ -532,34 +530,50 @@ void select_screen(void) { } static void dispatch_msg(void) { + self_src = streq_i(config.nick, msg_src); + /* at this point, we know the message source and destination, so: */ + /* FIXME: maybe we know... */ select_screen(); + new_scr_status = SCR_OTHER; - if(streq_i(msg_cmd, "PRIVMSG")) { + if(cmd_is("PRIVMSG")) { + new_scr_status = SCR_ACTIVE; do_privmsg(); - } else if(streq_i(msg_cmd, "NOTICE")) { + } else if(cmd_is("NOTICE")) { do_notice(); - } else if(streq_i(msg_cmd, "JOIN")) { + } else if(cmd_is("JOIN")) { do_join(); - } else if(streq_i(msg_cmd, "NICK")) { + } else if(cmd_is("NICK")) { do_nick(); - } else if(streq_i(msg_cmd, "QUIT")) { + } else if(cmd_is("QUIT")) { do_quit(); - } else if(streq_i(msg_cmd, "PART")) { + } else if(cmd_is("PART")) { do_part(); - } else if(streq_i(msg_cmd, "TOPIC")) { + } else if(cmd_is("TOPIC")) { do_topic(); - } else if(streq_i(msg_cmd, "KICK")) { + } else if(cmd_is("KICK")) { do_kick(); - } else if(streq_i(msg_cmd, "MODE")) { + } else if(cmd_is("MODE")) { do_mode(); - } else if(streq_i(msg_cmd, "PONG")) { - if(*msg_text != 'A') do_server_pong(); - } else if(isdigit(msg_cmd[0])) { + } else if(cmd_is("PONG")) { + if(*msg_text == 'A') { + return; /* do not set screen status */ + } else { + do_server_pong(); + } + } else if(isnum(msg_cmd[0])) { do_numeric(); } else { do_catchall(0); } + + if(scr_active != scr_current) { + if(scr_status[scr_active] < new_scr_status) { + scr_status[scr_active] = new_scr_status; + scr_show_status(scr_current); + } + } } /* msgbuf contains a complete message from the server, whose @@ -568,73 +582,52 @@ static void dispatch_msg(void) { static void parse_msg(void) { char *p; - msg_cmd = msg_text = msg_src = msg_dest = 0; - msg = msgbuf; - /* ignore empty message */ - if(!*msg) return; + if(!*msgbuf) return; - /* - scr_print_active("RAW: "); - scr_print_active(msg); - scr_eol_active(); - */ - - /* if there's a final multiword arg... */ - /* FIXME: channel names can have colons, which breaks this... */ - p = strstr(msg + 1, " :"); /* +1 to skip leading colon in msg source */ - if(p) { - msg_text = p + 2; - *p = 0; - } + msg_cmd = msg_text = msg_src = msg_dest = 0; + memset(msg_args, 0, sizeof(msg_args)); /* first token is either the source (with a :) or a command (without) */ - p = strtok(msg, " "); - if(!p) { - invalid_msg('1'); - return; - } - - if(*p == ':') { - msg_src = p + 1; /* generally :irc.example.com or :nick!user@host */ - msg_cmd = strtok(0, " "); + if(*msgbuf == ':') { + msg_src = msgbuf + 1; /* generally :irc.example.com or :nick!user@host */ + msg_cmd = nextarg(msgbuf); } else { msg_src = 0; /* no source supplied */ - msg_cmd = p; + msg_cmd = msgbuf; } + p = nextarg(msg_cmd); + #if 0 if(!msg_cmd) { invalid_msg('2'); return; } + #endif /* special case for ping, treat as 1 arg, even if it has space and no : */ - if(streq_i(msg_cmd, "PING")) { - msg_argcount = 1; - msg_args[0] = msg_cmd + 6; - do_pong(); + if(cmd_is("PING")) { + txbuf_set_str2("PONG ", msg_cmd + 6); + txbuf_send(); return; - } else { - for(msg_argcount = 0; msg_argcount < MAX_MSG_ARGS; msg_argcount++) { - p = strtok(0, " "); - if(p) { - msg_args[msg_argcount] = p; - /* if any arg is a channel name, use it for the dest */ - if(*p == '#') - msg_dest = p; - } else { - break; - } - } } - /* - if(msg_dest) { - scr_print_current("got here, msg_dest is: "); - scr_print_current(msg_dest); - scr_eol_current(); + for(msg_argcount = 0; msg_argcount < MAX_MSG_ARGS; msg_argcount++) { + if(!p) break; + + if(*p == ':') { + msg_text = p + 1; + break; + } + + msg_args[msg_argcount] = p; + + /* if any arg is a channel name, use it for the dest */ + if(*p == '#') + msg_dest = p; + + p = nextarg(p); } - */ if(!msg_dest) { if(msg_argcount) @@ -644,9 +637,9 @@ static void parse_msg(void) { } if(msg_src) { - if((p = strstr(msg_src, "!"))) { + if((p = strchr(msg_src, '!'))) { *p = '\0'; - } else if(strstr(msg_src, ".")) { + } else if(strchr(msg_src, '.')) { msg_src = 0; } } @@ -662,31 +655,30 @@ static void irc_split_Lines(void) { char *p = rx_buf; for(i = 0; i < rxbuflen; i++) { - msgbuf[msgbuf_len] = *p; - if(*p == CH_EOL) { - // msgbuf[msgbuf_len + 1] = '\0'; - /* do not include the EOL */ - msgbuf[msgbuf_len] = '\0'; - parse_msg(); - msgbuf_len = 0; - } else { - msgbuf_len++; + /* skip ASCII \r character */ + if(*p != 0x0d) { + if(*p == 0x0a) { + /* got ASCII \n */ + msgbuf[msgbuf_len] = '\0'; + parse_msg(); + msgbuf_len = 0; + } else { + msgbuf[msgbuf_len++] = *p; + } } p++; } } -/* TODO: there needs to be a scr_printnum() */ void print_errnum(void) { extern unsigned char err; scr_print_current("Error #"); - itoa(err, numbuf, 10); - scr_print_current(numbuf); + scr_cur_printnum(err); } static void start_minute_timer() { OS.cdtmf4 = 0xff; - OS.cdtmv4 = 60 * hz; + OS.cdtmv4 = timers.one_sec; } static char service_minute_timer() { @@ -716,17 +708,16 @@ static char service_minute_timer() { } else { /* idle >=121 sec, already sent a ping, got nothing back. we timed out. */ - bell(); /* for testing only */ - scr_print_current("Server timed out"); + scr_print_current("Server timed out\n"); return 0; } } -int irc_read(void) { +char irc_read(void) { if(!trip) return 1; last_read_min = minutes; - err = nstatus(conf->url); + err = nstatus(); if(err != 1) { scr_display(SCR_SERVER); @@ -749,7 +740,7 @@ int irc_read(void) { ind_net_rx(); if(rxbuflen > 0) { - err = nread(conf->url, rx_buf, rxbuflen); + err = nread_rxbuf(); if(err != 1) { ind_net_down(); print_errnum(); @@ -774,20 +765,25 @@ void irc_register(void) { /* 2nd arg: local (UNIX) username, just use the nick */ /* 3rd arg: "real" name */ - txbuf_set_str3("USER ", conf->nick, " 0 * :"); - txbuf_append_str(conf->real_name); + txbuf_set_str3("USER ", config.nick, " 0 * :"); + txbuf_append_str(config.real_name); txbuf_send(); send_nick(); } static void scrollback() { - OS.ch = 0xff; + char c; scr_scrollback(); - while(OS.ch == 0xff) + while(!keypress()) irc_read(); - keyclick(); - OS.ch = 0xff; + c = kgetc(); + if(c == '-') { + scr_scrollback_bonus(); + while(!keypress()) + irc_read(); + kgetc(); + } scr_end_scrollback(); } @@ -804,22 +800,23 @@ static void hunt_screen(signed char dir) { scr_display(s); } -static char *get_cur_chan(void) { - if(scr_current == SCR_SERVER && last_chan[0]) - return last_chan; - else if ((scr_current > 1) && (scr_names[scr_current][0] == '#')) +static char *get_cur(void) { + if((scr_current > 1) && (scr_names[scr_current][0] == '#')) return scr_names[scr_current]; else return 0; } +static char *get_cur_chan(void) { + if(scr_current == SCR_SERVER && last_chan[0]) + return last_chan; + else return get_cur(); +} + static char *get_cur_nick(void) { if(scr_current == SCR_PRIV && last_pm_nick[0]) return last_pm_nick; - else if (scr_current > 1 && scr_names[scr_current][0] != '#') - return scr_names[scr_current]; - else - return 0; + else return get_cur(); } static void send2_with_space(char *s1, char *s2) { @@ -857,7 +854,7 @@ static char cur_is_query(void) { /* count backwards here, because the [server] screen is 0, it's the least interesting one. */ -char find_scr_with_status(int status) { +char find_scr_with_status(char status) { signed char i; for(i = MAX_SCREENS - 1; i != -1; i--) { @@ -874,6 +871,8 @@ void switch_to_active() { i = find_scr_with_status(SCR_HILITE); if(i == 0xff) i = find_scr_with_status(SCR_ACTIVE); + if(i == 0xff) + i = find_scr_with_status(SCR_OTHER); if(i != 0xff) { scr_prev = scr_current; scr_display(i); @@ -918,13 +917,14 @@ static void toggle_edbox_only(void) { OS.sdlst = edbox_only_dlist; } -static void start_keystroke(void) { - char i, s; +void start_keystroke(char c) { + char s; - i = cgetc(); + start_latch = 0; + if(c == CH_ESC) return; - if(i >= '1' && i <= '7') { - s = i - '1'; + if(c >= '1' && c <= '7') { + s = c - '1'; if(s != scr_current) { if(scr_status[s] != SCR_UNUSED) { scr_prev = scr_current; @@ -934,32 +934,32 @@ static void start_keystroke(void) { return; } - switch(tolower(i)) { - case CH_CURS_UP: + switch(lcase(c)) { + case XCH_UP: case '-': scrollback(); return; case 0x18: /* ^X */ send_cur_chan_cmd("PART"); /* fall thru */ - case CH_ESC: + case 'x': scr_prev = SCR_PRIV; scr_destroy(scr_current); return; - case CH_CURS_LEFT: + case XCH_LEFT: case '+': scr_prev = scr_current; hunt_screen(-1); return; - case CH_CURS_RIGHT: + case XCH_RIGHT: case '*': scr_prev = scr_current; hunt_screen(1); return; - case CH_TAB: - i = scr_current; + case XCH_TAB: + c = scr_current; scr_display(scr_prev); - scr_prev = i; + scr_prev = c; return; case 'q': if(scr_current == SCR_PRIV && *last_pm_nick) { @@ -1007,29 +1007,17 @@ static void start_keystroke(void) { } } -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 { - edbox_keystroke(); - } -} - /* only exits on error (e.g. connection closed, which might be via /QUIT). */ void irc_loop(void) { /* this stuff happens on every connect. */ - hide_motd = conf->hide_motd; + hide_motd = config.hide_motd; msgbuf[0] = msgbuf_len = regged = irc_away = minutes = 0; + need_rejoin = 1; start_minute_timer(); while(1) { ind_check_timer(); - if(conf->atract_away) { + if(config.atract_away) { if(!irc_away && (OS.atract & 0x80)) { irc_away = 1; txbuf_send_str("AWAY :ATRACT mode"); @@ -1038,6 +1026,9 @@ void irc_loop(void) { if(!irc_read() || !service_minute_timer()) { return; } - keystroke(); + OS.cdtmv3 = 0; + do { + poll_keyboard(); + } while(OS.cdtmv3); } } @@ -1,32 +1,29 @@ -#define FNET_TRANSLATION 3 #define MAX_IRC_MSG_LEN 512 - -#define streq(x,y) !strcmp(x,y) -#define streq_i(x,y) !strcasecmp(x,y) +#include "rxtxbuf.h" /**** main.c */ -extern char *rx_buf; -extern unsigned short rxbuflen; + +extern unsigned int rxbuflen; +extern unsigned int txbuflen; + extern unsigned char err; extern unsigned char trip; -extern char hz; extern char reconnect_timeout; -extern unsigned int txbuflen; -extern char *tx_buf; - /* clears the transmit buffer. */ void txbuf_init(void); +void txbuf_append_chr(char c); + /* appends a string to the transmit buffer, updates txbuflen. */ void txbuf_append_str(const char *str); void txbuf_append_str2(const char *s1, const char *s2); -void txbuf_append_str3(const char *s1, const char *s2, const char *s3); +// void txbuf_append_str3(const char *s1, const char *s2, const char *s3); /* clears the transmit buffer, then appends a string to it. */ void txbuf_set_str(const char *str); -/* as txbuf_set_str2(), but multiple strings. */ +/* as txbuf_set_str(), but multiple strings. */ void txbuf_set_str2(const char *s1, const char *s2); void txbuf_set_str3(const char *s1, const char *s2, const char *s3); @@ -44,13 +41,13 @@ void fn_disconnect(void); /**** 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]; -extern int msg_argcount; +extern char msg_argcount; extern char irc_away; extern char bell_type; +extern char start_latch; extern char last_pm_nick[33]; extern char last_chan[33]; @@ -65,6 +62,8 @@ void print_errnum(void); void __fastcall__ bell(void); /* see src/bell.s */ +void start_keystroke(char c); + /**** cmd.c */ void cmd_command(char *cmd); void cmd_execute(void); @@ -74,3 +73,12 @@ void cmd_send_pm(char *args); void cmd_ctcp_ping(char *nick); void cmd_server_ping(void); unsigned int read_rtclok(void); /* irc.c needs this one so it's not static */ + +/* see isnum.s */ +extern char __fastcall__ isnum(char c); + +/* see a2uint.s */ +extern unsigned int __fastcall__ a2uint(char *str); + +/* nextarg.s */ +extern char *nextarg(char *arg); diff --git a/src/isnum.s b/src/isnum.s new file mode 100644 index 0000000..7aa3251 --- /dev/null +++ b/src/isnum.s @@ -0,0 +1,18 @@ + + .export _isnum + + ; isdigit() replacement that avoids cc65's ctype bloat. + ; returns 0 in A/X for non-digit, non-zero for digit. + ; *also* when calling from asm, the Z flag is set for + ; non-digit, clear for digit. +_isnum: + cmp #'0' + bcc ret0 + cmp #'9'+1 + bcs ret0 + lda #1 + .byte $2c +ret0: + lda #0 + tax + rts diff --git a/src/keyclick.c b/src/keyclick.c deleted file mode 100644 index d1e7505..0000000 --- a/src/keyclick.c +++ /dev/null @@ -1,8 +0,0 @@ -#include <atari.h> -#include <conio.h> - -/* sound the keyclick. obeys the NOCLIK flag on XL/XE. */ -void keyclick(void) { - OS.ch = 0; - cgetc(); -} diff --git a/src/keyclick.h b/src/keyclick.h deleted file mode 100644 index b73a212..0000000 --- a/src/keyclick.h +++ /dev/null @@ -1 +0,0 @@ -extern void keyclick(void); diff --git a/src/keytab.c b/src/keytab.c new file mode 100644 index 0000000..5fd8963 --- /dev/null +++ b/src/keytab.c @@ -0,0 +1,274 @@ +#include "keytab.h" + +/* +This table is a modified version of the one from the XL OS ROM. +Atari uses $80 to mean "unused". Here, I use 0 to mean unused. +Also, Atari's table is only 192 bytes. This is 256 bytes because +it includes ctrl-shift combos. +*/ + +char keytab[256] = { +// no modifier + 0x6c, // 0 = l + 0x6a, // 1 = j + 0x3b, // 2 = ; + XCH_SCR1, // 3 = 1200XL F1 + XCH_SCR2, // 4 = 1200XL F2 + 0x6b, // 5 = k + 0x2b, // 6 = + + 0x2a, // 7 = * + 0x6f, // 8 = o + 0x00, // 9 = (unused) + 0x70, // 10 = p + 0x75, // 11 = u + 0x9b, // 12 = EOL + 0x69, // 13 = i + 0x2d, // 14 = - + 0x3d, // 15 = = + 0x76, // 16 = v + 0x00, // 17 = (unused) + 0x63, // 18 = c + XCH_SCR3, // 19 = 1200XL F3 + XCH_SCR4, // 20 = 1200XL F4 + 0x62, // 21 = b + 0x78, // 22 = x + 0x7a, // 23 = z + 0x34, // 24 = 4 + 0x00, // 25 = (unused) + 0x33, // 26 = 3 + 0x36, // 27 = 6 + 0x1b, // 28 = esc + 0x35, // 29 = 5 + 0x32, // 30 = 2 + 0x31, // 31 = 1 + 0x2c, // 32 = , + 0x20, // 33 = space + 0x2e, // 34 = . + 0x6e, // 35 = n + 0x00, // 36 = (unused) + 0x6d, // 37 = m + 0x2f, // 38 = / + 0x02, // 39 = inverse (toggle bold) + 0x72, // 40 = r + 0x00, // 41 = (unused) + 0x65, // 42 = e + 0x79, // 43 = y + XCH_TAB, // 44 = tab + 0x74, // 45 = t + 0x77, // 46 = w + 0x71, // 47 = q + 0x39, // 48 = 9 + 0x00, // 49 = (unused) + 0x30, // 50 = 0 + 0x37, // 51 = 7 + XCH_BS, // 52 = ~ + 0x38, // 53 = 8 + 0x3c, // 54 = < + 0x3e, // 55 = > + 0x66, // 56 = f + 0x68, // 57 = h + 0x64, // 58 = d + 0x00, // 59 = (unused) + XCH_CAPS, // 60 = caps toggle + 0x67, // 61 = g + 0x73, // 62 = s + 0x61, // 63 = a + +// shift + 0x4c, // 64 = L + 0x4a, // 65 = J + 0x3a, // 66 = : + XCH_SCR5, // 67 = 1200XL Shift-F1 + XCH_SCR6, // 68 = 1200XL Shift-F2 + 0x4b, // 69 = K + 0x5c, // 70 = "\" + 0x5e, // 71 = ^ + 0x4f, // 72 = O + 0x00, // 73 = (unused) + 0x50, // 74 = P + 0x55, // 75 = U + 0x9b, // 76 = EOL + 0x49, // 77 = I + 0x5f, // 78 = _ + 0x7c, // 79 = | + 0x56, // 80 = V + 0x00, // 81 = (unused) + 0x43, // 82 = C + XCH_SCR7, // 83 = 1200XL Shift-F3 + XCH_ACTIVE, // 84 = 1200XL Shift-F4 + 0x42, // 85 = B + 0x58, // 86 = X + 0x5a, // 87 = Z + 0x24, // 88 = $ + 0x00, // 89 = (unused) + 0x23, // 90 = # + 0x26, // 91 = & + 0x60, // 92 = ` (atari shift-esc) + 0x25, // 93 = % + 0x22, // 94 = " + 0x21, // 95 = ! + 0x5b, // 96 = [ + 0x20, // 97 = space + 0x5d, // 98 = ] + 0x4e, // 99 = N + 0x00, // 100 = (unused) + 0x4d, // 101 = M + 0x3f, // 102 = ? + 0x00, // 103 = inverse + 0x52, // 104 = R + 0x00, // 105 = (unused) + 0x45, // 106 = E + 0x59, // 107 = Y + 0x00, // 108 = shift-tab + 0x54, // 109 = T + 0x57, // 110 = W + 0x51, // 111 = Q + 0x28, // 112 = ( + 0x00, // 113 = (unused) + 0x29, // 114 = ) + 0x27, // 115 = ' + 0x9c, // 116 = del line + 0x40, // 117 = @ + XCH_CLS, // 118 = } + 0x9d, // 119 = ins line + 0x46, // 120 = F + 0x48, // 121 = H + 0x44, // 122 = D + 0x00, // 123 = (unused) + XCH_CAPS, // 124 = caps lock + 0x47, // 125 = G + 0x53, // 126 = S + 0x41, // 127 = A + +// ctrl + 0x0c, // 128 = ^L + 0x00, // 129 = ^J + 0x00, // 130 = ^:, unused + 0x00, // 131 = (unused) + 0x00, // 132 = (unused) + 0x0b, // 133 = ^K + XCH_LEFT, // 134 = atari left arrow + XCH_RIGHT, // 135 = atari right arrow + 0x0f, // 136 = ^O + 0x00, // 137 = (unused) + 0x10, // 138 = ^P + 0x15, // 139 = ^U + 0x9b, // 140 = EOL + XCH_ITAL, // 141 = ^I: IRC italics format + XCH_UP, // 142 = atari up arrow + XCH_DOWN, // 143 = atari down arrow + 0x16, // 144 = ^V + 0x00, // 145 = (unused) + 0x03, // 146 = ^C + 0x00, // 147 = 1200XL ctrl-F3 (?) + 0x00, // 148 = (unused) + XCH_LWORD, // 149 = ^B + 0x18, // 150 = ^X + 0x1a, // 151 = ^Z + XCH_SCR4, // 152 = inverse 4 (switch to screen 4) + 0x00, // 153 = (unused) + XCH_SCR3, // 154 = inverse 3 (switch to screen 3) + XCH_SCR6, // 155 = inverse 6 (switch to screen 6) + 0x7e, // 156 = ~ (tilde, aka atari ctrl-esc) + XCH_SCR5, // 157 = inverse 5 (switch to screen 5) + XCH_SCR2, // 158 = inverse 2 (switch to screen 2) + XCH_SCR1, // 159 = inverse 1 (switch to screen 1) + 0x7b, // 160 = { + 0x20, // 161 = space + 0x7d, // 162 = } + 0x0e, // 163 = ^N + 0x00, // 164 = (unused) + 0x00, // 165 = ^M + 0x00, // 166 = (unused) + 0x00, // 167 = inverse + 0x12, // 168 = ^R + 0x00, // 169 = (unused) + 0x05, // 170 = ^E + 0x19, // 171 = ^Y + 0x00, // 172 = ctrl-tab + 0x14, // 173 = ^T + 0x17, // 174 = ^W + 0x11, // 175 = ^Q + 0x00, // 176 = inverse 9 (if we ever support 9 screens) + 0x00, // 177 unused? not XCH_SCR1? + 0x00, // 178 = inverse 0 (if we ever support 10 screens) + XCH_SCR7, // 179 = inverse 7 (switch to screen 7) + 0xfe, // 180 = del chr + 0x00, // 181 = inverse 8 (if we ever support 8 screens) + XCH_CLS, // 182 = } + 0xff, // 183 = ins chr + XCH_RWORD, // 184 = ^F + 0x08, // 185 = ^H + 0x04, // 186 = ^D + 0x00, // 187 = (unused) + 0x00, // 188 = ctrl-caps (unused) + 0x07, // 189 = ^G + 0x13, // 190 = ^S + 0x01, // 191 = ^A + +// ctrl+shift + 0x00, // 192 + 0x00, // 193 + 0x00, // 194 + 0x00, // 195 + 0x00, // 196 + 0x00, // 197 + 0x00, // 198 + 0x00, // 199 + 0x00, // 200 + 0x00, // 201 + 0x00, // 202 + XCH_UNDER, // 203 + 0x00, // 204 + 0x00, // 205 + XCH_LWORD, // 206, ctrl+shift+up + XCH_RWORD, // 207, ctrl+shift+down + 0x00, // 208 + 0x00, // 209 + 0x00, // 210 + 0x00, // 211 + 0x00, // 212 + 0x00, // 213 + 0x00, // 214 + 0x00, // 215 + 0x00, // 216 + 0x00, // 217 + 0x00, // 218 + 0x00, // 219 + 0x00, // 220 + 0x00, // 221 + 0x00, // 222 + 0x00, // 223 + 0x00, // 224 + 0x00, // 225 + 0x00, // 226 + 0x00, // 227 + 0x00, // 228 + 0x00, // 229 + 0x00, // 230 + 0x00, // 231 + 0x00, // 232 + 0x00, // 233 + 0x00, // 234 + 0x00, // 235 + 0x00, // 236 + 0x00, // 237 + 0x00, // 238 + 0x00, // 239 + 0x00, // 240 + 0x00, // 241 + 0x00, // 242 + 0x00, // 243 + 0x00, // 244 + 0x00, // 245 + 0x00, // 246 + 0x00, // 247 + 0x00, // 248 + 0x00, // 249 + 0x00, // 250 + 0x00, // 251 + 0x00, // 252 + 0x00, // 253 + 0x00, // 254 + 0x00, // 255 +}; diff --git a/src/keytab.h b/src/keytab.h new file mode 100644 index 0000000..04d3df9 --- /dev/null +++ b/src/keytab.h @@ -0,0 +1,34 @@ + +#define XCH_SCR1 0xb1 +#define XCH_SCR2 0xb2 +#define XCH_SCR3 0xb3 +#define XCH_SCR4 0xb4 +#define XCH_SCR5 0xb5 +#define XCH_SCR6 0xb6 +#define XCH_SCR7 0xb7 +/* when/if we have more screens: +#define XCH_SCR8 0xb8 +#define XCH_SCR9 0xb9 +#define XCH_SCR0 0xb0 +*/ + +#define XCH_TAB 0x80 +#define XCH_UP 0x81 +#define XCH_DOWN 0x82 +#define XCH_LEFT 0x83 +#define XCH_RIGHT 0x84 +#define XCH_LWORD 0x85 +#define XCH_RWORD 0x86 +#define XCH_CLS 0x87 +#define XCH_BS 0x88 +#define XCH_ACTIVE 0x89 +#define XCH_CAPS 0x8a + +/* these are just control characters */ +#define XCH_ITAL 0x1d +#define XCH_UNDER 0x1f + +/* cc65's atari.h should define this but doesn't */ +#define XCH_INSCHR 0xff + +extern char keytab[256]; diff --git a/src/kgetc.h b/src/kgetc.h new file mode 100644 index 0000000..3f96810 --- /dev/null +++ b/src/kgetc.h @@ -0,0 +1,2 @@ +char __fastcall__ kgetc(void); +char __fastcall__ keypress(void); diff --git a/src/kgetc.s b/src/kgetc.s new file mode 100644 index 0000000..524e348 --- /dev/null +++ b/src/kgetc.s @@ -0,0 +1,127 @@ +; kgetc() is a replacement for cc65's cgetc(). It does *not* call the +; OS's K: "get one byte" routine to decode keycodes into (AT)ASCII. It +; *does* support all 255 keycodes, meaning ctrl+shift combos work. + +; kgetc() actually does call the OS K: handler to produce a keyclick, but +; it does so with a fixed keycode (12, the Return key) and throws away +; the result. This means we can turn off the keyclick even on an 800. +; The OS NOCLIK location is used on the 800, too. + +; There is no blocking for another keystroke, if a key like Escape or +; Inverse is pressed. The ctrl-1 keystroke is detected, even though +; the OS "pre-empts" it (we never see $b1 in CH). + +; There is no inverse video mode! INVFLG is never set, and is ignored. +; There is no Control-lock either, although Caps-lock exists. + +; Unlike cgetc(), kgetc() doesn't need to be able to return 0 as a valid +; character. So instead, 0 is used to mean "ignore keystroke". + +; Since inverse numbers can't be returned, I've reused their ATASCII codes +; for 'switch window' with ctrl-1 through 7. + +; kgetc() blocks if no key has been pressed when it's called. to do +; non-blocking reads: +; if(keypress()) +; c = kgetc(); +; /* c is 0 if either no key or a non-ATASCII keystroke was pressed, +; e.g. caps lock or an unused ctrl-shift-letter. */ + + .include "atari.inc" + .export _kgetc, _keypress + .import _keytab + + XCH_CAPS = $8a ; must agree with keytab.h + + ; note: _keypress returns true/false, but also the Z + ; flag reflects the return status (Z clear for true). +_keypress: + ldx CH + cpx #$ff + bne ret1 ; if CH != $ff, a key is pressed... + ldx SSFLAG + bne ret1 ; if SSFLAG != 0, a key is pressed... + ldx HELPFG + bne ret1 ; if HELPFG != 0, a key (Help) is pressed... + txa ; return(0); + .byte $2c ; BIT ABS, skip LDA imm below +ret1: + lda #1 + rts + +_kgetc: + ldx HELPFG + beq read_ch ; Help key pressed? + lda #0 + sta HELPFG ; yes, clear it + ldx #$54 ; 1200XL Shift-F4 keycode + bne decode + +read_ch: + ldx CH + cpx #$ff + bne decode + ldx SSFLAG + beq read_ch + lda #0 + sta SSFLAG + ldx #$9f ; real Ctrl-1 keycode + +decode: + lda _keytab,x + beq done ; 0 in the table means ignore the keystroke (not even a click) + + cmp #XCH_CAPS + bne gotkey + + lda SHFLOK ; caps was pressed. we use SHFLOK different from how the OS does. + eor #$20 + sta SHFLOK + lda #0 + beq click_and_done + +gotkey: ; ATASCII code in A here, don't overwrite + cmp #'z'+1 ; only apply caps lock to a-z + bcs noshf + cmp #'a' + bcc noshf + sbc SHFLOK +noshf: + ldx NOCLIK + bne done ; obey the XL OS's keyclick disable flag, even on an 800 + + ; sound the keyclick (let the OS do it) +click_and_done: + pha + jsr keyclick + pla + +done: + ldx #$ff + stx CH + rts + +keyclick: + lda NOCLIK + bne return ; disabled + lda #0 + sta SSFLAG + lda #12 ; EOL (could be any regular key but we need 12 below) + sta CH ; fall thru to cgetc... + +; ripped from the cc65 lib src, and modified. +; original code by Christian Groessler, November-2002. +;_cgetc: + ;lda #12 ; already in A + sta ICAX1Z ; fix problems with direct call to KEYBDV + jsr callit +return: + ;ldx #0 + rts + +callit: + lda KEYBDV+5 + pha + lda KEYBDV+4 + pha + rts @@ -10,49 +10,41 @@ #include <stdlib.h> #include <stdio.h> #include <string.h> -#include <ctype.h> -#include <conio.h> // for cgetc() only #include "nio.h" #include "irc.h" #include "screen.h" #include "edbox.h" #include "config.h" -#include "keyclick.h" +#include "kgetc.h" #include "indic8.h" +#include "timers.h" 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 void *old_vprced; // old PROCEED vector, restored on exit. -unsigned short rxbuflen; // RX buffer length +unsigned int rxbuflen; // RX buffer length unsigned int txbuflen; // TX buffer length -char hz; /* 50 for PAL, 60 for NSTC */ char reconnect_timeout = 1; -/* TODO: user modes (default +iw), fg/bg color... */ - extern void ih(); // defined in intr.s void txbuf_init(void) { txbuflen = tx_buf[0] = 0; } -void txbuf_append_str(const char *str) { - while(*str) { - tx_buf[txbuflen++] = *str++; - } -} - void txbuf_append_str2(const char *s1, const char *s2) { txbuf_append_str(s1); txbuf_append_str(s2); } +/* void txbuf_append_str3(const char *s1, const char *s2, const char *s3) { txbuf_append_str(s1); txbuf_append_str(s2); txbuf_append_str(s3); } +*/ void txbuf_set_str(const char *str) { txbuf_init(); @@ -73,12 +65,15 @@ void txbuf_send(void) { /* don't send empty buffer */ if(!txbuflen) return; - /* always terminate with EOL */ - if(tx_buf[txbuflen - 1] != '\n') - tx_buf[txbuflen++] = '\n'; + /* always terminate with *ASCII* CRLF. + DO NOT USE '\n' or even '\x0a', cc65 turns it into $9b! */ + // tx_buf[txbuflen++] = 0x0d; + // tx_buf[txbuflen++] = 0x0a; + txbuf_append_chr(0x0d); + txbuf_append_chr(0x0a); ind_net_tx(); - nwrite(conf->url, tx_buf, txbuflen); + nwrite_txbuf(); ind_net_idle(); txbuf_init(); } @@ -92,10 +87,10 @@ void txbuf_send_str(const char *str) { int fn_connect(void) { scr_display(SCR_SERVER); scr_print_current("Connecting to: "); - scr_print_current(conf->url); + scr_print_current(config.url); scr_eol_current(); - err = nopen(conf->url, FNET_TRANSLATION); + err = nopen(); if(err != SUCCESS) { scr_print_current("Connection failed: "); @@ -118,7 +113,7 @@ int fn_connect(void) { } void fn_disconnect(void) { - nclose(conf->url); + nclose(); // Restore old PROCEED interrupt. PIA.pactl &= ~1; // disable interrupts @@ -130,9 +125,9 @@ void init_channels(void) { char i; for(i = 0; i < MAX_SCREENS - 2; i++) { - if(conf->channels[i][0]) { + if(config.channels[i][0]) { scr_status[i + 2] = SCR_INACTIVE; - strcpy(scr_names[i + 2], conf->channels[i]); + strcpy(scr_names[i + 2], config.channels[i]); } } } @@ -145,37 +140,30 @@ void reconnect(void) { OS.cdtmf3 = OS.ch = 0xff; if(reconnect_timeout) { - OS.cdtmv3 = reconnect_timeout * hz; + OS.cdtmv3 = reconnect_timeout * timers.hz; scr_print_current(" or wait "); - itoa(reconnect_timeout, numbuf, 10); - scr_print_current(numbuf); + scr_cur_printnum(reconnect_timeout); scr_print_current(" sec"); if(reconnect_timeout < 64) reconnect_timeout <<= 1; } scr_print_current(" to reconnect.\n"); - while(OS.cdtmf3 == 0xff && OS.ch == 0xff) + while(OS.cdtmf3 == 0xff && !keypress()) /* NOP */; - if(OS.ch != 0xff) { - keyclick(); - OS.ch = 0xff; - } + if(keypress()) kgetc(); } void main(void) { - bell_type = conf->alert_type; /* TODO: have bell.s read staight from the struct */ + bell_type = config.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 = conf->colors[0]; /* text BG, user-selected */ - // OS.color1 = (conf->colors[1] & 0x0f) | 0xc0; /* green (at user's brightness) */ - OS.color1 = conf->colors[1]; + OS.color2 = config.colors[0]; /* text BG, user-selected */ + OS.color1 = config.colors[1]; OS.color0 = 0x06; /* grey for inactive */ - OS.color3 = 0x46; /* red for highlight (not used yet) */ - OS.noclik = conf->disable_keyclick; - - hz = (GTIA_READ.pal & 0x0e) ? 60 : 50; + OS.color3 = 0x46; /* red for highlight */ + OS.noclik = config.disable_keyclick; edbox_clear(); scr_init(); diff --git a/src/nextarg.s b/src/nextarg.s new file mode 100644 index 0000000..f01cc04 --- /dev/null +++ b/src/nextarg.s @@ -0,0 +1,78 @@ + + ; in C: + +;; /* arg points to something like: +;; "part #channel I'm outta here\0" +;; after nextarg(), arg points to "part\0" only, and ret points +;; to "#channel I'm outta here\0". */ +;; char *nextarg(char *arg) { +;; /* iterate over the first word */ +;; while(*arg && *arg != ' ') +;; arg++; +;; +;; /* if we found a space, replace it with a null terminator */ +;; if(*arg) +;; *arg++ = 0; +;; else +;; return 0; /* found no space, there's no next arg! */ +;; +;; /* skip space(s) */ +;; while(*arg && *arg == ' ') +;; arg++; +;; +;; if(*arg) +;; return arg; +;; +;; return 0; +;; } + +; ...which compiles to ~175 bytes. we can do better in asm. + + .export _nextarg + .importzp ptr1 + +incptr1: + inc ptr1 + bne @ret + inc ptr1+1 +@ret: + rts ; always returns with Z flag clear (unless we roll over $FFFF -> 0!) + +_nextarg: + sta ptr1 + stx ptr1+1 + + ldy #0 ; actually this stays 0 the while time + + ; skips over the first word (aka sequence of non-spaces) +@skipword: + lda (ptr1),y + beq @ret0 ; found a null byte, return null (there is no next arg). + cmp #' ' + beq @foundspc + jsr incptr1 + bne @skipword ; branch always + +@foundspc: + ; ptr1 now points to a space. + tya ; 0 + sta (ptr1),y ; null out the space + + jsr incptr1 +@skipspc: + lda (ptr1),y + beq @ret0 ; found a null byte, return null (there is no next arg). + cmp #' ' + bne @done + jsr incptr1 + bne @skipspc + +@done: + lda ptr1 + ldx ptr1+1 + rts + +@ret0: + lda #0 + tax + rts @@ -2,201 +2,113 @@ * N: I/O */ -#include "nio.h" -#include "sio.h" +/* "stripped down" version that harcodes some parameters! Don't + use this as example code for writing your own fujinet app! */ + #include <atari.h> #include <stddef.h> +#include "nio.h" +#include "sio.h" +#include "config.h" +#include "rxtxbuf.h" #define TIMEOUT 0x1f /* approx 30 seconds */ -unsigned char nunit(char* devicespec) -{ - unsigned char unit=1; - - // Set unit to 1 unless explicitly specified. - if (devicespec[1]==':') - unit=1; - else if (devicespec[2]==':') - unit=devicespec[1]-0x30; // convert from alpha to integer. - else - unit=1; - - return unit; +#define UNIT 1 /* only support one FujiNet connection! */ + +/* 0 = no translation (used to use 3, CRLF) */ +#define TRANS 0 + +static char get_status(void) { + if(OS.dcb.dstats != SUCCESS) { + // something went wrong + // do we need to return extended status? + if(OS.dcb.dstats == DERROR) { + nstatus(); + return OS.dvstat[DVSTAT_EXTENDED_ERROR]; // return extended error. + } + } + return OS.dcb.dstats; // Return SIO error or success } -unsigned char nopen(char* devicespec, unsigned char trans) -{ - unsigned char unit=nunit(devicespec); - - OS.dcb.ddevic = DFUJI; // Fuji Device Identifier - OS.dcb.dunit = unit; // Unit number integer 1 through 4 - OS.dcb.dcomnd = 'O'; // Open - OS.dcb.dstats = DWRITE; // sending to to SIO device - OS.dcb.dbuf = devicespec; // eg: N:TCP// - OS.dcb.dtimlo = TIMEOUT; // approximately 30 second timeout - OS.dcb.dbyt = 256; // max size of our device spec - OS.dcb.daux1 = OUPDATE; // Read and write - OS.dcb.daux2 = trans; // CR/LF translation - siov(); - - if (OS.dcb.dstats!=SUCCESS) - { - // something went wrong - // do we need to return extended status? - if (OS.dcb.dstats==DERROR) - { - nstatus(devicespec); - return OS.dvstat[DVSTAT_EXTENDED_ERROR]; // return extended error. - } - } - return OS.dcb.dstats; // Return SIO error or success +static void set_defaults(void) { + OS.dcb.ddevic = DFUJI; // Fuji Device Identifier + OS.dcb.dunit = UNIT; // Unit number integer 1 through 4 + OS.dcb.dtimlo = TIMEOUT; // approximately 30 second timeout } -unsigned char nclose(char* devicespec) -{ - unsigned char unit=nunit(devicespec); - - OS.dcb.ddevic = DFUJI; - OS.dcb.dunit = unit; - OS.dcb.dcomnd = 'C'; // Close - OS.dcb.dstats = 0x00; - OS.dcb.dbuf = NULL; - OS.dcb.dtimlo = TIMEOUT; - OS.dcb.dbyt = 0; - OS.dcb.daux = 0; - siov(); - - if (OS.dcb.dstats!=SUCCESS) - { - // something went wrong - // do we need to return extended status? - if (OS.dcb.dstats==DERROR) - { - nstatus(devicespec); - return OS.dvstat[DVSTAT_EXTENDED_ERROR]; // return extended error. - } - } - return OS.dcb.dstats; // Return SIO error or success. +char nopen(void) { + set_defaults(); + OS.dcb.dcomnd = 'O'; // Open + OS.dcb.dstats = DWRITE; // sending to to SIO device + OS.dcb.dbuf = config.url; // eg: N:TCP// + OS.dcb.dbyt = 256; // max size of our device spec + OS.dcb.daux1 = OUPDATE; // Read and write + OS.dcb.daux2 = TRANS; // CR/LF translation + siov(); + + return get_status(); } -unsigned char nstatus(char* devicespec) -{ - unsigned char unit=nunit(devicespec); - - OS.dcb.ddevic = DFUJI; - OS.dcb.dunit = unit; - OS.dcb.dcomnd = 'S'; // status - OS.dcb.dstats = DREAD; - OS.dcb.dbuf = OS.dvstat; - OS.dcb.dtimlo = TIMEOUT; - OS.dcb.dbyt = sizeof(OS.dvstat); - OS.dcb.daux = 0; - siov(); - - return OS.dvstat[DVSTAT_EXTENDED_ERROR]; // return extended status -} +char nclose(void) { + set_defaults(); + OS.dcb.dcomnd = 'C'; // Close + OS.dcb.dstats = 0x00; + OS.dcb.dbuf = NULL; + OS.dcb.dbyt = 0; + OS.dcb.daux = 0; + siov(); -unsigned char nread(char* devicespec, unsigned char* buf, unsigned short len) -{ - unsigned char unit=nunit(devicespec); - - OS.dcb.ddevic = DFUJI; - OS.dcb.dunit = unit; - OS.dcb.dcomnd = 'R'; // read - OS.dcb.dstats = DREAD; - OS.dcb.dbuf = buf; - OS.dcb.dtimlo = TIMEOUT; - OS.dcb.dbyt = OS.dcb.daux = len; // Set the buffer size AND daux with length - siov(); - - if (OS.dcb.dstats!=SUCCESS) - { - // something went wrong - // do we need to return extended status? - if (OS.dcb.dstats==DERROR) - { - nstatus(devicespec); - return OS.dvstat[DVSTAT_EXTENDED_ERROR]; // return extended error. - } - } - return OS.dcb.dstats; // Return SIO error or success. + return get_status(); } -unsigned char nwrite(char* devicespec, unsigned char* buf, unsigned short len) -{ - unsigned char unit=nunit(devicespec); - - OS.dcb.ddevic = DFUJI; - OS.dcb.dunit = unit; - OS.dcb.dcomnd = 'W'; // write - OS.dcb.dstats = DWRITE; - OS.dcb.dbuf = buf; - OS.dcb.dtimlo = TIMEOUT; - OS.dcb.dbyt = OS.dcb.daux = len; - siov(); - - if (OS.dcb.dstats!=SUCCESS) - { - // something went wrong - // do we need to return extended status? - if (OS.dcb.dstats==DERROR) - { - nstatus(devicespec); - return OS.dvstat[DVSTAT_EXTENDED_ERROR]; // return extended error. - } - } - return OS.dcb.dstats; // Return SIO error or success. +char nstatus(void) { + set_defaults(); + OS.dcb.dcomnd = 'S'; // status + OS.dcb.dstats = DREAD; + OS.dcb.dbuf = OS.dvstat; + OS.dcb.dbyt = sizeof(OS.dvstat); + OS.dcb.daux = 0; + siov(); + + return OS.dvstat[DVSTAT_EXTENDED_ERROR]; // return extended status } -/* https://fujinet.online/wiki/?p=SIO-Command-%24FF-Reset-FujiNet */ -unsigned char nreset(void) { - OS.dcb.ddevic = 0x70; - OS.dcb.dunit = 1; - OS.dcb.dcomnd = 0xff; /* reset */ - OS.dcb.dstats = DWRITE; - OS.dcb.dbuf = 0; - OS.dcb.dtimlo = TIMEOUT; - OS.dcb.dbyt = 0; - OS.dcb.daux = 0; - siov(); - - return OS.dvstat[DVSTAT_EXTENDED_ERROR]; // return extended status +char nread_rxbuf(void) { + extern unsigned int rxbuflen; + set_defaults(); + OS.dcb.dcomnd = 'R'; // read + OS.dcb.dstats = DREAD; + OS.dcb.dbuf = rx_buf; + OS.dcb.dbyt = OS.dcb.daux = rxbuflen; // Set the buffer size AND daux with length + siov(); + + return get_status(); } +char nwrite_txbuf(void) { + extern unsigned int txbuflen; + set_defaults(); + OS.dcb.dcomnd = 'W'; // write + OS.dcb.dstats = DWRITE; + OS.dcb.dbuf = tx_buf; + OS.dcb.dbyt = OS.dcb.daux = txbuflen; + siov(); + + return get_status(); +} -/* IRC doesn't need this */ -#if 0 -unsigned char nlogin(char* devicespec, char *login, char *password) -{ - unsigned char unit=nunit(devicespec); - - OS.dcb.ddevic=0x71; - OS.dcb.dunit=unit; - OS.dcb.dcomnd=0xFD; - OS.dcb.dstats=0x80; - OS.dcb.dbuf=login; - OS.dcb.dtimlo=0x1f; - OS.dcb.dbyt=256; - OS.dcb.daux=0; - siov(); - - if (OS.dcb.dstats!=1) - { - nstatus(devicespec); - return OS.dvstat[DVSTAT_EXTENDED_ERROR]; // return ext err - } - - OS.dcb.dcomnd=0xFE; - OS.dcb.dstats=0x80; - OS.dcb.dbuf=password; - siov(); - - if (OS.dcb.dstats!=1) - { - nstatus(devicespec); - return OS.dvstat[DVSTAT_EXTENDED_ERROR]; // return ext err - } - - return OS.dcb.dstats; +/* https://fujinet.online/wiki/?p=SIO-Command-%24FF-Reset-FujiNet */ +char nreset(void) { + set_defaults(); + OS.dcb.ddevic = 0x70; + OS.dcb.dcomnd = 0xff; /* reset */ + OS.dcb.dstats = DWRITE; + OS.dcb.dbuf = 0; + OS.dcb.dbyt = 0; + OS.dcb.daux = 0; + siov(); + + return OS.dvstat[DVSTAT_EXTENDED_ERROR]; // return extended status } -#endif + @@ -22,58 +22,11 @@ #define DVSTAT_PROTOCOL 2 #define DVSTAT_EXTENDED_ERROR 3 -/** - * Open N: device with devicespec - * @param devicespec - an N: device spec, e.g. N:TCP://FOO.COM:1234/ - * @param translation mode, 0=none, 1=cr, 2=lf, 3=cr/lf - * @return error code, or 1 if successful. - */ -unsigned char nopen(char* devicespec, unsigned char trans); - -/** - * Close N: device with devicespec - * @param devicespec - an N: device spec to close (the unit number is extracted) - * @return error code, or 1 if successful. - */ -unsigned char nclose(char* devicespec); - -/** - * Get status of specific N: device - * @param devicespec - an N: device spec to status (the unit number is extracted) - * @return error code, or 1 if successful, DVSTAT is also filled with status info. - * - * Format of DVSTAT: - * OS.dcb.dvstat[0] = # of bytes waiting LO - * OS.dcb.dvstat[1] = # of bytes waiting HI - * OS.dcb.dvstat[2] = reserved - * OS.dcb.dvstat[3] = Error code of last I/O operation. !1 = error. - */ -unsigned char nstatus(char* devicespec); - -/** - * Read # of bytes from specific N: device. - * @param devicespec - an N: device spec to read bytes from. - * @param buf - The buffer to read into, must be at least as big as len. - * @param len - The # of bytes to read (up to 65535) - * @return error code, or 1 if successful, buf is filled with data. - */ -unsigned char nread(char* devicespec, unsigned char* buf, unsigned short len); - -/** - * Write # of bytes to specific N: device. - * @param devicespec - an N: device spec to write to. - * @param buf - The buffer to write to device, should be at least as big as len. - * @param len - The # of bytes to write (up to 65535) - * @return error code, or 1 if successful, buf is filled with data. - */ -unsigned char nwrite(char* devicespec, unsigned char* buf, unsigned short len); - -/** - * Send username and password credentials - * @param devicespec - The devicespec. - * @param login - The username to send - * @param password - The password to send - */ -// unsigned char nlogin(char* devicespec, char* login, char* password); +char nopen(void); +char nclose(void); +char nstatus(void); +char nread_rxbuf(void); +char nwrite_txbuf(void); +char nreset(void); #endif /* NIO_H */ diff --git a/src/permute.s b/src/permute.s new file mode 100644 index 0000000..d4206ea --- /dev/null +++ b/src/permute.s @@ -0,0 +1,78 @@ + .export _permute_nick + .importzp ptr1 + +;permutes last character (doesn't add one), so for "Bob" you get: +;Bo_, Bo1 through Bo9, BoA through BoZ +;Gives a total of 36 replacement nicks to try. +;Eventually we run out and start repeating, but by then the IRC +;server will have disconnected us. + +;; static void permute_nick(void) { +;; static char *last; +;; +;; last = conf->nick + strlen(conf->nick) - 1; +;; +;; if((*last >= '1' && *last < '9') || (*last >= 'A' && *last < 'Z')) { +;; (*last)++; +;; } else { +;; switch(*last) { +;; case '_': *last = '1'; break; +;; case '9': *last = 'A'; break; +;; default: *last = '_'; break; +;; } +;; } +;; } + +;; WARNING: the address of conf->nick is hardcoded here as $0480. +;; So is its length (25). + + nick = $0480 + +_permute_nick: + ldy #0 +@l1: + lda nick,y + beq perm + iny + cpy #$18 ; stop after 24 chars + bne @l1 + +perm: + dey ; last non-null character. + lda nick,y + + tax + + cpx #'1' + bcc nonnum + cpx #'8'+1 + bcc inc_and_ret + +nonnum: + cpx #'A' + bcc nonaplha + cpx #'Z' + bcc inc_and_ret + +nonaplha: + cpx #'_' + bne non_uscore + ldx #'1' + bne ret + +non_uscore: + cpx #'9' + bne non_9 + ldx #'A' + bne ret + +non_9: + ldx #'_' + bne ret + +inc_and_ret: + inx +ret: + txa + sta nick,y + rts diff --git a/src/pollkbd.s b/src/pollkbd.s new file mode 100644 index 0000000..2d44976 --- /dev/null +++ b/src/pollkbd.s @@ -0,0 +1,93 @@ +; in C: +;; void poll_keyboard(void) { +;; char c; +;; +;; if(!keypress()) return; +;; +;; /* have to latch start status because doing a keyclick clears CONSOL */ +;; if(GTIA_READ.consol == 6) start_latch = 1; +;; +;; c = kgetc(); +;; if(!c) return; +;; +;; /* maybe this shouldn't happen until user presses Enter in the edbox? +;; would let lurkers lurk and read scrollback... */ +;; if(irc_away) { +;; txbuf_send_str("AWAY"); +;; irc_away = 0; +;; } +;; +;; if(start_latch) { /* start pressed */ +;; start_keystroke(c); +;; } else { +;; edbox_keystroke(c); +;; OS.cdtmv3 = hz / 2; +;; } +;; } + +; compiles to ~128 bytes. + + .include "atari.inc" + + .export _poll_keyboard + .import _keypress, _kgetc, _start_latch, _start_keystroke + .import _edbox_keystroke, _txbuf_send_str, _irc_away + + _hz = $f0 ; must agree with timers.h! + + .rodata +away: + .byte "AWAY",0 + + .code +_poll_keyboard: + ;; if(!keypress()) return; + jsr _keypress + beq @ret ; Z flag set = no keypress + + ;; if(GTIA_READ.consol == 6) start_latch = 1; + lda CONSOL + cmp #6 + bne @nostart + sta _start_latch ; 6, not 1 (doesn't matter so long as it's non-zero) + +@nostart: + ; c = kgetc(); + jsr _kgetc ; note that _kgetc can set _start_latch too + tax ; just to set Z flag based on return value in A + ; if(!c) return; + beq @ret ; if _kgetc returned 0, we're done + + ; if(irc_away) { + ldx _irc_away + beq @noaway + ; irc_away = 0; + ldx #0 + stx _irc_away + ; txbuf_send_str("AWAY"); + pha ; _txbuf_send_str will clobber A so stash it + lda #<away + ldx #>away + jsr _txbuf_send_str + ; } + pla +@noaway: + ; if(start_latch) { /* start pressed */ + ldx _start_latch + beq @nolatch + ; start_keystroke(c); + ldx #0 + jmp _start_keystroke +@nolatch: + ; } else { + ;;;; ldx #0 ; X already 0 + ; edbox_keystroke(c); + jsr _edbox_keystroke + ; OS.cdtmv3 = hz / 2; + lda _hz + lsr + sta CDTMV3 + ; } + +@ret: + rts diff --git a/src/printnum.s b/src/printnum.s new file mode 100644 index 0000000..8b97db9 --- /dev/null +++ b/src/printnum.s @@ -0,0 +1,80 @@ +; print a number to the screen. +; based on Ullrich von Bassewitz's itoa.s from the cc65 lib src. +; it's modified to: +; - only support base 10. +; - only support unsigned int. +; - print the result directly to the fnchat screen, or +; - store the result directly in numbuf. + + .importzp sreg + .import _scr_putc_active, _scr_current, _scr_active, _numbuf + .export _scr_cur_printnum, _scr_act_printnum, _num_to_numbuf + + ; sreg+2 bit 7 is a flag that means "print" if set, or "store in numbuf: + ; if clear. also, the bottom bits of sreg+2 are the buffer position, when + ; bit 7 is clear. + +_num_to_numbuf: + ldy #0 + sty sreg+2 + beq printnum + +_scr_cur_printnum: + ldy _scr_current + sty _scr_active + +_scr_act_printnum: + ldy #$80 + sty sreg+2 + +printnum: + sta sreg + stx sreg+1 + + lda #0 + pha ; sentinel + +divloop: + ldy #$10 ; 16-bit remainder + lda #0 + +shift: + asl sreg + rol sreg+1 + rol a + cmp #$0a ; radix + bcc nosub + sbc #$0a + inc sreg + +nosub: + dey + bne shift + + ora #$30 ; make it an ASCII digit + pha ; save digit on stack + + ; are we done yet? + lda sreg + ora sreg+1 + bne divloop ; nope! + + ; get the results from the stack and print. +digitloop: + pla + beq done ; found sentinel? we're done. + ldy sreg+2 + bpl store + jsr _scr_putc_active + jmp digitloop + +store: + sta _numbuf,y + iny + lda #0 + sta _numbuf,y + sty sreg+2 + beq digitloop ; branch always + +done: + rts diff --git a/src/rxtxbuf.h b/src/rxtxbuf.h new file mode 100644 index 0000000..888092a --- /dev/null +++ b/src/rxtxbuf.h @@ -0,0 +1,5 @@ +typedef char buf512_t[512]; +#define RXBUF_ADDR 0xa000 +#define TXBUF_ADDR 0xa200 +#define rx_buf (*(buf512_t *)RXBUF_ADDR) +#define tx_buf (*(buf512_t *)TXBUF_ADDR) diff --git a/src/screen.c b/src/screen.c index 350a60c..02cc45a 100644 --- a/src/screen.c +++ b/src/screen.c @@ -5,9 +5,14 @@ #include "screen.h" #include "edbox.h" #include "indic8.h" +#include "streq.h" #define SDLST ((u16 *)0x0230) +/* if DOS isn't being used, the config will carve up the $0700-$1fff + area for extra scrollback. */ +unsigned int *bonus_addrs = (unsigned int *)0xd4; /* aka FR0 */ + char scr_status[MAX_SCREENS]; /* the screen that's currently displaying */ @@ -20,18 +25,26 @@ char scr_names[7][32]; static char xpos; -void scr_waitvcount(u8 c) { - while(ANTIC.vcount < c) +void scr_waitvcount_116(void) { + while(ANTIC.vcount < 116) /* NOP */; } static void scr_clear(char s) { + if(bonus_addrs[s]) { + memset(bonus_addrs[s], 0, 1000); + strcpy(bonus_addrs[s], "This is bonus scrollback!"); + } memset(screen_top_addrs[s], 0, 1000); memset(screen_bot_addrs[s], 0, 1000); memset(scr_names[s], 0, 32); } static void scr_scroll(char s) { + if(bonus_addrs[s]) { + memmove(bonus_addrs[s], bonus_addrs[s] + 40, 960); + memmove(bonus_addrs[s] + 960, screen_top_addrs[s], 40); + } memmove(screen_top_addrs[s], screen_top_addrs[s] + 40, 960); memmove(screen_top_addrs[s] + 960, screen_bot_addrs[s], 40); memmove(screen_bot_addrs[s], screen_bot_addrs[s] + 40, 920); @@ -43,9 +56,7 @@ void scr_init(void) { OS.sdmctl = 0; /* disappear the screen */ - ind_net_status = status_box + 40; - - scr_waitvcount(112); /* after the last GR.0 line */ + scr_waitvcount_116(); *SDLST = DLIST_BOT_ADDR; OS.chbas = FONT_ADDR_HI; @@ -111,22 +122,29 @@ void scr_display(char s) { scr_status[s] = SCR_INACTIVE; scr_current = s; - scr_waitvcount(112); + scr_waitvcount_116(); *dlist_bot_lms = (u16)screen_bot_addrs[s]; scr_show_status(s); } +void scr_scrollback_bonus(void) { + if(bonus_addrs[scr_current]) { + scr_waitvcount_116(); + *dlist_top_lms = (u16)bonus_addrs[scr_current]; + } +} + void scr_scrollback(void) { // OS.color2 = 0; - scr_waitvcount(112); + scr_waitvcount_116(); *dlist_top_lms = (u16)screen_top_addrs[scr_current]; *SDLST = (u16)dlist_top; } void scr_end_scrollback(void) { // OS.color2 = 192; - scr_waitvcount(112); + scr_waitvcount_116(); *SDLST = (u16)dlist_bot; } @@ -150,6 +168,9 @@ void scr_show_status(char s) { case SCR_ACTIVE: /* color1 */ sc |= 0x40; break; + case SCR_OTHER: /* color2 */ + sc |= 0x80; + break; case SCR_HILITE: /* color3 */ sc |= 0xc0; break; @@ -162,18 +183,12 @@ void scr_show_status(char s) { // *p++ = 0xbe; if(!edbox_visible) { - scr_waitvcount(112); + scr_waitvcount_116(); *dlist_status_lms = (u16)status_box; *dlist_last_line = 0x06; /* ANTIC GR.1 */ } } -void scr_hilite_active(void) { - if(scr_active == scr_current) return; - scr_status[scr_active] = SCR_HILITE; - scr_show_status(scr_current); -} - void scr_refresh(void) { scr_display(scr_current); } @@ -184,7 +199,7 @@ char scr_getbyname(const char *name) { if(!name) return 0; for(i = 2; i < MAX_SCREENS; i++) { - if(strcasecmp(name, scr_names[i]) == 0) + if(streq_i(name, scr_names[i])) return i; } @@ -286,10 +301,12 @@ void scr_print_priv(const char *text) { } void scr_activate(char s) { + /* if(s != scr_current) { if(scr_status[s] != SCR_HILITE) scr_status[s] = SCR_ACTIVE; scr_show_status(scr_current); } + */ scr_active = s; } diff --git a/src/screen.h b/src/screen.h index 787310d..2959b1e 100644 --- a/src/screen.h +++ b/src/screen.h @@ -7,8 +7,9 @@ #define SCR_UNUSED 0 #define SCR_INACTIVE 1 -#define SCR_ACTIVE 2 -#define SCR_HILITE 3 +#define SCR_OTHER 2 +#define SCR_ACTIVE 3 +#define SCR_HILITE 4 #define SCR_SERVER 0 #define SCR_PRIV 1 @@ -56,6 +57,7 @@ void scr_display(char s); note that it's a bad idea to write to the screen while it's scrolled back! */ void scr_scrollback(void); +void scr_scrollback_bonus(void); /* end scrollback mode (display the bottom 23 lines + status) */ void scr_end_scrollback(void); @@ -100,12 +102,14 @@ void scr_print_priv(const char *text); will have to call this. */ void scr_activate(char s); -void scr_hilite_active(void); - char *scr_get_cur_name(void); /* XXX: this really should be in a utils.c or common.c... */ -void scr_waitvcount(u8 c); +void scr_waitvcount_116(void); + +/* print decimal numbers (see printnum.s) */ +void scr_cur_printnum(u16 c); +void scr_act_printnum(u16 c); /**** end of public API ****/ @@ -1,17 +1,3 @@ ;; Call SIO - .export _siov - .export _rtclr - .export _cold_start - -_siov: JSR $E459 - RTS - -_rtclr: LDA #$00 - STA $12 - STA $13 - STA $14 - RTS - -_cold_start: - JMP $E477 + _siov = $e459 diff --git a/src/streq.h b/src/streq.h new file mode 100644 index 0000000..65b6ddb --- /dev/null +++ b/src/streq.h @@ -0,0 +1,5 @@ +extern char lcase(char c); +extern char streq(const char *s1, const char *s2); +extern char streq_i(const char *s1, const char *s2); +extern char strneq_i(const char *s1, const char *s2, char limit); +extern char find_nick(void); diff --git a/src/streq.s b/src/streq.s new file mode 100644 index 0000000..d063a35 --- /dev/null +++ b/src/streq.s @@ -0,0 +1,134 @@ + + .importzp sreg, ptr1, ptr2 + .import popptr1, popax + .import _msg_text + .export _streq, _streq_i, _strneq_i, _lcase, _find_nick + + ; extern __fastcall__ char lcase(char c); + ; extern __fastcall__ char streq(char *s1, char *s2); + ; extern __fastcall__ char streq_i(char *s1, char *s2); + ; extern __fastcall__ char strneq_i(char *s1, char *s2, char len); + ; extern __fastcall__ char find_nick(void); + + ; these are fast and small replacements for standard C library functions. + ; lcase() is a drop-in replacement for tolower(). + ; streq() is basically strcmp() except it returns true for equality, + ; false for inequality (so you can't e.g. use it for sorting). + ; also, it only supports strings up to 256 bytes long. + ; streq_i() is the case-insensitive verson of streq(). + ; strneq_i() is case-insensitive and stops after 'len' characters. + ; find_nick() does a case-insensitive search of msg_text for + ; config.nick. returns 1 if found, 0 if not. NOTE: only searches the + ; first 256 bytes of msg_text! this is a minor infelicity, but people + ; rarely type that much text in one message... + + limit = sreg+3 ; number of characters to compare (0 = 256) + insens = sreg ; bit 7 set = case insensitive, otherwise not + temp = sreg+1 ; nothing to see here, move along :) + temp2 = sreg+2 + + NICK = $0480 ; aka config.nick + +_lcase: + cmp #'Z'+1 + bcs lcret + cmp #'A' + bcc lcret + ora #$20 +lcret: + rts + +_strneq_i: + sta limit + jsr popax + ldy #$80 + sty insens + jmp doit + +_streq_i: + ldy #$80 + .byte $2c ; BIT abs, skip next +_streq: + ldy #0 + sty insens + ldy #0 + sty limit + +doit: + sta ptr2 + stx ptr2+1 + jsr popptr1 ; returns with Y=0 + +cmploop: + lda (ptr2),y + bit insens + bpl no_case_1 + jsr _lcase + +no_case_1: + sta temp + lda (ptr1),y + tax + bit insens + bpl no_case_2 + jsr _lcase + +no_case_2: + sec + sbc temp + bne ret0 + txa + beq ret1 + + iny + cpy limit + bne cmploop + +ret1: + lda #1 + .byte $2c +ret0: + lda #0 + tax + rts + +_find_nick: + lda _msg_text + sta ptr1 + lda _msg_text+1 + sta ptr1+1 + ldy #0 + ; loop over _msg_text, looking for the first character of NICK, + ; but convert both to lowercase before comparing. +@l: + lda (ptr1),y + beq ret0 ; if we hit the end of _msg_text, we're done, no match. + jsr _lcase + sta temp ; temp = lcase(A) + lda NICK ; compare to lcase(NICK[0]) + jsr _lcase + cmp temp + beq @found1 ; found a match for the first char, see if the rest matches. +@next: + iny + bne @l + beq ret0 +@found1: ; found a case-insensitive match for the first char of NICL... + sty temp2 ; save Y so we can pick back up in the @l loop if this isn't a match. + iny ; start with _msg_text char after the initial match. + ldx #1 ; start with 2nd char of NICK +@l2: + lda (ptr1),y + jsr _lcase + sta temp + lda NICK,x + beq ret1 ; if we hit the null terminator of NICK, we have a successful match! + jsr _lcase + cmp temp + bne @nope ; no match, get out of @l2 + iny + inx + bne @l2 ; matched a char, look at the next +@nope: + ldy temp2 ; restore Y + jmp @next ; jump to the bottom of the @l loop. diff --git a/src/timers.h b/src/timers.h new file mode 100644 index 0000000..a3e41f8 --- /dev/null +++ b/src/timers.h @@ -0,0 +1,8 @@ +typedef struct { + char hz; /* 50 for PAL, 60 for NTSC */ + char one_tenth_sec; /* 5 for PAL, 6 for NTSC */ + unsigned int one_sec; /* 1 sec, "hz" times 60 */ + unsigned int net_ind_time; /* 3.7 sec (just what The_Doctor__ ordered) */ +} timers_t; + +#define timers (*(timers_t *)(0xf0)) diff --git a/src/txbuf.s b/src/txbuf.s new file mode 100644 index 0000000..bc1d1c2 --- /dev/null +++ b/src/txbuf.s @@ -0,0 +1,50 @@ +;; void txbuf_append_chr(char c) { +;; tx_buf[txbuflen++] = c; +;; } + +; compiles to 45 bytes. routine below is 29 bytes (~33% smaller) + + tx_buf = $a200 ; MUST agree with src/rxtxbuf.h! + + .import _txbuflen + .export _txbuf_append_chr, _txbuf_append_str + .importzp sreg ; avoid ptr1 & friends, callers may use + +_txbuf_append_chr: + tax + lda #<tx_buf + clc + adc _txbuflen + sta sreg + lda #>tx_buf + adc _txbuflen+1 + sta sreg+1 + ldy #0 + txa + sta (sreg),y + inc _txbuflen + bne ret + inc _txbuflen+1 +ret: + rts ; always returns with Y == 0 + +;; void txbuf_append_str(const char *str) { +;; while(*str) { +;; txbuf_append_chr(*str++); +;; } +;; } + +; compiles to 52 bytes. +; this routine is 22 bytes, ~57% smaller. +_txbuf_append_str: + sta sreg+2 + stx sreg+3 + ldy #0 +@loop: + lda (sreg+2),y + beq ret + jsr _txbuf_append_chr + inc sreg+2 + bne @loop + inc sreg+3 + bne @loop |
