Annotation of hatari/python-ui/hatari.py, revision 1.1.1.10

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: #
1.1.1.10! root        6: # Copyright (C) 2008-2019 by Eero Tamminen
1.1       root        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: 
1.1.1.10! root       26: # Python v2:
        !            27: # - lacks Python v3 encoding arg for bytes()
        !            28: if str is bytes:
        !            29:     def bytes(s, encoding):
        !            30:         return s
        !            31: 
1.1       root       32: 
                     33: # Running Hatari instance
                     34: class Hatari:
                     35:     "running hatari instance and methods for communicating with it"
                     36:     basepath = "/tmp/hatari-ui-" + str(os.getpid())
                     37:     logpath = basepath + ".log"
                     38:     tracepath = basepath + ".trace"
                     39:     debugpath = basepath + ".debug"
                     40:     controlpath = basepath + ".socket"
                     41:     server = None # singleton due to path being currently one per user
                     42: 
                     43:     def __init__(self, hataribin = None):
                     44:         # collect hatari process zombies without waitpid()
                     45:         signal.signal(signal.SIGCHLD, signal.SIG_IGN)
                     46:         if hataribin:
                     47:             self.hataribin = hataribin
                     48:         else:
                     49:             self.hataribin = "hatari"
                     50:         self._create_server()
                     51:         self.control = None
                     52:         self.paused = False
                     53:         self.pid = 0
                     54: 
1.1.1.2   root       55:     def is_compatible(self):
                     56:         "check Hatari compatibility and return error string if it's not"
1.1.1.6   root       57:         error = "Hatari not found or it doesn't support the required --control-socket option!"
                     58:         pipe = os.popen(self.hataribin + " -h")
                     59:         for line in pipe.readlines():
1.1       root       60:             if line.find("--control-socket") >= 0:
1.1.1.6   root       61:                 error = None
                     62:                 break
                     63:         try:
                     64:             pipe.close()
                     65:         except IOError:
                     66:             pass
                     67:         return error
1.1.1.2   root       68: 
1.1.1.9   root       69:     def is_winuae(self):
                     70:         "check whether Hatari has WinUAE CPU core (=more features) or oldUAE one"
                     71:         result = False
                     72:         pipe = os.popen(self.hataribin + " -h")
                     73:         for line in pipe.readlines():
                     74:             if line.find("--mmu") >= 0:
                     75:                 result = True
                     76:                 break
                     77:         try:
                     78:             pipe.close()
                     79:         except IOError:
                     80:             pass
                     81:         return result
                     82: 
1.1.1.2   root       83:     def save_config(self):
                     84:         os.popen(self.hataribin + " --saveconfig")
1.1       root       85: 
                     86:     def _create_server(self):
                     87:         if self.server:
                     88:             return
                     89:         self.server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
                     90:         if os.path.exists(self.controlpath):
                     91:             os.unlink(self.controlpath)
                     92:         self.server.bind(self.controlpath)
                     93:         self.server.listen(1)
1.1.1.10! root       94: 
1.1       root       95:     def _send_message(self, msg):
                     96:         if self.control:
1.1.1.10! root       97:             self.control.send(bytes(msg, "ASCII"))
1.1       root       98:             return True
                     99:         else:
1.1.1.3   root      100:             print("ERROR: no Hatari (control socket)")
1.1       root      101:             return False
1.1.1.10! root      102: 
1.1       root      103:     def change_option(self, option):
                    104:         "change_option(option), changes given Hatari cli option"
                    105:         return self._send_message("hatari-option %s\n" % option)
1.1.1.10! root      106: 
1.1       root      107:     def set_path(self, key, path):
                    108:         "set_path(key, path), sets path with given key"
                    109:         return self._send_message("hatari-path %s %s\n" % (key, path))
                    110: 
                    111:     def set_device(self, device, enabled):
                    112:         # needed because CLI options cannot disable devices, only enable
                    113:         "set_path(device, enabled), sets whether given device is enabled or not"
                    114:         if enabled:
                    115:             return self._send_message("hatari-enable %s\n" % device)
                    116:         else:
                    117:             return self._send_message("hatari-disable %s\n" % device)
1.1.1.10! root      118: 
1.1       root      119:     def trigger_shortcut(self, shortcut):
                    120:         "trigger_shortcut(shortcut), triggers given Hatari (keyboard) shortcut"
                    121:         return self._send_message("hatari-shortcut %s\n" % shortcut)
                    122: 
                    123:     def insert_event(self, event):
                    124:         "insert_event(event), synthetizes given key/mouse Atari event"
                    125:         return self._send_message("hatari-event %s\n" % event)
                    126: 
                    127:     def debug_command(self, cmd):
                    128:         "debug_command(command), runs given Hatari debugger command"
                    129:         return self._send_message("hatari-debug %s\n" % cmd)
                    130: 
                    131:     def pause(self):
                    132:         "pause(), pauses Hatari emulation"
                    133:         return self._send_message("hatari-stop\n")
                    134: 
                    135:     def unpause(self):
                    136:         "unpause(), continues Hatari emulation"
                    137:         return self._send_message("hatari-cont\n")
1.1.1.10! root      138: 
1.1       root      139:     def _open_output_file(self, hataricommand, option, path):
                    140:         if os.path.exists(path):
                    141:             os.unlink(path)
                    142:         # TODO: why fifo doesn't work properly (blocks forever on read or
                    143:         #       reads only byte at the time and stops after first newline)?
                    144:         #os.mkfifo(path)
                    145:         #raw_input("attach strace now, then press Enter\n")
