|
|
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)
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.