Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 15 additions & 12 deletions src/toolManager/gui_fixed.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import sys
import ctypes
import subprocess
import platform
from pathlib import Path
from platform_utils import IS_WINDOWS, IS_LINUX, IS_MAC
from PyQt6.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QTableWidget, QTableWidgetItem, QHeaderView, QProgressBar,
Expand All @@ -15,20 +16,20 @@
from PyQt6.QtGui import QFont
import logging

PYTHON = sys.executable
SYSTEM = platform.system()
IS_WINDOWS = SYSTEM == "Windows"
IS_LINUX = SYSTEM == "Linux"

import os
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
PYTHON = sys.executable

BASE_DIR = Path(__file__).resolve().parent

if IS_WINDOWS:
BACKEND = os.path.join(BASE_DIR, "tool_manager_windows.py")
BACKEND = BASE_DIR / "tool_manager_windows.py"
elif IS_LINUX:
BACKEND = os.path.join(BASE_DIR, "tool_manager_linux.py")
BACKEND = BASE_DIR / "tool_manager_linux.py"
elif IS_MAC:
BACKEND = BASE_DIR / "tool_manager_macos.py"
else:
BACKEND = os.path.join(os.path.dirname(os.path.abspath(__file__)), "tool_manager_windows.py")
BACKEND = BASE_DIR / "tool_manager_linux.py"


TOOLS = {
"esim": {
Expand Down Expand Up @@ -101,7 +102,9 @@ def run(self):
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True
text=True,
encoding="utf-8",
errors="ignore"
)
process.stdin.write(self.password + "\n")
process.stdin.flush()
Expand Down Expand Up @@ -402,7 +405,7 @@ def get_sudo_password(self):
if IS_LINUX and self.sudo_password is None:
password, ok = QInputDialog.getText(
self, "Authentication Required",
"Enter your sudo password:", QLineEdit.Password
"Enter your sudo password:", QLineEdit.EchoMode.Password
)
if ok and password:
self.sudo_password = password
Expand Down
34 changes: 28 additions & 6 deletions src/toolManager/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
)
from PyQt6.QtCore import Qt, QThread, pyqtSignal
from PyQt6.QtGui import QFont
from platform_utils import IS_WINDOWS, IS_LINUX, IS_MAC, distro_label

try:
from gui_fixed import ToolManagerGUI
Expand All @@ -21,7 +22,24 @@
# ==================== CONFIG ====================
BASE_DIR = Path(__file__).resolve().parent
INFO_JSON = BASE_DIR / "information.json"
FULL_GUI = BASE_DIR / "gui_fixed.py"

if IS_WINDOWS:
BACKEND = BASE_DIR / "tool_manager_windows.py"
FULL_GUI = BASE_DIR / "gui_fixed.py"
OS_LABEL = "Windows Edition"
elif IS_LINUX:
BACKEND = BASE_DIR / "tool_manager_linux.py"
FULL_GUI = BASE_DIR / "updater_gui.py"
OS_LABEL = f"{distro_label()} Edition"
elif IS_MAC:
BACKEND = BASE_DIR / "tool_manager_macos.py"
FULL_GUI = BASE_DIR / "updater_gui.py"
OS_LABEL = f"{distro_label()} Edition"
else:
BACKEND = BASE_DIR / "tool_manager_linux.py"
FULL_GUI = BASE_DIR / "updater_gui.py"
OS_LABEL = "Linux Edition"

PYTHON = sys.executable

ANALOG_TOOLS = ["esim", "kicad", "ngspice"]
Expand All @@ -46,12 +64,16 @@
}

def is_admin():
if not IS_WINDOWS:
return True
try:
return ctypes.windll.shell32.IsUserAnAdmin()
except Exception:
return False

def relaunch_as_admin():
if not IS_WINDOWS:
return True
script = str(Path(__file__).resolve())

# Swap to pythonw.exe to suppress the black console window
Expand Down Expand Up @@ -97,7 +119,7 @@ def __init__(self, tools):

def run(self):
success = True
backend = str(BASE_DIR / "tool_manager_windows.py")
backend = str(BACKEND)
for tool, version in self.tools:
self.progress.emit(
f"Installing {TOOL_LABELS.get(tool, tool)} {version}..."
Expand Down Expand Up @@ -193,7 +215,7 @@ def _create_header(self):
t1 = QLabel("eSim Tool Manager")
t1.setFont(QFont("Arial", 24, QFont.Weight.Bold))
t1.setStyleSheet("color: white; background: transparent;")
t2 = QLabel("Package Management System • Windows Edition")
t2 = QLabel(OS_LABEL)
t2.setFont(QFont("Arial", 11))
t2.setStyleSheet("color: rgba(255,255,255,0.9); background: transparent;")
vbox.addWidget(t1)
Expand Down Expand Up @@ -651,12 +673,12 @@ def _uninstall_all(self):

def _run_uninstall(self, tools, label):
try:
backend = str(BASE_DIR / "tool_manager_windows.py")
backend = str(BACKEND)
for tool, _ in tools:
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",
Expand All @@ -669,7 +691,7 @@ def _run_uninstall(self, tools, label):


def main():
if sys.platform == "win32" and not is_admin():
if IS_WINDOWS 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.
relaunch_as_admin()
Expand Down
9 changes: 4 additions & 5 deletions src/toolManager/tool_manager_windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import time
import io
from pathlib import Path
from platform_utils import IS_WINDOWS, MSYS2_PATH

# Add local directory to path for backend utility imports
_local_path = str(Path(__file__).resolve().parent)
Expand All @@ -21,21 +22,19 @@

from utils import (
run_cmd_safe, run_cmd_stream, which, print_status,
DEFAULT_MSYS2_PATH, DEFAULT_ESIM_DIR, WIN_KICAD_PATHS,
DEFAULT_ESIM_DIR, WIN_KICAD_PATHS,
WIN_NGSPICE_PATHS, WIN_LLVM_PATHS, get_msys2_bash,
get_msys2_mingw_bin, get_msys2_mingw_root
)

if sys.platform == "win32":
if IS_WINDOWS:
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='ignore')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='ignore')

