1324 lines
44 KiB
Python
Executable File
1324 lines
44 KiB
Python
Executable File
#!/usr/bin/python3
|
|
import tkinter as tk
|
|
from tkinter import ttk
|
|
import os
|
|
import subprocess
|
|
from datetime import datetime
|
|
import tempfile
|
|
import urllib.request
|
|
import zipfile
|
|
from manager import (
|
|
OSDetector,
|
|
Theme,
|
|
LXToolsAppConfig,
|
|
System,
|
|
Image,
|
|
AppManager,
|
|
LxTools,
|
|
)
|
|
from network import NetworkChecker
|
|
from message import MessageDialog
|
|
|
|
# Initialize translations
|
|
_ = LXToolsAppConfig.setup_translations()
|
|
|
|
|
|
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 = System()
|
|
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/local/share/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 -f "$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 -f "$WIREPY_DIR/wp_app_config.py" /usr/local/share/shared_libs/
|
|
echo "Installed wp_app_config.py"
|
|
fi
|
|
|
|
# Install shared libraries
|
|
echo "Installing shared libraries..."
|
|
for file in common_tools.py message.py file_and_dir_ensure.py gitea.py __init__.py logview_app_config.py; do
|
|
if [ -f "$SHARED_DIR/$file" ]; then
|
|
cp -f "$SHARED_DIR/$file" /usr/local/share/shared_libs/
|
|
echo "Installed shared lib: $file"
|
|
fi
|
|
done
|
|
|
|
# Install LogViewer executable
|
|
if [ -f "$SHARED_DIR/logviewer.py" ]; then
|
|
cp -f "$SHARED_DIR/logviewer.py" /usr/local/share/shared_libs/
|
|
chmod 755 /usr/local/share/shared_libs/logviewer.py
|
|
echo "Installed logviewer.py (executable)"
|
|
fi
|
|
|
|
# Install icons
|
|
if [ -d "$WIREPY_DIR/lx-icons" ]; then
|
|
echo "Installing icons..."
|
|
cp -rf "$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 -rf "$WIREPY_DIR/TK-Themes"/* /usr/share/TK-Themes/
|
|
fi
|
|
|
|
# Install desktop file
|
|
if [ -f "$WIREPY_DIR/Wire-Py.desktop" ]; then
|
|
cp -f "$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 -f "$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 -f "$WIREPY_DIR/org.sslcrypt.policy" /usr/share/polkit-1/actions/
|
|
echo "Installed policy file"
|
|
fi
|
|
|
|
# Create symlink for Wirepy
|
|
ln -sf /usr/local/bin/wirepy.py /usr/local/bin/wirepy
|
|
# Create symlink for LogViewer
|
|
ln -sf /usr/local/share/shared_libs/logviewer.py /usr/local/bin/logviewer
|
|
echo "Created Wirepy and 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
|
|
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
|
|
|
|
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/local/share/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 -rf "$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 -rf "$WIREPY_DIR/lx-icons"/* /usr/share/icons/lx-icons/
|
|
fi
|
|
fi
|
|
|
|
# Install shared libraries
|
|
echo "Installing shared libraries..."
|
|
for file in common_tools.py message.py file_and_dir_ensure.py gitea.py __init__.py logview_app_config.py; do
|
|
if [ -f "$SHARED_DIR/$file" ]; then
|
|
cp -f "$SHARED_DIR/$file" /usr/local/share/shared_libs/
|
|
echo "Installed shared lib: $file"
|
|
fi
|
|
done
|
|
|
|
# Install LogViewer executable
|
|
if [ -f "$SHARED_DIR/logviewer.py" ]; then
|
|
cp -f "$SHARED_DIR/logviewer.py" /usr/local/share/shared_libs/
|
|
chmod 755 /usr/local/share/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/local/share/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)
|
|
|
|
|
|
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/local/share/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/local/share/shared_libs/$file
|
|
done
|
|
|
|
# Try to remove shared_libs directory if empty
|
|
rmdir /usr/local/share/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/local/share/shared_libs/$file
|
|
done
|
|
|
|
# Remove logviewer.py last
|
|
rm -f /usr/local/share/shared_libs/logviewer.py
|
|
|
|
# Try to remove shared_libs directory if empty
|
|
rmdir /usr/local/share/shared_libs 2>/dev/null || true
|
|
else
|
|
echo "Wire-Py still installed, keeping shared resources"
|
|
# Only remove logviewer-specific files
|
|
rm -f /usr/local/share/shared_libs/logview_app_config.py
|
|
rm -f /usr/local/share/shared_libs/logviewer.py
|
|
fi
|
|
|
|
echo "LogViewer uninstallation completed!"
|
|
"""
|
|
return script
|
|
|
|
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)
|
|
|
|
|
|
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
|
|
|
|
|
|
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 = Image()
|
|
|
|
# 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}+100+100"
|
|
)
|
|
LxTools.center_window_cross_platform(
|
|
self.root, LXToolsAppConfig.WINDOW_WIDTH, LXToolsAppConfig.WINDOW_HEIGHT
|
|
)
|
|
self.root.configure(bg=self.colors["bg"])
|
|
|
|
# Try to set icon
|
|
try:
|
|
icon = self.image_manager.load_image("download_icon")
|
|
if icon:
|
|
self.root.iconphoto(False, icon)
|
|
except:
|
|
pass
|
|
self.root.minsize(LXToolsAppConfig.WINDOW_WIDTH, LXToolsAppConfig.WINDOW_HEIGHT)
|
|
Theme.apply_light_theme(self.root)
|
|
# Create header
|
|
self._create_header()
|
|
|
|
# Create notebook (tabs)
|
|
self.notebook = ttk.Notebook(self.root, height=300)
|
|
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)
|
|
|
|
# LEFT SIDE: 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")
|
|
|
|
# Tool-Icon
|
|
tk.Label(
|
|
icon_text_frame, text="🔧", font=("Helvetica", 18), bg="#2c3e50", fg="white"
|
|
).pack(side="left", padx=(0, 8))
|
|
|
|
# App Name and 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",
|
|
pady=4,
|
|
).pack(anchor="w")
|
|
|
|
tk.Label(
|
|
text_frame,
|
|
text=f"v {LXToolsAppConfig.VERSION} • Linux App Installer",
|
|
font=("Helvetica", 9),
|
|
fg="#bdc3c7",
|
|
bg="#2c3e50",
|
|
).pack(anchor="w")
|
|
|
|
# RIGHT SIDE: System + Dynamic 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")
|
|
|
|
# DYNAMIC Status (begin empty)
|
|
self.header_status_label = tk.Label(
|
|
right_side, text="", font=("Helvetica", 10), bg="#2c3e50" # begin empty
|
|
)
|
|
self.header_status_label.pack(anchor="e", pady=(2, 0))
|
|
|
|
# Separator
|
|
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") # Green
|
|
elif not internet_ok:
|
|
self.update_header_status(_("No internet connection"), "#e74c3c") # Red
|
|
elif not repo_ok:
|
|
self.update_header_status(_("Repository unavailable"), "#f39c12") # Orange
|
|
else:
|
|
self.update_header_status(_("System checking..."), "#3498db") # Blue
|
|
|
|
def _create_projects_tab(self):
|
|
"""Create projects tab with project cards"""
|
|
projects_frame = ttk.Frame(self.notebook)
|
|
self.notebook.add(projects_frame, text=_("Applications"))
|
|
|
|
# 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)
|
|
|
|
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']}")
|
|
|
|
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, 0))
|
|
|
|
# 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()
|
|
|
|
def _create_modern_buttons(self):
|
|
"""Create modern styled buttons"""
|
|
button_frame = tk.Frame(self.root, bg=self.colors["bg"])
|
|
button_frame.pack(fill="x", padx=15, pady=(5, 10))
|
|
|
|
# Button style configuration
|
|
style = ttk.Style()
|
|
|
|
# Install button (green)
|
|
style.configure("Install.TButton", foreground="#27ae60", font=("Helvetica", 11))
|
|
style.map(
|
|
"Install.TButton",
|
|
foreground=[("active", "#14542f"), ("pressed", "#1e8449")],
|
|
)
|
|
|
|
# Uninstall button (red)
|
|
style.configure(
|
|
"Uninstall.TButton", foreground="#e74c3c", font=("Helvetica", 11)
|
|
)
|
|
style.map(
|
|
"Uninstall.TButton",
|
|
foreground=[("active", "#7d3b34"), ("pressed", "#c0392b")],
|
|
)
|
|
|
|
# Refresh button (blue)
|
|
style.configure("Refresh.TButton", foreground="#3498db", font=("Helvetica", 11))
|
|
style.map(
|
|
"Refresh.TButton",
|
|
foreground=[("active", "#1e3747"), ("pressed", "#2980b9")],
|
|
)
|
|
|
|
# Create buttons
|
|
install_btn = ttk.Button(
|
|
button_frame,
|
|
text=_("Install/Update"),
|
|
command=self.install_selected,
|
|
style="Install.TButton",
|
|
padding=8,
|
|
)
|
|
install_btn.pack(side="left", padx=(0, 10))
|
|
|
|
uninstall_btn = ttk.Button(
|
|
button_frame,
|
|
text=_("Uninstall"),
|
|
command=self.uninstall_selected,
|
|
style="Uninstall.TButton",
|
|
padding=8,
|
|
)
|
|
uninstall_btn.pack(side="left", padx=(0, 10))
|
|
|
|
refresh_btn = ttk.Button(
|
|
button_frame,
|
|
text=_("Refresh Status"),
|
|
command=self.refresh_status,
|
|
style="Refresh.TButton",
|
|
padding=8,
|
|
)
|
|
refresh_btn.pack(side="right")
|
|
|
|
def update_download_icon(self, status):
|
|
"""Update download icon based on status"""
|
|
if not self.download_icon_label:
|
|
return
|
|
|
|
if status == "downloading":
|
|
icon = self.image_manager.load_image("download_icon")
|
|
if icon:
|
|
self.download_icon_label.config(image=icon, text="")
|
|
self.download_icon_label.image = icon
|
|
else:
|
|
self.download_icon_label.config(text="⬇️", font=("Helvetica", 16))
|
|
|
|
elif status == "error":
|
|
icon = self.image_manager.load_image("download_error_icon")
|
|
if icon:
|
|
self.download_icon_label.config(image=icon, text="")
|
|
self.download_icon_label.image = icon
|
|
else:
|
|
self.download_icon_label.config(text="❌", font=("Helvetica", 16))
|
|
|
|
elif status == "success":
|
|
icon = self.image_manager.load_image("success_icon")
|
|
if icon:
|
|
self.download_icon_label.config(image=icon, text="")
|
|
self.download_icon_label.image = icon
|
|
else:
|
|
self.download_icon_label.config(text="✅", font=("Helvetica", 16))
|
|
|
|
self.download_icon_label.update()
|
|
|
|
def _reset_download_icon(self):
|
|
"""Reset download icon to neutral state"""
|
|
icon = self.image_manager.load_image("download_icon")
|
|
if icon:
|
|
self.download_icon_label.config(image=icon, text="")
|
|
self.download_icon_label.image = icon
|
|
else:
|
|
self.download_icon_label.config(text="📥", font=("Helvetica", 16))
|
|
|
|
def refresh_status(self):
|
|
"""Refresh application status and version information"""
|
|
self.update_progress(_("Refreshing status and checking versions..."))
|
|
self._reset_download_icon()
|
|
self.log_message(_("=== Refreshing Status ==="))
|
|
self.root.focus_set()
|
|
for project_key, project_info in self.app_manager.get_all_projects().items():
|
|
status_label = self.status_labels[project_key]
|
|
version_label = self.version_labels[project_key]
|
|
|
|
self.log_message(_(f"Checking {project_info['name']}..."))
|
|
|
|
if self.app_manager.is_installed(project_key):
|
|
installed_version = self.app_manager.get_installed_version(project_key)
|
|
status_label.config(
|
|
text=_(f"✅ Installed ({installed_version})"), fg="green"
|
|
)
|
|
self.log_message(
|
|
_(f"{project_info['name']}: Installed {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 != f"v. {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()
|
|
|
|
def install_selected(self):
|
|
"""Handle install button click"""
|
|
if not self.selected_project:
|
|
MessageDialog("error", _("Please select a project to install."))
|
|
self.root.focus_set()
|
|
return
|
|
|
|
# Check internet connection
|
|
if not NetworkChecker.check_internet_connection():
|
|
self.update_download_icon("error")
|
|
MessageDialog(
|
|
"error",
|
|
_(
|
|
"Network Error",
|
|
"No internet connection available.\nPlease check your network connection.",
|
|
),
|
|
)
|
|
self.root.focus_set()
|
|
return
|
|
|
|
if not NetworkChecker.check_repository_access():
|
|
self.update_download_icon("error")
|
|
MessageDialog(
|
|
"error",
|
|
_(
|
|
"Repository Error",
|
|
"Cannot access repository.\nPlease try again later.",
|
|
),
|
|
)
|
|
self.root.focus_set()
|
|
return
|
|
|
|
# Reset download icon
|
|
self._reset_download_icon()
|
|
project_info = self.app_manager.get_project_info(self.selected_project)
|
|
|
|
try:
|
|
self.update_download_icon("downloading")
|
|
self.installation_manager.install_project(self.selected_project)
|
|
self.update_download_icon("success")
|
|
MessageDialog(
|
|
"info",
|
|
_(f"{project_info['name']} has been successfully installed/updated."),
|
|
)
|
|
self.refresh_status()
|
|
except Exception as e:
|
|
self.update_download_icon("error")
|
|
MessageDialog("error", _(f"{e}"))
|
|
|
|
self.root.focus_set()
|
|
|
|
def uninstall_selected(self):
|
|
"""Handle uninstall button click"""
|
|
|
|
if not self.selected_project:
|
|
MessageDialog("error", _("Please select a project to uninstall."))
|
|
self.root.focus_set()
|
|
return
|
|
|
|
project_info = self.app_manager.get_project_info(self.selected_project)
|
|
|
|
if not self.app_manager.is_installed(self.selected_project):
|
|
MessageDialog("error", _(f"{project_info['name']} is not installed."))
|
|
self.root.focus_set()
|
|
return
|
|
|
|
try:
|
|
self.uninstallation_manager.uninstall_project(self.selected_project)
|
|
MessageDialog(
|
|
"info",
|
|
_(
|
|
f"{project_info['name']} has been successfully uninstalled.",
|
|
),
|
|
)
|
|
self.refresh_status()
|
|
self.root.focus_set()
|
|
except Exception as e:
|
|
MessageDialog("error", _(f"Uninstallation failed: {e}"))
|
|
self.root.focus_set()
|
|
|
|
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()
|
|
|
|
|
|
def main():
|
|
"""Main function to start the application"""
|
|
print(f"=== {LXToolsAppConfig.APP_NAME} v {LXToolsAppConfig.VERSION} ===")
|
|
print(_(f"Working directory: {os.getcwd()}"))
|
|
|
|
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:
|
|
MessageDialog(
|
|
"error", _("Fatal Error", f"Application failed to start: {e}")
|
|
)
|
|
except:
|
|
pass
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|