diff options
| author | B. Watson <urchlay@slackware.uk> | 2026-02-11 13:26:57 -0500 |
|---|---|---|
| committer | B. Watson <urchlay@slackware.uk> | 2026-02-11 13:26:57 -0500 |
| commit | b7b86e3a26d4bfba9c49f0a24ec030a7b3ebd8a3 (patch) | |
| tree | d2cb7323c78a4983e2a498110a077aaf24d1f696 | |
| download | fujinet-chat-b7b86e3a26d4bfba9c49f0a24ec030a7b3ebd8a3.tar.gz | |
initial commit
| -rw-r--r-- | Makefile | 350 | ||||
| -rw-r--r-- | install.mk | 2 | ||||
| -rw-r--r-- | parsetest.mk | 2 | ||||
| -rw-r--r-- | src/atari.cfg | 52 | ||||
| -rw-r--r-- | src/cio.h | 5 | ||||
| -rw-r--r-- | src/cio.s | 8 | ||||
| -rw-r--r-- | src/cmd.c | 19 | ||||
| -rw-r--r-- | src/conio.c | 37 | ||||
| -rw-r--r-- | src/conio.h | 14 | ||||
| -rw-r--r-- | src/err.c | 37 | ||||
| -rw-r--r-- | src/err.h | 22 | ||||
| -rw-r--r-- | src/intr.s | 7 | ||||
| -rw-r--r-- | src/irc.c | 306 | ||||
| -rw-r--r-- | src/irc.h | 59 | ||||
| -rw-r--r-- | src/main.c | 311 | ||||
| -rw-r--r-- | src/nio.c | 183 | ||||
| -rw-r--r-- | src/nio.h | 79 | ||||
| -rw-r--r-- | src/sio.h | 7 | ||||
| -rw-r--r-- | src/sio.s | 17 | ||||
| -rw-r--r-- | src/ui.c | 73 |
20 files changed, 1590 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..febbae0 --- /dev/null +++ b/Makefile @@ -0,0 +1,350 @@ +############################################################################### +### Generic Makefile for cc65 projects - full version with abstract options ### +### V1.3.0(w) 2010 - 2013 Oliver Schmidt & Patryk "Silver Dream !" Ćogiewa ### +############################################################################### + +############################################################################### +### In order to override defaults - values can be assigned to the variables ### +############################################################################### + +# Space or comma separated list of cc65 supported target platforms to build for. +# Default: c64 (lowercase!) +TARGETS := atari + +# Name of the final, single-file executable. +# Default: name of the current dir with target name appended +PROGRAM := fnchat + +# Path(s) to additional libraries required for linking the program +# Use only if you don't want to place copies of the libraries in SRCDIR +# Default: none +LIBS := + +# Custom linker configuration file +# Use only if you don't want to place it in SRCDIR +# Default: none +CONFIG := + +# Additional C compiler flags and options. +# Default: none +CFLAGS = -Oris + +# Additional assembler flags and options. +# Default: none +ASFLAGS = + +# Additional linker flags and options. +# Default: none +LDFLAGS = $(LDFLAGS.$(TARGETS)) +LDFLAGS.atari = --mapfile $(PROGRAM).map + +# Path to the directory containing C and ASM sources. +# Default: src +SRCDIR := + +# Path to the directory where object files are to be stored (inside respective target subdirectories). +# Default: obj +OBJDIR := + +# Command used to run the emulator. +# Default: depending on target platform. For default (c64) target: x64 -kernal kernal -VICIIdsize -autoload +EMUCMD := + +# Optional commands used before starting the emulation process, and after finishing it. +# Default: none +#PREEMUCMD := osascript -e "tell application \"System Events\" to set isRunning to (name of processes) contains \"X11.bin\"" -e "if isRunning is true then tell application \"X11\" to activate" +#PREEMUCMD := osascript -e "tell application \"X11\" to activate" +#POSTEMUCMD := osascript -e "tell application \"System Events\" to tell process \"X11\" to set visible to false" +#POSTEMUCMD := osascript -e "tell application \"Terminal\" to activate" +PREEMUCMD := +POSTEMUCMD := + +# On Windows machines VICE emulators may not be available in the PATH by default. +# In such case, please set the variable below to point to directory containing +# VICE emulators. +#VICE_HOME := "C:\Program Files\WinVICE-2.2-x86\" +VICE_HOME := + +# Options state file name. You should not need to change this, but for those +# rare cases when you feel you really need to name it differently - here you are +STATEFILE := Makefile.options + +################################################################################### +#### DO NOT EDIT BELOW THIS LINE, UNLESS YOU REALLY KNOW WHAT YOU ARE DOING! #### +################################################################################### + +################################################################################### +### Mapping abstract options to the actual compiler, assembler and linker flags ### +### Predefined compiler, assembler and linker flags, used with abstract options ### +### valid for 2.14.x. Consult the documentation of your cc65 version before use ### +################################################################################### + +# Compiler flags used to tell the compiler to optimise for SPEED +define _optspeed_ + CFLAGS += -Oris +endef + +# Compiler flags used to tell the compiler to optimise for SIZE +define _optsize_ + CFLAGS += -Or +endef + +# Compiler and assembler flags for generating listings +define _listing_ + CFLAGS += --listing $$(@:.o=.lst) + ASFLAGS += --listing $$(@:.o=.lst) + REMOVES += $(addsuffix .lst,$(basename $(OBJECTS))) +endef + +# Linker flags for generating map file +define _mapfile_ + LDFLAGS += --mapfile $$@.map + REMOVES += $(PROGRAM).map +endef + +# Linker flags for generating VICE label file +define _labelfile_ + LDFLAGS += -Ln $$@.lbl + REMOVES += $(PROGRAM).lbl +endef + +# Linker flags for generating a debug file +define _debugfile_ + LDFLAGS += -Wl --dbgfile,$$@.dbg + REMOVES += $(PROGRAM).dbg +endef + +############################################################################### +### Defaults to be used if nothing defined in the editable sections above ### +############################################################################### + +# Presume the C64 target like the cl65 compile & link utility does. +# Set TARGETS to override. +ifeq ($(TARGETS),) + TARGETS := c64 +endif + +# Presume we're in a project directory so name the program like the current +# directory. Set PROGRAM to override. +ifeq ($(PROGRAM),) + PROGRAM := $(notdir $(CURDIR)) +endif + +# Presume the C and asm source files to be located in the subdirectory 'src'. +# Set SRCDIR to override. +ifeq ($(SRCDIR),) + SRCDIR := src +endif + +# Presume the object and dependency files to be located in the subdirectory +# 'obj' (which will be created). Set OBJDIR to override. +ifeq ($(OBJDIR),) + OBJDIR := obj +endif +TARGETOBJDIR := $(OBJDIR)/$(TARGETS) + +# On Windows it is mandatory to have CC65_HOME set. So do not unnecessarily +# rely on cl65 being added to the PATH in this scenario. +ifdef CC65_HOME + CC := $(CC65_HOME)/bin/cl65 +else + CC := cl65 +endif + +# Default emulator commands and options for particular targets. +# Set EMUCMD to override. +c64_EMUCMD := $(VICE_HOME)xscpu64 -VICIIdsize -autostart +c128_EMUCMD := $(VICE_HOME)x128 -kernal kernal -VICIIdsize -autoload +vic20_EMUCMD := $(VICE_HOME)xvic -kernal kernal -VICdsize -autoload +pet_EMUCMD := $(VICE_HOME)xpet -Crtcdsize -autoload +plus4_EMUCMD := $(VICE_HOME)xplus4 -TEDdsize -autoload +# So far there is no x16 emulator in VICE (why??) so we have to use xplus4 with -memsize option +c16_EMUCMD := $(VICE_HOME)xplus4 -ramsize 16 -TEDdsize -autoload +cbm510_EMUCMD := $(VICE_HOME)xcbm2 -model 510 -VICIIdsize -autoload +cbm610_EMUCMD := $(VICE_HOME)xcbm2 -model 610 -Crtcdsize -autoload +atari_EMUCMD := atari800 -windowed -xl -pal -nopatchall -run + +ifeq ($(EMUCMD),) + EMUCMD = $($(CC65TARGET)_EMUCMD) +endif + +############################################################################### +### The magic begins ### +############################################################################### + +# The "Native Win32" GNU Make contains quite some workarounds to get along with +# cmd.exe as shell. However it does not provide means to determine that it does +# actually activate those workarounds. Especially does $(SHELL) NOT contain the +# value 'cmd.exe'. So the usual way to determine if cmd.exe is being used is to +# execute the command 'echo' without any parameters. Only cmd.exe will return a +# non-empy string - saying 'ECHO is on/off'. +# +# Many "Native Win32" prorams accept '/' as directory delimiter just fine. How- +# ever the internal commands of cmd.exe generally require '\' to be used. +# +# cmd.exe has an internal command 'mkdir' that doesn't understand nor require a +# '-p' to create parent directories as needed. +# +# cmd.exe has an internal command 'del' that reports a syntax error if executed +# without any file so make sure to call it only if there's an actual argument. +ifeq ($(shell echo),) + MKDIR = mkdir -p $1 + RMDIR = rmdir $1 + RMFILES = $(RM) $1 +else + MKDIR = mkdir $(subst /,\,$1) + RMDIR = rmdir $(subst /,\,$1) + RMFILES = $(if $1,del /f $(subst /,\,$1)) +endif +COMMA := , +SPACE := $(N/A) $(N/A) +define NEWLINE + + +endef +# Note: Do not remove any of the two empty lines above ! + +TARGETLIST := $(subst $(COMMA),$(SPACE),$(TARGETS)) + +ifeq ($(words $(TARGETLIST)),1) + +# Set PROGRAM to something like 'myprog.c64'. +override PROGRAM := $(PROGRAM).xex + +# Set SOURCES to something like 'src/foo.c src/bar.s'. +# Use of assembler files with names ending differently than .s is deprecated! +SOURCES := $(wildcard $(SRCDIR)/*.c) +SOURCES += $(wildcard $(SRCDIR)/*.s) +SOURCES += $(wildcard $(SRCDIR)/*.asm) +SOURCES += $(wildcard $(SRCDIR)/*.a65) + +# Add to SOURCES something like 'src/c64/me.c src/c64/too.s'. +# Use of assembler files with names ending differently than .s is deprecated! +SOURCES += $(wildcard $(SRCDIR)/$(TARGETLIST)/*.c) +SOURCES += $(wildcard $(SRCDIR)/$(TARGETLIST)/*.s) +SOURCES += $(wildcard $(SRCDIR)/$(TARGETLIST)/*.asm) +SOURCES += $(wildcard $(SRCDIR)/$(TARGETLIST)/*.a65) + +# Set OBJECTS to something like 'obj/c64/foo.o obj/c64/bar.o'. +OBJECTS := $(addsuffix .o,$(basename $(addprefix $(TARGETOBJDIR)/,$(notdir $(SOURCES))))) + +# Set DEPENDS to something like 'obj/c64/foo.d obj/c64/bar.d'. +DEPENDS := $(OBJECTS:.o=.d) + +# Add to LIBS something like 'src/foo.lib src/c64/bar.lib'. +LIBS += $(wildcard $(SRCDIR)/*.lib) +LIBS += $(wildcard $(SRCDIR)/$(TARGETLIST)/*.lib) + +# Add to CONFIG something like 'src/c64/bar.cfg src/foo.cfg'. +CONFIG += $(wildcard $(SRCDIR)/$(TARGETLIST)/*.cfg) +CONFIG += $(wildcard $(SRCDIR)/*.cfg) + +# Select CONFIG file to use. Target specific configs have higher priority. +ifneq ($(word 2,$(CONFIG)),) + CONFIG := $(firstword $(CONFIG)) + $(info Using config file $(CONFIG) for linking) +endif + +.SUFFIXES: +.PHONY: all test clean zap love + +all: $(PROGRAM) + +-include $(DEPENDS) +-include $(STATEFILE) + +# If OPTIONS are given on the command line then save them to STATEFILE +# if (and only if) they have actually changed. But if OPTIONS are not +# given on the command line then load them from STATEFILE. Have object +# files depend on STATEFILE only if it actually exists. +ifeq ($(origin OPTIONS),command line) + ifneq ($(OPTIONS),$(_OPTIONS_)) + ifeq ($(OPTIONS),) + $(info Removing OPTIONS) + $(shell $(RM) $(STATEFILE)) + $(eval $(STATEFILE):) + else + $(info Saving OPTIONS=$(OPTIONS)) + $(shell echo _OPTIONS_=$(OPTIONS) > $(STATEFILE)) + endif + $(eval $(OBJECTS): $(STATEFILE)) + endif +else + ifeq ($(origin _OPTIONS_),file) + $(info Using saved OPTIONS=$(_OPTIONS_)) + OPTIONS = $(_OPTIONS_) + $(eval $(OBJECTS): $(STATEFILE)) + endif +endif + +# Transform the abstract OPTIONS to the actual cc65 options. +$(foreach o,$(subst $(COMMA),$(SPACE),$(OPTIONS)),$(eval $(_$o_))) + +# Strip potential variant suffix from the actual cc65 target. +CC65TARGET := $(firstword $(subst .,$(SPACE),$(TARGETLIST))) + +# The remaining targets. +$(TARGETOBJDIR): + $(call MKDIR,$@) + +vpath %.c $(SRCDIR)/$(TARGETLIST) $(SRCDIR) + +$(TARGETOBJDIR)/%.o: %.c | $(TARGETOBJDIR) + $(CC) -t $(CC65TARGET) -c --create-dep $(@:.o=.d) $(CFLAGS) -o $@ $< + +vpath %.s $(SRCDIR)/$(TARGETLIST) $(SRCDIR) + +$(TARGETOBJDIR)/%.o: %.s | $(TARGETOBJDIR) + $(CC) -t $(CC65TARGET) -Wa -DDYN_DRV=0 -c --create-dep $(@:.o=.d) $(ASFLAGS) -o $@ $< + +vpath %.asm $(SRCDIR)/$(TARGETLIST) $(SRCDIR) + +$(TARGETOBJDIR)/%.o: %.asm | $(TARGETOBJDIR) + $(CC) -t $(CC65TARGET) -c --create-dep $(@:.o=.d) $(ASFLAGS) -o $@ $< + +vpath %.a65 $(SRCDIR)/$(TARGETLIST) $(SRCDIR) + +$(TARGETOBJDIR)/%.o: %.a65 | $(TARGETOBJDIR) + $(CC) -t $(CC65TARGET) -c --create-dep $(@:.o=.d) $(ASFLAGS) -o $@ $< + +$(PROGRAM): $(CONFIG) $(OBJECTS) $(LIBS) + $(CC) -t $(CC65TARGET) $(LDFLAGS) -o $@ $(patsubst %.cfg,-C %.cfg,$^) + + +test: $(PROGRAM) + $(PREEMUCMD) + $(EMUCMD) $< + $(POSTEMUCMD) + +clean: + $(call RMFILES,$(OBJECTS)) + $(call RMFILES,$(DEPENDS)) + $(call RMFILES,$(REMOVES)) + $(call RMFILES,$(PROGRAM)) + $(call RMFILES,test.map) + $(call RMFILES,$(PROGRAM).map) + $(call RMFILES,test.atr) + +else # $(words $(TARGETLIST)),1 + +all test clean: + $(foreach t,$(TARGETLIST),$(MAKE) TARGETS=$t $@$(NEWLINE)) + +endif # $(words $(TARGETLIST)),1 + +OBJDIRLIST := $(wildcard $(OBJDIR)/*) + +zap: + $(foreach o,$(OBJDIRLIST),-$(call RMFILES,$o/*.o $o/*.d $o/*.lst)$(NEWLINE)) + $(foreach o,$(OBJDIRLIST),-$(call RMDIR,$o)$(NEWLINE)) + -$(call RMDIR,$(OBJDIR)) + -$(call RMFILES,$(basename $(PROGRAM)).* $(STATEFILE)) + +love: + @echo "Not war, eh?" + +################################################################### +### Place your additional targets in the additional Makefiles ### +### in the same directory - their names have to end with ".mk"! ### +################################################################### +-include *.mk diff --git a/install.mk b/install.mk new file mode 100644 index 0000000..ca71538 --- /dev/null +++ b/install.mk @@ -0,0 +1,2 @@ +install: + cp irctest.xex /var/tnfs/ diff --git a/parsetest.mk b/parsetest.mk new file mode 100644 index 0000000..4e9eb8e --- /dev/null +++ b/parsetest.mk @@ -0,0 +1,2 @@ +parsetest: src/irc.c + rm -f parsetest; gcc -g -Wall -o parsetest src/irc.c diff --git a/src/atari.cfg b/src/atari.cfg new file mode 100644 index 0000000..ea190f7 --- /dev/null +++ b/src/atari.cfg @@ -0,0 +1,52 @@ +FEATURES { + STARTADDRESS: default = $2000; +} +SYMBOLS { + __EXEHDR__: type = import; + __AUTOSTART__: type = import; # force inclusion of autostart "trailer" + __STACKSIZE__: type = weak, value = $0800; # 2k stack + __STARTADDRESS__: type = export, value = %S; + __RESERVED_MEMORY__: type = weak, value = $0000; +} +MEMORY { + ZP: file = "", define = yes, start = $0082, size = $007E; + +# file header, just $FFFF + HEADER: file = %O, start = $0000, size = $0002; + +# "main program" load chunk + MAINHDR: file = %O, start = $0000, size = $0004; + MAIN: file = %O, define = yes, start = %S, size = $BC20 - __STACKSIZE__ - __RESERVED_MEMORY__ - %S; + TRAILER: file = %O, start = $0000, size = $0006; +} +SEGMENTS { + ZEROPAGE: load = ZP, type = zp; + EXTZP: load = ZP, type = zp, optional = yes; + EXEHDR: load = HEADER, type = ro; + MAINHDR: load = MAINHDR, type = ro; + STARTUP: load = MAIN, type = ro, define = yes; + LOWBSS: load = MAIN, type = rw, optional = yes; # not zero initialized + LOWCODE: load = MAIN, type = ro, define = yes, optional = yes; + ONCE: load = MAIN, type = ro, optional = yes; + CODE: load = MAIN, type = ro, define = yes; + RODATA: load = MAIN, type = ro; + DATA: load = MAIN, type = rw; + INIT: load = MAIN, type = rw, optional = yes; + BSS: load = MAIN, type = bss, define = yes; + AUTOSTRT: load = TRAILER, type = ro; +} +FEATURES { + CONDES: type = constructor, + label = __CONSTRUCTOR_TABLE__, + count = __CONSTRUCTOR_COUNT__, + segment = ONCE; + CONDES: type = destructor, + label = __DESTRUCTOR_TABLE__, + count = __DESTRUCTOR_COUNT__, + segment = RODATA; + CONDES: type = interruptor, + label = __INTERRUPTOR_TABLE__, + count = __INTERRUPTOR_COUNT__, + segment = RODATA, + import = __CALLIRQ__; +} diff --git a/src/cio.h b/src/cio.h new file mode 100644 index 0000000..d07c875 --- /dev/null +++ b/src/cio.h @@ -0,0 +1,5 @@ +/** + * Function to call cio + */ + +void ciov(); diff --git a/src/cio.s b/src/cio.s new file mode 100644 index 0000000..69a789a --- /dev/null +++ b/src/cio.s @@ -0,0 +1,8 @@ + ;; Call CIO + + .export _ciov + +_ciov: LDX #$00 + JSR $E456 + RTS + diff --git a/src/cmd.c b/src/cmd.c new file mode 100644 index 0000000..3c7f482 --- /dev/null +++ b/src/cmd.c @@ -0,0 +1,19 @@ +#include <atari.h> +#include <stdio.h> +#include "irc.h" + +void cmd_chan_text(const char *cmd) { + txbuf_set_str("PRIVMSG "); + txbuf_append_str(channel); + txbuf_append_str(" :"); + txbuf_append_str(cmd); + txbuf_send(); +} + +void cmd_command(const char *cmd) { + if(*cmd == '/') + txbuf_send_str(cmd + 1); + else if(channel[0]) + cmd_chan_text(cmd); + else ui_print("*** You are not on a channel\n"); +} diff --git a/src/conio.c b/src/conio.c new file mode 100644 index 0000000..2742ab6 --- /dev/null +++ b/src/conio.c @@ -0,0 +1,37 @@ +/** + * Simple conio for E: + */ + +#include <atari.h> +#include <string.h> +#include "cio.h" + +void printl(const char* c, int l) +{ + OS.iocb[0].buffer=c; + OS.iocb[0].buflen=l; + OS.iocb[0].command=IOCB_PUTCHR; + ciov(); +} + +void printc(char* c) +{ + OS.iocb[0].buffer=c; + OS.iocb[0].buflen=1; + OS.iocb[0].command=IOCB_PUTCHR; + ciov(); +} + +void print(const char* c) +{ + int l=strlen(c); + printl(c,l); +} + +void get_line(char* buf, unsigned char len) +{ + OS.iocb[0].buffer=buf; + OS.iocb[0].buflen=len; + OS.iocb[0].command=IOCB_GETREC; + ciov(); +} diff --git a/src/conio.h b/src/conio.h new file mode 100644 index 0000000..25ca6c2 --- /dev/null +++ b/src/conio.h @@ -0,0 +1,14 @@ +/** + * conio + */ + +#ifndef CONIO_H +#define CONIO_H + +void print(const char* c); +void printc(char* c); +void printl(const char* c, unsigned short l); +void get_line(char* buf, unsigned char len); +char get_char(void); + +#endif /* CONIO_H */ diff --git a/src/err.c b/src/err.c new file mode 100644 index 0000000..8d58670 --- /dev/null +++ b/src/err.c @@ -0,0 +1,37 @@ +/** + * FujiNet Tools for CLI + * + * Error output + * + * Author: Thomas Cherryhomes + * <thom.cherryhomes@gmail.com> + * + * Released under GPL, see COPYING + * for details + */ + +#include <atari.h> +#include "conio.h" + +const char error_138[]="FUJINET NOT RESPONDING\x9B"; +const char error_139[]="FUJINET NAK\x9b"; +const char error[]="SIO ERROR\x9b"; + +/** + * Show error + */ +void err_sio(void) +{ + switch (OS.dcb.dstats) + { + case 138: + print(error_138); + break; + case 139: + print(error_139); + break; + default: + print(error); + break; + } +} diff --git a/src/err.h b/src/err.h new file mode 100644 index 0000000..d3bf3d7 --- /dev/null +++ b/src/err.h @@ -0,0 +1,22 @@ +/** + * FujiNet Tools for CLI + * + * Error output + * + * Author: Thomas Cherryhomes + * <thom.cherryhomes@gmail.com> + * + * Released under GPL, see COPYING + * for details + */ + + +#ifndef ERR_H +#define ERR_H + +/** + * Show error + */ +void err_sio(void); + +#endif /* ERR_H */ diff --git a/src/intr.s b/src/intr.s new file mode 100644 index 0000000..00de34f --- /dev/null +++ b/src/intr.s @@ -0,0 +1,7 @@ + .export _ih + .import _trip + +_ih: LDA #$01 + STA _trip + PLA + RTI diff --git a/src/irc.c b/src/irc.c new file mode 100644 index 0000000..735ca2f --- /dev/null +++ b/src/irc.c @@ -0,0 +1,306 @@ +#include <stdbool.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> + +#include "irc.h" + +#ifdef __ATARI__ +#include <atari.h> +#include <conio.h> +#include "conio.h" +#include "nio.h" +#else +#define CH_EOL '|' +unsigned char rx_buf[MAX_IRC_MSG_LEN]; // RX buffer. +unsigned short bw=0; // # of bytes waiting. +#endif + +#define MAX_MSG 512 + +char *msg_src, *msg_cmd, *msg_dest, *msg_text; +char *msg_args[MAX_MSG_ARGS]; +int msg_argcount; + +static char msgbuf[MAX_MSG] = { 0 }; +static char *msg; /* with source removed */ +static int msgbuf_len = 0, msg_len = 0; + +static int joined = 0; + +#ifdef __ATARI__ +static void join_channel(void) { + ui_print("Joining channel...\n"); + txbuf_set_str("JOIN "); + txbuf_append_str(channel); + txbuf_append_str("\n"); + txbuf_send(); + joined = 1; +} + +static void do_pong(void) { + ui_putchar(CH_EOL); + ui_print("PING/PONG\n"); /* make hiding this a preference, or just ditch it */ + txbuf_set_str("PONG "); + txbuf_append_str(msg_args[0]); + txbuf_send(); +} + +static void do_privmsg(void) { + static char chan; + + chan = (*msg_dest == '#'); + + if(chan) { + ui_putchar('<'); + } else { + ui_putchar('*'); + } + + ui_print(msg_src); + + if(chan) { + ui_putchar('>'); + } else { + ui_putchar('*'); + } + + ui_putchar(' '); + ui_print(msg_text); +} + +static void do_catchall(void) { + int i; + if(msg_src) { + ui_print(msg_src); + ui_putchar(' '); + } + ui_print(msg_cmd); + for(i = 0; i < msg_argcount; i++) { + ui_putchar(' '); + ui_print(msg_args[i]); + } + if(msg_text) { + ui_putchar(' '); + ui_print(msg_text); + } +} + +static void do_numeric(void) { + do_catchall(); + + /* RPL_ENDOFMOTD or RPL_NOMOTD */ + if(!joined && (streq(msg_cmd, "372") || streq(msg_cmd, "422"))) { + join_channel(); + } +} + +static void invalid_msg(char type) { + ui_print("??? unknown, type "); + ui_putchar(type); + ui_putchar('\n'); +} +#else +static void do_pong(void) { } +static void invalid_msg(char type) { + printf("??? unknown, type %c\n", type); +} +#endif + +/* msgbuf contains a complete message from the server, whose + length is msgbuf_len. the last character *must* be CH_EOL, + and the last argument ends with CH_EOL. */ +static void parse_msg(void) { + char *p; + +#ifndef __ATARI__ + printf("\ngot message:\n"); + for(msg = msgbuf; *msg != CH_EOL; msg++) + putchar(*msg); + putchar('\n'); + putchar('\n'); +#endif + + msg_cmd = msg_text = msg_src = msg_dest = 0; + msg = msgbuf; + + /* ignore empty message */ + if(*msg == CH_EOL) return; + + /* 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; + } + + /* 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; /* generally :irc.example.com or :nick!user@host */ + msg_cmd = strtok(0, " "); + } else { + msg_src = 0; /* no source supplied */ + msg_cmd = p; + } + + if(!msg_cmd) { + invalid_msg('2'); + return; + } + + /* 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(); + return; + } else { + for(msg_argcount = 0; msg_argcount < MAX_MSG_ARGS; msg_argcount++) { + p = strtok(0, " "); + if(p) { + msg_args[msg_argcount] = p; + } else { + break; + } + } + } + if(msg_argcount) msg_dest = msg_args[0]; + + if(msg_src) { + if((p = strstr(msg_src, "!"))) { + msg_src++; + *p = '\0'; + } else { + msg_src = 0; + } + } + +#ifdef __ATARI__ + OS.crsinh = 1; + ui_start_msg(); + if(streq_i(msg_cmd, "PRIVMSG")) { + do_privmsg(); + } else if(isdigit(msg_cmd[0])) { + do_numeric(); + } else { + do_catchall(); + } + ui_end_msg(); +#else + { + int i; + printf("src: %s\n", msg_src ? msg_src : "<none>"); + printf("cmd: %s\n", msg_cmd ? msg_cmd : "<none>"); + printf("args: %d\n", msg_argcount); + for(i = 0; i < msg_argcount; i++) + printf(" %d: %s\n", i, msg_args[i]); + printf("text: %s\n", msg_text ? msg_text : "<none>"); + } +#endif +} + +static void irc_parse(void) { + int i; + char *p = rx_buf; + +#ifndef __ATARI__ + printf("irc_parse() called, bw == %d\n", bw); +#endif + + for(i = 0; i < bw; i++) { + msgbuf[msgbuf_len] = *p; + if(*p == CH_EOL) { + msgbuf[msgbuf_len + 1] = '\0'; + parse_msg(); + msgbuf_len = 0; + } else { + msgbuf_len++; + } + p++; + } +} + +#ifdef __ATARI__ +bool irc_read(void) { + if(!trip) return 1; + + err = nstatus(url); + + if(err == 136) { + ui_print("Disconnected, press any key...\n"); + cgetc(); + return 0; + } else if(err != 1) { + print_error(err); + return 0; + } + + // Get # of bytes waiting, no more than size of rx_buf + bw = OS.dvstat[1] * 256 + OS.dvstat[0]; + + if(bw > sizeof(rx_buf)) + bw = sizeof(rx_buf); + + if(bw > 0) { + err = nread(url, rx_buf, bw); + if(err != 1) { + ui_print("READ ERROR: "); + print_error(err); + return 0; + } + + trip = 0; + PIA.pactl |= 1; // Flag interrupt as serviced, ready for next one. + + irc_parse(); + } + + return 1; +} + +/* modern.ircdocs.horse say to do this IMMEDIATELY upon TCP + connection, without waiting for anything from the server. */ +void irc_register(void) { + txbuf_init(); + txbuf_append_str("USER "); + txbuf_append_str(usernick); /* local (UNIX) username, just use the nick */ + txbuf_append_str(" 0 * :FujiNetChat User\n"); /* "real" name (make it a pref?) */ + txbuf_send(); + + txbuf_init(); + txbuf_append_str("NICK "); + txbuf_append_str(usernick); + txbuf_append_str("\n"); + txbuf_send(); +} + +/* only exits on error (e.g. connection closed, which might be via /QUIT). */ +void irc_loop(void) { + while(1) { + if(!irc_read()) return; + + if(kbhit()) + if(joined) + ui_keystroke(); + else join_channel(); + } +} + +#else // !defined(__ATARI__) +/* parsetest */ +int main(int argc, char **argv) { + strcpy((char *)rx_buf, argv[1]); + bw = strlen(rx_buf); + irc_parse(); + /* + */ + return 0; +} +#endif diff --git a/src/irc.h b/src/irc.h new file mode 100644 index 0000000..77510d1 --- /dev/null +++ b/src/irc.h @@ -0,0 +1,59 @@ +#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) + +/**** main.c */ +extern char url[256]; +extern char usernick[32]; +extern char channel[32]; +extern unsigned char rx_buf[MAX_IRC_MSG_LEN]; +extern unsigned short bw; +extern unsigned char err; +extern unsigned char trip; + +extern unsigned int txbuflen; +extern char tx_buf[MAX_IRC_MSG_LEN]; + +/* clears the transmit buffer. */ +void txbuf_init(void); + +/* appends a string to the transmit buffer, updates txbuflen. */ +void txbuf_append_str(const char *str); + +/* clears the transmit buffer, then appends a string to it. */ +void txbuf_set_str(const char *str); + +/* sends whatever's in the transmit buffer, then clears it. if nothing was + in the buffer, nothing gets sent. */ +void txbuf_send(void); + +/* sends a string. clears transmit buffer first, then clears it again on exit. */ +void txbuf_send_str(const char *str); + +void print_error(unsigned char err); + +/**** irc.c */ +#define MAX_MSG_ARGS 8 +extern char *msg_src, *msg_cmd, *msg_dest, *msg_text; +extern char *msg_args[MAX_MSG_ARGS]; +extern int msg_argcount; + +/* call this once, right after TCP connection is established. */ +void irc_register(void); + +/* does all the work. doesn't return until we get disconnected from + the IRC server (via /quit or error). */ +void irc_loop(void); + +/**** ui.c */ +void ui_init(void); +void ui_start_msg(void); +void ui_end_msg(void); +void ui_keystroke(void); +void ui_print(const char *str); +void ui_putchar(char c); + +/**** cmd.c */ +void cmd_command(const char *cmd); diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..81f598e --- /dev/null +++ b/src/main.c @@ -0,0 +1,311 @@ +/* FujiNetChat, an IRC client. Based on NetCat and the old FujiChat. */ + +#define SELF "FujiNetChat" +#define VERSION "0.0" +#define BANNER SELF " v" VERSION " (B. Watson)\n" + +#define DEF_URL "N:TCP://irc.libera.chat:6667" +#define DEF_NICK "FNChatTest" +#define DEF_CHANNEL "##atari" + +#include <atari.h> +#include <stdbool.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <conio.h> // for kbhit() and cgetc() +#include "conio.h" // our local one. +#include "nio.h" +#include "irc.h" + +char url[256] = DEF_URL; // URL +char usernick[32] = DEF_NICK; +char tmp[8]; // temporary # to string +unsigned char err; // error code of last operation. +unsigned char trip=0; // if trip=1, fujinet is asking us for attention. +bool old_enabled=false; // were interrupts enabled for old vector +void* old_vprced; // old PROCEED vector, restored on exit. +unsigned short bw=0; // # of bytes waiting. +unsigned char rx_buf[MAX_IRC_MSG_LEN]; // RX buffer. +unsigned char tx_buf[MAX_IRC_MSG_LEN]; // TX buffer. +unsigned int txbuflen; // TX buffer length +char channel[32] = DEF_CHANNEL; + +/* TODO: user modes (default +iw), fg/bg color... */ + +extern void ih(); // defined in intr.s + +static void strcpy_to_eol(char *dst, const char *src) { + while(*src && (*src != CH_EOL)) { + *dst++ = *src++; + } + *dst = '\0'; +} + +/** + * Get URL from user. + */ +void get_config(void) { + OS.crsinh = 0; + + putchar(CH_CLR); + print(BANNER); + + while(1) { + print("\nURL ["); + print(url); + print("]?\n"); + get_line(tx_buf, sizeof(url) - 1); + if(tx_buf[0] != CH_EOL) strcpy_to_eol(url, tx_buf); + + print("Nick ["); + print(usernick); + print("]? "); + get_line(tx_buf, sizeof(usernick) - 1); + if(tx_buf[0] != CH_EOL) strcpy_to_eol(usernick, tx_buf); + + print("Channel ["); + print(channel); + print("]? "); + get_line(tx_buf, sizeof(channel) - 1); + if(tx_buf[0] != CH_EOL) strcpy_to_eol(channel, tx_buf); + + /* + print("\n\nURL: "); + print(url); + print("\nNick: "); + print(usernick); + print("\nChannel: "); + print(channel); + */ + + print("\n\nAre these settings OK [Y/n]? "); + if(tolower(cgetc()) != 'n') break; + } + + // print("Press Return to connect\n"); + // cgetc(); +} + +/** + * Print error + */ +void print_error(unsigned char err) { + itoa(err, tmp, 10); + print(tmp); + print("\n"); +} + +void txbuf_init(void) { + txbuflen = tx_buf[0] = 0; +} + +void txbuf_append_str(const char *str) { + while(*str) { + tx_buf[txbuflen++] = *str++; + } +} + +void txbuf_set_str(const char *str) { + txbuf_init(); + txbuf_append_str(str); +} + +void txbuf_send(void) { + if(!txbuflen) return; + nwrite(url, tx_buf, txbuflen); + txbuf_init(); +} + +void txbuf_send_str(const char *str) { + txbuf_init(); + txbuf_append_str(str); + txbuf_send(); +} + +int fn_connect(void) { + print("\n" "Connecting to: "); + print(url); + print("\n"); + + err = nopen(url, FNET_TRANSLATION); + + if(err != SUCCESS) { + print("Connection failed: "); + print_error(err); + return 0; + } + + // Open successful, set up interrupt + old_vprced = OS.vprced; // save the old interrupt vector + old_enabled = PIA.pactl & 1; // keep track of old interrupt state + PIA.pactl &= (~1); // Turn off interrupts before changing vector + OS.vprced = ih; // Set PROCEED interrupt vector to our interrupt handler. + PIA.pactl |= 1; // Indicate to PIA we are ready for PROCEED interrupt. + + return 1; +} + +void fn_disconnect(void) { + // Restore old PROCEED interrupt. + PIA.pactl &= ~1; // disable interrupts + OS.vprced=old_vprced; + PIA.pactl |= old_enabled; +} + +int main(void) { + OS.lmargn = 0; // Set left margin to 0 + OS.shflok = 0; // turn off shift-lock. + OS.soundr = 0; // Turn off SIO beeping sound + cursor(1); // Keep cursor on + + while(1) { + get_config(); + if(fn_connect()) { + irc_register(); + irc_loop(); + fn_disconnect(); + } + } + + OS.soundr = 3; // Restore SIO beeping sound + return 0; +} + +/* cruft from netcat: */ +/** + * Main entrypoint + */ +#if 0 +int main(int argc, char* argv[]) +{ + OS.soundr=0; // Turn off SIO beeping sound + cursor(1); // Keep cursor on + + while (running==true) + { + if (get_url(argc, argv)) + nc(); + else + running=false; + } + + OS.soundr=3; // Restore SIO beeping sound + return 0; +} +#endif + +#if 0 +/** + * NetCat + */ +void nc() +{ + OS.lmargn=0; // Set left margin to 0 + OS.shflok=0; // turn off shift-lock. + + // Attempt open. + print("\x9bOpening:\x9b"); + print(url); + print("\x9b"); + + err=nopen(url,trans); + + if (err != SUCCESS) + { + print("OPEN ERROR: "); + print_error(err); + return; + } + + // Open successful, set up interrupt + old_vprced = OS.vprced; // save the old interrupt vector + old_enabled = PIA.pactl & 1; // keep track of old interrupt state + PIA.pactl &= (~1); // Turn off interrupts before changing vector + OS.vprced = ih; // Set PROCEED interrupt vector to our interrupt handler. + PIA.pactl |= 1; // Indicate to PIA we are ready for PROCEED interrupt. + + // MAIN LOOP /////////////////////////////////////////////////////////// + + while (running==true) + { + // If key pressed, send it. + while (kbhit()) + { + tx_buf[txbuflen++]=cgetc(); + } + + if (txbuflen>0) + { + if (echo==true) + for (i=0;i<txbuflen;i++) + printc(&tx_buf[i]); + + err=nwrite(url,tx_buf,txbuflen); // Send character. + + if (err!=1) + { + print("WRITE ERROR: "); + print_error(err); + running=false; + continue; + } + txbuflen=0; + } + + if (trip==0) // is nothing waiting for us? + continue; + + // Something waiting for us, get status and bytes waiting. + err=nstatus(url); + + if (err==136) + { + print("DISCONNECTED.\x9b"); + running=false; + continue; + } + else if (err!=1) + { + print("STATUS ERROR: "); + print_error(err); + running=false; + continue; + } + + // Get # of bytes waiting, no more than size of rx_buf + bw=OS.dvstat[1]*256+OS.dvstat[0]; + + if (bw>sizeof(rx_buf)) + bw=sizeof(rx_buf); + + if (bw>0) + { + err=nread(url,rx_buf,bw); + + if (err!=1) + { + print("READ ERROR: "); + print_error(err); + running=false; + continue; + } + + // Print the buffer to screen. + printl(rx_buf,bw); + + trip=0; + PIA.pactl |= 1; // Flag interrupt as serviced, ready for next one. + } // if bw > 0 + } // while running + + // END MAIN LOOP /////////////////////////////////////////////////////// + + // Restore old PROCEED interrupt. + PIA.pactl &= ~1; // disable interrupts + OS.vprced=old_vprced; + PIA.pactl |= old_enabled; + +} +#endif diff --git a/src/nio.c b/src/nio.c new file mode 100644 index 0000000..1a5b14c --- /dev/null +++ b/src/nio.c @@ -0,0 +1,183 @@ +/** + * N: I/O + */ + +#include "nio.h" +#include "sio.h" +#include <atari.h> +#include <stddef.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; +} + +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 +} + +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. +} + +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 +} + +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. +} + +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. +} + +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; +} diff --git a/src/nio.h b/src/nio.h new file mode 100644 index 0000000..df992a2 --- /dev/null +++ b/src/nio.h @@ -0,0 +1,79 @@ +/** + * N: I/O + */ + +#ifndef NIO_H +#define NIO_H + +#define DFUJI 0x71 +#define DREAD 0x40 +#define DWRITE 0x80 +#define DUPDATE 0xC0 + +#define OREAD 0x04 +#define OWRITE 0x08 +#define OUPDATE 0x0C + +#define SUCCESS 1 +#define DERROR 144 + +#define DVSTAT_BYTES_WATING_LO 0 +#define DVSTAT_BYTES_WATING_HI 1 +#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); + +#endif /* NIO_H */ diff --git a/src/sio.h b/src/sio.h new file mode 100644 index 0000000..87b787e --- /dev/null +++ b/src/sio.h @@ -0,0 +1,7 @@ +/** + * Function to call sio + */ + +void siov(); +void rtclr(); +void cold_start(); diff --git a/src/sio.s b/src/sio.s new file mode 100644 index 0000000..3dd9191 --- /dev/null +++ b/src/sio.s @@ -0,0 +1,17 @@ + ;; 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 diff --git a/src/ui.c b/src/ui.c new file mode 100644 index 0000000..68f32a0 --- /dev/null +++ b/src/ui.c @@ -0,0 +1,73 @@ +#include <atari.h> +#include <stdio.h> +#include "irc.h" +#include <conio.h> +#include "conio.h" + +#define MAX_INPUT_LEN 119 + +static char input_buffer[MAX_INPUT_LEN + 1]; +static short inbuf_len = 0; + +void ui_init(void) { + OS.escflg = 0; + inbuf_len = input_buffer[0] = 0; +} + +void ui_start_msg() { + /* TODO: use msg_src and msg_dest to decide which window to + print to (when we have multi-window support) */ + OS.crsinh = 1; + putchar(CH_DELLINE); +} + +void ui_end_msg(void) { + OS.crsinh = 0; + // putchar(CH_EOL); // NO! + if(inbuf_len) print(input_buffer); +} + +void ui_print(const char *str) { + OS.escflg = 0x80; + print(str); + OS.escflg = 0; +} + +void ui_putchar(char c) { + putchar(c); +} + +void ui_keystroke(void) { + char c; + + OS.escflg = 0x80; + + /* pressing ctrl-3 (aka EOF) crashes cc65-compiled binaries *hard*, + so don't allow it. */ + if(OS.ch == (KEY_3 | KEY_CTRL)) { + OS.ch = KEY_NONE; + return; + } + + c = cgetc(); + + if(c == CH_EOL && !inbuf_len) + return; /* ignore empty message */ + + if(c == CH_DEL && inbuf_len) { + OS.escflg = 0; + putchar(c); + OS.escflg = 0x80; + input_buffer[inbuf_len--] = 0; + } else if(inbuf_len == MAX_INPUT_LEN) { + return; /* ignore */ + } else { + putchar(c); + input_buffer[inbuf_len++] = c; + input_buffer[inbuf_len] = 0; + if(c == CH_EOL) { + cmd_command(input_buffer); + ui_init(); + } + } +} |
