From 8a0471e45da16e7a677c4a2fcca251be1d58ee74 Mon Sep 17 00:00:00 2001 From: Sam Colwell Date: Sun, 4 Nov 2018 11:50:48 -0500 Subject: [PATCH 01/15] Move linux termios changes into Monitor._run --- py65/monitor.py | 28 ++++++++++++++++++++++++++++ py65/utils/console.py | 43 ++++++++++++++----------------------------- 2 files changed, 42 insertions(+), 29 deletions(-) diff --git a/py65/monitor.py b/py65/monitor.py index b63db62..9560fe8 100644 --- a/py65/monitor.py +++ b/py65/monitor.py @@ -467,6 +467,25 @@ class Monitor(cmd.Cmd): mpu = self._mpu mem = self._mpu.memory + if sys.platform[:3] != "win": + # For non-windows systems, switch to non-canonical + # and no-echo non-blocking-read mode. + import termios + + # Save the current terminal setup. + fd = self.stdin.fileno() + self.oldattr = termios.tcgetattr(fd) + + # Switch to noncanonical (instant) mode with no echo. + newattr = self.oldattr[:] + newattr[3] &= ~termios.ICANON & ~termios.ECHO + + # Switch to non-blocking reads. + newattr[6][termios.VMIN] = 0 + newattr[6][termios.VTIME] = 0 + termios.tcsetattr(fd, termios.TCSANOW, newattr) + + if not breakpoints: while True: mpu.step() @@ -483,6 +502,15 @@ class Monitor(cmd.Cmd): self._output(msg % self._breakpoints.index(pc)) break + if sys.platform[:3] != "win": + # For non-windows systems, switch back to canonical/echo + # mode. + import termios + # Restore the old input settings. + fd = self.stdin.fileno() + termios.tcsetattr(fd, termios.TCSANOW, self.oldattr) + + def help_radix(self): self._output("radix [H|D|O|B]") self._output("Set default radix to hex, decimal, octal, or binary.") diff --git a/py65/utils/console.py b/py65/utils/console.py index eb15df0..64a51a4 100644 --- a/py65/utils/console.py +++ b/py65/utils/console.py @@ -1,4 +1,5 @@ import sys +import time if sys.platform[:3] == "win": import msvcrt @@ -33,42 +34,26 @@ else: """ 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) + # Try to get a character with a non-blocking read. + char = stdin.read(1) + while char == '': + # If we don't get one right away, wait a bit and try again. + time.sleep(0.001) char = stdin.read(1) - finally: - termios.tcsetattr(fd, termios.TCSAFLUSH, oldattr) 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. """ - - 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) - - try: - char = '' - r, w, e = select.select([fd], [], [], 0.1) - if r: - char = stdin.read(1) - if char == "\n": - char = "\r" - finally: - termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm) - fcntl.fcntl(fd, fcntl.F_SETFL, oldflags) + char = '' + # Using non-blocking read as set up in Monitor._run + char = stdin.read(1) + if char == '': + # Don't use 100% CPU while waiting for input. + time.sleep(0.001) + if char == "\n": + char = "\r" return char From c2eb7897bc808373c26bf9cb884b2398a8a5e19d Mon Sep 17 00:00:00 2001 From: Sam Colwell Date: Sun, 4 Nov 2018 12:04:59 -0500 Subject: [PATCH 02/15] Change non-blocking reads to have 0.1 second timeout --- py65/monitor.py | 4 ++-- py65/utils/console.py | 8 ++------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/py65/monitor.py b/py65/monitor.py index 9560fe8..9e52bae 100644 --- a/py65/monitor.py +++ b/py65/monitor.py @@ -480,9 +480,9 @@ class Monitor(cmd.Cmd): newattr = self.oldattr[:] newattr[3] &= ~termios.ICANON & ~termios.ECHO - # Switch to non-blocking reads. + # Switch to non-blocking reads with 0.1 second timeout. newattr[6][termios.VMIN] = 0 - newattr[6][termios.VTIME] = 0 + newattr[6][termios.VTIME] = 1 termios.tcsetattr(fd, termios.TCSANOW, newattr) diff --git a/py65/utils/console.py b/py65/utils/console.py index 64a51a4..380377f 100644 --- a/py65/utils/console.py +++ b/py65/utils/console.py @@ -35,10 +35,8 @@ else: Does not echo the character. """ # Try to get a character with a non-blocking read. - char = stdin.read(1) + char = '' while char == '': - # If we don't get one right away, wait a bit and try again. - time.sleep(0.001) char = stdin.read(1) return char @@ -49,9 +47,7 @@ else: char = '' # Using non-blocking read as set up in Monitor._run char = stdin.read(1) - if char == '': - # Don't use 100% CPU while waiting for input. - time.sleep(0.001) + if char == "\n": char = "\r" return char From a705252440ec91f41e50580d679accc55f8d498e Mon Sep 17 00:00:00 2001 From: Sam Colwell Date: Wed, 7 Nov 2018 22:48:17 -0500 Subject: [PATCH 03/15] Moved platform-specific code into utils.console.py as functions --- py65/monitor.py | 32 +++++------------------ py65/utils/console.py | 61 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 26 deletions(-) diff --git a/py65/monitor.py b/py65/monitor.py index 9e52bae..05f7652 100644 --- a/py65/monitor.py +++ b/py65/monitor.py @@ -467,25 +467,10 @@ class Monitor(cmd.Cmd): mpu = self._mpu mem = self._mpu.memory - if sys.platform[:3] != "win": - # For non-windows systems, switch to non-canonical - # and no-echo non-blocking-read mode. - import termios - - # Save the current terminal setup. - fd = self.stdin.fileno() - self.oldattr = termios.tcgetattr(fd) - - # Switch to noncanonical (instant) mode with no echo. - newattr = self.oldattr[:] - newattr[3] &= ~termios.ICANON & ~termios.ECHO - - # Switch to non-blocking reads with 0.1 second timeout. - newattr[6][termios.VMIN] = 0 - newattr[6][termios.VTIME] = 1 - termios.tcsetattr(fd, termios.TCSANOW, newattr) - - + # 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() @@ -502,13 +487,8 @@ class Monitor(cmd.Cmd): self._output(msg % self._breakpoints.index(pc)) break - if sys.platform[:3] != "win": - # For non-windows systems, switch back to canonical/echo - # mode. - import termios - # Restore the old input settings. - fd = self.stdin.fileno() - termios.tcsetattr(fd, termios.TCSANOW, self.oldattr) + # Switch back to the previous input mode. + console.restore_mode(self.stdin) def help_radix(self): diff --git a/py65/utils/console.py b/py65/utils/console.py index 380377f..03595f9 100644 --- a/py65/utils/console.py +++ b/py65/utils/console.py @@ -1,9 +1,20 @@ import sys import time + + + if sys.platform[:3] == "win": import msvcrt + 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 @@ -30,14 +41,61 @@ else: import termios import fcntl + oldattr_stack = [ ] + + + 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 + """ + # For non-windows systems, switch to non-canonical + # and no-echo non-blocking-read mode. + + global oldattr_stack + + # Save the current terminal setup. + fd = stdin.fileno() + oldattr = termios.tcgetattr(fd) + oldattr_stack.append(oldattr) + + # Switch to noncanonical (instant) mode with no echo. + newattr = oldattr[:] + newattr[3] &= ~termios.ICANON & ~termios.ECHO + + # Switch to non-blocking reads with 0.1 second timeout. + newattr[6][termios.VMIN] = 0 + newattr[6][termios.VTIME] = 1 + termios.tcsetattr(fd, termios.TCSANOW, newattr) + + def restore_mode(stdin): + """For operating systems that support it, restore the previous + input mode. In this mode, a line can be entered and edited + until Enter is pressed, and then the line is passed to the + program for processing. + """ + + # Restore the previous input setup. + global oldattr_stack + fd = stdin.fileno() + # If there is a previous setting, restore it. + if oldattr_stack: + termios.tcsetattr(fd, termios.TCSANOW, oldattr_stack.pop()) + + + def getch(stdin): """ Read one character from stdin, blocking until one is available. Does not echo the character. """ # Try to get a character with a non-blocking read. char = '' + noncanonical_mode(stdin) while char == '': char = stdin.read(1) + restore_mode(stdin) return char def getch_noblock(stdin): @@ -45,8 +103,11 @@ else: character. If no character is available, an empty string is returned. """ char = '' + # Using non-blocking read as set up in Monitor._run + noncanonical_mode(stdin) char = stdin.read(1) + restore_mode(stdin) if char == "\n": char = "\r" From ea7ed5897996ef60f26fe16f23b20db5d129b895 Mon Sep 17 00:00:00 2001 From: Sam Colwell Date: Wed, 7 Nov 2018 23:30:42 -0500 Subject: [PATCH 04/15] Fix whitespace and comments. --- py65/utils/console.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/py65/utils/console.py b/py65/utils/console.py index 03595f9..3d41a3e 100644 --- a/py65/utils/console.py +++ b/py65/utils/console.py @@ -1,9 +1,6 @@ import sys import time - - - if sys.platform[:3] == "win": import msvcrt @@ -49,7 +46,7 @@ else: 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 + previous input behavior can be restored with restore_mode. """ # For non-windows systems, switch to non-canonical # and no-echo non-blocking-read mode. @@ -72,9 +69,7 @@ else: def restore_mode(stdin): """For operating systems that support it, restore the previous - input mode. In this mode, a line can be entered and edited - until Enter is pressed, and then the line is passed to the - program for processing. + input mode. """ # Restore the previous input setup. From b6e00ea1968857a64228ddcc1124501700b42048 Mon Sep 17 00:00:00 2001 From: Sam Colwell Date: Sat, 12 Jan 2019 12:33:00 -0500 Subject: [PATCH 05/15] Added fix from mlauke and restored input on ^C --- py65/monitor.py | 7 ++++ py65/utils/console.py | 80 +++++++++++++++++++++++++++++-------------- 2 files changed, 62 insertions(+), 25 deletions(-) diff --git a/py65/monitor.py b/py65/monitor.py index 05f7652..e31aa2a 100644 --- a/py65/monitor.py +++ b/py65/monitor.py @@ -55,6 +55,8 @@ class Monitor(cmd.Cmd): self._add_shortcuts() cmd.Cmd.__init__(self, stdin=stdin, stdout=stdout) + console.save_mode(self.stdin) + if argv is None: argv = sys.argv load, rom, goto = self._parse_args(argv) @@ -131,6 +133,7 @@ class Monitor(cmd.Cmd): result = cmd.Cmd.onecmd(self, line) except KeyboardInterrupt: self._output("Interrupt") + except Exception: (file, fun, line), t, v, tbinfo = compact_traceback() error = 'Error: %s, %s: file: %s line: %s' % (t, v, file, line) @@ -139,6 +142,9 @@ class Monitor(cmd.Cmd): if not line.startswith("quit"): self._output_mpu_status() + # Switch back to the previous input mode. + console.restore_mode(self.stdin) + return result def _reset(self, mpu_type, getc_addr=0xF004, putc_addr=0xF001): @@ -885,6 +891,7 @@ def main(args=None): c.cmdloop() except KeyboardInterrupt: c._output('') + console.restore_mode(c.stdin) if __name__ == "__main__": main() diff --git a/py65/utils/console.py b/py65/utils/console.py index 3d41a3e..cf6d905 100644 --- a/py65/utils/console.py +++ b/py65/utils/console.py @@ -4,6 +4,10 @@ import time if sys.platform[:3] == "win": import msvcrt + def save_mode(stdin): + """ get_mode is a no-op on Windows. """ + return + def noncanonical_mode(stdin): """ noncanonical_mode is a no-op on Windows. """ return @@ -38,8 +42,30 @@ else: import termios import fcntl - oldattr_stack = [ ] + oldattr = 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 + + # 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. + fd = stdin.fileno() + oldattr = termios.tcgetattr(fd) + except: + # Quietly ignore termios errors, such as stdin not being + # a tty. + print("DEBUG: Exception getting termios settings") + pass def noncanonical_mode(stdin): """For operating systems that support it, switch to noncanonical @@ -50,36 +76,39 @@ else: """ # 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 - global oldattr_stack - - # Save the current terminal setup. - fd = stdin.fileno() - oldattr = termios.tcgetattr(fd) - oldattr_stack.append(oldattr) + # Switch to non-blocking reads with 0.1 second timeout. + newattr[6][termios.VMIN] = 0 + newattr[6][termios.VTIME] = 1 + termios.tcsetattr(fd, termios.TCSANOW, newattr) + except: + # Quietly ignore termios errors, such as stdin not being + # a tty. + pass - # Switch to noncanonical (instant) mode with no echo. - newattr = oldattr[:] - newattr[3] &= ~termios.ICANON & ~termios.ECHO - - # Switch to non-blocking reads with 0.1 second timeout. - newattr[6][termios.VMIN] = 0 - newattr[6][termios.VTIME] = 1 - termios.tcsetattr(fd, termios.TCSANOW, newattr) - def restore_mode(stdin): """For operating systems that support it, restore the previous input mode. """ # Restore the previous input setup. - global oldattr_stack - fd = stdin.fileno() - # If there is a previous setting, restore it. - if oldattr_stack: - termios.tcsetattr(fd, termios.TCSANOW, oldattr_stack.pop()) - + global oldattr + try: + fd = stdin.fileno() + # If there is a previous setting, restore it. + if oldattr != None: + termios.tcsetattr(fd, 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. @@ -88,9 +117,11 @@ else: # 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 == '': char = stdin.read(1) - restore_mode(stdin) + # stdin has already been set up with a 0.1s delay, so we + # don't need an additional delay here. return char def getch_noblock(stdin): @@ -99,10 +130,9 @@ else: """ char = '' - # Using non-blocking read as set up in Monitor._run + # Using non-blocking read noncanonical_mode(stdin) char = stdin.read(1) - restore_mode(stdin) if char == "\n": char = "\r" From 1e1791652ec9421483548b550e198e6b5e12daa7 Mon Sep 17 00:00:00 2001 From: Sam Colwell Date: Sat, 12 Jan 2019 12:48:00 -0500 Subject: [PATCH 06/15] Comment fixups and remove unused import --- py65/monitor.py | 2 ++ py65/utils/console.py | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/py65/monitor.py b/py65/monitor.py index e31aa2a..1767729 100644 --- a/py65/monitor.py +++ b/py65/monitor.py @@ -55,6 +55,8 @@ class Monitor(cmd.Cmd): self._add_shortcuts() cmd.Cmd.__init__(self, stdin=stdin, stdout=stdout) + # Save the current input mode so it can be restored after + # after processing commands and before exiting. console.save_mode(self.stdin) if argv is None: diff --git a/py65/utils/console.py b/py65/utils/console.py index cf6d905..75c199c 100644 --- a/py65/utils/console.py +++ b/py65/utils/console.py @@ -1,11 +1,10 @@ import sys -import time if sys.platform[:3] == "win": import msvcrt def save_mode(stdin): - """ get_mode is a no-op on Windows. """ + """ save_mode is a no-op on Windows. """ return def noncanonical_mode(stdin): From d38831db1f7dab9d2b25a3055fec3dd86071b210 Mon Sep 17 00:00:00 2001 From: Sam Colwell Date: Sat, 12 Jan 2019 12:58:39 -0500 Subject: [PATCH 07/15] Removing unused imports --- py65/monitor.py | 2 -- py65/utils/console.py | 3 --- 2 files changed, 5 deletions(-) diff --git a/py65/monitor.py b/py65/monitor.py index 1767729..4a2d800 100644 --- a/py65/monitor.py +++ b/py65/monitor.py @@ -135,7 +135,6 @@ class Monitor(cmd.Cmd): result = cmd.Cmd.onecmd(self, line) except KeyboardInterrupt: self._output("Interrupt") - except Exception: (file, fun, line), t, v, tbinfo = compact_traceback() error = 'Error: %s, %s: file: %s line: %s' % (t, v, file, line) @@ -498,7 +497,6 @@ class Monitor(cmd.Cmd): # Switch back to the previous input mode. console.restore_mode(self.stdin) - def help_radix(self): self._output("radix [H|D|O|B]") self._output("Set default radix to hex, decimal, octal, or binary.") diff --git a/py65/utils/console.py b/py65/utils/console.py index 75c199c..59d4b56 100644 --- a/py65/utils/console.py +++ b/py65/utils/console.py @@ -36,10 +36,7 @@ if sys.platform[:3] == "win": return '' else: - import select - import os import termios - import fcntl oldattr = None From c2a64546cbab6a4690b1635b3785279cecb24c4e Mon Sep 17 00:00:00 2001 From: Sam Colwell Date: Sat, 12 Jan 2019 14:48:45 -0500 Subject: [PATCH 08/15] Add try block around stdin.read for cygwin --- py65/utils/console.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/py65/utils/console.py b/py65/utils/console.py index 59d4b56..3c7c67c 100644 --- a/py65/utils/console.py +++ b/py65/utils/console.py @@ -60,7 +60,6 @@ else: except: # Quietly ignore termios errors, such as stdin not being # a tty. - print("DEBUG: Exception getting termios settings") pass def noncanonical_mode(stdin): @@ -115,9 +114,15 @@ else: noncanonical_mode(stdin) # If we didn't get a character, ask again. while char == '': - char = stdin.read(1) - # stdin has already been set up with a 0.1s delay, so we - # don't need an additional delay here. + try: + char = stdin.read(1) + # stdin has already been set up with a 0.1s delay, so we + # don't need an additional delay here. + except KeyboardInterrupt: + # Pass along a CTRL-C interrupt. + raise + except: + pass return char def getch_noblock(stdin): @@ -128,7 +133,13 @@ else: # Using non-blocking read noncanonical_mode(stdin) - char = stdin.read(1) + try: + char = stdin.read(1) + except KeyboardInterrupt: + # Pass along a CTRL-C interrupt. + raise + except: + pass if char == "\n": char = "\r" From 5bebc72a569fc0330ef5fc80cfe6bb77bd2cf47a Mon Sep 17 00:00:00 2001 From: Sam Colwell Date: Sat, 12 Jan 2019 15:07:26 -0500 Subject: [PATCH 09/15] Adding try block in Monitor __init__ for cygwin --- py65/monitor.py | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/py65/monitor.py b/py65/monitor.py index 4a2d800..9f8226d 100644 --- a/py65/monitor.py +++ b/py65/monitor.py @@ -59,26 +59,35 @@ class Monitor(cmd.Cmd): # after processing commands and before exiting. console.save_mode(self.stdin) - if argv is None: - argv = sys.argv - load, rom, goto = self._parse_args(argv) + # Check for any exceptions thrown during __init__ while\ + # processing the arguments. + try: - self._reset(self.mpu_type, self.getc_addr, self.putc_addr) + if argv is None: + argv = sys.argv + load, rom, goto = self._parse_args(argv) - if load is not None: - self.do_load(load) + self._reset(self.mpu_type, self.getc_addr, self.putc_addr) - if goto is not None: - self.do_goto(goto) + if load is not None: + self.do_load(load) - 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) + 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(self.stdin) + raise def _parse_args(self, argv): try: From 0436bcde4b76af01243791081efa55a7bcabe59c Mon Sep 17 00:00:00 2001 From: Sam Colwell Date: Sat, 12 Jan 2019 15:45:15 -0500 Subject: [PATCH 10/15] clean up comments and indentation --- py65/monitor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/py65/monitor.py b/py65/monitor.py index 6ea44a4..57319b7 100644 --- a/py65/monitor.py +++ b/py65/monitor.py @@ -59,7 +59,7 @@ class Monitor(cmd.Cmd): # after processing commands and before exiting. console.save_mode(self.stdin) - # Check for any exceptions thrown during __init__ while\ + # Check for any exceptions thrown during __init__ while # processing the arguments. try: @@ -81,7 +81,7 @@ class Monitor(cmd.Cmd): physMask = self._mpu.memory.physMask reset = self._mpu.RESET & physMask dest = self._mpu.memory[reset] + \ - (self._mpu.memory[reset + 1] << self.byteWidth) + (self._mpu.memory[reset + 1] << self.byteWidth) self.do_goto("$%x" % dest) except: # Restore input mode on any exception and then rethrow the From 3a0fe6b33d6021f8ae5914a58ceec0e31ee0c2c4 Mon Sep 17 00:00:00 2001 From: Sam Colwell Date: Sat, 9 Feb 2019 11:20:12 -0500 Subject: [PATCH 11/15] Added fix for input on OSX --- py65/utils/console.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/py65/utils/console.py b/py65/utils/console.py index 3c7c67c..c44e0a8 100644 --- a/py65/utils/console.py +++ b/py65/utils/console.py @@ -37,6 +37,7 @@ if sys.platform[:3] == "win": else: import termios + from select import select oldattr = None @@ -115,9 +116,12 @@ else: # If we didn't get a character, ask again. while char == '': try: - char = stdin.read(1) - # stdin has already been set up with a 0.1s delay, so we - # don't need an additional delay here. + # 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 @@ -133,8 +137,14 @@ else: # Using non-blocking read noncanonical_mode(stdin) + try: - char = stdin.read(1) + # 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 From 0c78f33b432e1a5fbc34d1948b75d3a6cd229799 Mon Sep 17 00:00:00 2001 From: Sam Colwell Date: Sat, 9 Feb 2019 13:04:30 -0500 Subject: [PATCH 12/15] Trying new method to get unbuffered stdin --- py65/monitor.py | 27 ++++++++++++++++++++++++++- py65/utils/console.py | 4 ++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/py65/monitor.py b/py65/monitor.py index 57319b7..b9ea6d2 100644 --- a/py65/monitor.py +++ b/py65/monitor.py @@ -53,7 +53,32 @@ class Monitor(cmd.Cmd): self._width = 78 self.prompt = "." self._add_shortcuts() - cmd.Cmd.__init__(self, stdin=stdin, stdout=stdout) + + # Attempt to get 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, use the original version. + if stdin != None: + try: + # Reopen stdin with no buffer. + self.unbuffered_stdin = os.fdopen(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. + print("Error opening unbuffered stdin - using buffered version") + self.unbuffered_stdin = stdin + else: + # If stdin is None, try using sys.stdin for input. + try: + # Reopen the system's stdin with no buffer. + self.unbuffered_stdin = os.fdopen(sys.stdin.fileno(), 'rb', 0) + except Exception as e: + print(e) + print("Error opening default unbuffered stdin - using buffered version") + self.unbuffered_stdin = None + + cmd.Cmd.__init__(self, stdin=self.unbuffered_stdin, stdout=stdout) # Save the current input mode so it can be restored after # after processing commands and before exiting. diff --git a/py65/utils/console.py b/py65/utils/console.py index c44e0a8..de39da2 100644 --- a/py65/utils/console.py +++ b/py65/utils/console.py @@ -80,9 +80,9 @@ else: newattr = currentattr[:] newattr[3] &= ~termios.ICANON & ~termios.ECHO - # Switch to non-blocking reads with 0.1 second timeout. + # Switch to non-blocking reads with no timeout. newattr[6][termios.VMIN] = 0 - newattr[6][termios.VTIME] = 1 + newattr[6][termios.VTIME] = 0 termios.tcsetattr(fd, termios.TCSANOW, newattr) except: # Quietly ignore termios errors, such as stdin not being From 99b7f66070706aec914f0cfc2b20ef86df810870 Mon Sep 17 00:00:00 2001 From: Sam Colwell Date: Sat, 9 Feb 2019 13:53:34 -0500 Subject: [PATCH 13/15] Moved unbuffered stdin code to console.py --- py65/monitor.py | 30 ++++++------------------------ py65/utils/console.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/py65/monitor.py b/py65/monitor.py index b9ea6d2..e3f424b 100644 --- a/py65/monitor.py +++ b/py65/monitor.py @@ -54,31 +54,13 @@ class Monitor(cmd.Cmd): self.prompt = "." self._add_shortcuts() - # Attempt to get 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, use the original version. - if stdin != None: - try: - # Reopen stdin with no buffer. - self.unbuffered_stdin = os.fdopen(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. - print("Error opening unbuffered stdin - using buffered version") - self.unbuffered_stdin = stdin - else: - # If stdin is None, try using sys.stdin for input. - try: - # Reopen the system's stdin with no buffer. - self.unbuffered_stdin = os.fdopen(sys.stdin.fileno(), 'rb', 0) - except Exception as e: - print(e) - print("Error opening default unbuffered stdin - using buffered version") - self.unbuffered_stdin = None + # 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. + stdin = console.get_unbuffered_stdin(stdin) - cmd.Cmd.__init__(self, stdin=self.unbuffered_stdin, stdout=stdout) + cmd.Cmd.__init__(self, stdin=stdin, stdout=stdout) # Save the current input mode so it can be restored after # after processing commands and before exiting. diff --git a/py65/utils/console.py b/py65/utils/console.py index de39da2..21e6d47 100644 --- a/py65/utils/console.py +++ b/py65/utils/console.py @@ -3,6 +3,10 @@ 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 @@ -37,10 +41,37 @@ if sys.platform[:3] == "win": else: import termios + import os from select import select oldattr = 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(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. + print("Error opening unbuffered stdin - using buffered version") + 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(sys.stdin.fileno(), 'rb', 0) + except Exception as e: + print(e) + print("Error opening default unbuffered stdin - using buffered version") + 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 From 853968efbc49e55f27b0b3110a90bab2fc0a2abc Mon Sep 17 00:00:00 2001 From: Sam Colwell Date: Sat, 9 Feb 2019 16:41:27 -0500 Subject: [PATCH 14/15] Duping stdin fd before using fdopen --- py65/monitor.py | 32 ++++++++++++++++++++++---------- py65/utils/console.py | 23 ++++++++++++++--------- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/py65/monitor.py b/py65/monitor.py index e3f424b..8027e44 100644 --- a/py65/monitor.py +++ b/py65/monitor.py @@ -54,17 +54,17 @@ class Monitor(cmd.Cmd): self.prompt = "." self._add_shortcuts() + # Save the current system input mode so it can be restored after + # after processing commands and before exiting. + console.save_mode(sys.stdin) + # 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. - stdin = console.get_unbuffered_stdin(stdin) + self.unbuffered_stdin = console.get_unbuffered_stdin(stdin) - cmd.Cmd.__init__(self, stdin=stdin, stdout=stdout) - - # Save the current input mode so it can be restored after - # after processing commands and before exiting. - console.save_mode(self.stdin) + cmd.Cmd.__init__(self, stdin=self.unbuffered_stdin, stdout=stdout) # Check for any exceptions thrown during __init__ while # processing the arguments. @@ -93,9 +93,21 @@ class Monitor(cmd.Cmd): except: # Restore input mode on any exception and then rethrow the # exception. - console.restore_mode(self.stdin) + 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 + + def _parse_args(self, argv): try: shortopts = 'hi:o:m:l:r:g:' @@ -160,7 +172,7 @@ class Monitor(cmd.Cmd): self._output_mpu_status() # Switch back to the previous input mode. - console.restore_mode(self.stdin) + console.restore_mode() return result @@ -511,7 +523,7 @@ class Monitor(cmd.Cmd): break # Switch back to the previous input mode. - console.restore_mode(self.stdin) + console.restore_mode() def help_radix(self): self._output("radix [H|D|O|B]") @@ -907,7 +919,7 @@ def main(args=None): c.cmdloop() except KeyboardInterrupt: c._output('') - console.restore_mode(c.stdin) + console.restore_mode() if __name__ == "__main__": main() diff --git a/py65/utils/console.py b/py65/utils/console.py index 21e6d47..de6f61d 100644 --- a/py65/utils/console.py +++ b/py65/utils/console.py @@ -45,6 +45,7 @@ else: from select import select oldattr = None + oldstdin = None def get_unbuffered_stdin(stdin): """ Attempt to get and return a copy of stdin that is @@ -55,21 +56,20 @@ else: if stdin != None: try: # Reopen stdin with no buffer. - return os.fdopen(stdin.fileno(), 'rb', 0) + 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. - print("Error opening unbuffered stdin - using buffered version") 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(sys.stdin.fileno(), 'rb', 0) - except Exception as e: - print(e) - print("Error opening default unbuffered stdin - using buffered version") + 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): @@ -82,11 +82,13 @@ else: # 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: @@ -120,19 +122,22 @@ else: # a tty. pass - def restore_mode(stdin): + def restore_mode(): """For operating systems that support it, restore the previous input mode. """ # Restore the previous input setup. global oldattr + global oldstdin try: - fd = stdin.fileno() + # Restore the original system stdin. + oldfd = oldstdin.fileno() # If there is a previous setting, restore it. if oldattr != None: - termios.tcsetattr(fd, termios.TCSANOW, oldattr) + # 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 From 631f7eda744ca02bd0d239c8ef31b0bba0cbd00a Mon Sep 17 00:00:00 2001 From: Sam Colwell Date: Sat, 9 Feb 2019 21:36:38 -0500 Subject: [PATCH 15/15] Fix for linefeeds on python 3.x --- py65/utils/console.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/py65/utils/console.py b/py65/utils/console.py index de6f61d..0678422 100644 --- a/py65/utils/console.py +++ b/py65/utils/console.py @@ -187,8 +187,9 @@ else: except: pass - if char == "\n": - char = "\r" + # Convert linefeeds to carriage returns. + if char != '' and ord(char) == 10: + char = '\r' return char