import locale import gettext import signal import tkinter as tk from tkinter import ttk from pathlib import Path from typing import Optional, NoReturn, Any, Dict import logging import os import sys import shutil import subprocess import stat from network import GiteaUpdater class Locale: APP_NAME = "lxtoolsinstaller" # Locale settings LOCALE_DIR = "./locale/" @staticmethod def setup_translations() -> gettext.gettext: """Initialize translations and set the translation function""" try: locale.bindtextdomain(Locale.APP_NAME, Locale.LOCALE_DIR) gettext.bindtextdomain(Locale.APP_NAME, Locale.LOCALE_DIR) gettext.textdomain(Locale.APP_NAME) except: pass return gettext.gettext # Initialize translations _ = Locale.setup_translations() class Detector: @staticmethod def get_os() -> str: """Detect operating system using ordered list""" try: with open("/etc/os-release", "r") as f: content = f.read().lower() # Check each OS in order (specific first) for keyword, os_name in LXToolsAppConfig.OS_DETECTION: if keyword in content: return os_name return "Unknown System" except FileNotFoundError: return "File not found" @staticmethod def get_host_python_version() -> str: try: result = subprocess.run( ["python3", "--version"], capture_output=True, text=True, check=True ) version_str = result.stdout.strip().replace("Python ", "") return version_str[:4] # example "3.13" except Exception: print("Python not found") return None @staticmethod def get_user_gt_1() -> bool: """This method may be required for the future if the number of users""" path = Path("/home") user_directories = [ entry for entry in path.iterdir() if entry.is_dir() and entry.name != "root" and entry.name != "lost+found" ] # Count the number of user directories numbers = len(user_directories) if not numbers > 1: return True else: return False @staticmethod def get_wget() -> bool: """Check if wget is installed""" result = subprocess.run( ["which", "wget"], capture_output=True, text=True, check=False ) if result.returncode == 0: return True else: return False @staticmethod def get_unzip() -> bool: """Check if wget is installed""" result = subprocess.run( ["which", "unzip"], capture_output=True, text=True, check=False ) if result.returncode == 0: return True else: return False @staticmethod def get_requests() -> bool: """Check if requests is installed""" result = subprocess.run( ["pacman", "-Qs", "python-requests"], capture_output=True, text=True, check=False, ) if result.returncode == 0: return True else: return False @staticmethod def get_polkit() -> bool: """Check if network manager is installed""" os_system = Detector.get_os() deb = ["Debian", "Ubuntu", "Linux Mint", "Pop!_OS"] arch = ["Arch Linux", "Manjaro", "EndeavourOS", "ArcoLinux", "Garuda Linux"] if os_system in deb: result = subprocess.run( ["which pkexec"], capture_output=True, shell=True, text=True, check=False, ) if result.returncode == 0: return True else: print(f"STDERR: {result.stderr}") return False elif os_system in arch: result = subprocess.run( ["pacman", "-Qs", "polkit"], capture_output=True, text=True, check=False, ) if result.returncode == 0: return True else: print(f"STDERR: {result.stderr}") return False elif os_system == "Fedora": result = subprocess.run( ["systemctl --type=service | grep polkit"], capture_output=True, shell=True, text=True, check=False, ) if result.returncode == 0: return True else: print(f"STDERR: {result.stderr}") return False elif os_system == "SUSE Tumbleweed" or os_system == "SUSE Leap": result = subprocess.run( ["zypper search --installed-only | grep pkexec"], capture_output=True, shell=True, text=True, check=False, ) if result.returncode == 0: return True else: print(f"STDERR: {result.stderr}") return False @staticmethod def get_networkmanager() -> bool: """Check if network manager is installed""" os_system = Detector.get_os() deb = ["Debian", "Ubuntu", "Linux Mint", "Pop!_OS"] arch = ["Arch Linux", "Manjaro", "EndeavourOS", "ArcoLinux", "Garuda Linux"] if os_system in deb: result = subprocess.run( ["apt list --installed | grep network-manager"], capture_output=True, shell=True, text=True, check=False, ) if result.returncode == 0: return True else: print(f"STDERR: {result.stderr}") return False elif os_system in arch: result = subprocess.run( ["pacman", "-Qs", "networkmanager"], capture_output=True, text=True, check=False, ) if result.returncode == 0: return True else: print(f"STDERR: {result.stderr}") return False elif os_system == "Fedora": result = subprocess.run( ["which NetworkManager"], capture_output=True, shell=True, text=True, check=True, ) if result.returncode == 0: return True else: print(f"STDERR: {result.stderr}") return False elif os_system == "SUSE Tumbleweed" or os_system == "SUSE Leap": result = subprocess.run( ["systemctl status NetworkManager"], capture_output=True, shell=True, text=True, check=True, ) if result.returncode == 0: return True else: print(f"STDERR: {result.stderr}") return False class Theme: @staticmethod def apply_light_theme(root) -> bool: """Apply light theme""" try: theme_dir = LXToolsAppConfig.THEMES_DIR water_theme_path = os.path.join(theme_dir, "water.tcl") if os.path.exists(water_theme_path): try: root.tk.call("source", water_theme_path) root.tk.call("set_theme", "light") return True except tk.TclError: pass # System theme fallback try: style = ttk.Style() if "clam" in style.theme_names(): style.theme_use("clam") return True except: pass except Exception: pass return False class System: @staticmethod def create_directories(directories) -> None: """Create system directories using pkexec""" for directory in directories: subprocess.run(["pkexec", "mkdir", "-p", directory], check=True) @staticmethod def copy_file(src, dest, make_executable=False) -> None: """Copy file using pkexec""" subprocess.run(["pkexec", "cp", src, dest], check=True) if make_executable: subprocess.run(["pkexec", "chmod", "755", dest], check=True) @staticmethod def copy_directory(src, dest) -> None: """Copy directory using pkexec""" subprocess.run(["pkexec", "cp", "-r", src, dest], check=True) @staticmethod def remove_file(path) -> None: """Remove file using pkexec""" subprocess.run(["pkexec", "rm", "-f", path], check=False) @staticmethod def remove_directory(path) -> None: """Remove directory using pkexec""" subprocess.run(["pkexec", "rm", "-rf", path], check=False) @staticmethod def create_symlink(target, link_name) -> None: """Create symbolic link using pkexec""" subprocess.run(["pkexec", "rm", "-f", link_name], check=False) subprocess.run(["pkexec", "ln", "-sf", target, link_name], check=True) @staticmethod def create_ssl_key(pem_file) -> bool: """Create SSL key using pkexec""" try: subprocess.run( ["pkexec", "openssl", "genrsa", "-out", pem_file, "4096"], check=True ) subprocess.run(["pkexec", "chmod", "600", pem_file], check=True) return True except subprocess.CalledProcessError: return False class Image: def __init__(self): self.images = {} def load_image(self, image_key, fallback_paths=None) -> Optional[tk.PhotoImage]: """Load PNG image using tk.PhotoImage with fallback options""" if image_key in self.images: return self.images[image_key] # Define image paths based on key image_paths = { "app_icon": [ "./lx-icons/48/wg_vpn.png", "/usr/share/icons/lx-icons/48/wg_vpn.png", ], "download_icon": [ "./lx-icons/32/download.png", "/usr/share/icons/lx-icons/32/download.png", ], "download_error_icon": [ "./lx-icons/32/download_error.png", "/usr/share/icons/lx-icons/32/download_error.png", ], "success_icon": [ "./lx-icons/32/download.png", "/usr/share/icons/lx-icons/32/download.png", ], "icon_vpn": [ "./lx-icons/48/wg_vpn.png", "/usr/share/icons/lx-icons/48/wg_vpn.png", ], "icon_log": [ "./lx-icons/48/log.png", "/usr/share/icons/lx-icons/48/log.png", ], "header_image": [ "./lx-icons/32/lxtools_key.png", "/usr/share/icons/lx-icons/32/lxtools_key.png", ], } # Get paths to try paths_to_try = image_paths.get(image_key, []) # Add fallback paths if provided if fallback_paths: paths_to_try.extend(fallback_paths) # Try to load image from paths for path in paths_to_try: try: if os.path.exists(path): photo = tk.PhotoImage(file=path) self.images[image_key] = photo return photo except tk.TclError as e: print(f"{LocaleStrings.MSGP['fail_load_image']}{path}: {e}") continue # Return None if no image found return None class AppManager: def __init__(self): self.projects = LXToolsAppConfig.PROJECTS def get_project_info(self, project_key) -> Optional[dict]: """Get project information by key""" return self.projects.get(project_key) def get_all_projects(self) -> dict: """Get all project configurations""" return self.projects def is_installed(self, project_key) -> bool: detected_os = Detector.get_os() """Check if project is installed with better detection""" if project_key == "wirepy": # Check for wirepy symlink return os.path.exists("/usr/local/bin/wirepy") and os.path.islink( "/usr/local/bin/wirepy" ) elif project_key == "logviewer": # Check for logviewer symlink AND executable file symlink_exists = os.path.exists("/usr/local/bin/logviewer") executable_exists = os.path.exists( f"{LXToolsAppConfig.SHARED_LIBS_DESTINATION[detected_os]}/logviewer.py" ) executable_is_executable = False if executable_exists: try: # Check if file is executable file_stat = os.stat( f"{LXToolsAppConfig.SHARED_LIBS_DESTINATION[detected_os]}/logviewer.py" ) executable_is_executable = bool(file_stat.st_mode & stat.S_IEXEC) except: executable_is_executable = False # LogViewer is installed if symlink exists AND executable file exists AND is executable is_installed = ( symlink_exists and executable_exists and executable_is_executable ) # Debug logging print(LocaleStrings.MSGP["logviewer_check"]) print(f"{LocaleStrings.MSGP['symlink_exist']}{symlink_exists}") print(f"{LocaleStrings.MSGP['executable_exist']}{executable_exists}") print(f"{LocaleStrings.MSGP['is_executable']}{executable_is_executable}") print(f"{LocaleStrings.MSGP['final_result']}{is_installed}") return is_installed return False def get_installed_version(self, project_key) -> str: detected_os = Detector.get_os() """Get installed version from config file""" try: if project_key == "wirepy": config_file = f"{LXToolsAppConfig.SHARED_LIBS_DESTINATION[detected_os]}/wp_app_config.py" elif project_key == "logviewer": config_file = f"{LXToolsAppConfig.SHARED_LIBS_DESTINATION[detected_os]}/logview_app_config.py" else: return "Unknown" if os.path.exists(config_file): with open(config_file, "r") as f: content = f.read() for line in content.split("\n"): if "VERSION" in line and "=" in line: version = line.split("=")[1].strip().strip("\"'") return version return "Unknown" except Exception as e: print(f"{LocaleStrings.MSGP['get_version_error']}{project_key}: {e}") return "Unknown" def get_latest_version(self, project_key) -> str: """Get latest version from API""" project_info = self.get_project_info(project_key) if not project_info: return "Unknown" return GiteaUpdater.get_latest_version_from_api(project_info["api_url"]) def check_other_apps_installed(self, exclude_key) -> bool: """Check if other apps are still installed""" return any( self.is_installed(key) for key in self.projects.keys() if key != exclude_key ) class LxTools: @staticmethod def center_window_cross_platform(window, width, height) -> None: """ Centers a window on the primary monitor in a way that works on both X11 and Wayland Args: window: The tkinter window to center width: Window width height: Window height """ # Calculate the position before showing the window # First attempt: Try to use GDK if available (works on both X11 and Wayland) try: import gi gi.require_version("Gdk", "3.0") from gi.repository import Gdk display = Gdk.Display.get_default() monitor = display.get_primary_monitor() or display.get_monitor(0) geometry = monitor.get_geometry() scale_factor = monitor.get_scale_factor() # Calculate center position on the primary monitor x = geometry.x + (geometry.width - width // scale_factor) // 2 y = geometry.y + (geometry.height - height // scale_factor) // 2 # Set window geometry window.geometry(f"{width}x{height}+{x}+{y}") return except (ImportError, AttributeError): pass # Second attempt: Try xrandr for X11 try: import subprocess output = subprocess.check_output( ["xrandr", "--query"], universal_newlines=True ) # Parse the output to find the primary monitor primary_info = None for line in output.splitlines(): if "primary" in line: parts = line.split() for part in parts: if "x" in part and "+" in part: primary_info = part break break if primary_info: # Parse the geometry: WIDTH x HEIGHT+X+Y geometry = primary_info.split("+") dimensions = geometry[0].split("x") primary_width = int(dimensions[0]) primary_height = int(dimensions[1]) primary_x = int(geometry[1]) primary_y = int(geometry[2]) # Calculate center position on the primary monitor x = primary_x + (primary_width - width) // 2 y = primary_y + (primary_height - height) // 2 # Set window geometry window.geometry(f"{width}x{height}+{x}+{y}") return except (ImportError, IndexError, ValueError): pass # Final fallback: Use standard Tkinter method screen_width = window.winfo_screenwidth() screen_height = window.winfo_screenheight() # Try to make an educated guess for multi-monitor setups # If screen width is much larger than height, assume multiple monitors side by side if ( screen_width > screen_height * 1.8 ): # Heuristic for detecting multiple monitors # Assume the primary monitor is on the left half screen_width = screen_width // 2 x = (screen_width - width) // 2 y = (screen_height - height) // 2 window.geometry(f"{width}x{height}+{x}+{y}") @staticmethod def clean_files(tmp_dir: Path = None, file: Path = None) -> None: """ Deletes temporary files and directories for cleanup when exiting the application. This method safely removes an optional directory defined by `AppConfig.TEMP_DIR` and a single file to free up resources at the end of the program's execution. All operations are performed securely, and errors such as `FileNotFoundError` are ignored if the target files or directories do not exist. :param tmp_dir: (Path, optional): Path to the temporary directory that should be deleted. If `None`, the value of `AppConfig.TEMP_DIR` is used. :param file: (Path, optional): Path to the file that should be deleted. If `None`, no additional file will be deleted. Returns: None: The method does not return any value. """ if tmp_dir is not None: shutil.rmtree(tmp_dir, ignore_errors=True) try: if file is not None: Path.unlink(file) except FileNotFoundError: pass @staticmethod def sigi(file_path: Optional[Path] = None, file: Optional[Path] = None) -> None: """ Function for cleanup after a program interruption :param file: Optional - File to be deleted :param file_path: Optional - Directory to be deleted """ def signal_handler(signum: int, frame: Any) -> NoReturn: """ Determines clear text names for signal numbers and handles signals Args: signum: The signal number frame: The current stack frame Returns: NoReturn since the function either exits the program or continues execution """ signals_to_names_dict: Dict[int, str] = dict( (getattr(signal, n), n) for n in dir(signal) if n.startswith("SIG") and "_" not in n ) signal_name: str = signals_to_names_dict.get( signum, f"Unnamed signal: {signum}" ) # End program for certain signals, report to others only reception if signum in (signal.SIGINT, signal.SIGTERM): exit_code: int = 1 logging.error( f"\nSignal {signal_name} {signum} received. => Aborting with exit code {exit_code}.", exc_info=True, ) LxTools.clean_files(file_path, file) logging.info("Breakdown by user...") sys.exit(exit_code) else: logging.info(f"Signal {signum} received and ignored.") LxTools.clean_files(file_path, file) logging.error("Process unexpectedly ended...") # Register signal handlers for various signals signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) signal.signal(signal.SIGHUP, signal_handler) @staticmethod def remove_lxtools_files() -> None: if getattr(sys, "_MEIPASS", None) is not None: shutil.rmtree("./locale") shutil.rmtree("./TK-Themes") shutil.rmtree("./lx-icons") class LXToolsAppConfig: @staticmethod def extract_data_files() -> None: if getattr(sys, "_MEIPASS", None) is not None: os.makedirs("/tmp/lxtools", exist_ok=True) # Liste der Quellordner (entspricht dem "datas"-Eintrag in lxtools_installer.spec) source_dirs = [ os.path.join(sys._MEIPASS, "locale"), # für locale/... os.path.join(sys._MEIPASS, "TK-Themes"), # für TK-Themes/... os.path.join(sys._MEIPASS, "lx-icons"), # für lx-icons/... ] target_dir = os.path.abspath( os.getcwd() ) # Zielverzeichnis: aktueller Ordner for source_dir in source_dirs: group_name = os.path.basename( source_dir ) # Erhält den Gruppen-Name (z. B. 'locale', 'TK-Themes') for root, dirs, files in os.walk(source_dir): for file in files: src_path = os.path.join(root, file) # Relativer Pfad innerhalb des Quellordners rel_path_from_source_root = os.path.relpath( src_path, source_dir ) # Ziel-Pfad unter dem Gruppen-Ordner im aktuellen Verzeichnis dst_path = os.path.join( target_dir, group_name, rel_path_from_source_root ) os.makedirs(os.path.dirname(dst_path), exist_ok=True) shutil.copy2(src_path, dst_path) # Set the SSL certificate file path by start as appimage os.environ["SSL_CERT_FILE"] = os.path.join( os.path.dirname(os.path.abspath(__file__)), "certs", "cacert.pem" ) VERSION = "1.1.8" WINDOW_WIDTH = 460 WINDOW_HEIGHT = 580 # Working directory WORK_DIR = os.getcwd() ICONS_DIR = os.path.join(WORK_DIR, "lx-icons") THEMES_DIR = os.path.join(WORK_DIR, "TK-Themes") TEMP_DIR = "/tmp/lxtools" # Download URLs WIREPY_URL = "https://git.ilunix.de/punix/Wire-Py/archive/main.zip" SHARED_LIBS_URL = "https://git.ilunix.de/punix/shared_libs/archive/main.zip" # API URLs for version checking WIREPY_API_URL = "https://git.ilunix.de/api/v1/repos/punix/Wire-Py/releases" SHARED_LIBS_API_URL = ( "https://git.ilunix.de/api/v1/repos/punix/shared_libs/releases" ) # GPG required EXPECTED_FINGERPRINT = "743745087C6414E00F1EF84D4CCF06B6CE2A4C7F" KEY_URL_OPENPGP = ( f"https://keys.openpgp.org/vks/v1/by-fingerprint/{EXPECTED_FINGERPRINT}" ) KEY_URL_GITILUNIX = ( "https://git.ilunix.de/punix/lxtools_installer/raw/branch/main/public_key.asc" ) # Project configurations PROJECTS = { "wirepy": { "name": "Wire-Py", "description": _("WireGuard VPN Manager with GUI"), "download_url": WIREPY_URL, "api_url": WIREPY_API_URL, "icon_key": "icon_vpn", "main_executable": "wirepy.py", "symlink_name": "wirepy", "config_file": "wp_app_config.py", "desktop_file": "Wire-Py.desktop", "policy_file": "org.sslcrypt.policy", "requires_ssl": True, }, "logviewer": { "name": "LogViewer", "description": _("System Log Viewer with GUI"), "download_url": SHARED_LIBS_URL, "api_url": SHARED_LIBS_API_URL, "icon_key": "icon_log", "main_executable": "logviewer.py", "symlink_name": "logviewer", "config_file": "logview_app_config.py", "desktop_file": "LogViewer.desktop", "policy_file": None, "requires_ssl": False, }, } # OS Detection List (order matters - specific first, generic last) OS_DETECTION = [ ("mint", "Linux Mint"), ("pop", "Pop!_OS"), ("manjaro", "Manjaro"), ("garuda", "Garuda Linux"), ("endeavouros", "EndeavourOS"), ("fedora", "Fedora"), ("tumbleweed", "SUSE Tumbleweed"), ("leap", "SUSE Leap"), ("arch", "Arch Linux"), ("ubuntu", "Ubuntu"), ("debian", "Debian"), ] # Package manager commands for TKinter installation TKINTER_INSTALL_COMMANDS = { "Ubuntu": "apt install -y python3-tk", "Debian": "apt install -y python3-tk", "Linux Mint": "apt install -y python3-tk", "Pop!_OS": "apt install -y python3-tk", "Fedora": "dnf install -y python3-tkinter", "Arch Linux": "pacman -S --noconfirm tk", "Manjaro": "pacman -S --noconfirm tk", "Garuda Linux": "pacman -S --noconfirm tk", "EndeavourOS": "pacman -S --noconfirm tk", "SUSE Tumbleweed": "zypper install -y python3-tk", "SUSE Leap": "zypper install -y python3-tk", } SHARED_LIBS_DESTINATION = { "Ubuntu": "/usr/lib/python3/dist-packages/shared_libs", "Debian": "/usr/lib/python3/dist-packages/shared_libs", "Linux Mint": "/usr/lib/python3/dist-packages/shared_libs", "Pop!_OS": "/usr/lib/python3/dist-packages/shared_libs", "Fedora": f"/usr/lib64/python{Detector.get_host_python_version()}/site-packages/shared_libs", "Arch Linux": f"/usr/lib64/python{Detector.get_host_python_version()}/site-packages/shared_libs", "Manjaro": f"/usr/lib64/python{Detector.get_host_python_version()}/site-packages/shared_libs", "Garuda Linux": f"/usr/lib64/python{Detector.get_host_python_version()}/site-packages/shared_libs", "EndeavourOS": f"/usr/lib64/python{Detector.get_host_python_version()}/site-packages/shared_libs", "SUSE Tumbleweed": f"/usr/lib64/python{Detector.get_host_python_version()}/site-packages/shared_libs", "SUSE Leap": f"/usr/lib64/python{Detector.get_host_python_version()}/site-packages/shared_libs", } LXToolsAppConfig.extract_data_files() class LocaleStrings: MSGI = { "refresh_and_check": _("Refreshing status and checking versions..."), "start_install": _("Starting installation of "), "install": _("Installing "), "install_success": _(" installation successfully!"), "install_failed": _("Installation failed: "), "install_create": _("Created install script: "), "install_script_failed": _("Installation script failed: "), "install_timeout": _("Installation timed out"), "installed": _("Installed "), } MSGU = { "uninstall": _("Uninstalling "), "uninstall_success": _(" uninstalled successfully!"), "uninstall_failed": _("Uninstallation failed: "), "uninstall_create": _("Created uninstall script: "), "uninstall_script_failed": _("Uninstallation script failed: "), "uninstall_timeout": _("Uninstallation timed out"), } # MSGO = Other messages MSGO = { "unknown_project": _("Unknown project: "), "not_install": _(" is not installed."), "download_from": _("Downloading from "), "extract_files": _("Extracting files..."), "download_failed": _("Download failed: "), "head_string2": _("System: "), "head_string3": _("Linux App Installer"), "ready": _("Ready for installation"), "no_internet": _("No internet connection"), "repo_unavailable": _("Repository unavailable"), "system_check": _("System checking..."), "applications": _("Applications"), "progress": _("Progress"), "refresh2": _("Status refresh completed"), "python_check": _("Python not installed"), "polkit_check": _("Please install Polkit!"), "networkmanager_check": _("Please install Networkmanager!"), "polkit_check_log": _("Polkit check: "), "networkmanager_check_log": _("Networkmanager check: "), } # MSGC = Strings on Cards MSGC = { "checking": _("Checking..."), "version_check": _("Version: Checking..."), "update_on": _("Update on "), "available_lower": _("available"), "up_to_date": _("Up to date"), "latest_unknown": _("Latest unknown"), "could_not_check": _("Could not check latest version"), "check_last_failed": _("Latest: Check failed"), "version_check_failed": _("Version check failed"), "not_installed": _("Not installed"), "available": _("Available "), "available_unknown": _("Available unknown"), "available_ckeck_failed": _("Available: Check failed"), } # MSGL = Strings on Logmessages MSGL = { "selected_app": _("Selected project: "), "log_name": _("Installation Log"), "work_dir": _("Working directory: "), "icons_dir": _("Icons directory: "), "detected_os": _("Detected OS: "), "log_cleared": _("Log cleared"), "working_dir": _("Working directory: "), "user_interuppt": _("\nApplication interrupted by user."), "fatal_error": _("Fatal error: "), "fatal_app_error": _("Fatal Error Application failed to start: "), } # MSGB = Strings on Buttons MSGB = { "clear_log": _("Clear Log"), "install": _("Install/Update"), "uninstall": _("Uninstall"), "refresh": _("Refresh Status"), } # MSGM = String on MessagDialogs MSGM = { "please_select": _("Please select a project to install."), "network_error": _( "No internet connection available.\nPlease check your network connection.", ), "repo_error": _( "Cannot access repository.\nPlease try again later.", ), "has_success_update": _("has been successfully installed/updated."), "please_select_uninstall": _("Please select a project to uninstall."), } # MSGP = Others print strings MSGP = { "tk_install": _("Installing tkinter for "), "command_string": _("Command: "), "tk_success": _("TKinter installation completed successfully!"), "tk_failed": _("TKinter installation failed: "), "tk_timeout": _("TKinter installation timed out"), "tk_install_error": _("Error installing tkinter: "), "tk_command_error": _("No tkinter installation command defined for "), "fail_load_image": _("Failed to load image from "), "logviewer_check": _("LogViewer installation check:"), "symlink_exist": _(" Symlink exists: "), "executable_exist": _(" Executable exists: "), "is_executable": _(" Is executable: "), "final_result": _(" Final result: "), "get_version_error": _("Error getting version for "), } # MSGG = String on AppImageMessagDialogs and Strings MSGA = { "gitea_gpg_error": _("Error verifying signature: "), "not_gpg_found": _("Could not find GPG signature: "), "SHA256_File_not_found": _("SHA256-File not found: "), "SHA256_File_not_found1": _("Would you like to continue?"), "SHA256_hash mismatch": _("SHA256 hash mismatch. File might be corrupted!"), "Failed_retrieving": _("Failed to retrieve version from Gitea API"), "Error_retrieving": _("Error retrieving latest version: "), "gpg_verify_success": _("GPG verification successful. Signature is valid."), "error_gpg_check": _("Error GPG check: "), "error_gpg_download": _("Error downloading or verifying AppImage: "), "error_repo": _("Error accessing Gitea repository: "), "error": _("Error: "), "appimage_renamed": _("The AppImage renamed..."), "appimage_rename_error": _("Error renaming the AppImage: "), "appimage_executable_error": _("Error making the AppImage executable: "), "appimage_executable_success": _("The AppImage has been made executable"), "appimage_not_exist": _("Error: The AppImage file does not exist."), } MSGGPG = { "gpg_missing": _( "Warning: 'gpg' is not installed. Please install it to verify the AppImage signature." ), "url_not_reachable": _("URL not reachable: "), "corrupted_file": _( "No fingerprint found in the key. File might be corrupted or empty." ), "mismatch": _("Fingerprint mismatch: Expected "), "but_got": _("but got: "), "failed_import": _("Failed to import public key: "), "fingerprint_extract": _("GPG fingerprint extraction failed: "), "error_import_key": _("Error importing GPG key from "), "error_updating_trust_level": _("Error updating trust level: "), "error_executing_script": _("Error executing script to update trust level: "), "keyserver_reachable": _( "OpenPGP keyserver is reachable. Proceeding with download." ), "keyserver_unreachable": _( "OpenPGP keyserver unreachable. Skipping this source." ), "all_keys_valid": _( "All keys have valid fingerprints matching the expected value." ), "set_trust_level": _("Trust level 5 successfully applied to key "), "not_all_keys_are_valid": _("Not all keys are valid."), }