--- hatari/python-ui/hatari.py 2019/04/09 08:48:23 1.1.1.2 +++ hatari/python-ui/hatari.py 2019/04/09 08:57:22 1.1.1.9 @@ -3,7 +3,7 @@ # Classes for Hatari emulator instance and mapping its congfiguration # variables with its command line option. # -# Copyright (C) 2008-2010 by Eero Tamminen +# Copyright (C) 2008-2016 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 @@ -48,10 +48,31 @@ class Hatari: def is_compatible(self): "check Hatari compatibility and return error string if it's not" - for line in os.popen(self.hataribin + " -h").readlines(): + error = "Hatari not found or it doesn't support the required --control-socket option!" + pipe = os.popen(self.hataribin + " -h") + for line in pipe.readlines(): if line.find("--control-socket") >= 0: - return None - return "Hatari not found or it doesn't support the required --control-socket option!" + error = None + break + try: + pipe.close() + except IOError: + pass + return error + + def is_winuae(self): + "check whether Hatari has WinUAE CPU core (=more features) or oldUAE one" + result = False + pipe = os.popen(self.hataribin + " -h") + for line in pipe.readlines(): + if line.find("--mmu") >= 0: + result = True + break + try: + pipe.close() + except IOError: + pass + return result def save_config(self): os.popen(self.hataribin + " --saveconfig") @@ -70,7 +91,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): @@ -144,12 +165,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): @@ -171,45 +192,44 @@ 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 + if self.control: + self.control.close() + self.control = None return False return True - def run(self, extra_args = None, parent_win = None): - "run([parent window][,embedding args]), runs Hatari" + def run(self, extra_args = None, parent_id = None): + "run([embedding args][,parent window ID]), runs 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 - if parent_win: - self._set_embed_env(env, parent_win) + if parent_id: + self._set_embed_env(env, parent_id) # callers need to take care of confirming quitting args = [self.hataribin, "--confirm-quit", "off"] if self.server: 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): - if sys.platform == 'win32': - win_id = parent_win.handle - else: - win_id = parent_win.xid + def _set_embed_env(self, env, win_id): # tell SDL to use given widget's window #env["SDL_WINDOWID"] = str(win_id) @@ -225,9 +245,9 @@ class Hatari: def kill(self): "kill(), kill Hatari if it's running" - if self.pid: + if self.is_running(): 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() @@ -257,16 +277,25 @@ class HatariConfigMapping(ConfigStore): "printout": ("[Printer]", "szPrintToFileName", "Printer output"), "soundout": ("[Sound]", "szYMCaptureFileName", "Sound output") } + has_modeltype = True # from v2.0 onwards + has_keepstres = True # only with SDL1 "access methods to Hatari configuration file variables and command line options" def __init__(self, hatari): - userconfdir = ".hatari" - ConfigStore.__init__(self, userconfdir) + confdirs = [".config/hatari", ".hatari"] + ConfigStore.__init__(self, confdirs) conffilename = "hatari.cfg" self.load(self.get_filepath(conffilename)) self._hatari = hatari self._lock_updates = False + self._desktop_w = 0 + self._desktop_h = 0 self._options = [] + self._winuae = hatari.is_winuae() + # initialize has_* attribs for things that may not be anymore + # valid on Hatari config file and/or command line + self.get_machine() + self.get_desktop_st() def validate(self): "exception is thrown if the loaded configuration isn't compatible" @@ -287,7 +316,10 @@ class HatariConfigMapping(ConfigStore): else: getattr(self, method)() - def _change_option(self, option): + def _change_option(self, option, quoted = None): + "handle option changing, and quote spaces for quoted part of it" + if quoted: + option = "%s %s" % (option, quoted.replace(" ", "\\ ")) if self._lock_updates: self._options.append(option) else: @@ -308,7 +340,7 @@ class HatariConfigMapping(ConfigStore): # ------------ paths --------------- def get_paths(self): paths = [] - for key, item in self._paths.items(): + for key, item in list(self._paths.items()): paths.append((key, self.get(item[0], item[1]), item[2])) return paths @@ -343,23 +375,45 @@ class HatariConfigMapping(ConfigStore): # ------------ machine --------------- def get_machine_types(self): - return ("ST", "STE", "TT", "Falcon") + if self.has_modeltype: + return ("ST", "MegaST", "STE", "MegaSTE", "TT", "Falcon") + else: + return ("ST", "STE", "TT", "Falcon") def get_machine(self): - return self.get("[System]", "nMachineType") + try: + return self.get("[System]", "nModelType") + except KeyError: + self.has_modeltype = False + return self.get("[System]", "nMachineType") + + def has_accurate_winsize(self): + if self.has_modeltype: + return (self.get_machine() < 4) + else: + return (self.get_machine() < 2) def set_machine(self, value): - self.set("[System]", "nMachineType", value) - self._change_option("--machine %s" % ("st", "ste", "tt", "falcon")[value]) + if self.has_modeltype: + self.set("[System]", "nModelType", value) + self._change_option("--machine %s" % ("st", "megast", "ste", "megaste", "tt", "falcon")[value]) + else: + 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") + if self._winuae: + return ("68000", "68010", "68020", "68E030", "68040", "68060") + else: + return ("68000", "68010", "68020", "68EC030+FPU", "68040") def get_cpulevel(self): return self.get("[System]", "nCpuLevel") def set_cpulevel(self, value): + if value == 5: # WinUAE 060 + value = 6 self.set("[System]", "nCpuLevel", value) self._change_option("--cpulevel %d" % value) @@ -374,7 +428,7 @@ class HatariConfigMapping(ConfigStore): 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 + print("WARNING: CPU clock idx %d, clock fixed to 8 Mhz" % value) value = 8 else: value = clocks[value] @@ -421,32 +475,60 @@ class HatariConfigMapping(ConfigStore): # 48kHz, 44.1kHz and STE/TT/Falcon DMA 50066Hz divisable values return ("6000", "6258", "8000", "11025", "12000", "12517", "16000", "22050", "24000", "25033", "32000", - "48000", "44100", "50066") + "44100", "48000", "50066") def get_sound(self): enabled = self.get("[Sound]", "bEnableSound") hz = str(self.get("[Sound]", "nPlaybackFreq")) - return (enabled, hz) + idx = self.get_sound_values().index(hz) + return (enabled, idx) - def set_sound(self, enabled, hz): + def set_sound(self, enabled, idx): # map get_sound_values() index to Hatari config - try: - hz = int(hz) - except ValueError: - hz = 0 - if hz < 6000 or hz > 50066: - hz = 44100 - self.set("[Sound]", "nPlaybackFreq", hz) + hz = self.get_sound_values()[idx] + self.set("[Sound]", "nPlaybackFreq", int(hz)) self.set("[Sound]", "bEnableSound", enabled) - value = str(hz) # and to cli option if enabled: - self._change_option("--sound %s" % value) + self._change_option("--sound %s" % hz) else: self._change_option("--sound off") - return value - - + + def get_ymmixer_types(self): + return ("linear", "table", "model") + + def get_ymmixer(self): + # values for types are start from 1, not 0 + return self.get("[Sound]", "YmVolumeMixing")-1 + + def set_ymmixer(self, value): + self.set("[Sound]", "YmVolumeMixing", value+1) + self._change_option("--ym-mixing %s" % self.get_ymmixer_types()[value]) + + def get_bufsize(self): + return self.get("[Sound]", "nSdlAudioBufferSize") + + def set_bufsize(self, value): + value = int(value) + if value < 10: value = 10 + if value > 100: value = 100 + self.set("[Sound]", "nSdlAudioBufferSize", value) + self._change_option("--sound-buffer-size %d" % value) + + def get_sync(self): + return self.get("[Sound]", "bEnableSoundSync") + + def set_sync(self, value): + self.set("[Sound]", "bEnableSoundSync", value) + self._change_option("--sound-sync %s" % str(value)) + + def get_mic(self): + return self.get("[Sound]", "bEnableMicrophone") + + def set_mic(self, value): + self.set("[Sound]", "bEnableMicrophone", value) + self._change_option("--mic %s" % str(value)) + # ----------- joystick -------------- def get_joystick_types(self): return ("Disabled", "Real joystick", "Keyboard") @@ -471,28 +553,51 @@ class HatariConfigMapping(ConfigStore): joytype = ("none", "real", "keys")[value] self._change_option("--joy%d %s" % (port, joytype)) - # ------------ floppy image dir --------------- + # ------------ floppy handling --------------- def get_floppydir(self): return self.get("[Floppy]", "szDiskImageDirectory") def set_floppydir(self, path): return self.set("[Floppy]", "szDiskImageDirectory", path) - # ------------ floppy disk images --------------- def get_floppy(self, drive): return self.get("[Floppy]", "szDisk%cFileName" % ("A", "B")[drive]) def set_floppy(self, drive, filename): self.set("[Floppy]", "szDisk%cFileName" % ("A", "B")[drive], filename) - self._change_option("--disk-%c %s" % (("a", "b")[drive], filename)) + self._change_option("--disk-%c" % ("a", "b")[drive], str(filename)) + + def get_floppy_drives(self): + return (self.get("[Floppy]", "EnableDriveA"), self.get("[Floppy]", "EnableDriveB")) - # ------------ 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)) + def set_floppy_drives(self, drives): + idx = 0 + for drive in ("A", "B"): + value = drives[idx] + self.set("[Floppy]", "EnableDrive%c" % drive, value) + self._change_option("--drive-%c %s" % (drive.lower(), str(value))) + idx += 1 + + def get_fastfdc(self): + return self.get("[Floppy]", "FastFloppy") + + def set_fastfdc(self, value): + self.set("[Floppy]", "FastFloppy", value) + self._change_option("--fastfdc %s" % str(value)) + + def get_doublesided(self): + driveA = self.get("[Floppy]", "DriveA_NumberOfHeads") + driveB = self.get("[Floppy]", "DriveB_NumberOfHeads") + if driveA > 1 or driveB > 1: + return True + return False + + def set_doublesided(self, value): + if value: sides = 2 + else: sides = 1 + for drive in ("A", "B"): + self.set("[Floppy]", "Drive%c_NumberOfHeads" % drive, sides) + self._change_option("--drive-%c-heads %d" % (drive.lower(), sides)) # ------------- disk protection ------------- def get_protection_types(self): @@ -512,16 +617,41 @@ class HatariConfigMapping(ConfigStore): self.set("[HardDisk]", "nWriteProtection", value) self._change_option("--protect-hd %s" % self.get_protection_types()[value]) - # ------------ GEMDOS HD (dir) --------------- - def get_gemdos_dir(self): + # ------------ GEMDOS HD (dir) emulation --------------- + def get_hd_cases(self): + return ("No conversion", "Upper case", "Lower case") + + def get_hd_case(self): + return self.get("[HardDisk]", "nGemdosCase") + + def set_hd_case(self, value): + values = ("off", "upper", "lower") + self.set("[HardDisk]", "nGemdosCase", value) + self._change_option("--gemdos-case %s" % values[value]) + + def get_hd_drives(self): + return ['skip ACSI/IDE'] + [("%c:" % x) for x in range(ord('C'), ord('Z')+1)] + + def get_hd_drive(self): + return self.get("[HardDisk]", "nGemdosDrive") + 1 + + def set_hd_drive(self, value): + value -= 1 + self.set("[HardDisk]", "nGemdosDrive", value) + drive = chr(ord('C') + value) + if value < 0: + drive = "skip" + self._change_option("--gemdos-drive %s" % drive) + + def get_hd_dir(self): self.get("[HardDisk]", "bUseHardDiskDirectory") # for validation return self.get("[HardDisk]", "szHardDiskDirectory") - def set_gemdos_dir(self, dirname): + def set_hd_dir(self, dirname): if dirname and os.path.isdir(dirname): self.set("[HardDisk]", "bUseHardDiskDirectory", True) self.set("[HardDisk]", "szHardDiskDirectory", dirname) - self._change_option("--harddrive %s" % dirname) + self._change_option("--harddrive", str(dirname)) # ------------ ACSI HD (file) --------------- def get_acsi_image(self): @@ -532,7 +662,7 @@ class HatariConfigMapping(ConfigStore): if filename and os.path.isfile(filename): self.set("[HardDisk]", "bUseHardDiskImage", True) self.set("[HardDisk]", "szHardDiskImage", filename) - self._change_option("--acsi %s" % filename) + self._change_option("--acsi", str(filename)) # ------------ IDE master (file) --------------- def get_idemaster_image(self): @@ -543,7 +673,7 @@ class HatariConfigMapping(ConfigStore): if filename and os.path.isfile(filename): self.set("[HardDisk]", "bUseIdeMasterHardDiskImage", True) self.set("[HardDisk]", "szIdeMasterHardDiskImage", filename) - self._change_option("--ide-master %s" % filename) + self._change_option("--ide-master", str(filename)) # ------------ IDE slave (file) --------------- def get_ideslave_image(self): @@ -554,7 +684,7 @@ class HatariConfigMapping(ConfigStore): if filename and os.path.isfile(filename): self.set("[HardDisk]", "bUseIdeSlaveHardDiskImage", True) self.set("[HardDisk]", "szIdeSlaveHardDiskImage", filename) - self._change_option("--ide-slave %s" % filename) + self._change_option("--ide-slave", str(filename)) # ------------ TOS ROM --------------- def get_tos(self): @@ -562,7 +692,7 @@ class HatariConfigMapping(ConfigStore): def set_tos(self, filename): self.set("[ROM]", "szTosImageFileName", filename) - self._change_option("--tos %s" % filename) + self._change_option("--tos", str(filename)) # ------------ memory --------------- def get_memory_names(self): @@ -587,6 +717,23 @@ class HatariConfigMapping(ConfigStore): self.set("[Memory]", "nMemorySize", memsize) self._change_option("--memsize %d" % memsize) + def get_ttram(self): + return self.get("[Memory]", "nTTRamSize") + + def set_ttram(self, memsize): + # guarantee correct type (Gtk float -> config int) + memsize = int(memsize) + self.set("[Memory]", "nTTRamSize", memsize) + self._change_option("--ttram %d" % memsize) + if memsize: + # TT-RAM need 32-bit addressing (i.e. disable 24-bit) + self.set("[System]", "bAddressSpace24", False) + self._change_option("--addr24 off") + else: + # switch 24-bit addressing back for compatibility + self.set("[System]", "bAddressSpace24", True) + self._change_option("--addr24 on", False) + # ------------ monitor --------------- def get_monitor_types(self): return ("Mono", "RGB", "VGA", "TV") @@ -611,7 +758,6 @@ class HatariConfigMapping(ConfigStore): def get_frameskip(self): fs = self.get("[Screen]", "nFrameSkips") - print "Frameskip", fs if fs < 0 or fs > 5: return 5 return fs @@ -621,6 +767,14 @@ class HatariConfigMapping(ConfigStore): self.set("[Screen]", "nFrameSkips", value) self._change_option("--frameskips %d" % value) + # ------------ VBL slowdown --------------- + def get_slowdown_names(self): + return ("Disabled", "2x", "3x", "4x", "5x", "6x", "8x") + + def set_slowdown(self, value): + value = 1 + int(value) + self._change_option("--slowdown %d" % value) + # ------------ spec512 --------------- def get_spec512threshold(self): return self.get("[Screen]", "nSpec512Threshold") @@ -630,6 +784,35 @@ 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)) + + # --------- keep desktop res - st ------ + def get_desktop_st(self): + try: + return self.get("[Screen]", "bKeepResolutionST") + except KeyError: + self.has_keepstres = False + return False + + def set_desktop_st(self, value): + if self.has_keepstres: + self.set("[Screen]", "bKeepResolutionST", value) + self._change_option("--desktop-st %s" % str(value)) + + # ------------ force max --------------- + def get_force_max(self): + return self.get("[Screen]", "bForceMax") + + def set_force_max(self, value): + self.set("[Screen]", "bForceMax", value) + self._change_option("--force-max %s" % str(value)) + # ------------ show borders --------------- def get_borders(self): return self.get("[Screen]", "bAllowOverscan") @@ -646,6 +829,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") @@ -663,9 +854,20 @@ class HatariConfigMapping(ConfigStore): self._change_option("--aspect %s" % str(value)) # ------------ max window size --------------- + def set_desktop_size(self, w, h): + self._desktop_w = w + self._desktop_h = h + + def get_desktop_size(self): + return (self._desktop_w, self._desktop_h) + def get_max_size(self): w = self.get("[Screen]", "nMaxWidth") h = self.get("[Screen]", "nMaxHeight") + # default to desktop size? + if not (w or h): + w = self._desktop_w + h = self._desktop_h return (w, h) def set_max_size(self, w, h): @@ -678,7 +880,7 @@ class HatariConfigMapping(ConfigStore): # TODO: remove once UI doesn't need this anymore def set_zoom(self, value): - print "Just setting Zoom, configuration doesn't anymore have setting for this." + print("Just setting Zoom, configuration doesn't anymore have setting for this.") if value: zoom = 2 else: @@ -688,7 +890,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"): @@ -697,11 +899,11 @@ class HatariConfigMapping(ConfigStore): return (width, height) # 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!" - videl = True - else: + if self.has_accurate_winsize(): videl = False + else: + print("WARNING: With Videl, window size is unknown -> may be inaccurate!") + videl = True # mono monitor? if self.get_monitor() == 0: