|
|
1.1 ! root 1: #!/usr/bin/env python ! 2: # ! 3: # Tests embedding hatari with three different methods: ! 4: # "hatari": ask Hatari to reparent to given window ! 5: # "sdl1": Give SDL window into which it should reparent ! 6: # -> SDL doesn't handle (mouse, key, expose) events ! 7: # although according to "xev" it's window receives them! ! 8: # Bug in SDL (not one of the originally needed features?)? ! 9: # "reparent": Find Hatari window and reparent it into pygtk widget in python ! 10: # - Needs "xwininfo" and "awk" i.e. not real alternative ! 11: # ! 12: # Using three alternative widgets: ! 13: # "drawingarea" ! 14: # "eventbox" ! 15: # "socket" ! 16: # ! 17: # Results: ! 18: # drawingarea & evenbox / sdl1 & hatari: ! 19: # -> XCB fails unknown seq num when importing Hatari window ! 20: # drawingarea & evenbox / reparent: ! 21: # -> keyboard input doesn't work ! 22: # socket / sdl1: ! 23: # -> keyboard input doesn't work ! 24: # socket / reparent & hatari: ! 25: # -> works fine ! 26: ! 27: import os ! 28: import sys ! 29: import time ! 30: ! 31: import gi ! 32: gi.require_version('Gtk', '3.0') ! 33: from gi.repository import Gtk ! 34: from gi.repository import Gdk ! 35: from gi.repository import GdkX11 ! 36: from gi.repository import GObject ! 37: ! 38: def usage(error): ! 39: print("\nusage: %s <widget> <embed method>\n" % sys.argv[0].split(os.path.sep)[-1]) ! 40: print("Opens window with given <widget>, runs Hatari and tries to embed it") ! 41: print("with given <method>\n") ! 42: print("<widget> can be <drawingarea|eventbox|socket>") ! 43: print("<method> can be <sdl1|hatari|reparent>\n") ! 44: print("ERROR: %s\n" % error) ! 45: sys.exit(1) ! 46: ! 47: ! 48: class AppUI(): ! 49: hatari_wd = 640 ! 50: hatari_ht = 436 # Hatari window enables statusbar by default ! 51: ! 52: def __init__(self, widget, method): ! 53: if method in ("hatari", "reparent", "sdl1"): ! 54: self.method = method ! 55: else: ! 56: usage("unknown <method> '%s'" % method) ! 57: if widget == "drawingarea": ! 58: widgettype = Gtk.DrawingArea ! 59: elif widget == "eventbox": ! 60: widgettype = Gtk.EventBox ! 61: elif widget == "socket": ! 62: # XEMBED socket for Hatari/SDL ! 63: widgettype = Gtk.Socket ! 64: else: ! 65: usage("unknown <widget> '%s'" % widget) ! 66: self.window = self.create_window() ! 67: self.add_hatari_parent(self.window, widgettype) ! 68: self.window.show_all() ! 69: # wait a while before starting Hatari to make ! 70: # sure parent window has been realized ! 71: GObject.timeout_add(500, self.timeout_cb) ! 72: ! 73: def create_window(self): ! 74: window = Gtk.Window(Gtk.WindowType.TOPLEVEL) ! 75: window.connect("destroy", self.do_quit) ! 76: return window ! 77: ! 78: def do_quit(self, widget): ! 79: if self.hatari_pid: ! 80: os.kill(self.hatari_pid, 9) ! 81: print("killed Hatari PID %d" % self.hatari_pid) ! 82: self.hatari_pid = 0 ! 83: Gtk.main_quit() ! 84: ! 85: def add_hatari_parent(self, parent, widgettype): ! 86: # Note: CAN_FOCUS has to be set for the widget embedding Hatari ! 87: # and *unset* for everything else, otherwise Hatari doesn't ! 88: # receive *any* keyevents. ! 89: self.hatari_pid = 0 ! 90: vbox = Gtk.VBox() ! 91: button = Gtk.Button("Test Button", can_focus=False) ! 92: vbox.add(button) ! 93: widget = widgettype(can_focus=True) ! 94: widget.set_size_request(self.hatari_wd, self.hatari_ht) ! 95: widget.set_events(Gdk.EventMask.ALL_EVENTS_MASK) ! 96: self.hatariparent = widget ! 97: # TODO: when running 320x200, parent could be centered to here ! 98: vbox.add(widget) ! 99: # test focus ! 100: label = Gtk.Label(label="Test SpinButton:") ! 101: vbox.add(label) ! 102: # disable focus, otherwise Hatari doesn't receive keys!!! ! 103: spin = Gtk.SpinButton(can_focus=False) ! 104: spin.set_range(0, 10) ! 105: spin.set_digits(0) ! 106: spin.set_numeric(True) ! 107: spin.set_increments(1, 2) ! 108: vbox.add(spin) ! 109: parent.add(vbox) ! 110: ! 111: def timeout_cb(self): ! 112: self.do_hatari_method() ! 113: return False # only once ! 114: ! 115: def do_hatari_method(self): ! 116: pid = os.fork() ! 117: if pid < 0: ! 118: print("ERROR: fork()ing Hatari failed!") ! 119: return ! 120: if pid: ! 121: # in parent ! 122: if self.method == "reparent": ! 123: hatari_win = self.find_hatari_window() ! 124: if hatari_win: ! 125: self.reparent_hatari_window(hatari_win) ! 126: self.hatari_pid = pid ! 127: else: ! 128: os.kill(pid, signal.SIGKILL) ! 129: print("killed process with PID %d" % pid) ! 130: self.hatari_pid = 0 ! 131: else: ! 132: print("Waiting Hatari process to embed itself...") ! 133: # method == "sdl1" or "hatari" ! 134: self.hatari_pid = pid ! 135: else: ! 136: # child runs Hatari ! 137: args = ("hatari", "-m", "-z", "2") ! 138: os.execvpe("hatari", args, self.get_hatari_env()) ! 139: ! 140: def get_hatari_env(self): ! 141: if self.method == "reparent": ! 142: return os.environ ! 143: # tell Hatari / SDL to use (embed itself inside) given widget's window ! 144: win_id = self.hatariparent.get_window().get_xid() ! 145: env = os.environ ! 146: if self.method == "sdl1": ! 147: # SDL2 doesn't support this anymore ! 148: env["SDL_WINDOWID"] = str(win_id) ! 149: elif self.method == "hatari": ! 150: env["PARENT_WIN_ID"] = str(win_id) ! 151: return env ! 152: ! 153: def find_hatari_window(self): ! 154: # find hatari window by its WM class string and reparent it ! 155: # wait 1s to make sure Hatari child gets its window up ! 156: cmd = """sleep 1; xwininfo -root -tree|awk '/"hatari" "hatari"/{print $1}'""" ! 157: counter = 0 ! 158: while counter < 8: ! 159: pipe = os.popen(cmd) ! 160: windows = [] ! 161: for line in pipe.readlines(): ! 162: windows.append(int(line, 16)) ! 163: try: ! 164: pipe.close() ! 165: except IOError: ! 166: # handle child process exiting silently ! 167: pass ! 168: if not windows: ! 169: counter += 1 ! 170: print("WARNING: no Hatari window found yet, retrying...") ! 171: time.sleep(1) ! 172: continue ! 173: if len(windows) > 1: ! 174: print("WARNING: multiple Hatari windows, picking first one...") ! 175: return windows[0] ! 176: print("ERROR: no windows with the 'hatari' WM class found") ! 177: return None ! 178: ! 179: def reparent_hatari_window(self, hatari_win): ! 180: print("Importing foreign (Hatari) window 0x%x" % hatari_win) ! 181: display = GdkX11.X11Display.get_default() ! 182: window = GdkX11.X11Window.foreign_new_for_display(display, hatari_win) ! 183: if not window: ! 184: print("ERROR: X window importing failed!") ! 185: return False ! 186: parent = self.hatariparent.get_window() ! 187: if not window: ! 188: print("ERROR: where hatariparent window disappeared?") ! 189: return False ! 190: print("Found Hatari window ID: 0x%x, reparenting..." % hatari_win) ! 191: print("...to container window ID: 0x%x" % parent.get_xid()) ! 192: window.reparent(parent, 0, 0) ! 193: #window.reparent(self.hatariparent.get_toplevel().window, 0, 0) ! 194: #window.reparent(self.hatariparent.get_root_window(), 0, 0) ! 195: #window.show() ! 196: #window.raise_() ! 197: # If python would destroy the Gtk widget when it goes out of scope, ! 198: # the foreign window widget destructor would delete Hatari window. ! 199: # So, keep a reference ! 200: #self.hatariwindow = window ! 201: return True ! 202: ! 203: def run(self): ! 204: self.window.show_all() ! 205: Gtk.main() ! 206: ! 207: ! 208: if len(sys.argv) != 3: ! 209: usage("wrong number of arguments") ! 210: app = AppUI(sys.argv[1], sys.argv[2]) ! 211: app.run()
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.