1.1.1.10! root      146: 
1.1       root      147:         # ask Hatari to open/create the requested output file...
                    148:         hataricommand("%s %s" % (option, path))
                    149:         wait = 0.025
                    150:         # ...and wait for it to appear before returning it
                    151:         for i in range(0, 8):
                    152:             time.sleep(wait)
                    153:             if os.path.exists(path):
                    154:                 return open(path, "r")
                    155:             wait += wait
                    156:         return None
                    157: 
                    158:     def open_debug_output(self):
                    159:         "open_debug_output() -> file, opens Hatari debugger output file"
                    160:         return self._open_output_file(self.debug_command, "f", self.debugpath)
                    161: 
                    162:     def open_trace_output(self):
                    163:         "open_trace_output() -> file, opens Hatari tracing output file"
                    164:         return self._open_output_file(self.change_option, "--trace-file", self.tracepath)
                    165: 
                    166:     def open_log_output(self):
                    167:         "open_trace_output() -> file, opens Hatari debug log file"
                    168:         return self._open_output_file(self.change_option, "--log-file", self.logpath)
1.1.1.10! root      169: 
1.1       root      170:     def get_lines(self, fileobj):
                    171:         "get_lines(file) -> list of lines readable from given Hatari output file"
                    172:         # wait until data is available, then wait for some more
                    173:         # and only then the data can be read, otherwise its old
1.1.1.3   root      174:         print("Request&wait data from Hatari...")
1.1       root      175:         select.select([fileobj], [], [])
                    176:         time.sleep(0.1)
1.1.1.3   root      177:         print("...read the data lines")
1.1       root      178:         lines = fileobj.readlines()
1.1.1.3   root      179:         print("".join(lines))
1.1       root      180:         return lines
                    181: 
                    182:     def enable_embed_info(self):
                    183:         "enable_embed_info(), request embedded Hatari window ID change information"
                    184:         self._send_message("hatari-embed-info\n")
                    185: 
                    186:     def get_embed_info(self):
                    187:         "get_embed_info() -> (width, height), get embedded Hatari window size"
1.1.1.10! root      188:         width, height = self.control.recv(12).split(b"x")
1.1       root      189:         return (int(width), int(height))
                    190: 
                    191:     def get_control_socket(self):
                    192:         "get_control_socket() -> socket which can be checked for embed ID changes"
                    193:         return self.control
1.1.1.10! root      194: 
1.1       root      195:     def is_running(self):
                    196:         "is_running() -> bool, True if Hatari is running, False otherwise"
                    197:         if not self.pid:
                    198:             return False
                    199:         try:
                    200:             os.waitpid(self.pid, os.WNOHANG)
1.1.1.3   root      201:         except OSError as value:
                    202:             print("Hatari PID %d had exited in the meanwhile:\n\t%s" % (self.pid, value))
1.1       root      203:             self.pid = 0
1.1.1.5   root      204:             if self.control:
                    205:                 self.control.close()
                    206:                 self.control = None
1.1       root      207:             return False
                    208:         return True
1.1.1.10! root      209: 
1.1.1.9   root      210:     def run(self, extra_args = None, parent_id = None):
                    211:         "run([embedding args][,parent window ID]), runs Hatari"
1.1       root      212:         # if parent_win given, embed Hatari to it
                    213:         pid = os.fork()
                    214:         if pid < 0:
1.1.1.3   root      215:             print("ERROR: fork()ing Hatari failed!")
1.1       root      216:             return
                    217:         if pid:
                    218:             # in parent
                    219:             self.pid = pid
                    220:             if self.server:
1.1.1.3   root      221:                 print("WAIT hatari to connect to control socket...")
1.1       root      222:                 (self.control, addr) = self.server.accept()
1.1.1.3   root      223:                 print("connected!")
1.1       root      224:         else:
                    225:             # child runs Hatari
                    226:             env = os.environ
1.1.1.9   root      227:             if parent_id:
                    228:                 self._set_embed_env(env, parent_id)
1.1       root      229:             # callers need to take care of confirming quitting
                    230:             args = [self.hataribin, "--confirm-quit", "off"]
                    231:             if self.server:
                    232:                 args += ["--control-socket", self.controlpath]
                    233:             if extra_args:
                    234:                 args += extra_args
1.1.1.3   root      235:             print("RUN:", args)
1.1       root      236:             os.execvpe(self.hataribin, args, env)
                    237: 
1.1.1.9   root      238:     def _set_embed_env(self, env, win_id):
1.1       root      239:         # tell SDL to use given widget's window
                    240:         #env["SDL_WINDOWID"] = str(win_id)
                    241: 
                    242:         # above is broken: when SDL uses a window it hasn't created itself,
                    243:         # it for some reason doesn't listen to any events delivered to that
                    244:         # window nor implements XEMBED protocol to get them in a way most
                    245:         # friendly to embedder:
                    246:         #   http://standards.freedesktop.org/xembed-spec/latest/
                    247:         #
                    248:         # Instead we tell hatari to reparent itself after creating
                    249:         # its own window into this program widget window
                    250:         env["PARENT_WIN_ID"] = str(win_id)
                    251: 
                    252:     def kill(self):
                    253:         "kill(), kill Hatari if it's running"
1.1.1.5   root      254:         if self.is_running():
1.1       root      255:             os.kill(self.pid, signal.SIGKILL)
1.1.1.3   root      256:             print("killed hatari with PID %d" % self.pid)
1.1       root      257:             self.pid = 0
                    258:         if self.control:
                    259:             self.control.close()
                    260:             self.control = None
                    261: 
                    262: 
                    263: # Mapping of requested values both to Hatari configuration
                    264: # and command line options.
                    265: #
                    266: # By default this doesn't allow setting any other configuration
                    267: # variables than the ones that were read from the configuration
                    268: # file i.e. you get an exception if configuration variables
1.1.1.2   root      269: # don't match to current Hatari.  So before using this the current
                    270: # Hatari configuration should have been saved at least once.
