Annotation of hatari/python-ui/hatari-console.py, revision 1.1.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.