Merge pull request #48 from SamCoVT/master

Proposed fix for #47 Save and restore termios settings with helper functions in utils.console
This commit is contained in:
Mike Naberezny 2020-10-11 13:56:30 -07:00 committed by GitHub
commit 5ae7eeb6f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 210 additions and 46 deletions

View File

@ -53,28 +53,60 @@ class Monitor(cmd.Cmd):
self._width = 78
self.prompt = "."
self._add_shortcuts()
cmd.Cmd.__init__(self, stdin=stdin, stdout=stdout)
if argv is None:
argv = sys.argv
load, rom, goto = self._parse_args(argv)
# Save the current system input mode so it can be restored after
# after processing commands and before exiting.
console.save_mode(sys.stdin)
self._reset(self.mpu_type, self.getc_addr, self.putc_addr)
# Attempt to get a copy of stdin that is unbuffered on systems
# that support it. This allows for immediate response to
# typed input as well as pasted input. If unable to get an
# unbuffered version of stdin, the original version is returned.
self.unbuffered_stdin = console.get_unbuffered_stdin(stdin)
if load is not None:
self.do_load("%r" % load)
cmd.Cmd.__init__(self, stdin=self.unbuffered_stdin, stdout=stdout)
if goto is not None:
self.do_goto(goto)
# Check for any exceptions thrown during __init__ while
# processing the arguments.
try:
if argv is None:
argv = sys.argv
load, rom, goto = self._parse_args(argv)
self._reset(self.mpu_type, self.getc_addr, self.putc_addr)
if load is not None:
self.do_load("%r" % load)
if goto is not None:
self.do_goto(goto)
if rom is not None:
# load a ROM and run from the reset vector
self.do_load("%r top" % rom)
physMask = self._mpu.memory.physMask
reset = self._mpu.RESET & physMask
dest = self._mpu.memory[reset] + \
(self._mpu.memory[reset + 1] << self.byteWidth)
self.do_goto("$%x" % dest)
except:
# Restore input mode on any exception and then rethrow the
# exception.
console.restore_mode()
raise
def __del__(self):
try:
# Restore the input mode.
console.restore_mode()
# Close the unbuffered input file handle, if it exists.
if self.unbuffered_stdin != None:
if self.unbuffered_stdin != sys.stdin:
self.unbuffered_stdin.close()
except:
pass
if rom is not None:
# load a ROM and run from the reset vector
self.do_load("%r top" % rom)
physMask = self._mpu.memory.physMask
reset = self._mpu.RESET & physMask
dest = self._mpu.memory[reset] + \
(self._mpu.memory[reset + 1] << self.byteWidth)
self.do_goto("$%x" % dest)
def _parse_args(self, argv):
try:
@ -139,6 +171,9 @@ class Monitor(cmd.Cmd):
if not line.startswith("quit"):
self._output_mpu_status()
# Switch back to the previous input mode.
console.restore_mode()
return result
def _reset(self, mpu_type, getc_addr=0xF004, putc_addr=0xF001):
@ -467,6 +502,10 @@ class Monitor(cmd.Cmd):
mpu = self._mpu
mem = self._mpu.memory
# Switch to immediate (noncanonical) no-echo input mode on POSIX
# operating systems. This has no effect on Windows.
console.noncanonical_mode(self.stdin)
if not breakpoints:
while True:
mpu.step()
@ -483,6 +522,9 @@ class Monitor(cmd.Cmd):
self._output(msg % self._breakpoints.index(pc))
break
# Switch back to the previous input mode.
console.restore_mode()
def help_radix(self):
self._output("radix [H|D|O|B]")
self._output("Set default radix to hex, decimal, octal, or binary.")
@ -877,6 +919,7 @@ def main(args=None):
c.cmdloop()
except KeyboardInterrupt:
c._output('')
console.restore_mode()
if __name__ == "__main__":
main()

View File

