29.06.2025 (show changelog)

This commit is contained in:
2025-07-02 12:40:08 +02:00
parent e824094556
commit 12904e843c
9 changed files with 357 additions and 70 deletions

24
.gitignore vendored
View File

@ -4,3 +4,27 @@ debug.log
.idea .idea
.vscode .vscode
__pycache__ __pycache__
# Build-Artefakte
build/
dist/
certs/
*.spec.bak
lxtools_installer
lxtools_installer.AppImage
lxtools_installer_compat
build_compatible.sh
build_local.sh
clean_build.sh
start_builder.sh
test_extract.py
test_paths.py
test_resources.py
manager_fixed.py
# Docker-Build
docker_build/
debug_docker.sh
Dockerfile.nuitka
DOCKER_BUILD_ANLEITUNG.md
nuitka_builder.py

View File

@ -2,10 +2,20 @@ Changelog for LXTools installer
## [Unreleased] ## [Unreleased]
- language set auto detection
- -
### Added
29.06.2025
- add methode sigi, clean_files and remove_lxtools_files
for remove files and dirs on close lxtools_installer
- fix message dialog on font and padding
- add methods check polkit and check Networkmanager is installed
and view in header is result false
### Added ### Added
23-06-2025 23-06-2025

BIN
lx-icons/16/settings.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 B

BIN
lx-icons/16/wg_vpn.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 846 B

View File

