mirror of
https://github.com/nixietab/picodulce.git
synced 2025-04-09 18:08:57 +01:00
Compare commits
No commits in common. "main" and "0.11.9" have entirely different histories.
239
authser.py
239
authser.py
@ -1,239 +0,0 @@
|
||||
import sys
|
||||
import re
|
||||
import colorama
|
||||
import requests
|
||||
from PyQt5.QtWidgets import (QApplication, QDialog, QLabel, QVBoxLayout,
|
||||
QPushButton, QLineEdit, QMessageBox)
|
||||
from PyQt5.QtCore import QThread, pyqtSignal, Qt, QUrl, QObject, QTimer
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
from picomc.logging import logger
|
||||
|
||||
# Constants
|
||||
URL_DEVICE_AUTH = "https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode"
|
||||
URL_TOKEN = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token"
|
||||
CLIENT_ID = "c52aed44-3b4d-4215-99c5-824033d2bc0f"
|
||||
SCOPE = "XboxLive.signin offline_access"
|
||||
GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code"
|
||||
|
||||
class AuthDialog(QDialog):
|
||||
def __init__(self, url, code, parent=None, error_mode=False):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Microsoft Authentication")
|
||||
self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
|
||||
self.setModal(True)
|
||||
self.setup_ui(url, code, error_mode)
|
||||
|
||||
def setup_ui(self, url, code, error_mode):
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
if error_mode:
|
||||
error_label = QLabel("Error in Login - Please try again")
|
||||
error_label.setStyleSheet("QLabel { color: red; font-weight: bold; }")
|
||||
layout.addWidget(error_label)
|
||||
|
||||
instructions = QLabel(
|
||||
"To authenticate your Microsoft Account:\n\n"
|
||||
"1. Click 'Open Authentication Page' or visit:\n"
|
||||
"2. Copy the code below\n"
|
||||
"3. Paste the code on the Microsoft website\n"
|
||||
"4. After completing authentication, click 'I've Completed Authentication'"
|
||||
)
|
||||
instructions.setWordWrap(True)
|
||||
layout.addWidget(instructions)
|
||||
|
||||
url_label = QLabel(url)
|
||||
url_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
|
||||
url_label.setWordWrap(True)
|
||||
layout.addWidget(url_label)
|
||||
|
||||
self.code_input = QLineEdit(code)
|
||||
self.code_input.setReadOnly(True)
|
||||
self.code_input.setAlignment(Qt.AlignCenter)
|
||||
self.code_input.setStyleSheet("""
|
||||
QLineEdit {
|
||||
font-size: 16pt;
|
||||
font-weight: bold;
|
||||
padding: 5px;
|
||||
}
|
||||
""")
|
||||
layout.addWidget(self.code_input)
|
||||
|
||||
copy_button = QPushButton("Copy Code")
|
||||
copy_button.clicked.connect(self.copy_code)
|
||||
layout.addWidget(copy_button)
|
||||
|
||||
open_url_button = QPushButton("Open Authentication Page")
|
||||
open_url_button.clicked.connect(lambda: self.open_url(url))
|
||||
layout.addWidget(open_url_button)
|
||||
|
||||
continue_button = QPushButton("I've Completed Authentication")
|
||||
continue_button.clicked.connect(self.accept)
|
||||
layout.addWidget(continue_button)
|
||||
|
||||
def copy_code(self):
|
||||
clipboard = QApplication.clipboard()
|
||||
clipboard.setText(self.code_input.text())
|
||||
|
||||
def open_url(self, url):
|
||||
QDesktopServices.openUrl(QUrl(url))
|
||||
|
||||
class AuthenticationThread(QThread):
|
||||
auth_data_received = pyqtSignal(dict)
|
||||
error_occurred = pyqtSignal(str)
|
||||
auth_error_detected = pyqtSignal(str)
|
||||
finished = pyqtSignal()
|
||||
access_token_received = pyqtSignal(str, str)
|
||||
|
||||
def __init__(self, account):
|
||||
super().__init__()
|
||||
self.account = account
|
||||
self.device_code = None
|
||||
self.is_running = True
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
self.authenticate(self.account)
|
||||
except Exception as e:
|
||||
self.error_occurred.emit(str(e))
|
||||
self.finished.emit()
|
||||
|
||||
def authenticate(self, account):
|
||||
try:
|
||||
data = {"client_id": CLIENT_ID, "scope": SCOPE}
|
||||
|
||||
# Request device code
|
||||
resp = requests.post(URL_DEVICE_AUTH, data)
|
||||
resp.raise_for_status()
|
||||
|
||||
j = resp.json()
|
||||
self.device_code = j["device_code"]
|
||||
user_code = j["user_code"]
|
||||
link = j["verification_uri"]
|
||||
|
||||
# Format message with colorama
|
||||
msg = j["message"]
|
||||
msg = msg.replace(
|
||||
user_code, colorama.Fore.RED + user_code + colorama.Fore.RESET
|
||||
).replace(link, colorama.Style.BRIGHT + link + colorama.Style.NORMAL)
|
||||
|
||||
# Emit auth data received signal
|
||||
self.auth_data_received.emit({'url': link, 'code': user_code})
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.error(f"Request failed: {e}")
|
||||
self.error_occurred.emit(str(e))
|
||||
self.finished.emit()
|
||||
|
||||
def poll_for_token(self):
|
||||
try:
|
||||
data = {"code": self.device_code, "grant_type": GRANT_TYPE, "client_id": CLIENT_ID}
|
||||
resp = requests.post(URL_TOKEN, data)
|
||||
if resp.status_code == 400:
|
||||
j = resp.json()
|
||||
logger.debug(j)
|
||||
if j["error"] == "authorization_pending":
|
||||
logger.warning(j["error_description"])
|
||||
self.auth_error_detected.emit(j["error_description"])
|
||||
return
|
||||
else:
|
||||
raise Exception(j["error_description"])
|
||||
resp.raise_for_status()
|
||||
j = resp.json()
|
||||
access_token = j["access_token"]
|
||||
refresh_token = j["refresh_token"]
|
||||
logger.debug("OAuth device code flow successful")
|
||||
self.access_token_received.emit(access_token, refresh_token)
|
||||
self.finished.emit()
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.error(f"Request failed: {e}")
|
||||
self.error_occurred.emit(str(e))
|
||||
self.finished.emit()
|
||||
|
||||
def send_enter(self):
|
||||
self.poll_for_token()
|
||||
|
||||
def stop(self):
|
||||
self.is_running = False
|
||||
|
||||
class MinecraftAuthenticator(QObject):
|
||||
auth_finished = pyqtSignal(bool)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.auth_thread = None
|
||||
self.current_auth_data = None
|
||||
self.auth_dialog = None
|
||||
self.success = False
|
||||
|
||||
def authenticate(self, username):
|
||||
self.success = False
|
||||
self.auth_thread = AuthenticationThread(username)
|
||||
self.auth_thread.auth_data_received.connect(self.show_auth_dialog)
|
||||
self.auth_thread.auth_error_detected.connect(self.handle_auth_error)
|
||||
self.auth_thread.error_occurred.connect(self.show_error)
|
||||
self.auth_thread.access_token_received.connect(self.on_access_token_received)
|
||||
self.auth_thread.finished.connect(self.on_authentication_finished)
|
||||
self.auth_thread.start()
|
||||
|
||||
def show_auth_dialog(self, auth_data):
|
||||
self.current_auth_data = auth_data
|
||||
|
||||
if self.auth_dialog is not None:
|
||||
self.auth_dialog.close()
|
||||
self.auth_dialog = None
|
||||
|
||||
self.auth_dialog = AuthDialog(auth_data['url'], auth_data['code'])
|
||||
if self.auth_dialog.exec_() == QDialog.Accepted:
|
||||
self.auth_thread.send_enter()
|
||||
|
||||
def handle_auth_error(self, output):
|
||||
if self.current_auth_data:
|
||||
if self.auth_dialog is not None:
|
||||
self.auth_dialog.close()
|
||||
self.auth_dialog = None
|
||||
|
||||
self.auth_dialog = AuthDialog(
|
||||
self.current_auth_data['url'],
|
||||
self.current_auth_data['code'],
|
||||
error_mode=True
|
||||
)
|
||||
if self.auth_dialog.exec_() == QDialog.Accepted:
|
||||
self.auth_thread.send_enter()
|
||||
|
||||
def show_error(self, error_message):
|
||||
QMessageBox.critical(None, "Error", f"Authentication error: {error_message}")
|
||||
self.success = False
|
||||
self.auth_finished.emit(False)
|
||||
|
||||
def on_access_token_received(self, access_token, refresh_token):
|
||||
QMessageBox.information(None, "Success", "Authentication successful!")
|
||||
self.success = True
|
||||
self.auth_finished.emit(True)
|
||||
|
||||
def on_authentication_finished(self):
|
||||
if self.auth_dialog is not None:
|
||||
self.auth_dialog.close()
|
||||
self.auth_dialog = None
|
||||
|
||||
if self.auth_thread:
|
||||
self.auth_thread.stop()
|
||||
self.auth_thread = None
|
||||
|
||||
if not self.success:
|
||||
self.auth_finished.emit(False)
|
||||
|
||||
def cleanup(self):
|
||||
if self.auth_dialog is not None:
|
||||
self.auth_dialog.close()
|
||||
self.auth_dialog = None
|
||||
|
||||
if self.auth_thread and self.auth_thread.isRunning():
|
||||
self.auth_thread.stop()
|
||||
self.auth_thread.wait()
|
||||
|
||||
# Example usage
|
||||
if __name__ == '__main__':
|
||||
app = QApplication(sys.argv)
|
||||
authenticator = MinecraftAuthenticator()
|
||||
authenticator.authenticate("TestUser")
|
||||
sys.exit(app.exec_())
|
119
healthcheck.py
119
healthcheck.py
@ -1,119 +0,0 @@
|
||||
import os
|
||||
import json
|
||||
import requests
|
||||
|
||||
|
||||
class HealthCheck:
|
||||
def __init__(self):
|
||||
self.config = None
|
||||
|
||||
def check_config_file(self):
|
||||
config_path = "config.json"
|
||||
default_config = {
|
||||
"IsRCPenabled": False,
|
||||
"CheckUpdate": False,
|
||||
"IsBleeding": False,
|
||||
"LastPlayed": "",
|
||||
"IsFirstLaunch": True,
|
||||
"Instance": "default",
|
||||
"Theme": "Dark.json",
|
||||
"ThemeBackground": True,
|
||||
"ThemeRepository": "https://raw.githubusercontent.com/nixietab/picodulce-themes/main/repo.json",
|
||||
"Locale": "en"
|
||||
}
|
||||
|
||||
# Step 1: Check if the file exists; if not, create it with default values
|
||||
if not os.path.exists(config_path):
|
||||
with open(config_path, "w") as config_file:
|
||||
json.dump(default_config, config_file, indent=4)
|
||||
self.config = default_config
|
||||
return
|
||||
|
||||
# Step 2: Try loading the config file, handle invalid JSON
|
||||
try:
|
||||
with open(config_path, "r") as config_file:
|
||||
self.config = json.load(config_file)
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
# File is corrupted, overwrite it with default configuration
|
||||
with open(config_path, "w") as config_file:
|
||||
json.dump(default_config, config_file, indent=4)
|
||||
self.config = default_config
|
||||
return
|
||||
|
||||
# Step 3: Check for missing keys and add defaults if necessary
|
||||
updated = False
|
||||
for key, value in default_config.items():
|
||||
if key not in self.config: # Field is missing
|
||||
self.config[key] = value
|
||||
updated = True
|
||||
|
||||
# Step 4: Save the repaired config back to the file
|
||||
if updated:
|
||||
with open(config_path, "w") as config_file:
|
||||
json.dump(self.config, config_file, indent=4)
|
||||
|
||||
def themes_integrity(self):
|
||||
# Define folder and file paths
|
||||
themes_folder = "themes"
|
||||
dark_theme_file = os.path.join(themes_folder, "Dark.json")
|
||||
native_theme_file = os.path.join(themes_folder, "Native.json")
|
||||
|
||||
# Define the default content for Dark.json
|
||||
dark_theme_content = {
|
||||
"manifest": {
|
||||
"name": "Dark",
|
||||
"description": "The default picodulce launcher theme",
|
||||
"author": "Nixietab",
|
||||
"license": "MIT"
|
||||
},
|
||||
"palette": {
|
||||
"Window": "#353535",
|
||||
"WindowText": "#ffffff",
|
||||
"Base": "#191919",
|
||||
"AlternateBase": "#353535",
|
||||
"ToolTipBase": "#ffffff",
|
||||
"ToolTipText": "#ffffff",
|
||||
"Text": "#ffffff",
|
||||
"Button": "#353535",
|
||||
"ButtonText": "#ffffff",
|
||||
"BrightText": "#ff0000",
|
||||
"Link": "#2a82da",
|
||||
"Highlight": "#4bb679",
|
||||
"HighlightedText": "#ffffff"
|
||||
},
|
||||
"background_image_base64": ""
|
||||
}
|
||||
|
||||
# Define the default content for Native.json
|
||||
native_theme_content = {
|
||||
"manifest": {
|
||||
"name": "Native",
|
||||
"description": "The native looks of your OS",
|
||||
"author": "Your Qt Style",
|
||||
"license": "Any"
|
||||
},
|
||||
"palette": {}
|
||||
}
|
||||
|
||||
# Step 1: Ensure the themes folder exists
|
||||
if not os.path.exists(themes_folder):
|
||||
print(f"Creating folder: {themes_folder}")
|
||||
os.makedirs(themes_folder)
|
||||
|
||||
# Step 2: Ensure Dark.json exists
|
||||
if not os.path.isfile(dark_theme_file):
|
||||
print(f"Creating file: {dark_theme_file}")
|
||||
with open(dark_theme_file, "w", encoding="utf-8") as file:
|
||||
json.dump(dark_theme_content, file, indent=2)
|
||||
print("Dark.json has been created successfully.")
|
||||
|
||||
# Step 3: Ensure Native.json exists
|
||||
if not os.path.isfile(native_theme_file):
|
||||
print(f"Creating file: {native_theme_file}")
|
||||
with open(native_theme_file, "w", encoding="utf-8") as file:
|
||||
json.dump(native_theme_content, file, indent=2)
|
||||
print("Native.json has been created successfully.")
|
||||
|
||||
# Check if both files exist and print OK message
|
||||
if os.path.isfile(dark_theme_file) and os.path.isfile(native_theme_file):
|
||||
print("Theme Integrity OK")
|
30
modulecli.py
30
modulecli.py
@ -1,30 +0,0 @@
|
||||
import click
|
||||
from picomc.cli.main import picomc_cli
|
||||
from io import StringIO
|
||||
import sys
|
||||
|
||||
def run_command(command="picomc"):
|
||||
# Redirect stdout and stderr to capture the command output
|
||||
old_stdout, old_stderr = sys.stdout, sys.stderr
|
||||
sys.stdout = mystdout = StringIO()
|
||||
sys.stderr = mystderr = StringIO()
|
||||
|
||||
try:
|
||||
picomc_cli.main(args=command.split())
|
||||
except SystemExit as e:
|
||||
if e.code != 0:
|
||||
print(f"Command exited with code {e.code}", file=sys.stderr)
|
||||
except Exception as e:
|
||||
print(f"Unexpected error: {e}", file=sys.stderr)
|
||||
finally:
|
||||
# Restore stdout and stderr
|
||||
sys.stdout = old_stdout
|
||||
sys.stderr = old_stderr
|
||||
|
||||
output = mystdout.getvalue().strip()
|
||||
error = mystderr.getvalue().strip()
|
||||
|
||||
if not output:
|
||||
return f"Error: No output from command. Stderr: {error}"
|
||||
|
||||
return output
|
574
picodulce.py
574
picodulce.py
@ -10,30 +10,21 @@ import requests
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
|
||||
from authser import MinecraftAuthenticator
|
||||
from healthcheck import HealthCheck
|
||||
import modulecli
|
||||
|
||||
from PyQt5.QtWidgets import QApplication, QComboBox, QWidget, QInputDialog, QVBoxLayout, QListWidget, 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.QtCore import Qt, QObject, pyqtSignal, QThread, QUrl, QMetaObject, Q_ARG, QByteArray, QSize
|
||||
from datetime import datetime
|
||||
|
||||
logging.basicConfig(level=logging.ERROR, format='%(levelname)s - %(message)s')
|
||||
logging.basicConfig(level=logging.ERROR, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
|
||||
class PicomcVersionSelector(QWidget):
|
||||
def __init__(self):
|
||||
self.current_state = "menu"
|
||||
self.open_dialogs = []
|
||||
|
||||
# Set up and use the health_check module
|
||||
health_checker = HealthCheck()
|
||||
health_checker.themes_integrity()
|
||||
health_checker.check_config_file()
|
||||
self.config = health_checker.config
|
||||
|
||||
self.check_config_file()
|
||||
self.themes_integrity()
|
||||
themes_folder = "themes"
|
||||
|
||||
theme_file = self.config.get("Theme", "Dark.json")
|
||||
|
||||
# Ensure the theme file exists in the themes directory
|
||||
@ -60,10 +51,6 @@ class PicomcVersionSelector(QWidget):
|
||||
if self.config.get("IsFirstLaunch", False):
|
||||
self.FirstLaunch()
|
||||
|
||||
self.authenticator = MinecraftAuthenticator(self)
|
||||
self.authenticator.auth_finished.connect(self._on_auth_finished)
|
||||
|
||||
|
||||
def load_theme_from_file(self, file_path, app):
|
||||
self.theme = {}
|
||||
# Check if the file exists, else load 'Dark.json'
|
||||
@ -123,17 +110,82 @@ class PicomcVersionSelector(QWidget):
|
||||
else:
|
||||
print("Theme dosn't seem to have a stylesheet")
|
||||
|
||||
def themes_integrity(self):
|
||||
# Define folder and file paths
|
||||
themes_folder = "themes"
|
||||
dark_theme_file = os.path.join(themes_folder, "Dark.json")
|
||||
native_theme_file = os.path.join(themes_folder, "Native.json")
|
||||
|
||||
# Define the default content for Dark.json
|
||||
dark_theme_content = {
|
||||
"manifest": {
|
||||
"name": "Dark",
|
||||
"description": "The default picodulce launcher theme",
|
||||
"author": "Nixietab",
|
||||
"license": "MIT"
|
||||
},
|
||||
"palette": {
|
||||
"Window": "#353535",
|
||||
"WindowText": "#ffffff",
|
||||
"Base": "#191919",
|
||||
"AlternateBase": "#353535",
|
||||
"ToolTipBase": "#ffffff",
|
||||
"ToolTipText": "#ffffff",
|
||||
"Text": "#ffffff",
|
||||
"Button": "#353535",
|
||||
"ButtonText": "#ffffff",
|
||||
"BrightText": "#ff0000",
|
||||
"Link": "#2a82da",
|
||||
"Highlight": "#4bb679",
|
||||
"HighlightedText": "#ffffff"
|
||||
},
|
||||
"background_image_base64": ""
|
||||
}
|
||||
|
||||
# Define the default content for Native.json
|
||||
native_theme_content = {
|
||||
"manifest": {
|
||||
"name": "Native",
|
||||
"description": "The native looks of your OS",
|
||||
"author": "Your Qt Style",
|
||||
"license": "Any"
|
||||
},
|
||||
"palette": {}
|
||||
}
|
||||
|
||||
# Step 1: Ensure the themes folder exists
|
||||
if not os.path.exists(themes_folder):
|
||||
print(f"Creating folder: {themes_folder}")
|
||||
os.makedirs(themes_folder)
|
||||
|
||||
# Step 2: Ensure Dark.json exists
|
||||
if not os.path.isfile(dark_theme_file):
|
||||
print(f"Creating file: {dark_theme_file}")
|
||||
with open(dark_theme_file, "w", encoding="utf-8") as file:
|
||||
json.dump(dark_theme_content, file, indent=2)
|
||||
print("Dark.json has been created successfully.")
|
||||
|
||||
# Step 3: Ensure Native.json exists
|
||||
if not os.path.isfile(native_theme_file):
|
||||
print(f"Creating file: {native_theme_file}")
|
||||
with open(native_theme_file, "w", encoding="utf-8") as file:
|
||||
json.dump(native_theme_content, file, indent=2)
|
||||
print("Native.json has been created successfully.")
|
||||
|
||||
# Check if both files exist and print OK message
|
||||
if os.path.isfile(dark_theme_file) and os.path.isfile(native_theme_file):
|
||||
print("Theme Integrity OK")
|
||||
|
||||
|
||||
def FirstLaunch(self):
|
||||
try:
|
||||
self.config_path = "config.json"
|
||||
print("Running picomc instance create default command...")
|
||||
|
||||
# Run the command using modulecli
|
||||
command = "instance create default"
|
||||
result = modulecli.run_command(command)
|
||||
# Run the command using subprocess
|
||||
result = subprocess.run(["picomc", "instance", "create", "default"], check=True, capture_output=True, text=True)
|
||||
|
||||
# Print the output of the command
|
||||
print("Command output:", result)
|
||||
print("Command output:", result.stdout)
|
||||
|
||||
# Change the value of IsFirstLaunch to False
|
||||
self.config["IsFirstLaunch"] = False
|
||||
@ -144,58 +196,15 @@ class PicomcVersionSelector(QWidget):
|
||||
json.dump(self.config, f, indent=4)
|
||||
print("Configuration saved to", self.config_path)
|
||||
|
||||
except Exception as e:
|
||||
except subprocess.CalledProcessError as e:
|
||||
print("An error occurred while creating the instance.")
|
||||
print("Error output:", str(e))
|
||||
print("Error output:", e.stderr)
|
||||
|
||||
def resize_event(self, event):
|
||||
if hasattr(self, 'movie_label'):
|
||||
self.movie_label.setGeometry(0, 0, 400, 320)
|
||||
event.accept() # Accept the resize event
|
||||
|
||||
def load_theme_background(self):
|
||||
"""Load and set the theme background image from base64 data in the theme configuration."""
|
||||
if not self.config.get("ThemeBackground", False): # Default to False if ThemeBackground is missing
|
||||
return
|
||||
|
||||
# Get the base64 string for the background image from the theme file
|
||||
theme_background_base64 = self.theme.get("background_image_base64", "")
|
||||
if not theme_background_base64:
|
||||
print("No background GIF base64 string found in the theme file.")
|
||||
return
|
||||
|
||||
try:
|
||||
# Decode the base64 string to get the binary data
|
||||
background_image_data = QByteArray.fromBase64(theme_background_base64.encode())
|
||||
temp_gif_path = "temp.gif" # Write the gif into a temp file because Qt stuff
|
||||
with open(temp_gif_path, 'wb') as temp_gif_file:
|
||||
temp_gif_file.write(background_image_data)
|
||||
|
||||
# Create a QMovie object from the temporary file
|
||||
movie = QMovie(temp_gif_path)
|
||||
if movie.isValid():
|
||||
self.setAutoFillBackground(True)
|
||||
palette = self.palette()
|
||||
|
||||
# Set the QMovie to a QLabel
|
||||
self.movie_label = QLabel(self)
|
||||
self.movie_label.setMovie(movie)
|
||||
self.movie_label.setGeometry(0, 0, movie.frameRect().width(), movie.frameRect().height())
|
||||
self.movie_label.setScaledContents(True) # Ensure the QLabel scales its contents
|
||||
movie.start()
|
||||
|
||||
# Use the QLabel pixmap as the brush texture
|
||||
brush = QBrush(QPixmap(movie.currentPixmap()))
|
||||
brush.setStyle(Qt.TexturePattern)
|
||||
palette.setBrush(QPalette.Window, brush)
|
||||
self.setPalette(palette)
|
||||
|
||||
# Adjust the QLabel size when the window is resized
|
||||
self.movie_label.resizeEvent = self.resize_event
|
||||
else:
|
||||
print("Error: Failed to load background GIF from base64 string.")
|
||||
except Exception as e:
|
||||
print(f"Error: Failed to decode and set background GIF. {e}")
|
||||
|
||||
def init_ui(self):
|
||||
self.setWindowTitle('PicoDulce Launcher') # Change window title
|
||||
@ -212,8 +221,44 @@ class PicomcVersionSelector(QWidget):
|
||||
with open("config.json", "r") as config_file:
|
||||
config = json.load(config_file)
|
||||
|
||||
# Load theme background
|
||||
self.load_theme_background()
|
||||
if self.config.get("ThemeBackground", False): # Default to False if ThemeBackground is missing
|
||||
# Get the base64 string for the background image from the theme file
|
||||
theme_background_base64 = self.theme.get("background_image_base64", "")
|
||||
if theme_background_base64:
|
||||
try:
|
||||
# Decode the base64 string to get the binary data
|
||||
background_image_data = QByteArray.fromBase64(theme_background_base64.encode())
|
||||
temp_gif_path = "temp.gif" # Write the gif into a temp file because Qt stuff
|
||||
with open(temp_gif_path, 'wb') as temp_gif_file:
|
||||
temp_gif_file.write(background_image_data)
|
||||
|
||||
# Create a QMovie object from the temporary file
|
||||
movie = QMovie(temp_gif_path)
|
||||
if movie.isValid():
|
||||
self.setAutoFillBackground(True)
|
||||
palette = self.palette()
|
||||
|
||||
# Set the QMovie to a QLabel
|
||||
self.movie_label = QLabel(self)
|
||||
self.movie_label.setMovie(movie)
|
||||
self.movie_label.setGeometry(0, 0, movie.frameRect().width(), movie.frameRect().height())
|
||||
self.movie_label.setScaledContents(True) # Ensure the QLabel scales its contents
|
||||
movie.start()
|
||||
|
||||
# Use the QLabel pixmap as the brush texture
|
||||
brush = QBrush(QPixmap(movie.currentPixmap()))
|
||||
brush.setStyle(Qt.TexturePattern)
|
||||
palette.setBrush(QPalette.Window, brush)
|
||||
self.setPalette(palette)
|
||||
|
||||
# Adjust the QLabel size when the window is resized
|
||||
self.movie_label.resizeEvent = self.resize_event
|
||||
else:
|
||||
print("Error: Failed to load background GIF from base64 string.")
|
||||
except Exception as e:
|
||||
print(f"Error: Failed to decode and set background GIF. {e}")
|
||||
else:
|
||||
print("No background GIF base64 string found in the theme file.")
|
||||
|
||||
# Create title label
|
||||
title_label = QLabel('PicoDulce Launcher') # Change label text
|
||||
@ -278,7 +323,7 @@ class PicomcVersionSelector(QWidget):
|
||||
main_layout.setSpacing(20)
|
||||
|
||||
self.setLayout(main_layout)
|
||||
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
focus_widget = self.focusWidget()
|
||||
if event.key() == Qt.Key_Down:
|
||||
@ -293,6 +338,50 @@ class PicomcVersionSelector(QWidget):
|
||||
else:
|
||||
super().keyPressEvent(event)
|
||||
|
||||
def check_config_file(self):
|
||||
config_path = "config.json"
|
||||
default_config = {
|
||||
"IsRCPenabled": False,
|
||||
"CheckUpdate": False,
|
||||
"IsBleeding": False,
|
||||
"LastPlayed": "",
|
||||
"IsFirstLaunch": True,
|
||||
"Instance": "default",
|
||||
"Theme": "Dark.json",
|
||||
"ThemeBackground": True,
|
||||
"ThemeRepository": "https://raw.githubusercontent.com/nixietab/picodulce-themes/main/repo.json"
|
||||
}
|
||||
|
||||
# Step 1: Check if the file exists; if not, create it with default values
|
||||
if not os.path.exists(config_path):
|
||||
with open(config_path, "w") as config_file:
|
||||
json.dump(default_config, config_file, indent=4)
|
||||
self.config = default_config
|
||||
return
|
||||
|
||||
# Step 2: Try loading the config file, handle invalid JSON
|
||||
try:
|
||||
with open(config_path, "r") as config_file:
|
||||
self.config = json.load(config_file)
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
# File is corrupted, overwrite it with default configuration
|
||||
with open(config_path, "w") as config_file:
|
||||
json.dump(default_config, config_file, indent=4)
|
||||
self.config = default_config
|
||||
return
|
||||
|
||||
# Step 3: Check for missing keys and add defaults if necessary
|
||||
updated = False
|
||||
for key, value in default_config.items():
|
||||
if key not in self.config: # Field is missing
|
||||
self.config[key] = value
|
||||
updated = True
|
||||
|
||||
# Step 4: Save the repaired config back to the file
|
||||
if updated:
|
||||
with open(config_path, "w") as config_file:
|
||||
json.dump(self.config, config_file, indent=4)
|
||||
|
||||
def open_settings_dialog(self):
|
||||
dialog = QDialog(self)
|
||||
dialog.setWindowTitle('Settings')
|
||||
@ -356,27 +445,24 @@ class PicomcVersionSelector(QWidget):
|
||||
|
||||
# QListWidget to display available themes
|
||||
json_files_label = QLabel('Installed Themes:')
|
||||
self.json_files_list_widget = QListWidget()
|
||||
json_files_list_widget = QListWidget()
|
||||
|
||||
# Track selected theme
|
||||
self.selected_theme = theme_filename # Default to current theme
|
||||
|
||||
# Build the list of themes
|
||||
themes_list = self.build_themes_list()
|
||||
|
||||
# Populate themes initially
|
||||
self.populate_themes(self.json_files_list_widget, themes_list)
|
||||
self.populate_themes(json_files_list_widget)
|
||||
|
||||
# Update current theme label when a theme is selected
|
||||
self.json_files_list_widget.itemClicked.connect(
|
||||
lambda: self.on_theme_selected(self.json_files_list_widget, current_theme_label)
|
||||
json_files_list_widget.itemClicked.connect(
|
||||
lambda: self.on_theme_selected(json_files_list_widget, current_theme_label)
|
||||
)
|
||||
|
||||
# Add widgets to the layout
|
||||
customization_layout.addWidget(theme_background_checkbox)
|
||||
customization_layout.addWidget(current_theme_label)
|
||||
customization_layout.addWidget(json_files_label)
|
||||
customization_layout.addWidget(self.json_files_list_widget)
|
||||
customization_layout.addWidget(json_files_list_widget)
|
||||
|
||||
# Button to download themes
|
||||
download_themes_button = QPushButton("Download More Themes")
|
||||
@ -397,8 +483,8 @@ class PicomcVersionSelector(QWidget):
|
||||
discord_rcp_checkbox.isChecked(),
|
||||
check_updates_checkbox.isChecked(),
|
||||
theme_background_checkbox.isChecked(),
|
||||
self.selected_theme,
|
||||
bleeding_edge_checkbox.isChecked()
|
||||
self.selected_theme, # Pass the selected theme here
|
||||
bleeding_edge_checkbox.isChecked() # Pass the bleeding edge setting here
|
||||
)
|
||||
)
|
||||
|
||||
@ -421,9 +507,9 @@ class PicomcVersionSelector(QWidget):
|
||||
if response == QMessageBox.No:
|
||||
checkbox.setChecked(False)
|
||||
|
||||
def build_themes_list(self):
|
||||
def populate_themes(self, json_files_list_widget):
|
||||
themes_folder = os.path.join(os.getcwd(), "themes")
|
||||
themes_list = []
|
||||
json_files_list_widget.clear()
|
||||
if os.path.exists(themes_folder):
|
||||
json_files = [f for f in os.listdir(themes_folder) if f.endswith('.json')]
|
||||
for json_file in json_files:
|
||||
@ -439,21 +525,15 @@ class PicomcVersionSelector(QWidget):
|
||||
|
||||
# Create display text and list item
|
||||
display_text = f"{name}\n{description}\nBy: {author}"
|
||||
themes_list.append((display_text, json_file))
|
||||
return themes_list
|
||||
list_item = QListWidgetItem(display_text)
|
||||
list_item.setData(Qt.UserRole, json_file) # Store the JSON filename as metadata
|
||||
|
||||
def populate_themes(self, json_files_list_widget, themes_list):
|
||||
json_files_list_widget.clear()
|
||||
for display_text, json_file in themes_list:
|
||||
list_item = QListWidgetItem(display_text)
|
||||
list_item.setData(Qt.UserRole, json_file) # Store the JSON filename as metadata
|
||||
# Style the name in bold
|
||||
font = QFont()
|
||||
font.setBold(False)
|
||||
list_item.setFont(font)
|
||||
|
||||
# Style the name in bold
|
||||
font = QFont()
|
||||
font.setBold(False)
|
||||
list_item.setFont(font)
|
||||
|
||||
json_files_list_widget.addItem(list_item)
|
||||
json_files_list_widget.addItem(list_item)
|
||||
|
||||
# Apply spacing and styling to the list
|
||||
json_files_list_widget.setStyleSheet("""
|
||||
@ -471,8 +551,8 @@ class PicomcVersionSelector(QWidget):
|
||||
if selected_item:
|
||||
self.selected_theme = selected_item.data(Qt.UserRole)
|
||||
current_theme_label.setText(f"Current Theme: {self.selected_theme}")
|
||||
|
||||
## REPOSITORY BLOCK BEGGINS
|
||||
|
||||
## REPOSITORY BLOCK BEGGINS
|
||||
|
||||
def download_themes_window(self):
|
||||
dialog = QDialog(self)
|
||||
@ -509,16 +589,9 @@ class PicomcVersionSelector(QWidget):
|
||||
main_layout.addLayout(right_layout)
|
||||
dialog.setLayout(main_layout)
|
||||
|
||||
dialog.finished.connect(lambda: self.update_themes_list())
|
||||
|
||||
|
||||
self.load_themes()
|
||||
dialog.exec_()
|
||||
|
||||
def update_themes_list(self):
|
||||
themes_list = self.build_themes_list()
|
||||
self.populate_themes(self.json_files_list_widget, themes_list)
|
||||
|
||||
def fetch_themes(self):
|
||||
try:
|
||||
with open("config.json", "r") as config_file:
|
||||
@ -662,6 +735,20 @@ class PicomcVersionSelector(QWidget):
|
||||
)
|
||||
self.__init__()
|
||||
|
||||
def get_palette(self, palette_type):
|
||||
"""Retrieve the corresponding palette based on the palette type."""
|
||||
palettes = {
|
||||
"Dark": self.create_dark_palette,
|
||||
"Obsidian": self.create_obsidian_palette,
|
||||
"Redstone": self.create_redstone_palette,
|
||||
"Alpha": self.create_alpha_palette,
|
||||
"Strawberry": self.create_strawberry_palette,
|
||||
"Native": self.create_native_palette,
|
||||
"Christmas": self.create_christmas_palette,
|
||||
}
|
||||
# Default to dark palette if the type is not specified or invalid
|
||||
return palettes.get(palette_type, self.create_dark_palette)()
|
||||
|
||||
def get_system_info(self):
|
||||
# Get system information
|
||||
java_version = subprocess.getoutput("java -version 2>&1 | head -n 1")
|
||||
@ -701,16 +788,16 @@ class PicomcVersionSelector(QWidget):
|
||||
|
||||
def open_game_directory(self):
|
||||
try:
|
||||
# Run the command using modulecli
|
||||
command = "instance dir"
|
||||
result = modulecli.run_command(command)
|
||||
game_directory = result.strip()
|
||||
# Run the command and capture the output
|
||||
result = subprocess.run(['picomc', 'instance', 'dir'], capture_output=True, text=True, check=True)
|
||||
game_directory = result.stdout.strip()
|
||||
|
||||
# Open the directory in the system's file explorer
|
||||
QDesktopServices.openUrl(QUrl.fromLocalFile(game_directory))
|
||||
except Exception as e:
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error running picomc command: {e}")
|
||||
|
||||
|
||||
def populate_installed_versions(self):
|
||||
config_path = "config.json"
|
||||
|
||||
@ -731,17 +818,20 @@ class PicomcVersionSelector(QWidget):
|
||||
|
||||
# Run the command and capture the output
|
||||
try:
|
||||
command = "version list"
|
||||
output = modulecli.run_command(command)
|
||||
process = subprocess.Popen(['picomc', 'version', 'list'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
output, error = process.communicate()
|
||||
|
||||
if not output:
|
||||
raise Exception("Failed to get output from modulecli")
|
||||
except Exception as e:
|
||||
logging.error("Error running 'picomc': %s", e)
|
||||
if process.returncode != 0:
|
||||
raise subprocess.CalledProcessError(process.returncode, process.args, output=output, stderr=error)
|
||||
except FileNotFoundError:
|
||||
logging.error("'picomc' command not found. Please ensure it's installed and in your PATH.")
|
||||
return
|
||||
except subprocess.CalledProcessError as e:
|
||||
logging.error("Error running 'picomc': %s", e.stderr)
|
||||
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()]
|
||||
|
||||
# Get the last played version from the config
|
||||
last_played = self.config.get("LastPlayed", "")
|
||||
@ -754,26 +844,32 @@ class PicomcVersionSelector(QWidget):
|
||||
# Populate the installed versions combo box
|
||||
self.installed_version_combo.clear()
|
||||
self.installed_version_combo.addItems(versions)
|
||||
|
||||
|
||||
def populate_installed_versions_normal_order(self):
|
||||
# Run the 'picomc instance create default' command at the start
|
||||
try:
|
||||
command = "instance create default"
|
||||
output = modulecli.run_command(command)
|
||||
if not output:
|
||||
raise Exception("Failed to get output from modulecli for 'instance create default'")
|
||||
except Exception as e:
|
||||
logging.error("Error creating default instance: %s", str(e))
|
||||
process = subprocess.Popen(['picomc', 'instance', 'create', 'default'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
output, error = process.communicate()
|
||||
if process.returncode != 0:
|
||||
raise subprocess.CalledProcessError(process.returncode, process.args, error)
|
||||
except FileNotFoundError:
|
||||
logging.error("'picomc' command not found. Please make sure it's installed and in your PATH.")
|
||||
return
|
||||
except subprocess.CalledProcessError as e:
|
||||
logging.error("Error creating default instance: %s", e.stderr)
|
||||
return
|
||||
|
||||
# Run the 'picomc version list' command and get the output
|
||||
try:
|
||||
command = "version list"
|
||||
output = modulecli.run_command(command)
|
||||
if not output:
|
||||
raise Exception("Failed to get output from modulecli for 'version list'")
|
||||
except Exception as e:
|
||||
logging.error("Error: %s", str(e))
|
||||
process = subprocess.Popen(['picomc', 'version', 'list'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
output, error = process.communicate()
|
||||
if process.returncode != 0:
|
||||
raise subprocess.CalledProcessError(process.returncode, process.args, error)
|
||||
except FileNotFoundError:
|
||||
logging.error("'picomc' command not found. Please make sure it's installed and in your PATH.")
|
||||
return
|
||||
except subprocess.CalledProcessError as e:
|
||||
logging.error("Error: %s", e.stderr)
|
||||
return
|
||||
|
||||
# Parse the output and replace '[local]' with a space
|
||||
@ -800,7 +896,7 @@ class PicomcVersionSelector(QWidget):
|
||||
|
||||
# Check if there are any accounts
|
||||
try:
|
||||
account_list_output = modulecli.run_command("account list").strip()
|
||||
account_list_output = subprocess.check_output(["picomc", "account", "list"]).decode("utf-8").strip()
|
||||
if not account_list_output:
|
||||
QMessageBox.warning(self, "No Account Available", "Please create an account first.")
|
||||
return
|
||||
@ -809,25 +905,18 @@ class PicomcVersionSelector(QWidget):
|
||||
if '*' not in account_list_output:
|
||||
QMessageBox.warning(self, "No Account Selected", "Please select an account.")
|
||||
return
|
||||
except Exception as e:
|
||||
except subprocess.CalledProcessError as e:
|
||||
error_message = f"Error fetching accounts: {str(e)}"
|
||||
logging.error(error_message)
|
||||
QMessageBox.critical(self, "Error", error_message)
|
||||
return
|
||||
|
||||
selected_instance = self.installed_version_combo.currentText()
|
||||
logging.info(f"Selected instance from dropdown: {selected_instance}")
|
||||
|
||||
# Verify the selected instance value before starting the game
|
||||
if not selected_instance:
|
||||
logging.error("No instance selected.")
|
||||
QMessageBox.warning(self, "No Instance Selected", "Please select an instance.")
|
||||
return
|
||||
logging.info(f"Selected instance: {selected_instance}")
|
||||
|
||||
play_thread = threading.Thread(target=self.run_game, args=(selected_instance,))
|
||||
play_thread.start()
|
||||
|
||||
|
||||
def run_game(self, selected_instance):
|
||||
try:
|
||||
# Set current_state to the selected instance
|
||||
@ -842,14 +931,10 @@ class PicomcVersionSelector(QWidget):
|
||||
update_thread = threading.Thread(target=self.update_last_played, args=(selected_instance,))
|
||||
update_thread.start()
|
||||
|
||||
# Run the game using the modulecli module
|
||||
command = f"instance launch --version-override {selected_instance} {instance_value}"
|
||||
output = modulecli.run_command(command)
|
||||
|
||||
if not output:
|
||||
raise Exception("Failed to get output from modulecli")
|
||||
# Run the game subprocess with the instance_value from config.json
|
||||
subprocess.run(['picomc', 'instance', 'launch', '--version-override', selected_instance, instance_value], check=True)
|
||||
|
||||
except Exception as e:
|
||||
except subprocess.CalledProcessError as e:
|
||||
error_message = f"Error playing {selected_instance}: {e}"
|
||||
logging.error(error_message)
|
||||
# Use QMetaObject.invokeMethod to call showError safely
|
||||
@ -861,7 +946,6 @@ class PicomcVersionSelector(QWidget):
|
||||
# Reset current_state to "menu" after the game closes
|
||||
self.current_state = "menu"
|
||||
|
||||
|
||||
def update_last_played(self, selected_instance):
|
||||
config_path = "config.json"
|
||||
self.config["LastPlayed"] = selected_instance
|
||||
@ -959,16 +1043,16 @@ class PicomcVersionSelector(QWidget):
|
||||
return
|
||||
|
||||
try:
|
||||
command = f"account create {username}"
|
||||
command = ['picomc', 'account', 'create', username]
|
||||
if is_microsoft:
|
||||
command += " --ms"
|
||||
command.append('--ms')
|
||||
|
||||
modulecli.run_command(command)
|
||||
subprocess.run(command, check=True)
|
||||
QMessageBox.information(dialog, "Success", f"Account '{username}' created successfully!")
|
||||
self.populate_accounts_for_all_dialogs()
|
||||
dialog.accept()
|
||||
except Exception as e:
|
||||
error_message = f"Error creating account: {str(e)}"
|
||||
except subprocess.CalledProcessError as e:
|
||||
error_message = f"Error creating account: {e.stderr.decode()}"
|
||||
logging.error(error_message)
|
||||
QMessageBox.critical(dialog, "Error", error_message)
|
||||
|
||||
@ -979,36 +1063,19 @@ class PicomcVersionSelector(QWidget):
|
||||
return False
|
||||
|
||||
def authenticate_account(self, dialog, account_name):
|
||||
# Clean up the account name
|
||||
# Authenticate a selected account
|
||||
account_name = account_name.strip().lstrip(" * ")
|
||||
if not account_name:
|
||||
QMessageBox.warning(dialog, "Warning", "Please select an account to authenticate.")
|
||||
return
|
||||
|
||||
try:
|
||||
# Create authenticator instance if it doesn't exist
|
||||
if self.authenticator is None:
|
||||
self.authenticator = MinecraftAuthenticator(self)
|
||||
self.authenticator.auth_finished.connect(self._on_auth_finished)
|
||||
|
||||
# Start authentication process
|
||||
self.authenticator.authenticate(account_name)
|
||||
|
||||
except Exception as e:
|
||||
error_message = f"Error authenticating account '{account_name}': {str(e)}"
|
||||
subprocess.run(['picomc', 'account', 'authenticate', account_name], check=True)
|
||||
QMessageBox.information(self, "Success", f"Account '{account_name}' authenticated successfully!")
|
||||
except subprocess.CalledProcessError as e:
|
||||
error_message = f"Error authenticating account '{account_name}': {e.stderr.decode()}"
|
||||
logging.error(error_message)
|
||||
QMessageBox.critical(dialog, "Error", error_message)
|
||||
|
||||
def _on_auth_finished(self, success):
|
||||
if success:
|
||||
QMessageBox.information(self, "Success", "Account authenticated successfully!")
|
||||
else:
|
||||
QMessageBox.critical(self, "Error", "Failed to authenticate account")
|
||||
|
||||
# Cleanup
|
||||
if self.authenticator:
|
||||
self.authenticator.cleanup()
|
||||
self.authenticator = None
|
||||
QMessageBox.critical(self, "Error", error_message)
|
||||
|
||||
def remove_account(self, dialog, username):
|
||||
# Remove a selected account
|
||||
@ -1021,21 +1088,23 @@ class PicomcVersionSelector(QWidget):
|
||||
confirm_dialog = QMessageBox.question(dialog, "Confirm Removal", confirm_message, QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
|
||||
if confirm_dialog == QMessageBox.Yes:
|
||||
try:
|
||||
command = f"account remove {username}"
|
||||
modulecli.run_command(command)
|
||||
subprocess.run(['picomc', 'account', 'remove', username], check=True)
|
||||
QMessageBox.information(dialog, "Success", f"Account '{username}' removed successfully!")
|
||||
self.populate_accounts_for_all_dialogs()
|
||||
except Exception as e:
|
||||
error_message = f"Error removing account: {str(e)}"
|
||||
except subprocess.CalledProcessError as e:
|
||||
error_message = f"Error removing account: {e.stderr.decode()}"
|
||||
logging.error(error_message)
|
||||
QMessageBox.critical(dialog, "Error", error_message)
|
||||
|
||||
|
||||
def populate_accounts(self, account_combo):
|
||||
# Populate the account dropdown
|
||||
try:
|
||||
command = "account list"
|
||||
output = modulecli.run_command(command)
|
||||
|
||||
process = subprocess.Popen(['picomc', 'account', 'list'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
output, error = process.communicate()
|
||||
if process.returncode != 0:
|
||||
raise subprocess.CalledProcessError(process.returncode, process.args, error)
|
||||
|
||||
# Process accounts, keeping the one with "*" at the top
|
||||
accounts = output.splitlines()
|
||||
starred_account = None
|
||||
@ -1059,8 +1128,10 @@ class PicomcVersionSelector(QWidget):
|
||||
for account in normal_accounts:
|
||||
account_combo.addItem(account)
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Error: {str(e)}")
|
||||
except FileNotFoundError:
|
||||
logging.error("'picomc' command not found. Please make sure it's installed and in your PATH.")
|
||||
except subprocess.CalledProcessError as e:
|
||||
logging.error(f"Error: {e.stderr}")
|
||||
|
||||
def populate_accounts_for_all_dialogs(self):
|
||||
# Update account dropdowns in all open dialogs
|
||||
@ -1077,16 +1148,14 @@ class PicomcVersionSelector(QWidget):
|
||||
return
|
||||
|
||||
try:
|
||||
command = f"account setdefault {account_name}"
|
||||
modulecli.run_command(command)
|
||||
subprocess.run(['picomc', 'account', 'setdefault', account_name], check=True)
|
||||
QMessageBox.information(self, "Success", f"Account '{account_name}' set as default!")
|
||||
self.populate_accounts_for_all_dialogs()
|
||||
except Exception as e:
|
||||
error_message = f"Error setting default account '{account_name}': {str(e)}"
|
||||
except subprocess.CalledProcessError as e:
|
||||
error_message = f"Error setting default account '{account_name}': {e.stderr.decode()}"
|
||||
logging.error(error_message)
|
||||
QMessageBox.critical(self, "Error", error_message)
|
||||
|
||||
|
||||
def show_about_dialog(self):
|
||||
# Load the version number from version.json
|
||||
try:
|
||||
@ -1157,7 +1226,7 @@ class PicomcVersionSelector(QWidget):
|
||||
# Download and apply the update
|
||||
self.download_update(remote_version_info)
|
||||
else:
|
||||
print(f"You already have the latest version!")
|
||||
QMessageBox.information(self, "Up to Date", "You already have the latest version!")
|
||||
else:
|
||||
logging.error("Failed to read local version information.")
|
||||
QMessageBox.critical(self, "Error", "Failed to check for updates.")
|
||||
@ -1316,11 +1385,10 @@ class DownloadThread(QThread):
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
command = f"version prepare {self.version}"
|
||||
modulecli.run_command(command)
|
||||
subprocess.run(['picomc', 'version', 'prepare', self.version], check=True)
|
||||
self.completed.emit(True, f"Version {self.version} prepared successfully!")
|
||||
except Exception as e:
|
||||
error_message = f"Error preparing {self.version}: {str(e)}"
|
||||
except subprocess.CalledProcessError as e:
|
||||
error_message = f"Error preparing {self.version}: {e.stderr.decode()}"
|
||||
self.completed.emit(False, error_message)
|
||||
|
||||
class ModLoaderAndVersionMenu(QDialog):
|
||||
@ -1337,11 +1405,11 @@ class ModLoaderAndVersionMenu(QDialog):
|
||||
# Create tabs
|
||||
install_mod_tab = QWidget()
|
||||
download_version_tab = QWidget()
|
||||
instances_tab = QWidget()
|
||||
instances_tab = QWidget() # New tab for instances
|
||||
|
||||
tab_widget.addTab(download_version_tab, "Download Version")
|
||||
tab_widget.addTab(install_mod_tab, "Install Mod Loader")
|
||||
tab_widget.addTab(instances_tab, "Instances")
|
||||
tab_widget.addTab(instances_tab, "Instances") # Add the new tab
|
||||
|
||||
# Add content to "Install Mod Loader" tab
|
||||
self.setup_install_mod_loader_tab(install_mod_tab)
|
||||
@ -1393,8 +1461,11 @@ class ModLoaderAndVersionMenu(QDialog):
|
||||
if instance_name:
|
||||
try:
|
||||
# Run the "picomc instance create" command
|
||||
command = f"instance create {instance_name}"
|
||||
modulecli.run_command(command)
|
||||
process = subprocess.Popen(['picomc', 'instance', 'create', instance_name], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
output, error = process.communicate()
|
||||
|
||||
if process.returncode != 0:
|
||||
raise subprocess.CalledProcessError(process.returncode, process.args, error)
|
||||
|
||||
# Notify the user that the instance was created
|
||||
QMessageBox.information(self, "Instance Created", f"Instance '{instance_name}' has been created successfully.")
|
||||
@ -1405,9 +1476,11 @@ class ModLoaderAndVersionMenu(QDialog):
|
||||
# Optionally select the newly created instance
|
||||
self.on_instance_selected(self.instances_list_widget.item(self.instances_list_widget.count() - 1))
|
||||
|
||||
except Exception as e:
|
||||
logging.error("Error creating instance: %s", str(e))
|
||||
QMessageBox.critical(self, "Error", f"Failed to create instance: {str(e)}")
|
||||
except FileNotFoundError:
|
||||
logging.error("'picomc' command not found. Please make sure it's installed and in your PATH.")
|
||||
except subprocess.CalledProcessError as e:
|
||||
logging.error("Error creating instance: %s", e.stderr)
|
||||
QMessageBox.critical(self, "Error", f"Failed to create instance: {e.stderr}")
|
||||
else:
|
||||
QMessageBox.warning(self, "Invalid Input", "Please enter a valid instance name.")
|
||||
|
||||
@ -1418,8 +1491,14 @@ class ModLoaderAndVersionMenu(QDialog):
|
||||
|
||||
try:
|
||||
# Run the "picomc instance rename" command
|
||||
command = f"instance rename {old_instance_name} {new_instance_name}"
|
||||
modulecli.run_command(command)
|
||||
process = subprocess.Popen(
|
||||
['picomc', 'instance', 'rename', old_instance_name, new_instance_name],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
|
||||
)
|
||||
output, error = process.communicate()
|
||||
|
||||
if process.returncode != 0:
|
||||
raise subprocess.CalledProcessError(process.returncode, process.args, error)
|
||||
|
||||
QMessageBox.information(self, "Instance Renamed", f"Instance '{old_instance_name}' has been renamed to '{new_instance_name}' successfully.")
|
||||
|
||||
@ -1431,9 +1510,11 @@ class ModLoaderAndVersionMenu(QDialog):
|
||||
if matching_items:
|
||||
self.instances_list_widget.setCurrentItem(matching_items[0])
|
||||
|
||||
except Exception as e:
|
||||
logging.error("Error renaming instance: %s", str(e))
|
||||
QMessageBox.critical(self, "Error", f"Failed to rename instance: {str(e)}")
|
||||
except FileNotFoundError:
|
||||
logging.error("'picomc' command not found. Please make sure it's installed and in your PATH.")
|
||||
except subprocess.CalledProcessError as e:
|
||||
logging.error("Error renaming instance: %s", e.stderr)
|
||||
QMessageBox.critical(self, "Error", f"Failed to rename instance: {e.stderr}")
|
||||
|
||||
def delete_instance(self, instance_name):
|
||||
if instance_name == "default":
|
||||
@ -1448,8 +1529,11 @@ class ModLoaderAndVersionMenu(QDialog):
|
||||
if confirm_delete == QMessageBox.Yes:
|
||||
try:
|
||||
# Run the "picomc instance delete" command
|
||||
command = f"instance delete {instance_name}"
|
||||
modulecli.run_command(command)
|
||||
process = subprocess.Popen(['picomc', 'instance', 'delete', instance_name], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
output, error = process.communicate()
|
||||
|
||||
if process.returncode != 0:
|
||||
raise subprocess.CalledProcessError(process.returncode, process.args, error)
|
||||
|
||||
# Notify the user that the instance was deleted
|
||||
QMessageBox.information(self, "Instance Deleted", f"Instance '{instance_name}' has been deleted successfully.")
|
||||
@ -1457,16 +1541,19 @@ class ModLoaderAndVersionMenu(QDialog):
|
||||
# Reload the instances list
|
||||
self.load_instances()
|
||||
|
||||
except Exception as e:
|
||||
logging.error("Error deleting instance: %s", str(e))
|
||||
QMessageBox.critical(self, "Error", f"Failed to delete instance: {str(e)}")
|
||||
except FileNotFoundError:
|
||||
logging.error("'picomc' command not found. Please make sure it's installed and in your PATH.")
|
||||
except subprocess.CalledProcessError as e:
|
||||
logging.error("Error deleting instance: %s", e.stderr)
|
||||
QMessageBox.critical(self, "Error", f"Failed to delete instance: {e.stderr}")
|
||||
|
||||
def load_instances(self):
|
||||
try:
|
||||
# Run the "picomc instance list" command
|
||||
command = "instance list"
|
||||
output = modulecli.run_command(command)
|
||||
|
||||
process = subprocess.Popen(['picomc', 'instance', 'list'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
output, error = process.communicate()
|
||||
if process.returncode != 0:
|
||||
raise subprocess.CalledProcessError(process.returncode, process.args, error)
|
||||
|
||||
# Parse the output and add each instance to the list widget
|
||||
instances = output.splitlines()
|
||||
self.instances_list_widget.clear() # Clear the previous list
|
||||
@ -1475,9 +1562,10 @@ class ModLoaderAndVersionMenu(QDialog):
|
||||
self.instances_list_widget.addItem(item)
|
||||
self.add_instance_buttons(item, instance)
|
||||
|
||||
except Exception as e:
|
||||
logging.error("Error fetching instances: %s", str(e))
|
||||
|
||||
except FileNotFoundError:
|
||||
logging.error("'picomc' command not found. Please make sure it's installed and in your PATH.")
|
||||
except subprocess.CalledProcessError as e:
|
||||
logging.error("Error fetching instances: %s", e.stderr)
|
||||
|
||||
def add_instance_buttons(self, list_item, instance_name):
|
||||
widget = QWidget()
|
||||
@ -1633,19 +1721,21 @@ class ModLoaderAndVersionMenu(QDialog):
|
||||
options.append('--beta')
|
||||
if options:
|
||||
try:
|
||||
command = 'version list ' + ' '.join(options)
|
||||
output = modulecli.run_command(command)
|
||||
if "Error" in output:
|
||||
logging.error(output)
|
||||
return
|
||||
|
||||
# Parse the output and replace '[local]' with a space
|
||||
versions = output.splitlines()
|
||||
versions = [version.replace('[local]', ' ').strip() for version in versions]
|
||||
self.version_combo.addItems(versions)
|
||||
except Exception as e:
|
||||
logging.error("Unexpected error: %s", e)
|
||||
process = subprocess.Popen(['picomc', 'version', 'list'] + options, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
output, error = process.communicate()
|
||||
if process.returncode != 0:
|
||||
raise subprocess.CalledProcessError(process.returncode, process.args, error)
|
||||
except FileNotFoundError:
|
||||
logging.error("'picomc' command not found. Please make sure it's installed and in your PATH.")
|
||||
return
|
||||
except subprocess.CalledProcessError as e:
|
||||
logging.error("Error: %s", e.stderr)
|
||||
return
|
||||
|
||||
# Parse the output and replace '[local]' with a space
|
||||
versions = output.splitlines()
|
||||
versions = [version.replace('[local]', ' ').strip() for version in versions]
|
||||
self.version_combo.addItems(versions)
|
||||
# Update the download button state whenever versions are updated
|
||||
self.update_download_button_state()
|
||||
|
||||
@ -1662,7 +1752,7 @@ class ModLoaderAndVersionMenu(QDialog):
|
||||
|
||||
# Connect the combo box signal to the update function
|
||||
self.version_combo.currentIndexChanged.connect(self.update_download_button_state)
|
||||
|
||||
|
||||
def update_download_button_state(self):
|
||||
self.download_button.setEnabled(self.version_combo.currentIndex() != -1)
|
||||
|
||||
@ -1702,10 +1792,15 @@ class ModLoaderAndVersionMenu(QDialog):
|
||||
|
||||
def populate_available_releases(self, version_combo, install_forge, install_fabric):
|
||||
try:
|
||||
command = "version list --release"
|
||||
output = modulecli.run_command(command)
|
||||
except Exception as e:
|
||||
logging.error("Error: %s", str(e))
|
||||
process = subprocess.Popen(['picomc', 'version', 'list', '--release'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
output, error = process.communicate()
|
||||
if process.returncode != 0:
|
||||
raise subprocess.CalledProcessError(process.returncode, process.args, error)
|
||||
except FileNotFoundError:
|
||||
logging.error("'picomc' command not found. Please make sure it's installed and in your PATH.")
|
||||
return
|
||||
except subprocess.CalledProcessError as e:
|
||||
logging.error("Error: %s", e.stderr)
|
||||
return
|
||||
|
||||
if install_fabric:
|
||||
@ -1735,13 +1830,12 @@ class ModLoaderAndVersionMenu(QDialog):
|
||||
|
||||
try:
|
||||
if mod_loader == 'forge':
|
||||
command = f"mod loader forge install --game {version}"
|
||||
subprocess.run(['picomc', 'mod', 'loader', 'forge', 'install', '--game', version], check=True)
|
||||
elif mod_loader == 'fabric':
|
||||
command = f"mod loader fabric install {version}"
|
||||
modulecli.run_command(command)
|
||||
subprocess.run(['picomc', 'mod', 'loader', 'fabric', 'install', version], check=True)
|
||||
QMessageBox.information(self, "Success", f"{mod_loader.capitalize()} installed successfully for version {version}!")
|
||||
except Exception as e:
|
||||
error_message = f"Error installing {mod_loader} for version {version}: {str(e)}"
|
||||
except subprocess.CalledProcessError as e:
|
||||
error_message = f"Error installing {mod_loader} for version {version}: {e.stderr.decode()}"
|
||||
QMessageBox.critical(self, "Error", error_message)
|
||||
logging.error(error_message)
|
||||
|
||||
|
@ -1,15 +1,12 @@
|
||||
{
|
||||
"version": "0.13",
|
||||
"version": "0.11.9",
|
||||
"links": [
|
||||
"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/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/holiday.ico",
|
||||
"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/modulecli.py"
|
||||
"https://raw.githubusercontent.com/nixietab/picodulce/main/holiday.ico"
|
||||
],
|
||||
"versionBleeding": "0.13-194"
|
||||
"versionBleeding": "0.11.8-149"
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user