import alsaaudio
import atexit
+import fcntl
import math
import os
import select
+import struct
import sys
import termios
import time
0x60,
]
-# see tone.lst
+# see /util/tone.lst
TONE_REST = 0x300
TONE_TONE = 0x308
TONE_DURL = 0x309
TONE_FREQL = 0x30d
TONE_FREQH = 0x314
-# see lemonade_flash_patched.lst
+# see /lemonade/lemonade_flash_patched.lst
LEMONADE_FLASH_INIT = 0x3600
LEMONADE_FLASH_INIT_PATCHED = 0x95ef
LEMONADE_FLASH_EXECUTE = 0x3603
COUT_STATE_CTRL_A = 1
cout_state = COUT_STATE_NORMAL
+# see https://github.com/python/cpython/blob/3.10/Lib/tty.py
+TERMIOS_IFLAG = 0
+TERMIOS_OFLAG = 1
+TERMIOS_CFLAG = 2
+TERMIOS_LFLAG = 3
+TERMIOS_ISPEED = 4
+TERMIOS_OSPEED = 5
+TERMIOS_CC = 6
+
def init():
global termios_attr, pcm
if termios_attr is None and os.isatty(fd_in):
termios_attr = termios.tcgetattr(fd_in)
atexit.register(deinit)
- tty.setraw(fd_in)
+
+ # deep copy the termios structure
+ attr = list(termios_attr)
+ attr[TERMIOS_CC] = list(attr[TERMIOS_CC])
+
+ # see cfmakeraw() in termios man page
+ attr[TERMIOS_IFLAG] &= ~(
+ termios.IGNBRK |
+ termios.BRKINT |
+ termios.PARMRK |
+ termios.ISTRIP |
+ termios.INLCR |
+ termios.IGNCR |
+ termios.ICRNL |
+ termios.IXON
+ )
+ attr[TERMIOS_OFLAG] &= ~termios.OPOST
+ attr[TERMIOS_LFLAG] &= ~(
+ termios.ECHO |
+ termios.ECHONL |
+ termios.ICANON |
+ #termios.ISIG |
+ termios.IEXTEN
+ )
+ attr[TERMIOS_CFLAG] &= ~(termios.CSIZE | termios.PARENB);
+ attr[TERMIOS_CFLAG] |= termios.CS8;
+ # now customize
+ attr[TERMIOS_LFLAG] |= termios.ISIG
+ attr[TERMIOS_CC][termios.VINTR] = 3 # ctrl-c
+ termios.tcsetattr(fd_in, termios.TCSAFLUSH, attr)
+
# auto wrap off, hide cursor, reset attributes
write('\x1b[?7l\x1b[?25l\x1b[0m')
# note: reset to initial state clears the screen which I do not like,
# but it may be the only way to clear our change to the beep settings
#write('\x1bc\x1b[?7h\x1b[?25h\x1b[0m')
+
+ # without full reset: reset scrolling region (or user must run "reset")
+ s = struct.pack('HHHH', 0, 0, 0, 0)
+ t = fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, s)
+ h, w, _, _ = struct.unpack('HHHH', t)
+ set_scrolling_region(0, h)
+
+ # auto wrap on, show cursor, reset attributes
write('\x1b[?7h\x1b[?25h\x1b[0m')
termios.tcsetattr(fd_in, termios.TCSADRAIN, termios_attr)
ch_in = read(1)
if len(ch_in) == 0:
raise Exception('end of input') # due to piping or input redirection
- if ch_in == '\x03':
- raise Exception('user break')
if ch_in == '\x7f':
ch_in = '\b'
mem[HW_IOADR] = (ord(ch_in) & 0x7f) | 0x80
def clreop():
write('\x1b[J')
+def set_scrolling_region(y0, y1):
+ # save cursor, set scrolling region, restore cursor
+ write(f'\x1b[s\x1b[{y0 + 1:d};{y1:d}r\x1b[u')
+
def home():
# move cursor home, clear to end of screen
# just approximates proper behaviour of clearing the window rectangle
format = alsaaudio.PCM_FORMAT_U8,
periodsize = PCM_PERIODSIZE
)
- i = 0
- while i < len(buf):
- i += pcm.write(buf[i:])
- pcm.close()
+ try:
+ i = 0
+ while i < len(buf):
+ i += pcm.write(buf[i:])
+ finally:
+ pcm.close()
else:
assert False
elif addr == ZP_CV:
write(cv())
elif addr == ZP_WNDTOP or addr == ZP_WNDBTM:
- # save cursor, set scrolling region, restore cursor
- write(
- f'\x1b[s\x1b[{low_mem[ZP_WNDTOP] + 1:d};{low_mem[ZP_WNDBTM]:d}r\x1b[u'
- )
+ set_scrolling_region(low_mem[ZP_WNDTOP], low_mem[ZP_WNDBTM])
elif addr == ZP_INVFLG:
write(invflg())
else: