Annotation of hatari/python-ui/debugui.py, revision 1.1.1.8

1.1.1.8 ! root        1: #!/usr/bin/env python
1.1       root        2: #
1.1.1.8 ! root        3: # A Debug UI for the Hatari, part of Python Gtk Hatari UI
1.1       root        4: #
1.1.1.8 ! root        5: # Copyright (C) 2008-2019 by Eero Tamminen
1.1       root        6: #
                      7: # This program is free software; you can redistribute it and/or modify
                      8: # it under the terms of the GNU General Public License as published by
                      9: # the Free Software Foundation; either version 2 of the License, or
                     10: # (at your option) any later version.
                     11: #
                     12: # This program is distributed in the hope that it will be useful,
                     13: # but WITHOUT ANY WARRANTY; without even the implied warranty of
                     14: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
                     15: # GNU General Public License for more details.
                     16: 
                     17: import os
1.1.1.8 ! root       18: import gi
        !            19: # use correct version of gtk
        !            20: gi.require_version('Gtk', '3.0')
        !            21: from gi.repository import Gtk
        !            22: from gi.repository import Gdk
        !            23: from gi.repository import Pango
1.1       root       24: 
                     25: from config import ConfigStore
                     26: from uihelpers import UInfo, create_button, create_toggle, \
                     27:      create_table_dialog, table_add_entry_row, table_add_widget_row, \
                     28:      get_save_filename, FselEntry
                     29: from dialogs import TodoDialog, ErrorDialog, AskDialog, KillDialog
                     30: 
                     31: 
                     32: def dialog_apply_cb(widget, dialog):
1.1.1.8 ! root       33:     dialog.response(Gtk.ResponseType.APPLY)
1.1       root       34: 
                     35: 
                     36: # -------------
                     37: # Table dialogs
                     38: 
                     39: class SaveDialog:
                     40:     def __init__(self, parent):
1.1.1.3   root       41:         table, self.dialog = create_table_dialog(parent, "Save from memory", 3, 2)
1.1       root       42:         self.file = FselEntry(self.dialog)
1.1.1.8 ! root       43:         table_add_widget_row(table, 0, 0, "File name:", self.file.get_container())
        !            44:         self.address = table_add_entry_row(table, 1, 0, "Save address:", 6)
1.1       root       45:         self.address.connect("activate", dialog_apply_cb, self.dialog)
1.1.1.8 ! root       46:         self.length = table_add_entry_row(table, 2, 0, "Number of bytes:", 6)
1.1       root       47:         self.length.connect("activate", dialog_apply_cb, self.dialog)
1.1.1.8 ! root       48: 
1.1       root       49:     def run(self, address):
                     50:         "run(address) -> (filename,address,length), all as strings"
                     51:         if address:
                     52:             self.address.set_text("%06X" % address)
                     53:         self.dialog.show_all()
                     54:         filename = length = None
                     55:         while 1:
                     56:             response = self.dialog.run()
1.1.1.8 ! root       57:             if response == Gtk.ResponseType.APPLY:
1.1       root       58:                 filename = self.file.get_filename()
                     59:                 address_txt = self.address.get_text()
                     60:                 length_txt = self.length.get_text()
                     61:                 if filename and address_txt and length_txt:
                     62:                     try:
                     63:                         address = int(address_txt, 16)
                     64:                     except ValueError:
                     65:                         ErrorDialog(self.dialog).run("address needs to be in hex")
                     66:                         continue
                     67:                     try:
                     68:                         length = int(length_txt)
                     69:                     except ValueError:
                     70:                         ErrorDialog(self.dialog).run("length needs to be a number")
                     71:                         continue
                     72:                     if os.path.exists(filename):
                     73:                         question = "File:\n%s\nexists, replace?" % filename
                     74:                         if not AskDialog(self.dialog).run(question):
                     75:                             continue
                     76:                     break
                     77:                 else:
                     78:                     ErrorDialog(self.dialog).run("please fill the field(s)")
                     79:             else:
                     80:                 break
                     81:         self.dialog.hide()
                     82:         return (filename, address, length)
                     83: 
                     84: 
                     85: class LoadDialog:
                     86:     def __init__(self, parent):
1.1.1.8 ! root       87:         chooser = Gtk.FileChooserButton('Select a File')
1.1       root       88:         chooser.set_local_only(True)  # Hatari cannot access URIs
                     89:         chooser.set_width_chars(12)
1.1.1.3   root       90:         table, self.dialog = create_table_dialog(parent, "Load to memory", 2, 2)
1.1.1.8 ! root       91:         self.file = table_add_widget_row(table, 0, 0, "File name:", chooser)
        !            92:         self.address = table_add_entry_row(table, 1, 0, "Load address:", 6)
1.1       root       93:         self.address.connect("activate", dialog_apply_cb, self.dialog)
                     94: 
                     95:     def run(self, address):
                     96:         "run(address) -> (filename,address), all as strings"
                     97:         if address:
                     98:             self.address.set_text("%06X" % address)
                     99:         self.dialog.show_all()
                    100:         filename = None
                    101:         while 1:
                    102:             response = self.dialog.run()
1.1.1.8 ! root      103:             if response == Gtk.ResponseType.APPLY:
1.1       root      104:                 filename = self.file.get_filename()
                    105:                 address_txt = self.address.get_text()
                    106:                 if filename and address_txt:
                    107:                     try:
                    108:                         address = int(address_txt, 16)
                    109:                     except ValueError:
                    110:                         ErrorDialog(self.dialog).run("address needs to be in hex")
                    111:                         continue
                    112:                     break
                    113:                 else:
                    114:                     ErrorDialog(self.dialog).run("please fill the field(s)")
                    115:             else:
                    116:                 break
                    117:         self.dialog.hide()
                    118:         return (filename, address)
                    119: 
                    120: 
                    121: class OptionsDialog:
                    122:     def __init__(self, parent):
1.1.1.8 ! root      123:         self.dialog = Gtk.Dialog("Debugger UI options", parent,
        !           124:             Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
        !           125:             (Gtk.STOCK_APPLY,  Gtk.ResponseType.APPLY,
        !           126:              Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE))
1.1.1.3   root      127: 
1.1.1.8 ! root      128:         self.lines = Gtk.Adjustment(0, 5, 50)
        !           129:         scale = Gtk.HScale(adjustment=self.lines)
1.1.1.3   root      130:         scale.set_digits(0)
1.1.1.8 ! root      131: 
        !           132:         self.follow_pc = Gtk.CheckButton("On stop, set address to PC")
1.1.1.3   root      133: 
                    134:         vbox = self.dialog.vbox
1.1.1.8 ! root      135:         vbox.add(Gtk.Label(label="Memdump/disasm lines:"))
1.1.1.3   root      136:         vbox.add(scale)
                    137:         vbox.add(self.follow_pc)
                    138:         vbox.show_all()
                    139: 
                    140:     def run(self, lines, follow_pc):
                    141:         "run(lines,follow_pc) -> (lines,follow_pc)"
                    142:         self.follow_pc.set_active(follow_pc)
                    143:         self.lines.set_value(lines)
1.1       root      144:         self.dialog.show_all()
1.1.1.3   root      145:         response = self.dialog.run()
1.1.1.8 ! root      146:         if response == Gtk.ResponseType.APPLY:
1.1.1.3   root      147:             lines = int(self.lines.get_value())
                    148:             follow_pc = self.follow_pc.get_active()
1.1       root      149:         self.dialog.hide()
1.1.1.3   root      150:         return (lines, follow_pc)
1.1       root      151: 
                    152: 
                    153: # ----------------------------------------------------
                    154: 
                    155: # constants for the other classes
                    156: class Constants:
                    157:     # dump modes
                    158:     DISASM = 1
                    159:     MEMDUMP = 2
                    160:     REGISTERS = 3
                    161:     # move IDs
                    162:     MOVE_MIN = 1
                    163:     MOVE_MED = 2
                    164:     MOVE_MAX = 3
                    165: 
                    166: 
                    167: # class for the memory address entry, view (label) and
                    168: # the logic for memory dump modes and moving in memory
                    169: class MemoryAddress:
                    170:     # class variables
                    171:     debug_output = None
                    172:     hatari = None
                    173: 
                    174:     def __init__(self, hatariobj):
                    175:         # hatari
                    176:         self.debug_output = hatariobj.open_debug_output()
                    177:         self.hatari = hatariobj
                    178:         # widgets
                    179:         self.entry, self.memory = self.create_widgets()
                    180:         # settings
                    181:         self.dumpmode = Constants.REGISTERS
1.1.1.3   root      182:         self.follow_pc = True
1.1       root      183:         self.lines = 12
                    184:         # addresses
                    185:         self.first = None
                    186:         self.second = None
                    187:         self.last = None
1.1.1.8 ! root      188: 
1.1       root      189:     def clear(self):
1.1.1.3   root      190:         if self.follow_pc:
                    191:             # get first address from PC when next stopped
                    192:             self.first = None
1.1       root      193:         self.second = None
                    194:         self.last  = None
                    195: 
                    196:     def create_widgets(self):
1.1.1.8 ! root      197:         entry = Gtk.Entry(max_length=6, width_chars=6)
1.1       root      198:         entry.connect("activate", self._entry_cb)
1.1.1.8 ! root      199:         memory = Gtk.Label(halign=Gtk.Align.START, margin_start=8, margin_end=8, margin_top=8)
        !           200:         mono = Pango.FontDescription("monospace")
1.1       root      201:         memory.modify_font(mono)
                    202:         entry.modify_font(mono)
                    203:         return (entry, memory)
                    204: 
                    205:     def _entry_cb(self, widget):
                    206:         try:
                    207:             address = int(widget.get_text(), 16)
                    208:         except ValueError:
                    209:             ErrorDialog(widget.get_toplevel()).run("invalid address")
                    210:             return
                    211:         self.dump(address)
                    212: 
                    213:     def reset_entry(self):
                    214:         self.entry.set_text("%06X" % self.first)
1.1.1.8 ! root      215: 
1.1       root      216:     def get(self):
                    217:         return self.first
                    218: 
                    219:     def get_memory_label(self):
                    220:         return self.memory
1.1.1.8 ! root      221: 
1.1       root      222:     def get_address_entry(self):
                    223:         return self.entry
                    224: 
1.1.1.3   root      225:     def get_follow_pc(self):
                    226:         return self.follow_pc
1.1.1.8 ! root      227: 
1.1.1.3   root      228:     def set_follow_pc(self, follow_pc):
                    229:         self.follow_pc = follow_pc
                    230: 
1.1       root      231:     def get_lines(self):
                    232:         return self.lines
1.1.1.8 ! root      233: 
1.1       root      234:     def set_lines(self, lines):
                    235:         self.lines = lines
1.1.1.8 ! root      236: 
1.1       root      237:     def set_dumpmode(self, mode):
                    238:         self.dumpmode = mode
                    239:         self.dump()
1.1.1.8 ! root      240: 
1.1       root      241:     def dump(self, address = None, move_idx = 0):
                    242:         if self.dumpmode == Constants.REGISTERS:
                    243:             output = self._get_registers()
                    244:             self.memory.set_label("".join(output))
                    245:             return
1.1.1.3   root      246: 
                    247:         if not address:
                    248:             if not self.first:
                    249:                 self._get_registers()
                    250:             address = self.first
                    251: 
1.1       root      252:         if not address:
1.1.1.4   root      253:             print("ERROR: address needed")
1.1       root      254:             return
1.1.1.8 ! root      255: 
1.1       root      256:         if self.dumpmode == Constants.MEMDUMP:
                    257:             output = self._get_memdump(address, move_idx)
                    258:         elif self.dumpmode == Constants.DISASM:
                    259:             output = self._get_disasm(address, move_idx)
                    260:         else:
1.1.1.4   root      261:             print("ERROR: unknown dumpmode:", self.dumpmode)
1.1       root      262:             return
                    263:         self.memory.set_label("".join(output))
                    264:         if move_idx:
                    265:             self.reset_entry()
1.1.1.8 ! root      266: 
1.1       root      267:     def _get_registers(self):
                    268:         self.hatari.debug_command("r")
                    269:         output = self.hatari.get_lines(self.debug_output)
                    270:         if not self.first:
1.1.1.6   root      271:             # 2nd last line has first PC in 1st column, last line next PC in 2nd column
1.1       root      272:             self.second = int(output[-1][output[-1].find(":")+2:], 16)
1.1.1.6   root      273:             # OldUAE CPU core has ':' in both
                    274:             offset = output[-2].find(":")
                    275:             if offset < 0:
                    276:                 # WinUAE CPU core only in one
                    277:                 offset = output[-2].find(" ")
                    278:             if offset < 0:
                    279:                 print("ERROR: unable to parse register dump line:\n\t'%s'", output[-2])
                    280:                 return output
                    281:             self.first = int(output[-2][:offset], 16)
1.1       root      282:             self.reset_entry()
                    283:         return output
                    284: 
                    285:     def _get_memdump(self, address, move_idx):
                    286:         linewidth = 16
                    287:         screenful = self.lines*linewidth
                    288:         # no move, left/right, up/down, page up/down (no overlap)
                    289:         offsets = [0, 2, linewidth, screenful]
                    290:         offset = offsets[abs(move_idx)]
                    291:         if move_idx < 0:
                    292:             address -= offset
                    293:         else:
                    294:             address += offset
                    295:         self._set_clamped(address, address+screenful)
1.1.1.2   root      296:         self.hatari.debug_command("m $%06x-$%06x" % (self.first, self.last))
1.1       root      297:         # get & set debugger command results
                    298:         output = self.hatari.get_lines(self.debug_output)
                    299:         self.second = address + linewidth
                    300:         return output
1.1.1.8 ! root      301: 
1.1       root      302:     def _get_disasm(self, address, move_idx):
                    303:         # TODO: uses brute force i.e. ask for more lines that user has
                    304:         # requested to be sure that the window is filled, assuming
                    305:         # 6 bytes is largest possible instruction+args size
                    306:         # (I don't remember anymore my m68k asm...)
                    307:         screenful = 6*self.lines
                    308:         # no move, left/right, up/down, page up/down
                    309:         offsets = [0, 2, 4, screenful]
                    310:         offset = offsets[abs(move_idx)]
                    311:         # force one line of overlap in page up/down
                    312:         if move_idx < 0:
                    313:             address -= offset
                    314:             if address < 0:
                    315:                 address = 0
                    316:             if move_idx == -Constants.MOVE_MAX and self.second:
                    317:                 screenful = self.second - address
                    318:         else:
                    319:             if move_idx == Constants.MOVE_MED and self.second:
                    320:                 address = self.second
                    321:             elif move_idx == Constants.MOVE_MAX and self.last:
                    322:                 address = self.last
                    323:             else:
                    324:                 address += offset
                    325:         self._set_clamped(address, address+screenful)
1.1.1.2   root      326:         self.hatari.debug_command("d $%06x-$%06x" % (self.first, self.last))
1.1       root      327:         # get & set debugger command results
                    328:         output = self.hatari.get_lines(self.debug_output)
                    329:         # cut output to desired length and check new addresses
                    330:         if len(output) > self.lines:
                    331:             if move_idx < 0:
                    332:                 output = output[-self.lines:]
                    333:             else:
                    334:                 output = output[:self.lines]
                    335:         # with disasm need to re-get the addresses from the output
1.1.1.4   root      336:         self.first  = int(output[0][1:output[0].find(":")], 16)
                    337:         self.second = int(output[1][1:output[1].find(":")], 16)
                    338:         self.last   = int(output[-1][1:output[-1].find(":")], 16)
1.1       root      339:         return output
                    340: 
                    341:     def _set_clamped(self, first, last):
                    342:         "set_clamped(first,last), clamp addresses to valid address range and set them"
                    343:         assert(first < last)
                    344:         if first < 0:
                    345:             last = last-first
                    346:             first = 0
                    347:         if last > 0xffffff:
                    348:             first = 0xffffff - (last-first)
                    349:             last = 0xffffff
                    350:         self.first = first
                    351:         self.last = last
                    352: 
                    353: 
                    354: # the Hatari debugger UI class and methods
                    355: class HatariDebugUI:
1.1.1.8 ! root      356: 
1.1       root      357:     def __init__(self, hatariobj, do_destroy = False):
                    358:         self.address = MemoryAddress(hatariobj)
                    359:         self.hatari = hatariobj
                    360:         # set when needed/created
                    361:         self.dialog_load = None
                    362:         self.dialog_save = None
                    363:         self.dialog_options = None
                    364:         # set when UI created
                    365:         self.keys = None
                    366:         self.stop_button = None
                    367:         # set on option load
                    368:         self.config = None
                    369:         self.load_options()
                    370:         # UI initialization/creation
                    371:         self.window = self.create_ui("Hatari Debug UI", do_destroy)
1.1.1.8 ! root      372: 
1.1       root      373:     def create_ui(self, title, do_destroy):
                    374:         # buttons at top
1.1.1.8 ! root      375:         hbox1 = Gtk.HBox()
1.1       root      376:         self.create_top_buttons(hbox1)
                    377: 
                    378:         # disasm/memory dump at the middle
1.1.1.8 ! root      379:         addr = self.address.get_memory_label()
1.1       root      380: 
                    381:         # buttons at bottom