1.1       root      271: #
                    272: # Because of some inconsistencies in the values (see e.g. sound),
                    273: # this cannot just do these according to some mapping table, but
                    274: # it needs actual method for (each) setting.
                    275: class HatariConfigMapping(ConfigStore):
                    276:     _paths = {
                    277:         "memauto": ("[Memory]", "szAutoSaveFileName", "Automatic memory snapshot"),
                    278:         "memsave": ("[Memory]", "szMemoryCaptureFileName", "Manual memory snapshot"),
                    279:         "midiin":  ("[Midi]", "sMidiInFileName", "Midi input"),
                    280:         "midiout": ("[Midi]", "sMidiOutFileName", "Midi output"),
1.1.1.10! root      281:         "rs232in": ("[RS232]", "szInFileName", "RS232 (MFP) I/O input"),
        !           282:         "rs232out":("[RS232]", "szOutFileName", "RS232 (MFP) I/O output"),
        !           283: #        "sccbin":  ("[RS232]", "sSccBInFileName", "RS232 (SCC-B) I/O input"),
        !           284:         "sccbout": ("[RS232]", "sSccBOutFileName", "RS232 (SCC-B) I/O output"),
        !           285:         "printout":("[Printer]", "szPrintToFileName", "Printer output"),
        !           286:         "soundout":("[Sound]", "szYMCaptureFileName", "Sound output")
1.1       root      287:     }
1.1.1.10! root      288:     has_hd_sections = True # from v2.2 onwards separate ACSI/SCSI/IDE sections
        !           289:     has_modeltype = True   # from v2.0 onwards
        !           290:     has_keepstres = True   # only with SDL1
1.1       root      291:     "access methods to Hatari configuration file variables and command line options"
                    292:     def __init__(self, hatari):
1.1.1.9   root      293:         confdirs = [".config/hatari", ".hatari"]
                    294:         ConfigStore.__init__(self, confdirs)
1.1.1.2   root      295:         conffilename = "hatari.cfg"
                    296:         self.load(self.get_filepath(conffilename))
                    297: 
1.1       root      298:         self._hatari = hatari
                    299:         self._lock_updates = False
1.1.1.4   root      300:         self._desktop_w = 0
                    301:         self._desktop_h = 0
1.1       root      302:         self._options = []
1.1.1.9   root      303:         self._winuae = hatari.is_winuae()
                    304:         # initialize has_* attribs for things that may not be anymore
                    305:         # valid on Hatari config file and/or command line
                    306:         self.get_machine()
1.1.1.10! root      307:         self.get_acsi_image()
1.1.1.9   root      308:         self.get_desktop_st()
1.1       root      309: 
1.1.1.2   root      310:     def validate(self):
                    311:         "exception is thrown if the loaded configuration isn't compatible"
                    312:         for method in dir(self):
                    313:             if '_' not in method:
                    314:                 continue
                    315:             # check class getters
                    316:             starts = method[:method.find("_")]
                    317:             if starts != "get":
                    318:                 continue
                    319:             # but ignore getters for other things than config
                    320:             ends = method[method.rfind("_")+1:]
                    321:             if ends in ("types", "names", "values", "changes", "checkpoint", "filepath"):
                    322:                 continue
                    323:             if ends in ("floppy", "joystick"):
                    324:                 # use port '0' for checks
                    325:                 getattr(self, method)(0)
                    326:             else:
                    327:                 getattr(self, method)()
                    328: 
1.1.1.6   root      329:     def _change_option(self, option, quoted = None):
                    330:         "handle option changing, and quote spaces for quoted part of it"
                    331:         if quoted:
                    332:             option = "%s %s" % (option, quoted.replace(" ", "\\ "))
1.1       root      333:         if self._lock_updates:
                    334:             self._options.append(option)
                    335:         else:
                    336:             self._hatari.change_option(option)
                    337: 
                    338:     def lock_updates(self):
                    339:         "lock_updates(), collect Hatari configuration changes"
                    340:         self._lock_updates = True
1.1.1.10! root      341: 
1.1       root      342:     def flush_updates(self):
                    343:         "flush_updates(), apply collected Hatari configuration changes"
                    344:         self._lock_updates = False
                    345:         if not self._options:
                    346:             return
                    347:         self._hatari.change_option(" ".join(self._options))
                    348:         self._options = []
                    349: 
                    350:     # ------------ paths ---------------
                    351:     def get_paths(self):
                    352:         paths = []
1.1.1.9   root      353:         for key, item in list(self._paths.items()):
1.1       root      354:             paths.append((key, self.get(item[0], item[1]), item[2]))
                    355:         return paths
1.1.1.10! root      356: 
1.1       root      357:     def set_paths(self, paths):
                    358:         for key, path in paths:
                    359:             self.set(self._paths[key][0], self._paths[key][1], path)
                    360:             self._hatari.set_path(key, path)
                    361: 
                    362:     # ------------ midi ---------------
                    363:     def get_midi(self):
                    364:         return self.get("[Midi]", "bEnableMidi")
                    365: 
                    366:     def set_midi(self, value):
                    367:         self.set("[Midi]", "bEnableMidi", value)
                    368:         self._hatari.set_device("midi", value)
                    369: 
                    370:     # ------------ printer ---------------
                    371:     def get_printer(self):
                    372:         return self.get("[Printer]", "bEnablePrinting")
                    373: 
                    374:     def set_printer(self, value):
                    375:         self.set("[Printer]", "bEnablePrinting", value)
                    376:         self._hatari.set_device("printer", value)
                    377: 
                    378:     # ------------ RS232 ---------------
                    379:     def get_rs232(self):
                    380:         return self.get("[RS232]", "bEnableRS232")
                    381: 
                    382:     def set_rs232(self, value):
                    383:         self.set("[RS232]", "bEnableRS232", value)
                    384:         self._hatari.set_device("rs232", value)
                    385: 
1.1.1.10! root      386:     def get_sccb(self):
        !           387:         return self.get("[RS232]", "bEnableSccB")
        !           388: 
        !           389:     def set_sccb(self, value):
        !           390:         self.set("[RS232]", "bEnableSccB", value)
        !           391:         self._hatari.set_device("sccb", value)
        !           392: 
1.1       root      393:     # ------------ machine ---------------
                    394:     def get_machine_types(self):
1.1.1.9   root      395:         if self.has_modeltype:
                    396:             return ("ST", "MegaST", "STE", "MegaSTE", "TT", "Falcon")
                    397:         else:
                    398:             return ("ST", "STE", "TT", "Falcon")
