Compare commits

..

No commits in common. "main" and "0.13.7" have entirely different histories.
main ... 0.13.7

6 changed files with 157 additions and 554 deletions

View File

@ -134,8 +134,6 @@ 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": {
@ -225,14 +223,13 @@ class AuthenticationThread(QThread):
self.is_running = False self.is_running = False
class MinecraftAuthenticator(QObject): class MinecraftAuthenticator(QObject):
auth_finished = pyqtSignal(bool, str) auth_finished = pyqtSignal(bool)
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
@ -245,12 +242,6 @@ 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)
@ -272,24 +263,9 @@ class MinecraftAuthenticator(QObject):
self.auth_thread.stop() self.auth_thread.stop()
def show_error(self, error_msg): def show_error(self, error_msg):
self.error_message = error_msg QMessageBox.critical(None, "Error", 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:
@ -329,7 +305,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)}")
self.error_message = f"Failed to initialize account data: {str(e)}" QMessageBox.critical(None, "Error", 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):
@ -352,15 +328,17 @@ 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)}")
self.error_message = f"Failed to update account data: {str(e)}" QMessageBox.critical(None, "Error", f"Failed to update account data: {str(e)}")
self.success = False self.success = False
# We don't emit here, we wait for the thread to finish self.auth_finished.emit(self.success)
def on_authentication_finished(self): def on_authentication_finished(self):
if self.auth_dialog is not None: if self.auth_dialog is not None:
@ -372,9 +350,7 @@ 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.error_message) self.auth_finished.emit(False)
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:

BIN
drums.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -1,367 +0,0 @@
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

View File

@ -42,5 +42,5 @@ def run_command(command="zucaro"):
gc.collect() gc.collect()
if not output: if not output:
return None return f"Error: No output from command. Stderr: {error}"
return output return output

View File

