]> glassweightruler.freedombox.rocks Git - waydroid.git/blob - tools/actions/initializer.py
gpu: Use intel_hasvk on intel graphics gen 8 or lower
[waydroid.git] / tools / actions / initializer.py
1 # Copyright 2021 Erfan Abdi
2 # SPDX-License-Identifier: GPL-3.0-or-later
3 import logging
4 import os
5 from tools import helpers
6 import tools.config
7
8 import sys
9 import threading
10 import multiprocessing
11 import select
12 import queue
13 import time
14 import dbus
15 import dbus.service
16 from gi.repository import GLib
17
18 def is_initialized(args):
19 return os.path.isfile(args.config) and os.path.isdir(tools.config.defaults["rootfs"])
20
21 def get_vendor_type(args):
22 vndk_str = helpers.props.host_get(args, "ro.vndk.version")
23 ret = "MAINLINE"
24 if vndk_str != "":
25 vndk = int(vndk_str)
26 if vndk > 31:
27 vndk -= 1 # 12L -> Halium 12
28 if vndk > 19:
29 ret = "HALIUM_" + str(vndk - 19)
30
31 return ret
32
33 def setup_config(args):
34 cfg = tools.config.load(args)
35 args.arch = helpers.arch.host()
36 cfg["waydroid"]["arch"] = args.arch
37
38 args.vendor_type = get_vendor_type(args)
39 cfg["waydroid"]["vendor_type"] = args.vendor_type
40
41 helpers.drivers.setupBinderNodes(args)
42 cfg["waydroid"]["binder"] = args.BINDER_DRIVER
43 cfg["waydroid"]["vndbinder"] = args.VNDBINDER_DRIVER
44 cfg["waydroid"]["hwbinder"] = args.HWBINDER_DRIVER
45
46 has_preinstalled_images = False
47 preinstalled_images_paths = tools.config.defaults["preinstalled_images_paths"]
48 for preinstalled_images in preinstalled_images_paths:
49 if os.path.isdir(preinstalled_images):
50 if os.path.isfile(preinstalled_images + "/system.img") and os.path.isfile(preinstalled_images + "/vendor.img"):
51 has_preinstalled_images = True
52 args.images_path = preinstalled_images
53 break
54 else:
55 logging.warning("Found directory {} but missing system or vendor image, ignoring...".format(preinstalled_images))
56
57 if not args.images_path:
58 args.images_path = tools.config.defaults["images_path"]
59 cfg["waydroid"]["images_path"] = args.images_path
60
61 if has_preinstalled_images:
62 cfg["waydroid"]["system_ota"] = args.system_ota = "None"
63 cfg["waydroid"]["vendor_ota"] = args.vendor_ota = "None"
64 cfg["waydroid"]["system_datetime"] = tools.config.defaults["system_datetime"]
65 cfg["waydroid"]["vendor_datetime"] = tools.config.defaults["vendor_datetime"]
66 tools.config.save(args, cfg)
67 return True
68
69 channels_cfg = tools.config.load_channels()
70 if not args.system_channel:
71 args.system_channel = channels_cfg["channels"]["system_channel"]
72 if not args.vendor_channel:
73 args.vendor_channel = channels_cfg["channels"]["vendor_channel"]
74 if not args.rom_type:
75 args.rom_type = channels_cfg["channels"]["rom_type"]
76 if not args.system_type:
77 args.system_type = channels_cfg["channels"]["system_type"]
78
79 if not args.system_channel or not args.vendor_channel:
80 logging.error("ERROR: You must provide 'System OTA' and 'Vendor OTA' URLs.")
81 return False
82
83 args.system_ota = args.system_channel + "/" + args.rom_type + \
84 "/waydroid_" + args.arch + "/" + args.system_type + ".json"
85 system_request = helpers.http.retrieve(args.system_ota)
86 if system_request[0] != 200:
87 raise ValueError(
88 "Failed to get system OTA channel: {}, error: {}".format(args.system_ota, system_request[0]))
89
90 device_codename = helpers.props.host_get(args, "ro.product.device")
91 args.vendor_type = None
92 for vendor in [device_codename, get_vendor_type(args)]:
93 vendor_ota = args.vendor_channel + "/waydroid_" + \
94 args.arch + "/" + vendor.replace(" ", "_") + ".json"
95 vendor_request = helpers.http.retrieve(vendor_ota)
96 if vendor_request[0] == 200:
97 args.vendor_type = vendor
98 args.vendor_ota = vendor_ota
99 break
100
101 if not args.vendor_type:
102 raise ValueError(
103 "Failed to get vendor OTA channel: {}".format(vendor_ota))
104
105 if args.system_ota != cfg["waydroid"].get("system_ota"):
106 cfg["waydroid"]["system_datetime"] = tools.config.defaults["system_datetime"]
107 if args.vendor_ota != cfg["waydroid"].get("vendor_ota"):
108 cfg["waydroid"]["vendor_datetime"] = tools.config.defaults["vendor_datetime"]
109
110 cfg["waydroid"]["vendor_type"] = args.vendor_type
111 cfg["waydroid"]["system_ota"] = args.system_ota
112 cfg["waydroid"]["vendor_ota"] = args.vendor_ota
113 tools.config.save(args, cfg)
114 return True
115
116 def init(args):
117 if not is_initialized(args) or args.force:
118 initializer_service = None
119 try:
120 initializer_service = tools.helpers.ipc.DBusContainerService("/Initializer", "id.waydro.Initializer")
121 except dbus.DBusException:
122 pass
123 if not setup_config(args):
124 return
125 status = "STOPPED"
126 if os.path.exists(tools.config.defaults["lxc"] + "/waydroid"):
127 status = helpers.lxc.status(args)
128 if status != "STOPPED":
129 logging.info("Stopping container")
130 try:
131 container = tools.helpers.ipc.DBusContainerService()
132 args.session = container.GetSession()
133 container.Stop(False)
134 except Exception as e:
135 logging.debug(e)
136 tools.actions.container_manager.stop(args)
137 if args.images_path not in tools.config.defaults["preinstalled_images_paths"]:
138 helpers.images.get(args)
139 else:
140 helpers.images.remove_overlay(args)
141 if not os.path.isdir(tools.config.defaults["rootfs"]):
142 os.mkdir(tools.config.defaults["rootfs"])
143 if not os.path.isdir(tools.config.defaults["overlay"]):
144 os.mkdir(tools.config.defaults["overlay"])
145 os.mkdir(tools.config.defaults["overlay"]+"/vendor")
146 if not os.path.isdir(tools.config.defaults["overlay_rw"]):
147 os.mkdir(tools.config.defaults["overlay_rw"])
148 os.mkdir(tools.config.defaults["overlay_rw"]+"/system")
149 os.mkdir(tools.config.defaults["overlay_rw"]+"/vendor")
150 helpers.drivers.probeAshmemDriver(args)
151 helpers.lxc.setup_host_perms(args)
152 helpers.lxc.set_lxc_config(args)
153 helpers.lxc.make_base_props(args)
154 if status != "STOPPED":
155 logging.info("Starting container")
156 try:
157 container.Start(args.session)
158 except Exception as e:
159 logging.debug(e)
160 logging.error("Failed to restart container. Please do so manually.")
161
162 if "running_init_in_service" not in args or not args.running_init_in_service:
163 try:
164 if initializer_service:
165 initializer_service.Done()
166 except dbus.DBusException:
167 pass
168 else:
169 logging.info("Already initialized")
170
171 def wait_for_init(args):
172 helpers.ipc.create_channel("remote_init_output")
173
174 mainloop = GLib.MainLoop()
175 dbus_obj = DbusInitializer(mainloop, dbus.SystemBus(), '/Initializer', args)
176 mainloop.run()
177
178 # After init
179 dbus_obj.remove_from_connection()
180
181 class DbusInitializer(dbus.service.Object):
182 def __init__(self, looper, bus, object_path, args):
183 self.args = args
184 self.looper = looper
185 dbus.service.Object.__init__(self, bus, object_path)
186
187 @dbus.service.method("id.waydro.Initializer", in_signature='a{ss}', out_signature='', sender_keyword="sender", connection_keyword="conn")
188 def Init(self, params, sender=None, conn=None):
189 channels_cfg = tools.config.load_channels()
190 no_auth = params["system_channel"] == channels_cfg["channels"]["system_channel"] and \
191 params["vendor_channel"] == channels_cfg["channels"]["vendor_channel"]
192 if no_auth or ensure_polkit_auth(sender, conn, "id.waydro.Initializer.Init"):
193 threading.Thread(target=remote_init_server, args=(self.args, params)).start()
194 else:
195 raise PermissionError("Polkit: Authentication failed")
196
197 @dbus.service.method("id.waydro.Initializer", in_signature='', out_signature='')
198 def Done(self):
199 if is_initialized(self.args):
200 self.looper.quit()
201
202 def ensure_polkit_auth(sender, conn, privilege):
203 dbus_info = dbus.Interface(conn.get_object("org.freedesktop.DBus", "/org/freedesktop/DBus/Bus", False), "org.freedesktop.DBus")
204 pid = dbus_info.GetConnectionUnixProcessID(sender)
205 polkit = dbus.Interface(dbus.SystemBus().get_object("org.freedesktop.PolicyKit1", "/org/freedesktop/PolicyKit1/Authority", False), "org.freedesktop.PolicyKit1.Authority")
206 try:
207 (is_auth, _, _) = polkit.CheckAuthorization(
208 ("unix-process", {
209 "pid": dbus.UInt32(pid, variant_level=1),
210 "start-time": dbus.UInt64(0, variant_level=1)}),
211 privilege, {"AllowUserInteraction": "true"},
212 dbus.UInt32(1),
213 "",
214 timeout=300)
215 return is_auth
216 except dbus.DBusException:
217 raise PermissionError("Polkit: Authentication timed out")
218
219 def background_remote_init_process(args):
220 with helpers.ipc.open_channel("remote_init_output", "wb") as channel_out:
221 class StdoutRedirect(logging.StreamHandler):
222 def write(self, s):
223 channel_out.write(str.encode(s))
224 def flush(self):
225 pass
226 def emit(self, record):
227 if record.levelno >= logging.INFO:
228 self.write(self.format(record) + self.terminator)
229
230 out = StdoutRedirect()
231 sys.stdout = sys.stderr = out
232 logging.getLogger().addHandler(out)
233
234 ctl_queue = queue.Queue()
235 def try_init(args):
236 try:
237 init(args)
238 except Exception as e:
239 print(str(e))
240 finally:
241 ctl_queue.put(0)
242
243 def poll_pipe():
244 poller = select.poll()
245 poller.register(channel_out, select.POLLERR)
246 poller.poll()
247 # When reaching here the client was terminated
248 ctl_queue.put(0)
249
250 init_thread = threading.Thread(target=try_init, args=(args,))
251 init_thread.daemon = True
252 init_thread.start()
253
254 poll_thread = threading.Thread(target=poll_pipe)
255 poll_thread.daemon = True
256 poll_thread.start()
257
258 # Join any one of the two threads
259 # Then exit the subprocess to kill the remaining thread.
260 # Can you believe this is the only way to kill a thread in python???
261 ctl_queue.get()
262
263 sys.stdout = sys.__stdout__
264 sys.stderr = sys.__stderr__
265 logging.getLogger().removeHandler(out)
266
267 def remote_init_server(args, params):
268 args.force = True
269 args.images_path = ""
270 args.rom_type = ""
271 args.system_channel = params["system_channel"]
272 args.vendor_channel = params["vendor_channel"]
273 args.system_type = params["system_type"]
274 args.running_init_in_service = True
275
276 p = multiprocessing.Process(target=background_remote_init_process, args=(args,))
277 p.daemon = True
278 p.start()
279 p.join()
280
281 def remote_init_client(args):
282 # Local imports cause Gtk is intrusive
283 import gi
284 gi.require_version("Gtk", "3.0")
285 from gi.repository import Gtk
286
287 bus = dbus.SystemBus()
288
289 if is_initialized(args):
290 try:
291 tools.helpers.ipc.DBusContainerService("/Initializer", "id.waydro.Initializer").Done()
292 except dbus.DBusException:
293 pass
294 return
295
296 def notify_and_quit(caller):
297 if is_initialized(args):
298 try:
299 tools.helpers.ipc.DBusContainerService("/Initializer", "id.waydro.Initializer").Done()
300 except dbus.DBusException:
301 pass
302 GLib.idle_add(Gtk.main_quit)
303
304 class WaydroidInitWindow(Gtk.Window):
305 def __init__(self):
306 super().__init__(title="Initialize Waydroid")
307 channels_cfg = tools.config.load_channels()
308
309 self.set_default_size(600, 250)
310 self.set_icon_name("waydroid")
311
312 grid = Gtk.Grid(row_spacing=6, column_spacing=6, margin=10, column_homogeneous=True)
313 grid.set_hexpand(True)
314 grid.set_vexpand(True)
315 self.add(grid)
316
317 sysOtaLabel = Gtk.Label("System OTA")
318 sysOtaEntry = Gtk.Entry()
319 sysOtaEntry.set_text(channels_cfg["channels"]["system_channel"])
320 grid.attach(sysOtaLabel, 0, 0, 1, 1)
321 grid.attach_next_to(sysOtaEntry ,sysOtaLabel, Gtk.PositionType.RIGHT, 2, 1)
322 self.sysOta = sysOtaEntry.get_buffer()
323
324 vndOtaLabel = Gtk.Label("Vendor OTA")
325 vndOtaEntry = Gtk.Entry()
326 vndOtaEntry.set_text(channels_cfg["channels"]["vendor_channel"])
327 grid.attach(vndOtaLabel, 0, 1, 1, 1)
328 grid.attach_next_to(vndOtaEntry, vndOtaLabel, Gtk.PositionType.RIGHT, 2, 1)
329 self.vndOta = vndOtaEntry.get_buffer()
330
331 sysTypeLabel = Gtk.Label("Android Type")
332 sysTypeCombo = Gtk.ComboBoxText()
333 sysTypeCombo.set_entry_text_column(0)
334 for t in ["VANILLA", "GAPPS"]:
335 sysTypeCombo.append_text(t)
336 sysTypeCombo.set_active(0)
337 grid.attach(sysTypeLabel, 0, 2, 1, 1)
338 grid.attach_next_to(sysTypeCombo, sysTypeLabel, Gtk.PositionType.RIGHT, 2, 1)
339 self.sysType = sysTypeCombo
340
341 downloadBtn = Gtk.Button("Download")
342 downloadBtn.connect("clicked", self.on_download_btn_clicked)
343 grid.attach(downloadBtn, 1,3,1,1)
344 self.downloadBtn = downloadBtn
345
346 doneBtn = Gtk.Button("Done")
347 doneBtn.connect("clicked", lambda x: self.destroy())
348 doneBtn.get_style_context().add_class('suggested-action')
349 grid.attach_next_to(doneBtn, downloadBtn, Gtk.PositionType.RIGHT, 1, 1)
350 self.doneBtn = doneBtn
351
352 outScrolledWindow = Gtk.ScrolledWindow()
353 outScrolledWindow.set_hexpand(True)
354 outScrolledWindow.set_vexpand(True)
355 outTextView = Gtk.TextView()
356 outTextView.set_property('editable', False)
357 outTextView.set_property('cursor-visible', False)
358 outScrolledWindow.add(outTextView)
359 grid.attach(outScrolledWindow, 0, 4, 3, 1)
360 self.outScrolledWindow = outScrolledWindow
361 self.outTextView = outTextView
362 self.outBuffer = outTextView.get_buffer()
363 self.outBuffer.create_mark("end", self.outBuffer.get_end_iter(), False)
364
365 self.open_channel = None
366
367 def scroll_to_bottom(self):
368 self.outTextView.scroll_mark_onscreen(self.outBuffer.get_mark("end"))
369
370 def on_download_btn_clicked(self, widget):
371 widget.set_sensitive(False)
372 self.doneBtn.hide()
373 self.outTextView.show()
374 init_params = (self.sysOta.get_text(), self.vndOta.get_text(), self.sysType.get_active_text())
375 init_runner = threading.Thread(target=self.run_init, args=init_params)
376 init_runner.daemon = True
377 init_runner.start()
378
379 def run_init(self, systemOta, vendorOta, systemType):
380 def draw_sync(s):
381 if s.startswith('\r'):
382 last = self.outBuffer.get_iter_at_line(self.outBuffer.get_line_count()-1)
383 last.backward_char()
384 self.outBuffer.delete(last, self.outBuffer.get_end_iter())
385 self.outBuffer.insert(self.outBuffer.get_end_iter(), s)
386 self.scroll_to_bottom()
387 def draw(s):
388 GLib.idle_add(draw_sync, s)
389
390 if self.open_channel is not None:
391 self.open_channel.close()
392 # Wait for other end to reset
393 time.sleep(1)
394
395 draw("Waiting for waydroid container service...\n")
396 try:
397 params = {
398 "system_channel": self.sysOta.get_text(),
399 "vendor_channel": self.vndOta.get_text(),
400 "system_type": self.sysType.get_active_text()
401 }
402 tools.helpers.ipc.DBusContainerService("/Initializer", "id.waydro.Initializer").Init(params, timeout=310)
403 except dbus.DBusException as e:
404 if e.get_dbus_name() == "org.freedesktop.DBus.Python.PermissionError":
405 draw(e.get_dbus_message().splitlines()[-1] + "\n")
406 else:
407 draw("The waydroid container service is not listening\n")
408 GLib.idle_add(self.downloadBtn.set_sensitive, True)
409 return
410
411 with helpers.ipc.open_channel("remote_init_output", "rb") as channel:
412 self.open_channel = channel
413 GLib.idle_add(self.downloadBtn.set_sensitive, True)
414 line = ""
415 try:
416 while True:
417 data = channel.read(1)
418 if len(data) == 0:
419 draw(line)
420 break
421 c = data.decode()
422 if c == '\r':
423 draw(line)
424 line = c
425 else:
426 line += c
427 if c == '\n':
428 draw(line)
429 line = ""
430 except:
431 draw("\nInterrupted\n")
432
433 if is_initialized(args):
434 GLib.idle_add(self.doneBtn.show)
435 draw("Done\n")
436
437
438 GLib.set_prgname("Waydroid")
439 win = WaydroidInitWindow()
440 win.connect("destroy", notify_and_quit)
441
442 win.show_all()
443 win.outTextView.hide()
444 win.doneBtn.hide()
445
446 Gtk.main()