1.1       root      399: 
                    400:     def get_machine(self):
1.1.1.9   root      401:         try:
                    402:             return self.get("[System]", "nModelType")
                    403:         except KeyError:
                    404:             self.has_modeltype = False
                    405:             return self.get("[System]", "nMachineType")
                    406: 
                    407:     def has_accurate_winsize(self):
                    408:         if self.has_modeltype:
                    409:             return (self.get_machine() < 4)
                    410:         else:
                    411:             return (self.get_machine() < 2)
1.1       root      412: 
                    413:     def set_machine(self, value):
1.1.1.9   root      414:         if self.has_modeltype:
                    415:             self.set("[System]", "nModelType", value)
                    416:             self._change_option("--machine %s" % ("st", "megast", "ste", "megaste", "tt", "falcon")[value])
                    417:         else:
                    418:             self.set("[System]", "nMachineType", value)
                    419:             self._change_option("--machine %s" % ("st", "ste", "tt", "falcon")[value])
1.1       root      420: 
1.1.1.2   root      421:     # ------------ CPU level ---------------
                    422:     def get_cpulevel_types(self):
1.1.1.9   root      423:         if self._winuae:
                    424:             return ("68000", "68010", "68020", "68E030", "68040", "68060")
                    425:         else:
                    426:             return ("68000", "68010", "68020", "68EC030+FPU", "68040")
1.1.1.2   root      427: 
                    428:     def get_cpulevel(self):
                    429:         return self.get("[System]", "nCpuLevel")
                    430: 
                    431:     def set_cpulevel(self, value):
1.1.1.9   root      432:         if value == 5: # WinUAE 060
                    433:             value = 6
1.1.1.2   root      434:         self.set("[System]", "nCpuLevel", value)
                    435:         self._change_option("--cpulevel %d" % value)
                    436: 
                    437:     # ------------ CPU clock ---------------
                    438:     def get_cpuclock_types(self):
1.1.1.10! root      439:         return ("8 MHz", "16 MHz", "32 MHz")
1.1.1.2   root      440: 
                    441:     def get_cpuclock(self):
                    442:         clocks = {8:0, 16: 1, 32:2}
                    443:         return clocks[self.get("[System]", "nCpuFreq")]
                    444: 
                    445:     def set_cpuclock(self, value):
                    446:         clocks = [8, 16, 32]
                    447:         if value < 0 or value > 2:
1.1.1.3   root      448:             print("WARNING: CPU clock idx %d, clock fixed to 8 Mhz" % value)
1.1.1.2   root      449:             value = 8
                    450:         else:
                    451:             value = clocks[value]
                    452:         self.set("[System]", "nCpuFreq", value)
                    453:         self._change_option("--cpuclock %d" % value)
                    454: 
                    455:     # ------------ DSP type ---------------
                    456:     def get_dsp_types(self):
                    457:         return ("None", "Dummy", "Emulated")
                    458: 
                    459:     def get_dsp(self):
                    460:         return self.get("[System]", "nDSPType")
                    461: 
                    462:     def set_dsp(self, value):
                    463:         self.set("[System]", "nDSPType", value)
                    464:         self._change_option("--dsp %s" % ("none", "dummy", "emu")[value])
                    465: 
1.1       root      466:     # ------------ compatible ---------------
                    467:     def get_compatible(self):
                    468:         return self.get("[System]", "bCompatibleCpu")
                    469: 
                    470:     def set_compatible(self, value):
                    471:         self.set("[System]", "bCompatibleCpu", value)
                    472:         self._change_option("--compatible %s" % str(value))
                    473: 
                    474:     # ------------ Timer-D ---------------
                    475:     def get_timerd(self):
                    476:         return self.get("[System]", "bPatchTimerD")
                    477: 
                    478:     def set_timerd(self, value):
                    479:         self.set("[System]", "bPatchTimerD", value)
                    480:         self._change_option("--timer-d %s" % str(value))
                    481: 
                    482:     # ------------ fastforward ---------------
                    483:     def get_fastforward(self):
                    484:         return self.get("[System]", "bFastForward")
                    485: 
                    486:     def set_fastforward(self, value):
                    487:         self.set("[System]", "bFastForward", value)
                    488:         self._change_option("--fast-forward %s" % str(value))
1.1.1.10! root      489: 
1.1       root      490:     # ------------ sound ---------------
                    491:     def get_sound_values(self):
                    492:         # 48kHz, 44.1kHz and STE/TT/Falcon DMA 50066Hz divisable values
                    493:         return ("6000", "6258", "8000", "11025", "12000", "12517",
                    494:                 "16000", "22050", "24000", "25033", "32000",
1.1.1.5   root      495:                 "44100", "48000", "50066")
1.1.1.10! root      496: 
1.1       root      497:     def get_sound(self):
                    498:         enabled = self.get("[Sound]", "bEnableSound")
                    499:         hz = str(self.get("[Sound]", "nPlaybackFreq"))
1.1.1.5   root      500:         idx = self.get_sound_values().index(hz)
                    501:         return (enabled, idx)
1.1       root      502: 
1.1.1.5   root      503:     def set_sound(self, enabled, idx):
1.1       root      504:         # map get_sound_values() index to Hatari config
1.1.1.5   root      505:         hz = self.get_sound_values()[idx]
                    506:         self.set("[Sound]", "nPlaybackFreq", int(hz))
1.1       root      507:         self.set("[Sound]", "bEnableSound", enabled)
                    508:         # and to cli option
                    509:         if enabled:
1.1.1.5   root      510:             self._change_option("--sound %s" % hz)
1.1       root      511:         else:
                    512:             self._change_option("--sound off")
1.1.1.10! root      513: 
1.1.1.5   root      514:     def get_ymmixer_types(self):
1.1.1.10! root      515:         return ("Linear", "ST table", "Math model")
        !           516: 
1.1.1.5   root      517:     def get_ymmixer(self):
                    518:         # values for types are start from 1, not 0
                    519:         return self.get("[Sound]", "YmVolumeMixing")-1
