import socket, threading, queue, logging from gi.repository import GObject def controlServerEntrypoint(f): # mark the method as something that requires view's class f.is_control_server_entrypoint = True return f class ControlServer(): log = logging.getLogger('ControlServer') def __init__(self, videomix): '''Initialize server and start listening.''' self.videomix = videomix sock = socket.socket() sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(('0.0.0.0', 23000)) sock.listen(1) # register socket for callback inside the GTK-Mainloop GObject.io_add_watch(sock, GObject.IO_IN, self.listener) def listener(self, sock, *args): '''Asynchronous connection listener. Starts a handler for each connection.''' conn, addr = sock.accept() self.log.info("Connection from %s", addr) # register data-received handler inside the GTK-Mainloop GObject.io_add_watch(conn, GObject.IO_IN, self.handler) return True def handler(self, conn, *args): '''Asynchronous connection handler. Processes each line from the socket.''' line = conn.recv(4096) if not len(line): self.log.debug("Connection closed.") return False r = self.processLine(line.decode('utf-8')) if isinstance(r, str): conn.send((r+'\n').encode('utf-8')) return False conn.send('OK\n'.encode('utf-8')) return True def processLine(self, line): command, argstring = (line.strip()+' ').split(' ', 1) args = argstring.strip().split() self.log.info(command % args) if not hasattr(self.videomix, command): return 'unknown command {}'.format(command) f = getattr(self.videomix, command) if not hasattr(f, 'is_control_server_entrypoint'): return 'method {} not callable from controlserver'.format(command) try: return f(*args) except Exception as e: return str(e)