aboutsummaryrefslogtreecommitdiffstats
path: root/termcast_client
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2014-09-22 17:59:28 -0400
committerJesse Luehrs <doy@tozt.net>2014-09-22 17:59:28 -0400
commit674cf3d56349bed44380db2cb72529f797d7ee44 (patch)
treecc6b7ec2b44cf6c15e4b83ef951d508e6f4175e0 /termcast_client
parent19c854d15fdf9034f4fe7bfa061d5f2bcbd43f52 (diff)
downloadpython-termcast-client-674cf3d56349bed44380db2cb72529f797d7ee44.tar.gz
python-termcast-client-674cf3d56349bed44380db2cb72529f797d7ee44.zip
move some things around
Diffstat (limited to 'termcast_client')
-rw-r--r--termcast_client/__init__.py97
-rw-r--r--termcast_client/pity.py94
2 files changed, 191 insertions, 0 deletions
diff --git a/termcast_client/__init__.py b/termcast_client/__init__.py
new file mode 100644
index 0000000..64e6c2a
--- /dev/null
+++ b/termcast_client/__init__.py
@@ -0,0 +1,97 @@
+import argparse
+import json
+import os
+import pity
+import shutil
+import signal
+import socket
+import sys
+
+class Client(object):
+ def __init__(self, host, port, username, password):
+ self.host = host
+ self.port = port
+ self.username = username
+ self.password = password
+
+ def run(self, argv):
+ sock = socket.socket()
+ sock.connect((self.host, self.port))
+ sock.send(self._build_connection_string())
+ self.winch_set = False
+ pity.spawn(
+ argv,
+ lambda fd: self._master_read(fd, sock),
+ handle_window_size=True
+ )
+
+ def _master_read(self, fd, sock):
+ if not self.winch_set:
+ prev_handler = signal.getsignal(signal.SIGWINCH)
+ signal.signal(
+ signal.SIGWINCH,
+ lambda signum, frame: self._winch(
+ sock,
+ lambda: prev_handler(signum, frame)
+ )
+ )
+ self.winch_set = True
+
+ data = os.read(fd, 1024)
+ sock.send(data)
+ return data
+
+ def _winch(self, sock, prev_handler):
+ prev_handler()
+ # XXX a bit racy - should try to avoid splitting existing escape
+ # sequences
+ sock.send(self._build_winsize_metadata_string())
+
+ def _build_connection_string(self):
+ auth = (
+ b'hello ' +
+ self.username.encode('utf-8') +
+ b' ' +
+ self.password.encode('utf-8') +
+ b'\n'
+ )
+ metadata = self._build_connection_metadata_string()
+ return auth + metadata
+
+ def _build_connection_metadata_string(self):
+ # for now
+ return self._build_winsize_metadata_string()
+
+ def _build_winsize_metadata_string(self):
+ size = shutil.get_terminal_size()
+ return self._build_metadata_string({
+ "geometry": [ size.columns, size.lines ],
+ })
+
+ def _build_metadata_string(self, data):
+ return b'\033]499;' + json.dumps(data).encode('utf-8') + b'\007'
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--host', default="noway.ratry.ru")
+ parser.add_argument('--port', type=int, default=31337)
+ parser.add_argument('--username', default=os.getenv("USER"))
+ parser.add_argument('--password', default="asdf")
+ parser.add_argument(
+ 'command',
+ nargs=argparse.REMAINDER,
+ )
+
+ args = parser.parse_args(sys.argv[1:])
+
+ client = Client(
+ host=args.host,
+ port=args.port,
+ username=args.username,
+ password=args.password,
+ )
+
+ command = args.command
+ if len(args.command) < 1:
+ command = os.getenv("SHELL", default="/bin/sh")
+ client.run(command)
diff --git a/termcast_client/pity.py b/termcast_client/pity.py
new file mode 100644
index 0000000..26a9e49
--- /dev/null
+++ b/termcast_client/pity.py
@@ -0,0 +1,94 @@
+import fcntl
+import os
+import pty
+import signal
+import termios
+import tty
+
+CHILD = pty.CHILD
+STDIN_FILENO = pty.STDIN_FILENO
+STDOUT_FILENO = pty.STDOUT_FILENO
+STDERR_FILENO = pty.STDERR_FILENO
+
+def fork(handle_window_size=False):
+ # copied from pty.py, with modifications
+ master_fd, slave_fd = openpty()
+ slave_name = os.ttyname(slave_fd)
+ pid = os.fork()
+ if pid == CHILD:
+ # Establish a new session.
+ os.setsid()
+ os.close(master_fd)
+
+ if handle_window_size:
+ clone_window_size_from(slave_name, STDIN_FILENO)
+
+ # Slave becomes stdin/stdout/stderr of child.
+ os.dup2(slave_fd, STDIN_FILENO)
+ os.dup2(slave_fd, STDOUT_FILENO)
+ os.dup2(slave_fd, STDERR_FILENO)
+ if (slave_fd > STDERR_FILENO):
+ os.close (slave_fd)
+
+ # Explicitly open the tty to make it become a controlling tty.
+ tmp_fd = os.open(os.ttyname(STDOUT_FILENO), os.O_RDWR)
+ os.close(tmp_fd)
+ else:
+ os.close(slave_fd)
+
+ # Parent and child process.
+ return pid, master_fd, slave_name
+
+
+def openpty():
+ return pty.openpty()
+
+def spawn(argv, master_read=pty._read, stdin_read=pty._read, handle_window_size=False):
+ # copied from pty.py, with modifications
+ # note that it references a few private functions - would be nice to not
+ # do that, but you know
+ if type(argv) == type(''):
+ argv = (argv,)
+ pid, master_fd, slave_name = fork(handle_window_size)
+ if pid == CHILD:
+ os.execlp(argv[0], *argv)
+ try:
+ mode = tty.tcgetattr(STDIN_FILENO)
+ tty.setraw(STDIN_FILENO)
+ restore = 1
+ except tty.error: # This is the same as termios.error
+ restore = 0
+
+ if handle_window_size:
+ signal.signal(
+ signal.SIGWINCH,
+ lambda signum, frame: _winch(slave_name, pid)
+ )
+
+ while True:
+ try:
+ pty._copy(master_fd, master_read, stdin_read)
+ except InterruptedError:
+ continue
+ except OSError:
+ if restore:
+ tty.tcsetattr(STDIN_FILENO, tty.TCSAFLUSH, mode)
+ break
+
+ os.close(master_fd)
+ return os.waitpid(pid, 0)[1]
+
+def clone_window_size_from(slave_name, from_fd):
+ slave_fd = os.open(slave_name, os.O_RDWR)
+ try:
+ fcntl.ioctl(
+ slave_fd,
+ termios.TIOCSWINSZ,
+ fcntl.ioctl(from_fd, termios.TIOCGWINSZ, " " * 1024)
+ )
+ finally:
+ os.close(slave_fd)
+
+def _winch(slave_name, child_pid):
+ clone_window_size_from(slave_name, STDIN_FILENO)
+ os.kill(child_pid, signal.SIGWINCH)