1585 lines
55 KiB
Python
Executable File
1585 lines
55 KiB
Python
Executable File
#!/usr/bin/python3
|
|
import tkinter as tk
|
|
from tkinter import messagebox, ttk
|
|
import os
|
|
import subprocess
|
|
import urllib.request
|
|
import json
|
|
import tempfile
|
|
import zipfile
|
|
import shutil
|
|
import sys
|
|
import socket
|
|
|
|
# Add current directory to path for imports
|
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
sys.path.insert(0, current_dir)
|
|
|
|
# Try to import common_tools, but provide fallback
|
|
try:
|
|
from common_tools import LxTools, center_window_cross_platform
|
|
|
|
USE_LXTOOLS = True
|
|
print("Using local common_tools from LxTools project")
|
|
except ImportError:
|
|
try:
|
|
from shared_libs.common_tools import LxTools, center_window_cross_platform
|
|
|
|
USE_LXTOOLS = True
|
|
print("Using shared_libs.common_tools")
|
|
except ImportError:
|
|
print("Warning: common_tools not found, using integrated methods")
|
|
USE_LXTOOLS = False
|
|
|
|
|
|
# ----------------------------
|
|
# App Configuration Class (korrigiert)
|
|
# ----------------------------
|
|
class LXToolsAppConfig:
|
|
VERSION = "1.0.8"
|
|
APP_NAME = "LX Tools Installer"
|
|
WINDOW_WIDTH = 650
|
|
WINDOW_HEIGHT = 600
|
|
DEBUG_WINDOW_HEIGHT = 700
|
|
|
|
# LxTools Installer eigene Ressourcen
|
|
WORK_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
ICONS_DIR = os.path.join(WORK_DIR, "lx-icons")
|
|
THEMES_DIR = os.path.join(WORK_DIR, "TK-Themes")
|
|
|
|
# Download URLs für alle installierbaren Projekte
|
|
PROJECTS = {
|
|
"wirepy": {
|
|
"name": "Wire-Py",
|
|
"description": "🔐 WireGuard VPN Manager",
|
|
"download_url": "https://git.ilunix.de/punix/Wire-Py/archive/main.zip",
|
|
"api_url": "https://git.ilunix.de/api/v1/repos/punix/Wire-Py/releases",
|
|
"archive_folder": "Wire-Py",
|
|
"icon_key": "wirepy_icon",
|
|
},
|
|
"logviewer": {
|
|
"name": "LogViewer",
|
|
"description": "📋 System Log Viewer",
|
|
"download_url": "https://git.ilunix.de/punix/shared_libs/archive/main.zip",
|
|
"api_url": "https://git.ilunix.de/api/v1/repos/punix/shared_libs/releases",
|
|
"archive_folder": "shared_libs",
|
|
"icon_key": "logviewer_icon",
|
|
},
|
|
}
|
|
|
|
# Shared Libraries (für alle Projekte benötigt)
|
|
SHARED_LIBS_URL = "https://git.ilunix.de/punix/shared_libs/archive/main.zip"
|
|
SHARED_LIBS_API_URL = (
|
|
"https://git.ilunix.de/api/v1/repos/punix/shared_libs/releases"
|
|
)
|
|
|
|
@staticmethod
|
|
def get_icon_path(icon_key):
|
|
"""Get icon path with fallbacks - KORRIGIERT"""
|
|
base_paths = [
|
|
LXToolsAppConfig.ICONS_DIR,
|
|
os.path.join(LXToolsAppConfig.WORK_DIR, "lx-icons"),
|
|
"./lx-icons",
|
|
"/usr/share/icons/lx-icons",
|
|
]
|
|
|
|
icon_mapping = {
|
|
"app_icon": ["64/download.png", "48/download.png", "32/download.png"],
|
|
"wirepy_icon": ["32/wg_vpn.png", "48/wg_vpn.png"],
|
|
"logviewer_icon": ["32/log.png", "48/log.png"],
|
|
"download_icon": ["32/download.png", "48/download.png"],
|
|
"download_error_icon": ["32/download_error.png", "48/error.png"],
|
|
"success_icon": ["32/download.png", "48/download.png"],
|
|
}
|
|
|
|
if icon_key not in icon_mapping:
|
|
return None
|
|
|
|
for base_path in base_paths:
|
|
if not os.path.exists(base_path):
|
|
continue
|
|
for icon_file in icon_mapping[icon_key]:
|
|
full_path = os.path.join(base_path, icon_file)
|
|
if os.path.exists(full_path):
|
|
print(f"Found icon: {full_path}")
|
|
return full_path
|
|
|
|
print(f"Icon not found: {icon_key}")
|
|
return None
|
|
|
|
|
|
# ----------------------------
|
|
# Integrierte LxTools Methoden (korrigiert)
|
|
# ----------------------------
|
|
class IntegratedLxTools:
|
|
@staticmethod
|
|
def center_window_cross_platform(window):
|
|
"""Center window on screen - works with multiple monitors"""
|
|
window.update_idletasks()
|
|
|
|
# Get window dimensions
|
|
window_width = window.winfo_reqwidth()
|
|
window_height = window.winfo_reqheight()
|
|
|
|
# Get screen dimensions
|
|
screen_width = window.winfo_screenwidth()
|
|
screen_height = window.winfo_screenheight()
|
|
|
|
# Calculate position
|
|
pos_x = (screen_width // 2) - (window_width // 2)
|
|
pos_y = (screen_height // 2) - (window_height // 2)
|
|
|
|
# Ensure window is not positioned off-screen
|
|
pos_x = max(0, pos_x)
|
|
pos_y = max(0, pos_y)
|
|
|
|
window.geometry(f"{window_width}x{window_height}+{pos_x}+{pos_y}")
|
|
|
|
@staticmethod
|
|
def msg_window(parent, title, message, msg_type="info", width=400, height=200):
|
|
"""Custom message window with proper centering"""
|
|
msg_win = tk.Toplevel(parent)
|
|
msg_win.title(title)
|
|
msg_win.geometry(f"{width}x{height}")
|
|
msg_win.transient(parent)
|
|
msg_win.grab_set()
|
|
|
|
# Configure grid
|
|
msg_win.grid_columnconfigure(0, weight=1)
|
|
msg_win.grid_rowconfigure(0, weight=1)
|
|
msg_win.grid_rowconfigure(1, weight=0)
|
|
|
|
# Message frame
|
|
msg_frame = ttk.Frame(msg_win, padding=20)
|
|
msg_frame.grid(row=0, column=0, sticky="nsew")
|
|
msg_frame.grid_columnconfigure(0, weight=1)
|
|
msg_frame.grid_rowconfigure(0, weight=1)
|
|
|
|
# Message text
|
|
msg_label = tk.Label(
|
|
msg_frame,
|
|
text=message,
|
|
wraplength=width - 40,
|
|
justify="left",
|
|
font=("Helvetica", 10),
|
|
)
|
|
msg_label.grid(row=0, column=0, sticky="nsew")
|
|
|
|
# Button frame
|
|
btn_frame = ttk.Frame(msg_win)
|
|
btn_frame.grid(row=1, column=0, sticky="ew", padx=20, pady=(0, 20))
|
|
btn_frame.grid_columnconfigure(0, weight=1)
|
|
|
|
# OK Button
|
|
ok_btn = ttk.Button(btn_frame, text="OK", command=msg_win.destroy)
|
|
ok_btn.grid(row=0, column=0)
|
|
|
|
# Center the window
|
|
IntegratedLxTools.center_window_cross_platform(msg_win)
|
|
|
|
# Focus
|
|
msg_win.focus_set()
|
|
ok_btn.focus_set()
|
|
|
|
return msg_win
|
|
|
|
|
|
# ----------------------------
|
|
# Theme Manager Class (korrigiert)
|
|
# ----------------------------
|
|
class ThemeManager:
|
|
@staticmethod
|
|
def apply_light_theme(root):
|
|
"""Apply light theme using your working method"""
|
|
try:
|
|
# Verwende TK-Themes aus dem aktuellen LxTools Projekt-Ordner
|
|
theme_dir = LXToolsAppConfig.THEMES_DIR
|
|
water_theme_path = os.path.join(theme_dir, "water.tcl")
|
|
|
|
print(f"Looking for theme at: {water_theme_path}")
|
|
|
|
if os.path.exists(water_theme_path):
|
|
try:
|
|
# DEINE funktionierende Methode:
|
|
root.tk.call("source", water_theme_path)
|
|
root.tk.call("set_theme", "light")
|
|
print("Successfully applied water theme with set_theme light")
|
|
return True
|
|
except tk.TclError as e:
|
|
print(f"Theme loading failed: {e}")
|
|
|
|
# Fallback: Versuche ohne set_theme
|
|
try:
|
|
root.tk.call("source", water_theme_path)
|
|
style = ttk.Style()
|
|
available_themes = style.theme_names()
|
|
print(f"Available themes: {available_themes}")
|
|
|
|
# Versuche verschiedene Theme-Namen
|
|
for theme_name in ["water", "Water", "light", "awlight"]:
|
|
if theme_name in available_themes:
|
|
style.theme_use(theme_name)
|
|
print(f"Applied theme: {theme_name}")
|
|
return True
|
|
|
|
except Exception as e2:
|
|
print(f"Fallback theme loading failed: {e2}")
|
|
else:
|
|
print(f"Theme file not found: {water_theme_path}")
|
|
print(f"Current working directory: {os.getcwd()}")
|
|
print(f"Theme directory exists: {os.path.exists(theme_dir)}")
|
|
if os.path.exists(theme_dir):
|
|
print(f"Files in theme directory: {os.listdir(theme_dir)}")
|
|
|
|
# System theme fallback
|
|
try:
|
|
style = ttk.Style()
|
|
if "clam" in style.theme_names():
|
|
style.theme_use("clam")
|
|
print("Using fallback theme: clam")
|
|
return True
|
|
except:
|
|
pass
|
|
|
|
except Exception as e:
|
|
print(f"Theme loading completely failed: {e}")
|
|
|
|
return False
|
|
|
|
|
|
# ----------------------------
|
|
# Image Manager Class (korrigiert)
|
|
# ----------------------------
|
|
class ImageManager:
|
|
def __init__(self):
|
|
self.images = {}
|
|
|
|
def load_image(self, icon_key, fallback_paths=None):
|
|
"""Load PNG image using tk.PhotoImage with fallback options"""
|
|
if icon_key in self.images:
|
|
return self.images[icon_key]
|
|
|
|
# Get primary path from config
|
|
primary_path = LXToolsAppConfig.get_icon_path(icon_key)
|
|
paths_to_try = []
|
|
|
|
if primary_path:
|
|
paths_to_try.append(primary_path)
|
|
|
|
# Add fallback paths
|
|
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[icon_key] = photo
|
|
print(f"Successfully loaded image: {path}")
|
|
return photo
|
|
except tk.TclError as e:
|
|
print(f"Failed to load image from {path}: {e}")
|
|
continue
|
|
|
|
# Return None if no image found
|
|
print(f"No image found for key: {icon_key}")
|
|
return None
|
|
|
|
|
|
# ----------------------------
|
|
# OS Detection Class (korrigiert)
|
|
# ----------------------------
|
|
class OSDetector:
|
|
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"),
|
|
]
|
|
|
|
@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 OSDetector.OS_DETECTION:
|
|
if keyword in content:
|
|
return os_name
|
|
|
|
return "Unknown System"
|
|
except FileNotFoundError:
|
|
return "File not found"
|
|
|
|
|
|
# ----------------------------
|
|
# Network Checker Class (korrigiert)
|
|
# ----------------------------
|
|
class NetworkChecker:
|
|
@staticmethod
|
|
def check_internet(host="8.8.8.8", port=53, timeout=3): # ← Korrigierter Name
|
|
"""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(url="https://git.ilunix.de"): # ← Korrigierter Name
|
|
"""Check if repository is accessible"""
|
|
try:
|
|
urllib.request.urlopen(url, timeout=5)
|
|
return True
|
|
except:
|
|
return False
|
|
|
|
|
|
# ----------------------------
|
|
# App Manager Class (korrigiert)
|
|
# ----------------------------
|
|
class AppManager:
|
|
def __init__(self):
|
|
self.projects = LXToolsAppConfig.PROJECTS
|
|
|
|
def get_all_projects(self):
|
|
"""Get all project configurations"""
|
|
return self.projects
|
|
|
|
def get_project_info(self, project_key):
|
|
"""Get project information by key"""
|
|
return self.projects.get(project_key)
|
|
|
|
def is_installed(self, project_key):
|
|
"""Check if project is installed"""
|
|
if project_key == "wirepy":
|
|
return os.path.exists("/usr/local/bin/wirepy")
|
|
elif project_key == "logviewer":
|
|
return os.path.exists("/usr/local/bin/logviewer")
|
|
else:
|
|
return os.path.exists(f"/usr/local/bin/{project_key}")
|
|
|
|
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:
|
|
config_file = f"/usr/lib/python3/dist-packages/shared_libs/{project_key}_app_config.py"
|
|
|
|
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 - KORRIGIERT"""
|
|
try:
|
|
project_info = self.get_project_info(project_key)
|
|
if not project_info:
|
|
return "Unknown"
|
|
|
|
with urllib.request.urlopen(
|
|
project_info["api_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")
|
|
return "Unknown" # ← FIX: Korrigierte Syntax
|
|
except Exception as e:
|
|
print(f"API Error for {project_key}: {e}")
|
|
return "Unknown" # ← FIX: Korrigierte Syntax
|
|
|
|
|
|
# ----------------------------
|
|
# Installation Manager Class (korrigiert)
|
|
# ----------------------------
|
|
class InstallationManager:
|
|
def __init__(
|
|
self,
|
|
app_manager,
|
|
progress_callback=None,
|
|
icon_callback=None,
|
|
debug_callback=None,
|
|
):
|
|
self.app_manager = app_manager
|
|
self.progress_callback = progress_callback
|
|
self.icon_callback = icon_callback
|
|
self.debug_callback = debug_callback
|
|
|
|
def install_project(self, project_key):
|
|
"""Install any project generically"""
|
|
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 {project_info['name']} installation...")
|
|
self.update_icon("downloading")
|
|
|
|
try:
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
# Download project
|
|
self.update_progress(f"Downloading {project_info['name']}...")
|
|
if not self._download_and_extract(
|
|
project_info["download_url"], temp_dir
|
|
):
|
|
raise Exception(f"Failed to download {project_info['name']}")
|
|
|
|
# Download shared libs
|
|
self.update_progress("Downloading shared libraries...")
|
|
shared_temp = os.path.join(temp_dir, "shared")
|
|
if not self._download_and_extract(
|
|
LXToolsAppConfig.SHARED_LIBS_URL, shared_temp
|
|
):
|
|
raise Exception("Failed to download shared libraries")
|
|
|
|
# Create installation script
|
|
self.update_progress("Preparing installation...")
|
|
script_path = self._create_install_script(
|
|
project_key, project_info, temp_dir
|
|
)
|
|
|
|
# Execute installation
|
|
self.update_progress("Installing...")
|
|
self._execute_install_script(script_path)
|
|
|
|
self.update_progress(f"{project_info['name']} installation completed!")
|
|
self.update_icon("success")
|
|
return True
|
|
|
|
except Exception as e:
|
|
self.update_icon("error")
|
|
raise Exception(f"Installation failed: {e}")
|
|
|
|
def _download_and_extract(self, url, extract_to):
|
|
"""Download and extract ZIP file"""
|
|
try:
|
|
with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as tmp_file:
|
|
urllib.request.urlretrieve(url, tmp_file.name)
|
|
|
|
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:
|
|
self.debug_log(f"Download failed: {e}")
|
|
return False
|
|
|
|
def _create_install_script(self, project_key, project_info, temp_dir):
|
|
"""Create installation script for any project"""
|
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".sh", delete=False) as f:
|
|
|
|
project_source = os.path.join(temp_dir, project_info["archive_folder"])
|
|
shared_source = os.path.join(temp_dir, "shared", "shared_libs")
|
|
|
|
if project_key == "wirepy":
|
|
script_content = f"""#!/bin/bash
|
|
set -e
|
|
|
|
echo "=== {project_info['name']} Installation Script ==="
|
|
|
|
# Create 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
|
|
|
|
# Install shared libraries
|
|
echo "Installing shared libraries..."
|
|
if [ -d "{shared_source}" ]; then
|
|
cp -f "{shared_source}"/*.py /usr/lib/python3/dist-packages/shared_libs/ 2>/dev/null || true
|
|
fi
|
|
|
|
# Install Wire-Py files
|
|
echo "Installing Wire-Py files..."
|
|
cp -f "{project_source}/wirepy.py" /usr/local/bin/
|
|
cp -f "{project_source}/start_wg.py" /usr/local/bin/
|
|
cp -f "{project_source}/ssl_encrypt.py" /usr/local/bin/
|
|
cp -f "{project_source}/ssl_decrypt.py" /usr/local/bin/
|
|
cp -f "{project_source}/match_found.py" /usr/local/bin/
|
|
cp -f "{project_source}/tunnel.py" /usr/local/bin/ 2>/dev/null || true
|
|
|
|
# Make executable
|
|
chmod 755 /usr/local/bin/wirepy.py
|
|
chmod 755 /usr/local/bin/start_wg.py
|
|
chmod 755 /usr/local/bin/ssl_encrypt.py
|
|
chmod 755 /usr/local/bin/ssl_decrypt.py
|
|
chmod 755 /usr/local/bin/match_found.py
|
|
chmod 755 /usr/local/bin/tunnel.py 2>/dev/null || true
|
|
|
|
# Install config
|
|
cp -f "{project_source}/wp_app_config.py" /usr/lib/python3/dist-packages/shared_libs/
|
|
|
|
# Install icons
|
|
echo "Installing icons..."
|
|
if [ -d "{project_source}/lx-icons" ]; then
|
|
cp -rf "{project_source}/lx-icons"/* /usr/share/icons/lx-icons/
|
|
fi
|
|
|
|
# Install desktop file
|
|
if [ -f "{project_source}/Wire-Py.desktop" ]; then
|
|
cp -f "{project_source}/Wire-Py.desktop" /usr/share/applications/
|
|
fi
|
|
|
|
# Install policy file
|
|
if [ -f "{project_source}/org.sslcrypt.policy" ]; then
|
|
cp -f "{project_source}/org.sslcrypt.policy" /usr/share/polkit-1/actions/
|
|
fi
|
|
|
|
# Install language files
|
|
echo "Installing language files..."
|
|
if [ -d "{project_source}/languages/de" ]; then
|
|
cp -f "{project_source}/languages/de"/*.mo /usr/share/locale/de/LC_MESSAGES/ 2>/dev/null || true
|
|
fi
|
|
|
|
# Create symlink
|
|
rm -f /usr/local/bin/wirepy
|
|
ln -sf /usr/local/bin/wirepy.py /usr/local/bin/wirepy
|
|
|
|
# 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 2>/dev/null || echo "Warning: SSL key creation failed"
|
|
chmod 600 /usr/local/etc/ssl/pwgk.pem 2>/dev/null || true
|
|
fi
|
|
|
|
echo "=== {project_info['name']} installation completed ==="
|
|
"""
|
|
|
|
elif project_key == "logviewer":
|
|
script_content = f"""#!/bin/bash
|
|
set -e
|
|
|
|
echo "=== {project_info['name']} Installation Script ==="
|
|
|
|
# Create 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
|
|
|
|
# Install shared libraries
|
|
echo "Installing shared libraries..."
|
|
if [ -d "{shared_source}" ]; then
|
|
cp -f "{shared_source}"/*.py /usr/lib/python3/dist-packages/shared_libs/ 2>/dev/null || true
|
|
fi
|
|
|
|
# Install LogViewer
|
|
echo "Installing LogViewer..."
|
|
cp -f "{shared_source}/logviewer.py" /usr/local/bin/
|
|
chmod 755 /usr/local/bin/logviewer.py
|
|
|
|
# Install config
|
|
cp -f "{shared_source}/logview_app_config.py" /usr/lib/python3/dist-packages/shared_libs/
|
|
|
|
# Install icons (if available)
|
|
if [ -d "{shared_source}/lx-icons" ]; then
|
|
cp -rf "{shared_source}/lx-icons"/* /usr/share/icons/lx-icons/ 2>/dev/null || true
|
|
fi
|
|
|
|
# Install desktop file (if available)
|
|
if [ -f "{shared_source}/LogViewer.desktop" ]; then
|
|
cp -f "{shared_source}/LogViewer.desktop" /usr/share/applications/
|
|
fi
|
|
|
|
# Install language files (if available)
|
|
if [ -d "{shared_source}/languages/de" ]; then
|
|
cp -f "{shared_source}/languages/de"/*.mo /usr/share/locale/de/LC_MESSAGES/ 2>/dev/null || true
|
|
fi
|
|
|
|
# Create symlink
|
|
rm -f /usr/local/bin/logviewer
|
|
ln -sf /usr/local/bin/logviewer.py /usr/local/bin/logviewer
|
|
|
|
echo "=== {project_info['name']} installation completed ==="
|
|
"""
|
|
|
|
else:
|
|
# Generisches Script für zukünftige Projekte
|
|
script_content = f"""#!/bin/bash
|
|
set -e
|
|
|
|
echo "=== {project_info['name']} Installation Script ==="
|
|
|
|
# Create 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
|
|
|
|
# Install shared libraries
|
|
echo "Installing shared libraries..."
|
|
if [ -d "{shared_source}" ]; then
|
|
cp -f "{shared_source}"/*.py /usr/lib/python3/dist-packages/shared_libs/ 2>/dev/null || true
|
|
fi
|
|
|
|
# Install project files (generic approach)
|
|
echo "Installing {project_info['name']} files..."
|
|
if [ -f "{project_source}/{project_key}.py" ]; then
|
|
cp -f "{project_source}/{project_key}.py" /usr/local/bin/
|
|
chmod 755 /usr/local/bin/{project_key}.py
|
|
|
|
# Create symlink
|
|
rm -f /usr/local/bin/{project_key}
|
|
ln -sf /usr/local/bin/{project_key}.py /usr/local/bin/{project_key}
|
|
fi
|
|
|
|
# Install config (if exists)
|
|
if [ -f "{project_source}/{project_key}_app_config.py" ]; then
|
|
cp -f "{project_source}/{project_key}_app_config.py" /usr/lib/python3/dist-packages/shared_libs/
|
|
fi
|
|
|
|
# Install icons (if available)
|
|
if [ -d "{project_source}/lx-icons" ]; then
|
|
cp -rf "{project_source}/lx-icons"/* /usr/share/icons/lx-icons/ 2>/dev/null || true
|
|
fi
|
|
|
|
# Install desktop file (if available)
|
|
if [ -f "{project_source}/{project_info['name']}.desktop" ]; then
|
|
cp -f "{project_source}/{project_info['name']}.desktop" /usr/share/applications/
|
|
fi
|
|
|
|
# Install language files (if available)
|
|
if [ -d "{project_source}/languages/de" ]; then
|
|
cp -f "{project_source}/languages/de"/*.mo /usr/share/locale/de/LC_MESSAGES/ 2>/dev/null || true
|
|
fi
|
|
|
|
echo "=== {project_info['name']} installation completed ==="
|
|
"""
|
|
|
|
f.write(script_content)
|
|
script_path = f.name
|
|
|
|
# Make script executable
|
|
os.chmod(script_path, 0o755)
|
|
self.debug_log(f"Created install script: {script_path}")
|
|
return script_path
|
|
|
|
def _execute_install_script(self, script_path):
|
|
"""Execute installation script with pkexec"""
|
|
try:
|
|
result = subprocess.run(
|
|
["pkexec", "bash", script_path],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=120,
|
|
)
|
|
|
|
if result.returncode != 0:
|
|
error_msg = f"Installation script failed: {result.stderr}"
|
|
self.debug_log(f"ERROR: {error_msg}")
|
|
self.debug_log(f"STDOUT: {result.stdout}")
|
|
raise Exception(error_msg)
|
|
|
|
self.debug_log("Installation script output:")
|
|
self.debug_log(result.stdout)
|
|
|
|
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)
|
|
self.debug_log(f"Progress: {message}")
|
|
|
|
def update_icon(self, status):
|
|
if self.icon_callback:
|
|
self.icon_callback(status)
|
|
|
|
def debug_log(self, message):
|
|
if self.debug_callback:
|
|
self.debug_callback(message)
|
|
print(message)
|
|
|
|
|
|
# ----------------------------
|
|
# Uninstallation Manager Class (korrigiert)
|
|
# ----------------------------
|
|
class UninstallationManager:
|
|
def __init__(self, app_manager, progress_callback=None, debug_callback=None):
|
|
self.app_manager = app_manager
|
|
self.progress_callback = progress_callback
|
|
self.debug_callback = debug_callback
|
|
|
|
def uninstall_project(self, project_key):
|
|
"""Uninstall any project generically"""
|
|
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"Starting {project_info['name']} uninstallation...")
|
|
|
|
try:
|
|
# Create uninstallation script
|
|
script_path = self._create_uninstall_script(project_key, project_info)
|
|
|
|
# Execute uninstallation
|
|
self.update_progress("Executing uninstallation...")
|
|
self._execute_uninstall_script(script_path)
|
|
|
|
# Remove user config directories
|
|
self._cleanup_user_files(project_key)
|
|
|
|
self.update_progress(f"{project_info['name']} uninstalled successfully!")
|
|
return True
|
|
|
|
except Exception as e:
|
|
self.update_progress(f"Error during uninstallation: {str(e)}")
|
|
raise
|
|
|
|
def _create_uninstall_script(self, project_key, project_info):
|
|
"""Create uninstallation script"""
|
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".sh", delete=False) as f:
|
|
|
|
if project_key == "wirepy":
|
|
script_content = f"""#!/bin/bash
|
|
set -e
|
|
|
|
echo "=== {project_info['name']} Uninstallation Script ==="
|
|
|
|
# Remove Wire-Py files
|
|
rm -f /usr/local/bin/wirepy.py
|
|
rm -f /usr/local/bin/start_wg.py
|
|
rm -f /usr/local/bin/ssl_encrypt.py
|
|
rm -f /usr/local/bin/ssl_decrypt.py
|
|
rm -f /usr/local/bin/match_found.py
|
|
rm -f /usr/local/bin/tunnel.py
|
|
rm -f /usr/local/bin/wirepy
|
|
|
|
# Remove config
|
|
rm -f /usr/lib/python3/dist-packages/shared_libs/wp_app_config.py
|
|
|
|
# Remove desktop file
|
|
rm -f /usr/share/applications/Wire-Py.desktop
|
|
|
|
# Remove policy file
|
|
rm -f /usr/share/polkit-1/actions/org.sslcrypt.policy
|
|
|
|
# Remove language files
|
|
rm -f /usr/share/locale/de/LC_MESSAGES/wirepy.mo
|
|
|
|
# Check if other projects are still installed
|
|
OTHER_PROJECTS_INSTALLED=false
|
|
if [ -f "/usr/local/bin/logviewer" ]; then
|
|
OTHER_PROJECTS_INSTALLED=true
|
|
fi
|
|
|
|
# Remove shared resources only if no other projects
|
|
if [ "$OTHER_PROJECTS_INSTALLED" = false ]; then
|
|
echo "No other LX projects found, removing shared resources..."
|
|
rm -rf /usr/share/icons/lx-icons
|
|
rm -rf /usr/local/etc/ssl
|
|
rmdir /usr/lib/python3/dist-packages/shared_libs 2>/dev/null || true
|
|
fi
|
|
|
|
echo "=== {project_info['name']} uninstallation completed ==="
|
|
"""
|
|
|
|
elif project_key == "logviewer":
|
|
script_content = f"""#!/bin/bash
|
|
set -e
|
|
|
|
echo "=== {project_info['name']} Uninstallation Script ==="
|
|
|
|
# Remove LogViewer files
|
|
rm -f /usr/local/bin/logviewer.py
|
|
rm -f /usr/local/bin/logviewer
|
|
|
|
# Remove config
|
|
rm -f /usr/lib/python3/dist-packages/shared_libs/logview_app_config.py
|
|
|
|
# Remove desktop file
|
|
rm -f /usr/share/applications/LogViewer.desktop
|
|
|
|
# Remove language files
|
|
rm -f /usr/share/locale/de/LC_MESSAGES/logviewer.mo
|
|
|
|
# Check if other projects are still installed
|
|
OTHER_PROJECTS_INSTALLED=false
|
|
if [ -f "/usr/local/bin/wirepy" ]; then
|
|
OTHER_PROJECTS_INSTALLED=true
|
|
fi
|
|
|
|
# Remove shared resources only if no other projects
|
|
if [ "$OTHER_PROJECTS_INSTALLED" = false ]; then
|
|
echo "No other LX projects found, removing shared resources..."
|
|
rm -rf /usr/share/icons/lx-icons
|
|
rmdir /usr/lib/python3/dist-packages/shared_libs 2>/dev/null || true
|
|
fi
|
|
|
|
echo "=== {project_info['name']} uninstallation completed ==="
|
|
"""
|
|
|
|
else:
|
|
# Generisches Uninstall-Script
|
|
script_content = f"""#!/bin/bash
|
|
set -e
|
|
|
|
echo "=== {project_info['name']} Uninstallation Script ==="
|
|
|
|
# Remove project files
|
|
rm -f /usr/local/bin/{project_key}.py
|
|
rm -f /usr/local/bin/{project_key}
|
|
|
|
# Remove config
|
|
rm -f /usr/lib/python3/dist-packages/shared_libs/{project_key}_app_config.py
|
|
|
|
# Remove desktop file
|
|
rm -f /usr/share/applications/{project_info['name']}.desktop
|
|
|
|
echo "=== {project_info['name']} uninstallation completed ==="
|
|
"""
|
|
|
|
f.write(script_content)
|
|
script_path = f.name
|
|
|
|
# Make script executable
|
|
os.chmod(script_path, 0o755)
|
|
self.debug_log(f"Created uninstall script: {script_path}")
|
|
return script_path
|
|
|
|
def _execute_uninstall_script(self, script_path):
|
|
"""Execute uninstallation script with pkexec"""
|
|
try:
|
|
result = subprocess.run(
|
|
["pkexec", "bash", script_path],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=60,
|
|
)
|
|
|
|
if result.returncode != 0:
|
|
error_msg = f"Uninstallation script failed: {result.stderr}"
|
|
self.debug_log(f"ERROR: {error_msg}")
|
|
self.debug_log(f"STDOUT: {result.stdout}")
|
|
raise Exception(error_msg)
|
|
|
|
self.debug_log("Uninstallation script output:")
|
|
self.debug_log(result.stdout)
|
|
|
|
except subprocess.TimeoutExpired:
|
|
raise Exception("Uninstallation timed out")
|
|
except subprocess.CalledProcessError as e:
|
|
raise Exception(f"Uninstallation script failed: {e}")
|
|
|
|
def _cleanup_user_files(self, project_key):
|
|
"""Clean up user configuration files"""
|
|
try:
|
|
if project_key == "wirepy":
|
|
config_dir = os.path.expanduser("~/.config/wire_py")
|
|
log_file = os.path.expanduser("~/.local/share/lxlogs/wirepy.log")
|
|
elif project_key == "logviewer":
|
|
config_dir = os.path.expanduser("~/.config/logviewer")
|
|
log_file = os.path.expanduser("~/.local/share/lxlogs/logviewer.log")
|
|
else:
|
|
config_dir = os.path.expanduser(f"~/.config/{project_key}")
|
|
log_file = os.path.expanduser(
|
|
f"~/.local/share/lxlogs/{project_key}.log"
|
|
)
|
|
|
|
# Remove user config directory
|
|
if os.path.exists(config_dir):
|
|
shutil.rmtree(config_dir)
|
|
self.debug_log(f"Removed user config: {config_dir}")
|
|
|
|
# Remove log file
|
|
if os.path.exists(log_file):
|
|
os.remove(log_file)
|
|
self.debug_log(f"Removed log file: {log_file}")
|
|
|
|
except Exception as e:
|
|
self.debug_log(f"Warning: Could not clean up user files: {e}")
|
|
|
|
def update_progress(self, message):
|
|
if self.progress_callback:
|
|
self.progress_callback(message)
|
|
self.debug_log(f"Progress: {message}")
|
|
|
|
def debug_log(self, message):
|
|
if self.debug_callback:
|
|
self.debug_callback(message)
|
|
print(message)
|
|
|
|
|
|
# ----------------------------
|
|
# Main GUI Application Class (korrigiert)
|
|
# ----------------------------
|
|
class LXToolsGUI:
|
|
def __init__(self):
|
|
self.root = None
|
|
self.progress_label = None
|
|
self.download_icon_label = None
|
|
self.project_var = None
|
|
self.status_labels = {}
|
|
self.version_labels = {}
|
|
self.debug_text = None
|
|
self.show_debug = False
|
|
|
|
# Initialize managers
|
|
self.app_manager = AppManager()
|
|
self.installation_manager = InstallationManager(
|
|
self.app_manager,
|
|
self.update_progress,
|
|
self.update_download_icon,
|
|
self.debug_log,
|
|
)
|
|
self.uninstallation_manager = UninstallationManager(
|
|
self.app_manager, self.update_progress, self.debug_log
|
|
)
|
|
self.image_manager = ImageManager()
|
|
|
|
# Detect OS
|
|
self.detected_os = OSDetector.detect_os()
|
|
|
|
def create_gui(self):
|
|
"""Create the main GUI"""
|
|
self.root = tk.Tk()
|
|
self.root.title(f"{LXToolsAppConfig.APP_NAME} v{LXToolsAppConfig.VERSION}")
|
|
|
|
# Set window size
|
|
window_height = (
|
|
LXToolsAppConfig.DEBUG_WINDOW_HEIGHT
|
|
if self.show_debug
|
|
else LXToolsAppConfig.WINDOW_HEIGHT
|
|
)
|
|
self.root.geometry(f"{LXToolsAppConfig.WINDOW_WIDTH}x{window_height}")
|
|
|
|
# Apply theme
|
|
ThemeManager.apply_light_theme(self.root)
|
|
|
|
# Configure main grid
|
|
self.root.grid_columnconfigure(0, weight=1)
|
|
self.root.grid_rowconfigure(0, weight=0) # Header
|
|
self.root.grid_rowconfigure(1, weight=0) # OS Info
|
|
self.root.grid_rowconfigure(2, weight=1) # Projects
|
|
self.root.grid_rowconfigure(3, weight=0) # Progress
|
|
self.root.grid_rowconfigure(4, weight=0) # Buttons
|
|
if self.show_debug:
|
|
self.root.grid_rowconfigure(5, weight=1) # Debug
|
|
|
|
# Create GUI sections
|
|
self._create_header_section()
|
|
self._create_os_info_section()
|
|
self._create_projects_section()
|
|
self._create_progress_section()
|
|
self._create_buttons_section()
|
|
|
|
if self.show_debug:
|
|
self._create_debug_section()
|
|
|
|
# Load app icon
|
|
self._load_app_icon()
|
|
|
|
# Center window
|
|
if USE_LXTOOLS:
|
|
center_window_cross_platform(self.root)
|
|
else:
|
|
IntegratedLxTools.center_window_cross_platform(self.root)
|
|
|
|
# Initial status refresh
|
|
self.root.after(100, self.refresh_status)
|
|
|
|
return self.root
|
|
|
|
def _create_header_section(self):
|
|
"""Create header section with title and version"""
|
|
header_frame = ttk.Frame(self.root, padding=15)
|
|
header_frame.grid(row=0, column=0, sticky="ew", padx=10, pady=(10, 0))
|
|
header_frame.grid_columnconfigure(0, weight=1)
|
|
|
|
# Title
|
|
title_label = tk.Label(
|
|
header_frame,
|
|
text=f"🚀 {LXToolsAppConfig.APP_NAME}",
|
|
font=("Helvetica", 16, "bold"),
|
|
fg="#2E86AB",
|
|
)
|
|
title_label.grid(row=0, column=0, sticky="w")
|
|
|
|
# Version
|
|
version_label = tk.Label(
|
|
header_frame,
|
|
text=f"Version {LXToolsAppConfig.VERSION}",
|
|
font=("Helvetica", 10),
|
|
fg="gray",
|
|
)
|
|
version_label.grid(row=1, column=0, sticky="w")
|
|
|
|
def _create_os_info_section(self):
|
|
"""Create OS information section"""
|
|
os_frame = ttk.LabelFrame(self.root, text="System Information", padding=10)
|
|
os_frame.grid(row=1, column=0, sticky="ew", padx=15, pady=10)
|
|
os_frame.grid_columnconfigure(1, weight=1)
|
|
|
|
# OS Detection
|
|
tk.Label(os_frame, text="Detected OS:", font=("Helvetica", 10, "bold")).grid(
|
|
row=0, column=0, sticky="w", padx=(0, 10)
|
|
)
|
|
tk.Label(os_frame, text=self.detected_os, font=("Helvetica", 10)).grid(
|
|
row=0, column=1, sticky="w"
|
|
)
|
|
|
|
# Working Directory
|
|
tk.Label(os_frame, text="Working Dir:", font=("Helvetica", 10, "bold")).grid(
|
|
row=1, column=0, sticky="w", padx=(0, 10)
|
|
)
|
|
tk.Label(os_frame, text=LXToolsAppConfig.WORK_DIR, font=("Helvetica", 9)).grid(
|
|
row=1, column=1, sticky="w"
|
|
)
|
|
|
|
def _create_projects_section(self):
|
|
"""Create projects selection and status section"""
|
|
projects_frame = ttk.LabelFrame(
|
|
self.root, text="Available Projects", padding=10
|
|
)
|
|
projects_frame.grid(row=2, column=0, sticky="nsew", padx=15, pady=(0, 10))
|
|
projects_frame.grid_columnconfigure(0, weight=1)
|
|
projects_frame.grid_rowconfigure(1, weight=1)
|
|
|
|
# Project selection
|
|
selection_frame = ttk.Frame(projects_frame)
|
|
selection_frame.grid(row=0, column=0, sticky="ew", pady=(0, 10))
|
|
selection_frame.grid_columnconfigure(1, weight=1)
|
|
|
|
tk.Label(
|
|
selection_frame, text="Select Project:", font=("Helvetica", 10, "bold")
|
|
).grid(row=0, column=0, sticky="w", padx=(0, 10))
|
|
|
|
self.project_var = tk.StringVar()
|
|
project_combo = ttk.Combobox(
|
|
selection_frame,
|
|
textvariable=self.project_var,
|
|
values=list(self.app_manager.get_all_projects().keys()),
|
|
state="readonly",
|
|
width=20,
|
|
)
|
|
project_combo.grid(row=0, column=1, sticky="w")
|
|
project_combo.bind("<<ComboboxSelected>>", self._on_project_selected)
|
|
|
|
# Projects status frame with scrollbar
|
|
status_container = ttk.Frame(projects_frame)
|
|
status_container.grid(row=1, column=0, sticky="nsew")
|
|
status_container.grid_columnconfigure(0, weight=1)
|
|
status_container.grid_rowconfigure(0, weight=1)
|
|
|
|
# Canvas for scrolling
|
|
canvas = tk.Canvas(status_container, height=200)
|
|
scrollbar = ttk.Scrollbar(
|
|
status_container, orient="vertical", command=canvas.yview
|
|
)
|
|
scrollable_frame = ttk.Frame(canvas)
|
|
|
|
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.grid(row=0, column=0, sticky="nsew")
|
|
scrollbar.grid(row=0, column=1, sticky="ns")
|
|
|
|
# Project status entries
|
|
scrollable_frame.grid_columnconfigure(1, weight=1)
|
|
|
|
row = 0
|
|
for project_key, project_info in self.app_manager.get_all_projects().items():
|
|
# Project icon and name
|
|
project_frame = ttk.Frame(scrollable_frame, padding=5)
|
|
project_frame.grid(row=row, column=0, columnspan=3, sticky="ew", pady=2)
|
|
project_frame.grid_columnconfigure(1, weight=1)
|
|
|
|
# Try to load project icon
|
|
icon = self.image_manager.load_image(project_info["icon_key"])
|
|
if icon:
|
|
icon_label = tk.Label(project_frame, image=icon)
|
|
icon_label.image = icon # Keep reference
|
|
icon_label.grid(row=0, column=0, rowspan=2, padx=(0, 10))
|
|
else:
|
|
# Fallback emoji
|
|
emoji_map = {"wirepy": "🔐", "logviewer": "📋"}
|
|
emoji = emoji_map.get(project_key, "📦")
|
|
tk.Label(project_frame, text=emoji, font=("Helvetica", 16)).grid(
|
|
row=0, column=0, rowspan=2, padx=(0, 10)
|
|
)
|
|
|
|
# Project name and description
|
|
tk.Label(
|
|
project_frame,
|
|
text=f"{project_info['name']} - {project_info['description']}",
|
|
font=("Helvetica", 11, "bold"),
|
|
).grid(row=0, column=1, sticky="w")
|
|
|
|
# Status label
|
|
status_label = tk.Label(
|
|
project_frame, text="Checking...", font=("Helvetica", 9)
|
|
)
|
|
status_label.grid(row=1, column=1, sticky="w")
|
|
self.status_labels[project_key] = status_label
|
|
|
|
# Version label
|
|
version_label = tk.Label(project_frame, text="", font=("Helvetica", 9))
|
|
version_label.grid(row=2, column=1, sticky="w")
|
|
self.version_labels[project_key] = version_label
|
|
|
|
row += 1
|
|
|
|
def _create_progress_section(self):
|
|
"""Create progress section with download icon"""
|
|
progress_frame = ttk.LabelFrame(self.root, text="Progress", padding=10)
|
|
progress_frame.grid(row=3, column=0, sticky="ew", padx=15, pady=(0, 10))
|
|
progress_frame.grid_columnconfigure(1, weight=1)
|
|
|
|
# Download icon
|
|
self.download_icon_label = tk.Label(progress_frame, text="", width=4)
|
|
self.download_icon_label.grid(row=0, column=0, padx=(0, 10))
|
|
|
|
# Progress text
|
|
self.progress_label = tk.Label(
|
|
progress_frame,
|
|
text="Ready for installation...",
|
|
font=("Helvetica", 10),
|
|
fg="blue",
|
|
anchor="w",
|
|
)
|
|
self.progress_label.grid(row=0, column=1, sticky="ew")
|
|
|
|
# Reset download icon
|
|
self._reset_download_icon()
|
|
|
|
def _create_buttons_section(self):
|
|
"""Create buttons section"""
|
|
buttons_frame = ttk.Frame(self.root, padding=10)
|
|
buttons_frame.grid(row=4, column=0, sticky="ew", padx=15, pady=(0, 10))
|
|
buttons_frame.grid_columnconfigure(0, weight=1)
|
|
|
|
# Button container
|
|
btn_container = ttk.Frame(buttons_frame)
|
|
btn_container.grid(row=0, column=0)
|
|
|
|
# Install/Update button
|
|
install_btn = ttk.Button(
|
|
btn_container,
|
|
text="📥 Install/Update",
|
|
command=self.install_project,
|
|
width=15,
|
|
)
|
|
install_btn.grid(row=0, column=0, padx=(0, 10))
|
|
|
|
# Uninstall button
|
|
uninstall_btn = ttk.Button(
|
|
btn_container, text="🗑️ Uninstall", command=self.uninstall_project, width=15
|
|
)
|
|
uninstall_btn.grid(row=0, column=1, padx=(0, 10))
|
|
|
|
# Refresh button
|
|
refresh_btn = ttk.Button(
|
|
btn_container, text="Refresh", command=self.refresh_status, width=15
|
|
)
|
|
refresh_btn.grid(row=0, column=2, padx=(0, 10))
|
|
|
|
# Debug toggle button
|
|
debug_btn = ttk.Button(
|
|
btn_container, text="Debug", command=self.toggle_debug, width=15
|
|
)
|
|
debug_btn.grid(row=0, column=3)
|
|
|
|
def _create_debug_section(self):
|
|
"""Create debug section"""
|
|
debug_frame = ttk.LabelFrame(self.root, text="Debug Output", padding=10)
|
|
debug_frame.grid(row=5, column=0, sticky="nsew", padx=15, pady=(0, 10))
|
|
debug_frame.grid_columnconfigure(0, weight=1)
|
|
debug_frame.grid_rowconfigure(0, weight=1)
|
|
|
|
# Debug text with scrollbar
|
|
debug_container = ttk.Frame(debug_frame)
|
|
debug_container.grid(row=0, column=0, sticky="nsew")
|
|
debug_container.grid_columnconfigure(0, weight=1)
|
|
debug_container.grid_rowconfigure(0, weight=1)
|
|
|
|
self.debug_text = tk.Text(
|
|
debug_container, height=8, font=("Courier", 9), bg="#f8f8f8", fg="#333333"
|
|
)
|
|
self.debug_text.grid(row=0, column=0, sticky="nsew")
|
|
|
|
# Scrollbar for debug text
|
|
debug_scrollbar = ttk.Scrollbar(
|
|
debug_container, orient="vertical", command=self.debug_text.yview
|
|
)
|
|
debug_scrollbar.grid(row=0, column=1, sticky="ns")
|
|
self.debug_text.configure(yscrollcommand=debug_scrollbar.set)
|
|
|
|
# Clear debug button
|
|
clear_debug_btn = ttk.Button(
|
|
debug_frame, text="Clear Debug", command=self.clear_debug
|
|
)
|
|
clear_debug_btn.grid(row=1, column=0, sticky="e", pady=(5, 0))
|
|
|
|
def _load_app_icon(self):
|
|
"""Load application icon"""
|
|
try:
|
|
icon_path = LXToolsAppConfig.get_icon_path("app_icon")
|
|
if icon_path and os.path.exists(icon_path):
|
|
icon = tk.PhotoImage(file=icon_path)
|
|
self.root.iconphoto(False, icon)
|
|
print(f"App icon loaded: {icon_path}")
|
|
else:
|
|
print("App icon not found, using default")
|
|
except Exception as e:
|
|
print(f"Failed to load app icon: {e}")
|
|
|
|
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:
|
|
# Fallback to emoji
|
|
self.download_icon_label.config(text="📥", font=("Helvetica", 16))
|
|
|
|
def _on_project_selected(self, event=None):
|
|
"""Handle project selection change"""
|
|
selected = self.project_var.get()
|
|
if selected:
|
|
self.update_progress(f"Selected: {selected}")
|
|
|
|
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 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 debug_log(self, message):
|
|
"""Add message to debug log"""
|
|
if self.debug_text:
|
|
self.debug_text.insert(tk.END, f"{message}\n")
|
|
self.debug_text.see(tk.END)
|
|
self.debug_text.update()
|
|
print(f"Debug: {message}")
|
|
|
|
def clear_debug(self):
|
|
"""Clear debug text"""
|
|
if self.debug_text:
|
|
self.debug_text.delete(1.0, tk.END)
|
|
|
|
def toggle_debug(self):
|
|
"""Toggle debug window visibility"""
|
|
self.show_debug = not self.show_debug
|
|
|
|
# Recreate window with new size
|
|
if self.root:
|
|
self.root.destroy()
|
|
|
|
# Create new window
|
|
self.create_gui()
|
|
self.root.mainloop()
|
|
|
|
def refresh_status(self):
|
|
"""Refresh application status and version information"""
|
|
self.update_progress("Refreshing status and checking versions...")
|
|
self._reset_download_icon()
|
|
|
|
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]
|
|
|
|
if self.app_manager.is_installed(project_key):
|
|
installed_version = self.app_manager.get_installed_version(project_key)
|
|
status_label.config(
|
|
text=f"✅ Installed (v{installed_version})", fg="green"
|
|
)
|
|
|
|
# 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",
|
|
)
|
|
else:
|
|
version_label.config(
|
|
text=f"Latest: v{latest_version} (Up to date)",
|
|
fg="green",
|
|
)
|
|
else:
|
|
version_label.config(text=f"Latest: Unknown", fg="gray")
|
|
except Exception as e:
|
|
version_label.config(text=f"Latest: Check failed", fg="gray")
|
|
self.debug_log(f"Version check failed for {project_key}: {e}")
|
|
else:
|
|
status_label.config(text=f"❌ Not installed", fg="red")
|
|
|
|
# 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"
|
|
)
|
|
else:
|
|
version_label.config(text=f"Available: Unknown", fg="gray")
|
|
except Exception as e:
|
|
version_label.config(text=f"Available: Check failed", fg="gray")
|
|
self.debug_log(f"Version check failed for {project_key}: {e}")
|
|
|
|
self.update_progress("Status refresh completed.")
|
|
|
|
def install_project(self):
|
|
"""Handle install button click"""
|
|
selected_project = self.project_var.get()
|
|
if not selected_project:
|
|
messagebox.showwarning("Warning", "Please select a project to install.")
|
|
return
|
|
|
|
# Check internet connection
|
|
if not NetworkChecker.check_internet():
|
|
self.update_download_icon("error")
|
|
messagebox.showerror(
|
|
"Network Error",
|
|
"No internet connection available.\nPlease check your network connection.",
|
|
)
|
|
return
|
|
|
|
if not NetworkChecker.check_repository():
|
|
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(selected_project)
|
|
|
|
# Check if already installed
|
|
if self.app_manager.is_installed(selected_project):
|
|
installed_version = self.app_manager.get_installed_version(selected_project)
|
|
latest_version = self.app_manager.get_latest_version(selected_project)
|
|
|
|
dialog_text = (
|
|
f"{project_info['name']} is already installed.\n\n"
|
|
f"Installed version: v{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_project(selected_project)
|
|
return
|
|
else: # Update
|
|
self.update_progress("Updating application...")
|
|
|
|
try:
|
|
self.installation_manager.install_project(selected_project)
|
|
|
|
# Success message
|
|
if USE_LXTOOLS:
|
|
LxTools.msg_window(
|
|
self.root,
|
|
"Success",
|
|
f"{project_info['name']} has been successfully installed/updated.",
|
|
"info",
|
|
)
|
|
else:
|
|
messagebox.showinfo(
|
|
"Success",
|
|
f"{project_info['name']} has been successfully installed/updated.",
|
|
)
|
|
|
|
self.refresh_status()
|
|
|
|
except Exception as e:
|
|
# Show error icon
|
|
self.update_download_icon("error")
|
|
|
|
error_msg = f"Installation failed: {e}"
|
|
self.debug_log(f"ERROR: {error_msg}")
|
|
|
|
if USE_LXTOOLS:
|
|
LxTools.msg_window(
|
|
self.root, "Error", error_msg, "error", width=500, height=300
|
|
)
|
|
else:
|
|
messagebox.showerror("Error", error_msg)
|
|
|
|
def uninstall_project(self, project_key=None):
|
|
"""Handle uninstall button click"""
|
|
if project_key is None:
|
|
project_key = self.project_var.get()
|
|
|
|
if not project_key:
|
|
messagebox.showwarning("Warning", "Please select a project to uninstall.")
|
|
return
|
|
|
|
project_info = self.app_manager.get_project_info(project_key)
|
|
|
|
if not self.app_manager.is_installed(project_key):
|
|
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(project_key)
|
|
|
|
# Success message
|
|
if USE_LXTOOLS:
|
|
LxTools.msg_window(
|
|
self.root,
|
|
"Success",
|
|
f"{project_info['name']} has been successfully uninstalled.",
|
|
"info",
|
|
)
|
|
else:
|
|
messagebox.showinfo(
|
|
"Success",
|
|
f"{project_info['name']} has been successfully uninstalled.",
|
|
)
|
|
|
|
self.refresh_status()
|
|
|
|
except Exception as e:
|
|
error_msg = f"Uninstallation failed: {e}"
|
|
self.debug_log(f"ERROR: {error_msg}")
|
|
|
|
if USE_LXTOOLS:
|
|
LxTools.msg_window(
|
|
self.root, "Error", error_msg, "error", width=500, height=300
|
|
)
|
|
else:
|
|
messagebox.showerror("Error", error_msg)
|
|
|
|
def run(self):
|
|
"""Start the GUI application"""
|
|
try:
|
|
print(f"Starting {LXToolsAppConfig.APP_NAME} v{LXToolsAppConfig.VERSION}")
|
|
print(f"Working directory: {LXToolsAppConfig.WORK_DIR}")
|
|
print(f"Icons directory: {LXToolsAppConfig.ICONS_DIR}")
|
|
print(f"Using LxTools: {USE_LXTOOLS}")
|
|
|
|
root = self.create_gui()
|
|
root.mainloop()
|
|
|
|
except KeyboardInterrupt:
|
|
print("\nApplication interrupted by user.")
|
|
except Exception as e:
|
|
print(f"Fatal error: {e}")
|
|
if self.root:
|
|
messagebox.showerror("Fatal Error", f"Application failed to start: {e}")
|
|
|
|
|
|
# ----------------------------
|
|
# Main Application Entry Point
|
|
# ----------------------------
|
|
def main():
|
|
"""Main function to start the application"""
|
|
try:
|
|
# Check if running as root (not recommended)
|
|
if os.geteuid() == 0:
|
|
print("Warning: Running as root is not recommended!")
|
|
print("The installer will use pkexec for privilege escalation when needed.")
|
|
|
|
# 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:
|
|
import tkinter.messagebox as mb
|
|
|
|
mb.showerror("Fatal Error", f"Application failed to start: {e}")
|
|
except:
|
|
pass # If even tkinter fails, just print
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|