1.1.1.8 ! root      382:         hbox2 = Gtk.HBox()
1.1       root      383:         self.create_bottom_buttons(hbox2)
                    384: 
                    385:         # their container
1.1.1.8 ! root      386:         vbox = Gtk.VBox()
        !           387:         vbox.pack_start(hbox1, False, True, 0)
        !           388:         vbox.pack_start(addr, True, True, 0)
        !           389:         vbox.pack_start(hbox2, False, True, 0)
        !           390: 
1.1       root      391:         # and the window for all of this
1.1.1.8 ! root      392:         window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
        !           393:         window.set_events(Gdk.EventMask.KEY_RELEASE_MASK)
1.1       root      394:         window.connect("key_release_event", self.key_event_cb)
                    395:         if do_destroy:
                    396:             window.connect("delete_event", self.quit)
                    397:         else:
                    398:             window.connect("delete_event", self.hide)
                    399:         window.set_icon_from_file(UInfo.icon)
                    400:         window.set_title(title)
                    401:         window.add(vbox)
                    402:         return window
1.1.1.8 ! root      403: 
1.1       root      404:     def create_top_buttons(self, box):
                    405:         self.stop_button = create_toggle("Stop", self.stop_cb)
                    406:         box.add(self.stop_button)
                    407: 
                    408:         monitor = create_button("Monitor...", self.monitor_cb)
                    409:         box.add(monitor)
1.1.1.8 ! root      410: 
1.1       root      411:         buttons = (
                    412:             ("<<<", "Page_Up",  -Constants.MOVE_MAX),
                    413:             ("<<",  "Up",       -Constants.MOVE_MED),
                    414:             ("<",   "Left",     -Constants.MOVE_MIN),
                    415:             (">",   "Right",     Constants.MOVE_MIN),
                    416:             (">>",  "Down",      Constants.MOVE_MED),
                    417:             (">>>", "Page_Down", Constants.MOVE_MAX)
                    418:         )
                    419:         self.keys = {}
                    420:         for label, keyname, offset in buttons:
                    421:             button = create_button(label, self.set_address_offset, offset)
1.1.1.8 ! root      422:             keyval = Gdk.keyval_from_name(keyname)
1.1       root      423:             self.keys[keyval] =  offset
                    424:             box.add(button)
                    425: 
                    426:         # to middle of <<>> buttons
                    427:         address_entry = self.address.get_address_entry()
1.1.1.8 ! root      428:         box.pack_start(address_entry, False, True, 0)
1.1       root      429:         box.reorder_child(address_entry, 5)
                    430: 
                    431:     def create_bottom_buttons(self, box):
                    432:         radios = (
                    433:             ("Registers", Constants.REGISTERS),
                    434:             ("Memdump", Constants.MEMDUMP),
                    435:             ("Disasm", Constants.DISASM)
                    436:         )
                    437:         group = None
                    438:         for label, mode in radios:
1.1.1.8 ! root      439:             button = Gtk.RadioButton(label=label, group=group, can_focus=False)
1.1       root      440:             if not group:
                    441:                 group = button
                    442:             button.connect("toggled", self.dumpmode_cb, mode)
                    443:             box.add(button)
                    444:         group.set_active(True)
                    445: 
                    446:         dialogs = (
                    447:             ("Memload...", self.memload_cb),
                    448:             ("Memsave...", self.memsave_cb),
                    449:             ("Options...", self.options_cb)
                    450:         )
                    451:         for label, cb in dialogs:
                    452:             button = create_button(label, cb)
                    453:             box.add(button)
                    454: 
                    455:     def stop_cb(self, widget):
                    456:         if widget.get_active():
                    457:             self.hatari.pause()
                    458:             self.address.clear()
                    459:             self.address.dump()
                    460:         else:
                    461:             self.hatari.unpause()
                    462: 
                    463:     def dumpmode_cb(self, widget, mode):
                    464:         if widget.get_active():
                    465:             self.address.set_dumpmode(mode)
                    466: 
                    467:     def key_event_cb(self, widget, event):
                    468:         if event.keyval in self.keys:
                    469:             self.address.dump(None, self.keys[event.keyval])
                    470: 
                    471:     def set_address_offset(self, widget, move_idx):
                    472:         self.address.dump(None, move_idx)
