Skip to content

Commit

Permalink
Merge pull request #41 from hartwork/improve-pty-handling
Browse files Browse the repository at this point in the history
Improve PTY handling
  • Loading branch information
hartwork authored Dec 16, 2023
2 parents ae0a1db + 8677601 commit bd9654a
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 8 deletions.
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ authors = [
requires-python = ">=3.9"
dependencies = [
"coloredlogs>=15.0.1",
"pexpect>=4.9.0",
]
keywords = ["Wine", "sandbox", "sandboxing", "bubblewrap", "bwrap"]
classifiers = [
Expand Down
10 changes: 2 additions & 8 deletions sandwine/_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

import logging
import os
import pty
import random
import shlex
import signal
Expand All @@ -34,6 +33,7 @@

import coloredlogs

from sandwine._pty import pty_spawn_argv
from sandwine._x11 import X11Display, X11Mode, create_x11_context, detect_and_require_nested_x11

_logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -435,13 +435,7 @@ def spawn_argv(argv: list[str], with_pty: bool) -> int:
if not with_pty:
return subprocess.call(argv)

wait_status = pty.spawn(argv)

exit_code = os.waitstatus_to_exitcode(wait_status)
if exit_code < 0: # e.g. -2 for "killed by SIGINT"
exit_code = 128 - exit_code # e.g. -2 -> 130

return exit_code
return pty_spawn_argv(argv)


def main():
Expand Down
68 changes: 68 additions & 0 deletions sandwine/_pty.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# This file is part of the sandwine project.
#
# Copyright (c) 2023 Sebastian Pipping <sebastian@pipping.org>
#
# sandwine is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# sandwine is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with sandwine. If not, see <https://www.gnu.org/licenses/>.

import fcntl
import os
import pty
import signal
import struct
import termios
from functools import partial
from unittest.mock import patch

import pexpect


def _copy_window_size(to_p: pexpect.spawn, from_fd: int):
if not os.isatty(from_fd):
return

s = struct.pack('HHHH', 0, 0, 0, 0)
a = struct.unpack('HHHH', fcntl.ioctl(from_fd, termios.TIOCGWINSZ, s))
if not to_p.closed:
to_p.setwinsize(a[0], a[1])


def _handle_sigwinch(p: pexpect.spawn, *_):
_copy_window_size(to_p=p, from_fd=pty.STDOUT_FILENO)


def pty_spawn_argv(argv):
# NOTE: This implementation is known to not be the real deal,
# e.g. Ctrl+Z will not work at all. It's just a cheap win over
# the previous implementation before the real deal.
p = pexpect.spawn(command=argv[0], args=argv[1:], timeout=None)

signal.signal(signal.SIGWINCH, partial(_handle_sigwinch, p))

_copy_window_size(p, from_fd=pty.STDOUT_FILENO)

if os.isatty(pty.STDIN_FILENO):
p.interact()
else:
with patch('tty.tcgetattr'), patch('tty.tcsetattr'), patch('tty.setraw'):
p.interact()

p.wait()
p.close()

if p.signalstatus is not None:
exit_code = 128 + p.signalstatus
else:
exit_code = p.exitstatus

return exit_code

0 comments on commit bd9654a

Please sign in to comment.