Annotation of hatari/python-ui/tests/gtk-hatari-embed-test.py, revision 1.1.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.