1.1.1.10! root      520: 
1.1.1.5   root      521:     def set_ymmixer(self, value):
1.1.1.10! root      522:         ymmixer_types = ("linear", "table", "model")
1.1.1.5   root      523:         self.set("[Sound]", "YmVolumeMixing", value+1)
1.1.1.10! root      524:         self._change_option("--ym-mixing %s" % ymmixer_types[value])
        !           525: 
1.1.1.7   root      526:     def get_bufsize(self):
                    527:         return self.get("[Sound]", "nSdlAudioBufferSize")
1.1.1.10! root      528: 
1.1.1.7   root      529:     def set_bufsize(self, value):
                    530:         value = int(value)
                    531:         if value < 10: value = 10
                    532:         if value > 100: value = 100
                    533:         self.set("[Sound]", "nSdlAudioBufferSize", value)
                    534:         self._change_option("--sound-buffer-size %d" % value)
1.1.1.10! root      535: 
1.1.1.7   root      536:     def get_sync(self):
                    537:         return self.get("[Sound]", "bEnableSoundSync")
1.1.1.10! root      538: 
1.1.1.7   root      539:     def set_sync(self, value):
                    540:         self.set("[Sound]", "bEnableSoundSync", value)
                    541:         self._change_option("--sound-sync %s" % str(value))
                    542: 
1.1.1.5   root      543:     def get_mic(self):
                    544:         return self.get("[Sound]", "bEnableMicrophone")
1.1.1.10! root      545: 
1.1.1.5   root      546:     def set_mic(self, value):
                    547:         self.set("[Sound]", "bEnableMicrophone", value)
                    548:         self._change_option("--mic %s" % str(value))
                    549: 
1.1       root      550:     # ----------- joystick --------------
                    551:     def get_joystick_types(self):
                    552:         return ("Disabled", "Real joystick", "Keyboard")
1.1.1.10! root      553: 
1.1       root      554:     def get_joystick_names(self):
                    555:         return (
                    556:         "ST Joystick 0",
                    557:         "ST Joystick 1",
                    558:         "STE Joypad A",
                    559:         "STE Joypad B",
                    560:         "Parport stick 1",
                    561:         "Parport stick 2"
                    562:         )
                    563: 
                    564:     def get_joystick(self, port):
                    565:         # return index to get_joystick_values() array
                    566:         return self.get("[Joystick%d]" % port, "nJoystickMode")
                    567: 
                    568:     def set_joystick(self, port, value):
                    569:         # map get_sound_values() index to Hatari config
                    570:         self.set("[Joystick%d]" % port, "nJoystickMode", value)
                    571:         joytype = ("none", "real", "keys")[value]
                    572:         self._change_option("--joy%d %s" % (port, joytype))
                    573: 
1.1.1.7   root      574:     # ------------ floppy handling ---------------
1.1       root      575:     def get_floppydir(self):
                    576:         return self.get("[Floppy]", "szDiskImageDirectory")
1.1.1.10! root      577: 
1.1       root      578:     def set_floppydir(self, path):
                    579:         return self.set("[Floppy]", "szDiskImageDirectory", path)
                    580: 
                    581:     def get_floppy(self, drive):
                    582:         return self.get("[Floppy]", "szDisk%cFileName" % ("A", "B")[drive])
1.1.1.10! root      583: 
1.1       root      584:     def set_floppy(self, drive, filename):
                    585:         self.set("[Floppy]", "szDisk%cFileName" %  ("A", "B")[drive], filename)
1.1.1.6   root      586:         self._change_option("--disk-%c" % ("a", "b")[drive], str(filename))
1.1       root      587: 
1.1.1.7   root      588:     def get_floppy_drives(self):
                    589:         return (self.get("[Floppy]", "EnableDriveA"), self.get("[Floppy]", "EnableDriveB"))
                    590: 
                    591:     def set_floppy_drives(self, drives):
                    592:         idx = 0
                    593:         for drive in ("A", "B"):
                    594:             value = drives[idx]
                    595:             self.set("[Floppy]", "EnableDrive%c" % drive, value)
                    596:             self._change_option("--drive-%c %s" % (drive.lower(), str(value)))
                    597:             idx += 1
                    598: 
1.1.1.4   root      599:     def get_fastfdc(self):
                    600:         return self.get("[Floppy]", "FastFloppy")
                    601: 
                    602:     def set_fastfdc(self, value):
                    603:         self.set("[Floppy]", "FastFloppy", value)
                    604:         self._change_option("--fastfdc %s" % str(value))
1.1.1.2   root      605: 
1.1.1.7   root      606:     def get_doublesided(self):
                    607:         driveA = self.get("[Floppy]", "DriveA_NumberOfHeads")
                    608:         driveB = self.get("[Floppy]", "DriveB_NumberOfHeads")
                    609:         if driveA > 1 or driveB > 1:
                    610:             return True
                    611:         return False
1.1.1.10! root      612: 
1.1.1.7   root      613:     def set_doublesided(self, value):
                    614:         if value: sides = 2
                    615:         else:     sides = 1
                    616:         for drive in ("A", "B"):
                    617:             self.set("[Floppy]", "Drive%c_NumberOfHeads" % drive, sides)
                    618:             self._change_option("--drive-%c-heads %d" % (drive.lower(), sides))
                    619: 
1.1.1.2   root      620:     # ------------- disk protection -------------
                    621:     def get_protection_types(self):
                    622:         return ("Off", "On", "Auto")
                    623: 
                    624:     def get_floppy_protection(self):
                    625:         return self.get("[Floppy]", "nWriteProtection")
                    626: 
                    627:     def get_hd_protection(self):
                    628:         return self.get("[HardDisk]", "nWriteProtection")
                    629: 
                    630:     def set_floppy_protection(self, value):
                    631:         self.set("[Floppy]", "nWriteProtection", value)
                    632:         self._change_option("--protect-floppy %s" % self.get_protection_types()[value])
                    633: 
                    634:     def set_hd_protection(self, value):
                    635:         self.set("[HardDisk]", "nWriteProtection", value)
                    636:         self._change_option("--protect-hd %s" % self.get_protection_types()[value])
                    637: 
