--- hatari/python-ui/hatari.py 2019/04/09 08:46:58 1.1 +++ hatari/python-ui/hatari.py 2019/04/09 08:49:05 1.1.1.3 @@ -3,7 +3,7 @@ # Classes for Hatari emulator instance and mapping its congfiguration # variables with its command line option. # -# Copyright (C) 2008 by Eero Tamminen +# Copyright (C) 2008-2011 by Eero Tamminen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -41,18 +41,20 @@ class Hatari: self.hataribin = hataribin else: self.hataribin = "hatari" - self._assert_hatari_compatibility() self._create_server() self.control = None self.paused = False self.pid = 0 - def _assert_hatari_compatibility(self): + def is_compatible(self): + "check Hatari compatibility and return error string if it's not" for line in os.popen(self.hataribin + " -h").readlines(): if line.find("--control-socket") >= 0: - return - print "ERROR: Hatari not found or it doesn't support the required --control-socket option!" - sys.exit(-1) + return None + return "Hatari not found or it doesn't support the required --control-socket option!" + + def save_config(self): + os.popen(self.hataribin + " --saveconfig") def _create_server(self): if self.server: @@ -68,7 +70,7 @@ class Hatari: self.control.send(msg) return True else: - print "ERROR: no Hatari (control socket)" + print("ERROR: no Hatari (control socket)") return False def change_option(self, option): @@ -142,12 +144,12 @@ class Hatari: "get_lines(file) -> list of lines readable from given Hatari output file" # wait until data is available, then wait for some more # and only then the data can be read, otherwise its old - print "Request&wait data from Hatari..." + print("Request&wait data from Hatari...") select.select([fileobj], [], []) time.sleep(0.1) - print "...read the data lines" + print("...read the data lines") lines = fileobj.readlines() - print "".join(lines) + print("".join(lines)) return lines def enable_embed_info(self): @@ -169,8 +171,8 @@ class Hatari: return False try: os.waitpid(self.pid, os.WNOHANG) - except OSError, value: - print "Hatari PID %d had exited in the meanwhile:\n\t%s" % (self.pid, value) + except OSError as value: + print("Hatari PID %d had exited in the meanwhile:\n\t%s" % (self.pid, value)) self.pid = 0 return False return True @@ -180,15 +182,15 @@ class Hatari: # if parent_win given, embed Hatari to it pid = os.fork() if pid < 0: - print "ERROR: fork()ing Hatari failed!" + print("ERROR: fork()ing Hatari failed!") return if pid: # in parent self.pid = pid if self.server: - print "WAIT hatari to connect to control socket...", + print("WAIT hatari to connect to control socket...") (self.control, addr) = self.server.accept() - print "connected!" + print("connected!") else: # child runs Hatari env = os.environ @@ -200,7 +202,7 @@ class Hatari: args += ["--control-socket", self.controlpath] if extra_args: args += extra_args - print "RUN:", args + print("RUN:", args) os.execvpe(self.hataribin, args, env) def _set_embed_env(self, env, parent_win): @@ -225,7 +227,7 @@ class Hatari: "kill(), kill Hatari if it's running" if self.pid: os.kill(self.pid, signal.SIGKILL) - print "killed hatari with PID %d" % self.pid + print("killed hatari with PID %d" % self.pid) self.pid = 0 if self.control: self.control.close() @@ -238,8 +240,8 @@ class Hatari: # By default this doesn't allow setting any other configuration # variables than the ones that were read from the configuration # file i.e. you get an exception if configuration variables -# don't match to current Hatari. So before using this you should -# have saved Hatari configuration at least once. +# don't match to current Hatari. So before using this the current +# Hatari configuration should have been saved at least once. # # Because of some inconsistencies in the values (see e.g. sound), # this cannot just do these according to some mapping table, but @@ -257,11 +259,34 @@ class HatariConfigMapping(ConfigStore): } "access methods to Hatari configuration file variables and command line options" def __init__(self, hatari): - ConfigStore.__init__(self, "hatari.cfg") + userconfdir = ".hatari" + ConfigStore.__init__(self, userconfdir) + conffilename = "hatari.cfg" + self.load(self.get_filepath(conffilename)) + self._hatari = hatari self._lock_updates = False self._options = [] + def validate(self): + "exception is thrown if the loaded configuration isn't compatible" + for method in dir(self): + if '_' not in method: + continue + # check class getters + starts = method[:method.find("_")] + if starts != "get": + continue + # but ignore getters for other things than config + ends = method[method.rfind("_")+1:] + if ends in ("types", "names", "values", "changes", "checkpoint", "filepath"): + continue + if ends in ("floppy", "joystick"): + # use port '0' for checks + getattr(self, method)(0) + else: + getattr(self, method)() + def _change_option(self, option): if self._lock_updates: self._options.append(option) @@ -327,6 +352,46 @@ class HatariConfigMapping(ConfigStore): self.set("[System]", "nMachineType", value) self._change_option("--machine %s" % ("st", "ste", "tt", "falcon")[value]) + # ------------ CPU level --------------- + def get_cpulevel_types(self): + return ("68000", "68010", "68020", "68EC030+FPU", "68040") + + def get_cpulevel(self): + return self.get("[System]", "nCpuLevel") + + def set_cpulevel(self, value): + self.set("[System]", "nCpuLevel", value) + self._change_option("--cpulevel %d" % value) + + # ------------ CPU clock --------------- + def get_cpuclock_types(self): + return ("8 MHz", "16 MHz", "32 MHz") + + def get_cpuclock(self): + clocks = {8:0, 16: 1, 32:2} + return clocks[self.get("[System]", "nCpuFreq")] + + def set_cpuclock(self, value): + clocks = [8, 16, 32] + if value < 0 or value > 2: + print("WARNING: CPU clock idx %d, clock fixed to 8 Mhz" % value) + value = 8 + else: + value = clocks[value] + self.set("[System]", "nCpuFreq", value) + self._change_option("--cpuclock %d" % value) + + # ------------ DSP type --------------- + def get_dsp_types(self): + return ("None", "Dummy", "Emulated") + + def get_dsp(self): + return self.get("[System]", "nDSPType") + + def set_dsp(self, value): + self.set("[System]", "nDSPType", value) + self._change_option("--dsp %s" % ("none", "dummy", "emu")[value]) + # ------------ compatible --------------- def get_compatible(self): return self.get("[System]", "bCompatibleCpu") @@ -343,6 +408,14 @@ class HatariConfigMapping(ConfigStore): self.set("[System]", "bPatchTimerD", value) self._change_option("--timer-d %s" % str(value)) + # ------------ RTC --------------- + def get_rtc(self): + return self.get("[System]", "bRealTimeClock") + + def set_rtc(self, value): + self.set("[System]", "bRealTimeClock", value) + self._change_option("--rtc %s" % str(value)) + # ------------ fastforward --------------- def get_fastforward(self): return self.get("[System]", "bFastForward") @@ -421,22 +494,75 @@ class HatariConfigMapping(ConfigStore): self.set("[Floppy]", "szDisk%cFileName" % ("A", "B")[drive], filename) self._change_option("--disk-%c %s" % (("a", "b")[drive], filename)) - # ------------ use harddisk --------------- - def get_use_harddisk(self): - return self.get("[HardDisk]", "bUseHardDiskDirectory") - - def set_use_harddisk(self, value): - self.set("[HardDisk]", "bUseHardDiskDirectory", value) - # TODO: add to hatari option for this - - # ------------ harddisk (dir) --------------- - def get_harddisk(self): + # ------------ slow FDC access --------------- + def get_slowfdc(self): + return self.get("[Floppy]", "bSlowFloppy") + + def set_slowfdc(self, value): + self.set("[Floppy]", "bSlowFloppy", value) + self._change_option("--slowfdc %s" % str(value)) + + # ------------- disk protection ------------- + def get_protection_types(self): + return ("Off", "On", "Auto") + + def get_floppy_protection(self): + return self.get("[Floppy]", "nWriteProtection") + + def get_hd_protection(self): + return self.get("[HardDisk]", "nWriteProtection") + + def set_floppy_protection(self, value): + self.set("[Floppy]", "nWriteProtection", value) + self._change_option("--protect-floppy %s" % self.get_protection_types()[value]) + + def set_hd_protection(self, value): + self.set("[HardDisk]", "nWriteProtection", value) + self._change_option("--protect-hd %s" % self.get_protection_types()[value]) + + # ------------ GEMDOS HD (dir) --------------- + def get_gemdos_dir(self): + self.get("[HardDisk]", "bUseHardDiskDirectory") # for validation return self.get("[HardDisk]", "szHardDiskDirectory") - - def set_harddisk(self, dirname): + + def set_gemdos_dir(self, dirname): + if dirname and os.path.isdir(dirname): + self.set("[HardDisk]", "bUseHardDiskDirectory", True) self.set("[HardDisk]", "szHardDiskDirectory", dirname) - if self.get_use_harddisk(): - self._change_option("--harddrive %s" % dirname) + self._change_option("--harddrive %s" % dirname) + + # ------------ ACSI HD (file) --------------- + def get_acsi_image(self): + self.get("[HardDisk]", "bUseHardDiskImage") # for validation + return self.get("[HardDisk]", "szHardDiskImage") + + def set_acsi_image(self, filename): + if filename and os.path.isfile(filename): + self.set("[HardDisk]", "bUseHardDiskImage", True) + self.set("[HardDisk]", "szHardDiskImage", filename) + self._change_option("--acsi %s" % filename) + + # ------------ IDE master (file) --------------- + def get_idemaster_image(self): + self.get("[HardDisk]", "bUseIdeMasterHardDiskImage") # for validation + return self.get("[HardDisk]", "szIdeMasterHardDiskImage") + + def set_idemaster_image(self, filename): + if filename and os.path.isfile(filename): + self.set("[HardDisk]", "bUseIdeMasterHardDiskImage", True) + self.set("[HardDisk]", "szIdeMasterHardDiskImage", filename) + self._change_option("--ide-master %s" % filename) + + # ------------ IDE slave (file) --------------- + def get_ideslave_image(self): + self.get("[HardDisk]", "bUseIdeSlaveHardDiskImage") # for validation + return self.get("[HardDisk]", "szIdeSlaveHardDiskImage") + + def set_ideslave_image(self, filename): + if filename and os.path.isfile(filename): + self.set("[HardDisk]", "bUseIdeSlaveHardDiskImage", True) + self.set("[HardDisk]", "szIdeSlaveHardDiskImage", filename) + self._change_option("--ide-slave %s" % filename) # ------------ TOS ROM --------------- def get_tos(self): @@ -493,7 +619,7 @@ class HatariConfigMapping(ConfigStore): def get_frameskip(self): fs = self.get("[Screen]", "nFrameSkips") - print "Frameskip", fs + print("Frameskip", fs) if fs < 0 or fs > 5: return 5 return fs @@ -512,6 +638,14 @@ class HatariConfigMapping(ConfigStore): self.set("[Screen]", "nSpec512Threshold", value) self._change_option("--spec512 %d" % value) + # --------- keep desktop res ----------- + def get_desktop(self): + return self.get("[Screen]", "bKeepResolution") + + def set_desktop(self, value): + self.set("[Screen]", "bKeepResolution", value) + self._change_option("--desktop %s" % str(value)) + # ------------ show borders --------------- def get_borders(self): return self.get("[Screen]", "bAllowOverscan") @@ -528,6 +662,14 @@ class HatariConfigMapping(ConfigStore): self.set("[Screen]", "bShowStatusbar", value) self._change_option("--statusbar %s" % str(value)) + # ------------ crop statusbar --------------- + def get_crop(self): + return self.get("[Screen]", "bCrop") + + def set_crop(self, value): + self.set("[Screen]", "bCrop", value) + self._change_option("--crop %s" % str(value)) + # ------------ show led --------------- def get_led(self): return self.get("[Screen]", "bShowDriveLed") @@ -536,12 +678,31 @@ class HatariConfigMapping(ConfigStore): self.set("[Screen]", "bShowDriveLed", value) self._change_option("--drive-led %s" % str(value)) - # ------------ use zoom --------------- - def get_zoom(self): - return self.get("[Screen]", "bZoomLowRes") - + # ------------ monitor aspect ratio --------------- + def get_aspectcorrection(self): + return self.get("[Screen]", "bAspectCorrect") + + def set_aspectcorrection(self, value): + self.set("[Screen]", "bAspectCorrect", value) + self._change_option("--aspect %s" % str(value)) + + # ------------ max window size --------------- + def get_max_size(self): + w = self.get("[Screen]", "nMaxWidth") + h = self.get("[Screen]", "nMaxHeight") + return (w, h) + + def set_max_size(self, w, h): + # guarantee correct type (Gtk float -> config int) + w = int(w); h = int(h) + self.set("[Screen]", "nMaxWidth", w) + self.set("[Screen]", "nMaxHeight", h) + self._change_option("--max-width %d" % w) + self._change_option("--max-height %d" % h) + + # TODO: remove once UI doesn't need this anymore def set_zoom(self, value): - self.set("[Screen]", "bZoomLowRes", value) + print("Just setting Zoom, configuration doesn't anymore have setting for this.") if value: zoom = 2 else: @@ -551,7 +712,7 @@ class HatariConfigMapping(ConfigStore): # ------------ configured Hatari window size --------------- def get_window_size(self): if self.get("[Screen]", "bFullScreen"): - print "WARNING: don't start Hatari UI with fullscreened Hatari!" + print("WARNING: don't start Hatari UI with fullscreened Hatari!") # VDI resolution? if self.get("[Screen]", "bUseExtVdiResolutions"): @@ -561,27 +722,40 @@ class HatariConfigMapping(ConfigStore): # window sizes for other than ST & STE can differ if self.get("[System]", "nMachineType") not in (0, 1): - print "WARNING: neither ST nor STE machine, window size inaccurate!" + print("WARNING: neither ST nor STE machine, window size inaccurate!") + videl = True + else: + videl = False # mono monitor? - if self.get("[Screen]", "nMonitorType") == 0: + if self.get_monitor() == 0: return (640, 400) # no, color width = 320 height = 200 - # add overscan borders? - if self.get("[Screen]", "bAllowOverscan"): - # max size with overscan borders - width += self.get("[Screen]", "nWindowBorderPixelsLeft") - width += self.get("[Screen]", "nWindowBorderPixelsRight") - height = 29 + 200 + self.get("[Screen]", "nWindowBorderPixelsBottom") # statusbar? - if self.get("[Screen]", "bShowStatusbar"): - height += 10 - # zoomed? - if self.get("[Screen]", "bZoomLowRes"): + if self.get_statusbar(): + sbar = 12 + height += sbar + else: + sbar = 0 + # zoom? + maxw, maxh = self.get_max_size() + if 2*width <= maxw and 2*height <= maxh: width *= 2 height *= 2 + zoom = 2 + else: + zoom = 1 + # overscan borders? + if self.get_borders() and not videl: + # properly aligned borders on top of zooming + leftx = (maxw-width)/zoom + borderx = 2*(min(48,leftx/2)/16)*16 + lefty = (maxh-height)/zoom + bordery = min(29+47, lefty) + width += zoom*borderx + height += zoom*bordery return (width, height)