|
|
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.