Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ jobs:
- name: Install GNOME Platform and SDK
run: |
flatpak remote-add --if-not-exists --user flathub https://flathub.org/repo/flathub.flatpakrepo
flatpak install --user -y flathub org.gnome.Platform//49
flatpak install --user -y flathub org.gnome.Sdk//49
flatpak install --user -y flathub org.gnome.Platform//50
flatpak install --user -y flathub org.gnome.Sdk//50

- name: Build Flatpak
run: |
Expand Down
10 changes: 10 additions & 0 deletions data/io.github.vikdevelop.SaveDesktop.metainfo.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@
</screenshots>

<releases>
<release version="4.1" date="2026-07-05">
<url>https://github.com/vikdevelop/SaveDesktop/releases/tag/4.0</url>
<description>
<ul>
<li>Added support for Niri WM and LXQt DE</li>
<li>Added a status window for showing the progress of Flatpak apps installation after configuration import</li>
<li>Updated the GNOME runtime and 7-Zip to the latest versions</li>
</ul>
</description>
</release>
<release version="4.0" date="2026-02-22">
<url>https://github.com/vikdevelop/SaveDesktop/releases/tag/4.0</url>
<description>
Expand Down
4 changes: 2 additions & 2 deletions io.github.vikdevelop.SaveDesktop.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@
{
"type" : "git",
"url" : "https://github.com/ip7z/7zip.git",
"tag" : "26.01",
"commit" : "8c63d71ff886bda90c86db28466287f977374237"
"tag" : "26.02",
"commit" : "f9d78aff31a5f2521ae7ddbdc97c4a8855808959"
}
],
"x-checker-data" : {
Expand Down
2 changes: 1 addition & 1 deletion meson.build
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
project('savedesktop',
version: '4.0',
version: '4.1',
meson_version: '>= 1.0.0',
default_options: [ 'warning_level=2', 'werror=false', ],
)
Expand Down
56 changes: 56 additions & 0 deletions po/savedesktop.pot
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,62 @@ msgstr ""
msgid "Keep existing Flatpak apps and data"
msgstr ""

#: src/gui/flatpaks_installer_window.py
msgid "Installing your Flatpak apps..."
msgstr ""

#: src/gui/flatpaks_installer_window.py
msgid "You can continue using your computer; everything runs in the background. Once the installation of your Flatpak apps and, if applicable, your user data is complete, a message will appear here and you’ll also see a system notification. You can also show the progress below."
msgstr ""

#: src/gui/flatpaks_installer_window.py
msgid "Installation queue is empty. Nothing new to install."
msgstr ""

#: src/gui/flatpaks_installer_window.py
msgid "Copying the Flatpak's user data to ~/.var/app"
msgstr ""

#: src/gui/flatpaks_installer_window.py
msgid "✔ Copied Flatpak's user data"
msgstr ""

#: src/gui/flatpaks_installer_window.py
msgid "Cleaning up orphaned user data..."
msgstr ""

#: src/gui/flatpaks_installer_window.py
msgid "All useless apps and orphaned data have been removed"
msgstr ""

#: src/gui/flatpaks_installer_window.py
msgid "Installing {count} new apps"
msgstr ""

#: src/gui/flatpaks_installer_window.py
msgid "{app} is already available in the system."
msgstr ""

#: src/gui/flatpaks_installer_window.py
msgid "↓ Installing {app} ({current}/{total})"
msgstr ""

#: src/gui/flatpaks_installer_window.py
msgid "✔ Finished installing {app}"
msgstr ""

#: src/gui/flatpaks_installer_window.py
msgid "✔ All operations have been completed successfully."
msgstr ""

#: src/gui/flatpaks_installer_window.py
msgid "Your apps have been installed!"
msgstr ""

#: src/gui/flatpaks_installer_window.py
msgid "This window will be closed automatically in 2 minutes."
msgstr ""

#: src/gui/templates/shortcuts_window.ui:50
#: src/gui/more_options_dialog.py
msgid "More options"
Expand Down
35 changes: 32 additions & 3 deletions src/core/de_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def create_flatpak_autostart():
"[Desktop Entry]\n"
"Name=SaveDesktop (Flatpak Apps installer)\n"
"Type=Application\n"
f"Exec=python3 {CACHE}/workspace/flatpaks_installer.py\n"
f"Exec=sh -c \"flatpak run io.github.vikdevelop.SaveDesktop --show-flatpaks-installer & python3 -u {CACHE}/workspace/flatpaks_installer.py > {CACHE}/workspace/log.pipe 2>&1\""
)

