diff --git a/kitty/boss.py b/kitty/boss.py index 0f9373458..33a2d1649 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -2,15 +2,11 @@ # vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2016, Kovid Goyal -import os -import fcntl -import termios -import struct - from PyQt5.QtCore import QObject from .screen import Screen from .term import TerminalWidget +from .utils import resize_pty, hangup class Boss(QObject): @@ -20,7 +16,7 @@ class Boss(QObject): self.screen = Screen(opts, parent=self) self.term = TerminalWidget(opts, self.screen.linebuf, parent) self.term.relayout_lines.connect(self.relayout_lines) - self.master_fd, self.slave_fd = os.openpty() + resize_pty(self.screen.columns, self.screen.lines) def apply_opts(self, opts): self.screen.apply_opts(opts) @@ -28,9 +24,9 @@ class Boss(QObject): def relayout_lines(self, previous, cells_per_line, previousl, lines_per_screen): self.screen.resize(lines_per_screen, cells_per_line) - fcntl.ioctl(self.master_fd, termios.TIOCSWINSZ, struct.pack('4H', cells_per_line, lines_per_screen, 0, 0)) + resize_pty(cells_per_line, lines_per_screen) def shutdown(self): - os.close(self.slave_fd), os.close(self.master_fd) del self.master_fd del self.slave_fd + hangup() diff --git a/kitty/main.py b/kitty/main.py index 4979c3c0c..85ee9f49e 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -16,6 +16,7 @@ from PyQt5.QtWidgets import QApplication, QMainWindow, QMessageBox from .config import load_config, validate_font from .constants import appname, str_version, config_dir from .boss import Boss +from .utils import fork_child class MainWindow(QMainWindow): @@ -70,7 +71,7 @@ def option_parser(): a('--class', default=appname, dest='cls', help=_('Set the class part of the WM_CLASS property')) a('--config', default=os.path.join(config_dir, 'kitty.conf'), help=_('Specify a path to the config file to use')) a('--cmd', '-c', default=None, help=_('Run python code in the kitty context')) - a('--exec', '-e', default=pwd.getpwuid(os.geteuid()).pw_shell or '/bin/sh', help=_('Run the specified command instead of the shell')) + a('--exec', '-e', dest='child', default=pwd.getpwuid(os.geteuid()).pw_shell or '/bin/sh', help=_('Run the specified command instead of the shell')) a('-d', '--directory', default='.', help=_('Change to the specified directory when launching')) a('--version', action='version', version='{} {} by Kovid Goyal'.format(appname, '.'.join(str_version))) return parser @@ -81,8 +82,10 @@ def main(): if args.cmd: exec(args.cmd) return + # Ensure the child process gets no environment from Qt opts = load_config(args.config) - os.chdir(args.directory) + fork_child(args.child, args.directory, opts) + QApplication.setAttribute(Qt.AA_DisableHighDpiScaling, True) app = QApplication([appname]) app.setOrganizationName(args.cls) diff --git a/kitty/utils.py b/kitty/utils.py index ab8f2b8f5..302c6acf7 100644 --- a/kitty/utils.py +++ b/kitty/utils.py @@ -2,6 +2,12 @@ # vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2016, Kovid Goyal +import os +import termios +import struct +import shlex +import fcntl +import signal from functools import lru_cache from PyQt5.QtGui import QFontMetrics @@ -24,3 +30,48 @@ def set_current_font_metrics(fm: QFontMetrics, cw: int) -> None: global current_font_metrics, cell_width current_font_metrics, cell_width = fm, cw wcwidth.cache_clear() + + +def create_pty(): + if not hasattr(create_pty, 'master'): + create_pty.master, create_pty.slave = os.openpty() + fcntl.fcntl(create_pty.slave, fcntl.F_SETFD, fcntl.fcntl(create_pty.slave, fcntl.F_GETFD) & ~fcntl.FD_CLOEXEC) + return create_pty.master, create_pty.slave + + +def fork_child(cmd, cwd, opts): + argv = shlex.split(cmd) + master, slave = create_pty() + pid = os.fork() + if pid == 0: + try: + os.chdir(cwd) + except EnvironmentError: + os.chdir('/') + os.setsid() + for i in range(3): + os.dup2(slave, i) + os.close(slave), os.close(master) + # Establish the controlling terminal (see man 7 credentials) + os.close(os.open(os.ttyname(1), os.O_RDWR)) + os.environ['TERM'] = opts.term + os.environ['COLORTERM'] = 'truecolor' + os.execvp(argv[0], argv) + else: + os.close(slave) + fork_child.pid = pid + return pid + + +def resize_pty(w, h): + master = create_pty()[0] + fcntl.ioctl(master, termios.TIOCSWINSZ, struct.pack('4H', w, h, 0, 0)) + + +def hangup(): + if hasattr(fork_child, 'pid'): + pid = fork_child.pid + del fork_child.pid + pgrp = os.getpgid(pid) + os.killpg(pgrp, signal.SIGHUP) + os.close(create_pty()[0])