1.1.1.7   root      638:     # ------------ GEMDOS HD (dir) emulation ---------------
                    639:     def get_hd_cases(self):
                    640:         return ("No conversion", "Upper case", "Lower case")
                    641: 
                    642:     def get_hd_case(self):
                    643:         return self.get("[HardDisk]", "nGemdosCase")
                    644: 
                    645:     def set_hd_case(self, value):
                    646:         values = ("off", "upper", "lower")
                    647:         self.set("[HardDisk]", "nGemdosCase", value)
                    648:         self._change_option("--gemdos-case %s" % values[value])
                    649: 
1.1.1.8   root      650:     def get_hd_drives(self):
                    651:         return ['skip ACSI/IDE'] + [("%c:" % x) for x in range(ord('C'), ord('Z')+1)]
                    652: 
                    653:     def get_hd_drive(self):
                    654:         return self.get("[HardDisk]", "nGemdosDrive") + 1
                    655: 
                    656:     def set_hd_drive(self, value):
                    657:         value -= 1
                    658:         self.set("[HardDisk]", "nGemdosDrive", value)
                    659:         drive = chr(ord('C') + value)
                    660:         if value < 0:
                    661:             drive = "skip"
                    662:         self._change_option("--gemdos-drive %s" % drive)
                    663: 
                    664:     def get_hd_dir(self):
1.1.1.2   root      665:         self.get("[HardDisk]", "bUseHardDiskDirectory") # for validation
1.1       root      666:         return self.get("[HardDisk]", "szHardDiskDirectory")
1.1.1.2   root      667: 
1.1.1.8   root      668:     def set_hd_dir(self, dirname):
1.1.1.2   root      669:         if dirname and os.path.isdir(dirname):
                    670:             self.set("[HardDisk]", "bUseHardDiskDirectory", True)
1.1       root      671:         self.set("[HardDisk]", "szHardDiskDirectory", dirname)
1.1.1.6   root      672:         self._change_option("--harddrive", str(dirname))
1.1.1.2   root      673: 
                    674:     # ------------ ACSI HD (file) ---------------
                    675:     def get_acsi_image(self):
1.1.1.10! root      676:         # v2.2 of config file or older?
        !           677:         try:
        !           678:             self.get("[ACSI]", "bUseDevice0")
        !           679:         except KeyError:
        !           680:             self.get("[HardDisk]", "bUseHardDiskImage")
        !           681:             self.has_hd_sections = False
        !           682:         if self.has_hd_sections:
        !           683:             return self.get("[ACSI]", "sDeviceFile0")
        !           684:         else:
        !           685:             return self.get("[HardDisk]", "szHardDiskImage")
1.1.1.2   root      686: 
                    687:     def set_acsi_image(self, filename):
1.1.1.10! root      688:         if self.has_hd_sections:
        !           689:             if filename and os.path.isfile(filename):
        !           690:                 self.set("[ACSI]", "bUseDevice0", True)
        !           691:             self.set("[ACSI]", "sDeviceFile0", filename)
        !           692:         else:
        !           693:             if filename and os.path.isfile(filename):
        !           694:                 self.set("[HardDisk]", "bUseHardDiskImage", True)
        !           695:             self.set("[HardDisk]", "szHardDiskImage", filename)
1.1.1.6   root      696:         self._change_option("--acsi", str(filename))
1.1.1.2   root      697: 
                    698:     # ------------ IDE master (file) ---------------
                    699:     def get_idemaster_image(self):
1.1.1.10! root      700:         if self.has_hd_sections:
        !           701:             return self.get("[IDE]", "sDeviceFile0")
        !           702:         else:
        !           703:             return self.get("[HardDisk]", "szIdeMasterHardDiskImage")
1.1.1.2   root      704: 
                    705:     def set_idemaster_image(self, filename):
1.1.1.10! root      706:         if self.has_hd_sections:
        !           707:             if filename and os.path.isfile(filename):
        !           708:                 self.set("[IDE]", "bUseDevice0", True)
        !           709:             self.set("[IDE]", "sDeviceFile0", filename)
        !           710:         else:
        !           711:             if filename and os.path.isfile(filename):
        !           712:                 self.set("[HardDisk]", "bUseIdeMasterHardDiskImage", True)
        !           713:             self.set("[HardDisk]", "szIdeMasterHardDiskImage", filename)
1.1.1.6   root      714:         self._change_option("--ide-master", str(filename))
1.1.1.2   root      715: 
                    716:     # ------------ IDE slave (file) ---------------
                    717:     def get_ideslave_image(self):
1.1.1.10! root      718:         if self.has_hd_sections:
        !           719:             return self.get("[IDE]", "sDeviceFile1")
        !           720:         else:
        !           721:             return self.get("[HardDisk]", "szIdeSlaveHardDiskImage")
1.1.1.2   root      722: 
                    723:     def set_ideslave_image(self, filename):
1.1.1.10! root      724:         if self.has_hd_sections:
        !           725:             if filename and os.path.isfile(filename):
        !           726:                 self.set("[IDE]", "bUseDevice1", True)
        !           727:             self.set("[IDE]", "sDeviceFile1", filename)
        !           728:         else:
        !           729:             if filename and os.path.isfile(filename):
        !           730:                 self.set("[HardDisk]", "bUseIdeSlaveHardDiskImage", True)
        !           731:             self.set("[HardDisk]", "szIdeSlaveHardDiskImage", filename)
1.1.1.6   root      732:         self._change_option("--ide-slave", str(filename))
1.1       root      733: 
                    734:     # ------------ TOS ROM ---------------
                    735:     def get_tos(self):
                    736:         return self.get("[ROM]", "szTosImageFileName")
1.1.1.10! root      737: 
1.1       root      738:     def set_tos(self, filename):
                    739:         self.set("[ROM]", "szTosImageFileName", filename)
