forked from lix-project/lix
78 lines
2.2 KiB
Python
78 lines
2.2 KiB
Python
|
#!/usr/bin/env python3
|
||
|
# SPDX-FileCopyrightText: 2001, 2002, 2003, 2004, 2005, 2006 Python Software Foundation; All Rights Reserved
|
||
|
# SPDX-FileCopyrightText: 2024 Jade Lovelace
|
||
|
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||
|
"""
|
||
|
This script exists to lose Lix a dependency on expect(1) for the ability to run
|
||
|
something in a pty.
|
||
|
|
||
|
Yes, it could be replaced by script(1) but macOS and Linux script(1) have
|
||
|
diverged sufficiently badly that even specifying a subcommand to run is not the
|
||
|
same.
|
||
|
"""
|
||
|
import pty
|
||
|
import sys
|
||
|
import os
|
||
|
from termios import ONLCR, ONLRET, ONOCR, OPOST, TCSAFLUSH, tcgetattr, tcsetattr
|
||
|
from tty import setraw
|
||
|
import termios
|
||
|
|
||
|
def setup_terminal():
|
||
|
# does not matter which fd we use because we are in a fresh pty
|
||
|
modi = tcgetattr(pty.STDOUT_FILENO)
|
||
|
[iflag, oflag, cflag, lflag, ispeed, ospeed, cc] = modi
|
||
|
|
||
|
# Turning \n into \r\n is not cool, Linux!
|
||
|
oflag &= ~ONLCR
|
||
|
# I don't know what "implementation dependent postprocessing means" but it
|
||
|
# sounds bad
|
||
|
oflag &= ~OPOST
|
||
|
# Assume that NL performs the role of CR; do not insert CRs at column 0
|
||
|
oflag |= ONLRET | ONOCR
|
||
|
|
||
|
modi = [iflag, oflag, cflag, lflag, ispeed, ospeed, cc]
|
||
|
|
||
|
tcsetattr(pty.STDOUT_FILENO, TCSAFLUSH, modi)
|
||
|
|
||
|
|
||
|
def spawn(argv: list[str]):
|
||
|
"""
|
||
|
As opposed to pty.spawn, this one more seriously controls the pty settings.
|
||
|
Necessary to turn off such fun functionality as onlcr (LF to CRLF).
|
||
|
|
||
|
This is essentially copy pasted from pty.spawn, since there is no way to
|
||
|
hook the child pre-execve
|
||
|
"""
|
||
|
pid, master_fd = pty.fork()
|
||
|
if pid == pty.CHILD:
|
||
|
setup_terminal()
|
||
|
os.execlp(argv[0], *argv)
|
||
|
|
||
|
try:
|
||
|
mode = tcgetattr(pty.STDIN_FILENO)
|
||
|
setraw(pty.STDIN_FILENO)
|
||
|
restore = True
|
||
|
except termios.error:
|
||
|
restore = False
|
||
|
|
||
|
try:
|
||
|
pty._copy(master_fd, pty._read, pty._read) # type: ignore
|
||
|
finally:
|
||
|
if restore:
|
||
|
tcsetattr(pty.STDIN_FILENO, TCSAFLUSH, mode) # type: ignore
|
||
|
|
||
|
os.close(master_fd)
|
||
|
return os.waitpid(pid, 0)[1]
|
||
|
|
||
|
|
||
|
def main():
|
||
|
if len(sys.argv) == 1:
|
||
|
print(f'Usage: {sys.argv[0]} [command args]', file=sys.stderr)
|
||
|
sys.exit(1)
|
||
|
|
||
|
sys.exit(os.waitstatus_to_exitcode(spawn(sys.argv[1:])))
|
||
|
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
main()
|