print("[OK] Created Flatpak autostart")
Expand Down Expand Up @@ -296,8 +296,7 @@ def __init__(self):
print("Saving KDE Plasma configuration...")
os.makedirs("DE/xdg-config", exist_ok=True)
os.makedirs("DE/xdg-data", exist_ok=True)
os.system(f"cp -R {home}/.config/[k]* ./DE/xdg-config/")
os.system(f"cp -R {home}/.local/share/[k]* ./DE/xdg-data/")
self.save_kde_config_data_dirs()
for src, dst in KDE_DIRS_SAVE:
if os.path.isfile(src):
safe_copy(src, os.path.join("DE", dst))
Expand All @@ -316,6 +315,36 @@ def save_dconf(self):
os.system("dconf dump / > ./General/dconf-settings.ini")
print("[OK] Saved dconf")

def save_kde_config_data_dirs(self):
# Target dirs
target_base_dir = "./DE"
mapping = [
(f"{self.home}/.config", Path(target_base_dir) / "xdg-config"),
(f"{self.home}/.local/share", Path(target_base_dir) / "xdg-data"),
]

for src_dir, dst_path in mapping:
# Creating destination dir
dst_path.mkdir(parents=True, exist_ok=True)

# Find folders begining with 'k' or 'K'
for path_str in glob.glob(f"{src_dir}/[kK]*"):
src_path = Path(path_str)

# Ignore kdeconnect
if src_path.name.lower() == "kdeconnect":
continue

target = dst_path / src_path.name

# Copying folders and files
if src_path.is_dir():
if target.exists():
shutil.rmtree(target)
shutil.copytree(src_path, target)
else:
shutil.copy2(src_path, target)

# ------------------------
# Import pipeline
# ------------------------
Expand Down
173 changes: 88 additions & 85 deletions src/core/flatpaks_installer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
import os, subprocess, shutil, json
import os, subprocess, shutil, json, time
from pathlib import Path

CACHE_FLATPAK = f"{Path.home()}/.var/app/io.github.vikdevelop.SaveDesktop/cache/tmp/workspace"
Expand All @@ -24,109 +24,112 @@

if dest_dir:
os.chdir(dest_dir)
# Check Flatpak user preferences
with open(f"flatpak-prefs.json") as fl:
with open("flatpak-prefs.json") as fl:
j = json.load(fl)

copy_data = j["copy-data"]
install_flatpaks = j["install-flatpaks"]
disabled_flatpaks = j["disabled-flatpaks"]
keep_flatpaks = j["keep-flatpaks"]

# Restore Flatpak user data archive
if copy_data:
print("[DEBUG] Copying the Flatpak's user data to ~/.var/app")
if os.path.exists(f"app"):
print("Copying the Flatpak apps' user data to the ~/.var/app directory (backward compatibility mode)")
os.system(f"cp -au ./app/ ~/.var/")

archive_file = "Flatpak_Apps/flatpak-apps-data.tgz" if os.path.exists("Flatpak_Apps/flatpak-apps-data.tgz") else "flatpak-apps-data.tgz"

if os.path.exists(archive_file):
tar_cmd = ["tar", "-xzvf", archive_file, "-C", f"{Path.home()}/.var"]
for d_app in disabled_flatpaks:
tar_cmd.extend([f"--exclude={d_app}", f"--exclude=app/{d_app}"])
subprocess.run(tar_cmd)

