Annotation of hatari/python-ui/tests/gtk-hatari-embed-test.py, revision 1.1

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()

unix.superglobalmegacorp.com

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