termios_attr = None
fd_in = sys.stdin.fileno()
fd_out = sys.stdout.fileno()
+ch_in = ''
poll_in = select.poll()
poll_in.register(fd_in, select.POLLIN)
mem = {
HW_PB1: 0,
HW_PB2: 0,
}
-gr_mem = [[0 for j in range(40)] for i in range(40)]
+gr_mem = [[0 for j in range(40)] for i in range(48)]
pdl_value = [255 for i in range(4)]
def init():
def in_hash(n):
pass
+# don't use Python buffered I/O facility for stdin, because we don't know of
+# a documented way to check the input buffer level, and without this we can't
+# meaningfully use poll() to check whether an input character is available
def read(n):
- return str(os.read(fd_in, 1), 'utf-8')
-
+ out = []
+ while len(out) < n:
+ # decode utf-8 character
+ _in = os.read(fd_in, 1)
+ if _in[0] & 0xe0 == 0xc0:
+ _in += os.read(fd__in, 1)
+ elif _in[0] & 0xf0 == 0xe0:
+ _in += os.read(fd_in, 2)
+ elif _in[0] & 0xf8 == 0xf0:
+ _in += os.read(fd_in, 3)
+ out.append(str(_in, 'utf-8'))
+ return ''.join(out)
+
+# don't use Python buffered I/O facility for stdout, because applesoft code
+# uses plot/print for animation, so we'd need to flush it every time anyway
def write(data):
os.write(fd_out, bytes(data, 'utf-8'))
# apple treats \r as \r\n, so if you write e.g. \r\n you'll get \r\n\n
crlf()
elif ord(ch) >= 0x20:
+ # some applications expect BASL, BASH = base address of current line
+ addr = bascalc(mem[ZP_CV])
+ mem[ZP_BASL] = addr & 0xff
+ mem[ZP_BASH] = addr >> 8
+ mem[addr + mem[ZP_CH]] = (ord(ch) & 0x7f) | 0x80
+
write(ch)
mem[ZP_CH] += 1
if mem[ZP_CH] >= mem[ZP_WNDLFT] + mem[ZP_WNDWTH]:
crlf()
-def get():
- # if there is a lookahead from PEEK of HW_IOADR, cancel and return it
- if mem[HW_IOADR] >= 0x80:
- mem[HW_IOADR] &= 0x7f
- return chr(HW_IOADR)
- ch = read(1)
- if len(ch) == 0:
+def get_internal():
+ global ch_in
+
+ ch_in = read(1)
+ if len(ch_in) == 0:
raise Exception('end of input') # due to piping or input redirection
- if ch == '\x03':
+ if ch_in == '\x03':
raise Exception('user break')
- if ch == '\x7f':
- ch = '\b'
+ if ch_in == '\x7f':
+ ch_in = '\b'
+ mem[HW_IOADR] = (ord(ch_in) & 0x7f) | 0x80
+ return
+
+def get():
+ global ch_in
+
+ if len(ch_in) == 0:
+ get_internal()
+ ch = ch_in
+ ch_in = ''
+ mem[HW_IOADR] &= 0x7f
return ch
def input():
write(f'\x1b[{y:d}d')
mem[ZP_CV] = y - 1
+def cleol():
+ write('\x1b[K')
+
def clreop():
write('\x1b[J')
def peek(addr):
addr &= 0xffff
if addr == HW_IOADR:
- if mem[HW_IOADR] < 0x80 and len(poll_in.poll(POLL_TIMEOUT_MS)):
- mem[HW_IOADR] = ord(read(1)) | 0x80
+ # sometimes the application ignores an invalid key and waits for a new one
+ #if len(ch_in) == 0 and len(poll_in.poll(POLL_TIMEOUT_MS)):
+ if len(poll_in.poll(POLL_TIMEOUT_MS)):
+ get_internal()
return mem.get(addr, 0)
def poke(addr, data):
+ global ch_in
+
addr &= 0xffff
- data &= 0xff
- mem[addr] = data
- if addr == ZP_WNDTOP or addr == ZP_WNDBTM:
- # save cursor, set scrolling region, restore cursor
- write(f'\x1b[s\x1b[{mem[ZP_WNDTOP] + 1:d};{mem[ZP_WNDBTM]:d}r\x1b[u')
+ if addr == HW_KBDSTRB:
+ ch_in = ''
+ mem[HW_IOADR] &= 0x7f
+ else:
+ data &= 0xff
+ mem[addr] = data
+ if addr == ZP_WNDTOP or addr == ZP_WNDBTM:
+ # save cursor, set scrolling region, restore cursor
+ write(f'\x1b[s\x1b[{mem[ZP_WNDTOP] + 1:d};{mem[ZP_WNDBTM]:d}r\x1b[u')
def call(addr):
global flash_color0
clreop()
elif addr == ROM_BELL2:
tone(1000, 100) # 1 kHz for .1 sec
+ elif addr == ROM_HOME:
+ home()
+ elif addr == ROM_CLEOL:
+ cleol()
else:
raise Exception(f'call {addr:04x}')
+def bascalc(y):
+ return 0x400 | ((y & 7) << 7) | ((y >> 3) * 40)
+
def text():
# save cursor, set scrolling region, restore cursor
write('\x1b[s\x1b1;24r\x1b[u')