1.1.1.6   root      740:         self._change_option("--tos", str(filename))
1.1       root      741: 
                    742:     # ------------ memory ---------------
                    743:     def get_memory_names(self):
                    744:         # empty item in list shouldn't be shown, filter them out
                    745:         return ("512kB", "1MB", "2MB", "4MB", "8MB", "14MB")
                    746: 
                    747:     def get_memory(self):
                    748:         "return index to what get_memory_names() returns"
                    749:         sizemap = (0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 5)
                    750:         memsize = self.get("[Memory]", "nMemorySize")
1.1.1.10! root      751:         if memsize >= 1024 and memsize <= 14*1024:
        !           752:             memsize //= 1024
        !           753:         elif memsize >= 512:
        !           754:             memsize = 0
        !           755:         elif memsize < 0 or memsize >= len(sizemap):
        !           756:             memsize = 1
        !           757:         return sizemap[memsize]
1.1       root      758: 
                    759:     def set_memory(self, idx):
                    760:         # map memory item index to memory size
                    761:         sizemap = (0, 1, 2, 4, 8, 14)
                    762:         if idx >= 0 and idx < len(sizemap):
                    763:             memsize = sizemap[idx]
                    764:         else:
                    765:             memsize = 1
1.1.1.10! root      766:         if memsize:
        !           767:             memsize *= 1024
        !           768:         else:
        !           769:             memsize = 512
1.1       root      770:         self.set("[Memory]", "nMemorySize", memsize)
                    771:         self._change_option("--memsize %d" % memsize)
                    772: 
1.1.1.8   root      773:     def get_ttram(self):
                    774:         return self.get("[Memory]", "nTTRamSize")
                    775: 
                    776:     def set_ttram(self, memsize):
                    777:         # guarantee correct type (Gtk float -> config int)
                    778:         memsize = int(memsize)
                    779:         self.set("[Memory]", "nTTRamSize", memsize)
                    780:         self._change_option("--ttram %d" % memsize)
                    781:         if memsize:
                    782:             # TT-RAM need 32-bit addressing (i.e. disable 24-bit)
                    783:             self.set("[System]", "bAddressSpace24", False)
                    784:             self._change_option("--addr24 off")
                    785:         else:
                    786:             # switch 24-bit addressing back for compatibility
                    787:             self.set("[System]", "bAddressSpace24", True)
                    788:             self._change_option("--addr24 on", False)
                    789: 
1.1       root      790:     # ------------ monitor ---------------
                    791:     def get_monitor_types(self):
                    792:         return ("Mono", "RGB", "VGA", "TV")
                    793: 
                    794:     def get_monitor(self):
                    795:         return self.get("[Screen]", "nMonitorType")
                    796: 
                    797:     def set_monitor(self, value):
                    798:         self.set("[Screen]", "nMonitorType", value)
                    799:         self._change_option("--monitor %s" % ("mono", "rgb", "vga", "tv")[value])
                    800: 
                    801:     # ------------ frameskip ---------------
                    802:     def get_frameskip_names(self):
                    803:         return (
                    804:             "Disabled",
                    805:             "1 frame",
                    806:             "2 frames",
                    807:             "3 frames",
                    808:             "4 frames",
                    809:             "Automatic"
                    810:         )
1.1.1.10! root      811: 
1.1       root      812:     def get_frameskip(self):
                    813:         fs = self.get("[Screen]", "nFrameSkips")
                    814:         if fs < 0 or fs > 5:
                    815:             return 5
                    816:         return fs
1.1.1.10! root      817: 
1.1       root      818:     def set_frameskip(self, value):
                    819:         value = int(value) # guarantee correct type
                    820:         self.set("[Screen]", "nFrameSkips", value)
                    821:         self._change_option("--frameskips %d" % value)
                    822: 
1.1.1.7   root      823:     # ------------ VBL slowdown ---------------
                    824:     def get_slowdown_names(self):
                    825:         return ("Disabled", "2x", "3x", "4x", "5x", "6x", "8x")
1.1.1.10! root      826: 
1.1.1.7   root      827:     def set_slowdown(self, value):
                    828:         value = 1 + int(value)
                    829:         self._change_option("--slowdown %d" % value)
                    830: 
1.1       root      831:     # ------------ spec512 ---------------
                    832:     def get_spec512threshold(self):
                    833:         return self.get("[Screen]", "nSpec512Threshold")
                    834: 
                    835:     def set_spec512threshold(self, value):
                    836:         value = int(value) # guarantee correct type
                    837:         self.set("[Screen]", "nSpec512Threshold", value)
                    838:         self._change_option("--spec512 %d" % value)
                    839: 
1.1.1.3   root      840:     # --------- keep desktop res -----------
                    841:     def get_desktop(self):
                    842:         return self.get("[Screen]", "bKeepResolution")
1.1.1.9   root      843: 
1.1.1.3   root      844:     def set_desktop(self, value):
                    845:         self.set("[Screen]", "bKeepResolution", value)
                    846:         self._change_option("--desktop %s" % str(value))
                    847: 
1.1.1.6   root      848:     # --------- keep desktop res - st ------
                    849:     def get_desktop_st(self):
1.1.1.9   root      850:         try:
                    851:             return self.get("[Screen]", "bKeepResolutionST")
                    852:         except KeyError:
                    853:             self.has_keepstres = False
                    854:             return False
                    855: 
1.1.1.6   root      856:     def set_desktop_st(self, value):
1.1.1.9   root      857:         if self.has_keepstres:
                    858:             self.set("[Screen]", "bKeepResolutionST", value)
                    859:             self._change_option("--desktop-st %s" % str(value))
1.1.1.6   root      860: 
                    861:     # ------------ force max ---------------
                    862:     def get_force_max(self):
                    863:         return self.get("[Screen]", "bForceMax")
1.1.1.10! root      864: 
1.1.1.6   root      865:     def set_force_max(self, value):
                    866:         self.set("[Screen]", "bForceMax", value)
                    867:         self._change_option("--force-max %s" % str(value))
                    868: 
