Added progress bar and abort button
Some checks failed
Version Change Action / version-release (push) Has been cancelled

This commit is contained in:
Nix 2025-11-28 01:19:20 -03:00 committed by GitHub
parent 971b9a07d9
commit 30e714ac30
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 211 additions and 16 deletions

200
loaddaemon.py Normal file
View File

@ -0,0 +1,200 @@
import sys
import threading
import time
import shlex
import gc
from io import StringIO
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QLabel, QProgressBar, QPushButton, QHBoxLayout
from PyQt5.QtCore import Qt, pyqtSignal, QObject, QTimer, QEvent
from PyQt5.QtGui import QFont
class LaunchSignals(QObject):
log_update = pyqtSignal(str)
launch_complete = pyqtSignal()
launch_aborted = pyqtSignal()
cleanup_done = pyqtSignal()
class AbortException(Exception):
pass
class StreamingCapture(StringIO):
def __init__(self, log_signal, launch_signal):
super().__init__()
self.log_signal = log_signal
self.launch_signal = launch_signal
self.launch_detected = False
self.abort_requested = False
def write(self, text):
if self.abort_requested:
raise AbortException("Launch aborted by user")
if text and text.strip():
# Check if signal is still valid before emitting
try:
self.log_signal.emit(text.strip())
except RuntimeError:
# Signal/Object might be deleted
pass
if not self.launch_detected:
lower_text = text.lower()
if "launching" in lower_text and ("game" in lower_text or "version" in lower_text or "minecraft" in lower_text):
self.launch_detected = True
try:
self.launch_signal.emit()
except RuntimeError:
pass
return super().write(text)
class LaunchWindow(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Launching Minecraft")
self.setModal(True)
self.resize(400, 150)
layout = QVBoxLayout()
self.status_label = QLabel("Initializing...")
self.status_label.setWordWrap(True)
self.status_label.setAlignment(Qt.AlignCenter)
layout.addWidget(self.status_label)
self.progress_bar = QProgressBar()
self.progress_bar.setRange(0, 0) # Indeterminate progress
self.progress_bar.setTextVisible(False)
layout.addWidget(self.progress_bar)
# Add a manual close/cancel button
button_layout = QHBoxLayout()
button_layout.addStretch()
self.cancel_button = QPushButton("Cancel")
self.cancel_button.clicked.connect(self.request_abort)
button_layout.addWidget(self.cancel_button)
layout.addLayout(button_layout)
self.setLayout(layout)
self.signals = LaunchSignals()
self.signals.log_update.connect(self.update_status)
self.signals.launch_complete.connect(self.on_launch_complete)
self.signals.launch_aborted.connect(self.on_launch_aborted)
self.signals.cleanup_done.connect(self.on_cleanup_done)
self.launch_detected = False
self.closing_scheduled = False
self.aborting = False
self.capture_streams = []
self.thread_running = False
def update_status(self, text):
if len(text) > 100:
text = text[:97] + "..."
self.status_label.setText(text)
def on_launch_complete(self):
if not self.closing_scheduled and not self.aborting:
self.closing_scheduled = True
self.status_label.setText("Game Launched! Closing window...")
self.progress_bar.setRange(0, 100)
self.progress_bar.setValue(100)
self.cancel_button.setEnabled(False)
QTimer.singleShot(3000, self.accept)
def on_launch_aborted(self):
self.status_label.setText("Launch Aborted.")
self.progress_bar.setRange(0, 100)
self.progress_bar.setValue(0)
def on_cleanup_done(self):
self.thread_running = False
# Now it is safe to close the window
super().reject()
def request_abort(self):
if self.thread_running and not self.aborting:
self.aborting = True
self.status_label.setText("Aborting...")
self.cancel_button.setEnabled(False)
# Signal streams to stop
for stream in self.capture_streams:
stream.abort_requested = True
elif not self.thread_running:
super().reject()
def reject(self):
# Handle Esc key or X button
self.request_abort()
def closeEvent(self, event):
if self.thread_running:
event.ignore()
self.request_abort()
else:
event.accept()
def launch_game(self, command):
self.thread_running = True
thread = threading.Thread(target=self._run_launch, args=(command,), daemon=True)
thread.start()
def _run_launch(self, command):
try:
modules_to_remove = [mod for mod in sys.modules if mod.startswith('zucaro')]
for mod in modules_to_remove:
del sys.modules[mod]
gc.collect()
from zucaro.cli.main import zucaro_cli
old_stdout, old_stderr = sys.stdout, sys.stderr
stdout_capture = StreamingCapture(self.signals.log_update, self.signals.launch_complete)
stderr_capture = StreamingCapture(self.signals.log_update, self.signals.launch_complete)
self.capture_streams = [stdout_capture, stderr_capture]
sys.stdout = stdout_capture
sys.stderr = stderr_capture
try:
zucaro_cli.main(args=shlex.split(command), standalone_mode=False)
except AbortException:
self.signals.launch_aborted.emit()
except SystemExit:
pass
except Exception as e:
self.signals.log_update.emit(f"Error: {str(e)}")
finally:
sys.stdout = old_stdout
sys.stderr = old_stderr
if not stdout_capture.launch_detected and not stderr_capture.launch_detected and not stdout_capture.abort_requested:
self.signals.launch_complete.emit()
modules_to_remove = [mod for mod in sys.modules if mod.startswith('zucaro')]
for mod in modules_to_remove:
del sys.modules[mod]
gc.collect()
self.signals.cleanup_done.emit()
except Exception as e:
try:
self.signals.log_update.emit(f"Error launching game: {str(e)}")
self.signals.cleanup_done.emit()
except:
pass
def launch_instance_with_window(command, parent=None):
window = LaunchWindow(parent)
window.launch_game(command)
window.exec_()
return window

View File

@ -14,6 +14,7 @@ import time
from authser import MinecraftAuthenticator from authser import MinecraftAuthenticator
from healthcheck import HealthCheck from healthcheck import HealthCheck
import modulecli import modulecli
import loaddaemon
from PyQt5.QtWidgets import QApplication, QComboBox, QWidget, QInputDialog, QVBoxLayout, QListWidget, QSpinBox, QFileDialog, QPushButton, QMessageBox, QDialog, QHBoxLayout, QLabel, QLineEdit, QCheckBox, QTabWidget, QFrame, QSpacerItem, QSizePolicy, QMainWindow, QGridLayout, QTextEdit, QListWidget, QListWidgetItem, QMenu from PyQt5.QtWidgets import QApplication, QComboBox, QWidget, QInputDialog, QVBoxLayout, QListWidget, QSpinBox, QFileDialog, QPushButton, QMessageBox, QDialog, QHBoxLayout, QLabel, QLineEdit, QCheckBox, QTabWidget, QFrame, QSpacerItem, QSizePolicy, QMainWindow, QGridLayout, QTextEdit, QListWidget, QListWidgetItem, QMenu
from PyQt5.QtGui import QFont, QIcon, QColor, QPalette, QMovie, QPixmap, QDesktopServices, QBrush from PyQt5.QtGui import QFont, QIcon, QColor, QPalette, QMovie, QPixmap, QDesktopServices, QBrush
@ -893,16 +894,14 @@ class zucaroVersionSelector(QWidget):
QMessageBox.warning(self, "No Instance Selected", "Please select an instance.") QMessageBox.warning(self, "No Instance Selected", "Please select an instance.")
return return
play_thread = threading.Thread(target=self.run_game, args=(selected_instance,)) self.launch_game_with_window(selected_instance)
play_thread.start()
def run_game(self, selected_instance): def launch_game_with_window(self, selected_instance):
try: try:
self.current_state = selected_instance self.current_state = selected_instance
self.start_time = time.time() self.start_time = time.time()
# Read config
with open('config.json', 'r') as config_file: with open('config.json', 'r') as config_file:
config = json.load(config_file) config = json.load(config_file)
instance_value = config.get("Instance", "default") instance_value = config.get("Instance", "default")
@ -910,11 +909,9 @@ class zucaroVersionSelector(QWidget):
manage_java = config.get("ManageJava", False) manage_java = config.get("ManageJava", False)
java_path = config.get("JavaPath", "") java_path = config.get("JavaPath", "")
# Update last played on a thread update_thread = threading.Thread(target=self.update_last_played, args=(selected_instance,), daemon=True)
update_thread = threading.Thread(target=self.update_last_played, args=(selected_instance,))
update_thread.start() update_thread.start()
# Build command
command = f"instance launch {instance_value} --version-override {selected_instance} --assigned-ram {max_ram}" command = f"instance launch {instance_value} --version-override {selected_instance} --assigned-ram {max_ram}"
if manage_java: if manage_java:
command += " --manage-java" command += " --manage-java"
@ -922,17 +919,14 @@ class zucaroVersionSelector(QWidget):
command += f" --java {java_path}" command += f" --java {java_path}"
print(f"Launching command: {command}") print(f"Launching command: {command}")
output = modulecli.run_command(command) loaddaemon.launch_instance_with_window(command, self)
if not output:
raise Exception("Failed to get output from modulecli")
print(f"modulecli output: {output}")
except Exception as e: except Exception as e:
error_message = f"Error playing {selected_instance}: {e}" error_message = f"Error playing {selected_instance}: {e}"
print(error_message) # Add this for debugging print(error_message)
logging.error(error_message) logging.error(error_message)
# (Show error in UI if necessary) QMessageBox.critical(self, "Error", error_message)
finally: finally:
self.current_state = "menu" self.current_state = "menu"
self.update_total_playtime(self.start_time) self.update_total_playtime(self.start_time)

View File

@ -1,5 +1,5 @@
{ {
"version": "0.13.7", "version": "0.13.8",
"links": [ "links": [
"https://raw.githubusercontent.com/nixietab/picodulce/main/version.json", "https://raw.githubusercontent.com/nixietab/picodulce/main/version.json",
"https://raw.githubusercontent.com/nixietab/picodulce/main/picodulce.py", "https://raw.githubusercontent.com/nixietab/picodulce/main/picodulce.py",
@ -9,7 +9,8 @@
"https://raw.githubusercontent.com/nixietab/picodulce/main/holiday.ico", "https://raw.githubusercontent.com/nixietab/picodulce/main/holiday.ico",
"https://raw.githubusercontent.com/nixietab/picodulce/main/authser.py", "https://raw.githubusercontent.com/nixietab/picodulce/main/authser.py",
"https://raw.githubusercontent.com/nixietab/picodulce/main/healthcheck.py", "https://raw.githubusercontent.com/nixietab/picodulce/main/healthcheck.py",
"https://raw.githubusercontent.com/nixietab/picodulce/main/modulecli.py" "https://raw.githubusercontent.com/nixietab/picodulce/main/modulecli.py",
"https://raw.githubusercontent.com/nixietab/picodulce/main/loaddaemon.py"
], ],
"versionBleeding": "0.13.3-212" "versionBleeding": "0.13.3-212"
} }