diff --git a/examples/reference-gertty.yaml b/examples/reference-gertty.yaml index 5a33fdd..e621f52 100644 --- a/examples/reference-gertty.yaml +++ b/examples/reference-gertty.yaml @@ -47,6 +47,9 @@ servers: # time it starts (so that it does not grow without bound). If you # would like to log to a different location, you may specify it here. # log-file: ~/.gertty.log +# Gertty listens on a unix domain socket for remote commands at +# ~/.gertty.sock. You may change the path here: +# socket: ~/.gertty.sock # Gertty comes with two palettes defined internally. The default # palette is suitable for use on a terminal with a dark background. diff --git a/gertty/app.py b/gertty/app.py index b72f006..c47a31f 100644 --- a/gertty/app.py +++ b/gertty/app.py @@ -19,6 +19,7 @@ import dateutil import logging import os import re +import socket import subprocess import sys import textwrap @@ -256,6 +257,8 @@ class App(object): self.error_queue = queue.Queue() self.error_pipe = self.loop.watch_pipe(self._errorPipeInput) self.logged_warnings = set() + self.command_pipe = self.loop.watch_pipe(self._commandPipeInput) + self.command_queue = queue.Queue() warnings.showwarning = self._showWarning @@ -268,6 +271,9 @@ class App(object): self.loop.screen.tty_signal_keys(start='undefined', stop='undefined') #self.loop.screen.set_terminal_properties(colors=88) + + self.startSocketListener() + if not disable_sync: self.sync_thread = threading.Thread(target=self.sync.run, args=(self.sync_pipe,)) self.sync_thread.daemon = True @@ -294,6 +300,35 @@ class App(object): self.popup(dialog) + def startSocketListener(self): + if os.path.exists(self.config.socket_path): + os.unlink(self.config.socket_path) + self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self.socket.bind(self.config.socket_path) + self.socket.listen(1) + self.socket_thread = threading.Thread(target=self._socketListener) + self.socket_thread.daemon = True + self.socket_thread.start() + + def _socketListener(self): + while True: + try: + s, addr = self.socket.accept() + self.log.debug("Accepted socket connection %s" % (s,)) + buf = '' + while True: + buf += s.recv(1) + if buf[-1] == '\n': + break + buf = buf.strip() + self.log.debug("Received %s from socket" % (buf,)) + s.close() + parts = buf.split() + self.command_queue.put((parts[0], parts[1:])) + os.write(self.command_pipe, six.b('command\n')) + except Exception: + self.log.exception("Exception in socket handler") + def clearInputBuffer(self): if self.input_buffer: self.input_buffer = [] @@ -598,7 +633,18 @@ class App(object): if category == requestsexceptions.InsecureRequestWarning: return self.error_queue.put(('Warning', m)) - os.write(self.error_pipe, 'error\n') + os.write(self.error_pipe, six.b('error\n')) + + def _commandPipeInput(self, data=None): + (command, data) = self.command_queue.get() + if command == 'open': + url = data[0] + self.log.debug("Opening URL %s" % (url,)) + result = self.parseInternalURL(url) + if result is not None: + self.openInternalURL(result) + else: + self.log.error("Unable to parse command %s with data %s" % (command, data)) def toggleHeldChange(self, change_key): with self.db.getSession() as session: @@ -709,6 +755,21 @@ class PrintPaletteAction(argparse.Action): print(attr) sys.exit(0) +class OpenChangeAction(argparse.Action): + def __call__(self, parser, namespace, values, option_string=None): + cf = config.Config(namespace.server, namespace.palette, + namespace.keymap, namespace.path) + url = values[0] + result = urlparse.urlparse(values[0]) + if not url.startswith(cf.url): + print('Supplied URL must start with %s' % (cf.url,)) + sys.exit(1) + + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + s.connect(cf.socket_path) + s.sendall('open %s\n' % url) + sys.exit(0) + def main(): parser = argparse.ArgumentParser( description='Console client for Gerrit Code Review.') @@ -728,6 +789,9 @@ def main(): help='print the keymap command names to stdout') parser.add_argument('--print-palette', nargs=0, action=PrintPaletteAction, help='print the palette attribute names to stdout') + parser.add_argument('--open', nargs=1, action=OpenChangeAction, + metavar='URL', + help='open the given URL in a running Gertty') parser.add_argument('--version', dest='version', action='version', version=version(), help='show Gertty\'s version') diff --git a/gertty/config.py b/gertty/config.py index 51296ad..42089b8 100644 --- a/gertty/config.py +++ b/gertty/config.py @@ -47,6 +47,7 @@ class ConfigSchema(object): 'dburi': str, v.Required('git-root'): str, 'log-file': str, + 'socket': str, 'auth-type': str, } @@ -173,6 +174,8 @@ class Config(object): self.git_root = os.path.expanduser(server['git-root']) self.dburi = server.get('dburi', 'sqlite:///' + os.path.expanduser('~/.gertty.db')) + socket_path = server.get('socket', '~/.gertty.sock') + self.socket_path = os.path.expanduser(socket_path) log_file = server.get('log-file', '~/.gertty.log') self.log_file = os.path.expanduser(log_file)