#!/usr/bin/python3 ################### Teil 1 - Imports und Basis-Konfiguration ################### import gettext import locale import tkinter as tk from tkinter import messagebox, ttk import shutil import os import socket import subprocess import tempfile import urllib.request import zipfile import json import stat from pathlib import Path from datetime import datetime ################### Teil 2 - LXTools App Configuration ################### class LXToolsAppConfig: VERSION = "1.1.3" APP_NAME = "LX Tools Installer" WINDOW_WIDTH = 500 WINDOW_HEIGHT = 720 # Working directory WORK_DIR = os.getcwd() ICONS_DIR = os.path.join(WORK_DIR, "lx-icons") THEMES_DIR = os.path.join(WORK_DIR, "TK-Themes") # Locale settings LOCALE_DIR = Path("/usr/share/locale/") # 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" ) # 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"), ("suse", "openSUSE"), ("arch", "Arch Linux"), ("ubuntu", "Ubuntu"), ("debian", "Debian"), ] # Package manager commands for TKinter installation TKINTER_INSTALL_COMMANDS = { "Ubuntu": ["apt", "update", "&&", "apt", "install", "-y", "python3-tk"], "Debian": ["apt", "update", "&&", "apt", "install", "-y", "python3-tk"], "Linux Mint": ["apt", "update", "&&", "apt", "install", "-y", "python3-tk"], "Pop!_OS": ["apt", "update", "&&", "apt", "install", "-y", "python3-tk"], "Fedora": ["dnf", "install", "-y", "tkinter"], "Arch Linux": ["pacman", "-S", "--noconfirm", "tk"], "Manjaro": ["pacman", "-S", "--noconfirm", "tk"], "Garuda Linux": ["pacman", "-S", "--noconfirm", "tk"], "EndeavourOS": ["pacman", "-S", "--noconfirm", "tk"], "openSUSE": ["zypper", "install", "-y", "python3-tk"], "SUSE Tumbleweed": ["zypper", "install", "-y", "python3-tk"], "SUSE Leap": ["zypper", "install", "-y", "python3-tk"], } @staticmethod def setup_translations(): """Initialize translations and set the translation function""" try: locale.bindtextdomain( LXToolsAppConfig.APP_NAME, LXToolsAppConfig.LOCALE_DIR ) gettext.bindtextdomain( LXToolsAppConfig.APP_NAME, LXToolsAppConfig.LOCALE_DIR ) gettext.textdomain(LXToolsAppConfig.APP_NAME) except: pass return gettext.gettext # Initialize translations _ = LXToolsAppConfig.setup_translations() # ---------------------------- # Color Manager Class (NEU für einheitliche Farben) # ---------------------------- class ColorManager: @staticmethod def get_system_colors(root): """Get system colors that work across different themes""" try: # Versuche system-spezifische Farben zu ermitteln default_bg = root.cget("bg") # Teste verschiedene Farbnamen test_colors = ["#f0f0f0", "#e1e1e1", "#d9d9d9", "lightgray"] working_bg = default_bg for color in test_colors: try: # Teste ob die Farbe funktioniert test_label = tk.Label(root, bg=color) working_bg = color test_label.destroy() break except tk.TclError: continue return { "default_bg": working_bg, "card_bg": "#f8f9fa", # Hellerer Hintergrund für Cards "hover_bg": "#e6f3ff", "selected_bg": "#cce7ff", "border_color": "#cccccc", "header_bg": "#2c3e50", # Dunkler Header "header_fg": "white", # Weiße Schrift im Header "progress_bg": "#f1f3f4", # Wie Progress Label } except: # Fallback Farben return { "default_bg": "#f0f0f0", "card_bg": "#f8f9fa", "hover_bg": "#e6f3ff", "selected_bg": "#cce7ff", "border_color": "#cccccc", "header_bg": "#2c3e50", "header_fg": "white", "progress_bg": "#f1f3f4", } # ---------------------------- # Theme Manager Class # ---------------------------- class ThemeManager: @staticmethod def apply_light_theme(root): """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 ################### Teil 3 - OS Detection und System Manager ################### class OSDetector: @staticmethod def detect_os(): """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 check_tkinter_available(): """Check if tkinter is available""" try: import tkinter return True except ImportError: return False @staticmethod def install_tkinter(): """Install tkinter based on detected OS""" detected_os = OSDetector.detect_os() if detected_os in LXToolsAppConfig.TKINTER_INSTALL_COMMANDS: commands = LXToolsAppConfig.TKINTER_INSTALL_COMMANDS[detected_os] print(f"Installing tkinter for {detected_os}...") print(f"Command: {' '.join(commands)}") try: # Use pkexec for privilege escalation full_command = ["pkexec", "bash", "-c", " ".join(commands)] result = subprocess.run( full_command, capture_output=True, text=True, timeout=300 ) if result.returncode == 0: print("TKinter installation completed successfully!") return True else: print(f"TKinter installation failed: {result.stderr}") return False except subprocess.TimeoutExpired: print("TKinter installation timed out") return False except Exception as e: print(f"Error installing tkinter: {e}") return False else: print(f"No tkinter installation command defined for {detected_os}") return False class SystemManager: @staticmethod def create_directories(directories): """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): """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): """Copy directory using pkexec""" subprocess.run(["pkexec", "cp", "-r", src, dest], check=True) @staticmethod def remove_file(path): """Remove file using pkexec""" subprocess.run(["pkexec", "rm", "-f", path], check=False) @staticmethod def remove_directory(path): """Remove directory using pkexec""" subprocess.run(["pkexec", "rm", "-rf", path], check=False) @staticmethod def create_symlink(target, link_name): """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): """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 ################### Teil 4 - Image Manager und Gitea API ################### class ImageManager: def __init__(self): self.images = {} def load_image(self, image_key, fallback_paths=None): """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", ], } # 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"Failed to load image from {path}: {e}") continue # Return None if no image found return None class GiteaUpdate: @staticmethod def api_down(url, current_version=""): """Get latest version from Gitea API""" try: with urllib.request.urlopen(url, timeout=10) as response: data = json.loads(response.read().decode()) if data and len(data) > 0: latest_version = data[0].get("tag_name", "Unknown") return latest_version.lstrip("v") # Remove 'v' prefix if present return "Unknown" except Exception as e: print(f"API Error: {e}") return "Unknown" class NetworkChecker: @staticmethod def check_internet_connection(host="8.8.8.8", port=53, timeout=3): """Check if internet connection is available""" try: socket.setdefaulttimeout(timeout) socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect((host, port)) return True except socket.error: return False @staticmethod def check_repository_access(url="https://git.ilunix.de", timeout=5): """Check if repository is accessible""" try: urllib.request.urlopen(url, timeout=timeout) return True except: return False ################### Teil 5 - Download Manager ################### class DownloadManager: @staticmethod def download_and_extract(url, extract_to, progress_callback=None): """Download and extract ZIP file""" try: if progress_callback: progress_callback(f"Downloading from {url}...") with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as tmp_file: urllib.request.urlretrieve(url, tmp_file.name) if progress_callback: progress_callback("Extracting files...") with zipfile.ZipFile(tmp_file.name, "r") as zip_ref: zip_ref.extractall(extract_to) os.unlink(tmp_file.name) return True except Exception as e: if progress_callback: progress_callback(f"Download failed: {str(e)}") return False ################### Teil 6 - App Manager ################### class AppManager: def __init__(self): self.projects = LXToolsAppConfig.PROJECTS def get_project_info(self, project_key): """Get project information by key""" return self.projects.get(project_key) def get_all_projects(self): """Get all project configurations""" return self.projects def is_installed(self, project_key): """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( "/usr/lib/python3/dist-packages/shared_libs/logviewer.py" ) executable_is_executable = False if executable_exists: try: # Check if file is executable file_stat = os.stat( "/usr/lib/python3/dist-packages/shared_libs/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(f"LogViewer installation check:") print(f" Symlink exists: {symlink_exists}") print(f" Executable exists: {executable_exists}") print(f" Is executable: {executable_is_executable}") print(f" Final result: {is_installed}") return is_installed return False def get_installed_version(self, project_key): """Get installed version from config file""" try: if project_key == "wirepy": config_file = ( "/usr/lib/python3/dist-packages/shared_libs/wp_app_config.py" ) elif project_key == "logviewer": config_file = ( "/usr/lib/python3/dist-packages/shared_libs/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"Error getting version for {project_key}: {e}") return "Unknown" def get_latest_version(self, project_key): """Get latest version from API""" project_info = self.get_project_info(project_key) if not project_info: return "Unknown" return GiteaUpdate.api_down(project_info["api_url"]) def check_other_apps_installed(self, exclude_key): """Check if other apps are still installed""" return any( self.is_installed(key) for key in self.projects.keys() if key != exclude_key ) ################### Teil 7 - Installation Manager ################### class InstallationManager: def __init__( self, app_manager, progress_callback=None, icon_callback=None, log_callback=None ): self.app_manager = app_manager self.progress_callback = progress_callback self.icon_callback = icon_callback self.log_callback = log_callback self.system_manager = SystemManager() self.download_manager = DownloadManager() def install_project(self, project_key): """Install or update project""" project_info = self.app_manager.get_project_info(project_key) if not project_info: raise Exception(f"Unknown project: {project_key}") self.update_progress(f"Starting installation of {project_info['name']}...") self.log(f"=== Installing {project_info['name']} ===") try: # Create installation script script_content = self._create_install_script(project_key) # Execute installation self._execute_install_script(script_content) self.update_progress( f"{project_info['name']} installation completed successfully!" ) self.log(f"=== {project_info['name']} installed successfully ===") # Set success icon self.update_icon("success") except Exception as e: self.log(f"ERROR: Installation failed: {e}") self.update_icon("error") raise Exception(f"Installation failed: {e}") def _create_install_script(self, project_key): """Create installation script based on project""" if project_key == "wirepy": return self._create_wirepy_install_script() elif project_key == "logviewer": return self._create_logviewer_install_script() else: raise Exception(f"Unknown project: {project_key}") def _create_wirepy_install_script(self): """Create Wire-Py installation script""" script = f"""#!/bin/bash set -e echo "=== Wire-Py Installation ===" # Create necessary directories mkdir -p /usr/lib/python3/dist-packages/shared_libs mkdir -p /usr/share/icons/lx-icons mkdir -p /usr/share/locale/de/LC_MESSAGES mkdir -p /usr/share/applications mkdir -p /usr/local/etc/ssl mkdir -p /usr/share/polkit-1/actions mkdir -p /usr/share/TK-Themes # Download and extract Wire-Py cd /tmp rm -rf wirepy_install mkdir wirepy_install cd wirepy_install echo "Downloading Wire-Py..." wget -q "{LXToolsAppConfig.WIREPY_URL}" -O wirepy.zip unzip -q wirepy.zip WIREPY_DIR=$(find . -name "Wire-Py" -type d | head -1) echo "Downloading shared libraries..." wget -q "{LXToolsAppConfig.SHARED_LIBS_URL}" -O shared.zip unzip -q shared.zip SHARED_DIR=$(find . -name "shared_libs" -type d | head -1) # Install Wire-Py files echo "Installing Wire-Py executables..." for file in wirepy.py start_wg.py ssl_encrypt.py ssl_decrypt.py match_found.py tunnel.py; do if [ -f "$WIREPY_DIR/$file" ]; then cp "$WIREPY_DIR/$file" /usr/local/bin/ chmod 755 /usr/local/bin/$file echo "Installed $file" fi done # Install config if [ -f "$WIREPY_DIR/wp_app_config.py" ]; then cp "$WIREPY_DIR/wp_app_config.py" /usr/lib/python3/dist-packages/shared_libs/ echo "Installed wp_app_config.py" fi # Install shared libraries echo "Installing shared libraries..." for file in common_tools.py file_and_dir_ensure.py gitea.py __init__.py logview_app_config.py logviewer.py; do if [ -f "$SHARED_DIR/$file" ]; then cp "$SHARED_DIR/$file" /usr/lib/python3/dist-packages/shared_libs/ echo "Installed shared lib: $file" fi done # Install icons if [ -d "$WIREPY_DIR/lx-icons" ]; then echo "Installing icons..." cp -r "$WIREPY_DIR/lx-icons"/* /usr/share/icons/lx-icons/ fi # Install TK-Themes if [ -d "$WIREPY_DIR/TK-Themes" ]; then echo "Installing TK-Themes..." cp -r "$WIREPY_DIR/TK-Themes"/* /usr/share/TK-Themes/ fi # Install desktop file if [ -f "$WIREPY_DIR/Wire-Py.desktop" ]; then cp "$WIREPY_DIR/Wire-Py.desktop" /usr/share/applications/ echo "Installed desktop file" fi # Install language files if [ -d "$WIREPY_DIR/languages/de" ]; then echo "Installing language files..." cp "$WIREPY_DIR/languages/de"/*.mo /usr/share/locale/de/LC_MESSAGES/ 2>/dev/null || true fi # Install policy file if [ -f "$WIREPY_DIR/org.sslcrypt.policy" ]; then cp "$WIREPY_DIR/org.sslcrypt.policy" /usr/share/polkit-1/actions/ echo "Installed policy file" fi # Create symlink ln -sf /usr/local/bin/wirepy.py /usr/local/bin/wirepy echo "Created symlink" # Create SSL key if not exists if [ ! -f /usr/local/etc/ssl/pwgk.pem ]; then echo "Creating SSL key..." openssl genrsa -out /usr/local/etc/ssl/pwgk.pem 4096 chmod 600 /usr/local/etc/ssl/pwgk.pem fi # Cleanup cd /tmp rm -rf wirepy_install echo "Wire-Py installation completed!" """ return script ################### Teil 8 - Installation Manager LogViewer Script ################### def _create_logviewer_install_script(self): """Create LogViewer installation script""" script = f"""#!/bin/bash set -e echo "=== LogViewer Installation ===" # Create necessary directories mkdir -p /usr/lib/python3/dist-packages/shared_libs mkdir -p /usr/share/icons/lx-icons mkdir -p /usr/share/locale/de/LC_MESSAGES mkdir -p /usr/share/applications mkdir -p /usr/share/TK-Themes # Download and extract shared libraries (contains LogViewer) cd /tmp rm -rf logviewer_install mkdir logviewer_install cd logviewer_install echo "Downloading LogViewer and shared libraries..." wget -q "{LXToolsAppConfig.SHARED_LIBS_URL}" -O shared.zip unzip -q shared.zip SHARED_DIR=$(find . -name "shared_libs" -type d | head -1) # Check if TK-Themes exists, if not download Wire-Py for themes if [ ! -d "/usr/share/TK-Themes" ] || [ -z "$(ls -A /usr/share/TK-Themes 2>/dev/null)" ]; then echo "TK-Themes not found, downloading from Wire-Py..." wget -q "{LXToolsAppConfig.WIREPY_URL}" -O wirepy.zip unzip -q wirepy.zip WIREPY_DIR=$(find . -name "Wire-Py" -type d | head -1) if [ -d "$WIREPY_DIR/TK-Themes" ]; then echo "Installing TK-Themes..." cp -r "$WIREPY_DIR/TK-Themes"/* /usr/share/TK-Themes/ fi # Also install icons from Wire-Py if not present if [ -d "$WIREPY_DIR/lx-icons" ] && [ ! -d "/usr/share/icons/lx-icons" ]; then echo "Installing icons from Wire-Py..." cp -r "$WIREPY_DIR/lx-icons"/* /usr/share/icons/lx-icons/ fi fi # Install shared libraries echo "Installing shared libraries..." for file in common_tools.py file_and_dir_ensure.py gitea.py __init__.py logview_app_config.py; do if [ -f "$SHARED_DIR/$file" ]; then cp "$SHARED_DIR/$file" /usr/lib/python3/dist-packages/shared_libs/ echo "Installed shared lib: $file" fi done # Install LogViewer executable if [ -f "$SHARED_DIR/logviewer.py" ]; then cp "$SHARED_DIR/logviewer.py" /usr/lib/python3/dist-packages/shared_libs/ chmod 755 /usr/lib/python3/dist-packages/shared_libs/logviewer.py echo "Installed logviewer.py (executable)" fi # Create LogViewer desktop file cat > /usr/share/applications/LogViewer.desktop << 'EOF' [Desktop Entry] Version=1.0 Type=Application Name=LogViewer Comment=System Log Viewer Exec=/usr/local/bin/logviewer Icon=/usr/share/icons/lx-icons/48/log.png Terminal=false Categories=System;Utility; StartupNotify=true EOF echo "Created LogViewer desktop file" # Create symlink for LogViewer ln -sf /usr/lib/python3/dist-packages/shared_libs/logviewer.py /usr/local/bin/logviewer echo "Created LogViewer symlink" # Install language files if available if [ -d "$SHARED_DIR/languages/de" ]; then echo "Installing language files..." cp "$SHARED_DIR/languages/de"/*.mo /usr/share/locale/de/LC_MESSAGES/ 2>/dev/null || true fi # Cleanup cd /tmp rm -rf logviewer_install echo "LogViewer installation completed!" """ return script def _execute_install_script(self, script_content): """Execute installation script with pkexec""" try: with tempfile.NamedTemporaryFile( mode="w", suffix=".sh", delete=False ) as script_file: script_file.write(script_content) script_file.flush() # Make script executable os.chmod(script_file.name, 0o755) self.log(f"Created install script: {script_file.name}") # Execute with pkexec result = subprocess.run( ["pkexec", "bash", script_file.name], capture_output=True, text=True, timeout=300, # 5 minutes timeout ) # Log output if result.stdout: self.log(f"STDOUT: {result.stdout}") if result.stderr: self.log(f"STDERR: {result.stderr}") # Clean up os.unlink(script_file.name) if result.returncode != 0: raise Exception(f"Installation script failed: {result.stderr}") except subprocess.TimeoutExpired: raise Exception("Installation timed out") except subprocess.CalledProcessError as e: raise Exception(f"Installation script failed: {e}") def update_progress(self, message): if self.progress_callback: self.progress_callback(message) def update_icon(self, status): if self.icon_callback: self.icon_callback(status) def log(self, message): if self.log_callback: self.log_callback(message) ################### Teil 9 - Uninstallation Manager ################### class UninstallationManager: def __init__(self, app_manager, progress_callback=None, log_callback=None): self.app_manager = app_manager self.progress_callback = progress_callback self.log_callback = log_callback def uninstall_project(self, project_key): """Uninstall project""" project_info = self.app_manager.get_project_info(project_key) if not project_info: raise Exception(f"Unknown project: {project_key}") if not self.app_manager.is_installed(project_key): raise Exception(f"{project_info['name']} is not installed.") self.update_progress(f"Uninstalling {project_info['name']}...") self.log(f"=== Uninstalling {project_info['name']} ===") try: # Create uninstallation script script_content = self._create_uninstall_script(project_key) # Execute uninstallation self._execute_uninstall_script(script_content) self.update_progress(f"{project_info['name']} uninstalled successfully!") self.log(f"=== {project_info['name']} uninstalled successfully ===") except Exception as e: self.log(f"ERROR: Uninstallation failed: {e}") raise Exception(f"Uninstallation failed: {e}") def _create_uninstall_script(self, project_key): """Create uninstallation script based on project""" if project_key == "wirepy": return self._create_wirepy_uninstall_script() elif project_key == "logviewer": return self._create_logviewer_uninstall_script() else: raise Exception(f"Unknown project: {project_key}") def _create_wirepy_uninstall_script(self): """Create Wire-Py uninstallation script""" script = """#!/bin/bash set -e echo "=== Wire-Py Uninstallation ===" # Remove Wire-Py executables echo "Removing Wire-Py executables..." for file in wirepy.py start_wg.py ssl_encrypt.py ssl_decrypt.py match_found.py tunnel.py; do rm -f /usr/local/bin/$file echo "Removed $file" done # Remove symlink rm -f /usr/local/bin/wirepy echo "Removed wirepy symlink" # Remove config rm -f /usr/lib/python3/dist-packages/shared_libs/wp_app_config.py echo "Removed wp_app_config.py" # Remove desktop file rm -f /usr/share/applications/Wire-Py.desktop echo "Removed desktop file" # Remove policy file rm -f /usr/share/polkit-1/actions/org.sslcrypt.policy echo "Removed policy file" # Remove language files rm -f /usr/share/locale/de/LC_MESSAGES/wirepy.mo echo "Removed language files" # Remove user config directory if [ -d "$HOME/.config/wire_py" ]; then rm -rf "$HOME/.config/wire_py" echo "Removed user config directory" fi # Remove log file rm -f "$HOME/.local/share/lxlogs/wirepy.log" echo "Removed log file" # Check if LogViewer is still installed before removing shared resources if [ ! -f /usr/local/bin/logviewer ] || [ ! -L /usr/local/bin/logviewer ]; then echo "No other LX apps found, removing shared resources..." rm -rf /usr/share/icons/lx-icons rm -rf /usr/share/TK-Themes rm -rf /usr/local/etc/ssl # Remove shared libs for file in common_tools.py file_and_dir_ensure.py gitea.py __init__.py logview_app_config.py logviewer.py; do rm -f /usr/lib/python3/dist-packages/shared_libs/$file done # Try to remove shared_libs directory if empty rmdir /usr/lib/python3/dist-packages/shared_libs 2>/dev/null || true else echo "LogViewer still installed, keeping shared resources" fi echo "Wire-Py uninstallation completed!" """ return script def _create_logviewer_uninstall_script(self): """Create LogViewer uninstallation script""" script = """#!/bin/bash set -e echo "=== LogViewer Uninstallation ===" # Remove LogViewer symlink rm -f /usr/local/bin/logviewer echo "Removed logviewer symlink" # Remove desktop file rm -f /usr/share/applications/LogViewer.desktop echo "Removed desktop file" # Remove language files rm -f /usr/share/locale/de/LC_MESSAGES/logviewer.mo echo "Removed language files" # Remove user config directory if [ -d "$HOME/.config/logviewer" ]; then rm -rf "$HOME/.config/logviewer" echo "Removed user config directory" fi # Remove log file rm -f "$HOME/.local/share/lxlogs/logviewer.log" echo "Removed log file" # Check if Wire-Py is still installed before removing shared resources if [ ! -f /usr/local/bin/wirepy ] || [ ! -L /usr/local/bin/wirepy ]; then echo "No other LX apps found, removing shared resources..." rm -rf /usr/share/icons/lx-icons rm -rf /usr/share/TK-Themes # Remove shared libs (but keep logviewer.py if we're only uninstalling logviewer) for file in common_tools.py file_and_dir_ensure.py gitea.py __init__.py logview_app_config.py; do rm -f /usr/lib/python3/dist-packages/shared_libs/$file done # Remove logviewer.py last rm -f /usr/lib/python3/dist-packages/shared_libs/logviewer.py # Try to remove shared_libs directory if empty rmdir /usr/lib/python3/dist-packages/shared_libs 2>/dev/null || true else echo "Wire-Py still installed, keeping shared resources" # Only remove logviewer-specific files rm -f /usr/lib/python3/dist-packages/shared_libs/logview_app_config.py rm -f /usr/lib/python3/dist-packages/shared_libs/logviewer.py fi echo "LogViewer uninstallation completed!" """ return script ################### Teil 10 - Uninstallation Manager Helper Methods ################### def _execute_uninstall_script(self, script_content): """Execute uninstallation script with pkexec""" try: with tempfile.NamedTemporaryFile( mode="w", suffix=".sh", delete=False ) as script_file: script_file.write(script_content) script_file.flush() # Make script executable os.chmod(script_file.name, 0o755) self.log(f"Created uninstall script: {script_file.name}") # Execute with pkexec result = subprocess.run( ["pkexec", "bash", script_file.name], capture_output=True, text=True, timeout=120, ) # Log output if result.stdout: self.log(f"STDOUT: {result.stdout}") if result.stderr: self.log(f"STDERR: {result.stderr}") # Clean up os.unlink(script_file.name) if result.returncode != 0: raise Exception(f"Uninstallation script failed: {result.stderr}") except subprocess.TimeoutExpired: raise Exception("Uninstallation timed out") except subprocess.CalledProcessError as e: raise Exception(f"Uninstallation script failed: {e}") def update_progress(self, message): if self.progress_callback: self.progress_callback(message) def log(self, message): if self.log_callback: self.log_callback(message) ################### Teil 11 - LXToolsGUI Klasse (Hauptklasse) ################### class LXToolsGUI: def __init__(self): self.root = None self.notebook = None self.progress_label = None self.download_icon_label = None self.log_text = None self.selected_project = None self.project_frames = {} self.status_labels = {} self.version_labels = {} # Managers self.app_manager = AppManager() self.installation_manager = InstallationManager( self.app_manager, self.update_progress, self.update_download_icon, self.log_message, ) self.uninstallation_manager = UninstallationManager( self.app_manager, self.update_progress, self.log_message ) self.image_manager = ImageManager() # Detect OS self.detected_os = OSDetector.detect_os() # Color scheme self.colors = { "bg": "#f8f9fa", "card_bg": "#ffffff", "hover_bg": "#e3f2fd", "selected_bg": "#bbdefb", "progress_bg": "#f8f9fa", "text": "#2c3e50", "accent": "#3498db", } def create_gui(self): """Create the main GUI""" self.root = tk.Tk() self.root.title(f"{LXToolsAppConfig.APP_NAME} v{LXToolsAppConfig.VERSION}") self.root.geometry( f"{LXToolsAppConfig.WINDOW_WIDTH}x{LXToolsAppConfig.WINDOW_HEIGHT}" ) self.root.configure(bg=self.colors["bg"]) # Try to set icon try: icon = self.image_manager.load_image("download_icon") if icon: self.root.iconphoto(False, icon) except: pass # self.root.minsize(LXToolsAppConfig.WINDOW_WIDTH, LXToolsAppConfig.WINDOW_HEIGHT) ThemeManager.apply_light_theme(self.root) # Create header self._create_header() # Create notebook (tabs) self.notebook = ttk.Notebook(self.root) self.notebook.pack(fill="both", expand=True, padx=15, pady=(10, 10)) # Create tabs self._create_projects_tab() self._create_log_tab() # Create progress section self._create_progress_section() # Create buttons self._create_modern_buttons() # Initial status refresh self.root.after(100, self.refresh_status) return self.root def _create_header(self): """Create clean header""" # HEADER header_frame = tk.Frame(self.root, bg="#2c3e50", height=70) header_frame.pack(fill="x", pady=(0, 0)) header_frame.pack_propagate(False) # Content content = tk.Frame(header_frame, bg="#2c3e50") content.pack(fill="both", expand=True, padx=15, pady=12) # LINKE SEITE: Icon + App Info left_side = tk.Frame(content, bg="#2c3e50") left_side.pack(side="left", anchor="w") icon_text_frame = tk.Frame(left_side, bg="#2c3e50") icon_text_frame.pack(anchor="w") # Werkzeug-Icon tk.Label( icon_text_frame, text="🔧", font=("Helvetica", 18), bg="#2c3e50", fg="white" ).pack(side="left", padx=(0, 8)) # App Name und Version text_frame = tk.Frame(icon_text_frame, bg="#2c3e50") text_frame.pack(side="left") tk.Label( text_frame, text="Lx Tools Installer", font=("Helvetica", 14, "bold"), fg="white", bg="#2c3e50", ).pack(anchor="w") tk.Label( text_frame, text=f"v{LXToolsAppConfig.VERSION} • Linux Application Installer", font=("Helvetica", 9), fg="#bdc3c7", bg="#2c3e50", ).pack(anchor="w") # RECHTE SEITE: System + Dynamischer Status right_side = tk.Frame(content, bg="#2c3e50") right_side.pack(side="right", anchor="e") tk.Label( right_side, text=f"System: {self.detected_os}", font=("Helvetica", 11), fg="#ecf0f1", bg="#2c3e50", ).pack(anchor="e") # DYNAMISCHER Status (anfangs leer) self.header_status_label = tk.Label( right_side, text="", font=("Helvetica", 10), bg="#2c3e50" # Anfangs leer ) self.header_status_label.pack(anchor="e", pady=(2, 0)) # Trennlinie separator = tk.Frame(self.root, height=1, bg="#34495e") separator.pack(fill="x", pady=0) def update_header_status(self, message="", color="#1abc9c"): """Update status in header""" if hasattr(self, "header_status_label"): self.header_status_label.config(text=message, fg=color) def check_ready_status(self): """Check if system is ready for installation""" # Prüfungen: internet_ok = NetworkChecker.check_internet_connection() repo_ok = NetworkChecker.check_repository_access() if internet_ok and repo_ok: self.update_header_status("Ready for installation", "#1abc9c") # Grün elif not internet_ok: self.update_header_status("No internet connection", "#e74c3c") # Rot elif not repo_ok: self.update_header_status("Repository unavailable", "#f39c12") # Orange else: self.update_header_status("System checking...", "#3498db") # Blau ################### Teil 12 - GUI Projects Tab ################### def _create_projects_tab(self): """Create projects tab with project cards""" projects_frame = ttk.Frame(self.notebook) self.notebook.add(projects_frame, text="Projects") # Scrollable frame canvas = tk.Canvas(projects_frame, bg=self.colors["bg"]) scrollbar = ttk.Scrollbar( projects_frame, orient="vertical", command=canvas.yview ) scrollable_frame = tk.Frame(canvas, bg=self.colors["bg"]) scrollable_frame.bind( "", lambda e: canvas.configure(scrollregion=canvas.bbox("all")) ) canvas.create_window((0, 0), window=scrollable_frame, anchor="nw") canvas.configure(yscrollcommand=scrollbar.set) canvas.pack(side="left", fill="both", expand=True) scrollbar.pack(side="right", fill="y") # Create project cards for project_key, project_info in self.app_manager.get_all_projects().items(): self._create_project_card(scrollable_frame, project_key, project_info) def _create_project_card(self, parent, project_key, project_info): """Create a project card""" # Main card frame card_frame = tk.Frame(parent, bg=self.colors["bg"]) card_frame.pack(fill="x", padx=10, pady=5) # Content frame (the actual card) content_frame = tk.Frame( card_frame, bg=self.colors["card_bg"], relief="solid", bd=1 ) content_frame.pack(fill="x", padx=5, pady=2) # Store frame reference self.project_frames[project_key] = card_frame # Make entire card clickable self._make_clickable(content_frame, content_frame, project_key) # Header with icon and title header_frame = tk.Frame(content_frame, bg=self.colors["card_bg"]) header_frame.pack(fill="x", padx=15, pady=(15, 5)) # Icon icon_label = tk.Label(header_frame, bg=self.colors["card_bg"]) icon_label.pack(side="left", padx=(0, 10)) # Try to load project icon icon = self.image_manager.load_image( project_info.get("icon_key", "default_icon") ) if icon: icon_label.config(image=icon) icon_label.image = icon else: # Use emoji based on project if project_key == "wirepy": icon_label.config(text="🔒", font=("Helvetica", 24)) elif project_key == "logviewer": icon_label.config(text="📋", font=("Helvetica", 24)) else: icon_label.config(text="📦", font=("Helvetica", 24)) # Title and description title_frame = tk.Frame(header_frame, bg=self.colors["card_bg"]) title_frame.pack(side="left", fill="x", expand=True) title_label = tk.Label( title_frame, text=project_info["name"], font=("Helvetica", 14, "bold"), bg=self.colors["card_bg"], fg=self.colors["text"], anchor="w", ) title_label.pack(fill="x") desc_label = tk.Label( title_frame, text=project_info["description"], font=("Helvetica", 10), bg=self.colors["card_bg"], fg="#7f8c8d", anchor="w", wraplength=300, ) desc_label.pack(fill="x") # Status section status_frame = tk.Frame(content_frame, bg=self.colors["card_bg"]) status_frame.pack(fill="x", padx=15, pady=(5, 15)) # Status label status_label = tk.Label( status_frame, text="❓ Checking...", font=("Helvetica", 10), bg=self.colors["card_bg"], fg="#95a5a6", anchor="w", ) status_label.pack(fill="x") # Version label version_label = tk.Label( status_frame, text="Version: Checking...", font=("Helvetica", 9), bg=self.colors["card_bg"], fg="#95a5a6", anchor="w", ) version_label.pack(fill="x") # Store label references self.status_labels[project_key] = status_label self.version_labels[project_key] = version_label # Make all elements clickable for widget in [ header_frame, title_frame, title_label, desc_label, status_frame, status_label, version_label, ]: self._make_clickable(widget, content_frame, project_key) # Make icon clickable too self._make_clickable(icon_label, content_frame, project_key) ################### Teil 13 - GUI Card Interaction Methods ################### def _make_clickable(self, widget, main_frame, project_key): """Make widget clickable with hover effects""" def on_click(event): self.select_project(project_key) def on_enter(event): if self.selected_project == project_key: main_frame.config(bg=self.colors["selected_bg"]) self._update_frame_children_bg(main_frame, self.colors["selected_bg"]) else: main_frame.config(bg=self.colors["hover_bg"]) self._update_frame_children_bg(main_frame, self.colors["hover_bg"]) def on_leave(event): if self.selected_project == project_key: main_frame.config(bg=self.colors["selected_bg"]) self._update_frame_children_bg(main_frame, self.colors["selected_bg"]) else: main_frame.config(bg=self.colors["card_bg"]) self._update_frame_children_bg(main_frame, self.colors["card_bg"]) widget.bind("", on_click) widget.bind("", on_enter) widget.bind("", on_leave) def _update_frame_children_bg(self, frame, bg_color): """Recursively update background color of all children""" try: for child in frame.winfo_children(): if isinstance(child, (tk.Frame, tk.Label)): child.config(bg=bg_color) if isinstance(child, tk.Frame): self._update_frame_children_bg(child, bg_color) except tk.TclError: # Ignore color errors pass def select_project(self, project_key): """Select a project""" # Reset previous selection if self.selected_project and self.selected_project in self.project_frames: old_frame = self.project_frames[self.selected_project] old_content = old_frame.winfo_children()[0] # content_frame old_content.config(bg=self.colors["card_bg"]) self._update_frame_children_bg(old_content, self.colors["card_bg"]) # Set new selection self.selected_project = project_key if project_key in self.project_frames: new_frame = self.project_frames[project_key] new_content = new_frame.winfo_children()[0] # content_frame new_content.config(bg=self.colors["selected_bg"]) self._update_frame_children_bg(new_content, self.colors["selected_bg"]) project_info = self.app_manager.get_project_info(project_key) self.log_message(f"Selected project: {project_info['name']}") ################### Teil 14 - GUI Log Tab und Progress Section ################### def _create_log_tab(self): """Create log tab""" log_frame = ttk.Frame(self.notebook) self.notebook.add(log_frame, text="Installation Log") # Log text with scrollbar log_container = tk.Frame(log_frame) log_container.pack(fill="both", expand=True, padx=10, pady=10) self.log_text = tk.Text( log_container, wrap=tk.WORD, font=("Consolas", 9), bg="#1e1e1e", fg="#d4d4d4", insertbackground="white", selectbackground="#264f78", ) log_scrollbar = ttk.Scrollbar( log_container, orient="vertical", command=self.log_text.yview ) self.log_text.configure(yscrollcommand=log_scrollbar.set) self.log_text.pack(side="left", fill="both", expand=True) log_scrollbar.pack(side="right", fill="y") # Log controls log_controls = tk.Frame(log_frame) log_controls.pack(fill="x", padx=10, pady=(0, 10)) # Clear log button clear_log_btn = ttk.Button( log_controls, text="Clear Log", command=self.clear_log ) clear_log_btn.pack(side="right") # Initial log message self.log_message( f"=== {LXToolsAppConfig.APP_NAME} v{LXToolsAppConfig.VERSION} ===" ) self.log_message(f"Working directory: {LXToolsAppConfig.WORK_DIR}") self.log_message(f"Icons directory: {LXToolsAppConfig.ICONS_DIR}") self.log_message(f"Detected OS: {self.detected_os}") self.log_message("Ready for installation...") def _create_progress_section(self): """Create progress section with download icon""" progress_frame = ttk.LabelFrame(self.root, text="Progress", padding=10) progress_frame.pack(fill="x", padx=15, pady=10) # Container for Icon and Progress progress_container = tk.Frame(progress_frame) progress_container.pack(fill="x") # Download Icon (left) self.download_icon_label = tk.Label(progress_container, text="", width=50) self.download_icon_label.pack(side="left", padx=(0, 10)) # Progress Text (right, expandable) self.progress_label = tk.Label( progress_container, text="Ready for installation...", font=("Helvetica", 10), fg="blue", anchor="w", ) self.progress_label.pack(side="left", fill="x", expand=True) # Initial icon load (neutral) self._reset_download_icon() ################### Teil 15 - GUI Buttons and Icon Management ################### def _create_modern_buttons(self): """Create modern styled buttons""" button_frame = tk.Frame(self.root, bg=self.colors["bg"]) button_frame.pack(fill="x", padx=15, pady=(5, 10)) # Button style configuration style = ttk.Style() # Install button (green) style.configure("Install.TButton", foreground="#27ae60", font=("Helvetica", 11)) style.map( "Install.TButton", foreground=[("active", "#14542f"), ("pressed", "#1e8449")], ) # Uninstall button (red) style.configure( "Uninstall.TButton", foreground="#e74c3c", font=("Helvetica", 11) ) style.map( "Uninstall.TButton", foreground=[("active", "#7d3b34"), ("pressed", "#c0392b")], ) # Refresh button (blue) style.configure("Refresh.TButton", foreground="#3498db", font=("Helvetica", 11)) style.map( "Refresh.TButton", foreground=[("active", "#1e3747"), ("pressed", "#2980b9")], ) # Create buttons install_btn = ttk.Button( button_frame, text="Install/Update Selected", command=self.install_selected, style="Install.TButton", padding=8, ) install_btn.pack(side="left", padx=(0, 10)) uninstall_btn = ttk.Button( button_frame, text="Uninstall Selected", command=self.uninstall_selected, style="Uninstall.TButton", padding=8, ) uninstall_btn.pack(side="left", padx=(0, 10)) refresh_btn = ttk.Button( button_frame, text="Refresh Status", command=self.refresh_status, style="Refresh.TButton", padding=8, ) refresh_btn.pack(side="right") def update_download_icon(self, status): """Update download icon based on status""" if not self.download_icon_label: return if status == "downloading": icon = self.image_manager.load_image("download_icon") if icon: self.download_icon_label.config(image=icon, text="") self.download_icon_label.image = icon else: self.download_icon_label.config(text="⬇️", font=("Helvetica", 16)) elif status == "error": icon = self.image_manager.load_image("download_error_icon") if icon: self.download_icon_label.config(image=icon, text="") self.download_icon_label.image = icon else: self.download_icon_label.config(text="❌", font=("Helvetica", 16)) elif status == "success": icon = self.image_manager.load_image("success_icon") if icon: self.download_icon_label.config(image=icon, text="") self.download_icon_label.image = icon else: self.download_icon_label.config(text="✅", font=("Helvetica", 16)) self.download_icon_label.update() def _reset_download_icon(self): """Reset download icon to neutral state""" icon = self.image_manager.load_image("download_icon") if icon: self.download_icon_label.config(image=icon, text="") self.download_icon_label.image = icon else: self.download_icon_label.config(text="📥", font=("Helvetica", 16)) ################### Teil 16 - GUI Status Management ################### def refresh_status(self): """Refresh application status and version information""" self.update_progress("Refreshing status and checking versions...") self._reset_download_icon() self.log_message("=== Refreshing Status ===") self.root.focus_set() for project_key, project_info in self.app_manager.get_all_projects().items(): status_label = self.status_labels[project_key] version_label = self.version_labels[project_key] self.log_message(f"Checking {project_info['name']}...") if self.app_manager.is_installed(project_key): installed_version = self.app_manager.get_installed_version(project_key) status_label.config( text=f"✅ Installed ({installed_version})", fg="green" ) self.log_message( f"{project_info['name']}: Installed v{installed_version}" ) # Get latest version from API try: latest_version = self.app_manager.get_latest_version(project_key) if latest_version != "Unknown": if installed_version != latest_version: version_label.config( text=f"Latest: v {latest_version} (Update available)", fg="orange", ) self.log_message( f"{project_info['name']}: Update available v{latest_version}" ) else: version_label.config( text=f"Latest: v {latest_version} (Up to date)", fg="green", ) self.log_message(f"{project_info['name']}: Up to date") else: version_label.config(text="Latest: Unknown", fg="gray") self.log_message( f"{project_info['name']}: Could not check latest version" ) except Exception as e: version_label.config(text="Latest: Check failed", fg="gray") self.log_message( f"{project_info['name']}: Version check failed: {e}" ) else: status_label.config(text="❌ Not installed", fg="red") self.log_message(f"{project_info['name']}: Not installed") # Still show latest available version try: latest_version = self.app_manager.get_latest_version(project_key) if latest_version != "Unknown": version_label.config( text=f"Available: v {latest_version}", fg="blue" ) self.log_message( f"{project_info['name']}: Available v {latest_version}" ) else: version_label.config(text="Available: Unknown", fg="gray") except Exception as e: version_label.config(text="Available: Check failed", fg="gray") self.log_message( f"{project_info['name']}: Version check failed: {e}" ) self.update_progress("Status refresh completed.") self.log_message("=== Status refresh completed ===") self.check_ready_status() ################### Teil 17 - GUI Action Methods ################### def install_selected(self): """Handle install button click""" if not self.selected_project: messagebox.showwarning("Warning", "Please select a project to install.") return # Check internet connection if not NetworkChecker.check_internet_connection(): self.update_download_icon("error") messagebox.showerror( "Network Error", "No internet connection available.\nPlease check your network connection.", ) return if not NetworkChecker.check_repository_access(): self.update_download_icon("error") messagebox.showerror( "Repository Error", "Cannot access repository.\nPlease try again later." ) return self.root.focus_set() # Reset download icon self._reset_download_icon() project_info = self.app_manager.get_project_info(self.selected_project) # Check if already installed if self.app_manager.is_installed(self.selected_project): installed_version = self.app_manager.get_installed_version( self.selected_project ) latest_version = self.app_manager.get_latest_version(self.selected_project) dialog_text = ( f"{project_info['name']} is already installed.\n\n" f"Installed version: {installed_version}\n" f"Latest version: v {latest_version}\n\n" f"YES = Update (reinstall all files)\n" f"NO = Uninstall\n" f"Cancel = Do nothing" ) result = messagebox.askyesnocancel( f"{project_info['name']} already installed", dialog_text ) if result is None: # Cancel self.update_progress("Installation cancelled.") return elif not result: # Uninstall self.uninstall_selected() return else: # Update self.update_progress("Updating application...") try: self.update_download_icon("downloading") self.installation_manager.install_project(self.selected_project) self.update_download_icon("success") messagebox.showinfo( "Success", f"{project_info['name']} has been successfully installed/updated.", ) self.refresh_status() except Exception as e: self.update_download_icon("error") messagebox.showerror("Error", f"Installation failed: {e}") def uninstall_selected(self): """Handle uninstall button click""" if not self.selected_project: messagebox.showwarning("Warning", "Please select a project to uninstall.") return project_info = self.app_manager.get_project_info(self.selected_project) if not self.app_manager.is_installed(self.selected_project): messagebox.showinfo("Info", f"{project_info['name']} is not installed.") return result = messagebox.askyesno( "Confirm Uninstall", f"Are you sure you want to uninstall {project_info['name']}?\n\n" f"This will remove all application files and user configurations.", ) if not result: return try: self.uninstallation_manager.uninstall_project(self.selected_project) messagebox.showinfo( "Success", f"{project_info['name']} has been successfully uninstalled." ) self.refresh_status() except Exception as e: messagebox.showerror("Error", f"Uninstallation failed: {e}") self.root.focus_set() ################### Teil 18 - GUI Helper Methods ################### def update_progress(self, message): """Update progress message""" if self.progress_label: self.progress_label.config(text=message) self.progress_label.update() print(f"Progress: {message}") def log_message(self, message): """Add message to log""" if self.log_text: timestamp = datetime.now().strftime("%H:%M:%S") log_entry = f"[{timestamp}] {message}\n" self.log_text.insert(tk.END, log_entry) self.log_text.see(tk.END) self.log_text.update() print(f"Log: {message}") def clear_log(self): """Clear the log""" if self.log_text: self.log_text.delete(1.0, tk.END) self.log_message("Log cleared") def run(self): """Start the GUI application""" root = self.create_gui() root.mainloop() ################### Teil 19 - TKinter Check und Main Function ################### def check_and_install_tkinter(): """Check if tkinter is available and install if needed""" if not OSDetector.check_tkinter_available(): print("TKinter is not available on this system.") detected_os = OSDetector.detect_os() print(f"Detected OS: {detected_os}") response = input("Would you like to install TKinter? (y/n): ").lower().strip() if response in ["y", "yes"]: print("Installing TKinter...") if OSDetector.install_tkinter(): print("TKinter installed successfully!") print("Please restart the application.") return False else: print("Failed to install TKinter.") print("Please install TKinter manually:") if detected_os in ["Ubuntu", "Debian", "Linux Mint", "Pop!_OS"]: print("sudo apt update && sudo apt install python3-tk") elif detected_os == "Fedora": print("sudo dnf install tkinter") elif detected_os in [ "Arch Linux", "Manjaro", "Garuda Linux", "EndeavourOS", ]: print("sudo pacman -S tk") elif "SUSE" in detected_os or "openSUSE" in detected_os: print("sudo zypper install python3-tk") else: print("Please check your distribution's package manager.") return False else: print("TKinter is required to run this application.") return False return True def main(): """Main function to start the application""" print(f"=== {LXToolsAppConfig.APP_NAME} v{LXToolsAppConfig.VERSION} ===") print(f"Working directory: {os.getcwd()}") # Check and install tkinter if needed if not check_and_install_tkinter(): return try: # Create and run the GUI app = LXToolsGUI() app.run() except KeyboardInterrupt: print("\nApplication interrupted by user.") except Exception as e: print(f"Fatal error: {e}") try: messagebox.showerror("Fatal Error", f"Application failed to start: {e}") except: pass if __name__ == "__main__": main()