Remote control: Fix kitty @ sometimes failing to read the response from kitty.

Fixes #614
This commit is contained in:
Kovid Goyal 2018-06-08 15:35:01 +05:30
parent 44b84ba295
commit 19721014d0
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
5 changed files with 123 additions and 41 deletions

View file

@ -80,6 +80,9 @@ Changelog
- Linux: Include a man page and the HTML docs when building the linux-package
- Remote control: Fix kitty @ sometimes failing to read the response from
kitty. (:iss:`614`)
0.10.1 [2018-05-24]
------------------------------

View file

@ -22,7 +22,7 @@
ALT, CTRL, PRESS, RELEASE, REPEAT, SHIFT, C, D, backspace_key,
decode_key_event, enter_key
)
from kitty.utils import screen_size_function
from kitty.utils import screen_size_function, write_all
from .handler import Handler
from .operations import init_state, reset_state
@ -41,16 +41,6 @@ def debug(*a, **kw):
fobj.flush()
def write_all(fd, data):
if isinstance(data, str):
data = data.encode('utf-8')
while data:
n = os.write(fd, data)
if not n:
break
data = data[n:]
class TermManager:
def __init__(self, input_fd, output_fd):

View file

@ -11,12 +11,15 @@
#include <unistd.h>
#undef _DARWIN_C_SOURCE
#endif
#define _DEFAULT_SOURCE
#include <termios.h>
#include "data-types.h"
#include "control-codes.h"
#include "modes.h"
#include <stddef.h>
#include <termios.h>
#include <signal.h>
#include <fcntl.h>
#ifdef WITH_PROFILER
#include <gperftools/profiler.h>
#endif
@ -103,7 +106,34 @@ stop_profiler(PyObject UNUSED *self) {
}
#endif
static PyObject*
open_tty(PyObject *self UNUSED, PyObject *args UNUSED) {
int fd = open("/dev/tty", O_RDWR | O_NONBLOCK | O_CLOEXEC | O_NOCTTY);
struct termios *termios_p = calloc(1, sizeof(struct termios));
if (!termios_p) return PyErr_NoMemory();
if (tcgetattr(fd, termios_p) != 0) { free(termios_p); PyErr_SetFromErrno(PyExc_OSError); return NULL; }
struct termios raw_termios = *termios_p;
cfmakeraw(&raw_termios);
raw_termios.c_cc[VMIN] = 0; raw_termios.c_cc[VTIME] = 0;
if (tcsetattr(fd, TCSAFLUSH, &raw_termios) != 0) { free(termios_p); PyErr_SetFromErrno(PyExc_OSError); return NULL; }
return Py_BuildValue("iN", fd, PyLong_FromVoidPtr(termios_p));
}
static PyObject*
close_tty(PyObject *self UNUSED, PyObject *args) {
PyObject *tp;
int fd;
if (!PyArg_ParseTuple(args, "iO!", &fd, &PyLong_Type, &tp)) return NULL;
struct termios *termios_p = PyLong_AsVoidPtr(tp);
tcsetattr(fd, TCSAFLUSH, termios_p);
free(termios_p);
close(fd);
Py_RETURN_NONE;
}
static PyMethodDef module_methods[] = {
{"open_tty", open_tty, METH_NOARGS, ""},
{"close_tty", close_tty, METH_VARARGS, ""},
{"set_iutf8", (PyCFunction)pyset_iutf8, METH_VARARGS, ""},
{"thread_write", (PyCFunction)cm_thread_write, METH_VARARGS, ""},
{"parse_bytes", (PyCFunction)parse_bytes, METH_VARARGS, ""},

View file

@ -3,15 +3,18 @@
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
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 .constants import appname, version
from .utils import parse_address_spec, read_with_timeout
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
def handle_cmd(boss, window, cmd):
@ -44,39 +47,85 @@ def encode_send(send):
return b'\x1bP' + send + b'\x1b\\'
class SocketIO:
def __init__(self, to):
self.family, self.address = parse_address_spec(to)[:2]
def __enter__(self):
import socket
self.socket = socket.socket(self.family)
self.socket.setblocking(True)
self.socket.connect(self.address)
def __exit__(self, *a):
import socket
self.socket.shutdown(socket.SHUT_RDWR)
self.socket.close()
def send(self, data):
import socket
with self.socket.makefile('wb') as out:
out.write(data)
self.socket.shutdown(socket.SHUT_WR)
def recv(self, more_needed, timeout):
# We dont bother with more_needed since the server will close the
# connection after transmission
self.socket.settimeout(timeout)
with self.socket.makefile('rb') as src:
data = src.read()
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):
import socket
send = encode_send(send)
if to:
family, address = parse_address_spec(to)[:2]
s = socket.socket(family)
s.connect(address)
out = s.makefile('wb')
else:
out = open('/dev/tty', 'wb')
with out:
out.write(send)
if to:
s.shutdown(socket.SHUT_WR)
if no_response:
return {'ok': True}
io = SocketIO(to) if to else TTYIO()
with io:
io.send(send)
if no_response:
return {'ok': True}
dcs = re.compile(br'\x1bP@kitty-cmd([^\x1b]+)\x1b\\')
received = b''
dcs = re.compile(br'\x1bP@kitty-cmd([^\x1b]+)\x1b\\')
match = None
received = b''
match = None
def more_needed(data):
nonlocal received, match
received += data
match = dcs.search(received)
return match is None
def more_needed(data):
nonlocal received, match
received += data
match = dcs.search(received)
return match is None
io.recv(more_needed, timeout=10)
if to:
src = s.makefile('rb')
else:
src = open('/dev/tty', 'rb')
with src:
read_with_timeout(more_needed, src=src)
if match is None:
raise SystemExit('Failed to receive response from ' + appname)
response = json.loads(match.group(1).decode('ascii'))

View file

@ -352,6 +352,16 @@ def parse_address_spec(spec):
return family, address, socket_path
def write_all(fd, data):
if isinstance(data, str):
data = data.encode('utf-8')
while data:
n = os.write(fd, data)
if not n:
break
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)