1 # Copyright 2021 Erfan Abdi
2 # SPDX-License-Identifier: GPL-3.0-or-later
5 from tools
import helpers
10 import multiprocessing
14 def is_initialized(args
):
15 return os
.path
.isfile(args
.config
) and os
.path
.isdir(tools
.config
.defaults
["rootfs"])
17 def get_vendor_type(args
):
18 vndk_str
= helpers
.props
.host_get(args
, "ro.vndk.version")
23 ret
= "HALIUM_" + str(vndk
- 19)
27 def setup_config(args
):
28 cfg
= tools
.config
.load(args
)
29 args
.arch
= helpers
.arch
.host()
30 cfg
["waydroid"]["arch"] = args
.arch
32 preinstalled_images
= tools
.config
.defaults
["preinstalled_images_path"]
33 if not args
.images_path
:
34 if os
.path
.isdir(preinstalled_images
):
35 if os
.path
.isfile(preinstalled_images
+ "/system.img") and os
.path
.isfile(preinstalled_images
+ "/vendor.img"):
36 args
.images_path
= preinstalled_images
38 logging
.error("Missing system or vendor on preinstalled images dir, fallback to default")
39 if not args
.images_path
:
40 args
.images_path
= tools
.config
.defaults
["images_path"]
41 cfg
["waydroid"]["images_path"] = args
.images_path
43 channels_cfg
= tools
.config
.load_channels()
44 if not args
.system_channel
:
45 args
.system_channel
= channels_cfg
["channels"]["system_channel"]
46 if not args
.vendor_channel
:
47 args
.vendor_channel
= channels_cfg
["channels"]["vendor_channel"]
49 args
.rom_type
= channels_cfg
["channels"]["rom_type"]
50 if not args
.system_type
:
51 args
.system_type
= channels_cfg
["channels"]["system_type"]
53 args
.system_ota
= args
.system_channel
+ "/" + args
.rom_type
+ \
54 "/waydroid_" + args
.arch
+ "/" + args
.system_type
+ ".json"
55 system_request
= helpers
.http
.retrieve(args
.system_ota
)
56 if system_request
[0] != 200:
57 if args
.images_path
!= preinstalled_images
:
59 "Failed to get system OTA channel: {}, error: {}".format(args
.system_ota
, system_request
[0]))
61 args
.system_ota
= "None"
63 device_codename
= helpers
.props
.host_get(args
, "ro.product.device")
64 args
.vendor_type
= None
65 for vendor
in [device_codename
, get_vendor_type(args
)]:
66 vendor_ota
= args
.vendor_channel
+ "/waydroid_" + \
67 args
.arch
+ "/" + vendor
.replace(" ", "_") + ".json"
68 vendor_request
= helpers
.http
.retrieve(vendor_ota
)
69 if vendor_request
[0] == 200:
70 args
.vendor_type
= vendor
71 args
.vendor_ota
= vendor_ota
74 if not args
.vendor_type
:
75 if args
.images_path
!= preinstalled_images
:
77 "Failed to get vendor OTA channel: {}".format(vendor_ota
))
79 args
.vendor_ota
= "None"
80 args
.vendor_type
= get_vendor_type(args
)
82 if args
.system_ota
!= cfg
["waydroid"].get("system_ota"):
83 cfg
["waydroid"]["system_datetime"] = tools
.config
.defaults
["system_datetime"]
84 if args
.vendor_ota
!= cfg
["waydroid"].get("vendor_ota"):
85 cfg
["waydroid"]["vendor_datetime"] = tools
.config
.defaults
["vendor_datetime"]
87 cfg
["waydroid"]["vendor_type"] = args
.vendor_type
88 cfg
["waydroid"]["system_ota"] = args
.system_ota
89 cfg
["waydroid"]["vendor_ota"] = args
.vendor_ota
90 helpers
.drivers
.setupBinderNodes(args
)
91 cfg
["waydroid"]["binder"] = args
.BINDER_DRIVER
92 cfg
["waydroid"]["vndbinder"] = args
.VNDBINDER_DRIVER
93 cfg
["waydroid"]["hwbinder"] = args
.HWBINDER_DRIVER
94 tools
.config
.save(args
, cfg
)
97 if not is_initialized(args
) or args
.force
:
100 if os
.path
.exists(tools
.config
.defaults
["lxc"] + "/waydroid"):
101 status
= helpers
.lxc
.status(args
)
102 if status
!= "STOPPED":
103 logging
.info("Stopping container")
104 helpers
.lxc
.stop(args
)
105 helpers
.images
.umount_rootfs(args
)
106 if args
.images_path
!= tools
.config
.defaults
["preinstalled_images_path"]:
107 helpers
.images
.get(args
)
108 if not os
.path
.isdir(tools
.config
.defaults
["rootfs"]):
109 os
.mkdir(tools
.config
.defaults
["rootfs"])
110 helpers
.lxc
.setup_host_perms(args
)
111 helpers
.lxc
.set_lxc_config(args
)
112 helpers
.lxc
.make_base_props(args
)
113 if status
!= "STOPPED":
114 logging
.info("Starting container")
115 helpers
.images
.mount_rootfs(args
, args
.images_path
)
116 helpers
.lxc
.start(args
)
118 helpers
.ipc
.notify(channel
="init", msg
="done")
120 logging
.info("Already initialized")
122 def wait_for_init(args
):
123 helpers
.ipc
.create_channel("init")
124 helpers
.ipc
.create_channel("remote_init_output")
126 print('WayDroid waiting for initialization...')
127 msg
= helpers
.ipc
.read_one(channel
="init")
129 if is_initialized(args
):
133 if msg
.startswith("cmd"):
134 remote_init_server(args
, msg
)
137 def background_remote_init_process(args
):
138 with helpers
.ipc
.open_channel("remote_init_output", "wb") as channel_out
:
139 class StdoutRedirect(logging
.StreamHandler
):
141 channel_out
.write(str.encode(s
))
144 def emit(self
, record
):
145 if record
.levelno
>= logging
.INFO
:
146 self
.write(self
.format(record
) + self
.terminator
)
148 out
= StdoutRedirect()
149 sys
.stdout
= sys
.stderr
= out
150 logging
.getLogger().addHandler(out
)
152 ctl_queue
= queue
.Queue()
156 except Exception as e
:
162 poller
= select
.poll()
163 poller
.register(channel_out
, select
.POLLERR
)
165 # When reaching here the client was terminated
168 init_thread
= threading
.Thread(target
=try_init
, args
=(args
,))
169 init_thread
.daemon
= True
172 poll_thread
= threading
.Thread(target
=poll_pipe
)
173 poll_thread
.daemon
= True
176 # Join any one of the two threads
177 # Then exit the subprocess to kill the remaining thread.
178 # Can you believe this is the only way to kill a thread in python???
181 sys
.stdout
= sys
.__stdout
__
182 sys
.stderr
= sys
.__stderr
__
183 logging
.getLogger().removeHandler(out
)
185 def remote_init_server(args
, cmd
):
186 params
= cmd
.split('\f')[1:]
188 args
.images_path
= ""
190 args
.system_channel
= params
[0]
191 args
.vendor_channel
= params
[1]
192 args
.system_type
= params
[2]
194 p
= multiprocessing
.Process(target
=background_remote_init_process
, args
=(args
,))
199 def remote_init_client(args
):
200 # Local imports cause Gtk is intrusive
202 gi
.require_version("Gtk", "3.0")
203 from gi
.repository
import Gtk
, GLib
205 if is_initialized(args
):
206 helpers
.ipc
.notify(channel
="init", msg
="done")
209 def notify_and_quit(caller
):
210 if is_initialized(args
):
211 helpers
.ipc
.notify(channel
="init", msg
="done")
212 GLib
.idle_add(Gtk
.main_quit
)
214 class WaydroidInitWindow(Gtk
.Window
):
216 super().__init
__(title
="Initialize Waydroid")
217 self
.set_default_size(600, 250)
218 self
.set_icon_from_file(tools
.config
.tools_src
+ "/data/AppIcon.png")
220 grid
= Gtk
.Grid(row_spacing
=6, column_spacing
=6, margin
=10, column_homogeneous
=True)
221 grid
.set_hexpand(True)
222 grid
.set_vexpand(True)
225 sysOtaLabel
= Gtk
.Label("System OTA")
226 sysOtaEntry
= Gtk
.Entry()
227 sysOtaEntry
.set_text(tools
.config
.channels_defaults
["system_channel"])
228 grid
.attach(sysOtaLabel
, 0, 0, 1, 1)
229 grid
.attach_next_to(sysOtaEntry
,sysOtaLabel
, Gtk
.PositionType
.RIGHT
, 2, 1)
230 self
.sysOta
= sysOtaEntry
.get_buffer()
232 vndOtaLabel
= Gtk
.Label("Vendor OTA")
233 vndOtaEntry
= Gtk
.Entry()
234 vndOtaEntry
.set_text(tools
.config
.channels_defaults
["vendor_channel"])
235 grid
.attach(vndOtaLabel
, 0, 1, 1, 1)
236 grid
.attach_next_to(vndOtaEntry
, vndOtaLabel
, Gtk
.PositionType
.RIGHT
, 2, 1)
237 self
.vndOta
= vndOtaEntry
.get_buffer()
239 sysTypeLabel
= Gtk
.Label("Android Type")
240 sysTypeCombo
= Gtk
.ComboBoxText()
241 sysTypeCombo
.set_entry_text_column(0)
242 for t
in ["VANILLA", "GAPPS"]:
243 sysTypeCombo
.append_text(t
)
244 sysTypeCombo
.set_active(0)
245 grid
.attach(sysTypeLabel
, 0, 2, 1, 1)
246 grid
.attach_next_to(sysTypeCombo
, sysTypeLabel
, Gtk
.PositionType
.RIGHT
, 2, 1)
247 self
.sysType
= sysTypeCombo
249 downloadBtn
= Gtk
.Button("Download")
250 downloadBtn
.connect("clicked", self
.on_download_btn_clicked
)
251 grid
.attach(downloadBtn
, 1,3,1,1)
252 self
.downloadBtn
= downloadBtn
254 doneBtn
= Gtk
.Button("Done")
255 doneBtn
.connect("clicked", lambda x
: self
.destroy())
256 doneBtn
.get_style_context().add_class('suggested-action')
257 grid
.attach_next_to(doneBtn
, downloadBtn
, Gtk
.PositionType
.RIGHT
, 1, 1)
258 self
.doneBtn
= doneBtn
260 outScrolledWindow
= Gtk
.ScrolledWindow()
261 outScrolledWindow
.set_hexpand(True)
262 outScrolledWindow
.set_vexpand(True)
263 outTextView
= Gtk
.TextView()
264 outTextView
.set_property('editable', False)
265 outTextView
.set_property('cursor-visible', False)
266 outScrolledWindow
.add(outTextView
)
267 grid
.attach(outScrolledWindow
, 0, 4, 3, 1)
268 self
.outScrolledWindow
= outScrolledWindow
269 self
.outTextView
= outTextView
270 self
.outBuffer
= outTextView
.get_buffer()
271 self
.outBuffer
.create_mark("end", self
.outBuffer
.get_end_iter(), False)
273 self
.open_channel
= None
275 def scroll_to_bottom(self
):
276 self
.outTextView
.scroll_mark_onscreen(self
.outBuffer
.get_mark("end"))
278 def on_download_btn_clicked(self
, widget
):
279 widget
.set_sensitive(False)
281 self
.outTextView
.show()
282 init_params
= (self
.sysOta
.get_text(), self
.vndOta
.get_text(), self
.sysType
.get_active_text())
283 init_runner
= threading
.Thread(target
=self
.run_init
, args
=init_params
)
284 init_runner
.daemon
= True
287 def run_init(self
, systemOta
, vendorOta
, systemType
):
289 if s
.startswith('\r'):
290 last
= self
.outBuffer
.get_iter_at_line(self
.outBuffer
.get_line_count()-1)
292 self
.outBuffer
.delete(last
, self
.outBuffer
.get_end_iter())
293 self
.outBuffer
.insert(self
.outBuffer
.get_end_iter(), s
)
294 self
.scroll_to_bottom()
296 GLib
.idle_add(draw_sync
, s
)
298 if self
.open_channel
is not None:
299 self
.open_channel
.close()
300 # Wait for other end to re-open
301 tmp
= helpers
.ipc
.open_channel("init", "w", buffering
=1)
304 draw("Waiting for waydroid container service...\n")
306 helpers
.ipc
.notify_blocking(channel
="init", msg
="{}\f{}\f{}\f{}".format(
307 "cmd", self
.sysOta
.get_text(), self
.vndOta
.get_text(), self
.sysType
.get_active_text()))
309 draw("The waydroid container service is not listening\n")
310 GLib
.idle_add(self
.downloadBtn
.set_sensitive
, True)
313 with helpers
.ipc
.open_channel("remote_init_output", "rb") as channel
:
314 self
.open_channel
= channel
315 GLib
.idle_add(self
.downloadBtn
.set_sensitive
, True)
319 data
= channel
.read(1)
333 draw("\nInterrupted\n")
335 if is_initialized(args
):
336 GLib
.idle_add(self
.doneBtn
.show
)
340 win
= WaydroidInitWindow()
341 win
.connect("destroy", notify_and_quit
)
344 win
.outTextView
.hide()