Remote control: Fix kitty @ sometimes failing to read the response from kitty.
Fixes #614
This commit is contained in:
parent
44b84ba295
commit
19721014d0
5 changed files with 123 additions and 41 deletions
|
|
@ -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]
|
||||
------------------------------
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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, ""},
|
||||
|
|
|
|||
|
|
@ -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'))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in a new issue