|
|
1.1 ! root 1: #!/usr/bin/env python ! 2: # ! 3: # Hatari console: ! 4: # Allows using Hatari shortcuts & debugger, changing paths, toggling ! 5: # devices and changing Hatari command line options (even for things you ! 6: # cannot change from the UI) from the console while Hatari is running. ! 7: # ! 8: # Copyright (C) 2008-2009 by Eero Tamminen <eerot at berlios> ! 9: # ! 10: # This program is free software; you can redistribute it and/or modify ! 11: # it under the terms of the GNU General Public License as published by ! 12: # the Free Software Foundation; either version 2 of the License, or ! 13: # (at your option) any later version. ! 14: # ! 15: # This program is distributed in the hope that it will be useful, ! 16: # but WITHOUT ANY WARRANTY; without even the implied warranty of ! 17: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ! 18: # GNU General Public License for more details. ! 19: ! 20: import os ! 21: import sys ! 22: import time ! 23: import signal ! 24: import socket ! 25: import readline ! 26: ! 27: # running Hatari instance ! 28: class Hatari: ! 29: controlpath = "/tmp/hatari-console-" + str(os.getpid()) + ".socket" ! 30: hataribin = "hatari" ! 31: ! 32: def __init__(self, args = None): ! 33: # collect hatari process zombies without waitpid() ! 34: signal.signal(signal.SIGCHLD, signal.SIG_IGN) ! 35: self._assert_hatari_compatibility() ! 36: self.server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) ! 37: if os.path.exists(self.controlpath): ! 38: os.unlink(self.controlpath) ! 39: self.server.bind(self.controlpath) ! 40: self.server.listen(1) ! 41: self.control = None ! 42: self.paused = False ! 43: self.pid = 0 ! 44: if not self.run(args): ! 45: print "ERROR: failed to run Hatari" ! 46: sys.exit(1) ! 47: ! 48: def _assert_hatari_compatibility(self): ! 49: for line in os.popen(self.hataribin + " -h").readlines(): ! 50: if line.find("--control-socket") >= 0: ! 51: return ! 52: print "ERROR: Hatari not found or it doesn't support the required --control-socket option!" ! 53: sys.exit(-1) ! 54: ! 55: def is_running(self): ! 56: if not self.pid: ! 57: return False ! 58: try: ! 59: os.waitpid(self.pid, os.WNOHANG) ! 60: except OSError, value: ! 61: print "Hatari PID %d had exited in the meanwhile:\n\t%s" % (self.pid, value) ! 62: self.pid = 0 ! 63: return False ! 64: return True ! 65: ! 66: def run(self, args): ! 67: if self.control: ! 68: print "ERROR: Hatari is already running, stop it first" ! 69: return ! 70: pid = os.fork() ! 71: if pid < 0: ! 72: print "ERROR: fork()ing Hatari failed!" ! 73: return ! 74: if pid: ! 75: # in parent ! 76: self.pid = pid ! 77: print "WAIT hatari to connect to control socket...", ! 78: (self.control, addr) = self.server.accept() ! 79: print "connected!" ! 80: return self.control ! 81: else: ! 82: # child runs Hatari ! 83: allargs = [self.hataribin, "--control-socket", self.controlpath] + args ! 84: print "RUN:", allargs ! 85: os.execvp(self.hataribin, allargs) ! 86: ! 87: def send_message(self, msg): ! 88: if self.control: ! 89: print "->", msg ! 90: self.control.sendall(msg + "\n") ! 91: # KLUDGE: wait so that Hatari output comes before next prompt ! 92: time.sleep(0.2) ! 93: return True ! 94: else: ! 95: print "ERROR: no Hatari (control socket)" ! 96: return False ! 97: ! 98: def change_option(self, option): ! 99: return self.send_message("hatari-option %s" % option) ! 100: ! 101: def trigger_shortcut(self, shortcut): ! 102: return self.send_message("hatari-shortcut %s" % shortcut) ! 103: ! 104: def insert_event(self, event): ! 105: return self.send_message("hatari-event %s" % event) ! 106: ! 107: def debug_command(self, cmd): ! 108: return self.send_message("hatari-debug %s" % cmd) ! 109: ! 110: def change_path(self, path): ! 111: return self.send_message("hatari-path %s" % path) ! 112: ! 113: def toggle_device(self, device): ! 114: return self.send_message("hatari-toggle %s" % device) ! 115: ! 116: def pause(self): ! 117: return self.send_message("hatari-stop") ! 118: ! 119: def unpause(self): ! 120: return self.send_message("hatari-cont") ! 121: ! 122: def stop(self): ! 123: if self.pid: ! 124: os.kill(self.pid, signal.SIGKILL) ! 125: print "killed hatari with PID %d" % self.pid ! 126: self.control = None ! 127: self.pid = 0 ! 128: ! 129: ! 130: # command line parsing with readline ! 131: class CommandInput: ! 132: prompt = "hatari-command: " ! 133: historysize = 99 ! 134: ! 135: def __init__(self, commands): ! 136: readline.set_history_length(self.historysize) ! 137: readline.parse_and_bind("tab: complete") ! 138: readline.set_completer_delims(" \t\r\n") ! 139: readline.set_completer(self.complete) ! 140: self.commands = commands ! 141: ! 142: def complete(self, text, state): ! 143: idx = 0 ! 144: #print "text: '%s', state '%d'" % (text, state) ! 145: for cmd in self.commands: ! 146: if cmd.startswith(text): ! 147: idx += 1 ! 148: if idx > state: ! 149: return cmd ! 150: ! 151: def loop(self): ! 152: try: ! 153: rawline = raw_input(self.prompt) ! 154: return rawline ! 155: except EOFError: ! 156: return "" ! 157: ! 158: ! 159: # update with: hatari -h|grep -- --|sed 's/^ *\(--[^ ]*\).*$/ "\1",/' ! 160: option_tokens = [ ! 161: "--help", ! 162: "--version", ! 163: "--confirm-quit", ! 164: "--configfile", ! 165: "--fast-forward", ! 166: "--mono", ! 167: "--monitor", ! 168: "--fullscreen", ! 169: "--window", ! 170: "--grab", ! 171: "--zoom", ! 172: "--frameskips", ! 173: "--borders", ! 174: "--statusbar", ! 175: "--drive-led", ! 176: "--spec512", ! 177: "--bpp", ! 178: "--vdi", ! 179: "--vdi-planes", ! 180: "--vdi-width", ! 181: "--vdi-height", ! 182: "--joystick", ! 183: "--printer", ! 184: "--midi-in", ! 185: "--midi-out", ! 186: "--rs232-in", ! 187: "--rs232-out", ! 188: "--disk-a", ! 189: "--disk-b", ! 190: "--slowfdc", ! 191: "--harddrive", ! 192: "--acsi", ! 193: "--ide", ! 194: "--memsize", ! 195: "--tos", ! 196: "--cartridge", ! 197: "--memstate", ! 198: "--cpulevel", ! 199: "--cpuclock", ! 200: "--compatible", ! 201: "--machine", ! 202: "--blitter", ! 203: "--dsp", ! 204: "--sound", ! 205: "--keymap", ! 206: "--debug", ! 207: "--bios-intercept", ! 208: "--trace", ! 209: "--trace-file", ! 210: "--log-file", ! 211: "--log-level", ! 212: "--alert-level", ! 213: "--run-vbls" ! 214: ] ! 215: shortcut_tokens = [ ! 216: "mousemode", ! 217: "coldreset", ! 218: "warmreset", ! 219: "screenshot", ! 220: "bosskey", ! 221: "recanim", ! 222: "recsound", ! 223: "savemem" ! 224: ] ! 225: event_tokens = [ ! 226: "doubleclick", ! 227: "rightpress", ! 228: "rightrelease", ! 229: "keypress", ! 230: "keyrelease" ! 231: ] ! 232: device_tokens = [ ! 233: "printer", ! 234: "rs232", ! 235: "midi", ! 236: ] ! 237: path_tokens = [ ! 238: "memauto", ! 239: "memsave", ! 240: "midiout", ! 241: "printout", ! 242: "soundout", ! 243: "rs232in", ! 244: "rs232out" ! 245: ] ! 246: # use the long variants of the commands for clarity ! 247: debugger_tokens = [ ! 248: "address", ! 249: "breakpoint", ! 250: "cont", ! 251: "cpureg", ! 252: "disasm", ! 253: "dspaddress", ! 254: "dspbreak", ! 255: "dspcont", ! 256: "dspdisasm", ! 257: "dspmemdump", ! 258: "dspreg", ! 259: "help", ! 260: "loadbin", ! 261: "logfile", ! 262: "memdump", ! 263: "memwrite", ! 264: "savebin", ! 265: "setopt" ! 266: ] ! 267: ! 268: ! 269: def list_items(title, items): ! 270: print "\n%s:" % title ! 271: for item in items: ! 272: print "*", item ! 273: ! 274: def show_help(): ! 275: print """ ! 276: Hatari-console help ! 277: ------------------- ! 278: ! 279: Hatari-console allows you to invoke Hatari remote configuration facilities ! 280: from console while Hatari is running and use TAB completion on their names. ! 281: The supported facilities are:""" ! 282: list_items("Command line options", option_tokens) ! 283: list_items("Keyboard shortcuts", shortcut_tokens) ! 284: list_items("Event invocation", event_tokens) ! 285: list_items("Device toggling", device_tokens) ! 286: list_items("Path setting", path_tokens) ! 287: list_items("Debugger commands", debugger_tokens) ! 288: print """ ! 289: and commands to "pause", "unpause" and "quit" Hatari. ! 290: ! 291: For command line options you can see further help with "--help" ! 292: and for debugger with "h". Some other facilities may give help ! 293: if you give them invalid input. ! 294: """ ! 295: ! 296: ! 297: def main(): ! 298: hatari = Hatari(sys.argv[1:]) ! 299: process_tokens = { ! 300: "pause": hatari.pause, ! 301: "unpause": hatari.unpause, ! 302: "quit": hatari.stop ! 303: } ! 304: ! 305: print "************************************************************" ! 306: print "* Use the TAB key to see all the available Hatari commands *" ! 307: print "************************************************************" ! 308: tokens = ["console-help"] ! 309: for items in [option_tokens, shortcut_tokens, event_tokens, ! 310: debugger_tokens, device_tokens, path_tokens, ! 311: process_tokens.keys()]: ! 312: for token in items: ! 313: if token in tokens: ! 314: print "ERROR: token '%s' already in tokens" % token ! 315: sys.exit(1) ! 316: tokens += items ! 317: command = CommandInput(tokens) ! 318: ! 319: while 1: ! 320: line = command.loop().strip() ! 321: if not hatari.is_running(): ! 322: print "Exiting as there's no Hatari (anymore)..." ! 323: sys.exit(0) ! 324: if not line: ! 325: continue ! 326: first = line.split(" ")[0] ! 327: # multiple items ! 328: if first in event_tokens: ! 329: hatari.insert_event(line) ! 330: elif first in debugger_tokens: ! 331: hatari.debug_command(line) ! 332: elif first in option_tokens: ! 333: hatari.change_option(line) ! 334: elif first in path_tokens: ! 335: hatari.change_path(line) ! 336: # single item ! 337: elif line in device_tokens: ! 338: hatari.toggle_device(line) ! 339: elif line in shortcut_tokens: ! 340: hatari.trigger_shortcut(line) ! 341: elif line in process_tokens: ! 342: process_tokens[line]() ! 343: elif line == "console-help": ! 344: show_help() ! 345: else: ! 346: print "ERROR: unknown hatari-console command:", line ! 347: ! 348: ! 349: if __name__ == "__main__": ! 350: main()
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.