# Load Flatpaks from bash scripts
# pre-calculation (math only, no actions)
desired_flatpaks = {}
if install_flatpaks:
print("[DEBUG] Scanning the Bash scripts...")
if os.path.exists("Flatpak_Apps"):
installed_flatpaks_files = ['Flatpak_Apps/installed_flatpaks.sh', 'Flatpak_Apps/installed_user_flatpaks.sh']
else:
installed_flatpaks_files = ['installed_flatpaks.sh', 'installed_user_flatpaks.sh']

desired_flatpaks = {}
for file in installed_flatpaks_files:
files_to_check = ['Flatpak_Apps/installed_flatpaks.sh', 'Flatpak_Apps/installed_user_flatpaks.sh'] if os.path.exists("Flatpak_Apps") else ['installed_flatpaks.sh', 'installed_user_flatpaks.sh']
for file in files_to_check:
if os.path.exists(file):
print(f"[DEBUG] Reading: {file}")
with open(file, 'r') as f:
for line in f:
for line in f.readlines():
if 'flatpak' in line and 'install' in line:
parts = line.split()

app_id = None
for part in parts:
if "." in part and not part.startswith("-"):
app_id = part
break

if not app_id:
continue

if app_id in disabled_flatpaks:
print(f"[SKIP] Disabled app: {app_id}")
continue

method = "--user" if "--user" in parts else "--system"
desired_flatpaks[app_id] = method
print(f"[DEBUG] Added to the installation queue: {app_id} ({method})")

if not desired_flatpaks:
print("[DEBUG] The installation queue is empty. It couldn't find any valid Flatpaks to install.")

# Get currently installed Flatpaks
system_flatpak_dir = '/var/lib/flatpak/app'
user_flatpak_dir = os.path.expanduser('~/.local/share/flatpak/app')
installed_apps = {}
for directory, method in [(system_flatpak_dir, '--system'), (user_flatpak_dir, '--user')]:
if os.path.exists(directory):
for app in os.listdir(directory):
if os.path.isdir(os.path.join(directory, app)):
installed_apps[app] = method

for app, method in desired_flatpaks.items():
if app not in installed_apps:
print(f"[INSTALL] Installing {app} ({method})")
if method == '--user':
os.system("flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo")
subprocess.run(['flatpak', 'install', method, 'flathub', app, '-y'])
else:
print(f"[INFO] {app} is available in the system.")

# Remove Flatpaks, which are not in the list (only if it's allowed by the user)
app_id = next((p for p in parts if "." in p and not p.startswith("-")), None)
if app_id and app_id not in disabled_flatpaks:
desired_flatpaks[app_id] = "--user" if "--user" in parts else "--system"

installed_apps = {}
for directory, method in [('/var/lib/flatpak/app', '--system'), (os.path.expanduser('~/.local/share/flatpak/app'), '--user')]:
if os.path.exists(directory):
for app in os.listdir(directory):
if os.path.isdir(os.path.join(directory, app)):
installed_apps[app] = method

apps_to_install = {app: method for app, method in desired_flatpaks.items() if app not in installed_apps}
apps_to_remove = {app: method for app, method in installed_apps.items() if app not in desired_flatpaks} if not keep_flatpaks else {}

# Calculate exact total steps
total_steps = 0
if copy_data: total_steps += 1
if install_flatpaks: total_steps += len(apps_to_install)
if not keep_flatpaks:
for app, method in installed_apps.items():
if app not in desired_flatpaks:
print(f"[REMOVE] {method.title()} Flatpak: {app}")
total_steps += len(apps_to_remove)
total_steps += 1 # one extra step for orphaned dir cleanup

current_step = 0

def report_progress():
"""Helper to print the hidden progress tag for the GTK UI"""
global current_step
current_step += 1
print(f"[PROGRESS] {current_step}/{total_steps}", flush=True)

if total_steps == 0:
print("There's no need to install any new apps, since they're all available on your system.", flush=True)
else:
# phase 2: execution

