]> glassweightruler.freedombox.rocks Git - waydroid.git/blob - tools/actions/initializer.py
Configure multiple preinstalled images paths
[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
14 def is_initialized(args):
15 return os.path.isfile(args.config) and os.path.isdir(tools.config.defaults["rootfs"])
16
17 def get_vendor_type(args):
18 vndk_str = helpers.props.host_get(args, "ro.vndk.version")
19 ret = "MAINLINE"
20 if vndk_str != "":
21 vndk = int(vndk_str)
22 if vndk > 19:
23 ret = "HALIUM_" + str(vndk - 19)
24
25 return ret
26
27 def setup_config(args):
28 cfg = tools.config.load(args)
29 args.arch = helpers.arch.host()
30 cfg["waydroid"]["arch"] = args.arch
31
32 preinstalled_images_paths = tools.config.defaults["preinstalled_images_paths"]
33 if not args.images_path:
34 for preinstalled_images in preinstalled_images_paths:
35 if os.path.isdir(preinstalled_images):
36 if os.path.isfile(preinstalled_images + "/system.img") and os.path.isfile(preinstalled_images + "/vendor.img"):
37 args.images_path = preinstalled_images
38 break
39 else:
40 logging.warning("Found directory {} but missing system or vendor image, ignoring...".format(preinstalled_images))
41
42 if not args.images_path:
43 args.images_path = tools.config.defaults["images_path"]
44 cfg["waydroid"]["images_path"] = args.images_path
45
46 channels_cfg = tools.config.load_channels()
47 if not args.system_channel:
48 args.system_channel = channels_cfg["channels"]["system_channel"]
49 if not args.vendor_channel:
50 args.vendor_channel = channels_cfg["channels"]["vendor_channel"]
51 if not args.rom_type:
52 args.rom_type = channels_cfg["channels"]["rom_type"]
53 if not args.system_type:
54 args.system_type = channels_cfg["channels"]["system_type"]
55
56 args.system_ota = args.system_channel + "/" + args.rom_type + \
57 "/waydroid_" + args.arch + "/" + args.system_type + ".json"
58 system_request = helpers.http.retrieve(args.system_ota)
59 if system_request[0] != 200:
60 if args.images_path not in preinstalled_images_paths:
61 raise ValueError(
62 "Failed to get system OTA channel: {}, error: {}".format(args.system_ota, system_request[0]))
63 else:
64 args.system_ota = "None"
65
66 device_codename = helpers.props.host_get(args, "ro.product.device")
67 args.vendor_type = None
68 for vendor in [device_codename, get_vendor_type(args)]:
69 vendor_ota = args.vendor_channel + "/waydroid_" + \
70 args.arch + "/" + vendor.replace(" ", "_") + ".json"
71 vendor_request = helpers.http.retrieve(vendor_ota)
72 if vendor_request[0] == 200:
73 args.vendor_type = vendor
74 args.vendor_ota = vendor_ota
75 break
76
77 if not args.vendor_type:
78 if args.images_path not in preinstalled_images_paths:
79 raise ValueError(
80 "Failed to get vendor OTA channel: {}".format(vendor_ota))
81 else:
82 args.vendor_ota = "None"
83 args.vendor_type = get_vendor_type(args)
84
85 if args.system_ota != cfg["waydroid"].get("system_ota"):
86 cfg["waydroid"]["system_datetime"] = tools.config.defaults["system_datetime"]
87 if args.vendor_ota != cfg["waydroid"].get("vendor_ota"):
88 cfg["waydroid"]["vendor_datetime"] = tools.config.defaults["vendor_datetime"]
89
90 cfg["waydroid"]["vendor_type"] = args.vendor_type
91 cfg["waydroid"]["system_ota"] = args.system_ota
92 cfg["waydroid"]["vendor_ota"] = args.vendor_ota
93 helpers.drivers.setupBinderNodes(args)
94 cfg["waydroid"]["binder"] = args.BINDER_DRIVER
95 cfg["waydroid"]["vndbinder"] = args.VNDBINDER_DRIVER
96 cfg["waydroid"]["hwbinder"] = args.HWBINDER_DRIVER
97 tools.config.save(args, cfg)
98
99 def init(args):
100 if not is_initialized(args) or args.force:
101 setup_config(args)
102 status = "STOPPED"
103 if os.path.exists(tools.config.defaults["lxc"] + "/waydroid"):
104 status = helpers.lxc.status(args)
105 if status != "STOPPED":
106 logging.info("Stopping container")
107 helpers.lxc.stop(args)
108 helpers.images.umount_rootfs(args)
109 if args.images_path not in tools.config.defaults["preinstalled_images_paths"]:
110 helpers.images.get(args)
111 if not os.path.isdir(tools.config.defaults["rootfs"]):
112 os.mkdir(tools.config.defaults["rootfs"])
113 helpers.lxc.setup_host_perms(args)
114 helpers.lxc.set_lxc_config(args)
115 helpers.lxc.make_base_props(args)
116 if status != "STOPPED":
117 logging.info("Starting container")
118 helpers.images.mount_rootfs(args, args.images_path)
119 helpers.lxc.start(args)
120
121 helpers.ipc.notify(channel="init", msg="done")
122 else:
123 logging.info("Already initialized")
124
125 def wait_for_init(args):
126 helpers.ipc.create_channel("init")
127 helpers.ipc.create_channel("remote_init_output")
128 while True:
129 print('WayDroid waiting for initialization...')
130 msg = helpers.ipc.read_one(channel="init")
131 if msg == "done":
132 if is_initialized(args):
133 break
134 else:
135 continue
136 if msg.startswith("cmd"):
137 remote_init_server(args, msg)
138 continue
139
140 def background_remote_init_process(args):
141 with helpers.ipc.open_channel("remote_init_output", "wb") as channel_out:
142 class StdoutRedirect(logging.StreamHandler):
143 def write(self, s):
144 channel_out.write(str.encode(s))
145 def flush(self):
146 pass
147 def emit(self, record):
148 if record.levelno >= logging.INFO:
149 self.write(self.format(record) + self.terminator)
150
151 out = StdoutRedirect()
152 sys.stdout = sys.stderr = out
153 logging.getLogger().addHandler(out)
154
155 ctl_queue = queue.Queue()
156 def try_init(args):
157 try:
158 init(args)
159 except Exception as e:
160 print(str(e))
161 finally:
162 ctl_queue.put(0)
163
164 def poll_pipe():
165 poller = select.poll()
166 poller.register(channel_out, select.POLLERR)
167 poller.poll()
168 # When reaching here the client was terminated
169 ctl_queue.put(0)
170
171 init_thread = threading.Thread(target=try_init, args=(args,))
172 init_thread.daemon = True
173 init_thread.start()
174
175 poll_thread = threading.Thread(target=poll_pipe)
176 poll_thread.daemon = True
177 poll_thread.start()
178
179 # Join any one of the two threads
180 # Then exit the subprocess to kill the remaining thread.
181 # Can you believe this is the only way to kill a thread in python???
182 ctl_queue.get()
183
184 sys.stdout = sys.__stdout__
185 sys.stderr = sys.__stderr__
186 logging.getLogger().removeHandler(out)
187
188 def remote_init_server(args, cmd):
189 params = cmd.split('\f')[1:]
190 args.force = True
191 args.images_path = ""
192 args.rom_type = ""
193 args.system_channel = params[0]
194 args.vendor_channel = params[1]
195 args.system_type = params[2]
196
197 p = multiprocessing.Process(target=background_remote_init_process, args=(args,))
198 p.daemon = True
199 p.start()
200 p.join()
201
202 def remote_init_client(args):
203 # Local imports cause Gtk is intrusive
204 import gi
205 gi.require_version("Gtk", "3.0")
206 from gi.repository import Gtk, GLib
207
208 if is_initialized(args):
209 helpers.ipc.notify(channel="init", msg="done")
210 return
211
212 def notify_and_quit(caller):
213 if is_initialized(args):
214 helpers.ipc.notify(channel="init", msg="done")
215 GLib.idle_add(Gtk.main_quit)
216
217 class WaydroidInitWindow(Gtk.Window):
218 def __init__(self):
219 super().__init__(title="Initialize Waydroid")
220 self.set_default_size(600, 250)
221 self.set_icon_from_file(tools.config.tools_src + "/data/AppIcon.png")
222
223 grid = Gtk.Grid(row_spacing=6, column_spacing=6, margin=10, column_homogeneous=True)
224 grid.set_hexpand(True)
225 grid.set_vexpand(True)
226 self.add(grid)
227
228 sysOtaLabel = Gtk.Label("System OTA")
229 sysOtaEntry = Gtk.Entry()
230 sysOtaEntry.set_text(tools.config.channels_defaults["system_channel"])
231 grid.attach(sysOtaLabel, 0, 0, 1, 1)
232 grid.attach_next_to(sysOtaEntry ,sysOtaLabel, Gtk.PositionType.RIGHT, 2, 1)
233 self.sysOta = sysOtaEntry.get_buffer()
234
235 vndOtaLabel = Gtk.Label("Vendor OTA")
236 vndOtaEntry = Gtk.Entry()
237 vndOtaEntry.set_text(tools.config.channels_defaults["vendor_channel"])
238 grid.attach(vndOtaLabel, 0, 1, 1, 1)
239 grid.attach_next_to(vndOtaEntry, vndOtaLabel, Gtk.PositionType.RIGHT, 2, 1)
240 self.vndOta = vndOtaEntry.get_buffer()
241
242 sysTypeLabel = Gtk.Label("Android Type")
243 sysTypeCombo = Gtk.ComboBoxText()
244 sysTypeCombo.set_entry_text_column(0)
245 for t in ["VANILLA", "GAPPS"]:
246 sysTypeCombo.append_text(t)
247 sysTypeCombo.set_active(0)
248 grid.attach(sysTypeLabel, 0, 2, 1, 1)
249 grid.attach_next_to(sysTypeCombo, sysTypeLabel, Gtk.PositionType.RIGHT, 2, 1)
250 self.sysType = sysTypeCombo
251
252 downloadBtn = Gtk.Button("Download")
253 downloadBtn.connect("clicked", self.on_download_btn_clicked)
254 grid.attach(downloadBtn, 1,3,1,1)
255 self.downloadBtn = downloadBtn
256
257 doneBtn = Gtk.Button("Done")
258 doneBtn.connect("clicked", lambda x: self.destroy())
259 doneBtn.get_style_context().add_class('suggested-action')
260 grid.attach_next_to(doneBtn, downloadBtn, Gtk.PositionType.RIGHT, 1, 1)
261 self.doneBtn = doneBtn
262
263 outScrolledWindow = Gtk.ScrolledWindow()
264 outScrolledWindow.set_hexpand(True)
265 outScrolledWindow.set_vexpand(True)
266 outTextView = Gtk.TextView()
267 outTextView.set_property('editable', False)
268 outTextView.set_property('cursor-visible', False)
269 outScrolledWindow.add(outTextView)
270 grid.attach(outScrolledWindow, 0, 4, 3, 1)
271 self.outScrolledWindow = outScrolledWindow
272 self.outTextView = outTextView
273 self.outBuffer = outTextView.get_buffer()
274 self.outBuffer.create_mark("end", self.outBuffer.get_end_iter(), False)
275
276 self.open_channel = None
277
278 def scroll_to_bottom(self):
279 self.outTextView.scroll_mark_onscreen(self.outBuffer.get_mark("end"))
280
281 def on_download_btn_clicked(self, widget):
282 widget.set_sensitive(False)
283 self.doneBtn.hide()
284 self.outTextView.show()
285 init_params = (self.sysOta.get_text(), self.vndOta.get_text(), self.sysType.get_active_text())
286 init_runner = threading.Thread(target=self.run_init, args=init_params)
287 init_runner.daemon = True
288 init_runner.start()
289
290 def run_init(self, systemOta, vendorOta, systemType):
291 def draw_sync(s):
292 if s.startswith('\r'):
293 last = self.outBuffer.get_iter_at_line(self.outBuffer.get_line_count()-1)
294 last.backward_char()
295 self.outBuffer.delete(last, self.outBuffer.get_end_iter())
296 self.outBuffer.insert(self.outBuffer.get_end_iter(), s)
297 self.scroll_to_bottom()
298 def draw(s):
299 GLib.idle_add(draw_sync, s)
300
301 if self.open_channel is not None:
302 self.open_channel.close()
303 # Wait for other end to re-open
304 tmp = helpers.ipc.open_channel("init", "w", buffering=1)
305 tmp.close()
306
307 draw("Waiting for waydroid container service...\n")
308 try:
309 helpers.ipc.notify_blocking(channel="init", msg="{}\f{}\f{}\f{}".format(
310 "cmd", self.sysOta.get_text(), self.vndOta.get_text(), self.sysType.get_active_text()))
311 except:
312 draw("The waydroid container service is not listening\n")
313 GLib.idle_add(self.downloadBtn.set_sensitive, True)
314 return
315
316 with helpers.ipc.open_channel("remote_init_output", "rb") as channel:
317 self.open_channel = channel
318 GLib.idle_add(self.downloadBtn.set_sensitive, True)
319 line = ""
320 try:
321 while True:
322 data = channel.read(1)
323 if len(data) == 0:
324 draw(line)
325 break
326 c = data.decode()
327 if c == '\r':
328 draw(line)
329 line = c
330 else:
331 line += c
332 if c == '\n':
333 draw(line)
334 line = ""
335 except:
336 draw("\nInterrupted\n")
337
338 if is_initialized(args):
339 GLib.idle_add(self.doneBtn.show)
340 draw("Done\n")
341
342
343 GLib.set_prgname("Waydroid")
344 win = WaydroidInitWindow()
345 win.connect("destroy", notify_and_quit)
346
347 win.show_all()
348 win.outTextView.hide()
349 win.doneBtn.hide()
350
351 Gtk.main()