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

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