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

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: #
                      6: # Copyright (C) 2008 by Eero Tamminen <[email protected]>
                      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._assert_hatari_compatibility()
                     45:         self._create_server()
                     46:         self.control = None
                     47:         self.paused = False
                     48:         self.pid = 0
                     49: 
                     50:     def _assert_hatari_compatibility(self):
                     51:         for line in os.popen(self.hataribin + " -h").readlines():
                     52:             if line.find("--control-socket") >= 0:
                     53:                 return
                     54:         print "ERROR: Hatari not found or it doesn't support the required --control-socket option!"
                     55:         sys.exit(-1)
                     56: 
                     57:     def _create_server(self):
                     58:         if self.server:
                     59:             return
                     60:         self.server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
                     61:         if os.path.exists(self.controlpath):
                     62:             os.unlink(self.controlpath)
                     63:         self.server.bind(self.controlpath)
                     64:         self.server.listen(1)
                     65:         
                     66:     def _send_message(self, msg):
                     67:         if self.control:
                     68:             self.control.send(msg)
                     69:             return True
                     70:         else:
                     71:             print "ERROR: no Hatari (control socket)"
                     72:             return False
                     73:         
                     74:     def change_option(self, option):
                     75:         "change_option(option), changes given Hatari cli option"
                     76:         return self._send_message("hatari-option %s\n" % option)
                     77:         
                     78:     def set_path(self, key, path):
                     79:         "set_path(key, path), sets path with given key"
                     80:         return self._send_message("hatari-path %s %s\n" % (key, path))
                     81: 
                     82:     def set_device(self, device, enabled):
                     83:         # needed because CLI options cannot disable devices, only enable
                     84:         "set_path(device, enabled), sets whether given device is enabled or not"
                     85:         if enabled:
                     86:             return self._send_message("hatari-enable %s\n" % device)
                     87:         else:
                     88:             return self._send_message("hatari-disable %s\n" % device)
                     89:         
                     90:     def trigger_shortcut(self, shortcut):
                     91:         "trigger_shortcut(shortcut), triggers given Hatari (keyboard) shortcut"
                     92:         return self._send_message("hatari-shortcut %s\n" % shortcut)
                     93: 
                     94:     def insert_event(self, event):
                     95:         "insert_event(event), synthetizes given key/mouse Atari event"
                     96:         return self._send_message("hatari-event %s\n" % event)
                     97: 
                     98:     def debug_command(self, cmd):
                     99:         "debug_command(command), runs given Hatari debugger command"
                    100:         return self._send_message("hatari-debug %s\n" % cmd)
                    101: 
                    102:     def pause(self):
                    103:         "pause(), pauses Hatari emulation"
                    104:         return self._send_message("hatari-stop\n")
                    105: 
                    106:     def unpause(self):
                    107:         "unpause(), continues Hatari emulation"
                    108:         return self._send_message("hatari-cont\n")
                    109:     
                    110:     def _open_output_file(self, hataricommand, option, path):
                    111:         if os.path.exists(path):
                    112:             os.unlink(path)
                    113:         # TODO: why fifo doesn't work properly (blocks forever on read or
                    114:         #       reads only byte at the time and stops after first newline)?
                    115:         #os.mkfifo(path)
                    116:         #raw_input("attach strace now, then press Enter\n")
                    117:         
                    118:         # ask Hatari to open/create the requested output file...
                    119:         hataricommand("%s %s" % (option, path))
                    120:         wait = 0.025
                    121:         # ...and wait for it to appear before returning it
                    122:         for i in range(0, 8):
                    123:             time.sleep(wait)
                    124:             if os.path.exists(path):
                    125:                 return open(path, "r")
                    126:             wait += wait
                    127:         return None
                    128: 
                    129:     def open_debug_output(self):
                    130:         "open_debug_output() -> file, opens Hatari debugger output file"
                    131:         return self._open_output_file(self.debug_command, "f", self.debugpath)
                    132: 
                    133:     def open_trace_output(self):
                    134:         "open_trace_output() -> file, opens Hatari tracing output file"
                    135:         return self._open_output_file(self.change_option, "--trace-file", self.tracepath)
                    136: 
                    137:     def open_log_output(self):
                    138:         "open_trace_output() -> file, opens Hatari debug log file"
                    139:         return self._open_output_file(self.change_option, "--log-file", self.logpath)
                    140:     
                    141:     def get_lines(self, fileobj):
                    142:         "get_lines(file) -> list of lines readable from given Hatari output file"
                    143:         # wait until data is available, then wait for some more
                    144:         # and only then the data can be read, otherwise its old
                    145:         print "Request&wait data from Hatari..."
                    146:         select.select([fileobj], [], [])
                    147:         time.sleep(0.1)
                    148:         print "...read the data lines"
                    149:         lines = fileobj.readlines()
                    150:         print "".join(lines)
                    151:         return lines
                    152: 
                    153:     def enable_embed_info(self):
                    154:         "enable_embed_info(), request embedded Hatari window ID change information"
                    155:         self._send_message("hatari-embed-info\n")
                    156: 
                    157:     def get_embed_info(self):
                    158:         "get_embed_info() -> (width, height), get embedded Hatari window size"
                    159:         width, height = self.control.recv(12).split("x")
                    160:         return (int(width), int(height))
                    161: 
                    162:     def get_control_socket(self):
                    163:         "get_control_socket() -> socket which can be checked for embed ID changes"
                    164:         return self.control
                    165:         
                    166:     def is_running(self):
                    167:         "is_running() -> bool, True if Hatari is running, False otherwise"
                    168:         if not self.pid:
                    169:             return False
                    170:         try:
                    171:             os.waitpid(self.pid, os.WNOHANG)
                    172:         except OSError, value:
                    173:             print "Hatari PID %d had exited in the meanwhile:\n\t%s" % (self.pid, value)
                    174:             self.pid = 0
                    175:             return False
                    176:         return True
                    177:     
                    178:     def run(self, extra_args = None, parent_win = None):
                    179:         "run([parent window][,embedding args]), runs Hatari"
                    180:         # if parent_win given, embed Hatari to it
                    181:         pid = os.fork()
                    182:         if pid < 0:
                    183:             print "ERROR: fork()ing Hatari failed!"
                    184:             return
                    185:         if pid:
                    186:             # in parent
                    187:             self.pid = pid
                    188:             if self.server:
                    189:                 print "WAIT hatari to connect to control socket...",
                    190:                 (self.control, addr) = self.server.accept()
                    191:                 print "connected!"
                    192:         else:
                    193:             # child runs Hatari
                    194:             env = os.environ
                    195:             if parent_win:
                    196:                 self._set_embed_env(env, parent_win)
                    197:             # callers need to take care of confirming quitting
                    198:             args = [self.hataribin, "--confirm-quit", "off"]
                    199:             if self.server:
                    200:                 args += ["--control-socket", self.controlpath]
                    201:             if extra_args:
                    202:                 args += extra_args
                    203:             print "RUN:", args
                    204:             os.execvpe(self.hataribin, args, env)
                    205: 
                    206:     def _set_embed_env(self, env, parent_win):
                    207:         if sys.platform == 'win32':
                    208:             win_id = parent_win.handle
                    209:         else:
                    210:             win_id = parent_win.xid
                    211:         # tell SDL to use given widget's window
                    212:         #env["SDL_WINDOWID"] = str(win_id)
                    213: 
                    214:         # above is broken: when SDL uses a window it hasn't created itself,
                    215:         # it for some reason doesn't listen to any events delivered to that
                    216:         # window nor implements XEMBED protocol to get them in a way most
                    217:         # friendly to embedder:
                    218:         #   http://standards.freedesktop.org/xembed-spec/latest/
                    219:         #
                    220:         # Instead we tell hatari to reparent itself after creating
                    221:         # its own window into this program widget window
                    222:         env["PARENT_WIN_ID"] = str(win_id)
                    223: 
                    224:     def kill(self):
                    225:         "kill(), kill Hatari if it's running"
                    226:         if self.pid:
                    227:             os.kill(self.pid, signal.SIGKILL)
                    228:             print "killed hatari with PID %d" % self.pid
                    229:             self.pid = 0
                    230:         if self.control:
                    231:             self.control.close()
                    232:             self.control = None
                    233: 
                    234: 
                    235: # Mapping of requested values both to Hatari configuration
                    236: # and command line options.
                    237: #
                    238: # By default this doesn't allow setting any other configuration
                    239: # variables than the ones that were read from the configuration
                    240: # file i.e. you get an exception if configuration variables
                    241: # don't match to current Hatari.  So before using this you should
                    242: # have saved Hatari configuration at least once.
                    243: #
                    244: # Because of some inconsistencies in the values (see e.g. sound),
                    245: # this cannot just do these according to some mapping table, but
                    246: # it needs actual method for (each) setting.
                    247: class HatariConfigMapping(ConfigStore):
                    248:     _paths = {
                    249:         "memauto": ("[Memory]", "szAutoSaveFileName", "Automatic memory snapshot"),
                    250:         "memsave": ("[Memory]", "szMemoryCaptureFileName", "Manual memory snapshot"),
                    251:         "midiin":  ("[Midi]", "sMidiInFileName", "Midi input"),
                    252:         "midiout": ("[Midi]", "sMidiOutFileName", "Midi output"),
                    253:         "rs232in": ("[RS232]", "szInFileName", "RS232 I/O input"),
                    254:         "rs232out": ("[RS232]", "szOutFileName", "RS232 I/O output"),
                    255:         "printout": ("[Printer]", "szPrintToFileName", "Printer output"),
                    256:         "soundout": ("[Sound]", "szYMCaptureFileName", "Sound output")
                    257:     }
                    258:     "access methods to Hatari configuration file variables and command line options"
                    259:     def __init__(self, hatari):
                    260:         ConfigStore.__init__(self, "hatari.cfg")
                    261:         self._hatari = hatari
                    262:         self._lock_updates = False
                    263:         self._options = []
                    264: 
                    265:     def _change_option(self, option):
                    266:         if self._lock_updates:
                    267:             self._options.append(option)
                    268:         else:
                    269:             self._hatari.change_option(option)
                    270: 
                    271:     def lock_updates(self):
                    272:         "lock_updates(), collect Hatari configuration changes"
                    273:         self._lock_updates = True
                    274:     
                    275:     def flush_updates(self):
                    276:         "flush_updates(), apply collected Hatari configuration changes"
                    277:         self._lock_updates = False
                    278:         if not self._options:
                    279:             return
                    280:         self._hatari.change_option(" ".join(self._options))
                    281:         self._options = []
                    282: 
                    283:     # ------------ paths ---------------
                    284:     def get_paths(self):
                    285:         paths = []
                    286:         for key, item in self._paths.items():
                    287:             paths.append((key, self.get(item[0], item[1]), item[2]))
                    288:         return paths
                    289:     
                    290:     def set_paths(self, paths):
                    291:         for key, path in paths:
                    292:             self.set(self._paths[key][0], self._paths[key][1], path)
                    293:             self._hatari.set_path(key, path)
                    294: 
                    295:     # ------------ midi ---------------
                    296:     def get_midi(self):
                    297:         return self.get("[Midi]", "bEnableMidi")
                    298: 
                    299:     def set_midi(self, value):
                    300:         self.set("[Midi]", "bEnableMidi", value)
                    301:         self._hatari.set_device("midi", value)
                    302: 
                    303:     # ------------ printer ---------------
                    304:     def get_printer(self):
                    305:         return self.get("[Printer]", "bEnablePrinting")
                    306: 
                    307:     def set_printer(self, value):
                    308:         self.set("[Printer]", "bEnablePrinting", value)
                    309:         self._hatari.set_device("printer", value)
                    310: 
                    311:     # ------------ RS232 ---------------
                    312:     def get_rs232(self):
                    313:         return self.get("[RS232]", "bEnableRS232")
                    314: 
                    315:     def set_rs232(self, value):
                    316:         self.set("[RS232]", "bEnableRS232", value)
                    317:         self._hatari.set_device("rs232", value)
                    318: 
                    319:     # ------------ machine ---------------
                    320:     def get_machine_types(self):
                    321:         return ("ST", "STE", "TT", "Falcon")
                    322: 
                    323:     def get_machine(self):
                    324:         return self.get("[System]", "nMachineType")
                    325: 
                    326:     def set_machine(self, value):
                    327:         self.set("[System]", "nMachineType", value)
                    328:         self._change_option("--machine %s" % ("st", "ste", "tt", "falcon")[value])
                    329: 
                    330:     # ------------ compatible ---------------
                    331:     def get_compatible(self):
                    332:         return self.get("[System]", "bCompatibleCpu")
                    333: 
                    334:     def set_compatible(self, value):
                    335:         self.set("[System]", "bCompatibleCpu", value)
                    336:         self._change_option("--compatible %s" % str(value))
                    337: 
                    338:     # ------------ Timer-D ---------------
                    339:     def get_timerd(self):
                    340:         return self.get("[System]", "bPatchTimerD")
                    341: 
                    342:     def set_timerd(self, value):
                    343:         self.set("[System]", "bPatchTimerD", value)
                    344:         self._change_option("--timer-d %s" % str(value))
                    345: 
                    346:     # ------------ fastforward ---------------
                    347:     def get_fastforward(self):
                    348:         return self.get("[System]", "bFastForward")
                    349: 
                    350:     def set_fastforward(self, value):
                    351:         self.set("[System]", "bFastForward", value)
                    352:         self._change_option("--fast-forward %s" % str(value))
                    353:         
                    354:     # ------------ sound ---------------
                    355:     def get_sound_values(self):
                    356:         # 48kHz, 44.1kHz and STE/TT/Falcon DMA 50066Hz divisable values
                    357:         return ("6000", "6258", "8000", "11025", "12000", "12517",
                    358:                 "16000", "22050", "24000", "25033", "32000",
                    359:                 "48000", "44100", "50066")
                    360:     
                    361:     def get_sound(self):
                    362:         enabled = self.get("[Sound]", "bEnableSound")
                    363:         hz = str(self.get("[Sound]", "nPlaybackFreq"))
                    364:         return (enabled, hz)
                    365: 
                    366:     def set_sound(self, enabled, hz):
                    367:         # map get_sound_values() index to Hatari config
                    368:         try:
                    369:             hz = int(hz)
                    370:         except ValueError:
                    371:             hz = 0
                    372:         if hz < 6000 or hz > 50066:
                    373:             hz = 44100
                    374:         self.set("[Sound]", "nPlaybackFreq", hz)
                    375:         self.set("[Sound]", "bEnableSound", enabled)
                    376:         value = str(hz)
                    377:         # and to cli option
                    378:         if enabled:
                    379:             self._change_option("--sound %s" % value)
                    380:         else:
                    381:             self._change_option("--sound off")
                    382:         return value
                    383:         
                    384:         
                    385:     # ----------- joystick --------------
                    386:     def get_joystick_types(self):
                    387:         return ("Disabled", "Real joystick", "Keyboard")
                    388:     
                    389:     def get_joystick_names(self):
                    390:         return (
                    391:         "ST Joystick 0",
                    392:         "ST Joystick 1",
                    393:         "STE Joypad A",
                    394:         "STE Joypad B",
                    395:         "Parport stick 1",
                    396:         "Parport stick 2"
                    397:         )
                    398: 
                    399:     def get_joystick(self, port):
                    400:         # return index to get_joystick_values() array
                    401:         return self.get("[Joystick%d]" % port, "nJoystickMode")
                    402: 
                    403:     def set_joystick(self, port, value):
                    404:         # map get_sound_values() index to Hatari config
                    405:         self.set("[Joystick%d]" % port, "nJoystickMode", value)
                    406:         joytype = ("none", "real", "keys")[value]
                    407:         self._change_option("--joy%d %s" % (port, joytype))
                    408: 
                    409:     # ------------ floppy image dir ---------------
                    410:     def get_floppydir(self):
                    411:         return self.get("[Floppy]", "szDiskImageDirectory")
                    412:     
                    413:     def set_floppydir(self, path):
                    414:         return self.set("[Floppy]", "szDiskImageDirectory", path)
                    415: 
                    416:     # ------------ floppy disk images ---------------
                    417:     def get_floppy(self, drive):
                    418:         return self.get("[Floppy]", "szDisk%cFileName" % ("A", "B")[drive])
                    419:     
                    420:     def set_floppy(self, drive, filename):
                    421:         self.set("[Floppy]", "szDisk%cFileName" %  ("A", "B")[drive], filename)
                    422:         self._change_option("--disk-%c %s" % (("a", "b")[drive], filename))
                    423: 
                    424:     # ------------ use harddisk ---------------
                    425:     def get_use_harddisk(self):
                    426:         return self.get("[HardDisk]", "bUseHardDiskDirectory")
                    427:     
                    428:     def set_use_harddisk(self, value):
                    429:         self.set("[HardDisk]", "bUseHardDiskDirectory", value)
                    430:         # TODO: add to hatari option for this
                    431: 
                    432:     # ------------ harddisk (dir) ---------------
                    433:     def get_harddisk(self):
                    434:         return self.get("[HardDisk]", "szHardDiskDirectory")
                    435:     
                    436:     def set_harddisk(self, dirname):
                    437:         self.set("[HardDisk]", "szHardDiskDirectory", dirname)
                    438:         if self.get_use_harddisk():
                    439:             self._change_option("--harddrive %s" % dirname)
                    440: 
                    441:     # ------------ TOS ROM ---------------
                    442:     def get_tos(self):
                    443:         return self.get("[ROM]", "szTosImageFileName")
                    444:     
                    445:     def set_tos(self, filename):
                    446:         self.set("[ROM]", "szTosImageFileName", filename)
                    447:         self._change_option("--tos %s" % filename)
                    448: 
                    449:     # ------------ memory ---------------
                    450:     def get_memory_names(self):
                    451:         # empty item in list shouldn't be shown, filter them out
                    452:         return ("512kB", "1MB", "2MB", "4MB", "8MB", "14MB")
                    453: 
                    454:     def get_memory(self):
                    455:         "return index to what get_memory_names() returns"
                    456:         sizemap = (0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 5)
                    457:         memsize = self.get("[Memory]", "nMemorySize")
                    458:         if memsize >= 0 and memsize < len(sizemap):
                    459:             return sizemap[memsize]
                    460:         return 1 # default = 1BM
                    461: 
                    462:     def set_memory(self, idx):
                    463:         # map memory item index to memory size
                    464:         sizemap = (0, 1, 2, 4, 8, 14)
                    465:         if idx >= 0 and idx < len(sizemap):
                    466:             memsize = sizemap[idx]
                    467:         else:
                    468:             memsize = 1
                    469:         self.set("[Memory]", "nMemorySize", memsize)
                    470:         self._change_option("--memsize %d" % memsize)
                    471: 
                    472:     # ------------ monitor ---------------
                    473:     def get_monitor_types(self):
                    474:         return ("Mono", "RGB", "VGA", "TV")
                    475: 
                    476:     def get_monitor(self):
                    477:         return self.get("[Screen]", "nMonitorType")
                    478: 
                    479:     def set_monitor(self, value):
                    480:         self.set("[Screen]", "nMonitorType", value)
                    481:         self._change_option("--monitor %s" % ("mono", "rgb", "vga", "tv")[value])
                    482: 
                    483:     # ------------ frameskip ---------------
                    484:     def get_frameskip_names(self):
                    485:         return (
                    486:             "Disabled",
                    487:             "1 frame",
                    488:             "2 frames",
                    489:             "3 frames",
                    490:             "4 frames",
                    491:             "Automatic"
                    492:         )
                    493:     
                    494:     def get_frameskip(self):
                    495:         fs = self.get("[Screen]", "nFrameSkips")
                    496:         print "Frameskip", fs
                    497:         if fs < 0 or fs > 5:
                    498:             return 5
                    499:         return fs
                    500:     
                    501:     def set_frameskip(self, value):
                    502:         value = int(value) # guarantee correct type
                    503:         self.set("[Screen]", "nFrameSkips", value)
                    504:         self._change_option("--frameskips %d" % value)
                    505: 
                    506:     # ------------ spec512 ---------------
                    507:     def get_spec512threshold(self):
                    508:         return self.get("[Screen]", "nSpec512Threshold")
                    509: 
                    510:     def set_spec512threshold(self, value):
                    511:         value = int(value) # guarantee correct type
                    512:         self.set("[Screen]", "nSpec512Threshold", value)
                    513:         self._change_option("--spec512 %d" % value)
                    514: 
                    515:     # ------------ show borders ---------------
                    516:     def get_borders(self):
                    517:         return self.get("[Screen]", "bAllowOverscan")
                    518:     
                    519:     def set_borders(self, value):
                    520:         self.set("[Screen]", "bAllowOverscan", value)
                    521:         self._change_option("--borders %s" % str(value))
                    522: 
                    523:     # ------------ show statusbar ---------------
                    524:     def get_statusbar(self):
                    525:         return self.get("[Screen]", "bShowStatusbar")
                    526:     
                    527:     def set_statusbar(self, value):
                    528:         self.set("[Screen]", "bShowStatusbar", value)
                    529:         self._change_option("--statusbar %s" % str(value))
                    530: 
                    531:     # ------------ show led ---------------
                    532:     def get_led(self):
                    533:         return self.get("[Screen]", "bShowDriveLed")
                    534:     
                    535:     def set_led(self, value):
                    536:         self.set("[Screen]", "bShowDriveLed", value)
                    537:         self._change_option("--drive-led %s" % str(value))
                    538: 
                    539:     # ------------ use zoom ---------------
                    540:     def get_zoom(self):
                    541:         return self.get("[Screen]", "bZoomLowRes")
                    542:     
                    543:     def set_zoom(self, value):
                    544:         self.set("[Screen]", "bZoomLowRes", value)
                    545:         if value:
                    546:             zoom = 2
                    547:         else:
                    548:             zoom = 1
                    549:         self._change_option("--zoom %d" % zoom)
                    550: 
                    551:     # ------------ configured Hatari window size ---------------
                    552:     def get_window_size(self):
                    553:         if self.get("[Screen]", "bFullScreen"):
                    554:             print "WARNING: don't start Hatari UI with fullscreened Hatari!"
                    555: 
                    556:         # VDI resolution?
                    557:         if self.get("[Screen]", "bUseExtVdiResolutions"):
                    558:             width = self.get("[Screen]", "nVdiWidth")
                    559:             height = self.get("[Screen]", "nVdiHeight")
                    560:             return (width, height)
                    561:         
                    562:         # window sizes for other than ST & STE can differ
                    563:         if self.get("[System]", "nMachineType") not in (0, 1):
                    564:             print "WARNING: neither ST nor STE machine, window size inaccurate!"
                    565: 
                    566:         # mono monitor?
                    567:         if self.get("[Screen]", "nMonitorType") == 0:
                    568:             return (640, 400)
                    569: 
                    570:         # no, color
                    571:         width = 320
                    572:         height = 200
                    573:         # add overscan borders?
                    574:         if self.get("[Screen]", "bAllowOverscan"):
                    575:             # max size with overscan borders
                    576:             width += self.get("[Screen]", "nWindowBorderPixelsLeft")
                    577:             width += self.get("[Screen]", "nWindowBorderPixelsRight")
                    578:             height = 29 + 200 + self.get("[Screen]", "nWindowBorderPixelsBottom")
                    579:         # statusbar?
                    580:         if self.get("[Screen]", "bShowStatusbar"):
                    581:             height += 10
                    582:         # zoomed?
                    583:         if self.get("[Screen]", "bZoomLowRes"):
                    584:             width *= 2
                    585:             height *= 2
                    586: 
                    587:         return (width, height)

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.