1.1.1.8 ! root      473: 
1.1       root      474:     def monitor_cb(self, widget):
                    475:         TodoDialog(self.window).run("add register / memory address range monitor window.")
                    476: 
                    477:     def memload_cb(self, widget):
                    478:         if not self.dialog_load:
                    479:             self.dialog_load = LoadDialog(self.window)
                    480:         (filename, address) = self.dialog_load.run(self.address.get())
                    481:         if filename and address:
1.1.1.2   root      482:             self.hatari.debug_command("l %s $%06x" % (filename, address))
1.1       root      483: 
                    484:     def memsave_cb(self, widget):
                    485:         if not self.dialog_save:
                    486:             self.dialog_save = SaveDialog(self.window)
                    487:         (filename, address, length) = self.dialog_save.run(self.address.get())
                    488:         if filename and address and length:
1.1.1.2   root      489:             self.hatari.debug_command("s %s $%06x $%06x" % (filename, address, length))
1.1.1.8 ! root      490: 
1.1       root      491:     def options_cb(self, widget):
                    492:         if not self.dialog_options:
                    493:             self.dialog_options = OptionsDialog(self.window)
1.1.1.3   root      494:         old_lines = self.config.get("[General]", "nLines")
                    495:         old_follow_pc = self.config.get("[General]", "bFollowPC")
                    496:         lines, follow_pc = self.dialog_options.run(old_lines, old_follow_pc)
                    497:         if lines != old_lines:
1.1       root      498:             self.config.set("[General]", "nLines", lines)
                    499:             self.address.set_lines(lines)
1.1.1.3   root      500:         if follow_pc != old_follow_pc:
                    501:             self.config.set("[General]", "bFollowPC", follow_pc)
                    502:             self.address.set_follow_pc(follow_pc)
1.1       root      503: 
                    504:     def load_options(self):
                    505:         # TODO: move config to MemoryAddress class?
                    506:         # (depends on how monitoring of addresses should work)
                    507:         lines = self.address.get_lines()
1.1.1.3   root      508:         follow_pc = self.address.get_follow_pc()
1.1       root      509:         miss_is_error = False # needed for adding windows
1.1.1.3   root      510:         defaults = {
                    511:             "[General]": {
                    512:                "nLines": lines,
                    513:                 "bFollowPC": follow_pc
                    514:             }
                    515:         }
                    516:         userconfdir = ".hatari"
                    517:         config = ConfigStore(userconfdir, defaults, miss_is_error)
                    518:         configpath = config.get_filepath("debugui.cfg")
                    519:         config.load(configpath) # set defaults
                    520:         try:
                    521:             self.address.set_lines(config.get("[General]", "nLines"))
                    522:             self.address.set_follow_pc(config.get("[General]", "bFollowPC"))
                    523:         except (KeyError, AttributeError):
                    524:             ErrorDialog(None).run("Debug UI configuration mismatch!\nTry again after removing: '%s'." % configpath)
                    525:         self.config = config
1.1.1.8 ! root      526: 
1.1       root      527:     def save_options(self):
                    528:         self.config.save()
1.1.1.8 ! root      529: 
1.1       root      530:     def show(self):
                    531:         self.stop_button.set_active(True)
                    532:         self.window.show_all()
                    533:         self.window.deiconify()
                    534: 
                    535:     def hide(self, widget, arg):
                    536:         self.window.hide()
                    537:         self.stop_button.set_active(False)
                    538:         self.save_options()
                    539:         return True
                    540: 
                    541:     def quit(self, widget, arg):
                    542:         KillDialog(self.window).run(self.hatari)
1.1.1.8 ! root      543:         Gtk.main_quit()
1.1       root      544: 
                    545: 
                    546: def main():
                    547:     import sys
                    548:     from hatari import Hatari
                    549:     hatariobj = Hatari()
                    550:     if len(sys.argv) > 1:
                    551:         if sys.argv[1] in ("-h", "--help"):
1.1.1.4   root      552:             print("usage: %s [hatari options]" % os.path.basename(sys.argv[0]))
1.1       root      553:             return
                    554:         args = sys.argv[1:]
                    555:     else:
                    556:         args = None
                    557:     hatariobj.run(args)
                    558: 
                    559:     info = UInfo()
                    560:     debugui = HatariDebugUI(hatariobj, True)
                    561:     debugui.window.show_all()
1.1.1.8 ! root      562:     Gtk.main()
1.1       root      563:     debugui.save_options()
                    564: 
1.1.1.8 ! root      565: 
1.1       root      566: if __name__ == "__main__":
                    567:     main()

unix.superglobalmegacorp.com

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