From 018fb645c45e11833740dfdb943a9dd437c5e670 Mon Sep 17 00:00:00 2001 From: Alessandro Astone Date: Sun, 6 Jul 2025 14:16:15 +0200 Subject: [PATCH] user_manager: Rework desktop file handling * Update all desktop files on Android startup * Remove non-existing applications on Android startup * Preserve user comments and translations * Preserve user modifications to Categories and Actions * Preserve user modifications to NoDisplay * Default system applications to NoDisplay=true Closes: https://github.com/waydroid/waydroid/pull/339 Closes: https://github.com/waydroid/waydroid/issues/46 --- tools/interfaces/IUserMonitor.py | 4 + tools/services/user_manager.py | 153 +++++++++++++++++++++---------- 2 files changed, 109 insertions(+), 48 deletions(-) diff --git a/tools/interfaces/IUserMonitor.py b/tools/interfaces/IUserMonitor.py index 04f5a3f..e8fd656 100644 --- a/tools/interfaces/IUserMonitor.py +++ b/tools/interfaces/IUserMonitor.py @@ -10,6 +10,10 @@ SERVICE_NAME = "waydroidusermonitor" TRANSACTION_userUnlocked = 1 TRANSACTION_packageStateChanged = 2 +PACKAGE_ADDED = 0; +PACKAGE_REMOVED = 1; +PACKAGE_UPDATED = 2; + def add_service(args, userUnlocked, packageStateChanged): helpers.drivers.loadBinderNodes(args) try: diff --git a/tools/services/user_manager.py b/tools/services/user_manager.py index 9ab546a..64adba8 100644 --- a/tools/services/user_manager.py +++ b/tools/services/user_manager.py @@ -1,5 +1,6 @@ # Copyright 2021 Erfan Abdi # SPDX-License-Identifier: GPL-3.0-or-later +import glob import logging import os import threading @@ -7,6 +8,7 @@ import tools.config import tools.helpers.net from tools.interfaces import IUserMonitor from tools.interfaces import IPlatform +from gi.repository import GLib stopping = False @@ -14,58 +16,113 @@ def start(args, session, unlocked_cb=None): waydroid_data = session["waydroid_data"] apps_dir = session["xdg_data_home"] + "/applications/" - def makeDesktopFile(appInfo): + system_apps = [ + "com.android.calculator2", + "com.android.camera2", + "com.android.contacts", + "com.android.deskclock", + "com.android.documentsui", + "com.android.email", + "com.android.gallery3d", + "com.android.inputmethod.latin", + "com.android.settings", + "com.google.android.gms", + "org.lineageos.eleven", + "org.lineageos.etar", + "org.lineageos.jelly", + "org.lineageos.recorder" + ] + + + def prepend_list(old_list, new_list): + for s in reversed(new_list): + if s not in old_list: + old_list.insert(0, s) + + def glib_key_file_get_string_list(key_file, group, key): + try: + return key_file.get_string_list(group, key) + except: + return [] + + def glib_key_file_prepend_string_list(key_file, group, key, new_list): + old_list = glib_key_file_get_string_list(key_file, group, key) + prepend_list(old_list, new_list) + key_file.set_string_list(group, key, new_list) + + def glib_key_file_has_value(key_file, group, key): + try: + key_file.get_value(group, key) + return True + except: + return False + + + # Creates, deletes, or updates desktop file + def updateDesktopFile(appInfo): if appInfo is None: - return -1 + return showApp = False for cat in appInfo["categories"]: if cat.strip() == "android.intent.category.LAUNCHER": showApp = True if not showApp: - return -1 + try: + os.remove(desktop_file_path) + except: + pass packageName = appInfo["packageName"] desktop_file_path = apps_dir + "/waydroid." + packageName + ".desktop" - if not os.path.exists(desktop_file_path): - with open(desktop_file_path, "w") as desktop_file: - desktop_file.write(f"""\ -[Desktop Entry] -Type=Application -Name={appInfo["name"]} -Exec=waydroid app launch {packageName} -Icon={waydroid_data}/icons/{packageName}.png -Categories=X-WayDroid-App; -X-Purism-FormFactor=Workstation;Mobile; -Actions=app_settings; - -[Desktop Action app_settings] -Name=App Settings -Exec=waydroid app intent android.settings.APPLICATION_DETAILS_SETTINGS package:{packageName} -Icon={waydroid_data}/icons/com.android.settings.png -""") - return 0 - - def makeWaydroidDesktopFile(hide): + desktop_file = GLib.KeyFile() + try: + flags = GLib.KeyFileFlags.KEEP_COMMENTS | GLib.KeyFileFlags.KEEP_TRANSLATIONS + desktop_file.load_from_file(desktop_file_path, flags) + except: + pass + + desktop_file.set_string("Desktop Entry", "Type", "Application") + desktop_file.set_string("Desktop Entry", "Name", appInfo["name"]) + desktop_file.set_string("Desktop Entry", "Exec", f"waydroid app launch {packageName}") + desktop_file.set_string("Desktop Entry", "Icon", f"{waydroid_data}/icons/{packageName}.png") + glib_key_file_prepend_string_list(desktop_file, "Desktop Entry", "Categories", ["X-WayDroid-App"]) + desktop_file.set_string_list("Desktop Entry", "X-Purism-FormFactor", ["Workstation", "Mobile"]) + glib_key_file_prepend_string_list(desktop_file, "Desktop Entry", "Actions", ["app_settings"]) + if packageName in system_apps and not glib_key_file_has_value(desktop_file, "Desktop Entry", "NoDisplay"): + desktop_file.set_boolean("Desktop Entry", "NoDisplay", True) + + desktop_file.set_string("Desktop Action app_settings", "Name", "App Settings") + desktop_file.set_string("Desktop Action app_settings", "Exec", f"waydroid app intent android.settings.APPLICATION_DETAILS_SETTINGS package:{packageName}") + desktop_file.set_string("Desktop Action app_settings", "Icon", f"{waydroid_data}/icons/com.android.settings.png") + + desktop_file.save_to_file(desktop_file_path) + + + def updateWaydroidDesktopFile(hide): desktop_file_path = apps_dir + "/Waydroid.desktop" # If the user has set the desktop file as read-only, we won't replace it if os.path.isfile(desktop_file_path) and not os.access(desktop_file_path, os.W_OK): logging.info(f"Desktop file '{desktop_file_path}' is not writeable, not updating it") - else: - if os.path.isfile(desktop_file_path): - os.remove(desktop_file_path) - with open(desktop_file_path, "w") as desktop_file: - desktop_file.write(f"""\ -[Desktop Entry] -Type=Application -Name=Waydroid -Exec=waydroid show-full-ui -Categories=X-WayDroid-App;Utility; -X-Purism-FormFactor=Workstation;Mobile; -Icon=waydroid -NoDisplay={str(hide).lower()} -""") + return + + desktop_file = GLib.KeyFile() + try: + flags = GLib.KeyFileFlags.KEEP_COMMENTS | GLib.KeyFileFlags.KEEP_TRANSLATIONS + desktop_file.load_from_file(desktop_file_path, flags) + except: + pass + + desktop_file.set_string("Desktop Entry", "Type", "Application") + desktop_file.set_string("Desktop Entry", "Name", "Waydroid") + desktop_file.set_string("Desktop Entry", "Exec", "waydroid show-full-ui") + glib_key_file_prepend_string_list(desktop_file, "Desktop Entry", "Categories", ["X-WayDroid-App", "Utility"]) + desktop_file.set_string_list("Desktop Entry", "X-Purism-FormFactor", ["Workstation", "Mobile"]) + desktop_file.set_string("Desktop Entry", "Icon", "waydroid") + desktop_file.set_boolean("Desktop Entry", "NoDisplay", hide) + + desktop_file.save_to_file(desktop_file_path) def userUnlocked(uid): cfg = tools.config.load(args) @@ -83,27 +140,27 @@ NoDisplay={str(hide).lower()} os.mkdir(apps_dir, 0o700) appsList = platformService.getAppsInfo() for app in appsList: - makeDesktopFile(app) + updateDesktopFile(app) + for existing in glob.iglob(f'{apps_dir}/waydroid.*.desktop'): + if os.path.basename(existing) not in map(lambda appInfo: f"waydroid.{appInfo["packageName"]}.desktop", appsList): + os.remove(existing) multiwin = platformService.getprop("persist.waydroid.multi_windows", "false") - makeWaydroidDesktopFile(multiwin == "true") + updateWaydroidDesktopFile(multiwin == "true") if unlocked_cb: unlocked_cb() def packageStateChanged(mode, packageName, uid): platformService = IPlatform.get_service(args) if platformService: - appInfo = platformService.getAppInfo(packageName) desktop_file_path = apps_dir + "/waydroid." + packageName + ".desktop" - if mode == 0: - # Package added - makeDesktopFile(appInfo) - elif mode == 1: - if os.path.isfile(desktop_file_path): + if mode == IUserMonitor.PACKAGE_REMOVED: + try: os.remove(desktop_file_path) + except: + pass else: - if os.path.isfile(desktop_file_path): - if makeDesktopFile(appInfo) == -1: - os.remove(desktop_file_path) + appInfo = platformService.getAppInfo(packageName) + updateDesktopFile(appInfo) def service_thread(): while not stopping: -- 2.47.3