mirror of
https://github.com/nixietab/picodulce.git
synced 2025-12-14 02:03:59 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0014978163 | ||
|
|
01587aa5e7 | ||
|
|
41bca4d23b | ||
|
|
9bac8e2d6d | ||
|
|
da0f4449ea | ||
|
|
30e714ac30 | ||
|
|
971b9a07d9 |
44
authser.py
44
authser.py
@ -134,6 +134,8 @@ class AuthenticationThread(QThread):
|
|||||||
|
|
||||||
return j["access_token"], j["refresh_token"]
|
return j["access_token"], j["refresh_token"]
|
||||||
|
|
||||||
|
raise Exception("Authentication cancelled by user")
|
||||||
|
|
||||||
async def _xbl_auth(self, access_token):
|
async def _xbl_auth(self, access_token):
|
||||||
data = {
|
data = {
|
||||||
"Properties": {
|
"Properties": {
|
||||||
@ -223,13 +225,14 @@ class AuthenticationThread(QThread):
|
|||||||
self.is_running = False
|
self.is_running = False
|
||||||
|
|
||||||
class MinecraftAuthenticator(QObject):
|
class MinecraftAuthenticator(QObject):
|
||||||
auth_finished = pyqtSignal(bool)
|
auth_finished = pyqtSignal(bool, str)
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.auth_thread = None
|
self.auth_thread = None
|
||||||
self.auth_dialog = None
|
self.auth_dialog = None
|
||||||
self.success = False
|
self.success = False
|
||||||
|
self.error_message = None
|
||||||
self.username = None
|
self.username = None
|
||||||
|
|
||||||
# Initialize the launcher to get the correct config path
|
# Initialize the launcher to get the correct config path
|
||||||
@ -242,6 +245,12 @@ class MinecraftAuthenticator(QObject):
|
|||||||
|
|
||||||
# Create accounts.json if it doesn't exist
|
# Create accounts.json if it doesn't exist
|
||||||
if not self.save_to_accounts_json():
|
if not self.save_to_accounts_json():
|
||||||
|
self.auth_finished.emit(False, self.error_message)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check if account is online
|
||||||
|
if not self.validate_account_type():
|
||||||
|
self.auth_finished.emit(False, "Cannot authenticate an offline account")
|
||||||
return
|
return
|
||||||
|
|
||||||
self.auth_thread = AuthenticationThread(username)
|
self.auth_thread = AuthenticationThread(username)
|
||||||
@ -263,9 +272,24 @@ class MinecraftAuthenticator(QObject):
|
|||||||
self.auth_thread.stop()
|
self.auth_thread.stop()
|
||||||
|
|
||||||
def show_error(self, error_msg):
|
def show_error(self, error_msg):
|
||||||
QMessageBox.critical(None, "Error", error_msg)
|
self.error_message = error_msg
|
||||||
self.success = False
|
self.success = False
|
||||||
self.auth_finished.emit(False)
|
|
||||||
|
def validate_account_type(self):
|
||||||
|
try:
|
||||||
|
accounts_file = Path(self.config_path) / "accounts.json"
|
||||||
|
if accounts_file.exists():
|
||||||
|
with open(accounts_file) as f:
|
||||||
|
config = json.load(f)
|
||||||
|
|
||||||
|
if self.username in config["accounts"]:
|
||||||
|
return config["accounts"][self.username].get("microsoft", False)
|
||||||
|
|
||||||
|
return True # New account, will be created as microsoft
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to validate account type: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
def save_to_accounts_json(self):
|
def save_to_accounts_json(self):
|
||||||
try:
|
try:
|
||||||
@ -305,7 +329,7 @@ class MinecraftAuthenticator(QObject):
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to initialize account data: {str(e)}")
|
logger.error(f"Failed to initialize account data: {str(e)}")
|
||||||
QMessageBox.critical(None, "Error", f"Failed to initialize account data: {str(e)}")
|
self.error_message = f"Failed to initialize account data: {str(e)}"
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def on_access_token_received(self, data):
|
def on_access_token_received(self, data):
|
||||||
@ -328,17 +352,15 @@ class MinecraftAuthenticator(QObject):
|
|||||||
json.dump(config, f, indent=4)
|
json.dump(config, f, indent=4)
|
||||||
|
|
||||||
self.success = True
|
self.success = True
|
||||||
QMessageBox.information(None, "Success",
|
|
||||||
f"Successfully authenticated account: {self.username}")
|
|
||||||
else:
|
else:
|
||||||
raise Exception("Account not found in configuration")
|
raise Exception("Account not found in configuration")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to update account data: {str(e)}")
|
logger.error(f"Failed to update account data: {str(e)}")
|
||||||
QMessageBox.critical(None, "Error", f"Failed to update account data: {str(e)}")
|
self.error_message = f"Failed to update account data: {str(e)}"
|
||||||
self.success = False
|
self.success = False
|
||||||
|
|
||||||
self.auth_finished.emit(self.success)
|
# We don't emit here, we wait for the thread to finish
|
||||||
|
|
||||||
def on_authentication_finished(self):
|
def on_authentication_finished(self):
|
||||||
if self.auth_dialog is not None:
|
if self.auth_dialog is not None:
|
||||||
@ -350,7 +372,9 @@ class MinecraftAuthenticator(QObject):
|
|||||||
self.auth_thread = None
|
self.auth_thread = None
|
||||||
|
|
||||||
if not self.success:
|
if not self.success:
|
||||||
self.auth_finished.emit(False)
|
self.auth_finished.emit(False, self.error_message)
|
||||||
|
else:
|
||||||
|
self.auth_finished.emit(True, f"Successfully authenticated account: {self.username}")
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
if self.auth_dialog is not None:
|
if self.auth_dialog is not None:
|
||||||
@ -363,4 +387,4 @@ class MinecraftAuthenticator(QObject):
|
|||||||
|
|
||||||
def create_authenticator():
|
def create_authenticator():
|
||||||
"""Factory function to create a new MinecraftAuthenticator instance"""
|
"""Factory function to create a new MinecraftAuthenticator instance"""
|
||||||
return MinecraftAuthenticator()
|
return MinecraftAuthenticator()
|
||||||
367
loaddaemon.py
Normal file
367
loaddaemon.py
Normal file
@ -0,0 +1,367 @@
|
|||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import shlex
|
||||||
|
import gc
|
||||||
|
import re
|
||||||
|
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 and self.launch_signal:
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
class PrepareWindow(QDialog):
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.setWindowTitle("Preparing Version")
|
||||||
|
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_prepare_complete)
|
||||||
|
self.signals.launch_aborted.connect(self.on_prepare_aborted)
|
||||||
|
self.signals.cleanup_done.connect(self.on_cleanup_done)
|
||||||
|
|
||||||
|
self.aborting = False
|
||||||
|
self.capture_streams = []
|
||||||
|
self.thread_running = False
|
||||||
|
self.success = False
|
||||||
|
|
||||||
|
def update_status(self, text):
|
||||||
|
# Parse output for progress updates
|
||||||
|
|
||||||
|
if "Downloading" in text and "libraries" in text:
|
||||||
|
try:
|
||||||
|
count = int(re.search(r'\d+', text).group())
|
||||||
|
self.status_label.setText(f"Downloading {count} libraries...")
|
||||||
|
except:
|
||||||
|
self.status_label.setText(text)
|
||||||
|
elif "Checking" in text and "assets" in text:
|
||||||
|
try:
|
||||||
|
count = int(re.search(r'\d+', text).group())
|
||||||
|
self.status_label.setText(f"Checking {count} assets...")
|
||||||
|
except:
|
||||||
|
self.status_label.setText(text)
|
||||||
|
elif "Jar file" in text and "downloaded" in text:
|
||||||
|
self.status_label.setText("Downloading game jar...")
|
||||||
|
elif "Checking libraries" in text:
|
||||||
|
self.status_label.setText("Checking libraries...")
|
||||||
|
else:
|
||||||
|
if len(text) > 100:
|
||||||
|
text = text[:97] + "..."
|
||||||
|
self.status_label.setText(text)
|
||||||
|
|
||||||
|
def on_prepare_complete(self):
|
||||||
|
if not self.aborting:
|
||||||
|
self.success = True
|
||||||
|
self.status_label.setText("Version prepared successfully!")
|
||||||
|
self.progress_bar.setRange(0, 100)
|
||||||
|
self.progress_bar.setValue(100)
|
||||||
|
self.cancel_button.setEnabled(False)
|
||||||
|
QTimer.singleShot(1500, self.accept)
|
||||||
|
|
||||||
|
def on_prepare_aborted(self):
|
||||||
|
self.status_label.setText("Preparation Aborted.")
|
||||||
|
self.progress_bar.setRange(0, 100)
|
||||||
|
self.progress_bar.setValue(0)
|
||||||
|
self.success = False
|
||||||
|
|
||||||
|
def on_cleanup_done(self):
|
||||||
|
self.thread_running = False
|
||||||
|
if not self.success:
|
||||||
|
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):
|
||||||
|
self.request_abort()
|
||||||
|
|
||||||
|
def closeEvent(self, event):
|
||||||
|
if self.thread_running:
|
||||||
|
event.ignore()
|
||||||
|
self.request_abort()
|
||||||
|
else:
|
||||||
|
event.accept()
|
||||||
|
|
||||||
|
def prepare_version(self, version):
|
||||||
|
command = f"version prepare {version}"
|
||||||
|
self.thread_running = True
|
||||||
|
thread = threading.Thread(target=self._run_prepare, args=(command,), daemon=True)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
def _run_prepare(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, None)
|
||||||
|
stderr_capture = StreamingCapture(self.signals.log_update, None)
|
||||||
|
|
||||||
|
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.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 preparing version: {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
|
||||||
|
|
||||||
|
def prepare_version_with_window(version, parent=None):
|
||||||
|
window = PrepareWindow(parent)
|
||||||
|
window.prepare_version(version)
|
||||||
|
result = window.exec_()
|
||||||
|
return window.success
|
||||||
@ -42,5 +42,5 @@ def run_command(command="zucaro"):
|
|||||||
gc.collect()
|
gc.collect()
|
||||||
|
|
||||||
if not output:
|
if not output:
|
||||||
return f"Error: No output from command. Stderr: {error}"
|
return None
|
||||||
return output
|
return output
|
||||||
|
|||||||
284
picodulce.py
284
picodulce.py
@ -14,8 +14,9 @@ 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, QRadioButton
|
||||||
from PyQt5.QtGui import QFont, QIcon, QColor, QPalette, QMovie, QPixmap, QDesktopServices, QBrush
|
from PyQt5.QtGui import QFont, QIcon, QColor, QPalette, QMovie, QPixmap, QDesktopServices, QBrush
|
||||||
from PyQt5.QtCore import Qt, QObject, pyqtSignal, QThread, QUrl, QMetaObject, Q_ARG, QByteArray, QSize
|
from PyQt5.QtCore import Qt, QObject, pyqtSignal, QThread, QUrl, QMetaObject, Q_ARG, QByteArray, QSize
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@ -132,8 +133,9 @@ class zucaroVersionSelector(QWidget):
|
|||||||
# Run the command using modulecli
|
# Run the command using modulecli
|
||||||
command = "instance create default"
|
command = "instance create default"
|
||||||
result = modulecli.run_command(command)
|
result = modulecli.run_command(command)
|
||||||
|
if not result:
|
||||||
# Print the output of the command
|
print("Warning: modulecli returned empty response")
|
||||||
|
result = ""
|
||||||
print("Command output:", result)
|
print("Command output:", result)
|
||||||
|
|
||||||
# Change the value of IsFirstLaunch to False
|
# Change the value of IsFirstLaunch to False
|
||||||
@ -766,6 +768,9 @@ class zucaroVersionSelector(QWidget):
|
|||||||
# Run the command using modulecli
|
# Run the command using modulecli
|
||||||
command = "instance dir"
|
command = "instance dir"
|
||||||
result = modulecli.run_command(command)
|
result = modulecli.run_command(command)
|
||||||
|
if not result:
|
||||||
|
print("Error: Could not retrieve game directory")
|
||||||
|
return
|
||||||
game_directory = result.strip()
|
game_directory = result.strip()
|
||||||
|
|
||||||
# Open the directory in the system's file explorer
|
# Open the directory in the system's file explorer
|
||||||
@ -797,12 +802,12 @@ class zucaroVersionSelector(QWidget):
|
|||||||
output = modulecli.run_command(command)
|
output = modulecli.run_command(command)
|
||||||
|
|
||||||
if not output:
|
if not output:
|
||||||
raise Exception("Failed to get output from modulecli")
|
self.installed_version_combo.clear()
|
||||||
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error("Error running 'zucaro': %s", e)
|
self.installed_version_combo.clear()
|
||||||
return
|
return
|
||||||
|
|
||||||
# Parse the output and replace '[local]' with a space
|
|
||||||
versions = [version.replace('[local]', ' ').strip() for version in output.splitlines() if version.strip()]
|
versions = [version.replace('[local]', ' ').strip() for version in output.splitlines() if version.strip()]
|
||||||
|
|
||||||
# Get the last played version from the config
|
# Get the last played version from the config
|
||||||
@ -823,9 +828,10 @@ class zucaroVersionSelector(QWidget):
|
|||||||
command = "instance create default"
|
command = "instance create default"
|
||||||
output = modulecli.run_command(command)
|
output = modulecli.run_command(command)
|
||||||
if not output:
|
if not output:
|
||||||
raise Exception("Failed to get output from modulecli for 'instance create default'")
|
self.installed_version_combo.clear()
|
||||||
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error("Error creating default instance: %s", str(e))
|
self.installed_version_combo.clear()
|
||||||
return
|
return
|
||||||
|
|
||||||
# Run the 'zucaro version list' command and get the output
|
# Run the 'zucaro version list' command and get the output
|
||||||
@ -833,9 +839,10 @@ class zucaroVersionSelector(QWidget):
|
|||||||
command = "version list"
|
command = "version list"
|
||||||
output = modulecli.run_command(command)
|
output = modulecli.run_command(command)
|
||||||
if not output:
|
if not output:
|
||||||
raise Exception("Failed to get output from modulecli for 'version list'")
|
self.installed_version_combo.clear()
|
||||||
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error("Error: %s", str(e))
|
self.installed_version_combo.clear()
|
||||||
return
|
return
|
||||||
|
|
||||||
# Parse the output and replace '[local]' with a space
|
# Parse the output and replace '[local]' with a space
|
||||||
@ -862,10 +869,11 @@ class zucaroVersionSelector(QWidget):
|
|||||||
|
|
||||||
# Check if there are any accounts
|
# Check if there are any accounts
|
||||||
try:
|
try:
|
||||||
account_list_output = modulecli.run_command("account list").strip()
|
account_list_output = modulecli.run_command("account list")
|
||||||
if not account_list_output:
|
if not account_list_output:
|
||||||
QMessageBox.warning(self, "No Account Available", "Please create an account first.")
|
QMessageBox.warning(self, "No Account Available", "Please create an account first.")
|
||||||
return
|
return
|
||||||
|
account_list_output = account_list_output.strip()
|
||||||
|
|
||||||
# Check if the selected account has a '*' (indicating it's the selected one)
|
# Check if the selected account has a '*' (indicating it's the selected one)
|
||||||
if '*' not in account_list_output:
|
if '*' not in account_list_output:
|
||||||
@ -886,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")
|
||||||
@ -903,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"
|
||||||
@ -915,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)
|
||||||
print(f"modulecli output: {output}")
|
|
||||||
if not output:
|
|
||||||
raise Exception("Failed to get output from modulecli")
|
|
||||||
|
|
||||||
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)
|
||||||
@ -1073,11 +1074,11 @@ class zucaroVersionSelector(QWidget):
|
|||||||
logging.error(error_message)
|
logging.error(error_message)
|
||||||
QMessageBox.critical(dialog, "Error", error_message)
|
QMessageBox.critical(dialog, "Error", error_message)
|
||||||
|
|
||||||
def _on_auth_finished(self, success):
|
def _on_auth_finished(self, success, message):
|
||||||
if success:
|
if success:
|
||||||
QMessageBox.information(self, "Success", "Account authenticated successfully!")
|
QMessageBox.information(self, "Success", message)
|
||||||
else:
|
else:
|
||||||
QMessageBox.critical(self, "Error", "Failed to authenticate account")
|
QMessageBox.critical(self, "Error", message)
|
||||||
|
|
||||||
# Cleanup
|
# Cleanup
|
||||||
if self.authenticator:
|
if self.authenticator:
|
||||||
@ -1109,8 +1110,10 @@ class zucaroVersionSelector(QWidget):
|
|||||||
try:
|
try:
|
||||||
command = "account list"
|
command = "account list"
|
||||||
output = modulecli.run_command(command)
|
output = modulecli.run_command(command)
|
||||||
|
if not output:
|
||||||
|
account_combo.clear()
|
||||||
|
return
|
||||||
|
|
||||||
# Process accounts, keeping the one with "*" at the top
|
|
||||||
accounts = output.splitlines()
|
accounts = output.splitlines()
|
||||||
starred_account = None
|
starred_account = None
|
||||||
normal_accounts = []
|
normal_accounts = []
|
||||||
@ -1399,21 +1402,7 @@ class zucaroVersionSelector(QWidget):
|
|||||||
dialog.finished.connect(self.populate_installed_versions)
|
dialog.finished.connect(self.populate_installed_versions)
|
||||||
dialog.exec_()
|
dialog.exec_()
|
||||||
|
|
||||||
class DownloadThread(QThread):
|
|
||||||
completed = pyqtSignal(bool, str)
|
|
||||||
|
|
||||||
def __init__(self, version):
|
|
||||||
super().__init__()
|
|
||||||
self.version = version
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
try:
|
|
||||||
command = f"version prepare {self.version}"
|
|
||||||
modulecli.run_command(command)
|
|
||||||
self.completed.emit(True, f"Version {self.version} prepared successfully!")
|
|
||||||
except Exception as e:
|
|
||||||
error_message = f"Error preparing {self.version}: {str(e)}"
|
|
||||||
self.completed.emit(False, error_message)
|
|
||||||
|
|
||||||
class ModLoaderAndVersionMenu(QDialog):
|
class ModLoaderAndVersionMenu(QDialog):
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
@ -1455,35 +1444,50 @@ class ModLoaderAndVersionMenu(QDialog):
|
|||||||
def setup_instances_tab(self, instances_tab):
|
def setup_instances_tab(self, instances_tab):
|
||||||
layout = QVBoxLayout(instances_tab)
|
layout = QVBoxLayout(instances_tab)
|
||||||
|
|
||||||
# Create title label
|
|
||||||
title_label = QLabel('Manage Minecraft Instances')
|
title_label = QLabel('Manage Minecraft Instances')
|
||||||
title_label.setFont(QFont("Arial", 14))
|
title_label.setFont(QFont("Arial", 14, QFont.Bold))
|
||||||
layout.addWidget(title_label)
|
layout.addWidget(title_label)
|
||||||
|
|
||||||
# Create a label to display the current instance
|
info_label = QLabel('Click an instance to select it. Right-click for more options.')
|
||||||
self.current_instance_label = QLabel('Loading...') # Placeholder text
|
palette = info_label.palette()
|
||||||
|
info_label.setStyleSheet(f"color: {palette.color(QPalette.PlaceholderText).name()}; font-size: 10pt;")
|
||||||
|
layout.addWidget(info_label)
|
||||||
|
|
||||||
|
self.current_instance_label = QLabel('Current Instance: Loading...')
|
||||||
|
self.current_instance_label.setFont(QFont("Arial", 11, QFont.Bold))
|
||||||
layout.addWidget(self.current_instance_label)
|
layout.addWidget(self.current_instance_label)
|
||||||
|
|
||||||
# Create a QListWidget to display the instances
|
layout.addSpacing(10)
|
||||||
|
|
||||||
|
instances_label = QLabel('Available Instances:')
|
||||||
|
instances_label.setFont(QFont("Arial", 10))
|
||||||
|
layout.addWidget(instances_label)
|
||||||
|
|
||||||
self.instances_list_widget = QListWidget()
|
self.instances_list_widget = QListWidget()
|
||||||
|
self.instances_list_widget.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||||
|
self.instances_list_widget.customContextMenuRequested.connect(self.show_instance_context_menu)
|
||||||
|
self.instances_list_widget.setAlternatingRowColors(True)
|
||||||
layout.addWidget(self.instances_list_widget)
|
layout.addWidget(self.instances_list_widget)
|
||||||
|
|
||||||
# Create input field and button to create a new instance
|
layout.addSpacing(10)
|
||||||
|
|
||||||
|
create_label = QLabel('Create New Instance:')
|
||||||
|
create_label.setFont(QFont("Arial", 10))
|
||||||
|
layout.addWidget(create_label)
|
||||||
|
|
||||||
|
create_layout = QHBoxLayout()
|
||||||
self.create_instance_input = QLineEdit()
|
self.create_instance_input = QLineEdit()
|
||||||
self.create_instance_input.setPlaceholderText("Enter instance name")
|
self.create_instance_input.setPlaceholderText("Enter instance name")
|
||||||
layout.addWidget(self.create_instance_input)
|
create_layout.addWidget(self.create_instance_input)
|
||||||
|
|
||||||
create_instance_button = QPushButton("Create Instance")
|
create_instance_button = QPushButton("Create")
|
||||||
create_instance_button.clicked.connect(self.create_instance)
|
create_instance_button.clicked.connect(self.create_instance)
|
||||||
layout.addWidget(create_instance_button)
|
create_layout.addWidget(create_instance_button)
|
||||||
|
|
||||||
|
layout.addLayout(create_layout)
|
||||||
|
|
||||||
# Fetch and display the current instances
|
|
||||||
self.load_instances()
|
self.load_instances()
|
||||||
|
|
||||||
# Connect the item selection to the instance selection method
|
|
||||||
self.instances_list_widget.itemClicked.connect(self.on_instance_selected)
|
self.instances_list_widget.itemClicked.connect(self.on_instance_selected)
|
||||||
|
|
||||||
# Update the label with the current instance from the config
|
|
||||||
self.update_instance_label()
|
self.update_instance_label()
|
||||||
|
|
||||||
def create_instance(self):
|
def create_instance(self):
|
||||||
@ -1562,52 +1566,62 @@ class ModLoaderAndVersionMenu(QDialog):
|
|||||||
|
|
||||||
def load_instances(self):
|
def load_instances(self):
|
||||||
try:
|
try:
|
||||||
# Run the "zucaro instance list" command
|
|
||||||
command = "instance list"
|
command = "instance list"
|
||||||
output = modulecli.run_command(command)
|
output = modulecli.run_command(command)
|
||||||
|
if not output:
|
||||||
|
self.instances_list_widget.clear()
|
||||||
|
return
|
||||||
|
|
||||||
# Parse the output and add each instance to the list widget
|
|
||||||
instances = output.splitlines()
|
instances = output.splitlines()
|
||||||
self.instances_list_widget.clear() # Clear the previous list
|
self.instances_list_widget.clear()
|
||||||
|
|
||||||
|
with open('config.json', 'r') as config_file:
|
||||||
|
config_data = json.load(config_file)
|
||||||
|
current_instance = config_data.get('Instance', 'default')
|
||||||
|
|
||||||
for instance in instances:
|
for instance in instances:
|
||||||
|
instance_name = instance.strip()
|
||||||
item = QListWidgetItem()
|
item = QListWidgetItem()
|
||||||
|
font = QFont()
|
||||||
|
font.setPointSize(11)
|
||||||
|
|
||||||
|
if instance_name == current_instance:
|
||||||
|
item.setText(f"★ {instance_name}")
|
||||||
|
else:
|
||||||
|
item.setText(instance_name)
|
||||||
|
|
||||||
|
item.setFont(font)
|
||||||
self.instances_list_widget.addItem(item)
|
self.instances_list_widget.addItem(item)
|
||||||
self.add_instance_buttons(item, instance)
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error("Error fetching instances: %s", str(e))
|
logging.error("Error fetching instances: %s", str(e))
|
||||||
|
|
||||||
|
def show_instance_context_menu(self, position):
|
||||||
def add_instance_buttons(self, list_item, instance_name):
|
item = self.instances_list_widget.itemAt(position)
|
||||||
widget = QWidget()
|
if not item:
|
||||||
layout = QHBoxLayout(widget)
|
return
|
||||||
layout.setContentsMargins(0, 0, 0, 0)
|
|
||||||
|
instance_name = item.text().replace("★ ", "").strip()
|
||||||
label = QLabel(instance_name)
|
|
||||||
rename_button = QPushButton("Rename")
|
menu = QMenu()
|
||||||
delete_button = QPushButton("Delete")
|
|
||||||
|
select_action = menu.addAction("Select Instance")
|
||||||
# Stylize the buttons
|
menu.addSeparator()
|
||||||
button_style = """
|
rename_action = menu.addAction("Rename")
|
||||||
QPushButton {
|
delete_action = menu.addAction("Delete")
|
||||||
padding: 2px 5px;
|
|
||||||
}
|
if instance_name == "default":
|
||||||
"""
|
rename_action.setEnabled(False)
|
||||||
rename_button.setStyleSheet(button_style)
|
delete_action.setEnabled(False)
|
||||||
delete_button.setStyleSheet(button_style)
|
|
||||||
|
action = menu.exec_(self.instances_list_widget.mapToGlobal(position))
|
||||||
layout.addWidget(label)
|
|
||||||
layout.addStretch()
|
if action == select_action:
|
||||||
layout.addWidget(rename_button)
|
self.on_instance_selected(item)
|
||||||
layout.addWidget(delete_button)
|
elif action == rename_action:
|
||||||
|
self.prompt_rename_instance(instance_name)
|
||||||
widget.setLayout(layout)
|
elif action == delete_action:
|
||||||
list_item.setSizeHint(widget.sizeHint())
|
self.delete_instance(instance_name)
|
||||||
self.instances_list_widget.setItemWidget(list_item, widget)
|
|
||||||
|
|
||||||
# Connect button signals
|
|
||||||
rename_button.clicked.connect(lambda: self.prompt_rename_instance(instance_name))
|
|
||||||
delete_button.clicked.connect(lambda: self.delete_instance(instance_name))
|
|
||||||
|
|
||||||
def prompt_rename_instance(self, old_instance_name):
|
def prompt_rename_instance(self, old_instance_name):
|
||||||
new_instance_name, ok = QInputDialog.getText(
|
new_instance_name, ok = QInputDialog.getText(
|
||||||
@ -1619,8 +1633,7 @@ class ModLoaderAndVersionMenu(QDialog):
|
|||||||
self.rename_instance(old_instance_name, new_instance_name)
|
self.rename_instance(old_instance_name, new_instance_name)
|
||||||
|
|
||||||
def on_instance_selected(self, item):
|
def on_instance_selected(self, item):
|
||||||
widget = self.instances_list_widget.itemWidget(item)
|
instance_name = item.text().replace("★ ", "").strip()
|
||||||
instance_name = widget.findChild(QLabel).text()
|
|
||||||
|
|
||||||
config_file = 'config.json'
|
config_file = 'config.json'
|
||||||
|
|
||||||
@ -1637,6 +1650,7 @@ class ModLoaderAndVersionMenu(QDialog):
|
|||||||
logging.info(f"Config updated: Instance set to {instance_name}")
|
logging.info(f"Config updated: Instance set to {instance_name}")
|
||||||
|
|
||||||
self.update_instance_label()
|
self.update_instance_label()
|
||||||
|
self.load_instances()
|
||||||
|
|
||||||
except (json.JSONDecodeError, FileNotFoundError) as e:
|
except (json.JSONDecodeError, FileNotFoundError) as e:
|
||||||
logging.error(f"Error reading config.json: {e}")
|
logging.error(f"Error reading config.json: {e}")
|
||||||
@ -1652,12 +1666,12 @@ class ModLoaderAndVersionMenu(QDialog):
|
|||||||
config_data = json.load(file)
|
config_data = json.load(file)
|
||||||
|
|
||||||
current_instance = config_data.get('Instance', 'Not set')
|
current_instance = config_data.get('Instance', 'Not set')
|
||||||
self.current_instance_label.setText(f'Current instance: {current_instance}')
|
self.current_instance_label.setText(f'Current Instance: {current_instance}')
|
||||||
|
|
||||||
except (json.JSONDecodeError, FileNotFoundError) as e:
|
except (json.JSONDecodeError, FileNotFoundError) as e:
|
||||||
logging.error(f"Error reading config.json: {e}")
|
logging.error(f"Error reading config.json: {e}")
|
||||||
else:
|
else:
|
||||||
self.current_instance_label.setText('Current instance: Not set')
|
self.current_instance_label.setText('Current Instance: Not set')
|
||||||
|
|
||||||
|
|
||||||
def setup_install_mod_loader_tab(self, install_mod_tab):
|
def setup_install_mod_loader_tab(self, install_mod_tab):
|
||||||
@ -1668,11 +1682,13 @@ class ModLoaderAndVersionMenu(QDialog):
|
|||||||
title_label.setFont(QFont("Arial", 14))
|
title_label.setFont(QFont("Arial", 14))
|
||||||
layout.addWidget(title_label)
|
layout.addWidget(title_label)
|
||||||
|
|
||||||
# Create checkboxes for mod loaders
|
# Create radio buttons for mod loaders
|
||||||
self.forge_checkbox = QCheckBox('Forge')
|
self.forge_checkbox = QRadioButton('Forge')
|
||||||
self.fabric_checkbox = QCheckBox('Fabric')
|
self.fabric_checkbox = QRadioButton('Fabric')
|
||||||
|
self.quilt_checkbox = QRadioButton('Quilt')
|
||||||
layout.addWidget(self.forge_checkbox)
|
layout.addWidget(self.forge_checkbox)
|
||||||
layout.addWidget(self.fabric_checkbox)
|
layout.addWidget(self.fabric_checkbox)
|
||||||
|
layout.addWidget(self.quilt_checkbox)
|
||||||
|
|
||||||
# Create dropdown menu for versions
|
# Create dropdown menu for versions
|
||||||
self.version_combo_mod = QComboBox()
|
self.version_combo_mod = QComboBox()
|
||||||
@ -1681,19 +1697,23 @@ class ModLoaderAndVersionMenu(QDialog):
|
|||||||
def update_versions():
|
def update_versions():
|
||||||
self.version_combo_mod.clear()
|
self.version_combo_mod.clear()
|
||||||
if self.forge_checkbox.isChecked():
|
if self.forge_checkbox.isChecked():
|
||||||
self.populate_available_releases(self.version_combo_mod, True, False)
|
self.populate_available_releases(self.version_combo_mod, True, False, False)
|
||||||
elif self.fabric_checkbox.isChecked():
|
elif self.fabric_checkbox.isChecked():
|
||||||
self.populate_available_releases(self.version_combo_mod, False, True)
|
self.populate_available_releases(self.version_combo_mod, False, True, False)
|
||||||
|
elif self.quilt_checkbox.isChecked():
|
||||||
|
self.populate_available_releases(self.version_combo_mod, False, False, True)
|
||||||
|
|
||||||
self.forge_checkbox.clicked.connect(update_versions)
|
self.forge_checkbox.clicked.connect(update_versions)
|
||||||
self.fabric_checkbox.clicked.connect(update_versions)
|
self.fabric_checkbox.clicked.connect(update_versions)
|
||||||
|
self.quilt_checkbox.clicked.connect(update_versions)
|
||||||
|
|
||||||
# Create install button
|
# Create install button
|
||||||
install_button = QPushButton('Install')
|
install_button = QPushButton('Install')
|
||||||
install_button.clicked.connect(lambda: self.install_mod_loader(
|
install_button.clicked.connect(lambda: self.install_mod_loader(
|
||||||
self.version_combo_mod.currentText(),
|
self.version_combo_mod.currentText(),
|
||||||
self.forge_checkbox.isChecked(),
|
self.forge_checkbox.isChecked(),
|
||||||
self.fabric_checkbox.isChecked()
|
self.fabric_checkbox.isChecked(),
|
||||||
|
self.quilt_checkbox.isChecked()
|
||||||
))
|
))
|
||||||
layout.addWidget(install_button)
|
layout.addWidget(install_button)
|
||||||
|
|
||||||
@ -1707,6 +1727,7 @@ class ModLoaderAndVersionMenu(QDialog):
|
|||||||
|
|
||||||
# Create checkboxes for different version types
|
# Create checkboxes for different version types
|
||||||
self.release_checkbox = QCheckBox('Releases')
|
self.release_checkbox = QCheckBox('Releases')
|
||||||
|
self.release_checkbox.setChecked(True)
|
||||||
self.snapshot_checkbox = QCheckBox('Snapshots')
|
self.snapshot_checkbox = QCheckBox('Snapshots')
|
||||||
self.alpha_checkbox = QCheckBox('Alpha')
|
self.alpha_checkbox = QCheckBox('Alpha')
|
||||||
self.beta_checkbox = QCheckBox('Beta')
|
self.beta_checkbox = QCheckBox('Beta')
|
||||||
@ -1734,11 +1755,13 @@ class ModLoaderAndVersionMenu(QDialog):
|
|||||||
try:
|
try:
|
||||||
command = 'version list ' + ' '.join(options)
|
command = 'version list ' + ' '.join(options)
|
||||||
output = modulecli.run_command(command)
|
output = modulecli.run_command(command)
|
||||||
if "Error" in output:
|
if not output or "Error" in output:
|
||||||
logging.error(output)
|
if not output:
|
||||||
|
logging.error("Empty response from modulecli")
|
||||||
|
else:
|
||||||
|
logging.error(output)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Parse the output and replace '[local]' with a space
|
|
||||||
versions = output.splitlines()
|
versions = output.splitlines()
|
||||||
versions = [version.replace('[local]', ' ').strip() for version in versions]
|
versions = [version.replace('[local]', ' ').strip() for version in versions]
|
||||||
self.version_combo.addItems(versions)
|
self.version_combo.addItems(versions)
|
||||||
@ -1762,52 +1785,31 @@ class ModLoaderAndVersionMenu(QDialog):
|
|||||||
# Connect the combo box signal to the update function
|
# Connect the combo box signal to the update function
|
||||||
self.version_combo.currentIndexChanged.connect(self.update_download_button_state)
|
self.version_combo.currentIndexChanged.connect(self.update_download_button_state)
|
||||||
|
|
||||||
|
# Initial update
|
||||||
|
update_versions()
|
||||||
|
|
||||||
def update_download_button_state(self):
|
def update_download_button_state(self):
|
||||||
self.download_button.setEnabled(self.version_combo.currentIndex() != -1)
|
self.download_button.setEnabled(self.version_combo.currentIndex() != -1)
|
||||||
|
|
||||||
def show_popup(self):
|
|
||||||
self.popup = QDialog(self)
|
|
||||||
self.popup.setWindowTitle("Installing Version")
|
|
||||||
layout = QVBoxLayout(self.popup)
|
|
||||||
|
|
||||||
label = QLabel("The version is being installed...")
|
|
||||||
layout.addWidget(label)
|
|
||||||
|
|
||||||
movie = QMovie("drums.gif")
|
|
||||||
gif_label = QLabel()
|
|
||||||
gif_label.setMovie(movie)
|
|
||||||
layout.addWidget(gif_label)
|
|
||||||
|
|
||||||
movie.start()
|
|
||||||
self.popup.setGeometry(200, 200, 300, 200)
|
|
||||||
self.popup.setWindowModality(Qt.ApplicationModal)
|
|
||||||
self.popup.show()
|
|
||||||
|
|
||||||
def download_version(self, version):
|
def download_version(self, version):
|
||||||
# Show the popup in the main thread
|
success = loaddaemon.prepare_version_with_window(version, self)
|
||||||
self.show_popup()
|
|
||||||
|
|
||||||
self.download_thread = DownloadThread(version)
|
|
||||||
self.download_thread.completed.connect(self.on_download_completed)
|
|
||||||
self.download_thread.start()
|
|
||||||
|
|
||||||
def on_download_completed(self, success, message):
|
|
||||||
self.popup.close()
|
|
||||||
if success:
|
if success:
|
||||||
QMessageBox.information(self, "Success", message)
|
QMessageBox.information(self, "Success", f"Version {version} prepared successfully!")
|
||||||
else:
|
else:
|
||||||
QMessageBox.critical(self, "Error", message)
|
QMessageBox.critical(self, "Error", f"Failed to prepare version {version}.")
|
||||||
logging.error(message)
|
|
||||||
|
|
||||||
def populate_available_releases(self, version_combo, install_forge, install_fabric):
|
def populate_available_releases(self, version_combo, install_forge, install_fabric, install_quilt):
|
||||||
try:
|
try:
|
||||||
command = "version list --release"
|
command = "version list --release"
|
||||||
output = modulecli.run_command(command)
|
output = modulecli.run_command(command)
|
||||||
|
if not output:
|
||||||
|
logging.error("Empty response from modulecli")
|
||||||
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error("Error: %s", str(e))
|
logging.error("Error: %s", str(e))
|
||||||
return
|
return
|
||||||
|
|
||||||
if install_fabric:
|
if install_fabric or install_quilt:
|
||||||
releases = [version for version in output.splitlines() if version.startswith("1.") and int(version.split('.')[1]) >= 14]
|
releases = [version for version in output.splitlines() if version.startswith("1.") and int(version.split('.')[1]) >= 14]
|
||||||
elif install_forge:
|
elif install_forge:
|
||||||
releases = [version for version in output.splitlines() if version.startswith("1.") and float(version.split('.')[1]) >= 5]
|
releases = [version for version in output.splitlines() if version.startswith("1.") and float(version.split('.')[1]) >= 5]
|
||||||
@ -1817,8 +1819,8 @@ class ModLoaderAndVersionMenu(QDialog):
|
|||||||
version_combo.clear()
|
version_combo.clear()
|
||||||
version_combo.addItems(releases)
|
version_combo.addItems(releases)
|
||||||
|
|
||||||
def install_mod_loader(self, version, install_forge, install_fabric):
|
def install_mod_loader(self, version, install_forge, install_fabric, install_quilt):
|
||||||
if not install_forge and not install_fabric:
|
if not install_forge and not install_fabric and not install_quilt:
|
||||||
QMessageBox.warning(self, "Select Mod Loader", "Please select at least one mod loader.")
|
QMessageBox.warning(self, "Select Mod Loader", "Please select at least one mod loader.")
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -1827,6 +1829,8 @@ class ModLoaderAndVersionMenu(QDialog):
|
|||||||
mod_loader = 'forge'
|
mod_loader = 'forge'
|
||||||
elif install_fabric:
|
elif install_fabric:
|
||||||
mod_loader = 'fabric'
|
mod_loader = 'fabric'
|
||||||
|
elif install_quilt:
|
||||||
|
mod_loader = 'quilt'
|
||||||
|
|
||||||
if not mod_loader:
|
if not mod_loader:
|
||||||
QMessageBox.warning(self, "Select Mod Loader", "Please select at least one mod loader.")
|
QMessageBox.warning(self, "Select Mod Loader", "Please select at least one mod loader.")
|
||||||
@ -1837,6 +1841,8 @@ class ModLoaderAndVersionMenu(QDialog):
|
|||||||
command = f"mod loader forge install --game {version}"
|
command = f"mod loader forge install --game {version}"
|
||||||
elif mod_loader == 'fabric':
|
elif mod_loader == 'fabric':
|
||||||
command = f"mod loader fabric install {version}"
|
command = f"mod loader fabric install {version}"
|
||||||
|
elif mod_loader == 'quilt':
|
||||||
|
command = f"mod loader quilt install {version}"
|
||||||
modulecli.run_command(command)
|
modulecli.run_command(command)
|
||||||
QMessageBox.information(self, "Success", f"{mod_loader.capitalize()} installed successfully for version {version}!")
|
QMessageBox.information(self, "Success", f"{mod_loader.capitalize()} installed successfully for version {version}!")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
{
|
{
|
||||||
"version": "0.13.7",
|
"version": "0.13.10",
|
||||||
"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",
|
||||||
"https://raw.githubusercontent.com/nixietab/picodulce/main/requirements.txt",
|
"https://raw.githubusercontent.com/nixietab/picodulce/main/requirements.txt",
|
||||||
"https://raw.githubusercontent.com/nixietab/picodulce/main/drums.gif",
|
|
||||||
"https://raw.githubusercontent.com/nixietab/picodulce/main/marroc.py",
|
"https://raw.githubusercontent.com/nixietab/picodulce/main/marroc.py",
|
||||||
"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