BASE_DIR = Path(__file__).resolve().parent
STATE_FILE = BASE_DIR / "information.json"
BASE_DIR.mkdir(parents=True, exist_ok=True)

MSYS2_PATH = DEFAULT_MSYS2_PATH

DOWNLOAD_DIR = BASE_DIR / "Download"
DOWNLOAD_DIR.mkdir(parents=True, exist_ok=True)

Expand Down Expand Up @@ -202,7 +201,7 @@ def _find_ngspice_exe():
return which("ngspice") or which("ngspice.exe")

def find_llvm_fixed(version=None):
if platform.system() == "Windows":
if IS_WINDOWS:
import ctypes
HWND_BROADCAST = 0xFFFF
WM_SETTINGCHANGE = 0x001A
Expand Down
7 changes: 6 additions & 1 deletion src/toolManager/updater_gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
QAbstractItemView)
from PyQt6.QtCore import Qt, QThread, pyqtSignal
from PyQt6.QtGui import QFont
from platform_utils import IS_WINDOWS, distro_label

class InstallerThread(QThread):
progress = pyqtSignal(str, int)
Expand All @@ -22,6 +23,10 @@ def __init__(self, packages_to_install):
self.packages = packages_to_install

def run(self):
if IS_WINDOWS:
self.finished.emit(False, "Package Updater is Linux only.\nOn Windows, use the main Tool Manager.")
return

total = len(self.packages)
for pkg_idx, (package_name, version, script_name) in enumerate(self.packages):
try:
Expand Down Expand Up @@ -368,7 +373,7 @@ def create_header(self):
title.setFont(QFont("Segoe UI", 14, QFont.Weight.Bold))
title.setStyleSheet("color: white; background: transparent;")

subtitle = QLabel("Real-time progress • Ngspice 35-43 • Ubuntu 22.04")
subtitle = QLabel(f"Real-time progress • Ngspice 35-43 • {distro_label()}")
subtitle.setFont(QFont("Segoe UI", 8))
subtitle.setStyleSheet("color: rgba(255,255,255,0.9); background: transparent;")

Expand Down
35 changes: 13 additions & 22 deletions src/toolManager/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,18 @@
import threading
import time
from pathlib import Path
from platform_utils import IS_WINDOWS, get_mysys2_path

# ==================== CONSTANTS & DEFAULTS ====================

# Default Windows installation paths
DEFAULT_MSYS2_PATH = Path(r"C:\msys64")
DEFAULT_ESIM_DIR = Path(r"C:\FOSSEE\eSim")

try:
MSYS2_PATH = get_mysys2_path();
except RuntimeError as e:
print(f"[ERROR]: {e}", flush=True)

WIN_KICAD_PATHS = [
(r"C:\Program Files\KiCad\9.0\bin\kicad.exe", "9"),
(r"C:\Program Files\KiCad\8.0\bin\kicad.exe", "8"),
Expand All @@ -49,10 +54,6 @@
# Locations for MSYS2/MinGW64. The FOSSEE fallback supports the bundled
# MSYS2 environment provided by some eSim installers to ensure
# zero-dependency operation.
FOSSEE_MSYS_CANDIDATES = [
Path(r"C:\msys64\mingw64"),
Path(r"C:\FOSSEE\MSYS\mingw64"),
]

# ==================== HELPERS ====================

Expand All @@ -62,7 +63,7 @@ def run_cmd_safe(cmd, timeout=30, cwd=None, env=None):
Returns the subprocess.CompletedProcess result or None if failed.
"""
try:
if platform.system() == "Windows":
if IS_WINDOWS:
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
Expand Down Expand Up @@ -95,7 +96,7 @@ def run_cmd_stream(cmd, timeout=900, cwd=None, env=None):
Returns a tuple of (returncode, full_output_string).
"""
try:
if platform.system() == "Windows":
if IS_WINDOWS:
si = subprocess.STARTUPINFO()
si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
si.wShowWindow = subprocess.SW_HIDE
Expand Down Expand Up @@ -163,24 +164,14 @@ def get_msys2_bash():
"""
Returns the path to MSYS2 bash.exe if found, else None.
"""
bash_path = DEFAULT_MSYS2_PATH / "usr" / "bin" / "bash.exe"
bash_path = MSYS2_PATH / "usr" / "bin" / "bash.exe"
return bash_path if bash_path.exists() else None

def get_msys2_mingw_root():
"""
Returns the path to MSYS2 mingw64 root if found, else None.
"""
for candidate in FOSSEE_MSYS_CANDIDATES:
if candidate.exists():
return candidate
return None

def get_msys2_mingw_bin():
"""
Returns the path to MSYS2 mingw64/bin if found, else None.
"""
for candidate in FOSSEE_MSYS_CANDIDATES:
bin_dir = candidate / "bin"
if bin_dir.exists():
return bin_dir
return None

bin_dir = MSYS2_PATH / "bin"
if bin_dir.exists():
return bin_dir