@ -3,6 +3,22 @@ import sys
if sys.platform[:3] == "win":
import msvcrt
def get_unbuffered_stdin(stdin):
""" get_unbuffered_stdin returns the given stdin on Windows. """
return stdin
def save_mode(stdin):
""" save_mode is a no-op on Windows. """
return
def noncanonical_mode(stdin):
""" noncanonical_mode is a no-op on Windows. """
return
def restore_mode(stdin):
""" restore_mode is a no-op on Windows. """
return
def getch(stdin):
""" Read one character from the Windows console, blocking until one
is available. Does not echo the character. The stdin argument is
@ -24,51 +40,156 @@ if sys.platform[:3] == "win":
return ''
else:
import select
import os
import termios
import fcntl
import os
from select import select
oldattr = None
oldstdin = None
def get_unbuffered_stdin(stdin):
""" Attempt to get and return a copy of stdin that is
unbuffered. This allows for immediate response to typed input
as well as pasted input. If unable to get an unbuffered
version of stdin, return the original version.
"""
if stdin != None:
try:
# Reopen stdin with no buffer.
return os.fdopen(os.dup(stdin.fileno()), 'rb', 0)
except Exception as e:
print(e)
# Unable to reopen this file handle with no buffer.
# Just use the original file handle.
return stdin
else:
# If stdin is None, try using sys.stdin for input.
try:
# Reopen the system's stdin with no buffer.
return os.fdopen(os.dup(sys.stdin.fileno()), 'rb', 0)
except:
# If unable to get an unbuffered stdin, just return
# None, which is what we started with if we got here.
return None
def save_mode(stdin):
""" For operating systems that support it, save the original
input termios settings so they can be restored later. This
allows us to switch to noncanonical mode when software is
running in the simulator and back to the original mode when
accepting commands.
"""
# For non-Windows systems, save the original input settings,
# which will typically be blocking reads with echo.
global oldattr
global oldstdin
# When the input is not a pty/tty, this will fail.
# In that case, it's ok to ignore the failure.
try:
# Save the current terminal setup.
oldstdin = stdin
fd = stdin.fileno()
oldattr = termios.tcgetattr(fd)
except:
# Quietly ignore termios errors, such as stdin not being
# a tty.
pass
def noncanonical_mode(stdin):
"""For operating systems that support it, switch to noncanonical
mode. In this mode, characters are given immediately to the
program and no processing of editing characters (like backspace)
is performed. Echo is also turned off in this mode. The
previous input behavior can be restored with restore_mode.
"""
# For non-windows systems, switch to non-canonical
# and no-echo non-blocking-read mode.
try:
# Save the current terminal setup.
fd = stdin.fileno()
currentattr = termios.tcgetattr(fd)
# Switch to noncanonical (instant) mode with no echo.
newattr = currentattr[:]
newattr[3] &= ~termios.ICANON & ~termios.ECHO
# Switch to non-blocking reads with no timeout.
newattr[6][termios.VMIN] = 0
newattr[6][termios.VTIME] = 0
termios.tcsetattr(fd, termios.TCSANOW, newattr)
except:
# Quietly ignore termios errors, such as stdin not being
# a tty.
pass
def restore_mode():
"""For operating systems that support it, restore the previous
input mode.
"""
# Restore the previous input setup.
global oldattr
global oldstdin
try:
# Restore the original system stdin.
oldfd = oldstdin.fileno()
# If there is a previous setting, restore it.
if oldattr != None:
# Restore it on the original system stdin.
termios.tcsetattr(oldfd, termios.TCSANOW, oldattr)
except:
# Quietly ignore termios errors, such as stdin not being a tty.
pass
def getch(stdin):
""" Read one character from stdin, blocking until one is available.
Does not echo the character.
"""
fd = stdin.fileno()
oldattr = termios.tcgetattr(fd)
newattr = oldattr[:]
newattr[3] &= ~termios.ICANON & ~termios.ECHO
try:
termios.tcsetattr(fd, termios.TCSANOW, newattr)
char = stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSAFLUSH, oldattr)
# Try to get a character with a non-blocking read.
char = ''
noncanonical_mode(stdin)
# If we didn't get a character, ask again.
while char == '':
try:
# On OSX, calling read when no data is available causes the
# file handle to never return any future data, so we need to
# use select to make sure there is at least one char to read.
rd,wr,er = select([stdin], [], [], 0.01)
if rd != []:
char = stdin.read(1)
except KeyboardInterrupt:
# Pass along a CTRL-C interrupt.
raise
except:
pass
return char
def getch_noblock(stdin):
""" Read one character from stdin without blocking. Does not echo the
character. If no character is available, an empty string is returned.
"""
char = ''
fd = stdin.fileno()
oldterm = termios.tcgetattr(fd)
newattr = oldterm[:]
newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO
termios.tcsetattr(fd, termios.TCSANOW, newattr)
oldflags = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK)
# Using non-blocking read
noncanonical_mode(stdin)
try:
char = ''
r, w, e = select.select([fd], [], [], 0.1)
if r:
# On OSX, calling read when no data is available causes the
# file handle to never return any future data, so we need to
# use select to make sure there is at least one char to read.
rd,wr,er = select([stdin], [], [], 0.01)
if rd != []:
char = stdin.read(1)
if char == "\n":
char = "\r"
finally:
termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm)
fcntl.fcntl(fd, fcntl.F_SETFL, oldflags)
except KeyboardInterrupt:
# Pass along a CTRL-C interrupt.
raise
except:
pass
# Convert linefeeds to carriage returns.
if char != '' and ord(char) == 10:
char = '\r'
return char