import termios
import tty
+# see https://www.tinaja.com/ebooks/tearing_rework.pdf
+ZP_WNDLFT = 0x20 # Left side of scroll window
+ZP_WNDWTH = 0x21 # Width of scroll window
+ZP_WNDTOP = 0x22 # Top of scroll window
+ZP_WNDBTM = 0x23 # Bottom of scroll window
+ZP_CH = 0x24 # Cursor horizontal position
+ZP_CV = 0x25 # Cursor vertical position
+ZP_GBASL = 0x21 # LORES graphics base low
+ZP_GBASH = 0x27 # LORES graphics base high
+ZP_BASL = 0x28 # TEXT base address low
+ZP_BASH = 0x29 # TEXT base address high
+ZP_BAS2L = 0x2A # Scroll temporary base low
+ZP_BAS2H = 0x2B # Scroll temporary base high
+ZP_COLOR = 0x30 # Holds the LORES color value
+ZP_INVFLG = 0x32 # Normal/Inverse/Flash mask
+ZP_PROMPT = 0x33 # Holds prompt symbol
+ZP_YSAV = 0x34 # Temporary Y register hold
+ZP_CSWL = 0x36 # Output character hook low
+ZP_CSWH = 0x37 # Output character hook high
+ZP_KSWL = 0x38 # Input character hook low
+ZP_KSWH = 0x39 # Input character hook high
+ZP_ACC = 0x45 # Accumulator save
+ZP_XREG = 0x46 # X register save
+ZP_YREG = 0x47 # Y register save
+ZP_STATUS = 0x48 # Flag register save
+ZP_SPNT = 0x49 # Stack pointer save
+ZP_RNDL = 0x4E # Keybounce random number low
+ZP_RNDH = 0x4F # Keybounce random number high
+VP_REENTER = 0x03D0 # Re-enter DOS
+VP_RECONNECT = 0x03EA # Reconnect DOS I/O hooks
+VP_BRKV = 0x03F0 # Break vector address
+VP_SOFTEV = 0x03F2 # Warm start vector address
+VP_PWRDUP = 0x03F4 # Warm start EOR A5 checksum
+VP_AMPERV = 0x03F6 # Applesoft "&" Jump Code
+VP_USRADR = 0x03F8 # Control Y vector Jump Code
+VP_NMI = 0x03FB # NMI vector Jump Code
+VP_IRQLOC = 0x03FE # Interrupt vector address
+HW_IOADR = 0xC000 # Keyboard input location
+HW_KBDSTRB = 0xC010 # Keyboard strobe reset
+HW_TAPEOUT = 0xC020 # Cassette data output
+HW_SPKR = 0xC030 # Speaker click output
+HW_STROBE = 0xC040 # Game I/O connector strobe
+HW_TXTCLR = 0xC050 # Graphics ON soft switch
+HW_TXTSET = 0xC051 # Text ON soft switch
+HW_MIXCLR = 0xC052 # Full screen ON soft switch
+HW_MIXSET = 0xC053 # Split screen ON soft switch
+HW_LOWSCR = 0xC054 # Page ONE display soft switch
+HW_HISCR = 0xC055 # Page TWO display soft switch
+HW_LORES = 0xC056 # LORES ON soft switch
+HW_HIRES = 0xC057 # HIRES ON soft switch
+HW_AN0CLR = 0xC058 # Annunciator 0 OFF soft switch
+HW_AN0SET = 0xC059 # Annunciator 0 ON soft switch
+HW_AN1CLR = 0xC05A # Annunciator 1 OFF soft switch
+HW_AN1SET = 0xC05B # Annunciator 1 ON soft switch
+HW_AN2CLR = 0xC05C # Annunciator 2 OFF soft switch
+HW_AN2SET = 0xC05D # Annunciator 2 ON soft switch
+HW_AN3CLR = 0xC05E # Annunciator 3 OFF soft switch
+HW_AN3SET = 0xC05F # Annunciator 3 ON soft switch
+HW_TAPEIN = 0xC060 # Cassette tape read input
+HW_PB0 = 0xC061 # Push button 0 input
+HW_PB1 = 0xC062 # Push button 1 input
+HW_PB2 = 0xC063 # Push button 2 input
+HW_PDL0 = 0xC064 # Game Paddle 0 analog input
+HW_PDL1 = 0xC065 # Game Paddle 1 analog input
+HW_PDL2 = 0xC066 # Game Paddle 2 analog input
+HW_PDL3 = 0xC067 # Game Paddle 3 analog input
+ROM_PLOT = 0xF800 # Plot a block on LORES screen
+ROM_HLINE = 0xF819 # Draw a horizontal LORES line
+ROM_VLINE = 0xF828 # Draw a vertical LORES line
+ROM_CLRSCR = 0xF832 # Clear full LORES screen
+ROM_CLRTOP = 0xF836 # Clear top of LORES screen
+ROM_GBASCALC = 0xF847 # Calculate LORES base address
+ROM_NEXTCOL = 0xF85F # Increase LORES color by three
+ROM_SETCOL = 0xF864 # Set color for LORES plotting
+ROM_SCRN = 0xF871 # Read color of LORES screen
+ROM_PRNTAX = 0xF941 # Output A then X as hex
+ROM_PRBLNK = 0xF948 # Output three spaces via hooks
+ROM_PRBL2 = 0xF94A # Output X spaces via hooks
+ROM_STEP = 0xFA43 # Single step (old ROM only!)
+ROM_REGDSP = 0xFAD7 # Display working registers
+ROM_PREAD = 0xFB1E # Read a game paddle
+ROM_INIT = 0xFB2F # Initial i ze text screen
+ROM_SETTXT = 0xFB39 # Set up text screen
+ROM_SETGR = 0xFB40 # Setup LORES screen
+ROM_SETWND = 0xFB4B # Set text window to normal
+ROM_BASCALC = 0xFBC1 # Calculate text base address
+ROM_BELL = 0xFBD9 # 1 Beep speaker if ctrl G
+ROM_BELL2 = 0xFBE4 # Beep speaker once
+ROM_ADVANCE = 0xFBF4 # Move text cursor right by one
+ROM_VIDOUT = 0xFBFD # Output ASCII to screen only
+ROM_BS = 0xFC10 # Backspace screen
+ROM_UP = 0xFC1A # Move screen cursor up one
+ROM_VTAB = 0xFC22 # Vertical screen tab using CV
+ROM_VTABZ = 0xFC24 # Vertical screen tab using A
+ROM_ESCl = 0xFC2C # Process escape movements A-G
+ROM_CLREOP = 0xFC42 # Clear text to end of screen
+ROM_HOME = 0xFC58 # Clear screen and home cursor
+ROM_CR = 0xFC62 # Carriage return to screen
+ROM_LF = 0xFC66 # Line feed to screen onIy
+ROM_SCROLL = 0xFC70 # Scroll text screen up one
+ROM_CLEOL = 0xFC9C # Clear text to end of line
+ROM_WAIT = 0xFCA8 # Time delay set by accumulator
+ROM_RDKEY = 0xFD0C # Get input character via hooks
+ROM_KEYIN = 0xFD1B # Read the Apple keyboard
+ROM_RDCHAR = 0xFD35 # Get key and process ESC A-F
+ROM_CANCEL = 0xFD62 # Cancel keyboard line entry
+ROM_GETLNZ = 0xFD67 # CR, then get kbrl input line
+ROM_GETLN = 0xFD6A # Get input line from keyboard
+ROM_GETLN1 = 0xFD6F # Get kbd input, no prompt
+ROM_CROUT1 = 0xFD8B # Clear EOL then CR via hooks
+ROM_CROUT = 0xFD8E # Output return via hooks
+ROM_PRBYTE = 0xFDDA # Output full A in hpxto hooks
+ROM_PRHEX = 0xFDE3 # Output low A in hex to hooks
+ROM_COUT = 0xFDED # Output character via hooks
+ROM_COUT1 = 0xFDF0 # Output character to screen
+ROM_MOVE = 0xFE2C # Move block of memory
+ROM_VERIFY = 0xFE36 # Verify block of memory
+ROM_LIST = 0xFE5E # Disassemble 20 instructions
+ROM_L1ST2 = 0xFE63 # Disassemble A instructions
+ROM_SETINV = 0xFE80 # Print inverse text on screen
+ROM_SETNORM = 0xFE84 # Print normal text on screen
+ROM_SETVID = 0xFE93 # Grab output hooks for screen
+ROM_XBASIC = 0xFEB0 # Goto BASIC, destroying old
+ROM_BASCON = 0xFEB3 # Goto BASIC continuing old
+ROM_TRACE = 0xFEC2 # Start tracing (old ROM only!)
+ROM_WRITE = 0xFECD # Save to cassette tape
+ROM_READ = 0xFEFD # Read from cassette tape
+ROM_PRERR = 0xFF2D # Print "ERR" to output hook
+ROM_BELL = 0xFF3A # Output bell via hooks
+ROM_IORESR = 0xFF3F # Restore all working register
+ROM_IOSAVE = 0xFF4A # Save all working registers
+ROM_OLDRST = 0xFF59 # Old reset entry, no autostart
+ROM_MON = 0xFF65 # Enter monitor and beep spkr
+
# global state
attr = None
fd_in = sys.stdin.fileno()
fd_out = sys.stdout.fileno()
poll_in = select.poll()
poll_in.register(fd_in, select.POLLIN)
-mem = {}
+mem = {
+ ZP_CH: 0,
+ ZP_CV: 0
+}
def init():
global attr
def write(data):
os.write(fd_out, bytes(data, 'ascii'))
-def cr():
- # apple treats \r as \r\n, so if you write e.g. \r\n you'll get \r\n\n
- write('\r\n')
+def _print(data):
+ for ch in data:
+ if ch == '\r':
+ # apple treats \r as \r\n, so if you write e.g. \r\n you'll get \r\n\n
+ write('\r\n')
+ if mem[ZP_CV] < 23:
+ mem[ZP_CV] += 1
+ mem[ZP_CH] = 0
+ else:
+ write(ch)
+ if ch == '\a':
+ pass
+ elif ch == '\b':
+ if mem[ZP_CH] > 0:
+ mem[ZP_CH] -= 1
+ elif mem[ZP_CH] < 39:
+ mem[ZP_CH] += 1
+ else:
+ if mem[ZP_CV] < 23:
+ mem[ZP_CV] += 1
+ mem[ZP_CH] = 0
def get():
- data = read(1)
- if len(data) == 0:
+ ch = read(1)
+ if len(ch) == 0:
raise Exception('end of input') # due to piping or input redirection
- if data == '\x7f':
- data = '\b'
- return data
+ if ch == '\x7f':
+ ch = '\b'
+ return ch
def input():
line = ''
while True:
- key = get()
- _print(key)
- if key == '\r':
+ ch = get()
+ _print(ch)
+ if ch == '\r':
return line
- if key == '\b':
+ if ch == '\b':
if len(line):
- write(' \b')
+ _prnt(' \b')
line = line[:-1]
else:
# strictly, apple only does this if cursor not at left, maybe
# because it knows the echoed backspace has no effect at left
- cr()
+ _print('\r')
else:
if len(line) >= 247:
- write('\a')
+ _print('\a')
if len(line) >= 255:
- cr()
+ _print('\r')
line = ''
continue
- line += key
-
-def _print(data):
- # basically write(data.replace('\r', '\r\n')) but may be more responsive
- lines = data.split('\r')
- for line in lines[:-1]:
- write(line)
- cr()
- write(lines[-1])
+ line += ch
def htab(x):
write(f'\x1b[{x:d}G')
+ mem[ZP_CH] = x - 1
def vtab(y):
write(f'\x1b[{y:d}d')
+ mem[ZP_CV] = y - 1
def clreop():
write('\x1b[J')
def home():
write('\x1b[H\x1b[2J')
+ mem[ZP_CH] = 0
+ mem[ZP_CV] = 0
def normal():
write('\x1b[0m')
write(f'\x1b7\x1b[10;{freq:d}]\x1b[11;{dur:d}]\x07\x1b8')
def peek(addr):
- addr &= 0xffff
- return mem.get(addr, 0)
+ return mem.get(addr & 0xffff, 0)
def poke(addr, data):
- addr &= 0xffff
- data &= 0xff
- if data:
- mem[addr] = data
- elif addr in mem:
- del mem[addr]
+ mem[addr & 0xffff] = data & 0xff
def call(addr):
addr &= 0xffff
def vlin(y0, y1, x):
pass
+def tab(x):
+ x -= 1 + mem[ZP_CH]
+ return ' ' * x if x >= 1 else ''
+
if __name__ == '__main__':
init()
while True: