From 2f4bff710f5f45832807c45098af433fa7981f0f Mon Sep 17 00:00:00 2001 From: George Sandeep Date: Tue, 14 Jul 2026 19:56:07 +0530 Subject: [PATCH 1/7] feat(qt): add package structure and worker bridge --- src/toolManager/qt/__init__.py | 0 src/toolManager/qt/worker.py | 61 ++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 src/toolManager/qt/__init__.py create mode 100644 src/toolManager/qt/worker.py diff --git a/src/toolManager/qt/__init__.py b/src/toolManager/qt/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/toolManager/qt/worker.py b/src/toolManager/qt/worker.py new file mode 100644 index 000000000..45b74f490 --- /dev/null +++ b/src/toolManager/qt/worker.py @@ -0,0 +1,61 @@ +from PyQt6.QtCore import QThread, pyqtSignal +import subprocess +import sys + +try: + from toolManager.worker import BaseWorker, CommandWorker, InstallWorker, InstallerThread +except ImportError: + from toolManager.gui_fixed import CommandWorker + from toolManager.updater_gui import InstallerThread + + class BaseWorker(QThread): + def cancel(self): + pass + + try: + from toolManager.worker import InstallWorker + except ImportError: + from pathlib import Path + from toolManager.registry import TOOLS + + BASE_DIR = Path(__file__).resolve().parent.parent + PYTHON = sys.executable + + class InstallWorker(QThread): + progress = pyqtSignal(str) + finished = pyqtSignal(bool) + + def __init__(self, tools): + super().__init__() + self.tools = tools + + def run(self): + success = True + backend = str(BASE_DIR / "tool_manager_windows.py") + for tool, version in self.tools: + label = TOOLS[tool].label if tool in TOOLS else tool + self.progress.emit(f"Installing {label} {version}...") + try: + proc = subprocess.Popen( + [PYTHON, backend, "install", tool, version], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + encoding="utf-8", + errors="ignore", + ) + for line in proc.stdout: + line = line.rstrip() + if line: + self.progress.emit(f" {line}") + if "[ERROR]" in line or "install_failed|" in line: + success = False + proc.wait() + if proc.returncode != 0: + success = False + except Exception as e: + self.progress.emit(f" [ERROR] {tool}: {e}") + success = False + self.finished.emit(success) + +__all__ = ["BaseWorker", "CommandWorker", "InstallWorker", "InstallerThread"] From 598f3797ab1939502b8723d96accd6e5df93b07a Mon Sep 17 00:00:00 2001 From: George Sandeep Date: Tue, 14 Jul 2026 19:56:21 +0530 Subject: [PATCH 2/7] feat(qt): add gui_fixed.py adapter --- src/toolManager/qt/gui_fixed.py | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/toolManager/qt/gui_fixed.py diff --git a/src/toolManager/qt/gui_fixed.py b/src/toolManager/qt/gui_fixed.py new file mode 100644 index 000000000..06e61a99b --- /dev/null +++ b/src/toolManager/qt/gui_fixed.py @@ -0,0 +1,9 @@ +from toolManager.gui_fixed import ToolManagerGUI, UninstallWindow, CommandWorker +from toolManager.registry import TOOLS +from toolManager.constants import IS_WINDOWS, IS_LINUX +from toolManager.paths import get_toolmanager_root + +__all__ = [ + "ToolManagerGUI", "UninstallWindow", "CommandWorker", + "TOOLS", "IS_WINDOWS", "IS_LINUX", "get_toolmanager_root", +] From 5a6aa5acc7baa36dc1c88b83a3af3104e36bbddb Mon Sep 17 00:00:00 2001 From: George Sandeep Date: Tue, 14 Jul 2026 19:56:25 +0530 Subject: [PATCH 3/7] feat(qt): add main.py adapter --- src/toolManager/qt/main.py | 49 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/toolManager/qt/main.py diff --git a/src/toolManager/qt/main.py b/src/toolManager/qt/main.py new file mode 100644 index 000000000..98dfbc2bf --- /dev/null +++ b/src/toolManager/qt/main.py @@ -0,0 +1,49 @@ +from toolManager.main import ToolManagerWindow, main +from toolManager.registry import TOOLS, CATEGORIES +from toolManager.constants import IS_WINDOWS +from toolManager.qt.gui_fixed import ToolManagerGUI + +ANALOG_TOOLS = CATEGORIES["analog"] +DIGITAL_TOOLS = CATEGORIES["digital"] +TOOL_LABELS = {tid: spec.label for tid, spec in TOOLS.items() + if tid in CATEGORIES["analog"] or tid in CATEGORIES["digital"]} +TOOL_VERSIONS = { + "esim": "2.4", + "kicad": "latest", + "ngspice": "latest", + "ghdl": "latest", + "verilator": "latest", + "llvm": "latest", +} + +try: + from toolManager.platform_utils import is_admin, relaunch_as_admin +except ImportError: + import ctypes + import os + import sys + + def is_admin(): + if IS_WINDOWS: + try: + return ctypes.windll.shell32.IsUserAnAdmin() + except Exception: + return False + return True + + def relaunch_as_admin(): + script = sys.argv[0] + python_exe = sys.executable + if python_exe.lower().endswith("python.exe"): + pythonw_exe = python_exe[:-10] + "pythonw.exe" + if os.path.exists(pythonw_exe): + python_exe = pythonw_exe + ctypes.windll.shell32.ShellExecuteW( + None, "runas", python_exe, f'"{script}"', None, 1 + ) + +__all__ = [ + "ToolManagerWindow", "main", "ToolManagerGUI", + "TOOL_LABELS", "TOOL_VERSIONS", "ANALOG_TOOLS", "DIGITAL_TOOLS", + "IS_WINDOWS", "is_admin", "relaunch_as_admin", +] From df91ad8d8fcf3d6779cd60572ce49e0273cc5223 Mon Sep 17 00:00:00 2001 From: George Sandeep Date: Tue, 14 Jul 2026 19:56:29 +0530 Subject: [PATCH 4/7] feat(qt): add updater_gui.py adapter --- src/toolManager/qt/updater_gui.py | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/toolManager/qt/updater_gui.py diff --git a/src/toolManager/qt/updater_gui.py b/src/toolManager/qt/updater_gui.py new file mode 100644 index 000000000..9075386bc --- /dev/null +++ b/src/toolManager/qt/updater_gui.py @@ -0,0 +1,4 @@ +from toolManager.updater_gui import PackageUpdaterWindow, main +from toolManager.registry import TOOLS + +__all__ = ["PackageUpdaterWindow", "main", "TOOLS"] From cb7b9c0c5f6343fae43b13afee7014c0dd01474d Mon Sep 17 00:00:00 2001 From: George Sandeep Date: Tue, 14 Jul 2026 19:56:44 +0530 Subject: [PATCH 5/7] refactor(gui_fixed): replace inline OS detection with constants import --- src/toolManager/gui_fixed.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/toolManager/gui_fixed.py b/src/toolManager/gui_fixed.py index 1e96f0010..5d9979ae0 100644 --- a/src/toolManager/gui_fixed.py +++ b/src/toolManager/gui_fixed.py @@ -3,7 +3,6 @@ import sys import ctypes import subprocess -import platform from PyQt6.QtWidgets import ( QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem, QHeaderView, QProgressBar, @@ -15,12 +14,12 @@ from PyQt6.QtGui import QFont import logging -PYTHON = sys.executable -SYSTEM = platform.system() -IS_WINDOWS = SYSTEM == "Windows" -IS_LINUX = SYSTEM == "Linux" +try: + from toolManager.constants import IS_WINDOWS, IS_LINUX +except (ImportError, ValueError): + from constants import IS_WINDOWS, IS_LINUX -import os +PYTHON = sys.executable BASE_DIR = os.path.dirname(os.path.abspath(__file__)) if IS_WINDOWS: From 34fde2c109f22ddc18a703017f1d3683af066538 Mon Sep 17 00:00:00 2001 From: George Sandeep Date: Tue, 14 Jul 2026 19:59:29 +0530 Subject: [PATCH 6/7] refactor(main): use IS_WINDOWS from constants, fix import guard, remove dead code and comments --- src/toolManager/main.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/toolManager/main.py b/src/toolManager/main.py index e005821de..6c5ae16d7 100644 --- a/src/toolManager/main.py +++ b/src/toolManager/main.py @@ -14,14 +14,17 @@ from PyQt6.QtGui import QFont try: - from gui_fixed import ToolManagerGUI + from toolManager.gui_fixed import ToolManagerGUI except ImportError: - pass # Will handle gracefully later if missing + from gui_fixed import ToolManagerGUI + +try: + from toolManager.constants import IS_WINDOWS +except (ImportError, ValueError): + from constants import IS_WINDOWS -# ==================== CONFIG ==================== BASE_DIR = Path(__file__).resolve().parent INFO_JSON = BASE_DIR / "information.json" -FULL_GUI = BASE_DIR / "gui_fixed.py" PYTHON = sys.executable ANALOG_TOOLS = ["esim", "kicad", "ngspice"] @@ -54,7 +57,6 @@ def is_admin(): def relaunch_as_admin(): script = str(Path(__file__).resolve()) - # Swap to pythonw.exe to suppress the black console window python_exe = PYTHON if python_exe.lower().endswith("python.exe"): pythonw_exe = python_exe[:-10] + "pythonw.exe" @@ -126,7 +128,6 @@ def run(self): self.finished.emit(success) -# ==================== MAIN WINDOW ==================== class ToolManagerWindow(QMainWindow): def __init__(self): super().__init__() @@ -396,7 +397,6 @@ def _create_uninstall_tab(self): layout.setContentsMargins(30, 30, 30, 30) layout.setSpacing(16) - # Warning banner warn = QFrame() warn.setStyleSheet(""" QFrame { @@ -656,7 +656,7 @@ def _run_uninstall(self, tools, label): subprocess.Popen( [PYTHON, backend, "uninstall", tool, "none"], creationflags=(subprocess.CREATE_NO_WINDOW - if sys.platform == "win32" else 0) + if IS_WINDOWS else 0) ) QMessageBox.information( self, "Uninstall Started", @@ -669,9 +669,7 @@ def _run_uninstall(self, tools, label): def main(): - if sys.platform == "win32" and not is_admin(): - # Note: We deliberately skip a manual PyQt consent dialog here because - # Windows will natively prompt the user with a UAC shield anyway. + if IS_WINDOWS and not is_admin(): relaunch_as_admin() sys.exit(0) From 1b872208a72be7035553cdd81a57d89b7b3de3ad Mon Sep 17 00:00:00 2001 From: George Sandeep Date: Tue, 14 Jul 2026 19:59:55 +0530 Subject: [PATCH 7/7] refactor(main): derive tool lists from registry CATEGORIES --- src/toolManager/main.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/toolManager/main.py b/src/toolManager/main.py index 6c5ae16d7..445c00549 100644 --- a/src/toolManager/main.py +++ b/src/toolManager/main.py @@ -23,12 +23,17 @@ except (ImportError, ValueError): from constants import IS_WINDOWS +try: + from toolManager.registry import CATEGORIES +except (ImportError, ValueError): + from registry import CATEGORIES + BASE_DIR = Path(__file__).resolve().parent INFO_JSON = BASE_DIR / "information.json" PYTHON = sys.executable -ANALOG_TOOLS = ["esim", "kicad", "ngspice"] -DIGITAL_TOOLS = ["esim", "kicad", "ngspice", "ghdl", "verilator", "llvm"] +ANALOG_TOOLS = CATEGORIES["analog"] +DIGITAL_TOOLS = CATEGORIES["digital"] TOOL_LABELS = { "esim": "eSim",