Files
lxtools_installer/lxtools_installerv3.py
2025-06-06 01:14:23 +02:00

1865 lines
64 KiB
Python
Executable File

#!/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 = 700
# 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
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(
"<Configure>", 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("<Button-1>", on_click)
widget.bind("<Enter>", on_enter)
widget.bind("<Leave>", 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=(0, 15))
# Button style configuration
style = ttk.Style()
# Install button (green)
style.configure(
"Install.TButton", foreground="#27ae60", font=("Helvetica", 10, "bold")
)
style.map(
"Install.TButton",
foreground=[("active", "#2ecc71"), ("pressed", "#1e8449")],
)
# Uninstall button (red)
style.configure(
"Uninstall.TButton", foreground="#e74c3c", font=("Helvetica", 10, "bold")
)
style.map(
"Uninstall.TButton",
foreground=[("active", "#ec7063"), ("pressed", "#c0392b")],
)
# Refresh button (blue)
style.configure(
"Refresh.TButton", foreground="#3498db", font=("Helvetica", 10, "bold")
)
style.map(
"Refresh.TButton",
foreground=[("active", "#5dade2"), ("pressed", "#2980b9")],
)
# Create buttons
install_btn = ttk.Button(
button_frame,
text="Install/Update Selected",
command=self.install_selected,
style="Install.TButton",
)
install_btn.pack(side="left", padx=(0, 10))
uninstall_btn = ttk.Button(
button_frame,
text="Uninstall Selected",
command=self.uninstall_selected,
style="Uninstall.TButton",
)
uninstall_btn.pack(side="left", padx=(0, 10))
refresh_btn = ttk.Button(
button_frame,
text="Refresh Status",
command=self.refresh_status,
style="Refresh.TButton",
)
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 ===")
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
# 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}")
################### 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()