mirror of
https://github.com/nixietab/picodulce.git
synced 2025-12-02 04:38:43 +00:00
Added progress bar and abort button
Some checks failed
Version Change Action / version-release (push) Has been cancelled
Some checks failed
Version Change Action / version-release (push) Has been cancelled
This commit is contained in:
parent
971b9a07d9
commit
30e714ac30
200
loaddaemon.py
Normal file
200
loaddaemon.py
Normal 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
|
||||||
20
picodulce.py
20
picodulce.py
@ -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"
|
||||||
@ -923,16 +920,13 @@ class zucaroVersionSelector(QWidget):
|
|||||||
|
|
||||||
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)
|
||||||
|
|||||||
@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user