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