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

View File

@ -1,8 +1,11 @@
import locale
import gettext
import signal
import tkinter as tk
from tkinter import ttk
from pathlib import Path
from typing import Optional, NoReturn, Any, Dict
import logging
import os
import sys
import shutil
@ -96,6 +99,114 @@ class Detector:
else:
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:
@staticmethod
@ -180,7 +291,7 @@ class Image:
def __init__(self):
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"""
if image_key in self.images:
return self.images[image_key]
@ -228,7 +339,7 @@ class Image:
self.images[image_key] = photo
return photo
except tk.TclError as e:
print(f"{LocaleStrings.MSGP["fail_load_image"]}{path}: {e}")
print(f"{LocaleStrings.MSGP['fail_load_image']}{path}: {e}")
continue
# Return None if no image found
@ -239,7 +350,7 @@ class AppManager:
def __init__(self):
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"""
return self.projects.get(project_key)
@ -281,10 +392,10 @@ class AppManager:
# Debug logging
print(LocaleStrings.MSGP["logviewer_check"])
print(f"{LocaleStrings.MSGP["symlink_exist"]}{symlink_exists}")
print(f"{LocaleStrings.MSGP["executable_exist"]}{executable_exists}")
print(f"{LocaleStrings.MSGP["is_executable"]}{executable_is_executable}")
print(f"{LocaleStrings.MSGP["final_result"]}{is_installed}")
print(f"{LocaleStrings.MSGP['symlink_exist']}{symlink_exists}")
print(f"{LocaleStrings.MSGP['executable_exist']}{executable_exists}")
print(f"{LocaleStrings.MSGP['is_executable']}{executable_is_executable}")
print(f"{LocaleStrings.MSGP['final_result']}{is_installed}")
return is_installed
@ -310,7 +421,7 @@ class AppManager:
return version
return "Unknown"
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"
def get_latest_version(self, project_key) -> str:
@ -417,12 +528,98 @@ class LxTools:
y = (screen_height - height) // 2
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:
@staticmethod
def extract_data_files() -> 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)
source_dirs = [
os.path.join(sys._MEIPASS, "locale"), # für locale/...
@ -437,7 +634,7 @@ class LXToolsAppConfig:
for source_dir in source_dirs:
group_name = os.path.basename(
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 file in files:
@ -476,15 +673,15 @@ class LXToolsAppConfig:
pass
return gettext.gettext
VERSION = "1.1.6"
VERSION = "1.1.7"
APP_NAME = "lxtoolsinstaller"
WINDOW_WIDTH = 450
WINDOW_HEIGHT = 580
# Working directory
WORK_DIR = os.getcwd()
ICONS_DIR = os.path.join(WORK_DIR, "lx-icons")
THEMES_DIR = os.path.join(WORK_DIR, "TK-Themes")
TEMP_DIR = "/tmp/lxtools"
# Locale settings
LOCALE_DIR = "./locale/"
@ -618,6 +815,8 @@ class LocaleStrings:
"progress": _("Progress"),
"refresh2": _("Status refresh completed"),
"python_check": _("Python not installed"),
"polkit_check": _("Please install Polkit!"),
"networkmanager_check": _("Please install Networkmanager!"),
}
# MSGC = Strings on Cards