@ -2,6 +2,7 @@
import tkinter as tk import tkinter as tk
from tkinter import ttk from tkinter import ttk
import os import os
import sys
import subprocess import subprocess
import getpass import getpass
from datetime import datetime from datetime import datetime
@ -37,12 +38,12 @@ class InstallationManager:
"""Install or update project""" """Install or update project"""
project_info = self.app_manager.get_project_info(project_key) project_info = self.app_manager.get_project_info(project_key)
if not project_info: if not project_info:
raise Exception(f"{LocaleStrings.MSG["unknow_project"]}{project_key}") raise Exception(f"{LocaleStrings.MSG['unknow_project']}{project_key}")
self.update_progress( self.update_progress(
f"{LocaleStrings.MSGI["start_install"]}{project_info['name']}..." f"{LocaleStrings.MSGI['start_install']}{project_info['name']}..."
) )
self.log(f"=== {LocaleStrings.MSGI["install"]}{project_info['name']} ===") self.log(f"=== {LocaleStrings.MSGI['install']}{project_info['name']} ===")
try: try:
# Create installation script # Create installation script
@ -52,19 +53,19 @@ class InstallationManager:
self._execute_install_script(script_content) self._execute_install_script(script_content)
self.update_progress( self.update_progress(
f"{project_info["name"]}{LocaleStrings.MSGI["install_success"]}" f"{project_info['name']}{LocaleStrings.MSGI['install_success']}"
) )
self.log( self.log(
f"=== {project_info["name"]}{LocaleStrings.MSGI["install_success"]} ===" f"=== {project_info['name']}{LocaleStrings.MSGI['install_success']} ==="
) )
# Set success icon # Set success icon
self.update_icon("success") self.update_icon("success")
except Exception as e: except Exception as e:
self.log(f"ERROR: {LocaleStrings.MSGI["install_failed"]}{e}") self.log(f"ERROR: {LocaleStrings.MSGI['install_failed']}{e}")
self.update_icon("error") self.update_icon("error")
raise Exception(f"{LocaleStrings.MSGI["install_failed"]}{e}") raise Exception(f"{LocaleStrings.MSGI['install_failed']}{e}")
def _create_install_script(self, project_key): def _create_install_script(self, project_key):
"""Create installation script based on project""" """Create installation script based on project"""
@ -73,7 +74,7 @@ class InstallationManager:
elif project_key == "logviewer": elif project_key == "logviewer":
return self._create_logviewer_install_script() return self._create_logviewer_install_script()
else: else:
raise Exception(f"{LocaleStrings.MSGI["unknow_project"]}{project_key}") raise Exception(f"{LocaleStrings.MSGI['unknow_project']}{project_key}")
def _create_wirepy_install_script(self): def _create_wirepy_install_script(self):
detected_os = Detector.get_os() detected_os = Detector.get_os()
@ -331,7 +332,7 @@ echo "LogViewer installation completed!"
# Make script executable # Make script executable
os.chmod(script_file.name, 0o755) os.chmod(script_file.name, 0o755)
self.log(f"{LocaleStrings.MSGI["install_create"]}{script_file.name}") self.log(f"{LocaleStrings.MSGI['install_create']}{script_file.name}")
# Execute with pkexec # Execute with pkexec
result = subprocess.run( result = subprocess.run(
@ -352,13 +353,13 @@ echo "LogViewer installation completed!"
if result.returncode != 0: if result.returncode != 0:
raise Exception( raise Exception(
f"{LocaleStrings.MSGI["install_script_failed"]}{result.stderr}" f"{LocaleStrings.MSGI['install_script_failed']}{result.stderr}"
) )
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
raise Exception(LocaleStrings.MSGI["install_timeout"]) raise Exception(LocaleStrings.MSGI["install_timeout"])
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
raise Exception(f"{LocaleStrings.MSGI["install_script_failed"]}{e}") raise Exception(f"{LocaleStrings.MSGI['install_script_failed']}{e}")
def update_progress(self, message): def update_progress(self, message):
if self.progress_callback: if self.progress_callback:
@ -383,17 +384,17 @@ class UninstallationManager:
"""Uninstall project""" """Uninstall project"""
project_info = self.app_manager.get_project_info(project_key) project_info = self.app_manager.get_project_info(project_key)
if not project_info: if not project_info:
raise Exception(f"{LocaleStrings.MSGO["unknow_project"]}{project_key}") raise Exception(f"{LocaleStrings.MSGO['unknow_project']}{project_key}")
if not self.app_manager.is_installed(project_key): if not self.app_manager.is_installed(project_key):
raise Exception( raise Exception(
f"{project_info["name"]}{LocaleStrings.MSGO["not_installed"]}" f"{project_info['name']}{LocaleStrings.MSGO['not_installed']}"
) )
self.update_progress( self.update_progress(
f"{LocaleStrings.MSGU["uninstall"]}{project_info['name']}..." f"{LocaleStrings.MSGU['uninstall']}{project_info['name']}..."
) )
self.log(f"=== {LocaleStrings.MSGU["uninstall"]}{project_info['name']} ===") self.log(f"=== {LocaleStrings.MSGU['uninstall']}{project_info['name']} ===")
try: try:
# Create uninstallation script # Create uninstallation script
@ -403,15 +404,15 @@ class UninstallationManager:
self._execute_uninstall_script(script_content) self._execute_uninstall_script(script_content)
self.update_progress( self.update_progress(
f"{project_info['name']}{LocaleStrings.MSGU["uninstall_success"]}" f"{project_info['name']}{LocaleStrings.MSGU['uninstall_success']}"
) )
self.log( self.log(
f"=== {project_info['name']}{LocaleStrings.MSGU["uninstall_success"]} ===" f"=== {project_info['name']}{LocaleStrings.MSGU['uninstall_success']} ==="
) )
except Exception as e: except Exception as e:
self.log(f"ERROR: {LocaleStrings.MSGU['uninstall_failed']}{e}") self.log(f"ERROR: {LocaleStrings.MSGU['uninstall_failed']}{e}")
raise Exception(f"{LocaleStrings.MSGU["uninstall_failed"]}{e}") raise Exception(f"{LocaleStrings.MSGU['uninstall_failed']}{e}")
def _create_uninstall_script(self, project_key): def _create_uninstall_script(self, project_key):
"""Create uninstallation script based on project""" """Create uninstallation script based on project"""
@ -420,7 +421,7 @@ class UninstallationManager:
elif project_key == "logviewer": elif project_key == "logviewer":
return self._create_logviewer_uninstall_script() return self._create_logviewer_uninstall_script()
else: else:
raise Exception(f"{LocaleStrings.MSGO["unknow_project"]}{project_key}") raise Exception(f"{LocaleStrings.MSGO['unknow_project']}{project_key}")
def _create_wirepy_uninstall_script(self): def _create_wirepy_uninstall_script(self):
detected_os = Detector.get_os() detected_os = Detector.get_os()
@ -555,7 +556,7 @@ echo "LogViewer uninstallation completed!"
# Make script executable # Make script executable
os.chmod(script_file.name, 0o755) os.chmod(script_file.name, 0o755)
self.log(f"{LocaleStrings.MSGU["uninstall_create"]}{script_file.name}") self.log(f"{LocaleStrings.MSGU['uninstall_create']}{script_file.name}")
# Execute with pkexec # Execute with pkexec
result = subprocess.run( result = subprocess.run(
@ -576,13 +577,13 @@ echo "LogViewer uninstallation completed!"
if result.returncode != 0: if result.returncode != 0:
raise Exception( raise Exception(
f"{LocaleStrings.MSGU["uninstall_script_failed"]}{result.stderr}" f"{LocaleStrings.MSGU['uninstall_script_failed']}{result.stderr}"
) )
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
raise Exception(LocaleStrings.MSGU["uninstall_timeout"]) raise Exception(LocaleStrings.MSGU["uninstall_timeout"])
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
raise Exception(f"{LocaleStrings.MSGU["uninstall_script_failed"]}{e}") raise Exception(f"{LocaleStrings.MSGU['uninstall_script_failed']}{e}")
def update_progress(self, message): def update_progress(self, message):
if self.progress_callback: if self.progress_callback:
@ -599,7 +600,7 @@ class DownloadManager:
"""Download and extract ZIP file""" """Download and extract ZIP file"""
try: try:
if progress_callback: if progress_callback:
progress_callback(f"{LocaleStrings.MSGO["download_from"]}{url}...") progress_callback(f"{LocaleStrings.MSGO['download_from']}{url}...")
with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as tmp_file: with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as tmp_file:
urllib.request.urlretrieve(url, tmp_file.name) urllib.request.urlretrieve(url, tmp_file.name)
@ -615,7 +616,7 @@ class DownloadManager:
except Exception as e: except Exception as e:
if progress_callback: if progress_callback:
progress_callback(f"{LocaleStrings.MSGO["download_failed"]}{e}") progress_callback(f"{LocaleStrings.MSGO['download_failed']}{e}")
return False return False
@ -738,7 +739,7 @@ class LXToolsGUI:
tk.Label( tk.Label(
text_frame, text_frame,
text=f"v {LXToolsAppConfig.VERSION}{LocaleStrings.MSGO["head_string3"]}", text=f"v {LXToolsAppConfig.VERSION}{LocaleStrings.MSGO['head_string3']}",
font=("Helvetica", 9), font=("Helvetica", 9),
fg="#bdc3c7", fg="#bdc3c7",
bg="#2c3e50", bg="#2c3e50",
@ -750,7 +751,7 @@ class LXToolsGUI:
tk.Label( tk.Label(
right_side, right_side,
text=f"{LocaleStrings.MSGO["head_string2"]}{self.detected_os}", text=f"{LocaleStrings.MSGO['head_string2']}{self.detected_os}",
font=("Helvetica", 11), font=("Helvetica", 11),
fg="#ecf0f1", fg="#ecf0f1",
bg="#2c3e50", bg="#2c3e50",
@ -774,10 +775,21 @@ class LXToolsGUI:
def check_ready_status(self): def check_ready_status(self):
"""Check if system is ready for installation""" """Check if system is ready for installation"""
# Checks: # Checks:
polkit_ok = Detector.get_polkit()
networkmanager_ok = Detector.get_networkmanager()
internet_ok = NetworkChecker.check_internet_connection() internet_ok = NetworkChecker.check_internet_connection()
repo_ok = NetworkChecker.check_repository_access() repo_ok = NetworkChecker.check_repository_access()
result = Detector.get_host_python_version() result = Detector.get_host_python_version()
if not polkit_ok:
self.update_header_status(
LocaleStrings.MSGO["polkit_check"], "#e74c3c"
) # Red
if not networkmanager_ok:
self.update_header_status(
LocaleStrings.MSGO["networkmanager_check"], "#e74c3c"
) # Red
if internet_ok and repo_ok and result is not None: if internet_ok and repo_ok and result is not None:
self.update_header_status(LocaleStrings.MSGO["ready"], "#1abc9c") # Green self.update_header_status(LocaleStrings.MSGO["ready"], "#1abc9c") # Green
elif not internet_ok: elif not internet_ok:
@ -897,7 +909,7 @@ class LXToolsGUI:
# Status label # Status label
status_label = tk.Label( status_label = tk.Label(
status_frame, status_frame,
text=f"{LocaleStrings.MSGC["checking"]}", text=f"{LocaleStrings.MSGC['checking']}",
font=("Helvetica", 10), font=("Helvetica", 10),
bg=self.colors["card_bg"], bg=self.colors["card_bg"],
fg="#95a5a6", fg="#95a5a6",
@ -991,7 +1003,7 @@ class LXToolsGUI:
self._update_frame_children_bg(new_content, 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) project_info = self.app_manager.get_project_info(project_key)
self.log_message(f"{LocaleStrings.MSGL["selected_app"]}{project_info["name"]}") self.log_message(f"{LocaleStrings.MSGL['selected_app']}{project_info['name']}")
def _create_log_tab(self): def _create_log_tab(self):
"""Create log tab""" """Create log tab"""
@ -1037,12 +1049,12 @@ class LXToolsGUI:
self.log_message( self.log_message(
f"=== {LXToolsAppConfig.APP_NAME} v {LXToolsAppConfig.VERSION} ===" f"=== {LXToolsAppConfig.APP_NAME} v {LXToolsAppConfig.VERSION} ==="
) )
self.log_message(f"{LocaleStrings.MSGL["work_dir"]}{LXToolsAppConfig.WORK_DIR}") self.log_message(f"{LocaleStrings.MSGL['work_dir']}{LXToolsAppConfig.WORK_DIR}")
self.log_message( self.log_message(
f"{LocaleStrings.MSGL["icons_dir"]}{LXToolsAppConfig.ICONS_DIR}" f"{LocaleStrings.MSGL['icons_dir']}{LXToolsAppConfig.ICONS_DIR}"
) )
self.log_message(f"{LocaleStrings.MSGL["detected_os"]}{self.detected_os}") self.log_message(f"{LocaleStrings.MSGL['detected_os']}{self.detected_os}")
self.log_message(f"{LocaleStrings.MSGO["ready"]}...") self.log_message(f"{LocaleStrings.MSGO['ready']}...")
def _create_progress_section(self): def _create_progress_section(self):
"""Create progress section with download icon""" """Create progress section with download icon"""
@ -1062,7 +1074,7 @@ class LXToolsGUI:
# Progress Text (right, expandable) # Progress Text (right, expandable)
self.progress_label = tk.Label( self.progress_label = tk.Label(
progress_container, progress_container,
text=f"{LocaleStrings.MSGO["ready"]}...", text=f"{LocaleStrings.MSGO['ready']}...",
font=("Helvetica", 10), font=("Helvetica", 10),
fg="blue", fg="blue",
anchor="w", anchor="w",
@ -1175,22 +1187,22 @@ class LXToolsGUI:
"""Refresh application status and version information""" """Refresh application status and version information"""
self.update_progress(LocaleStrings.MSGI["refresh_and_check"]) self.update_progress(LocaleStrings.MSGI["refresh_and_check"])
self._reset_download_icon() self._reset_download_icon()
self.log_message(f"=== {LocaleStrings.MSGB["refresh"]} ===") self.log_message(f"=== {LocaleStrings.MSGB['refresh']} ===")
self.root.focus_set() self.root.focus_set()
for project_key, project_info in self.app_manager.get_all_projects().items(): for project_key, project_info in self.app_manager.get_all_projects().items():
status_label = self.status_labels[project_key] status_label = self.status_labels[project_key]
version_label = self.version_labels[project_key] version_label = self.version_labels[project_key]
self.log_message(f"{LocaleStrings.MSGC["checking"]} {project_info['name']}") self.log_message(f"{LocaleStrings.MSGC['checking']} {project_info['name']}")
if self.app_manager.is_installed(project_key): if self.app_manager.is_installed(project_key):
installed_version = self.app_manager.get_installed_version(project_key) installed_version = self.app_manager.get_installed_version(project_key)
status_label.config( status_label.config(
text=f"{LocaleStrings.MSGI["installed"]}({installed_version})", text=f"{LocaleStrings.MSGI['installed']}({installed_version})",
fg="green", fg="green",
) )
self.log_message( self.log_message(
f"{project_info['name']}: {LocaleStrings.MSGI["installed"]}({installed_version})" f"{project_info['name']}: {LocaleStrings.MSGI['installed']}({installed_version})"
) )
# Get latest version from API # Get latest version from API
@ -1199,41 +1211,41 @@ class LXToolsGUI:
if latest_version != "Unknown": if latest_version != "Unknown":
if installed_version != f"v. {latest_version}": if installed_version != f"v. {latest_version}":
version_label.config( version_label.config(
text=f"{LocaleStrings.MSGC["latest"]}(v. {latest_version}) {LocaleStrings.MSGC["update_available"]}", text=f"{LocaleStrings.MSGC['latest']}(v. {latest_version}) {LocaleStrings.MSGC['update_available']}",
fg="orange", fg="orange",
) )
self.log_message( self.log_message(
f"{project_info['name']}: {LocaleStrings.MSGC["update_available"]}(v. {latest_version})" f"{project_info['name']}: {LocaleStrings.MSGC['update_available']}(v. {latest_version})"
) )
else: else:
version_label.config( version_label.config(
text=f"{LocaleStrings.MSGC["latest"]}: (v. {latest_version}) {LocaleStrings.MSGC["up_to_date"]}", text=f"{LocaleStrings.MSGC['latest']}: (v. {latest_version}) {LocaleStrings.MSGC['up_to_date']}",
fg="green", fg="green",
) )
self.log_message( self.log_message(
f"{project_info['name']}: {LocaleStrings.MSGC["up_to_date"]}", f"{project_info['name']}: {LocaleStrings.MSGC['up_to_date']}",
) )
else: else:
version_label.config( version_label.config(
text=LocaleStrings.MSGC["latest_unknown"], fg="gray" text=LocaleStrings.MSGC["latest_unknown"], fg="gray"
) )
self.log_message( self.log_message(
f"{project_info['name']}: {LocaleStrings.MSGC["could_not_check"]}" f"{project_info['name']}: {LocaleStrings.MSGC['could_not_check']}"
) )
except Exception as e: except Exception as e:
version_label.config( version_label.config(
text=LocaleStrings.MSGC["check_last_failed"], fg="gray" text=LocaleStrings.MSGC["check_last_failed"], fg="gray"
) )
self.log_message( self.log_message(
f"{project_info['name']}: {LocaleStrings.MSGC["version_check_failed"]}: {e}" f"{project_info['name']}: {LocaleStrings.MSGC['version_check_failed']}: {e}"
) )
else: else:
status_label.config( status_label.config(
text=f"{LocaleStrings.MSGC["not_installed"]}", fg="red" text=f"{LocaleStrings.MSGC['not_installed']}", fg="red"
) )
self.log_message( self.log_message(
f"{project_info['name']}: {LocaleStrings.MSGC["not_installed"]}" f"{project_info['name']}: {LocaleStrings.MSGC['not_installed']}"
) )
# Still show latest available version # Still show latest available version
@ -1241,12 +1253,12 @@ class LXToolsGUI:
latest_version = self.app_manager.get_latest_version(project_key) latest_version = self.app_manager.get_latest_version(project_key)
if latest_version != "Unknown": if latest_version != "Unknown":
version_label.config( version_label.config(
text=f"{project_info['name']} {LocaleStrings.MSGC["available"]}: v {latest_version}", text=f"{project_info['name']} {LocaleStrings.MSGC['available']}: v {latest_version}",
fg="blue", fg="blue",
) )
self.log_message( self.log_message(
f"{project_info['name']} {LocaleStrings.MSGC["available"]}: v {latest_version}" f"{project_info['name']} {LocaleStrings.MSGC['available']}: v {latest_version}"
) )
else: else:
version_label.config( version_label.config(
@ -1257,11 +1269,11 @@ class LXToolsGUI:
text=LocaleStrings.MSGC["available_check_unknown"], fg="gray" text=LocaleStrings.MSGC["available_check_unknown"], fg="gray"
) )
self.log_message( self.log_message(
f" {project_info['name']}: {LocaleStrings.MSGC["version_check_failed"]}: {e}" f" {project_info['name']}: {LocaleStrings.MSGC['version_check_failed']}: {e}"
) )
self.update_progress(LocaleStrings.MSGO["refresh2"]) self.update_progress(LocaleStrings.MSGO["refresh2"])
self.log_message(f"=== {LocaleStrings.MSGO["refresh2"]} ===") self.log_message(f"=== {LocaleStrings.MSGO['refresh2']} ===")
self.check_ready_status() self.check_ready_status()
def install_selected(self): def install_selected(self):
@ -1294,7 +1306,7 @@ class LXToolsGUI:
self.update_download_icon("success") self.update_download_icon("success")
MessageDialog( MessageDialog(
"info", "info",
f"{project_info["name"]} {LocaleStrings.MSGM["has_success_update"]}", f"{project_info['name']} {LocaleStrings.MSGM['has_success_update']}",
wraplength=400, wraplength=400,
) )
@ -1317,7 +1329,7 @@ class LXToolsGUI:
if not self.app_manager.is_installed(self.selected_project): if not self.app_manager.is_installed(self.selected_project):
MessageDialog( MessageDialog(
"error", f"{project_info["name"]} {LocaleStrings.MSGO["not_installed"]}" "error", f"{project_info['name']} {LocaleStrings.MSGO['not_installed']}"
) )
self.root.focus_set() self.root.focus_set()
return return
@ -1326,14 +1338,14 @@ class LXToolsGUI:
self.uninstallation_manager.uninstall_project(self.selected_project) self.uninstallation_manager.uninstall_project(self.selected_project)
MessageDialog( MessageDialog(
"info", "info",
f"{project_info["name"]} {LocaleStrings.MSGU["uninstall_success"]}", f"{project_info['name']} {LocaleStrings.MSGU['uninstall_success']}",
wraplength=400, wraplength=400,
) )
self.refresh_status() self.refresh_status()
self.root.focus_set() self.root.focus_set()
except Exception as e: except Exception as e:
MessageDialog("error", f"{LocaleStrings.MSGU["uninstall_failed"]}: {e}") MessageDialog("error", f"{LocaleStrings.MSGU['uninstall_failed']}: {e}")
self.root.focus_set() self.root.focus_set()
def update_progress(self, message): def update_progress(self, message):
@ -1341,7 +1353,7 @@ class LXToolsGUI:
if self.progress_label: if self.progress_label:
self.progress_label.config(text=message) self.progress_label.config(text=message)
self.progress_label.update() self.progress_label.update()
print(f"{LocaleStrings.MSGO["progress"]}: {message}") print(f"{LocaleStrings.MSGO['progress']}: {message}")
def log_message(self, message): def log_message(self, message):
"""Add message to log""" """Add message to log"""
@ -1368,16 +1380,17 @@ class LXToolsGUI:
def main(): def main():
"""Main function to start the application""" """Main function to start the application"""
print(f"=== {LXToolsAppConfig.APP_NAME} v {LXToolsAppConfig.VERSION} ===") print(f"=== {LXToolsAppConfig.APP_NAME} v {LXToolsAppConfig.VERSION} ===")
print(f"{LocaleStrings.MSGL["working_dir"]}{os.getcwd()}") print(f"{LocaleStrings.MSGL['working_dir']}{os.getcwd()}")
try: try:
LxTools.sigi(LXToolsAppConfig.TEMP_DIR)
# Create and run the GUI # Create and run the GUI
app = LXToolsGUI() app = LXToolsGUI()
app.run() app.run()
except KeyboardInterrupt: except KeyboardInterrupt:
print(LocaleStrings.MSGL["user_interrupt"]) print(LocaleStrings.MSGL["user_interrupt"])
except Exception as e: except Exception as e:
print(f"{LocaleStrings.MSGL["fatal_error"]}: {e}") print(f"{LocaleStrings.MSGL['fatal_error']}: {e}")
try: try:
MessageDialog("error", f"{LocaleStrings.MSGL['fatal_app_error']}: {e}") MessageDialog("error", f"{LocaleStrings.MSGL['fatal_app_error']}: {e}")
@ -1387,3 +1400,6 @@ def main():
if __name__ == "__main__": if __name__ == "__main__":
main() main()
LxTools.remove_lxtools_files()
LxTools.clean_files(LXToolsAppConfig.TEMP_DIR)
sys.exit(0)

38
lxtools_installer.spec Normal file
View File

@ -0,0 +1,38 @@
# -*- mode: python ; coding: utf-8 -*-
a = Analysis(
['lxtools_installer.py'],
pathex=[],
binaries=[],
datas=[('locale/de/LC_MESSAGES/lxtoolsinstaller.mo', 'locale/de/LC_MESSAGES/'), ('manager.py', '.'), ('network.py', '.'), ('message.py', '.'), ('TK-Themes/theme/dark/*.png', 'TK-Themes/theme/dark'), ('TK-Themes/theme/light/*.png', 'TK-Themes/theme/light'), ('TK-Themes/water.tcl', 'TK-Themes'), ('TK-Themes/LICENSE', 'TK-Themes'), ('TK-Themes/theme/dark.tcl', 'TK-Themes/theme'), ('TK-Themes/theme/light.tcl', 'TK-Themes/theme'), ('lx-icons/32/*.png', 'lx-icons/32'), ('lx-icons/48/*.png', 'lx-icons/48'), ('lx-icons/64/*.png', 'lx-icons/64'), ('lx-icons/128/*.png', 'lx-icons/128'), ('lx-icons/256/*.png', 'lx-icons/256'), ('certs/cacert.pem', 'certs')],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='lxtools_installer',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)

View File

@ -1,8 +1,11 @@
import locale import locale
import gettext import gettext
import signal
import tkinter as tk import tkinter as tk
from tkinter import ttk from tkinter import ttk
from pathlib import Path from pathlib import Path
from typing import Optional, NoReturn, Any, Dict
import logging
import os import os
import sys import sys
import shutil import shutil
@ -96,6 +99,114 @@ class Detector:
else: else:
return False return False
@staticmethod
def get_polkit() -> bool:
"""Check if network manager is installed"""
os_system = Detector.get_os()
deb = ["Debian", "Ubuntu", "Linux Mint", "Pop!_OS"]
arch = ["Arch Linux", "Manjaro", "EndeavourOS", "ArcoLinux", "Garuda Linux"]
if os_system in deb:
result = subprocess.run(
["apt", "list", "--installed", "|", "grep", "polkit"],
capture_output=True,
text=True,
check=False,
)
if result.returncode == 0:
return True
else:
return False
elif os_system in arch:
result = subprocess.run(
["pacman", "-Qs", "polkit"],
capture_output=True,
text=True,
check=False,
)
if result.returncode == 0:
return True
else:
return False
elif os_system == "Fedora":
result = subprocess.run(
["dnf", "list", "--installed", "|", "grep", "polkit"],
capture_output=True,
text=True,
check=False,
)
if result.returncode == 0:
return True
else:
return False
elif os_system == "SUSE Tumbleweed" or os_system == "SUSE Leap":
result = subprocess.run(
["zypper", "search", "--installed-only", "|", "grep", "polkit"],
capture_output=True,
text=True,
check=False,
)
if result.returncode == 0:
return True
else:
return False
@staticmethod
def get_networkmanager() -> bool:
"""Check if network manager is installed"""
os_system = Detector.get_os()
deb = ["Debian", "Ubuntu", "Linux Mint", "Pop!_OS"]
arch = ["Arch Linux", "Manjaro", "EndeavourOS", "ArcoLinux", "Garuda Linux"]
if os_system in deb:
result = subprocess.run(
["apt", "list", "--installed", "|", "grep", "network-manager"],
capture_output=True,
text=True,
check=False,
)
if result.returncode == 0:
return True
else:
return False
elif os_system in arch:
result = subprocess.run(
["pacman", "-Qs", "networkmanager"],
capture_output=True,
text=True,
check=False,
)
if result.returncode == 0:
return True
else:
return False
elif os_system == "Fedora":
result = subprocess.run(
["dnf", "list", "--installed", "|", "grep", "NetworkManager"],
capture_output=True,
text=True,
check=False,
)
if result.returncode == 0:
return True
else:
return False
elif os_system == "SUSE Tumbleweed" or os_system == "SUSE Leap":
result = subprocess.run(
["zypper", "search", "--installed-only", "|", "grep", "networkmanager"],
capture_output=True,
text=True,
check=False,
)
if result.returncode == 0:
return True
else:
return False
class Theme: class Theme:
@staticmethod @staticmethod
@ -180,7 +291,7 @@ class Image:
def __init__(self): def __init__(self):
self.images = {} self.images = {}
def load_image(self, image_key, fallback_paths=None) -> None | tk.PhotoImage: def load_image(self, image_key, fallback_paths=None) -> Optional[tk.PhotoImage]:
"""Load PNG image using tk.PhotoImage with fallback options""" """Load PNG image using tk.PhotoImage with fallback options"""
if image_key in self.images: if image_key in self.images:
return self.images[image_key] return self.images[image_key]
@ -228,7 +339,7 @@ class Image:
self.images[image_key] = photo self.images[image_key] = photo
return photo return photo
except tk.TclError as e: except tk.TclError as e:
print(f"{LocaleStrings.MSGP["fail_load_image"]}{path}: {e}") print(f"{LocaleStrings.MSGP['fail_load_image']}{path}: {e}")
continue continue
# Return None if no image found # Return None if no image found
@ -239,7 +350,7 @@ class AppManager:
def __init__(self): def __init__(self):
self.projects = LXToolsAppConfig.PROJECTS self.projects = LXToolsAppConfig.PROJECTS
def get_project_info(self, project_key) -> dict | None: def get_project_info(self, project_key) -> Optional[dict]:
"""Get project information by key""" """Get project information by key"""
return self.projects.get(project_key) return self.projects.get(project_key)
@ -281,10 +392,10 @@ class AppManager:
# Debug logging # Debug logging
print(LocaleStrings.MSGP["logviewer_check"]) print(LocaleStrings.MSGP["logviewer_check"])
print(f"{LocaleStrings.MSGP["symlink_exist"]}{symlink_exists}") print(f"{LocaleStrings.MSGP['symlink_exist']}{symlink_exists}")
print(f"{LocaleStrings.MSGP["executable_exist"]}{executable_exists}") print(f"{LocaleStrings.MSGP['executable_exist']}{executable_exists}")
print(f"{LocaleStrings.MSGP["is_executable"]}{executable_is_executable}") print(f"{LocaleStrings.MSGP['is_executable']}{executable_is_executable}")
print(f"{LocaleStrings.MSGP["final_result"]}{is_installed}") print(f"{LocaleStrings.MSGP['final_result']}{is_installed}")
return is_installed return is_installed
@ -310,7 +421,7 @@ class AppManager:
return version return version
return "Unknown" return "Unknown"
except Exception as e: except Exception as e:
print(f"{LocaleStrings.MSGP["get_version_error"]}{project_key}: {e}") print(f"{LocaleStrings.MSGP['get_version_error']}{project_key}: {e}")
return "Unknown" return "Unknown"
def get_latest_version(self, project_key) -> str: def get_latest_version(self, project_key) -> str:
@ -417,12 +528,98 @@ class LxTools:
y = (screen_height - height) // 2 y = (screen_height - height) // 2
window.geometry(f"{width}x{height}+{x}+{y}") window.geometry(f"{width}x{height}+{x}+{y}")
@staticmethod
def clean_files(tmp_dir: Path = None, file: Path = None) -> None:
"""
Deletes temporary files and directories for cleanup when exiting the application.
This method safely removes an optional directory defined by `AppConfig.TEMP_DIR`
and a single file to free up resources at the end of the program's execution.
All operations are performed securely, and errors such as `FileNotFoundError`
are ignored if the target files or directories do not exist.
:param tmp_dir: (Path, optional): Path to the temporary directory that should be deleted.
If `None`, the value of `AppConfig.TEMP_DIR` is used.
:param file: (Path, optional): Path to the file that should be deleted.
If `None`, no additional file will be deleted.
Returns:
None: The method does not return any value.
"""
if tmp_dir is not None:
shutil.rmtree(tmp_dir, ignore_errors=True)
try:
if file is not None:
Path.unlink(file)
except FileNotFoundError:
pass
@staticmethod
def sigi(file_path: Optional[Path] = None, file: Optional[Path] = None) -> None:
"""
Function for cleanup after a program interruption
:param file: Optional - File to be deleted
:param file_path: Optional - Directory to be deleted
"""
def signal_handler(signum: int, frame: Any) -> NoReturn:
"""
Determines clear text names for signal numbers and handles signals
Args:
signum: The signal number
frame: The current stack frame
Returns:
NoReturn since the function either exits the program or continues execution
"""
signals_to_names_dict: Dict[int, str] = dict(
(getattr(signal, n), n)
for n in dir(signal)
if n.startswith("SIG") and "_" not in n
)
signal_name: str = signals_to_names_dict.get(
signum, f"Unnamed signal: {signum}"
)
# End program for certain signals, report to others only reception
if signum in (signal.SIGINT, signal.SIGTERM):
exit_code: int = 1
logging.error(
f"\nSignal {signal_name} {signum} received. => Aborting with exit code {exit_code}.",
exc_info=True,
)
LxTools.clean_files(file_path, file)
logging.info("Breakdown by user...")
sys.exit(exit_code)
else:
logging.info(f"Signal {signum} received and ignored.")
LxTools.clean_files(file_path, file)
logging.error("Process unexpectedly ended...")
# Register signal handlers for various signals
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGHUP, signal_handler)
@staticmethod
def remove_lxtools_files() -> None:
if getattr(sys, "_MEIPASS", None) is not None:
shutil.rmtree("./locale")
shutil.rmtree("./TK-Themes")
shutil.rmtree("./lx-icons")
class LXToolsAppConfig: class LXToolsAppConfig:
@staticmethod @staticmethod
def extract_data_files() -> None: def extract_data_files() -> None:
if getattr(sys, "_MEIPASS", None) is not None: if getattr(sys, "_MEIPASS", None) is not None:
os.makedirs("/tmp/lxtools", exist_ok=True)
# Liste der Quellordner (entspricht dem "datas"-Eintrag in lxtools_installer.spec) # Liste der Quellordner (entspricht dem "datas"-Eintrag in lxtools_installer.spec)
source_dirs = [ source_dirs = [
os.path.join(sys._MEIPASS, "locale"), # für locale/... os.path.join(sys._MEIPASS, "locale"), # für locale/...
@ -437,7 +634,7 @@ class LXToolsAppConfig:
for source_dir in source_dirs: for source_dir in source_dirs:
group_name = os.path.basename( group_name = os.path.basename(
source_dir source_dir
) # Erhält den Gruppen-Name (z.B. 'locale', 'TK-Themes') ) # Erhält den Gruppen-Name (z. B. 'locale', 'TK-Themes')
for root, dirs, files in os.walk(source_dir): for root, dirs, files in os.walk(source_dir):
for file in files: for file in files:
@ -476,15 +673,15 @@ class LXToolsAppConfig:
pass pass
return gettext.gettext return gettext.gettext
VERSION = "1.1.6" VERSION = "1.1.7"
APP_NAME = "lxtoolsinstaller" APP_NAME = "lxtoolsinstaller"
WINDOW_WIDTH = 450 WINDOW_WIDTH = 450
WINDOW_HEIGHT = 580 WINDOW_HEIGHT = 580
# Working directory # Working directory
WORK_DIR = os.getcwd() WORK_DIR = os.getcwd()
ICONS_DIR = os.path.join(WORK_DIR, "lx-icons") ICONS_DIR = os.path.join(WORK_DIR, "lx-icons")
THEMES_DIR = os.path.join(WORK_DIR, "TK-Themes") THEMES_DIR = os.path.join(WORK_DIR, "TK-Themes")
TEMP_DIR = "/tmp/lxtools"
# Locale settings # Locale settings
LOCALE_DIR = "./locale/" LOCALE_DIR = "./locale/"
@ -618,6 +815,8 @@ class LocaleStrings:
"progress": _("Progress"), "progress": _("Progress"),
"refresh2": _("Status refresh completed"), "refresh2": _("Status refresh completed"),
"python_check": _("Python not installed"), "python_check": _("Python not installed"),
"polkit_check": _("Please install Polkit!"),
"networkmanager_check": _("Please install Networkmanager!"),
} }
# MSGC = Strings on Cards # MSGC = Strings on Cards

View File

@ -129,7 +129,7 @@ class MessageDialog:
self.window = tk.Toplevel(master) self.window = tk.Toplevel(master)
self.window.grab_set() self.window.grab_set()
self.window.resizable(False, False) self.window.resizable(False, False)
ttk.Style().configure("TButton", font=("Helvetica", 11), padding=5) ttk.Style().configure("TButton")
self.buttons_widgets = [] self.buttons_widgets = []
self.current_button_index = 0 self.current_button_index = 0
self._load_icons() self._load_icons()