@ -14,9 +14,8 @@ 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, QRadioButton 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
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
@ -133,9 +132,8 @@ 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("Warning: modulecli returned empty response") # Print the output of the command
result = ""
print("Command output:", result) print("Command output:", result)
# Change the value of IsFirstLaunch to False # Change the value of IsFirstLaunch to False
@ -768,9 +766,6 @@ 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
@ -802,12 +797,12 @@ class zucaroVersionSelector(QWidget):
output = modulecli.run_command(command) output = modulecli.run_command(command)
if not output: if not output:
self.installed_version_combo.clear() raise Exception("Failed to get output from modulecli")
return
except Exception as e: except Exception as e:
self.installed_version_combo.clear() logging.error("Error running 'zucaro': %s", e)
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
@ -828,10 +823,9 @@ 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:
self.installed_version_combo.clear() raise Exception("Failed to get output from modulecli for 'instance create default'")
return
except Exception as e: except Exception as e:
self.installed_version_combo.clear() logging.error("Error creating default instance: %s", str(e))
return return
# Run the 'zucaro version list' command and get the output # Run the 'zucaro version list' command and get the output
@ -839,10 +833,9 @@ 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:
self.installed_version_combo.clear() raise Exception("Failed to get output from modulecli for 'version list'")
return
except Exception as e: except Exception as e:
self.installed_version_combo.clear() logging.error("Error: %s", str(e))
return return
# Parse the output and replace '[local]' with a space # Parse the output and replace '[local]' with a space
@ -869,11 +862,10 @@ 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") account_list_output = modulecli.run_command("account list").strip()
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:
@ -894,14 +886,16 @@ 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
self.launch_game_with_window(selected_instance) play_thread = threading.Thread(target=self.run_game, args=(selected_instance,))
play_thread.start()
def launch_game_with_window(self, selected_instance): def run_game(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")
@ -909,9 +903,11 @@ 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_thread = threading.Thread(target=self.update_last_played, args=(selected_instance,), daemon=True) # Update last played on a thread
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"
@ -920,13 +916,16 @@ class zucaroVersionSelector(QWidget):
print(f"Launching command: {command}") print(f"Launching command: {command}")
loaddaemon.launch_instance_with_window(command, self) output = modulecli.run_command(command)
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) print(error_message) # Add this for debugging
logging.error(error_message) logging.error(error_message)
QMessageBox.critical(self, "Error", error_message) # (Show error in UI if necessary)
finally: finally:
self.current_state = "menu" self.current_state = "menu"
self.update_total_playtime(self.start_time) self.update_total_playtime(self.start_time)
@ -1074,11 +1073,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, message): def _on_auth_finished(self, success):
if success: if success:
QMessageBox.information(self, "Success", message) QMessageBox.information(self, "Success", "Account authenticated successfully!")
else: else:
QMessageBox.critical(self, "Error", message) QMessageBox.critical(self, "Error", "Failed to authenticate account")
# Cleanup # Cleanup
if self.authenticator: if self.authenticator:
@ -1110,10 +1109,8 @@ 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 = []
@ -1402,7 +1399,21 @@ 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):
@ -1444,50 +1455,35 @@ 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, QFont.Bold)) title_label.setFont(QFont("Arial", 14))
layout.addWidget(title_label) layout.addWidget(title_label)
info_label = QLabel('Click an instance to select it. Right-click for more options.') # Create a label to display the current instance
palette = info_label.palette() self.current_instance_label = QLabel('Loading...') # Placeholder text
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)
layout.addSpacing(10) # Create a QListWidget to display the instances
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)
layout.addSpacing(10) # Create input field and button to create a new instance
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")
create_layout.addWidget(self.create_instance_input) layout.addWidget(self.create_instance_input)
create_instance_button = QPushButton("Create") create_instance_button = QPushButton("Create Instance")
create_instance_button.clicked.connect(self.create_instance) create_instance_button.clicked.connect(self.create_instance)
create_layout.addWidget(create_instance_button) 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):
@ -1566,62 +1562,52 @@ 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() self.instances_list_widget.clear() # Clear the previous list
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):
item = self.instances_list_widget.itemAt(position)
if not item:
return
instance_name = item.text().replace("", "").strip() def add_instance_buttons(self, list_item, instance_name):
widget = QWidget()
layout = QHBoxLayout(widget)
layout.setContentsMargins(0, 0, 0, 0)
menu = QMenu() label = QLabel(instance_name)
rename_button = QPushButton("Rename")
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;
}
"""
rename_button.setStyleSheet(button_style)
delete_button.setStyleSheet(button_style)
if instance_name == "default": layout.addWidget(label)
rename_action.setEnabled(False) layout.addStretch()
delete_action.setEnabled(False) layout.addWidget(rename_button)
layout.addWidget(delete_button)
action = menu.exec_(self.instances_list_widget.mapToGlobal(position)) widget.setLayout(layout)
list_item.setSizeHint(widget.sizeHint())
self.instances_list_widget.setItemWidget(list_item, widget)
if action == select_action: # Connect button signals
self.on_instance_selected(item) rename_button.clicked.connect(lambda: self.prompt_rename_instance(instance_name))
elif action == rename_action: delete_button.clicked.connect(lambda: self.delete_instance(instance_name))
self.prompt_rename_instance(instance_name)
elif action == delete_action:
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(
@ -1633,7 +1619,8 @@ 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):
instance_name = item.text().replace("", "").strip() widget = self.instances_list_widget.itemWidget(item)
instance_name = widget.findChild(QLabel).text()
config_file = 'config.json' config_file = 'config.json'
@ -1650,7 +1637,6 @@ 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}")
@ -1666,12 +1652,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):
@ -1682,13 +1668,11 @@ class ModLoaderAndVersionMenu(QDialog):
title_label.setFont(QFont("Arial", 14)) title_label.setFont(QFont("Arial", 14))
layout.addWidget(title_label) layout.addWidget(title_label)
# Create radio buttons for mod loaders # Create checkboxes for mod loaders
self.forge_checkbox = QRadioButton('Forge') self.forge_checkbox = QCheckBox('Forge')
self.fabric_checkbox = QRadioButton('Fabric') self.fabric_checkbox = QCheckBox('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()
@ -1697,23 +1681,19 @@ 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, False) self.populate_available_releases(self.version_combo_mod, True, False)
elif self.fabric_checkbox.isChecked(): elif self.fabric_checkbox.isChecked():
self.populate_available_releases(self.version_combo_mod, False, True, False) self.populate_available_releases(self.version_combo_mod, False, True)
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)
@ -1727,7 +1707,6 @@ 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')
@ -1755,13 +1734,11 @@ 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 not output or "Error" in output: if "Error" in output:
if not output: logging.error(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)
@ -1785,31 +1762,52 @@ 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 download_version(self, version): def show_popup(self):
success = loaddaemon.prepare_version_with_window(version, self) self.popup = QDialog(self)
if success: self.popup.setWindowTitle("Installing Version")
QMessageBox.information(self, "Success", f"Version {version} prepared successfully!") layout = QVBoxLayout(self.popup)
else:
QMessageBox.critical(self, "Error", f"Failed to prepare version {version}.")
def populate_available_releases(self, version_combo, install_forge, install_fabric, install_quilt): 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):
# Show the popup in the main thread
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:
QMessageBox.information(self, "Success", message)
else:
QMessageBox.critical(self, "Error", message)
logging.error(message)
def populate_available_releases(self, version_combo, install_forge, install_fabric):
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 or install_quilt: if install_fabric:
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]
@ -1819,8 +1817,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, install_quilt): def install_mod_loader(self, version, install_forge, install_fabric):
if not install_forge and not install_fabric and not install_quilt: if not install_forge and not install_fabric:
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
@ -1829,8 +1827,6 @@ 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.")
@ -1841,8 +1837,6 @@ 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:

View File

@ -1,15 +1,15 @@
{ {
"version": "0.13.10", "version": "0.13.7",
"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"
} }