diff --git a/usr/lib/webapp-manager/common.py b/usr/lib/webapp-manager/common.py index e9ec1aa..11264e4 100644 --- a/usr/lib/webapp-manager/common.py +++ b/usr/lib/webapp-manager/common.py @@ -17,9 +17,12 @@ import threading import traceback from typing import Optional +import tarfile +import time +from pathlib import Path # 2. Related third party imports. -from gi.repository import GObject +from gi.repository import GObject, GLib import PIL.Image import requests # Note: BeautifulSoup is an optional import supporting another way of getting a website's favicons. @@ -159,7 +162,7 @@ def get_webapps(self): for filename in os.listdir(APPS_DIR): if filename.lower().startswith("webapp-") and filename.endswith(".desktop"): path = os.path.join(APPS_DIR, filename) - codename = filename.replace("webapp-", "").replace("WebApp-", "").replace(".desktop", "") + codename = get_codename(path) if not os.path.isdir(path): try: webapp = WebAppLauncher(path, codename) @@ -307,8 +310,8 @@ def create_webapp(self, name, url, icon, category, browser, custom_parameters, i falkon_orig_prof_dir = os.path.join(os.path.expanduser("~/.config/falkon/profiles"), codename) os.symlink(falkon_profile_path, falkon_orig_prof_dir) - - def get_exec_string(self, browser, codename, custom_parameters, icon, isolate_profile, navbar, privatewindow, url): + @staticmethod + def get_exec_string( browser, codename, custom_parameters, icon, isolate_profile, navbar, privatewindow, url): if browser.browser_type in [BROWSER_TYPE_FIREFOX, BROWSER_TYPE_FIREFOX_FLATPAK, BROWSER_TYPE_FIREFOX_SNAP, BROWSER_TYPE_ZEN_FLATPAK]: # Firefox based if browser.browser_type == BROWSER_TYPE_FIREFOX: @@ -551,5 +554,124 @@ def download_favicon(url): images = sorted(images, key = lambda x: x[1].height, reverse=True) return images +@_async +def export_webapps(callback): + # The background export process + try: + result = "ok" + filename = "web-apps-" + time.strftime(f"%Y-%m-%d", time.localtime()) + suffix = len(list(Path.home().glob(f"*{filename}*"))) + suffix = f"({suffix})".replace("(0)", "") + export_path = str(Path(f"~/{filename}{suffix}.tar.gz").expanduser()) + + desktop_files = prepare_export() + # Write the .tar.gz file + with tarfile.open(export_path, "w:gz") as tar: + for desktop_file in desktop_files: + tar.add(desktop_file["full_path"], arcname=desktop_file["arcname"]) + tar.add(ICONS_DIR, "ice/icons/") + except Exception as e: + print(e) + result = "error" + + GLib.idle_add(callback, result, "export", export_path) + +@_async +def import_webapps(callback, path): + # The background import process + try: + result = "ok" + with tarfile.open(path, "r:gz") as tar: + files = tar.getnames() + base_dir = os.path.dirname(ICE_DIR) + for file in files: + try: + if not os.path.exists(os.path.join(base_dir, file)): # Skip existing files. + tar.extract(file, base_dir) + if file.startswith("applications/"): + # Update the .desktop file. Check if the browser is installed and rewrite the paths + path = os.path.join(base_dir, file) + if update_imported_desktop(path) == "error": + result = "error" + except Exception as e: # Keep the import process going even if there are problems extracting a file + print(e) + result = "error" + except Exception as e: + print(e) + result = "error" + + GLib.idle_add(callback, result, "import") + + +def prepare_export(): + # Search all web apps and desktop files. + files = [] + supported_browsers = WebAppManager.get_supported_browsers() + for filename in os.listdir(APPS_DIR): + if filename.lower().startswith("webapp-") and filename.endswith(".desktop"): + full_path = os.path.join(APPS_DIR, filename) + arcname = os.path.relpath(full_path, os.path.dirname(APPS_DIR)) + files.append({"full_path":full_path, "arcname":arcname}) + try: + # In older versions, some custom icons will not be automatically stored in the icons directory + webapp = WebAppLauncher(full_path, get_codename(full_path)) + icon = webapp.icon + if "/" in icon and not ICONS_DIR in icon: + filename = "".join(filter(str.isalpha, webapp.name)) + os.path.splitext(icon)[1] + new_path = os.path.join(ICONS_DIR, filename) + shutil.copyfile(icon, new_path) + icon = new_path + browser = next((browser for browser in supported_browsers if browser.name == webapp.web_browser), None) + WebAppManager.edit_webapp(WebAppManager,full_path, webapp.name, browser, webapp.url, icon, webapp.category, + webapp.custom_parameters, webapp.codename, webapp.isolate_profile, webapp.navbar, webapp.privatewindow) + except: + # Skip custom icon + pass + return files + + +def get_codename(path): + filename = os.path.basename(path) + codename = filename.replace(".desktop", "").replace("WebApp-", "").replace("webapp-", "") + return codename + +def update_imported_desktop(path): + try: + webapp = WebAppLauncher(path, get_codename(path)) + if "/" in webapp.icon: + # update icon path, important when the username differs. + iconpath = os.path.join(ICONS_DIR, os.path.basename(webapp.icon)) + else: + iconpath = webapp.icon + + # Check if the browser is installed + browsers = WebAppManager.get_supported_browsers() + configured_browser = next((browser for browser in browsers if browser.name == webapp.web_browser), None) + if os.path.exists(configured_browser.test_path) == False: + # If the browser is not installed, search another browser. + # 1. Sort browsers by same browser type + # 2. Sort the browsers by similarity of the name of the missing browser + similar_browsers = browsers + if configured_browser != None: + browser_type = configured_browser.browser_type + browser_name = configured_browser.name.split(" ")[0].lower() + else: + # Could not found the browser in the supported browser list. Search for an alternative. + browser_type = "" + browser_name = webapp.web_browser + similar_browsers.sort(key=lambda browser: (browser.browser_type == browser_type, browser_name not in browser.name.lower())) + configured_browser = None + for browser in similar_browsers: + if os.path.exists(browser.test_path): + configured_browser = browser + break + print(webapp.web_browser, "-Browser not installed") + # Apply the browser changes, the new icon path and create a profile if there is no existing one + WebAppManager.edit_webapp(WebAppManager, path, webapp.name, configured_browser, webapp.url, iconpath, webapp.category, + webapp.custom_parameters, webapp.codename, webapp.isolate_profile, webapp.navbar, webapp.privatewindow) + return "ok" + except: + return "error" + if __name__ == "__main__": download_favicon(sys.argv[1]) diff --git a/usr/lib/webapp-manager/webapp-manager.py b/usr/lib/webapp-manager/webapp-manager.py index dd77cae..ab292d6 100755 --- a/usr/lib/webapp-manager/webapp-manager.py +++ b/usr/lib/webapp-manager/webapp-manager.py @@ -21,7 +21,8 @@ from gi.repository import Gtk, Gdk, Gio, XApp, GdkPixbuf # 3. Local application/library specific imports. -from common import _async, idle, WebAppManager, download_favicon, ICONS_DIR, BROWSER_TYPE_FIREFOX, BROWSER_TYPE_FIREFOX_FLATPAK, BROWSER_TYPE_ZEN_FLATPAK, BROWSER_TYPE_FIREFOX_SNAP +from common import BROWSER_TYPE_FIREFOX, BROWSER_TYPE_FIREFOX_FLATPAK, BROWSER_TYPE_ZEN_FLATPAK, BROWSER_TYPE_FIREFOX_SNAP, ICONS_DIR +from common import _async, idle, WebAppManager, download_favicon, export_webapps, import_webapps setproctitle.setproctitle("webapp-manager") @@ -124,6 +125,16 @@ def __init__(self, application): self.window.add_accel_group(accel_group) menu = self.builder.get_object("main_menu") item = Gtk.ImageMenuItem() + item.set_image(Gtk.Image.new_from_icon_name("document-send-symbolic", Gtk.IconSize.MENU)) + item.set_label(_("Export")) + item.connect("activate", lambda widget: export_webapps(self.show_result)) + menu.append(item) + item = Gtk.ImageMenuItem() + item.set_image(Gtk.Image.new_from_icon_name("document-open-symbolic", Gtk.IconSize.MENU)) + item.set_label(_("Import...")) + item.connect("activate", self.import_select_location) + menu.append(item) + item = Gtk.ImageMenuItem() item.set_image( Gtk.Image.new_from_icon_name("preferences-desktop-keyboard-shortcuts-symbolic", Gtk.IconSize.MENU)) item.set_label(_("Keyboard Shortcuts")) @@ -315,9 +326,9 @@ def on_ok_button(self, widget): privatewindow = self.privatewindow_switch.get_active() icon = self.icon_chooser.get_icon() custom_parameters = self.customparameters_entry.get_text() - if "/tmp" in icon: - # If the icon path is in /tmp, move it. - filename = "".join(filter(str.isalpha, name)) + ".png" + # Always copy custom icons in the icon directory (important when exporting web-apps) + if "/" in icon and not ICONS_DIR in icon: + filename = "".join(filter(str.isalpha, name)) + os.path.splitext(icon)[1] new_path = os.path.join(ICONS_DIR, filename) shutil.copyfile(icon, new_path) icon = new_path @@ -537,6 +548,47 @@ def load_webapps(self): self.stack.set_visible_child_name("main_page") self.headerbar.set_subtitle(_("Run websites as if they were apps")) + def import_select_location(self, widget): + # Open the file chooser dialog + buttons = (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK) + title = _("Import WebApps - Select the archive") + dialog = Gtk.FileChooserDialog(title, self.window, Gtk.FileChooserAction.OPEN, buttons) + filter = Gtk.FileFilter() + filter.set_name(".tar.gz") + filter.add_pattern("*.tar.gz") + dialog.add_filter(filter) + response = dialog.run() + if response == Gtk.ResponseType.OK: + path = dialog.get_filename() + if path != "": + import_webapps(self.show_result, path) + dialog.destroy() + + def show_result(self, result, task, path=""): + # Displays a success or failure message when the import / export process is complete. + self.load_webapps() + if result == "ok" and task == "export": + # This dialog box gives users the option to open the containing directory. + title = _("Export completed!") + button_text = _("Open Containing Folder") + dialog = Gtk.Dialog(title, self.window, None, (button_text, 10, Gtk.STOCK_OK, Gtk.ResponseType.OK)) + dialog.get_content_area().add(Gtk.Label(label=_("WebApps have been exported successfully.")+f"\n\n{path}\n")) + dialog.show_all() + result = dialog.run() + if result == 10: + # Open Containing Folder + os.system("xdg-open " + os.path.dirname(path)) + else: + if result == "ok" and task == "import": + message = _("Import completed") + elif result != "ok" and task == "import": + message = _("Import not completed due to an error") + elif result != "ok" and task == "export": + message = _("Export failed") + + dialog = Gtk.MessageDialog(text=message, message_type=Gtk.MessageType.INFO, buttons=Gtk.ButtonsType.OK) + dialog.run() + dialog.destroy() if __name__ == "__main__": application = MyApplication("org.x.webapp-manager", Gio.ApplicationFlags.FLAGS_NONE) diff --git a/webapp-manager.pot b/webapp-manager.pot index 679fb64..b16a1ad 100644 --- a/webapp-manager.pot +++ b/webapp-manager.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-06-21 10:58+0200\n" +"POT-Creation-Date: 2025-07-25 15:09+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,208 +17,214 @@ msgstr "" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" -#: usr/lib/webapp-manager/common.py:208 usr/lib/webapp-manager/common.py:319 +#: usr/lib/webapp-manager/common.py:111 usr/lib/webapp-manager/common.py:271 +#: usr/lib/webapp-manager/common.py:442 +#: usr/share/webapp-manager/webapp-manager.ui.h:22 msgid "Web App" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:73 -#: usr/lib/webapp-manager/webapp-manager.py:231 -#: usr/lib/webapp-manager/webapp-manager.py:238 generate_desktop_files:25 -#: generate_desktop_files:39 usr/share/webapp-manager/webapp-manager.ui.h:1 +#: usr/lib/webapp-manager/webapp-manager.py:75 +#: usr/lib/webapp-manager/webapp-manager.py:232 +#: usr/lib/webapp-manager/webapp-manager.py:239 generate_desktop_files:25 +#: generate_desktop_files:39 usr/share/webapp-manager/webapp-manager.ui.h:23 #: usr/share/webapp-manager/shortcuts.ui.h:1 msgid "Web Apps" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:129 +#: usr/lib/webapp-manager/webapp-manager.py:130 msgid "Keyboard Shortcuts" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:136 -#: usr/lib/webapp-manager/webapp-manager.py:237 +#: usr/lib/webapp-manager/webapp-manager.py:137 +#: usr/lib/webapp-manager/webapp-manager.py:238 #: usr/share/webapp-manager/shortcuts.ui.h:8 msgid "About" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:141 +#: usr/lib/webapp-manager/webapp-manager.py:142 #: usr/share/webapp-manager/shortcuts.ui.h:10 msgid "Quit" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:156 +#: usr/lib/webapp-manager/webapp-manager.py:157 msgid "Icon" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:160 +#: usr/lib/webapp-manager/webapp-manager.py:161 msgid "Name" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:165 +#: usr/lib/webapp-manager/webapp-manager.py:166 msgid "Browser" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:179 -msgid "Internet" -msgstr "" - #: usr/lib/webapp-manager/webapp-manager.py:180 generate_desktop_files:48 msgid "Web" msgstr "" #: usr/lib/webapp-manager/webapp-manager.py:181 -msgid "Accessories" +msgid "Internet" msgstr "" #: usr/lib/webapp-manager/webapp-manager.py:182 -msgid "Games" +msgid "Accessories" msgstr "" #: usr/lib/webapp-manager/webapp-manager.py:183 -msgid "Graphics" +msgid "Games" msgstr "" #: usr/lib/webapp-manager/webapp-manager.py:184 -msgid "Office" +msgid "Graphics" msgstr "" #: usr/lib/webapp-manager/webapp-manager.py:185 -msgid "Sound & Video" +msgid "Office" msgstr "" #: usr/lib/webapp-manager/webapp-manager.py:186 -msgid "Programming" +msgid "Sound & Video" msgstr "" #: usr/lib/webapp-manager/webapp-manager.py:187 +msgid "Programming" +msgstr "" + +#: usr/lib/webapp-manager/webapp-manager.py:188 msgid "Education" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:209 +#: usr/lib/webapp-manager/webapp-manager.py:210 msgid "No supported browsers were detected." msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:239 -#: usr/lib/webapp-manager/webapp-manager.py:528 generate_desktop_files:25 +#: usr/lib/webapp-manager/webapp-manager.py:240 +#: usr/lib/webapp-manager/webapp-manager.py:542 generate_desktop_files:25 #: generate_desktop_files:39 msgid "Run websites as if they were apps" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:292 +#: usr/lib/webapp-manager/webapp-manager.py:291 #, python-format msgid "Delete '%s'" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:293 +#: usr/lib/webapp-manager/webapp-manager.py:292 #, python-format msgid "Are you sure you want to delete '%s'?" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:294 +#: usr/lib/webapp-manager/webapp-manager.py:293 msgid "This Web App will be permanently lost." msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:347 -#: usr/lib/webapp-manager/webapp-manager.py:379 -#: usr/lib/webapp-manager/webapp-manager.py:435 +#: usr/lib/webapp-manager/webapp-manager.py:349 +#: usr/lib/webapp-manager/webapp-manager.py:392 +#: usr/lib/webapp-manager/webapp-manager.py:448 msgid "Add a New Web App" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:369 +#: usr/lib/webapp-manager/webapp-manager.py:382 msgid "Edit Web App" msgstr "" -#: usr/lib/webapp-manager/webapp-manager.py:411 +#: usr/lib/webapp-manager/webapp-manager.py:424 msgid "Choose an icon" msgstr "" -#: usr/share/webapp-manager/webapp-manager.ui.h:2 -msgid "Manage your Web Apps" -msgstr "" - -#: usr/share/webapp-manager/webapp-manager.ui.h:3 +#: usr/share/webapp-manager/webapp-manager.ui.h:1 #: usr/share/webapp-manager/shortcuts.ui.h:2 msgid "Add" msgstr "" -#: usr/share/webapp-manager/webapp-manager.ui.h:4 +#: usr/share/webapp-manager/webapp-manager.ui.h:2 #: usr/share/webapp-manager/shortcuts.ui.h:4 msgid "Remove" msgstr "" -#: usr/share/webapp-manager/webapp-manager.ui.h:5 +#: usr/share/webapp-manager/webapp-manager.ui.h:3 #: usr/share/webapp-manager/shortcuts.ui.h:3 msgid "Edit" msgstr "" -#: usr/share/webapp-manager/webapp-manager.ui.h:6 +#: usr/share/webapp-manager/webapp-manager.ui.h:4 #: usr/share/webapp-manager/shortcuts.ui.h:5 msgid "Launch" msgstr "" -#: usr/share/webapp-manager/webapp-manager.ui.h:7 +#: usr/share/webapp-manager/webapp-manager.ui.h:5 msgid "Name:" msgstr "" -#: usr/share/webapp-manager/webapp-manager.ui.h:8 +#: usr/share/webapp-manager/webapp-manager.ui.h:6 msgid "Address:" msgstr "" -#: usr/share/webapp-manager/webapp-manager.ui.h:9 +#: usr/share/webapp-manager/webapp-manager.ui.h:7 msgid "Icon:" msgstr "" -#: usr/share/webapp-manager/webapp-manager.ui.h:10 +#: usr/share/webapp-manager/webapp-manager.ui.h:8 msgid "Website name" msgstr "" -#: usr/share/webapp-manager/webapp-manager.ui.h:11 +#: usr/share/webapp-manager/webapp-manager.ui.h:9 msgid "https://www.website.com" msgstr "" -#: usr/share/webapp-manager/webapp-manager.ui.h:12 +#: usr/share/webapp-manager/webapp-manager.ui.h:10 msgid "Cancel" msgstr "" -#: usr/share/webapp-manager/webapp-manager.ui.h:13 +#: usr/share/webapp-manager/webapp-manager.ui.h:11 msgid "OK" msgstr "" -#: usr/share/webapp-manager/webapp-manager.ui.h:14 +#: usr/share/webapp-manager/webapp-manager.ui.h:12 msgid "Find icons online" msgstr "" -#: usr/share/webapp-manager/webapp-manager.ui.h:15 +#: usr/share/webapp-manager/webapp-manager.ui.h:13 msgid "Category:" msgstr "" -#: usr/share/webapp-manager/webapp-manager.ui.h:16 +#: usr/share/webapp-manager/webapp-manager.ui.h:14 msgid "Browser:" msgstr "" -#: usr/share/webapp-manager/webapp-manager.ui.h:17 +#: usr/share/webapp-manager/webapp-manager.ui.h:15 msgid "" "If this option is enabled the website will run with its own browser profile." msgstr "" -#: usr/share/webapp-manager/webapp-manager.ui.h:18 +#: usr/share/webapp-manager/webapp-manager.ui.h:16 msgid "Isolated profile:" msgstr "" -#: usr/share/webapp-manager/webapp-manager.ui.h:19 +#: usr/share/webapp-manager/webapp-manager.ui.h:17 msgid "Navigation bar:" msgstr "" -#: usr/share/webapp-manager/webapp-manager.ui.h:20 +#: usr/share/webapp-manager/webapp-manager.ui.h:18 msgid "Private/Incognito Window:" msgstr "" -#: usr/share/webapp-manager/webapp-manager.ui.h:21 +#: usr/share/webapp-manager/webapp-manager.ui.h:19 msgid "Custom parameters:" msgstr "" -#: usr/share/webapp-manager/webapp-manager.ui.h:22 +#: usr/share/webapp-manager/webapp-manager.ui.h:20 msgid "Custom browser parameters" msgstr "" +#: usr/share/webapp-manager/webapp-manager.ui.h:21 +msgid "Description:" +msgstr "" + +#: usr/share/webapp-manager/webapp-manager.ui.h:24 +msgid "Manage your Web Apps" +msgstr "" + #: usr/share/webapp-manager/shortcuts.ui.h:6 msgid "Other Shortcuts" msgstr ""