diff --git a/README.md b/README.md index 1629ee1..293e943 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,12 @@ CTRL-A is the escape character. CTRL-A + Q quits megacom. CTRL-A + CTRL-A sends there will be more keyboard shortcuts later, hopefully +### non-tty mode + +megacom can be run even if stdin is not a tty. in this mode, keyboard shortcuts (CTRL-A) are +disabled and input is passed through verbatim. this can be useful to pipe input and output out of a +UART device with programs that are not tty-aware + ### baud any standard baud rate (as an integer) which is supported by pyserial can be used. usually you want diff --git a/megacom/__init__.py b/megacom/__init__.py index e49db09..8636cdb 100644 --- a/megacom/__init__.py +++ b/megacom/__init__.py @@ -2,6 +2,8 @@ import argparse import asyncio import contextlib import errno +import fcntl +import os import re import signal import sys @@ -31,29 +33,39 @@ MODE_LOOKUP = { class TtyRaw: - __slots__ = ["isatty", "fd", "settings"] + __slots__ = ["isatty", "infd", "outfd", "settings"] isatty: bool - fd: int + infd: int + outfd: int settings: List[Any] def __init__(self) -> None: self.isatty = False - self.fd = 0 + self.infd = 0 + self.outfd = 0 self.settings = [] - def __enter__(self) -> None: + def __enter__(self) -> 'TtyRaw': if sys.stdin.isatty(): self.isatty = True - self.fd = sys.stdin.fileno() - self.settings = termios.tcgetattr(self.fd) - tty.setraw(self.fd) - return None + self.infd = sys.stdin.fileno() + self.outfd = sys.stdout.fileno() + self.settings = termios.tcgetattr(self.infd) + tty.setraw(self.infd) + return self def __exit__(self, exc_type: Optional[Type[BaseException]], exc_value: Optional[BaseException], exc_traceback: Optional[TracebackType]) -> Literal[False]: if self.isatty: - termios.tcsetattr(self.fd, termios.TCSADRAIN, self.settings) + termios.tcsetattr(self.infd, termios.TCSADRAIN, self.settings) + # unset nonblocking modes + flags = fcntl.fcntl(self.infd, fcntl.F_GETFL) + flags = flags & (~os.O_NONBLOCK) + fcntl.fcntl(self.infd, fcntl.F_SETFL, flags) + flags = fcntl.fcntl(self.outfd, fcntl.F_GETFL) + flags = flags & (~os.O_NONBLOCK) + fcntl.fcntl(self.outfd, fcntl.F_SETFL, flags) return False @@ -74,15 +86,21 @@ ESC_CHAR = b"\x01" class KeycodeHandler: - __slots__ = ["exit_flag", "esc"] + __slots__ = ["exit_flag", "esc", "isatty"] exit_flag: asyncio.Event esc: bool + isatty: bool - def __init__(self) -> None: + def __init__(self, isatty: bool) -> None: self.exit_flag = asyncio.Event() self.esc = False + self.isatty = isatty def process(self, byte: bytes) -> bytes: + # only translate or eat input if stdin is actually a tty + if not self.isatty: + return byte + if self.esc: self.esc = False if byte == b"q": @@ -104,7 +122,7 @@ class KeycodeHandler: return byte -async def megacom(tty: str, baud: int, mode: str, logfile: Optional[str]) -> None: +async def megacom(ttyraw: TtyRaw, tty: str, baud: int, mode: str, logfile: Optional[str]) -> None: (stdin, stdout) = await setup_async() m = MODE_RE.match(mode) @@ -124,7 +142,7 @@ async def megacom(tty: str, baud: int, mode: str, logfile: Optional[str]) -> Non sys.exit(1) loop = asyncio.get_event_loop() - keycodes = KeycodeHandler() + keycodes = KeycodeHandler(ttyraw.isatty) loop.add_signal_handler(signal.SIGINT, lambda: keycodes.exit_flag.set()) @@ -268,6 +286,6 @@ def main() -> None: parser.add_argument("-l", "--logfile", type=str, default="", help="file to log to") args = parser.parse_args() - with TtyRaw(): - asyncio.run(megacom(args.tty, args.baud, args.mode, + with TtyRaw() as ttyraw: + asyncio.run(megacom(ttyraw, args.tty, args.baud, args.mode, args.logfile if args.logfile != "" else None))