|
|
1.1 ! root 1: #!/usr/bin/env python ! 2: # ! 3: # Classes for Hatari emulator instance and mapping its congfiguration ! 4: # variables with its command line option. ! 5: # ! 6: # Copyright (C) 2008 by Eero Tamminen <[email protected]> ! 7: # ! 8: # This program is free software; you can redistribute it and/or modify ! 9: # it under the terms of the GNU General Public License as published by ! 10: # the Free Software Foundation; either version 2 of the License, or ! 11: # (at your option) any later version. ! 12: # ! 13: # This program is distributed in the hope that it will be useful, ! 14: # but WITHOUT ANY WARRANTY; without even the implied warranty of ! 15: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ! 16: # GNU General Public License for more details. ! 17: ! 18: import os ! 19: import sys ! 20: import time ! 21: import signal ! 22: import socket ! 23: import select ! 24: from config import ConfigStore ! 25: ! 26: ! 27: # Running Hatari instance ! 28: class Hatari: ! 29: "running hatari instance and methods for communicating with it" ! 30: basepath = "/tmp/hatari-ui-" + str(os.getpid()) ! 31: logpath = basepath + ".log" ! 32: tracepath = basepath + ".trace" ! 33: debugpath = basepath + ".debug" ! 34: controlpath = basepath + ".socket" ! 35: server = None # singleton due to path being currently one per user ! 36: ! 37: def __init__(self, hataribin = None): ! 38: # collect hatari process zombies without waitpid() ! 39: signal.signal(signal.SIGCHLD, signal.SIG_IGN) ! 40: if hataribin: ! 41: self.hataribin = hataribin ! 42: else: ! 43: self.hataribin = "hatari" ! 44: self._assert_hatari_compatibility() ! 45: self._create_server() ! 46: self.control = None ! 47: self.paused = False ! 48: self.pid = 0 ! 49: ! 50: def _assert_hatari_compatibility(self): ! 51: for line in os.popen(self.hataribin + " -h").readlines(): ! 52: if line.find("--control-socket") >= 0: ! 53: return ! 54: print "ERROR: Hatari not found or it doesn't support the required --control-socket option!" ! 55: sys.exit(-1) ! 56: ! 57: def _create_server(self): ! 58: if self.server: ! 59: return ! 60: self.server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) ! 61: if os.path.exists(self.controlpath): ! 62: os.unlink(self.controlpath) ! 63: self.server.bind(self.controlpath) ! 64: self.server.listen(1) ! 65: ! 66: def _send_message(self, msg): ! 67: if self.control: ! 68: self.control.send(msg) ! 69: return True ! 70: else: ! 71: print "ERROR: no Hatari (control socket)" ! 72: return False ! 73: ! 74: def change_option(self, option): ! 75: "change_option(option), changes given Hatari cli option" ! 76: return self._send_message("hatari-option %s\n" % option) ! 77: ! 78: def set_path(self, key, path): ! 79: "set_path(key, path), sets path with given key" ! 80: return self._send_message("hatari-path %s %s\n" % (key, path)) ! 81: ! 82: def set_device(self, device, enabled): ! 83: # needed because CLI options cannot disable devices, only enable ! 84: "set_path(device, enabled), sets whether given device is enabled or not" ! 85: if enabled: ! 86: return self._send_message("hatari-enable %s\n" % device) ! 87: else: ! 88: return self._send_message("hatari-disable %s\n" % device) ! 89: ! 90: def trigger_shortcut(self, shortcut): ! 91: "trigger_shortcut(shortcut), triggers given Hatari (keyboard) shortcut" ! 92: return self._send_message("hatari-shortcut %s\n" % shortcut) ! 93: ! 94: def insert_event(self, event): ! 95: "insert_event(event), synthetizes given key/mouse Atari event" ! 96: return self._send_message("hatari-event %s\n" % event) ! 97: ! 98: def debug_command(self, cmd): ! 99: "debug_command(command), runs given Hatari debugger command" ! 100: return self._send_message("hatari-debug %s\n" % cmd) ! 101: ! 102: def pause(self): ! 103: "pause(), pauses Hatari emulation" ! 104: return self._send_message("hatari-stop\n") ! 105: ! 106: def unpause(self): ! 107: "unpause(), continues Hatari emulation" ! 108: return self._send_message("hatari-cont\n") ! 109: ! 110: def _open_output_file(self, hataricommand, option, path): ! 111: if os.path.exists(path): ! 112: os.unlink(path) ! 113: # TODO: why fifo doesn't work properly (blocks forever on read or ! 114: # reads only byte at the time and stops after first newline)? ! 115: #os.mkfifo(path) ! 116: #raw_input("attach strace now, then press Enter\n") ! 117: ! 118: # ask Hatari to open/create the requested output file... ! 119: hataricommand("%s %s" % (option, path)) ! 120: wait = 0.025 ! 121: # ...and wait for it to appear before returning it ! 122: for i in range(0, 8): ! 123: time.sleep(wait) ! 124: if os.path.exists(path): ! 125: return open(path, "r") ! 126: wait += wait ! 127: return None ! 128: ! 129: def open_debug_output(self): ! 130: "open_debug_output() -> file, opens Hatari debugger output file" ! 131: return self._open_output_file(self.debug_command, "f", self.debugpath) ! 132: ! 133: def open_trace_output(self): ! 134: "open_trace_output() -> file, opens Hatari tracing output file" ! 135: return self._open_output_file(self.change_option, "--trace-file", self.tracepath) ! 136: ! 137: def open_log_output(self): ! 138: "open_trace_output() -> file, opens Hatari debug log file" ! 139: return self._open_output_file(self.change_option, "--log-file", self.logpath) ! 140: ! 141: def get_lines(self, fileobj): ! 142: "get_lines(file) -> list of lines readable from given Hatari output file" ! 143: # wait until data is available, then wait for some more ! 144: # and only then the data can be read, otherwise its old ! 145: print "Request&wait data from Hatari..." ! 146: select.select([fileobj], [], []) ! 147: time.sleep(0.1) ! 148: print "...read the data lines" ! 149: lines = fileobj.readlines() ! 150: print "".join(lines) ! 151: return lines ! 152: ! 153: def enable_embed_info(self): ! 154: "enable_embed_info(), request embedded Hatari window ID change information" ! 155: self._send_message("hatari-embed-info\n") ! 156: ! 157: def get_embed_info(self): ! 158: "get_embed_info() -> (width, height), get embedded Hatari window size" ! 159: width, height = self.control.recv(12).split("x") ! 160: return (int(width), int(height)) ! 161: ! 162: def get_control_socket(self): ! 163: "get_control_socket() -> socket which can be checked for embed ID changes" ! 164: return self.control ! 165: ! 166: def is_running(self): ! 167: "is_running() -> bool, True if Hatari is running, False otherwise" ! 168: if not self.pid: ! 169: return False ! 170: try: ! 171: os.waitpid(self.pid, os.WNOHANG) ! 172: except OSError, value: ! 173: print "Hatari PID %d had exited in the meanwhile:\n\t%s" % (self.pid, value) ! 174: self.pid = 0 ! 175: return False ! 176: return True ! 177: ! 178: def run(self, extra_args = None, parent_win = None): ! 179: "run([parent window][,embedding args]), runs Hatari" ! 180: # if parent_win given, embed Hatari to it ! 181: pid = os.fork() ! 182: if pid < 0: ! 183: print "ERROR: fork()ing Hatari failed!" ! 184: return ! 185: if pid: ! 186: # in parent ! 187: self.pid = pid ! 188: if self.server: ! 189: print "WAIT hatari to connect to control socket...", ! 190: (self.control, addr) = self.server.accept() ! 191: print "connected!" ! 192: else: ! 193: # child runs Hatari ! 194: env = os.environ ! 195: if parent_win: ! 196: self._set_embed_env(env, parent_win) ! 197: # callers need to take care of confirming quitting ! 198: args = [self.hataribin, "--confirm-quit", "off"] ! 199: if self.server: ! 200: args += ["--control-socket", self.controlpath] ! 201: if extra_args: ! 202: args += extra_args ! 203: print "RUN:", args ! 204: os.execvpe(self.hataribin, args, env) ! 205: ! 206: def _set_embed_env(self, env, parent_win): ! 207: if sys.platform == 'win32': ! 208: win_id = parent_win.handle ! 209: else: ! 210: win_id = parent_win.xid ! 211: # tell SDL to use given widget's window ! 212: #env["SDL_WINDOWID"] = str(win_id) ! 213: ! 214: # above is broken: when SDL uses a window it hasn't created itself, ! 215: # it for some reason doesn't listen to any events delivered to that ! 216: # window nor implements XEMBED protocol to get them in a way most ! 217: # friendly to embedder: ! 218: # http://standards.freedesktop.org/xembed-spec/latest/ ! 219: # ! 220: # Instead we tell hatari to reparent itself after creating ! 221: # its own window into this program widget window ! 222: env["PARENT_WIN_ID"] = str(win_id) ! 223: ! 224: def kill(self): ! 225: "kill(), kill Hatari if it's running" ! 226: if self.pid: ! 227: os.kill(self.pid, signal.SIGKILL) ! 228: print "killed hatari with PID %d" % self.pid ! 229: self.pid = 0 ! 230: if self.control: ! 231: self.control.close() ! 232: self.control = None ! 233: ! 234: ! 235: # Mapping of requested values both to Hatari configuration ! 236: # and command line options. ! 237: # ! 238: # By default this doesn't allow setting any other configuration ! 239: # variables than the ones that were read from the configuration ! 240: # file i.e. you get an exception if configuration variables ! 241: # don't match to current Hatari. So before using this you should ! 242: # have saved Hatari configuration at least once. ! 243: # ! 244: # Because of some inconsistencies in the values (see e.g. sound), ! 245: # this cannot just do these according to some mapping table, but ! 246: # it needs actual method for (each) setting. ! 247: class HatariConfigMapping(ConfigStore): ! 248: _paths = { ! 249: "memauto": ("[Memory]", "szAutoSaveFileName", "Automatic memory snapshot"), ! 250: "memsave": ("[Memory]", "szMemoryCaptureFileName", "Manual memory snapshot"), ! 251: "midiin": ("[Midi]", "sMidiInFileName", "Midi input"), ! 252: "midiout": ("[Midi]", "sMidiOutFileName", "Midi output"), ! 253: "rs232in": ("[RS232]", "szInFileName", "RS232 I/O input"), ! 254: "rs232out": ("[RS232]", "szOutFileName", "RS232 I/O output"), ! 255: "printout": ("[Printer]", "szPrintToFileName", "Printer output"), ! 256: "soundout": ("[Sound]", "szYMCaptureFileName", "Sound output") ! 257: } ! 258: "access methods to Hatari configuration file variables and command line options" ! 259: def __init__(self, hatari): ! 260: ConfigStore.__init__(self, "hatari.cfg") ! 261: self._hatari = hatari ! 262: self._lock_updates = False ! 263: self._options = [] ! 264: ! 265: def _change_option(self, option): ! 266: if self._lock_updates: ! 267: self._options.append(option) ! 268: else: ! 269: self._hatari.change_option(option) ! 270: ! 271: def lock_updates(self): ! 272: "lock_updates(), collect Hatari configuration changes" ! 273: self._lock_updates = True ! 274: ! 275: def flush_updates(self): ! 276: "flush_updates(), apply collected Hatari configuration changes" ! 277: self._lock_updates = False ! 278: if not self._options: ! 279: return ! 280: self._hatari.change_option(" ".join(self._options)) ! 281: self._options = [] ! 282: ! 283: # ------------ paths --------------- ! 284: def get_paths(self): ! 285: paths = [] ! 286: for key, item in self._paths.items(): ! 287: paths.append((key, self.get(item[0], item[1]), item[2])) ! 288: return paths ! 289: ! 290: def set_paths(self, paths): ! 291: for key, path in paths: ! 292: self.set(self._paths[key][0], self._paths[key][1], path) ! 293: self._hatari.set_path(key, path) ! 294: ! 295: # ------------ midi --------------- ! 296: def get_midi(self): ! 297: return self.get("[Midi]", "bEnableMidi") ! 298: ! 299: def set_midi(self, value): ! 300: self.set("[Midi]", "bEnableMidi", value) ! 301: self._hatari.set_device("midi", value) ! 302: ! 303: # ------------ printer --------------- ! 304: def get_printer(self): ! 305: return self.get("[Printer]", "bEnablePrinting") ! 306: ! 307: def set_printer(self, value): ! 308: self.set("[Printer]", "bEnablePrinting", value) ! 309: self._hatari.set_device("printer", value) ! 310: ! 311: # ------------ RS232 --------------- ! 312: def get_rs232(self): ! 313: return self.get("[RS232]", "bEnableRS232") ! 314: ! 315: def set_rs232(self, value): ! 316: self.set("[RS232]", "bEnableRS232", value) ! 317: self._hatari.set_device("rs232", value) ! 318: ! 319: # ------------ machine --------------- ! 320: def get_machine_types(self): ! 321: return ("ST", "STE", "TT", "Falcon") ! 322: ! 323: def get_machine(self): ! 324: return self.get("[System]", "nMachineType") ! 325: ! 326: def set_machine(self, value): ! 327: self.set("[System]", "nMachineType", value) ! 328: self._change_option("--machine %s" % ("st", "ste", "tt", "falcon")[value]) ! 329: ! 330: # ------------ compatible --------------- ! 331: def get_compatible(self): ! 332: return self.get("[System]", "bCompatibleCpu") ! 333: ! 334: def set_compatible(self, value): ! 335: self.set("[System]", "bCompatibleCpu", value) ! 336: self._change_option("--compatible %s" % str(value)) ! 337: ! 338: # ------------ Timer-D --------------- ! 339: def get_timerd(self): ! 340: return self.get("[System]", "bPatchTimerD") ! 341: ! 342: def set_timerd(self, value): ! 343: self.set("[System]", "bPatchTimerD", value) ! 344: self._change_option("--timer-d %s" % str(value)) ! 345: ! 346: # ------------ fastforward --------------- ! 347: def get_fastforward(self): ! 348: return self.get("[System]", "bFastForward") ! 349: ! 350: def set_fastforward(self, value): ! 351: self.set("[System]", "bFastForward", value) ! 352: self._change_option("--fast-forward %s" % str(value)) ! 353: ! 354: # ------------ sound --------------- ! 355: def get_sound_values(self): ! 356: # 48kHz, 44.1kHz and STE/TT/Falcon DMA 50066Hz divisable values ! 357: return ("6000", "6258", "8000", "11025", "12000", "12517", ! 358: "16000", "22050", "24000", "25033", "32000", ! 359: "48000", "44100", "50066") ! 360: ! 361: def get_sound(self): ! 362: enabled = self.get("[Sound]", "bEnableSound") ! 363: hz = str(self.get("[Sound]", "nPlaybackFreq")) ! 364: return (enabled, hz) ! 365: ! 366: def set_sound(self, enabled, hz): ! 367: # map get_sound_values() index to Hatari config ! 368: try: ! 369: hz = int(hz) ! 370: except ValueError: ! 371: hz = 0 ! 372: if hz < 6000 or hz > 50066: ! 373: hz = 44100 ! 374: self.set("[Sound]", "nPlaybackFreq", hz) ! 375: self.set("[Sound]", "bEnableSound", enabled) ! 376: value = str(hz) ! 377: # and to cli option ! 378: if enabled: ! 379: self._change_option("--sound %s" % value) ! 380: else: ! 381: self._change_option("--sound off") ! 382: return value ! 383: ! 384: ! 385: # ----------- joystick -------------- ! 386: def get_joystick_types(self): ! 387: return ("Disabled", "Real joystick", "Keyboard") ! 388: ! 389: def get_joystick_names(self): ! 390: return ( ! 391: "ST Joystick 0", ! 392: "ST Joystick 1", ! 393: "STE Joypad A", ! 394: "STE Joypad B", ! 395: "Parport stick 1", ! 396: "Parport stick 2" ! 397: ) ! 398: ! 399: def get_joystick(self, port): ! 400: # return index to get_joystick_values() array ! 401: return self.get("[Joystick%d]" % port, "nJoystickMode") ! 402: ! 403: def set_joystick(self, port, value): ! 404: # map get_sound_values() index to Hatari config ! 405: self.set("[Joystick%d]" % port, "nJoystickMode", value) ! 406: joytype = ("none", "real", "keys")[value] ! 407: self._change_option("--joy%d %s" % (port, joytype)) ! 408: ! 409: # ------------ floppy image dir --------------- ! 410: def get_floppydir(self): ! 411: return self.get("[Floppy]", "szDiskImageDirectory") ! 412: ! 413: def set_floppydir(self, path): ! 414: return self.set("[Floppy]", "szDiskImageDirectory", path) ! 415: ! 416: # ------------ floppy disk images --------------- ! 417: def get_floppy(self, drive): ! 418: return self.get("[Floppy]", "szDisk%cFileName" % ("A", "B")[drive]) ! 419: ! 420: def set_floppy(self, drive, filename): ! 421: self.set("[Floppy]", "szDisk%cFileName" % ("A", "B")[drive], filename) ! 422: self._change_option("--disk-%c %s" % (("a", "b")[drive], filename)) ! 423: ! 424: # ------------ use harddisk --------------- ! 425: def get_use_harddisk(self): ! 426: return self.get("[HardDisk]", "bUseHardDiskDirectory") ! 427: ! 428: def set_use_harddisk(self, value): ! 429: self.set("[HardDisk]", "bUseHardDiskDirectory", value) ! 430: # TODO: add to hatari option for this ! 431: ! 432: # ------------ harddisk (dir) --------------- ! 433: def get_harddisk(self): ! 434: return self.get("[HardDisk]", "szHardDiskDirectory") ! 435: ! 436: def set_harddisk(self, dirname): ! 437: self.set("[HardDisk]", "szHardDiskDirectory", dirname) ! 438: if self.get_use_harddisk(): ! 439: self._change_option("--harddrive %s" % dirname) ! 440: ! 441: # ------------ TOS ROM --------------- ! 442: def get_tos(self): ! 443: return self.get("[ROM]", "szTosImageFileName") ! 444: ! 445: def set_tos(self, filename): ! 446: self.set("[ROM]", "szTosImageFileName", filename) ! 447: self._change_option("--tos %s" % filename) ! 448: ! 449: # ------------ memory --------------- ! 450: def get_memory_names(self): ! 451: # empty item in list shouldn't be shown, filter them out ! 452: return ("512kB", "1MB", "2MB", "4MB", "8MB", "14MB") ! 453: ! 454: def get_memory(self): ! 455: "return index to what get_memory_names() returns" ! 456: sizemap = (0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 5) ! 457: memsize = self.get("[Memory]", "nMemorySize") ! 458: if memsize >= 0 and memsize < len(sizemap): ! 459: return sizemap[memsize] ! 460: return 1 # default = 1BM ! 461: ! 462: def set_memory(self, idx): ! 463: # map memory item index to memory size ! 464: sizemap = (0, 1, 2, 4, 8, 14) ! 465: if idx >= 0 and idx < len(sizemap): ! 466: memsize = sizemap[idx] ! 467: else: ! 468: memsize = 1 ! 469: self.set("[Memory]", "nMemorySize", memsize) ! 470: self._change_option("--memsize %d" % memsize) ! 471: ! 472: # ------------ monitor --------------- ! 473: def get_monitor_types(self): ! 474: return ("Mono", "RGB", "VGA", "TV") ! 475: ! 476: def get_monitor(self): ! 477: return self.get("[Screen]", "nMonitorType") ! 478: ! 479: def set_monitor(self, value): ! 480: self.set("[Screen]", "nMonitorType", value) ! 481: self._change_option("--monitor %s" % ("mono", "rgb", "vga", "tv")[value]) ! 482: ! 483: # ------------ frameskip --------------- ! 484: def get_frameskip_names(self): ! 485: return ( ! 486: "Disabled", ! 487: "1 frame", ! 488: "2 frames", ! 489: "3 frames", ! 490: "4 frames", ! 491: "Automatic" ! 492: ) ! 493: ! 494: def get_frameskip(self): ! 495: fs = self.get("[Screen]", "nFrameSkips") ! 496: print "Frameskip", fs ! 497: if fs < 0 or fs > 5: ! 498: return 5 ! 499: return fs ! 500: ! 501: def set_frameskip(self, value): ! 502: value = int(value) # guarantee correct type ! 503: self.set("[Screen]", "nFrameSkips", value) ! 504: self._change_option("--frameskips %d" % value) ! 505: ! 506: # ------------ spec512 --------------- ! 507: def get_spec512threshold(self): ! 508: return self.get("[Screen]", "nSpec512Threshold") ! 509: ! 510: def set_spec512threshold(self, value): ! 511: value = int(value) # guarantee correct type ! 512: self.set("[Screen]", "nSpec512Threshold", value) ! 513: self._change_option("--spec512 %d" % value) ! 514: ! 515: # ------------ show borders --------------- ! 516: def get_borders(self): ! 517: return self.get("[Screen]", "bAllowOverscan") ! 518: ! 519: def set_borders(self, value): ! 520: self.set("[Screen]", "bAllowOverscan", value) ! 521: self._change_option("--borders %s" % str(value)) ! 522: ! 523: # ------------ show statusbar --------------- ! 524: def get_statusbar(self): ! 525: return self.get("[Screen]", "bShowStatusbar") ! 526: ! 527: def set_statusbar(self, value): ! 528: self.set("[Screen]", "bShowStatusbar", value) ! 529: self._change_option("--statusbar %s" % str(value)) ! 530: ! 531: # ------------ show led --------------- ! 532: def get_led(self): ! 533: return self.get("[Screen]", "bShowDriveLed") ! 534: ! 535: def set_led(self, value): ! 536: self.set("[Screen]", "bShowDriveLed", value) ! 537: self._change_option("--drive-led %s" % str(value)) ! 538: ! 539: # ------------ use zoom --------------- ! 540: def get_zoom(self): ! 541: return self.get("[Screen]", "bZoomLowRes") ! 542: ! 543: def set_zoom(self, value): ! 544: self.set("[Screen]", "bZoomLowRes", value) ! 545: if value: ! 546: zoom = 2 ! 547: else: ! 548: zoom = 1 ! 549: self._change_option("--zoom %d" % zoom) ! 550: ! 551: # ------------ configured Hatari window size --------------- ! 552: def get_window_size(self): ! 553: if self.get("[Screen]", "bFullScreen"): ! 554: print "WARNING: don't start Hatari UI with fullscreened Hatari!" ! 555: ! 556: # VDI resolution? ! 557: if self.get("[Screen]", "bUseExtVdiResolutions"): ! 558: width = self.get("[Screen]", "nVdiWidth") ! 559: height = self.get("[Screen]", "nVdiHeight") ! 560: return (width, height) ! 561: ! 562: # window sizes for other than ST & STE can differ ! 563: if self.get("[System]", "nMachineType") not in (0, 1): ! 564: print "WARNING: neither ST nor STE machine, window size inaccurate!" ! 565: ! 566: # mono monitor? ! 567: if self.get("[Screen]", "nMonitorType") == 0: ! 568: return (640, 400) ! 569: ! 570: # no, color ! 571: width = 320 ! 572: height = 200 ! 573: # add overscan borders? ! 574: if self.get("[Screen]", "bAllowOverscan"): ! 575: # max size with overscan borders ! 576: width += self.get("[Screen]", "nWindowBorderPixelsLeft") ! 577: width += self.get("[Screen]", "nWindowBorderPixelsRight") ! 578: height = 29 + 200 + self.get("[Screen]", "nWindowBorderPixelsBottom") ! 579: # statusbar? ! 580: if self.get("[Screen]", "bShowStatusbar"): ! 581: height += 10 ! 582: # zoomed? ! 583: if self.get("[Screen]", "bZoomLowRes"): ! 584: width *= 2 ! 585: height *= 2 ! 586: ! 587: return (width, height)
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.