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