# Restore Data
if copy_data:
print("[COPY] Copying the Flatpak's user data to ~/.var/app", flush=True)
if os.path.exists("app"):
os.system("cp -au ./app/ ~/.var/")
archive_file = "Flatpak_Apps/flatpak-apps-data.tgz" if os.path.exists("Flatpak_Apps/flatpak-apps-data.tgz") else "flatpak-apps-data.tgz"
if os.path.exists(archive_file):
tar_cmd = ["tar", "-xzf", archive_file, "-C", f"{Path.home()}/.var"]
for d_app in disabled_flatpaks:
tar_cmd.extend([f"--exclude={d_app}", f"--exclude=app/{d_app}"])
subprocess.run(tar_cmd)
print("✔ Copied Flatpak's user data", flush=True)
report_progress() # sends update to UI

# Install Apps
if install_flatpaks:
for app in desired_flatpaks:
if app in installed_apps:
print(f"[INFO] {app} is already available in the system.", flush=True)

if apps_to_install:
print(f"Installing {len(apps_to_install)} new apps", flush=True)
for i, (app, method) in enumerate(apps_to_install.items(), start=1):
print(f"↓ Installing {app} ({i}/{len(apps_to_install)})", flush=True)
if method == '--user':
os.system("flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo")
subprocess.run(['flatpak', 'install', method, 'flathub', app, '-y'])
print(f"✔ Finished installing {app}", flush=True)
report_progress() # sends update to UI

# Remove Apps and Cleanup
if not keep_flatpaks:
for app, method in apps_to_remove.items():
print(f"[REMOVE] {method.title()} Flatpak: {app}", flush=True)
subprocess.run(['flatpak', 'uninstall', method, app, '--delete-data', '-y'])
report_progress() # <--- SEND UPDATE TO UI

# Remove orphaned ~/.var/app directories
user_var_app = Path.home() / ".var/app"
if user_var_app.exists():
for app_dir in user_var_app.iterdir():
if app_dir.is_dir() and app_dir.name not in desired_flatpaks:
print(f"[REMOVE] Orphaned Flatpak user data: {app_dir.name}")
shutil.rmtree(app_dir)
print("[REMOVE] Cleaning up orphaned user data...", flush=True)
user_var_app = Path.home() / ".var/app"
if user_var_app.exists():
for app_dir in user_var_app.iterdir():
if app_dir.is_dir() and app_dir.name not in desired_flatpaks:
print(f" -> Deleted: {app_dir.name}", flush=True)
shutil.rmtree(app_dir)
print("[OK] All useless apps and orphaned data have been removed", flush=True)
report_progress() # <--- SEND UPDATE TO UI

print("✔ All operations have been completed successfully.", flush=True)

else:
print("Nothing to do.")
print("There's no need to install any new apps, since they're all available on your system.", flush=True)

# Remove the autostart file after finishing the operations
autostart_file = f"{Path.home()}/.config/autostart/io.github.vikdevelop.SaveDesktop.Flatpak.desktop"
if os.path.exists(autostart_file):
os.remove(autostart_file)

# Remove the cache dir after finishing the operations
if os.path.exists(CACHE_FLATPAK):
shutil.rmtree(CACHE_FLATPAK)
2 changes: 2 additions & 0 deletions src/globals.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ def get_app_environment():
"MATE": {"de_name": "MATE", "dirs": [(f"{home}/.config/caja", "caja")]},
"Deepin": {"de_name": "Deepin", "dirs": [(f"{home}/.config/deepin", "deepin"), (f"{home}/.local/share/deepin", "deepin-data")]},
"Hyprland": {"de_name": "Hyprland", "dirs": [(f"{home}/.config/hypr", "hypr")]},
"LXQt": {"de_name": "LXQt", "dirs": [(f"{home}/.config/lxqt", "lxqt")]},
"niri": {"de_name": "Niri", "dirs": [(f"{home}/.config/niri", "niri")]},
"KDE": {"de_name": "KDE Plasma"},
}

Expand Down
Loading
Loading