uae = FSUAE() uae.configure(floppy=args.floppy, rom=args.rom, executable=args.executable) ser_port = SOCAT('serial') ser_port.configure(tcp_port=8000) par_port = SOCAT('parallel') par_port.configure(tcp_port=8001) subprocess.run(['tmux', '-f', TMUX_CONF, '-L', SOCKET, 'start-server']) server = Server(config_file=TMUX_CONF, socket_name=SOCKET) if server.has_session(SESSION): server.kill_session(SESSION) session = server.new_session(session_name=SESSION, attach=False, window_name=':0', window_command='sleep 1') try: uae.start(session) ser_port.start(session) par_port.start(session) session.kill_window(':0') session.select_window(args.window or par_port.name) session.attach_session() finally:
class TMux: def __init__(self, session_name=None, configfile=None, sleep_sec=0.0): self.session_name = 'tmule' self.configfile = configfile if self.configfile: self.load_config() if 'session' in self.config: self.session_name = self.config['session'] else: self.config = None if session_name: self.session_name = session_name self.sleep_sec = sleep_sec # max number of loops to wait for process to come up self.maxCheckLoops = 16 # time to wait between checks (factor multiplied by loop) self.sleepCheckLoop = 1 def _on_terminate(self, proc): info("process {} terminated with exit code {}".format( proc, proc.returncode)) def _terminate(self, pid): procs = Process(pid).children(recursive=True) for p in procs: info("trying to terminate %s" % p) p.terminate() _, still_alive = wait_procs(procs, timeout=1, callback=self._on_terminate) for p in still_alive: info("killing %s" % p) p.kill() def _get_children_pids(self, pid): return Process(pid).children(recursive=True) def var_substitute(self, root): if type(root) == dict: for d in root: root[d] = self.var_substitute(root[d]) elif type(root) == list: for l in range(0, len(root)): root[l] = self.var_substitute(root[l]) elif type(root) == str: for vs in self.var_dict: root = root.replace('@%s@' % vs, self.var_dict[vs]) return root def load_config(self): with open(self.configfile) as data_file: self.config = load(data_file, Loader) self.var_dict = { 'TMULE_CONFIG_FILE': abspath(self.configfile), 'TMULE_CONFIG_DIR': dirname(abspath(self.configfile)), 'TMULE_SESSION_NAME': self.session_name } self.config = self.var_substitute(self.config) self.known_tags = set([]) for w in self.config['windows']: if 'tags' in w: for t in w['tags']: self.known_tags.add(t) def init(self): if not self.config: error('config file not loaded; call "load_config" first!') else: self.server = Server() if self.server.has_session(self.session_name): self.session = self.server.find_where( {"session_name": self.session_name}) debug('found running session %s on server' % self.session_name) else: info('starting new session %s on server' % self.session_name) self.session = self.server.new_session( session_name=self.session_name) for win in self.config['windows']: # print win, "***", self.config['windows'] window = self.session.find_where({"window_name": win['name']}) if window: debug('window %s already exists' % win['name']) else: info('create window %s' % win['name']) window = self.session.new_window(win['name']) exist_num_panes = len(window.list_panes()) while exist_num_panes < len(win['panes']): info('new pane needed in window %s' % win['name']) window.split_window(vertical=1) exist_num_panes = len(window.list_panes()) window.cmd('select-layout', 'tiled') def find_window(self, window_name): for win in self.config['windows']: if win['name'] == window_name: window = self.session.find_where({"window_name": win['name']}) # window.select_window() return win, window def send_ctrlc(self, pane): datestr = datetime.now().strftime('%c') pane.cmd("send-keys", "", "C-c") pane.cmd("send-keys", "", "C-c") pane.cmd("send-keys", "", "C-c") pane.send_keys('# tmux-controller sent Ctrl-C at %s' % datestr, enter=True, suppress_history=True) def launch_window(self, window_name, enter=True): info('launch %s' % window_name) winconf, window = self.find_window(window_name) pane_no = 0 datestr = datetime.now().strftime('%c') for cmd in winconf['panes']: pane = window.select_pane( '%s:%s.%d' % (window.session.name, window_name, pane_no)) debug('pane: %d -> %s' % (pane_no, pane)) self.send_ctrlc(pane) pane.send_keys('# tmux-controller starts new command %s' % datestr, enter=True, suppress_history=True) if 'init_cmd' in self.config: pane.send_keys(self.config['init_cmd'], enter=enter, suppress_history=False) pane.send_keys(cmd, enter=enter, suppress_history=False) pane_no += 1 winconf['_running'] = True def launch_all_windows(self, tags=set([])): for winconf in self.config['windows']: selected = 'skip' not in winconf or not winconf['skip'] if selected and tags: if 'tags' in winconf: selected = set(winconf['tags']).intersection(tags) else: selected = False if selected: self.launch_window(winconf['name']) w = self.sleep_sec if 'wait' in winconf: w = float(winconf['wait']) if w > 0: info('sleep %f seconds after launch of %s' % (w, winconf['name'])) sleep(w) if 'check' in winconf: debug('need to run check command') running = False loop = 0 check_cmd = '\n' if 'init_cmd' in self.config: check_cmd += self.config['init_cmd'] + '\n' check_cmd += winconf['check'] while not running and loop < self.maxCheckLoops: loop += 1 sleep(loop * self.sleepCheckLoop) #running = (call(winconf['check'], shell=True) == 0) running = (call(check_cmd, executable='/bin/bash', shell=True, stdout=None, stdin=None) == 0) info('ran check for %s (loop %d) => %s' % (winconf['name'], loop, running)) if loop >= self.maxCheckLoops: error('window %s failed to come up in time, ' 'not continuing launch.' % winconf['name']) break def stop_all_windows(self, tags=set([])): for winconf in self.config['windows'][::-1]: selected = 'skip' not in winconf or not winconf['skip'] if selected and tags: if 'tags' in winconf: selected = set(winconf['tags']).intersection(tags) if selected: self.stop_window(winconf['name']) def get_children_pids_all_windows(self): pids = [] for winconf in self.config['windows']: pids.extend(self.get_children_pids_window(winconf['name'])) return pids def kill_all_windows(self): for winconf in self.config['windows'][::-1]: try: self.kill_window(winconf['name']) except Exception as e: warning('There was an exception shutting down, ' 'carrying on regardless: %s' % str(e)) self.server.kill_session(self.session_name) def stop_window(self, window_name): info('stop %s' % window_name) winconf, window = self.find_window(window_name) self._stop_window(winconf, window) def __pids_clean_up(self, pids): sleep(1) for p in pids: try: self._terminate(p) except Exception as e: info('exception in termination, can be ignored: %s' % str(e)) def _stop_window(self, winconf, window): pane_no = 0 for _ in winconf['panes']: pane = window.select_pane( '%s:%s.%d' % (window.session.name, window.name, pane_no)) self.send_ctrlc(pane) pane_no += 1 pids = self._get_pids_window(window) Thread(target=self.__pids_clean_up, args=(pids, )).start() #for p in pids: #self._terminate(p) winconf['_running'] = False def kill_window(self, window_name): info('terminate %s' % window_name) winconf, window = self.find_window(window_name) # "-F '#{pane_active} #{pane_pid}") self._stop_window(winconf, window) pids = self._get_pids_window(window) sleep(1) Thread(target=self.__pids_clean_up, args=(pids, )).start() winconf['_running'] = False def list_windows(self): return [w['name'] for w in self.config['windows']] def get_pids_window(self, window_name): winconf, window = self.find_window(window_name) return self._get_pids_window(window) def _get_pids_window(self, window): r = window.cmd('list-panes', "-F #{pane_pid}") return [int(p) for p in r.stdout] def get_children_pids_window(self, window_name): _, window = self.find_window(window_name) return self._get_children_pids_window(window) def _get_children_pids_window(self, window): winpids = self._get_pids_window(window) pids = [] for pid in winpids: pids.extend(self._get_children_pids(pid)) return [p.pid for p in pids] def is_running(self, window_name): winconf, window = self.find_window(window_name) pids = self._get_children_pids_window(window) if len(pids) < 1: return False if 'check' in winconf: debug('need to run check command') check_cmd = '\n' if 'init_cmd' in self.config: check_cmd += self.config['init_cmd'] + '\n' check_cmd += winconf['check'] running = (call(check_cmd, executable='/bin/bash', shell=True, stdout=None, stdin=None) == 0) return running else: return True def _server(self, port=9999, keepalive=True): from .ws_protocol import JsonWSProtocol import web from web.httpserver import StaticMiddleware, StaticApp from autobahn.twisted.websocket import WebSocketServerProtocol, \ WebSocketServerFactory from autobahn.twisted.resource import WebSocketResource, WSGIRootResource from twisted.internet import reactor from twisted.web.server import Site from twisted.web.wsgi import WSGIResource from twisted.python import log from twisted.web.static import File tmux_self = self class TMuxWebServer(web.auto_application): def __init__(self): web.auto_application.__init__(self) self._renderer = web.template.render(path.realpath( path.join(path.dirname(__file__), 'www')), base="base", globals=globals()) self_app = self class Index(self.page): path = '/' def GET(self): tmux_self.load_config() tmux_self.init() ws_uri = '%s://%s%sws' % ( 'ws' if web.ctx['protocol'] == 'http' else 'wss', web.ctx['host'], web.ctx['fullpath']) return self_app._renderer.index( ws_uri, tmux_self.config, tmux_self.known_tags) class Log(self.page): path = '/log' def GET(self): lines = tmux_self.server.cmd('capture-pane', '-p', '-C', '-S', '-100000').stdout return '\n'.join(lines) class TMuxWSProtocol(JsonWSProtocol): def __init__(self): super(TMuxWSProtocol, self).__init__() def on_button(self, payload): debug('button pressed: \n%s' % pformat(payload)) window_name = payload['id'] cmd = payload['cmd'] if cmd == 'launch': if window_name == '': tmux_self.launch_all_windows() else: tmux_self.launch_window(window_name) elif cmd == 'launch-tag': tmux_self.launch_all_windows(tags={window_name}) elif cmd == 'stop': if window_name == '': tmux_self.stop_all_windows() else: tmux_self.stop_window(window_name) elif cmd == 'stop-tag': tmux_self.stop_all_windows(tags={window_name}) elif cmd == 'terminate': tmux_self.kill_all_windows() sleep(1) tmux_self.init() sleep(1) self.sendJSON(self.on_status()) def on_status(self, payload=None): debug('status-requested: ') res = {'windows': {}, 'method': 'update_status'} for w in tmux_self.config['windows']: res['windows'][w['name']] = tmux_self.is_running(w['name']) return res # return {'button_outcome': True} log.startLogging(sys.stdout) wsFactory = WebSocketServerFactory() wsFactory.protocol = TMuxWSProtocol wsResource = WebSocketResource(wsFactory) staticResource = File( path.realpath(path.join(path.dirname(__file__), 'www/static'))) app = TMuxWebServer() # create a Twisted Web WSGI resource for our Flask server wsgiResource = WSGIResource(reactor, reactor.getThreadPool(), app.wsgifunc()) # create a root resource serving everything via WSGI/Flask, but # the path "/ws" served by our WebSocket stuff rootResource = WSGIRootResource(wsgiResource, { b'ws': wsResource, b'static': staticResource }) # create a Twisted Web Site and run everything site = Site(rootResource) reactor.listenTCP(port, site) reactor.run() # kill everything when server dies if not keepalive: self.kill_all_windows()
class TMux: def __init__(self, session_name="tmule", configfile=None): if configfile: self.load_config(configfile) else: self.config = None self.session_name = session_name def _on_terminate(self, proc): info("process {} terminated with exit code {}".format( proc, proc.returncode)) def _terminate(self, pid): procs = Process(pid).children() for p in procs: p.terminate() gone, still_alive = wait_procs(procs, timeout=1, callback=self._on_terminate) for p in still_alive: p.kill() def _get_children_pids(self, pid): return Process(pid).children(recursive=True) def load_config(self, filename="sample_config.json"): with open(filename) as data_file: self.config = load(data_file, Loader) def init(self): if not self.config: error('config file not loaded; call "load_config" first!') else: self.server = Server() if self.server.has_session(self.session_name): self.session = self.server.find_where( {"session_name": self.session_name}) info('found running session %s on server' % self.session_name) else: info('starting new session %s on server' % self.session_name) self.session = self.server.new_session( session_name=self.session_name) for win in self.config['windows']: print win, "***", self.config['windows'] window = self.session.find_where({"window_name": win['name']}) if window: info('window %s already exists' % win['name']) else: info('create window %s' % win['name']) window = self.session.new_window(win['name']) exist_num_panes = len(window.list_panes()) while exist_num_panes < len(win['panes']): info('new pane needed in window %s' % win['name']) window.split_window(vertical=1) exist_num_panes = len(window.list_panes()) window.cmd('select-layout', 'tiled') def find_window(self, window_name): for win in self.config['windows']: if win['name'] == window_name: window = self.session.find_where({"window_name": win['name']}) #window.select_window() return win, window def send_ctrlc(self, pane): datestr = datetime.now().strftime('%c') pane.cmd("send-keys", "", "C-c") pane.cmd("send-keys", "", "C-c") pane.cmd("send-keys", "", "C-c") pane.send_keys('# tmux-controller sent Ctrl-C at %s' % datestr, enter=True, suppress_history=True) def launch_window(self, window_name, enter=True): info('launch %s' % window_name) winconf, window = self.find_window(window_name) pane_no = 0 datestr = datetime.now().strftime('%c') for cmd in winconf['panes']: pane = window.select_pane(pane_no) self.send_ctrlc(pane) pane.send_keys('# tmux-controller starts new command %s' % datestr, enter=True, suppress_history=True) if 'init_cmd' in self.config: pane.send_keys(self.config['init_cmd'], enter=enter, suppress_history=False) pane.send_keys(cmd, enter=enter, suppress_history=False) pane_no += 1 winconf['_running'] = True def launch_all_windows(self): for winconf in self.config['windows']: self.launch_window(winconf['name']) def stop_all_windows(self): for winconf in self.config['windows']: self.stop_window(winconf['name']) def get_children_pids_all_windows(self): pids = [] for winconf in self.config['windows']: pids.extend(self.get_children_pids_window(winconf['name'])) return pids def kill_all_windows(self): for winconf in self.config['windows']: self.kill_window(winconf['name']) self.server.kill_session(self.session_name) def stop_window(self, window_name): info('stop %s' % window_name) winconf, window = self.find_window(window_name) self._stop_window(winconf, window) def _stop_window(self, winconf, window): pane_no = 0 for cmd in winconf['panes']: pane = window.select_pane(pane_no) self.send_ctrlc(pane) pane_no += 1 pids = self._get_pids_window(window) sleep(.1) for p in pids: self._terminate(p) winconf['_running'] = False def kill_window(self, window_name): info('terminate %s' % window_name) winconf, window = self.find_window(window_name) # "-F '#{pane_active} #{pane_pid}") self._stop_window(winconf, window) pids = self._get_pids_window(window) for pid in pids: Process(pid).terminate() winconf['_running'] = False def list_windows(self): return [w['name'] for w in self.config['windows']] def get_pids_window(self, window_name): winconf, window = self.find_window(window_name) return self._get_pids_window(window) def _get_pids_window(self, window): r = window.cmd('list-panes', "-F #{pane_pid}") return [int(p) for p in r.stdout] def get_children_pids_window(self, window_name): winconf, window = self.find_window(window_name) return self._get_children_pids_window(window) def _get_children_pids_window(self, window): winpids = self._get_pids_window(window) pids = [] for pid in winpids: pids.extend(self._get_children_pids(pid)) return [p.pid for p in pids] def is_running(self, window_name): winconf, window = self.find_window(window_name) pids = self._get_children_pids_window(window) if len(pids) < 1: return False if 'check' in winconf: info('need to run check command') if call(winconf['check'], shell=True) == 0: return True else: return False else: return True def _server(self): import webnsock import web tmux_self = self class TMuxWebServer(webnsock.WebServer): def __init__(self): webnsock.WebServer.__init__(self) self._render = web.template.render(path.realpath( path.join(path.dirname(__file__), 'www')), base="base", globals=globals()) self_app = self class Index(self.page): path = '/' def GET(self): return self_app._render.index(tmux_self.config) class Log(self.page): path = '/log' def GET(self): lines = tmux_self.server.cmd('capture-pane', '-p', '-C', '-S', '-100000').stdout return '\n'.join(lines) class TMuxWSProtocol(webnsock.JsonWSProtocol): def __init__(self): super(TMuxWSProtocol, self).__init__() def on_button(self, payload): info('button pressed: \n%s' % pformat(payload)) window_name = payload['id'] cmd = payload['cmd'] if cmd == 'launch': if window_name == '': tmux_self.launch_all_windows() else: tmux_self.launch_window(window_name) elif cmd == 'stop': if window_name == '': tmux_self.stop_all_windows() else: tmux_self.stop_window(window_name) elif cmd == 'terminate': tmux_self.kill_all_windows() sleep(1) tmux_self.init() sleep(1) self.sendJSON(self.on_status()) def on_status(self, payload=None): info('status-requested: ') res = {'windows': {}, 'method': 'update_status'} for w in tmux_self.config['windows']: res['windows'][w['name']] = tmux_self.is_running(w['name']) return res #return {'button_outcome': True} self.webserver = webnsock.WebserverThread(TMuxWebServer(), port=9999) self.backend = webnsock.WSBackend(TMuxWSProtocol) signal.signal( signal.SIGINT, lambda s, f: webnsock.signal_handler( self.webserver, self.backend, s, f)) self.webserver.start() self.backend.talker(port=9998)