Make ctrl-c available all the time, improve cleanup to reset scrolling region
authorNick Downing <nick@ndcode.org>
Thu, 26 May 2022 08:20:06 +0000 (18:20 +1000)
committerNick Downing <nick@ndcode.org>
Thu, 26 May 2022 08:20:06 +0000 (18:20 +1000)
apple_io.py

index ba8cc4f..3218cc0 100644 (file)
@@ -1,8 +1,10 @@
 import alsaaudio
 import atexit
+import fcntl
 import math
 import os
 import select
+import struct
 import sys
 import termios
 import time
@@ -235,7 +237,7 @@ LITTLE_BRICK_OUT_TONE_DATA = [
   0x60,
 ]
 
-# see tone.lst
+# see /util/tone.lst
 TONE_REST = 0x300
 TONE_TONE = 0x308
 TONE_DURL = 0x309
@@ -243,7 +245,7 @@ TONE_DURH = 0x30b
 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
@@ -294,13 +296,52 @@ COUT_STATE_NORMAL = 0
 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')
 
@@ -312,6 +353,14 @@ def deinit():
     # 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)
@@ -419,8 +468,6 @@ def get_internal():
   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
@@ -485,6 +532,10 @@ def cleol():
 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
@@ -548,10 +599,12 @@ def tone(freq, dur): # Hz, s
       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
 
@@ -595,10 +648,7 @@ def poke(addr, data):
       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: