Annotation of hatari/python-ui/hatari-console.py, revision 1.1

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()

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.