From 5dbaf9aab0c598580947e6554d5399cd00647b7c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 8 Jun 2018 20:17:27 +0530 Subject: [PATCH] Clean up usage of TTYIO for remote cmds and the shell --- kitty/cmds.py | 40 ++++++++++++++------------ kitty/remote_control.py | 62 ++++++++++++++--------------------------- kitty/shell.py | 6 ---- kitty/utils.py | 60 +++++++++++++++++---------------------- 4 files changed, 68 insertions(+), 100 deletions(-) diff --git a/kitty/cmds.py b/kitty/cmds.py index 76d64f3e9..68a20d02a 100644 --- a/kitty/cmds.py +++ b/kitty/cmds.py @@ -10,7 +10,6 @@ from .cli import parse_args from .config import parse_config, parse_send_text_bytes from .constants import appname from .tabs import SpecialWindow -from .utils import non_blocking_read class MatchError(ValueError): @@ -132,27 +131,32 @@ def cmd_send_text(global_opts, opts, args): limit = 1024 ret = {'match': opts.match, 'is_binary': False} - def pipe(src=sys.stdin): + def pipe(): ret['is_binary'] = True - import select - with non_blocking_read() as fd: + if sys.stdin.isatty(): + import select + fd = sys.stdin.fileno() keep_going = True while keep_going: - rd = select.select([fd], [], []) - if rd: - data = sys.stdin.buffer.read() - if not data: - break - data = data.decode('utf-8') - if '\x04' in data: - data = data[:data.index('\x04')] - keep_going = False - while data: - ret['text'] = data[:limit] - yield ret - data = data[limit:] - else: + rd = select.select([fd], [], [])[0] + if not rd: break + data = os.read(fd, limit) + if not data: + break # eof + data = data.decode('utf-8') + if '\x04' in data: + data = data[:data.index('\x04')] + keep_going = False + ret['text'] = data + yield ret + else: + while True: + data = sys.stdin.read(limit) + if not data: + break + ret['text'] = data[:limit] + yield ret def chunks(text): ret['is_binary'] = False diff --git a/kitty/remote_control.py b/kitty/remote_control.py index 7f7298530..7a13e7ab8 100644 --- a/kitty/remote_control.py +++ b/kitty/remote_control.py @@ -3,18 +3,14 @@ # License: GPL v3 Copyright: 2018, Kovid Goyal import json -import os import re import sys -import types from functools import partial -from io import DEFAULT_BUFFER_SIZE from .cli import emph, parse_args from .cmds import cmap, parse_subcommand_cli from .constants import appname, version -from .fast_data_types import close_tty, open_tty -from .utils import parse_address_spec, write_all +from .utils import TTYIO, parse_address_spec def handle_cmd(boss, window, cmd): @@ -66,7 +62,14 @@ class SocketIO: def send(self, data): import socket with self.socket.makefile('wb') as out: - out.write(data) + if isinstance(data, bytes): + out.write(data) + else: + for chunk in data: + if isinstance(chunk, str): + chunk = chunk.encode('utf-8') + out.write(chunk) + out.flush() self.socket.shutdown(socket.SHUT_WR) def recv(self, more_needed, timeout): @@ -78,41 +81,23 @@ class SocketIO: more_needed(data) -class TTYIO: - - def __enter__(self): - self.tty_fd, self.original_termios = open_tty() - - def __exit__(self, *a): - close_tty(self.tty_fd, self.original_termios) - - def send(self, data): - write_all(self.tty_fd, data) - - def recv(self, more_needed, timeout): - import select - from time import monotonic - fd = self.tty_fd - start_time = monotonic() - while timeout > monotonic() - start_time: - rd = select.select([fd], [], [], max(0, timeout - (monotonic() - start_time)))[0] - if rd: - data = os.read(fd, DEFAULT_BUFFER_SIZE) - if not data: - break # eof - if not more_needed(data): - break - else: - break - - def do_io(to, send, no_response): - send = encode_send(send) + payload = send.get('payload') + if payload is None or isinstance(payload, (str, bytes)): + send_data = encode_send(send) + else: + def send_generator(): + for chunk in payload: + send['payload'] = chunk + yield encode_send(send) + send_data = send_generator() + io = SocketIO(to) if to else TTYIO() with io: - io.send(send) + io.send(send_data) if no_response: return {'ok': True} + dcs = re.compile(br'\x1bP@kitty-cmd([^\x1b]+)\x1b\\') received = b'' @@ -168,11 +153,6 @@ def main(args): 'cmd': cmd, 'version': version, } - if func.no_response and isinstance(payload, types.GeneratorType): - for item in payload: - send['payload'] = item - do_io(global_opts.to, send, func.no_response) - return if payload is not None: send['payload'] = payload response = do_io(global_opts.to, send, func.no_response) diff --git a/kitty/shell.py b/kitty/shell.py index 60b91dafe..b4b8f26d3 100644 --- a/kitty/shell.py +++ b/kitty/shell.py @@ -7,7 +7,6 @@ import readline import shlex import sys import traceback -import types from functools import lru_cache from .cli import ( @@ -134,11 +133,6 @@ def run_cmd(global_opts, cmd, func, opts, items): 'cmd': cmd, 'version': version, } - if func.no_response and isinstance(payload, types.GeneratorType): - for item in payload: - send['payload'] = item - do_io(global_opts.to, send, func.no_response) - return if payload is not None: send['payload'] = payload response = do_io(global_opts.to, send, func.no_response) diff --git a/kitty/utils.py b/kitty/utils.py index ab29623a8..632e9cc30 100644 --- a/kitty/utils.py +++ b/kitty/utils.py @@ -10,14 +10,14 @@ import os import re import string import sys -from contextlib import contextmanager from time import monotonic from .constants import ( appname, is_macos, is_wayland, supports_primary_selection ) from .fast_data_types import ( - GLSL_VERSION, log_error_string, redirect_std_streams, x11_display + GLSL_VERSION, close_tty, log_error_string, open_tty, redirect_std_streams, + x11_display ) from .rgb import Color, to_color @@ -362,45 +362,35 @@ def write_all(fd, data): data = data[n:] -def make_fd_non_blocking(fd): - oldfl = fcntl.fcntl(fd, fcntl.F_GETFL) - fcntl.fcntl(fd, fcntl.F_SETFL, oldfl | os.O_NONBLOCK) - return oldfl +class TTYIO: + def __enter__(self): + self.tty_fd, self.original_termios = open_tty() + return self -@contextmanager -def non_blocking_read(src=sys.stdin, disable_echo=False): - import termios - import tty - import fcntl - fd = src.fileno() - if src.isatty(): - old = termios.tcgetattr(fd) - tty.setraw(fd) - if disable_echo: - new = list(old) - new[3] |= termios.ECHO - termios.tcsetattr(fd, termios.TCSANOW, new) - oldfl = make_fd_non_blocking(fd) - yield fd - if src.isatty(): - termios.tcsetattr(fd, termios.TCSADRAIN, old) - fcntl.fcntl(fd, fcntl.F_SETFL, oldfl) + def __exit__(self, *a): + close_tty(self.tty_fd, self.original_termios) + def send(self, data): + if isinstance(data, (str, bytes)): + write_all(self.tty_fd, data) + else: + for chunk in data: + write_all(self.tty_fd, chunk) -def read_with_timeout(more_needed, timeout=10, src=sys.stdin.buffer): - import select - start_time = monotonic() - with non_blocking_read(src) as fd: + def recv(self, more_needed, timeout): + from io import DEFAULT_BUFFER_SIZE + import select + fd = self.tty_fd + start_time = monotonic() while timeout > monotonic() - start_time: rd = select.select([fd], [], [], max(0, timeout - (monotonic() - start_time)))[0] - if rd: - data = src.read() - if not data: - break # eof - if not more_needed(data): - break - else: + if not rd: + break + data = os.read(fd, DEFAULT_BUFFER_SIZE) + if not data: + break # eof + if not more_needed(data): break