1 # Copyright 2021 Erfan Abdi
2 # SPDX-License-Identifier: GPL-3.0-or-later
5 from tools
import helpers
10 import multiprocessing
16 from gi
.repository
import GLib
18 def is_initialized(args
):
19 return os
.path
.isfile(args
.config
) and os
.path
.isdir(tools
.config
.defaults
["rootfs"])
21 def get_vendor_type(args
):
22 vndk_str
= helpers
.props
.host_get(args
, "ro.vndk.version")
27 ret
= "HALIUM_" + str(vndk
- 19)
31 def setup_config(args
):
32 cfg
= tools
.config
.load(args
)
33 args
.arch
= helpers
.arch
.host()
34 cfg
["waydroid"]["arch"] = args
.arch
36 preinstalled_images_paths
= tools
.config
.defaults
["preinstalled_images_paths"]
37 if not args
.images_path
:
38 for preinstalled_images
in preinstalled_images_paths
:
39 if os
.path
.isdir(preinstalled_images
):
40 if os
.path
.isfile(preinstalled_images
+ "/system.img") and os
.path
.isfile(preinstalled_images
+ "/vendor.img"):
41 args
.images_path
= preinstalled_images
44 logging
.warning("Found directory {} but missing system or vendor image, ignoring...".format(preinstalled_images
))
46 if not args
.images_path
:
47 args
.images_path
= tools
.config
.defaults
["images_path"]
48 cfg
["waydroid"]["images_path"] = args
.images_path
50 channels_cfg
= tools
.config
.load_channels()
51 if not args
.system_channel
:
52 args
.system_channel
= channels_cfg
["channels"]["system_channel"]
53 if not args
.vendor_channel
:
54 args
.vendor_channel
= channels_cfg
["channels"]["vendor_channel"]
56 args
.rom_type
= channels_cfg
["channels"]["rom_type"]
57 if not args
.system_type
:
58 args
.system_type
= channels_cfg
["channels"]["system_type"]
60 args
.system_ota
= args
.system_channel
+ "/" + args
.rom_type
+ \
61 "/waydroid_" + args
.arch
+ "/" + args
.system_type
+ ".json"
62 system_request
= helpers
.http
.retrieve(args
.system_ota
)
63 if system_request
[0] != 200:
64 if args
.images_path
not in preinstalled_images_paths
:
66 "Failed to get system OTA channel: {}, error: {}".format(args
.system_ota
, system_request
[0]))
68 args
.system_ota
= "None"
70 device_codename
= helpers
.props
.host_get(args
, "ro.product.device")
71 args
.vendor_type
= None
72 for vendor
in [device_codename
, get_vendor_type(args
)]:
73 vendor_ota
= args
.vendor_channel
+ "/waydroid_" + \
74 args
.arch
+ "/" + vendor
.replace(" ", "_") + ".json"
75 vendor_request
= helpers
.http
.retrieve(vendor_ota
)
76 if vendor_request
[0] == 200:
77 args
.vendor_type
= vendor
78 args
.vendor_ota
= vendor_ota
81 if not args
.vendor_type
:
82 if args
.images_path
not in preinstalled_images_paths
:
84 "Failed to get vendor OTA channel: {}".format(vendor_ota
))
86 args
.vendor_ota
= "None"
87 args
.vendor_type
= get_vendor_type(args
)
89 if args
.system_ota
!= cfg
["waydroid"].get("system_ota"):
90 cfg
["waydroid"]["system_datetime"] = tools
.config
.defaults
["system_datetime"]
91 if args
.vendor_ota
!= cfg
["waydroid"].get("vendor_ota"):
92 cfg
["waydroid"]["vendor_datetime"] = tools
.config
.defaults
["vendor_datetime"]
94 cfg
["waydroid"]["vendor_type"] = args
.vendor_type
95 cfg
["waydroid"]["system_ota"] = args
.system_ota
96 cfg
["waydroid"]["vendor_ota"] = args
.vendor_ota
97 helpers
.drivers
.setupBinderNodes(args
)
98 cfg
["waydroid"]["binder"] = args
.BINDER_DRIVER
99 cfg
["waydroid"]["vndbinder"] = args
.VNDBINDER_DRIVER
100 cfg
["waydroid"]["hwbinder"] = args
.HWBINDER_DRIVER
101 tools
.config
.save(args
, cfg
)
104 if not is_initialized(args
) or args
.force
:
105 initializer_service
= None
107 initializer_service
= dbus
.SystemBus().get_object("id.waydro.ContainerService", "/Initializer")
108 except dbus
.DBusException
:
112 if os
.path
.exists(tools
.config
.defaults
["lxc"] + "/waydroid"):
113 status
= helpers
.lxc
.status(args
)
114 if status
!= "STOPPED":
115 logging
.info("Stopping container")
116 helpers
.lxc
.stop(args
)
117 helpers
.images
.umount_rootfs(args
)
118 if args
.images_path
not in tools
.config
.defaults
["preinstalled_images_paths"]:
119 helpers
.images
.get(args
)
120 if not os
.path
.isdir(tools
.config
.defaults
["rootfs"]):
121 os
.mkdir(tools
.config
.defaults
["rootfs"])
122 helpers
.lxc
.setup_host_perms(args
)
123 helpers
.lxc
.set_lxc_config(args
)
124 helpers
.lxc
.make_base_props(args
)
125 if status
!= "STOPPED":
126 logging
.info("Starting container")
127 helpers
.images
.mount_rootfs(args
, args
.images_path
)
128 helpers
.lxc
.start(args
)
130 if "running_init_in_service" not in args
or not args
.running_init_in_service
:
132 if initializer_service
:
133 initializer_service
.Done(dbus_interface
="id.waydro.Initializer")
134 except dbus
.DBusException
:
137 logging
.info("Already initialized")
139 def wait_for_init(args
):
140 helpers
.ipc
.create_channel("remote_init_output")
142 name
= dbus
.service
.BusName("id.waydro.ContainerService", dbus
.SystemBus())
143 mainloop
= GLib
.MainLoop()
144 dbus_obj
= DbusInitializer(mainloop
, dbus
.SystemBus(), '/Initializer', args
)
147 class DbusInitializer(dbus
.service
.Object
):
148 def __init__(self
, looper
, bus
, object_path
, args
):
151 dbus
.service
.Object
.__init
__(self
, bus
, object_path
)
153 @dbus.service.method("id.waydro.Initializer", in_signature
='a{ss}', out_signature
='')
154 def Init(self
, params
):
155 threading
.Thread(target
=remote_init_server
, args
=(self
.args
, params
)).start()
157 @dbus.service.method("id.waydro.Initializer", in_signature
='', out_signature
='')
159 if is_initialized(self
.args
):
162 def background_remote_init_process(args
):
163 with helpers
.ipc
.open_channel("remote_init_output", "wb") as channel_out
:
164 class StdoutRedirect(logging
.StreamHandler
):
166 channel_out
.write(str.encode(s
))
169 def emit(self
, record
):
170 if record
.levelno
>= logging
.INFO
:
171 self
.write(self
.format(record
) + self
.terminator
)
173 out
= StdoutRedirect()
174 sys
.stdout
= sys
.stderr
= out
175 logging
.getLogger().addHandler(out
)
177 ctl_queue
= queue
.Queue()
181 except Exception as e
:
187 poller
= select
.poll()
188 poller
.register(channel_out
, select
.POLLERR
)
190 # When reaching here the client was terminated
193 init_thread
= threading
.Thread(target
=try_init
, args
=(args
,))
194 init_thread
.daemon
= True
197 poll_thread
= threading
.Thread(target
=poll_pipe
)
198 poll_thread
.daemon
= True
201 # Join any one of the two threads
202 # Then exit the subprocess to kill the remaining thread.
203 # Can you believe this is the only way to kill a thread in python???
206 sys
.stdout
= sys
.__stdout
__
207 sys
.stderr
= sys
.__stderr
__
208 logging
.getLogger().removeHandler(out
)
210 def remote_init_server(args
, params
):
212 args
.images_path
= ""
214 args
.system_channel
= params
["system_channel"]
215 args
.vendor_channel
= params
["vendor_channel"]
216 args
.system_type
= params
["system_type"]
217 args
.running_init_in_service
= True
219 p
= multiprocessing
.Process(target
=background_remote_init_process
, args
=(args
,))
224 def remote_init_client(args
):
225 # Local imports cause Gtk is intrusive
227 gi
.require_version("Gtk", "3.0")
228 from gi
.repository
import Gtk
230 bus
= dbus
.SystemBus()
232 if is_initialized(args
):
234 bus
.get_object("id.waydro.ContainerService", "/Initializer").Done(dbus_interface
="id.waydro.Initializer")
235 except dbus
.DBusException
:
239 def notify_and_quit(caller
):
240 if is_initialized(args
):
242 bus
.get_object("id.waydro.ContainerService", "/Initializer").Done(dbus_interface
="id.waydro.Initializer")
243 except dbus
.DBusException
:
245 GLib
.idle_add(Gtk
.main_quit
)
247 class WaydroidInitWindow(Gtk
.Window
):
249 super().__init
__(title
="Initialize Waydroid")
250 self
.set_default_size(600, 250)
251 self
.set_icon_from_file(tools
.config
.tools_src
+ "/data/AppIcon.png")
253 grid
= Gtk
.Grid(row_spacing
=6, column_spacing
=6, margin
=10, column_homogeneous
=True)
254 grid
.set_hexpand(True)
255 grid
.set_vexpand(True)
258 sysOtaLabel
= Gtk
.Label("System OTA")
259 sysOtaEntry
= Gtk
.Entry()
260 sysOtaEntry
.set_text(tools
.config
.channels_defaults
["system_channel"])
261 grid
.attach(sysOtaLabel
, 0, 0, 1, 1)
262 grid
.attach_next_to(sysOtaEntry
,sysOtaLabel
, Gtk
.PositionType
.RIGHT
, 2, 1)
263 self
.sysOta
= sysOtaEntry
.get_buffer()
265 vndOtaLabel
= Gtk
.Label("Vendor OTA")
266 vndOtaEntry
= Gtk
.Entry()
267 vndOtaEntry
.set_text(tools
.config
.channels_defaults
["vendor_channel"])
268 grid
.attach(vndOtaLabel
, 0, 1, 1, 1)
269 grid
.attach_next_to(vndOtaEntry
, vndOtaLabel
, Gtk
.PositionType
.RIGHT
, 2, 1)
270 self
.vndOta
= vndOtaEntry
.get_buffer()
272 sysTypeLabel
= Gtk
.Label("Android Type")
273 sysTypeCombo
= Gtk
.ComboBoxText()
274 sysTypeCombo
.set_entry_text_column(0)
275 for t
in ["VANILLA", "GAPPS"]:
276 sysTypeCombo
.append_text(t
)
277 sysTypeCombo
.set_active(0)
278 grid
.attach(sysTypeLabel
, 0, 2, 1, 1)
279 grid
.attach_next_to(sysTypeCombo
, sysTypeLabel
, Gtk
.PositionType
.RIGHT
, 2, 1)
280 self
.sysType
= sysTypeCombo
282 downloadBtn
= Gtk
.Button("Download")
283 downloadBtn
.connect("clicked", self
.on_download_btn_clicked
)
284 grid
.attach(downloadBtn
, 1,3,1,1)
285 self
.downloadBtn
= downloadBtn
287 doneBtn
= Gtk
.Button("Done")
288 doneBtn
.connect("clicked", lambda x
: self
.destroy())
289 doneBtn
.get_style_context().add_class('suggested-action')
290 grid
.attach_next_to(doneBtn
, downloadBtn
, Gtk
.PositionType
.RIGHT
, 1, 1)
291 self
.doneBtn
= doneBtn
293 outScrolledWindow
= Gtk
.ScrolledWindow()
294 outScrolledWindow
.set_hexpand(True)
295 outScrolledWindow
.set_vexpand(True)
296 outTextView
= Gtk
.TextView()
297 outTextView
.set_property('editable', False)
298 outTextView
.set_property('cursor-visible', False)
299 outScrolledWindow
.add(outTextView
)
300 grid
.attach(outScrolledWindow
, 0, 4, 3, 1)
301 self
.outScrolledWindow
= outScrolledWindow
302 self
.outTextView
= outTextView
303 self
.outBuffer
= outTextView
.get_buffer()
304 self
.outBuffer
.create_mark("end", self
.outBuffer
.get_end_iter(), False)
306 self
.open_channel
= None
308 def scroll_to_bottom(self
):
309 self
.outTextView
.scroll_mark_onscreen(self
.outBuffer
.get_mark("end"))
311 def on_download_btn_clicked(self
, widget
):
312 widget
.set_sensitive(False)
314 self
.outTextView
.show()
315 init_params
= (self
.sysOta
.get_text(), self
.vndOta
.get_text(), self
.sysType
.get_active_text())
316 init_runner
= threading
.Thread(target
=self
.run_init
, args
=init_params
)
317 init_runner
.daemon
= True
320 def run_init(self
, systemOta
, vendorOta
, systemType
):
322 if s
.startswith('\r'):
323 last
= self
.outBuffer
.get_iter_at_line(self
.outBuffer
.get_line_count()-1)
325 self
.outBuffer
.delete(last
, self
.outBuffer
.get_end_iter())
326 self
.outBuffer
.insert(self
.outBuffer
.get_end_iter(), s
)
327 self
.scroll_to_bottom()
329 GLib
.idle_add(draw_sync
, s
)
331 if self
.open_channel
is not None:
332 self
.open_channel
.close()
333 # Wait for other end to reset
336 draw("Waiting for waydroid container service...\n")
339 "system_channel": self
.sysOta
.get_text(),
340 "vendor_channel": self
.vndOta
.get_text(),
341 "system_type": self
.sysType
.get_active_text()
343 bus
.get_object("id.waydro.ContainerService", "/Initializer").Init(params
, dbus_interface
="id.waydro.Initializer")
345 draw("The waydroid container service is not listening\n")
346 GLib
.idle_add(self
.downloadBtn
.set_sensitive
, True)
349 with helpers
.ipc
.open_channel("remote_init_output", "rb") as channel
:
350 self
.open_channel
= channel
351 GLib
.idle_add(self
.downloadBtn
.set_sensitive
, True)
355 data
= channel
.read(1)
369 draw("\nInterrupted\n")
371 if is_initialized(args
):
372 GLib
.idle_add(self
.doneBtn
.show
)
376 GLib
.set_prgname("Waydroid")
377 win
= WaydroidInitWindow()
378 win
.connect("destroy", notify_and_quit
)
381 win
.outTextView
.hide()