Files
lxtools_installer/lxtools_installer.py
2025-07-09 08:49:49 +02:00

1658 lines
59 KiB
Python
Executable File

#!/usr/bin/python3
import tkinter as tk
from tkinter import ttk
import os
import sys
import subprocess
from typing import Optional, List
import hashlib
import getpass
from datetime import datetime
import tempfile
import urllib.request
import zipfile
from manager import (
Detector,
Theme,
LocaleStrings,
LXToolsAppConfig,
System,
Image,
AppManager,
LxTools,
Locale,
)
from network import NetworkChecker, GiteaUpdater, GPGManager
from message import MessageDialog
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 check_gpg(self) -> bool:
if GPGManager.is_key_already_imported(
LXToolsAppConfig.EXPECTED_FINGERPRINT[-8:]
):
return True
all_keys_valid: bool = True
# 1. Check reachability of OpenPGP keyserver and process key if reachable
openpgp_reachable: bool = GPGManager.is_url_reachable(
LXToolsAppConfig.KEY_URL_OPENPGP
)
if openpgp_reachable:
self.log(LocaleStrings.MSGGPG["keyserver_reachable"])
self.update_progress(LocaleStrings.MSGGPG["keyserver_reachable"])
if not GPGManager.import_key_from_url(
key_url=LXToolsAppConfig.KEY_URL_OPENPGP,
expected_fingerprint=LXToolsAppConfig.EXPECTED_FINGERPRINT,
filename="public_key1.asc",
):
all_keys_valid = False
else:
self.log(LocaleStrings.MSGGPG["keyserver_unreachable"])
self.update_progress(LocaleStrings.MSGGPG["keyserver_unreachable"])
# 2. Always download and verify Git.ilunix public key
if not GPGManager.import_key_from_url(
key_url=LXToolsAppConfig.KEY_URL_GITILUNIX,
expected_fingerprint=LXToolsAppConfig.EXPECTED_FINGERPRINT,
filename="public_key2.asc",
):
all_keys_valid = False
# Final step: Proceed only if all fingerprints match or OpenPGP was unreachable
if all_keys_valid:
self.log(LocaleStrings.MSGGPG["all_keys_valid"])
self.update_progress(LocaleStrings.MSGGPG["all_keys_valid"])
key_id: str = LXToolsAppConfig.EXPECTED_FINGERPRINT[-8:]
if not GPGManager.update_gpg_trust_level(key_id, trust_level=5):
self.log(LocaleStrings.MSGGPG["error_updating_trust_level"], key_id)
self.update_progress(
LocaleStrings.MSGGPG["error_updating_trust_level"], key_id
)
return False
else:
self.log(f"{LocaleStrings.MSGGPG['set_trust_level']}{key_id}.")
self.update_progress(
f"{LocaleStrings.MSGGPG['set_trust_level']}{key_id}."
)
return True
else:
self.log(LocaleStrings.MSGGPG["not_all_keys_are_valid"])
self.update_progress(LocaleStrings.MSGGPG["not_all_keys_are_valid"])
def check_appimage(self) -> bool | None | str:
"""
Checks the current version of the lxtools_installer AppImage by:
1. Downloading it from Gitea (with checksum and signature verification).
2. Renaming and making it executable.
3. Comparing its SHA-256 hash with an existing reference file at `/usr/local/bin/lxtools_installer`.
4. Returns `True` if the hashes match, otherwise `False`.
Returns:
bool: True if AppImages are identical; False if they differ or an error occurred.
"""
appimage_path: Optional[str] = GiteaUpdater.download_appimage(
verify_checksum=True, verify_signature=True
)
tests: List[str] = [
"Checksum file not found",
"Signature file not found",
"Checksum mismatch",
"Signature verification failed",
]
if appimage_path is None or appimage_path in tests:
print(f"{LocaleStrings.MSGA['error']}{appimage_path}")
return None
else:
try:
if os.path.isfile(appimage_path):
new_filename = "/tmp/portinstaller/lxtools_installer"
result = subprocess.run(
["mv", appimage_path, new_filename],
capture_output=True,
text=True,
check=False,
)
if result.returncode == 0:
self.update_progress(LocaleStrings.MSGA["appimage_renamed"])
self.log(LocaleStrings.MSGA["appimage_renamed"])
else:
self.update_progress(
f"{LocaleStrings.MSGA['appimage_rename_error']}{result.stderr}"
)
self.log(
f"{LocaleStrings.MSGA['appimage_rename_error']}{result.stderr}"
)
result = subprocess.run(
["chmod", "+x", new_filename],
capture_output=True,
text=True,
check=False,
)
if result.returncode == 0:
self.update_progress(
LocaleStrings.MSGA["appimage_executable_success"]
)
self.log(LocaleStrings.MSGA["appimage_executable_success"])
else:
self.update_progress(
f"{LocaleStrings.MSGA['appimage_executable_error']}{result.stderr}"
)
self.log(
f"{LocaleStrings.MSGA['appimage_executable_error']}{result.stderr}"
)
# Define reference path
reference_appimage_path = "/usr/local/bin/lxtools_installer"
if os.path.isfile(reference_appimage_path):
def calculate_sha256(file_path: str) -> str:
"""
Calculates the SHA-256 hash of a file.
Args:
file_path (str): Path to the file.
Returns:
str: Hexdigest of the SHA-256 hash.
"""
hash_obj = hashlib.sha256()
with open(file_path, "rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
hash_obj.update(chunk)
return hash_obj.hexdigest()
# Calculate hashes
new_checksum = calculate_sha256(new_filename)
reference_checksum = calculate_sha256(reference_appimage_path)
# Compare and decide action
if new_checksum == reference_checksum:
return "True" # String as a compatibility solution to network.py
else:
return False
else:
return False
else:
self.update_progress(LocaleStrings.MSGA["appimage_not_exist"])
self.log(LocaleStrings.MSGA["appimage_not_exist"])
return False
except Exception as e:
self.update_progress(f"Exception occurred: {str(e)}")
self.log(f"Exception occurred: {str(e)}")
return None
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"{LocaleStrings.MSG['unknow_project']}{project_key}")
self.update_progress(
f"{LocaleStrings.MSGI['start_install']}{project_info['name']}..."
)
self.log(f"=== {LocaleStrings.MSGI['install']}{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']}{LocaleStrings.MSGI['install_success']}"
)
self.log(
f"=== {project_info['name']}{LocaleStrings.MSGI['install_success']} ==="
)
# Set success icon
self.update_icon("success")
except Exception as e:
self.log(f"ERROR: {LocaleStrings.MSGI['install_failed']}{e}")
self.update_icon("error")
raise Exception(f"{LocaleStrings.MSGI['install_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"{LocaleStrings.MSGI['unknow_project']}{project_key}")
def _create_wirepy_install_script(self):
gpg_checker = self.check_gpg()
result_appimage = self.check_appimage()
if not gpg_checker:
print(gpg_checker)
return
if result_appimage is True or result_appimage is None:
return
detected_os = Detector.get_os()
if detected_os == "Arch Linux":
result_unzip = Detector.get_unzip()
result_wget = Detector.get_wget()
result_requests = Detector.get_requests()
else:
result_unzip = None
result_wget = None
result_requests = None
"""Create Wire-Py installation script"""
script = f"""#!/bin/bash
set -e
if [ "{result_appimage}" = "False" ]; then
cp -f /tmp/portinstaller/lxtools_installer /usr/local/bin/lxtools_installer
fi
if [ "{detected_os}" = "Arch Linux" ]; then
if [ "{result_unzip}" = "False" ]; then
pacman -S --noconfirm unzip
fi
if [ "{result_wget}" = "False" ]; then
pacman -S --noconfirm wget
fi
if [ "{result_requests}" = "False" ]; then
pacman -S --noconfirm python-requests
fi
fi
{LXToolsAppConfig.TKINTER_INSTALL_COMMANDS[detected_os]} 2>&1 | grep -v "apt does not have a stable CLI interface"
# Create necessary directories
mkdir -p {LXToolsAppConfig.SHARED_LIBS_DESTINATION[detected_os]}
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" {LXToolsAppConfig.SHARED_LIBS_DESTINATION[detected_os]}/
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" {LXToolsAppConfig.SHARED_LIBS_DESTINATION[detected_os]}/
echo "Installed shared lib: $file"
fi
done
# Install LogViewer executable
if [ -f "$SHARED_DIR/logviewer.py" ]; then
cp -f "$SHARED_DIR/logviewer.py" {LXToolsAppConfig.SHARED_LIBS_DESTINATION[detected_os]}/
chmod 755 {LXToolsAppConfig.SHARED_LIBS_DESTINATION[detected_os]}/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 {LXToolsAppConfig.SHARED_LIBS_DESTINATION[detected_os]}/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 portinstaller
echo "Wire-Py installation completed!"
"""
return script
def _create_logviewer_install_script(self):
gpg_checker = self.check_gpg()
result_appimage = self.check_appimage()
if not gpg_checker:
print(gpg_checker)
return
if result_appimage is True or result_appimage is None:
return
detected_os = Detector.get_os()
if detected_os == "Arch Linux":
result_unzip = Detector.get_unzip()
result_wget = Detector.get_wget()
result_requests = Detector.get_requests()
else:
result_unzip = None
result_wget = None
result_requests = None
"""Create LogViewer installation script"""
script = f"""#!/bin/bash
set -e
{LXToolsAppConfig.TKINTER_INSTALL_COMMANDS[detected_os]} 2>&1 | grep -v "apt does not have a stable CLI interface"
if [ "{result_appimage}" = "False" ]; then
cp -f /tmp/portinstaller/lxtools_installer /usr/local/bin/lxtools_installer
fi
if [ "{detected_os}" = "Arch Linux" ]; then
if [ "{result_unzip}" = "False" ]; then
pacman -S --noconfirm unzip
fi
if [ "{result_wget}" = "False" ]; then
pacman -S --noconfirm wget
fi
if [ "{result_requests}" = "False" ]; then
pacman -S --noconfirm python-requests
fi
fi
# Create necessary directories
mkdir -p {LXToolsAppConfig.SHARED_LIBS_DESTINATION[detected_os]}
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" {LXToolsAppConfig.SHARED_LIBS_DESTINATION[detected_os]}/
echo "Installed shared lib: $file"
fi
done
# Install LogViewer executable
if [ -f "$SHARED_DIR/logviewer.py" ]; then
cp -f "$SHARED_DIR/logviewer.py" {LXToolsAppConfig.SHARED_LIBS_DESTINATION[detected_os]}/
chmod 755 {LXToolsAppConfig.SHARED_LIBS_DESTINATION[detected_os]}/logviewer.py
echo "Installed logviewer.py (executable)"
fi
# Create symlink for LogViewer
ln -sf {LXToolsAppConfig.SHARED_LIBS_DESTINATION[detected_os]}/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 portinstaller
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"{LocaleStrings.MSGI['install_create']}{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((result.stdout).strip("Log\n"))
if result.stderr:
self.log(f"STDERR: {result.stderr}")
# Clean up
os.unlink(script_file.name)
if result.returncode != 0:
raise Exception(
f"{LocaleStrings.MSGI['install_script_failed']}{result.stderr}"
)
except subprocess.TimeoutExpired:
raise Exception(LocaleStrings.MSGI["install_timeout"])
except subprocess.CalledProcessError as e:
raise Exception(f"{LocaleStrings.MSGI['install_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"{LocaleStrings.MSGO['unknow_project']}{project_key}")
if not self.app_manager.is_installed(project_key):
raise Exception(
f"{project_info['name']}{LocaleStrings.MSGO['not_installed']}"
)
self.update_progress(
f"{LocaleStrings.MSGU['uninstall']}{project_info['name']}..."
)
self.log(f"=== {LocaleStrings.MSGU['uninstall']}{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']}{LocaleStrings.MSGU['uninstall_success']}"
)
self.log(
f"=== {project_info['name']}{LocaleStrings.MSGU['uninstall_success']} ==="
)
except Exception as e:
self.log(f"ERROR: {LocaleStrings.MSGU['uninstall_failed']}{e}")
raise Exception(f"{LocaleStrings.MSGU['uninstall_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"{LocaleStrings.MSGO['unknow_project']}{project_key}")
def _create_wirepy_uninstall_script(self):
detected_os = Detector.get_os()
"""Create Wire-Py uninstallation script"""
script = f"""#!/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 {LXToolsAppConfig.SHARED_LIBS_DESTINATION[detected_os]}/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/{getpass.getuser()}/.config/wire_py ]; then
rm -rf /home/{getpass.getuser()}/.config/wire_py
echo "Removed user config directory"
fi
# Remove ssl private key
rm -rf /usr/local/etc/ssl
echo "Removed ssl private key"
# Remove log file
rm -f /home/{getpass.getuser()}/.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/bin/lxtools_installer
# 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 {LXToolsAppConfig.SHARED_LIBS_DESTINATION[detected_os]}/$file
done
# Try to remove shared_libs directory if empty
rmdir {LXToolsAppConfig.SHARED_LIBS_DESTINATION[detected_os]} 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):
detected_os = Detector.get_os()
"""Create LogViewer uninstallation script"""
script = f"""#!/bin/bash
set -e
echo "=== LogViewer Uninstallation ==="
# Remove LogViewer symlink
rm -f /usr/local/bin/logviewer
echo "Removed logviewer symlink"
# Remove language files
rm -f /usr/share/locale/de/LC_MESSAGES/logviewer.mo
echo "Removed language files"
# Remove user config directory
if [ -d /home/{getpass.getuser()}/.config/logviewer ]; then
rm -rf /home/{getpass.getuser()}/.config/logviewer
echo "Removed user config directory"
fi
# Remove log file
rm -f /home/{getpass.getuser()}/.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
rm -rf /usr/local/bin/lxtools_installer
# 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 {LXToolsAppConfig.SHARED_LIBS_DESTINATION[detected_os]}/$file
done
# Remove logviewer.py last
rm -f {LXToolsAppConfig.SHARED_LIBS_DESTINATION[detected_os]}/logviewer.py
# Try to remove shared_libs directory if empty
rmdir LXToolsAppConfig.SHARED_LIBS_DESTINATION[detected_os] 2>/dev/null || true
else
echo "Wire-Py still installed, keeping shared resources"
# Only remove logviewer-specific files
rm -f {LXToolsAppConfig.SHARED_LIBS_DESTINATION[detected_os]}/logview_app_config.py
rm -f {LXToolsAppConfig.SHARED_LIBS_DESTINATION[detected_os]}/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"{LocaleStrings.MSGU['uninstall_create']}{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(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"{LocaleStrings.MSGU['uninstall_script_failed']}{result.stderr}"
)
except subprocess.TimeoutExpired:
raise Exception(LocaleStrings.MSGU["uninstall_timeout"])
except subprocess.CalledProcessError as e:
raise Exception(f"{LocaleStrings.MSGU['uninstall_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"{LocaleStrings.MSGO['download_from']}{url}...")
with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as tmp_file:
urllib.request.urlretrieve(url, tmp_file.name)
if progress_callback:
progress_callback(LocaleStrings.MSGO["extract_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"{LocaleStrings.MSGO['download_failed']}{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.polkit_ok = Detector.get_polkit()
self.networkmanager_ok = Detector.get_networkmanager()
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 = Detector.get_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"{Locale.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)
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 (versuche echtes Icon, dann Fallback)
try:
icon = self.image_manager.load_image("header_image")
if icon:
# Resize icon für Header
icon_label = tk.Label(icon_text_frame, image=icon, bg="#2c3e50")
icon_label.image = icon # Referenz behalten
icon_label.pack(side="left", padx=(0, 8))
else:
raise Exception("Kein Icon gefunden")
except:
# Fallback: Unicode-Symbol statt Emoji
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}{LocaleStrings.MSGO['head_string3']}",
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"{LocaleStrings.MSGO['head_string2']}{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"""
# Führe alle Checks durch
internet_ok = NetworkChecker.check_internet_connection()
repo_ok = NetworkChecker.check_repository_access()
python_result = Detector.get_host_python_version()
# Sammle alle Probleme
issues = []
if not self.polkit_ok:
issues.append(("PolicyKit", "#e74c3c", LocaleStrings.MSGO["polkit_check"]))
if not self.networkmanager_ok:
issues.append(
(
"NetworkManager",
"#e74c3c",
LocaleStrings.MSGO["networkmanager_check"],
)
)
if not internet_ok:
issues.append(("Internet", "#e74c3c", LocaleStrings.MSGO["no_internet"]))
if not repo_ok:
issues.append(
("Repository", "#f39c12", LocaleStrings.MSGO["repo_unavailable"])
)
if python_result is None:
issues.append(("Python", "#e74c3c", LocaleStrings.MSGO["python_check"]))
# Bestimme Hauptstatus basierend auf Priorität
if issues:
# Zeige das wichtigste Problem (Internet > Python > Repository > Services)
for priority_check in [
"Internet",
"Python",
"Repository",
"PolicyKit",
"NetworkManager",
]:
for issue_name, color, message in issues:
if issue_name == priority_check:
print(f"DEBUG: Zeige Problem: {issue_name} - {message}")
self.update_header_status(message, color)
return
else:
self.update_header_status(LocaleStrings.MSGO["ready"], "#1abc9c") # Green
def _create_projects_tab(self):
"""Create projects tab with project cards"""
projects_frame = ttk.Frame(self.notebook)
self.notebook.add(projects_frame, text=LocaleStrings.MSGO["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=f"{LocaleStrings.MSGC['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=LocaleStrings.MSGC["version_check"],
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"{LocaleStrings.MSGL['selected_app']}{project_info['name']}")
def _create_log_tab(self):
"""Create log tab"""
log_frame = ttk.Frame(self.notebook)
self.notebook.add(log_frame, text=LocaleStrings.MSGL["log_name"])
# Log text with scrollbar
log_container = tk.Frame(log_frame)
log_container.pack(fill="both", expand=True, padx=10, pady=10)
# Important! pack_propagate(False) must be set here to display
# the Clear Log button correctly
log_container.pack_propagate(False)
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=(5, 0))
# Clear log button
clear_log_btn = ttk.Button(
log_controls, text=LocaleStrings.MSGB["clear_log"], command=self.clear_log
)
clear_log_btn.pack(side="right", pady=(0, 10))
# Initial log message
self.log_message(f"=== {Locale.APP_NAME} v {LXToolsAppConfig.VERSION} ===")
self.log_message(f"{LocaleStrings.MSGL['work_dir']}{LXToolsAppConfig.WORK_DIR}")
self.log_message(
f"{LocaleStrings.MSGL['icons_dir']}{LXToolsAppConfig.ICONS_DIR}"
)
self.log_message(f"{LocaleStrings.MSGL['detected_os']}{self.detected_os}")
self.log_message(f"{LocaleStrings.MSGO['polkit_check_log']}{self.polkit_ok}")
self.log_message(
f"{LocaleStrings.MSGO['networkmanager_check_log']}{self.networkmanager_ok}"
)
self.log_message(f"{LocaleStrings.MSGO['ready']}...")
def _create_progress_section(self):
"""Create progress section with download icon"""
progress_frame = ttk.LabelFrame(
self.root, text=LocaleStrings.MSGO["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=f"{LocaleStrings.MSGO['ready']}...",
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 (mit Error-Handling für Linux Mint)
try:
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")],
)
except Exception as e:
print(f"Style-Konfiguration fehlgeschlagen: {e}")
# Fallback: verwende Standard-Styles
# Create buttons (mit Fallback für Style-Probleme)
try:
install_btn = ttk.Button(
button_frame,
text=LocaleStrings.MSGB["install"],
command=self.install_selected,
style="Install.TButton",
padding=8,
)
except:
install_btn = ttk.Button(
button_frame,
text=LocaleStrings.MSGB["install"],
command=self.install_selected,
)
install_btn.pack(side="left", padx=(0, 10))
try:
uninstall_btn = ttk.Button(
button_frame,
text=LocaleStrings.MSGB["uninstall"],
command=self.uninstall_selected,
style="Uninstall.TButton",
padding=8,
)
except:
uninstall_btn = ttk.Button(
button_frame,
text=LocaleStrings.MSGB["uninstall"],
command=self.uninstall_selected,
)
uninstall_btn.pack(side="left", padx=(0, 10))
try:
refresh_btn = ttk.Button(
button_frame,
text=LocaleStrings.MSGB["refresh"],
command=self.refresh_status,
style="Refresh.TButton",
width=30,
padding=8,
)
except:
refresh_btn = ttk.Button(
button_frame,
text=LocaleStrings.MSGB["refresh"],
command=self.refresh_status,
)
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(LocaleStrings.MSGI["refresh_and_check"])
self._reset_download_icon()
self.log_message(f"=== {LocaleStrings.MSGB['refresh']} ===")
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"{LocaleStrings.MSGC['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"{LocaleStrings.MSGI['installed']}({installed_version})",
fg="green",
)
self.log_message(
f"{project_info['name']}: {LocaleStrings.MSGI['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"{LocaleStrings.MSGC['update_on']}(v. {latest_version}) {LocaleStrings.MSGC['available_lower']}",
fg="orange",
)
self.log_message(
f"{project_info['name']}: {LocaleStrings.MSGC['update_on']}(v. {latest_version} {LocaleStrings.MSGC['available_lower']})"
)
else:
version_label.config(
text=f"(v. {latest_version}) {LocaleStrings.MSGC['up_to_date']}",
fg="green",
)
self.log_message(
f"{project_info['name']}: {LocaleStrings.MSGC['up_to_date']}",
)
else:
version_label.config(
text=LocaleStrings.MSGC["latest_unknown"], fg="gray"
)
self.log_message(
f"{project_info['name']}: {LocaleStrings.MSGC['could_not_check']}"
)
except Exception as e:
version_label.config(
text=LocaleStrings.MSGC["check_last_failed"], fg="gray"
)
self.log_message(
f"{project_info['name']}: {LocaleStrings.MSGC['version_check_failed']}: {e}"
)
else:
status_label.config(
text=f"{LocaleStrings.MSGC['not_installed']}", fg="red"
)
self.log_message(
f"{project_info['name']}: {LocaleStrings.MSGC['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"{project_info['name']} {LocaleStrings.MSGC['available']}: v {latest_version}",
fg="blue",
)
self.log_message(
f"{project_info['name']} {LocaleStrings.MSGC['available']}: v {latest_version}"
)
else:
version_label.config(
text=LocaleStrings.MSGC["available_unknown"], fg="gray"
)
except Exception as e:
version_label.config(
text=LocaleStrings.MSGC["available_check_unknown"], fg="gray"
)
self.log_message(
f" {project_info['name']}: {LocaleStrings.MSGC['version_check_failed']}: {e}"
)
self.update_progress(LocaleStrings.MSGO["refresh2"])
self.log_message(f"=== {LocaleStrings.MSGO['refresh2']} ===")
self.check_ready_status()
def install_selected(self):
"""Handle install button click"""
if not self.selected_project:
MessageDialog("error", LocaleStrings.MSGM["please_select"])
self.root.focus_set()
return
# Check internet connection
if not NetworkChecker.check_internet_connection():
self.update_download_icon("error")
MessageDialog("error", LocaleStrings.MSGM["network_error"])
self.root.focus_set()
return
if not NetworkChecker.check_repository_access():
self.update_download_icon("error")
MessageDialog("error", LocaleStrings.MSGM["repo_error"])
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']} {LocaleStrings.MSGM['has_success_update']}",
wraplength=400,
)
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", LocaleStrings.MSGM["please_select_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']} {LocaleStrings.MSGO['not_installed']}"
)
self.root.focus_set()
return
try:
self.uninstallation_manager.uninstall_project(self.selected_project)
MessageDialog(
"info",
f"{project_info['name']} {LocaleStrings.MSGU['uninstall_success']}",
wraplength=400,
)
self.refresh_status()
self.root.focus_set()
except Exception as e:
MessageDialog("error", f"{LocaleStrings.MSGU['uninstall_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"{LocaleStrings.MSGO['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(LocaleStrings.MSGL["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"=== {Locale.APP_NAME} v {LXToolsAppConfig.VERSION} ===")
print(f"{LocaleStrings.MSGL['working_dir']}{os.getcwd()}")
try:
LxTools.sigi(LXToolsAppConfig.TEMP_DIR)
# Create and run the GUI
app = LXToolsGUI()
app.run()
except KeyboardInterrupt:
print(LocaleStrings.MSGL["user_interrupt"])
except Exception as e:
print(f"{LocaleStrings.MSGL['fatal_error']}: {e}")
try:
MessageDialog("error", f"{LocaleStrings.MSGL['fatal_app_error']}: {e}")
except:
pass
if __name__ == "__main__":
main()
LxTools.remove_lxtools_files()
LxTools.clean_files(LXToolsAppConfig.TEMP_DIR)
sys.exit(0)