|
|
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.8 ! root 6: # Copyright (C) 2008-2015 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.8 ! root 601: def get_hd_drives(self):
! 602: return ['skip ACSI/IDE'] + [("%c:" % x) for x in range(ord('C'), ord('Z')+1)]
! 603:
! 604: def get_hd_drive(self):
! 605: return self.get("[HardDisk]", "nGemdosDrive") + 1
! 606:
! 607: def set_hd_drive(self, value):
! 608: value -= 1
! 609: self.set("[HardDisk]", "nGemdosDrive", value)
! 610: drive = chr(ord('C') + value)
! 611: if value < 0:
! 612: drive = "skip"
! 613: self._change_option("--gemdos-drive %s" % drive)
! 614:
! 615: def get_hd_dir(self):
1.1.1.2 root 616: self.get("[HardDisk]", "bUseHardDiskDirectory") # for validation
1.1 root 617: return self.get("[HardDisk]", "szHardDiskDirectory")
1.1.1.2 root 618:
1.1.1.8 ! root 619: def set_hd_dir(self, dirname):
1.1.1.2 root 620: if dirname and os.path.isdir(dirname):
621: self.set("[HardDisk]", "bUseHardDiskDirectory", True)
1.1 root 622: self.set("[HardDisk]", "szHardDiskDirectory", dirname)
1.1.1.6 root 623: self._change_option("--harddrive", str(dirname))
1.1.1.2 root 624:
625: # ------------ ACSI HD (file) ---------------
626: def get_acsi_image(self):
627: self.get("[HardDisk]", "bUseHardDiskImage") # for validation
628: return self.get("[HardDisk]", "szHardDiskImage")
629:
630: def set_acsi_image(self, filename):
631: if filename and os.path.isfile(filename):
632: self.set("[HardDisk]", "bUseHardDiskImage", True)
633: self.set("[HardDisk]", "szHardDiskImage", filename)
1.1.1.6 root 634: self._change_option("--acsi", str(filename))
1.1.1.2 root 635:
636: # ------------ IDE master (file) ---------------
637: def get_idemaster_image(self):
638: self.get("[HardDisk]", "bUseIdeMasterHardDiskImage") # for validation
639: return self.get("[HardDisk]", "szIdeMasterHardDiskImage")
640:
641: def set_idemaster_image(self, filename):
642: if filename and os.path.isfile(filename):
643: self.set("[HardDisk]", "bUseIdeMasterHardDiskImage", True)
644: self.set("[HardDisk]", "szIdeMasterHardDiskImage", filename)
1.1.1.6 root 645: self._change_option("--ide-master", str(filename))
1.1.1.2 root 646:
647: # ------------ IDE slave (file) ---------------
648: def get_ideslave_image(self):
649: self.get("[HardDisk]", "bUseIdeSlaveHardDiskImage") # for validation
650: return self.get("[HardDisk]", "szIdeSlaveHardDiskImage")
651:
652: def set_ideslave_image(self, filename):
653: if filename and os.path.isfile(filename):
654: self.set("[HardDisk]", "bUseIdeSlaveHardDiskImage", True)
655: self.set("[HardDisk]", "szIdeSlaveHardDiskImage", filename)
1.1.1.6 root 656: self._change_option("--ide-slave", str(filename))
1.1 root 657:
658: # ------------ TOS ROM ---------------
659: def get_tos(self):
660: return self.get("[ROM]", "szTosImageFileName")
661:
662: def set_tos(self, filename):
663: self.set("[ROM]", "szTosImageFileName", filename)
1.1.1.6 root 664: self._change_option("--tos", str(filename))
1.1 root 665:
666: # ------------ memory ---------------
667: def get_memory_names(self):
668: # empty item in list shouldn't be shown, filter them out
669: return ("512kB", "1MB", "2MB", "4MB", "8MB", "14MB")
670:
671: def get_memory(self):
672: "return index to what get_memory_names() returns"
673: sizemap = (0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 5)
674: memsize = self.get("[Memory]", "nMemorySize")
675: if memsize >= 0 and memsize < len(sizemap):
676: return sizemap[memsize]
677: return 1 # default = 1BM
678:
679: def set_memory(self, idx):
680: # map memory item index to memory size
681: sizemap = (0, 1, 2, 4, 8, 14)
682: if idx >= 0 and idx < len(sizemap):
683: memsize = sizemap[idx]
684: else:
685: memsize = 1
686: self.set("[Memory]", "nMemorySize", memsize)
687: self._change_option("--memsize %d" % memsize)
688:
1.1.1.8 ! root 689: def get_ttram(self):
! 690: return self.get("[Memory]", "nTTRamSize")
! 691:
! 692: def set_ttram(self, memsize):
! 693: # guarantee correct type (Gtk float -> config int)
! 694: memsize = int(memsize)
! 695: self.set("[Memory]", "nTTRamSize", memsize)
! 696: self._change_option("--ttram %d" % memsize)
! 697: if memsize:
! 698: # TT-RAM need 32-bit addressing (i.e. disable 24-bit)
! 699: self.set("[System]", "bAddressSpace24", False)
! 700: self._change_option("--addr24 off")
! 701: else:
! 702: # switch 24-bit addressing back for compatibility
! 703: self.set("[System]", "bAddressSpace24", True)
! 704: self._change_option("--addr24 on", False)
! 705:
1.1 root 706: # ------------ monitor ---------------
707: def get_monitor_types(self):
708: return ("Mono", "RGB", "VGA", "TV")
709:
710: def get_monitor(self):
711: return self.get("[Screen]", "nMonitorType")
712:
713: def set_monitor(self, value):
714: self.set("[Screen]", "nMonitorType", value)
715: self._change_option("--monitor %s" % ("mono", "rgb", "vga", "tv")[value])
716:
717: # ------------ frameskip ---------------
718: def get_frameskip_names(self):
719: return (
720: "Disabled",
721: "1 frame",
722: "2 frames",
723: "3 frames",
724: "4 frames",
725: "Automatic"
726: )
727:
728: def get_frameskip(self):
729: fs = self.get("[Screen]", "nFrameSkips")
730: if fs < 0 or fs > 5:
731: return 5
732: return fs
733:
734: def set_frameskip(self, value):
735: value = int(value) # guarantee correct type
736: self.set("[Screen]", "nFrameSkips", value)
737: self._change_option("--frameskips %d" % value)
738:
1.1.1.7 root 739: # ------------ VBL slowdown ---------------
740: def get_slowdown_names(self):
741: return ("Disabled", "2x", "3x", "4x", "5x", "6x", "8x")
742:
743: def set_slowdown(self, value):
744: value = 1 + int(value)
745: self._change_option("--slowdown %d" % value)
746:
1.1 root 747: # ------------ spec512 ---------------
748: def get_spec512threshold(self):
749: return self.get("[Screen]", "nSpec512Threshold")
750:
751: def set_spec512threshold(self, value):
752: value = int(value) # guarantee correct type
753: self.set("[Screen]", "nSpec512Threshold", value)
754: self._change_option("--spec512 %d" % value)
755:
1.1.1.3 root 756: # --------- keep desktop res -----------
757: def get_desktop(self):
758: return self.get("[Screen]", "bKeepResolution")
759:
760: def set_desktop(self, value):
761: self.set("[Screen]", "bKeepResolution", value)
762: self._change_option("--desktop %s" % str(value))
763:
1.1.1.6 root 764: # --------- keep desktop res - st ------
765: def get_desktop_st(self):
766: return self.get("[Screen]", "bKeepResolutionST")
767:
768: def set_desktop_st(self, value):
769: self.set("[Screen]", "bKeepResolutionST", value)
770: self._change_option("--desktop-st %s" % str(value))
771:
772: # ------------ force max ---------------
773: def get_force_max(self):
774: return self.get("[Screen]", "bForceMax")
775:
776: def set_force_max(self, value):
777: self.set("[Screen]", "bForceMax", value)
778: self._change_option("--force-max %s" % str(value))
779:
1.1 root 780: # ------------ show borders ---------------
781: def get_borders(self):
782: return self.get("[Screen]", "bAllowOverscan")
783:
784: def set_borders(self, value):
785: self.set("[Screen]", "bAllowOverscan", value)
786: self._change_option("--borders %s" % str(value))
787:
788: # ------------ show statusbar ---------------
789: def get_statusbar(self):
790: return self.get("[Screen]", "bShowStatusbar")
791:
792: def set_statusbar(self, value):
793: self.set("[Screen]", "bShowStatusbar", value)
794: self._change_option("--statusbar %s" % str(value))
795:
1.1.1.3 root 796: # ------------ crop statusbar ---------------
797: def get_crop(self):
798: return self.get("[Screen]", "bCrop")
799:
800: def set_crop(self, value):
801: self.set("[Screen]", "bCrop", value)
802: self._change_option("--crop %s" % str(value))
803:
1.1 root 804: # ------------ show led ---------------
805: def get_led(self):
806: return self.get("[Screen]", "bShowDriveLed")
807:
808: def set_led(self, value):
809: self.set("[Screen]", "bShowDriveLed", value)
810: self._change_option("--drive-led %s" % str(value))
811:
1.1.1.2 root 812: # ------------ monitor aspect ratio ---------------
813: def get_aspectcorrection(self):
814: return self.get("[Screen]", "bAspectCorrect")
815:
816: def set_aspectcorrection(self, value):
817: self.set("[Screen]", "bAspectCorrect", value)
818: self._change_option("--aspect %s" % str(value))
819:
820: # ------------ max window size ---------------
1.1.1.4 root 821: def set_desktop_size(self, w, h):
822: self._desktop_w = w
823: self._desktop_h = h
1.1.1.5 root 824:
825: def get_desktop_size(self):
826: return (self._desktop_w, self._desktop_h)
1.1.1.4 root 827:
1.1.1.2 root 828: def get_max_size(self):
829: w = self.get("[Screen]", "nMaxWidth")
830: h = self.get("[Screen]", "nMaxHeight")
1.1.1.4 root 831: # default to desktop size?
832: if not (w or h):
833: w = self._desktop_w
834: h = self._desktop_h
1.1.1.2 root 835: return (w, h)
836:
837: def set_max_size(self, w, h):
838: # guarantee correct type (Gtk float -> config int)
839: w = int(w); h = int(h)
840: self.set("[Screen]", "nMaxWidth", w)
841: self.set("[Screen]", "nMaxHeight", h)
842: self._change_option("--max-width %d" % w)
843: self._change_option("--max-height %d" % h)
844:
845: # TODO: remove once UI doesn't need this anymore
1.1 root 846: def set_zoom(self, value):
1.1.1.3 root 847: print("Just setting Zoom, configuration doesn't anymore have setting for this.")
1.1 root 848: if value:
849: zoom = 2
850: else:
851: zoom = 1
852: self._change_option("--zoom %d" % zoom)
853:
854: # ------------ configured Hatari window size ---------------
855: def get_window_size(self):
856: if self.get("[Screen]", "bFullScreen"):
1.1.1.3 root 857: print("WARNING: don't start Hatari UI with fullscreened Hatari!")
1.1 root 858:
859: # VDI resolution?
860: if self.get("[Screen]", "bUseExtVdiResolutions"):
861: width = self.get("[Screen]", "nVdiWidth")
862: height = self.get("[Screen]", "nVdiHeight")
863: return (width, height)
864:
865: # window sizes for other than ST & STE can differ
866: if self.get("[System]", "nMachineType") not in (0, 1):
1.1.1.3 root 867: print("WARNING: neither ST nor STE machine, window size inaccurate!")
1.1.1.2 root 868: videl = True
869: else:
870: videl = False
1.1 root 871:
872: # mono monitor?
1.1.1.2 root 873: if self.get_monitor() == 0:
1.1 root 874: return (640, 400)
875:
876: # no, color
877: width = 320
878: height = 200
879: # statusbar?
1.1.1.2 root 880: if self.get_statusbar():
881: sbar = 12
882: height += sbar
883: else:
884: sbar = 0
885: # zoom?
886: maxw, maxh = self.get_max_size()
887: if 2*width <= maxw and 2*height <= maxh:
1.1 root 888: width *= 2
889: height *= 2
1.1.1.2 root 890: zoom = 2
891: else:
892: zoom = 1
893: # overscan borders?
894: if self.get_borders() and not videl:
895: # properly aligned borders on top of zooming
896: leftx = (maxw-width)/zoom
897: borderx = 2*(min(48,leftx/2)/16)*16
898: lefty = (maxh-height)/zoom
899: bordery = min(29+47, lefty)
900: width += zoom*borderx
901: height += zoom*bordery
1.1 root 902:
903: return (width, height)
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.