]> glassweightruler.freedombox.rocks Git - waydroid.git/blob - tools/services/user_manager.py
user_manager: Rework desktop file handling
[waydroid.git] / tools / services / user_manager.py
1 # Copyright 2021 Erfan Abdi
2 # SPDX-License-Identifier: GPL-3.0-or-later
3 import glob
4 import logging
5 import os
6 import threading
7 import tools.config
8 import tools.helpers.net
9 from tools.interfaces import IUserMonitor
10 from tools.interfaces import IPlatform
11 from gi.repository import GLib
12
13 stopping = False
14
15 def start(args, session, unlocked_cb=None):
16 waydroid_data = session["waydroid_data"]
17 apps_dir = session["xdg_data_home"] + "/applications/"
18
19 system_apps = [
20 "com.android.calculator2",
21 "com.android.camera2",
22 "com.android.contacts",
23 "com.android.deskclock",
24 "com.android.documentsui",
25 "com.android.email",
26 "com.android.gallery3d",
27 "com.android.inputmethod.latin",
28 "com.android.settings",
29 "com.google.android.gms",
30 "org.lineageos.eleven",
31 "org.lineageos.etar",
32 "org.lineageos.jelly",
33 "org.lineageos.recorder"
34 ]
35
36
37 def prepend_list(old_list, new_list):
38 for s in reversed(new_list):
39 if s not in old_list:
40 old_list.insert(0, s)
41
42 def glib_key_file_get_string_list(key_file, group, key):
43 try:
44 return key_file.get_string_list(group, key)
45 except:
46 return []
47
48 def glib_key_file_prepend_string_list(key_file, group, key, new_list):
49 old_list = glib_key_file_get_string_list(key_file, group, key)
50 prepend_list(old_list, new_list)
51 key_file.set_string_list(group, key, new_list)
52
53 def glib_key_file_has_value(key_file, group, key):
54 try:
55 key_file.get_value(group, key)
56 return True
57 except:
58 return False
59
60
61 # Creates, deletes, or updates desktop file
62 def updateDesktopFile(appInfo):
63 if appInfo is None:
64 return
65
66 showApp = False
67 for cat in appInfo["categories"]:
68 if cat.strip() == "android.intent.category.LAUNCHER":
69 showApp = True
70 if not showApp:
71 try:
72 os.remove(desktop_file_path)
73 except:
74 pass
75
76 packageName = appInfo["packageName"]
77
78 desktop_file_path = apps_dir + "/waydroid." + packageName + ".desktop"
79 desktop_file = GLib.KeyFile()
80 try:
81 flags = GLib.KeyFileFlags.KEEP_COMMENTS | GLib.KeyFileFlags.KEEP_TRANSLATIONS
82 desktop_file.load_from_file(desktop_file_path, flags)
83 except:
84 pass
85
86 desktop_file.set_string("Desktop Entry", "Type", "Application")
87 desktop_file.set_string("Desktop Entry", "Name", appInfo["name"])
88 desktop_file.set_string("Desktop Entry", "Exec", f"waydroid app launch {packageName}")
89 desktop_file.set_string("Desktop Entry", "Icon", f"{waydroid_data}/icons/{packageName}.png")
90 glib_key_file_prepend_string_list(desktop_file, "Desktop Entry", "Categories", ["X-WayDroid-App"])
91 desktop_file.set_string_list("Desktop Entry", "X-Purism-FormFactor", ["Workstation", "Mobile"])
92 glib_key_file_prepend_string_list(desktop_file, "Desktop Entry", "Actions", ["app_settings"])
93 if packageName in system_apps and not glib_key_file_has_value(desktop_file, "Desktop Entry", "NoDisplay"):
94 desktop_file.set_boolean("Desktop Entry", "NoDisplay", True)
95
96 desktop_file.set_string("Desktop Action app_settings", "Name", "App Settings")
97 desktop_file.set_string("Desktop Action app_settings", "Exec", f"waydroid app intent android.settings.APPLICATION_DETAILS_SETTINGS package:{packageName}")
98 desktop_file.set_string("Desktop Action app_settings", "Icon", f"{waydroid_data}/icons/com.android.settings.png")
99
100 desktop_file.save_to_file(desktop_file_path)
101
102
103 def updateWaydroidDesktopFile(hide):
104 desktop_file_path = apps_dir + "/Waydroid.desktop"
105 # If the user has set the desktop file as read-only, we won't replace it
106 if os.path.isfile(desktop_file_path) and not os.access(desktop_file_path, os.W_OK):
107 logging.info(f"Desktop file '{desktop_file_path}' is not writeable, not updating it")
108 return
109
110 desktop_file = GLib.KeyFile()
111 try:
112 flags = GLib.KeyFileFlags.KEEP_COMMENTS | GLib.KeyFileFlags.KEEP_TRANSLATIONS
113 desktop_file.load_from_file(desktop_file_path, flags)
114 except:
115 pass
116
117 desktop_file.set_string("Desktop Entry", "Type", "Application")
118 desktop_file.set_string("Desktop Entry", "Name", "Waydroid")
119 desktop_file.set_string("Desktop Entry", "Exec", "waydroid show-full-ui")
120 glib_key_file_prepend_string_list(desktop_file, "Desktop Entry", "Categories", ["X-WayDroid-App", "Utility"])
121 desktop_file.set_string_list("Desktop Entry", "X-Purism-FormFactor", ["Workstation", "Mobile"])
122 desktop_file.set_string("Desktop Entry", "Icon", "waydroid")
123 desktop_file.set_boolean("Desktop Entry", "NoDisplay", hide)
124
125 desktop_file.save_to_file(desktop_file_path)
126
127 def userUnlocked(uid):
128 cfg = tools.config.load(args)
129 logging.info("Android with user {} is ready".format(uid))
130
131 if cfg["waydroid"]["auto_adb"] == "True":
132 try:
133 tools.helpers.net.adb_connect(args)
134 except:
135 pass
136
137 platformService = IPlatform.get_service(args)
138 if platformService:
139 if not os.path.exists(apps_dir):
140 os.mkdir(apps_dir, 0o700)
141 appsList = platformService.getAppsInfo()
142 for app in appsList:
143 updateDesktopFile(app)
144 for existing in glob.iglob(f'{apps_dir}/waydroid.*.desktop'):
145 if os.path.basename(existing) not in map(lambda appInfo: f"waydroid.{appInfo["packageName"]}.desktop", appsList):
146 os.remove(existing)
147 multiwin = platformService.getprop("persist.waydroid.multi_windows", "false")
148 updateWaydroidDesktopFile(multiwin == "true")
149 if unlocked_cb:
150 unlocked_cb()
151
152 def packageStateChanged(mode, packageName, uid):
153 platformService = IPlatform.get_service(args)
154 if platformService:
155 desktop_file_path = apps_dir + "/waydroid." + packageName + ".desktop"
156 if mode == IUserMonitor.PACKAGE_REMOVED:
157 try:
158 os.remove(desktop_file_path)
159 except:
160 pass
161 else:
162 appInfo = platformService.getAppInfo(packageName)
163 updateDesktopFile(appInfo)
164
165 def service_thread():
166 while not stopping:
167 IUserMonitor.add_service(args, userUnlocked, packageStateChanged)
168
169 global stopping
170 stopping = False
171 args.user_manager = threading.Thread(target=service_thread)
172 args.user_manager.start()
173
174 def stop(args):
175 global stopping
176 stopping = True
177 try:
178 if args.userMonitorLoop:
179 args.userMonitorLoop.quit()
180 except AttributeError:
181 logging.debug("UserMonitor service is not even started")