1.1       root      869:     # ------------ show borders ---------------
                    870:     def get_borders(self):
                    871:         return self.get("[Screen]", "bAllowOverscan")
1.1.1.10! root      872: 
1.1       root      873:     def set_borders(self, value):
                    874:         self.set("[Screen]", "bAllowOverscan", value)
                    875:         self._change_option("--borders %s" % str(value))
                    876: 
                    877:     # ------------ show statusbar ---------------
                    878:     def get_statusbar(self):
                    879:         return self.get("[Screen]", "bShowStatusbar")
1.1.1.10! root      880: 
1.1       root      881:     def set_statusbar(self, value):
                    882:         self.set("[Screen]", "bShowStatusbar", value)
                    883:         self._change_option("--statusbar %s" % str(value))
                    884: 
1.1.1.3   root      885:     # ------------ crop statusbar ---------------
                    886:     def get_crop(self):
                    887:         return self.get("[Screen]", "bCrop")
1.1.1.10! root      888: 
1.1.1.3   root      889:     def set_crop(self, value):
                    890:         self.set("[Screen]", "bCrop", value)
                    891:         self._change_option("--crop %s" % str(value))
                    892: 
1.1       root      893:     # ------------ show led ---------------
                    894:     def get_led(self):
                    895:         return self.get("[Screen]", "bShowDriveLed")
1.1.1.10! root      896: 
1.1       root      897:     def set_led(self, value):
                    898:         self.set("[Screen]", "bShowDriveLed", value)
                    899:         self._change_option("--drive-led %s" % str(value))
                    900: 
1.1.1.2   root      901:     # ------------ monitor aspect ratio ---------------
                    902:     def get_aspectcorrection(self):
                    903:         return self.get("[Screen]", "bAspectCorrect")
1.1.1.10! root      904: 
1.1.1.2   root      905:     def set_aspectcorrection(self, value):
                    906:         self.set("[Screen]", "bAspectCorrect", value)
                    907:         self._change_option("--aspect %s" % str(value))
                    908: 
                    909:     # ------------ max window size ---------------
1.1.1.4   root      910:     def set_desktop_size(self, w, h):
                    911:         self._desktop_w = w
                    912:         self._desktop_h = h
1.1.1.10! root      913: 
1.1.1.5   root      914:     def get_desktop_size(self):
                    915:         return (self._desktop_w, self._desktop_h)
1.1.1.4   root      916: 
1.1.1.2   root      917:     def get_max_size(self):
                    918:         w = self.get("[Screen]", "nMaxWidth")
                    919:         h = self.get("[Screen]", "nMaxHeight")
1.1.1.4   root      920:         # default to desktop size?
                    921:         if not (w or h):
                    922:             w = self._desktop_w
                    923:             h = self._desktop_h
1.1.1.2   root      924:         return (w, h)
                    925: 
                    926:     def set_max_size(self, w, h):
                    927:         # guarantee correct type (Gtk float -> config int)
                    928:         w = int(w); h = int(h)
                    929:         self.set("[Screen]", "nMaxWidth", w)
                    930:         self.set("[Screen]", "nMaxHeight", h)
                    931:         self._change_option("--max-width %d" % w)
                    932:         self._change_option("--max-height %d" % h)
                    933: 
                    934:     # TODO: remove once UI doesn't need this anymore
1.1       root      935:     def set_zoom(self, value):
1.1.1.3   root      936:         print("Just setting Zoom, configuration doesn't anymore have setting for this.")
1.1       root      937:         if value:
                    938:             zoom = 2
                    939:         else:
                    940:             zoom = 1
                    941:         self._change_option("--zoom %d" % zoom)
                    942: 
                    943:     # ------------ configured Hatari window size ---------------
                    944:     def get_window_size(self):
                    945:         if self.get("[Screen]", "bFullScreen"):
1.1.1.3   root      946:             print("WARNING: don't start Hatari UI with fullscreened Hatari!")
1.1       root      947: 
                    948:         # VDI resolution?
                    949:         if self.get("[Screen]", "bUseExtVdiResolutions"):
                    950:             width = self.get("[Screen]", "nVdiWidth")
                    951:             height = self.get("[Screen]", "nVdiHeight")
                    952:             return (width, height)
1.1.1.10! root      953: 
1.1       root      954:         # window sizes for other than ST & STE can differ
1.1.1.9   root      955:         if self.has_accurate_winsize():
1.1.1.2   root      956:             videl = False
1.1.1.9   root      957:         else:
                    958:             print("WARNING: With Videl, window size is unknown -> may be inaccurate!")
                    959:             videl = True
1.1       root      960: 
                    961:         # mono monitor?
1.1.1.2   root      962:         if self.get_monitor() == 0:
1.1       root      963:             return (640, 400)
                    964: 
                    965:         # no, color
                    966:         width = 320
                    967:         height = 200
                    968:         # statusbar?
1.1.1.2   root      969:         if self.get_statusbar():
                    970:             sbar = 12
                    971:             height += sbar
                    972:         else:
                    973:             sbar = 0
                    974:         # zoom?
                    975:         maxw, maxh = self.get_max_size()
                    976:         if 2*width <= maxw and 2*height <= maxh:
1.1       root      977:             width *= 2
                    978:             height *= 2
1.1.1.2   root      979:             zoom = 2
                    980:         else:
                    981:             zoom = 1
                    982:         # overscan borders?
                    983:         if self.get_borders() and not videl:
                    984:             # properly aligned borders on top of zooming
1.1.1.10! root      985:             leftx = (maxw-width)//zoom
        !           986:             borderx = 2*(min(48,leftx//2)//16)*16
        !           987:             lefty = (maxh-height)//zoom
1.1.1.2   root      988:             bordery = min(29+47, lefty)
                    989:             width += zoom*borderx
                    990:             height += zoom*bordery
1.1       root      991: 
                    992:         return (width, height)

unix.superglobalmegacorp.com

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