mirror of
https://github.com/nixietab/picodulce.git
synced 2025-04-07 00:48:58 +01:00
Compare commits
56 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ba8072c669 | ||
![]() |
785e9be9f9 | ||
![]() |
0cbd000be4 | ||
![]() |
52b635285e | ||
![]() |
67a16c008a | ||
![]() |
a4bd707461 | ||
![]() |
1b27fffc96 | ||
![]() |
fade5f86b7 | ||
![]() |
823b438840 | ||
![]() |
9a8c3f44d0 | ||
![]() |
6b65fb0d1e | ||
![]() |
8247009d60 | ||
![]() |
e5c395d031 | ||
![]() |
263e6eae07 | ||
![]() |
ec99488326 | ||
![]() |
61cd427beb | ||
![]() |
cb2f5b52b3 | ||
![]() |
ba40354a5d | ||
![]() |
0c151b058e | ||
![]() |
fc7f47d273 | ||
![]() |
4f4ff35ee5 | ||
![]() |
8b9827b422 | ||
![]() |
892cbc4d07 | ||
![]() |
f61f15fe7e | ||
![]() |
d077a922c0 | ||
![]() |
9b70503d26 | ||
![]() |
ae9f25a7a8 | ||
![]() |
00ed5f97b9 | ||
![]() |
5dbbfd5d87 | ||
![]() |
37a1c5b0df | ||
![]() |
f2a1989993 | ||
![]() |
3d40ce7df3 | ||
![]() |
36ff8896ef | ||
![]() |
5f59acf0b4 | ||
![]() |
c48a193d9a | ||
![]() |
47a843c669 | ||
![]() |
52be28bb6c | ||
![]() |
6522b70066 | ||
![]() |
8d486a9af2 | ||
![]() |
97393e4ae7 | ||
![]() |
e35120bb36 | ||
![]() |
f2cfb3ceb3 | ||
![]() |
15246cd535 | ||
![]() |
3edcd10c12 | ||
![]() |
874e513b47 | ||
![]() |
a10318e00d | ||
![]() |
7608b647fe | ||
![]() |
60d16326b0 | ||
![]() |
0d300f0435 | ||
![]() |
3123ed30cf | ||
![]() |
db41858aae | ||
![]() |
8c0a794202 | ||
![]() |
514f6427ab | ||
![]() |
9920636b9c | ||
![]() |
5182e42f81 | ||
![]() |
a19b8a1545 |
36
.github/workflows/Bleeding-Job.yaml
vendored
Normal file
36
.github/workflows/Bleeding-Job.yaml
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
name: Bleeding Update version
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update-version:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Check out the repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
|
||||||
|
- name: Update version.json
|
||||||
|
run: |
|
||||||
|
git fetch --prune --unshallow
|
||||||
|
commit_count=$(git rev-list --count HEAD)
|
||||||
|
version=$(jq -r '.version' version.json)
|
||||||
|
jq --arg versionBleeding "$version-$commit_count" '. + {versionBleeding: $versionBleeding}' version.json > version.tmp && mv version.tmp version.json
|
||||||
|
|
||||||
|
- name: Commit and push changes
|
||||||
|
run: |
|
||||||
|
git config --global user.name 'github-actions[bot]'
|
||||||
|
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
|
||||||
|
git add version.json
|
||||||
|
git commit -m "Update version.json with commit count"
|
||||||
|
git push
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
4
.github/workflows/Build.yml
vendored
4
.github/workflows/Build.yml
vendored
@ -9,6 +9,8 @@ jobs:
|
|||||||
version-release:
|
version-release:
|
||||||
runs-on: windows-latest # Use Windows 10 runner
|
runs-on: windows-latest # Use Windows 10 runner
|
||||||
|
|
||||||
|
if: github.actor != 'github-actions[bot]' # Only run if the actor is not the GitHub Actions bot
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
@ -36,7 +38,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
dir actions-temp
|
dir actions-temp
|
||||||
dir
|
dir
|
||||||
|
|
||||||
- name: Get version and name from version.json
|
- name: Get version and name from version.json
|
||||||
id: version_info
|
id: version_info
|
||||||
run: |
|
run: |
|
||||||
|
76
PKGBUILD
Normal file
76
PKGBUILD
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
pkgname=picodulce
|
||||||
|
pkgver=0.11.7
|
||||||
|
pkgrel=1
|
||||||
|
pkgdesc="Launcher for Minecraft based on the picomc library"
|
||||||
|
arch=('x86_64')
|
||||||
|
OPTIONS=(!strip !docs libtool emptydirs)
|
||||||
|
url="https://github.com/nixietab/picodulce"
|
||||||
|
license=('MIT') # Replace with your project's license
|
||||||
|
depends=('python' 'python-virtualenv' 'xdg-utils')
|
||||||
|
makedepends=('git')
|
||||||
|
source=("git+https://github.com/nixietab/picodulce.git")
|
||||||
|
sha256sums=('SKIP')
|
||||||
|
|
||||||
|
package() {
|
||||||
|
cd "$srcdir/$pkgname"
|
||||||
|
|
||||||
|
# Create a directory for the application in the user's home directory
|
||||||
|
install -dm755 "$pkgdir/usr/share/$pkgname"
|
||||||
|
|
||||||
|
# Copy all project files to the created directory
|
||||||
|
cp -r . "$pkgdir/usr/share/$pkgname"
|
||||||
|
|
||||||
|
# Create a virtual environment
|
||||||
|
python -m venv "$pkgdir/usr/share/$pkgname/venv"
|
||||||
|
|
||||||
|
# Activate the virtual environment and install dependencies
|
||||||
|
source "$pkgdir/usr/share/$pkgname/venv/bin/activate"
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
# Create a run.sh script
|
||||||
|
install -Dm755 /dev/stdin "$pkgdir/usr/share/$pkgname/run.sh" <<EOF
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [ ! -d "venv" ]; then
|
||||||
|
echo "venv folder does not exist. Creating virtual environment..."
|
||||||
|
python3 -m venv venv
|
||||||
|
|
||||||
|
source venv/bin/activate
|
||||||
|
|
||||||
|
echo "Installing required packages..."
|
||||||
|
pip install -r requirements.txt
|
||||||
|
else
|
||||||
|
source venv/bin/activate
|
||||||
|
fi
|
||||||
|
|
||||||
|
python picodulce.py
|
||||||
|
EOF
|
||||||
|
|
||||||
|
|
||||||
|
# Make the run.sh script executable
|
||||||
|
chmod +x "$pkgdir/usr/share/$pkgname/run.sh"
|
||||||
|
|
||||||
|
# Create a desktop entry for the application
|
||||||
|
install -Dm644 /dev/stdin "$pkgdir/usr/share/applications/$pkgname.desktop" <<EOF
|
||||||
|
[Desktop Entry]
|
||||||
|
Name=Picodulce
|
||||||
|
Exec=/usr/share/picodulce/run.sh
|
||||||
|
Icon=/usr/share/picodulce/launcher_icon.ico
|
||||||
|
Terminal=true
|
||||||
|
Type=Application
|
||||||
|
Comment=Picodulce Launcher
|
||||||
|
Categories=Game;
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Ensure the normal user has permission to write to the picodulce folder
|
||||||
|
chown -R "$USER:$USER" "$pkgdir/usr/share/$pkgname"
|
||||||
|
chmod -R u+w "$pkgdir/usr/share/$pkgname"
|
||||||
|
|
||||||
|
#Install into bin
|
||||||
|
install -Dm755 /dev/stdin "$pkgdir/usr/bin/picodulce" <<EOF
|
||||||
|
#!/bin/bash
|
||||||
|
cd /usr/share/picodulce/
|
||||||
|
exec ./run.sh
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
# vim:set ts=2 sw=2 et:
|
16
README.md
16
README.md
@ -40,7 +40,21 @@
|
|||||||
- **Custom Theme Support**: Create and apply personalized themes with ease. A dedicated repository and guide are [available to help you get started.](https://github.com/nixietab/picodulce-themes)
|
- **Custom Theme Support**: Create and apply personalized themes with ease. A dedicated repository and guide are [available to help you get started.](https://github.com/nixietab/picodulce-themes)
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
If you are on windows you may be more interested in a [installer](https://github.com/nixietab/picodulce/releases/latest)
|
|
||||||
|
## Windows
|
||||||
|
For Windows systems using the [installer](https://github.com/nixietab/picodulce/releases/latest) is recommended
|
||||||
|
|
||||||
|
## Arch Linux
|
||||||
|
The package is available in the [AUR](https://aur.archlinux.org/packages/picodulce) as ```picodulce```
|
||||||
|
|
||||||
|
For installing on Arch without using an AUR helper a PKGBUILD is provided
|
||||||
|
```
|
||||||
|
git clone https://aur.archlinux.org/picodulce.git
|
||||||
|
cd picodulce
|
||||||
|
makepkg -si
|
||||||
|
```
|
||||||
|
|
||||||
|
## Other OS
|
||||||
|
|
||||||
### 1. Clone the repository
|
### 1. Clone the repository
|
||||||
|
|
||||||
|
244
authser.py
Normal file
244
authser.py
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import re
|
||||||
|
from PyQt5.QtWidgets import (QApplication, QDialog, QLabel, QVBoxLayout,
|
||||||
|
QPushButton, QLineEdit, QMessageBox)
|
||||||
|
from PyQt5.QtCore import QThread, pyqtSignal, Qt, QUrl, QObject
|
||||||
|
from PyQt5.QtGui import QDesktopServices
|
||||||
|
|
||||||
|
class AuthenticationParser:
|
||||||
|
@staticmethod
|
||||||
|
def clean_ansi(text):
|
||||||
|
ansi_clean = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
|
||||||
|
printable_clean = re.compile(r'[^\x20-\x7E\n]')
|
||||||
|
text = ansi_clean.sub('', text)
|
||||||
|
text = printable_clean.sub('', text)
|
||||||
|
return text.strip()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_auth_error(output):
|
||||||
|
cleaned_output = AuthenticationParser.clean_ansi(output)
|
||||||
|
return "AADSTS70016" in cleaned_output and "not yet been authorized" in cleaned_output
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_auth_output(output):
|
||||||
|
cleaned_output = AuthenticationParser.clean_ansi(output)
|
||||||
|
if AuthenticationParser.is_auth_error(cleaned_output):
|
||||||
|
return None
|
||||||
|
|
||||||
|
pattern = r"https://[^\s]+"
|
||||||
|
code_pattern = r"code\s+([A-Z0-9]+)"
|
||||||
|
|
||||||
|
url_match = re.search(pattern, cleaned_output)
|
||||||
|
code_match = re.search(code_pattern, cleaned_output, re.IGNORECASE)
|
||||||
|
|
||||||
|
if url_match and code_match:
|
||||||
|
return {
|
||||||
|
'url': url_match.group(0),
|
||||||
|
'code': code_match.group(1)
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
def __init__(self, account):
|
||||||
|
super().__init__()
|
||||||
|
self.account = account
|
||||||
|
self.process = None
|
||||||
|
self.is_running = True
|
||||||
|
self.current_output = ""
|
||||||
|
self.waiting_for_auth = False
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
command = f'picomc account authenticate {self.account}'
|
||||||
|
|
||||||
|
self.process = subprocess.Popen(
|
||||||
|
command,
|
||||||
|
shell=True,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
text=True,
|
||||||
|
bufsize=1,
|
||||||
|
universal_newlines=True
|
||||||
|
)
|
||||||
|
|
||||||
|
self.current_output = ""
|
||||||
|
while self.is_running and self.process.poll() is None:
|
||||||
|
line = self.process.stdout.readline()
|
||||||
|
if line:
|
||||||
|
self.current_output += line
|
||||||
|
|
||||||
|
if not self.waiting_for_auth:
|
||||||
|
parsed_data = AuthenticationParser.parse_auth_output(self.current_output)
|
||||||
|
if parsed_data:
|
||||||
|
self.auth_data_received.emit(parsed_data)
|
||||||
|
self.waiting_for_auth = True
|
||||||
|
self.current_output = ""
|
||||||
|
elif AuthenticationParser.is_auth_error(self.current_output):
|
||||||
|
self.auth_error_detected.emit(self.current_output)
|
||||||
|
self.waiting_for_auth = False
|
||||||
|
self.current_output = ""
|
||||||
|
|
||||||
|
self.process.wait()
|
||||||
|
self.finished.emit()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.error_occurred.emit(str(e))
|
||||||
|
self.finished.emit()
|
||||||
|
|
||||||
|
def send_enter(self):
|
||||||
|
if self.process and self.process.poll() is None:
|
||||||
|
self.process.stdin.write("\n")
|
||||||
|
self.process.stdin.flush()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.is_running = False
|
||||||
|
if self.process:
|
||||||
|
self.process.terminate()
|
||||||
|
|
||||||
|
class MinecraftAuthenticator(QObject): # Changed to inherit from QObject
|
||||||
|
auth_finished = pyqtSignal(bool) # Add signal for completion
|
||||||
|
|
||||||
|
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):
|
||||||
|
"""
|
||||||
|
Start the authentication process for the given username
|
||||||
|
Returns immediately, authentication result will be emitted via auth_finished signal
|
||||||
|
"""
|
||||||
|
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.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_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
|
||||||
|
|
||||||
|
self.success = True
|
||||||
|
self.auth_finished.emit(True)
|
||||||
|
|
||||||
|
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__':
|
||||||
|
authenticator = MinecraftAuthenticator()
|
||||||
|
authenticator.authenticate("TestUser")
|
119
healthcheck.py
Normal file
119
healthcheck.py
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
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")
|
399
picodulce.py
399
picodulce.py
@ -10,6 +10,10 @@ import requests
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from authser import MinecraftAuthenticator
|
||||||
|
from healthcheck import HealthCheck
|
||||||
|
|
||||||
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.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.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
|
||||||
@ -21,10 +25,14 @@ class PicomcVersionSelector(QWidget):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.current_state = "menu"
|
self.current_state = "menu"
|
||||||
self.open_dialogs = []
|
self.open_dialogs = []
|
||||||
self.check_config_file()
|
|
||||||
self.themes_integrity()
|
# 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
|
||||||
|
|
||||||
themes_folder = "themes"
|
themes_folder = "themes"
|
||||||
|
|
||||||
theme_file = self.config.get("Theme", "Dark.json")
|
theme_file = self.config.get("Theme", "Dark.json")
|
||||||
|
|
||||||
# Ensure the theme file exists in the themes directory
|
# Ensure the theme file exists in the themes directory
|
||||||
@ -48,6 +56,13 @@ class PicomcVersionSelector(QWidget):
|
|||||||
discord_rcp_thread.daemon = True # Make the thread a daemon so it terminates when the main program exits
|
discord_rcp_thread.daemon = True # Make the thread a daemon so it terminates when the main program exits
|
||||||
discord_rcp_thread.start()
|
discord_rcp_thread.start()
|
||||||
|
|
||||||
|
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):
|
def load_theme_from_file(self, file_path, app):
|
||||||
self.theme = {}
|
self.theme = {}
|
||||||
# Check if the file exists, else load 'Dark.json'
|
# Check if the file exists, else load 'Dark.json'
|
||||||
@ -107,78 +122,77 @@ class PicomcVersionSelector(QWidget):
|
|||||||
else:
|
else:
|
||||||
print("Theme dosn't seem to have a stylesheet")
|
print("Theme dosn't seem to have a stylesheet")
|
||||||
|
|
||||||
def themes_integrity(self):
|
def FirstLaunch(self):
|
||||||
# Define folder and file paths
|
try:
|
||||||
themes_folder = "themes"
|
self.config_path = "config.json"
|
||||||
dark_theme_file = os.path.join(themes_folder, "Dark.json")
|
print("Running picomc instance create default command...")
|
||||||
native_theme_file = os.path.join(themes_folder, "Native.json")
|
# 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.stdout)
|
||||||
|
|
||||||
|
# Change the value of IsFirstLaunch to False
|
||||||
|
self.config["IsFirstLaunch"] = False
|
||||||
|
print("IsFirstLaunch set to False")
|
||||||
|
|
||||||
# Define the default content for Dark.json
|
# Save the updated config to the config.json file
|
||||||
dark_theme_content = {
|
with open(self.config_path, 'w') as f:
|
||||||
"manifest": {
|
json.dump(self.config, f, indent=4)
|
||||||
"name": "Dark",
|
print("Configuration saved to", self.config_path)
|
||||||
"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")
|
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print("An error occurred while creating the instance.")
|
||||||
|
print("Error output:", e.stderr)
|
||||||
|
|
||||||
def resize_event(self, event):
|
def resize_event(self, event):
|
||||||
if hasattr(self, 'movie_label'):
|
if hasattr(self, 'movie_label'):
|
||||||
self.movie_label.setGeometry(0, 0, self.width(), self.height())
|
self.movie_label.setGeometry(0, 0, 400, 320)
|
||||||
event.accept() # Accept the resize event
|
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):
|
def init_ui(self):
|
||||||
self.setWindowTitle('PicoDulce Launcher') # Change window title
|
self.setWindowTitle('PicoDulce Launcher') # Change window title
|
||||||
@ -195,44 +209,8 @@ class PicomcVersionSelector(QWidget):
|
|||||||
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)
|
||||||
|
|
||||||
if self.config.get("ThemeBackground", False): # Default to False if ThemeBackground is missing
|
# Load theme background
|
||||||
# Get the base64 string for the background image from the theme file
|
self.load_theme_background()
|
||||||
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
|
# Create title label
|
||||||
title_label = QLabel('PicoDulce Launcher') # Change label text
|
title_label = QLabel('PicoDulce Launcher') # Change label text
|
||||||
@ -297,7 +275,7 @@ class PicomcVersionSelector(QWidget):
|
|||||||
main_layout.setSpacing(20)
|
main_layout.setSpacing(20)
|
||||||
|
|
||||||
self.setLayout(main_layout)
|
self.setLayout(main_layout)
|
||||||
|
|
||||||
def keyPressEvent(self, event):
|
def keyPressEvent(self, event):
|
||||||
focus_widget = self.focusWidget()
|
focus_widget = self.focusWidget()
|
||||||
if event.key() == Qt.Key_Down:
|
if event.key() == Qt.Key_Down:
|
||||||
@ -312,48 +290,6 @@ class PicomcVersionSelector(QWidget):
|
|||||||
else:
|
else:
|
||||||
super().keyPressEvent(event)
|
super().keyPressEvent(event)
|
||||||
|
|
||||||
def check_config_file(self):
|
|
||||||
config_path = "config.json"
|
|
||||||
default_config = {
|
|
||||||
"IsRCPenabled": False,
|
|
||||||
"CheckUpdate": False,
|
|
||||||
"LastPlayed": "",
|
|
||||||
"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):
|
def open_settings_dialog(self):
|
||||||
dialog = QDialog(self)
|
dialog = QDialog(self)
|
||||||
dialog.setWindowTitle('Settings')
|
dialog.setWindowTitle('Settings')
|
||||||
@ -378,9 +314,14 @@ class PicomcVersionSelector(QWidget):
|
|||||||
check_updates_checkbox = QCheckBox('Check Updates on Start')
|
check_updates_checkbox = QCheckBox('Check Updates on Start')
|
||||||
check_updates_checkbox.setChecked(self.config.get("CheckUpdate", False))
|
check_updates_checkbox.setChecked(self.config.get("CheckUpdate", False))
|
||||||
|
|
||||||
|
bleeding_edge_checkbox = QCheckBox('Bleeding Edge')
|
||||||
|
bleeding_edge_checkbox.setChecked(self.config.get("IsBleeding", False))
|
||||||
|
bleeding_edge_checkbox.stateChanged.connect(lambda: self.show_bleeding_edge_popup(bleeding_edge_checkbox))
|
||||||
|
|
||||||
settings_layout.addWidget(title_label)
|
settings_layout.addWidget(title_label)
|
||||||
settings_layout.addWidget(discord_rcp_checkbox)
|
settings_layout.addWidget(discord_rcp_checkbox)
|
||||||
settings_layout.addWidget(check_updates_checkbox)
|
settings_layout.addWidget(check_updates_checkbox)
|
||||||
|
settings_layout.addWidget(bleeding_edge_checkbox)
|
||||||
|
|
||||||
# Add buttons in the settings tab
|
# Add buttons in the settings tab
|
||||||
update_button = QPushButton('Check for updates')
|
update_button = QPushButton('Check for updates')
|
||||||
@ -412,24 +353,27 @@ class PicomcVersionSelector(QWidget):
|
|||||||
|
|
||||||
# QListWidget to display available themes
|
# QListWidget to display available themes
|
||||||
json_files_label = QLabel('Installed Themes:')
|
json_files_label = QLabel('Installed Themes:')
|
||||||
json_files_list_widget = QListWidget()
|
self.json_files_list_widget = QListWidget()
|
||||||
|
|
||||||
# Track selected theme
|
# Track selected theme
|
||||||
self.selected_theme = theme_filename # Default to current theme
|
self.selected_theme = theme_filename # Default to current theme
|
||||||
|
|
||||||
|
# Build the list of themes
|
||||||
|
themes_list = self.build_themes_list()
|
||||||
|
|
||||||
# Populate themes initially
|
# Populate themes initially
|
||||||
self.populate_themes(json_files_list_widget)
|
self.populate_themes(self.json_files_list_widget, themes_list)
|
||||||
|
|
||||||
# Update current theme label when a theme is selected
|
# Update current theme label when a theme is selected
|
||||||
json_files_list_widget.itemClicked.connect(
|
self.json_files_list_widget.itemClicked.connect(
|
||||||
lambda: self.on_theme_selected(json_files_list_widget, current_theme_label)
|
lambda: self.on_theme_selected(self.json_files_list_widget, current_theme_label)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add widgets to the layout
|
# Add widgets to the layout
|
||||||
customization_layout.addWidget(theme_background_checkbox)
|
customization_layout.addWidget(theme_background_checkbox)
|
||||||
customization_layout.addWidget(current_theme_label)
|
customization_layout.addWidget(current_theme_label)
|
||||||
customization_layout.addWidget(json_files_label)
|
customization_layout.addWidget(json_files_label)
|
||||||
customization_layout.addWidget(json_files_list_widget)
|
customization_layout.addWidget(self.json_files_list_widget)
|
||||||
|
|
||||||
# Button to download themes
|
# Button to download themes
|
||||||
download_themes_button = QPushButton("Download More Themes")
|
download_themes_button = QPushButton("Download More Themes")
|
||||||
@ -450,7 +394,8 @@ class PicomcVersionSelector(QWidget):
|
|||||||
discord_rcp_checkbox.isChecked(),
|
discord_rcp_checkbox.isChecked(),
|
||||||
check_updates_checkbox.isChecked(),
|
check_updates_checkbox.isChecked(),
|
||||||
theme_background_checkbox.isChecked(),
|
theme_background_checkbox.isChecked(),
|
||||||
self.selected_theme # Pass the selected theme here
|
self.selected_theme, # Pass the selected theme here
|
||||||
|
bleeding_edge_checkbox.isChecked() # Pass the bleeding edge setting here
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -462,9 +407,20 @@ class PicomcVersionSelector(QWidget):
|
|||||||
dialog.setLayout(main_layout)
|
dialog.setLayout(main_layout)
|
||||||
dialog.exec_()
|
dialog.exec_()
|
||||||
|
|
||||||
def populate_themes(self, json_files_list_widget):
|
def show_bleeding_edge_popup(self, checkbox):
|
||||||
|
if checkbox.isChecked():
|
||||||
|
response = QMessageBox.question(
|
||||||
|
self,
|
||||||
|
"Bleeding Edge Feature",
|
||||||
|
"Enabling 'Bleeding Edge' mode may expose you to unstable and experimental features. Do you want to enable it anyway? In normal mode, updates are only downloaded when a stable release is made.",
|
||||||
|
QMessageBox.Yes | QMessageBox.No
|
||||||
|
)
|
||||||
|
if response == QMessageBox.No:
|
||||||
|
checkbox.setChecked(False)
|
||||||
|
|
||||||
|
def build_themes_list(self):
|
||||||
themes_folder = os.path.join(os.getcwd(), "themes")
|
themes_folder = os.path.join(os.getcwd(), "themes")
|
||||||
json_files_list_widget.clear()
|
themes_list = []
|
||||||
if os.path.exists(themes_folder):
|
if os.path.exists(themes_folder):
|
||||||
json_files = [f for f in os.listdir(themes_folder) if f.endswith('.json')]
|
json_files = [f for f in os.listdir(themes_folder) if f.endswith('.json')]
|
||||||
for json_file in json_files:
|
for json_file in json_files:
|
||||||
@ -480,15 +436,21 @@ class PicomcVersionSelector(QWidget):
|
|||||||
|
|
||||||
# Create display text and list item
|
# Create display text and list item
|
||||||
display_text = f"{name}\n{description}\nBy: {author}"
|
display_text = f"{name}\n{description}\nBy: {author}"
|
||||||
list_item = QListWidgetItem(display_text)
|
themes_list.append((display_text, json_file))
|
||||||
list_item.setData(Qt.UserRole, json_file) # Store the JSON filename as metadata
|
return themes_list
|
||||||
|
|
||||||
# Style the name in bold
|
def populate_themes(self, json_files_list_widget, themes_list):
|
||||||
font = QFont()
|
json_files_list_widget.clear()
|
||||||
font.setBold(False)
|
for display_text, json_file in themes_list:
|
||||||
list_item.setFont(font)
|
list_item = QListWidgetItem(display_text)
|
||||||
|
list_item.setData(Qt.UserRole, json_file) # Store the JSON filename as metadata
|
||||||
|
|
||||||
json_files_list_widget.addItem(list_item)
|
# Style the name in bold
|
||||||
|
font = QFont()
|
||||||
|
font.setBold(False)
|
||||||
|
list_item.setFont(font)
|
||||||
|
|
||||||
|
json_files_list_widget.addItem(list_item)
|
||||||
|
|
||||||
# Apply spacing and styling to the list
|
# Apply spacing and styling to the list
|
||||||
json_files_list_widget.setStyleSheet("""
|
json_files_list_widget.setStyleSheet("""
|
||||||
@ -506,8 +468,8 @@ class PicomcVersionSelector(QWidget):
|
|||||||
if selected_item:
|
if selected_item:
|
||||||
self.selected_theme = selected_item.data(Qt.UserRole)
|
self.selected_theme = selected_item.data(Qt.UserRole)
|
||||||
current_theme_label.setText(f"Current Theme: {self.selected_theme}")
|
current_theme_label.setText(f"Current Theme: {self.selected_theme}")
|
||||||
|
|
||||||
## REPOSITORY BLOCK BEGGINS
|
## REPOSITORY BLOCK BEGGINS
|
||||||
|
|
||||||
def download_themes_window(self):
|
def download_themes_window(self):
|
||||||
dialog = QDialog(self)
|
dialog = QDialog(self)
|
||||||
@ -544,9 +506,16 @@ class PicomcVersionSelector(QWidget):
|
|||||||
main_layout.addLayout(right_layout)
|
main_layout.addLayout(right_layout)
|
||||||
dialog.setLayout(main_layout)
|
dialog.setLayout(main_layout)
|
||||||
|
|
||||||
|
dialog.finished.connect(lambda: self.update_themes_list())
|
||||||
|
|
||||||
|
|
||||||
self.load_themes()
|
self.load_themes()
|
||||||
dialog.exec_()
|
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):
|
def fetch_themes(self):
|
||||||
try:
|
try:
|
||||||
with open("config.json", "r") as config_file:
|
with open("config.json", "r") as config_file:
|
||||||
@ -666,13 +635,14 @@ class PicomcVersionSelector(QWidget):
|
|||||||
## REPOSITORY BLOCK ENDS
|
## REPOSITORY BLOCK ENDS
|
||||||
|
|
||||||
|
|
||||||
def save_settings(self, is_rcp_enabled, check_updates_on_start, theme_background, selected_theme):
|
def save_settings(self, is_rcp_enabled, check_updates_on_start, theme_background, selected_theme, is_bleeding):
|
||||||
config_path = "config.json"
|
config_path = "config.json"
|
||||||
updated_config = {
|
updated_config = {
|
||||||
"IsRCPenabled": is_rcp_enabled,
|
"IsRCPenabled": is_rcp_enabled,
|
||||||
"CheckUpdate": check_updates_on_start,
|
"CheckUpdate": check_updates_on_start,
|
||||||
"ThemeBackground": theme_background,
|
"ThemeBackground": theme_background,
|
||||||
"Theme": selected_theme
|
"Theme": selected_theme,
|
||||||
|
"IsBleeding": is_bleeding
|
||||||
}
|
}
|
||||||
|
|
||||||
# Update config values
|
# Update config values
|
||||||
@ -1017,19 +987,36 @@ class PicomcVersionSelector(QWidget):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def authenticate_account(self, dialog, account_name):
|
def authenticate_account(self, dialog, account_name):
|
||||||
# Authenticate a selected account
|
# Clean up the account name
|
||||||
account_name = account_name.strip().lstrip(" * ")
|
account_name = account_name.strip().lstrip(" * ")
|
||||||
if not account_name:
|
if not account_name:
|
||||||
QMessageBox.warning(dialog, "Warning", "Please select an account to authenticate.")
|
QMessageBox.warning(dialog, "Warning", "Please select an account to authenticate.")
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
subprocess.run(['picomc', 'account', 'authenticate', account_name], check=True)
|
# Create authenticator instance if it doesn't exist
|
||||||
QMessageBox.information(self, "Success", f"Account '{account_name}' authenticated successfully!")
|
if self.authenticator is None:
|
||||||
except subprocess.CalledProcessError as e:
|
self.authenticator = MinecraftAuthenticator(self)
|
||||||
error_message = f"Error authenticating account '{account_name}': {e.stderr.decode()}"
|
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)}"
|
||||||
logging.error(error_message)
|
logging.error(error_message)
|
||||||
QMessageBox.critical(self, "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
|
||||||
|
|
||||||
def remove_account(self, dialog, username):
|
def remove_account(self, dialog, username):
|
||||||
# Remove a selected account
|
# Remove a selected account
|
||||||
@ -1116,8 +1103,22 @@ class PicomcVersionSelector(QWidget):
|
|||||||
with open('version.json', 'r') as version_file:
|
with open('version.json', 'r') as version_file:
|
||||||
version_data = json.load(version_file)
|
version_data = json.load(version_file)
|
||||||
version_number = version_data.get('version', 'unknown version')
|
version_number = version_data.get('version', 'unknown version')
|
||||||
|
version_bleeding = version_data.get('versionBleeding', None)
|
||||||
except (FileNotFoundError, json.JSONDecodeError):
|
except (FileNotFoundError, json.JSONDecodeError):
|
||||||
version_number = 'unknown version'
|
version_number = 'unknown version'
|
||||||
|
version_bleeding = None
|
||||||
|
|
||||||
|
# Check the configuration for IsBleeding
|
||||||
|
try:
|
||||||
|
with open('config.json', 'r') as config_file:
|
||||||
|
config_data = json.load(config_file)
|
||||||
|
is_bleeding = config_data.get('IsBleeding', False)
|
||||||
|
except (FileNotFoundError, json.JSONDecodeError):
|
||||||
|
is_bleeding = False
|
||||||
|
|
||||||
|
# Use versionBleeding if IsBleeding is true
|
||||||
|
if is_bleeding and version_bleeding:
|
||||||
|
version_number = version_bleeding
|
||||||
|
|
||||||
about_message = (
|
about_message = (
|
||||||
f"PicoDulce Launcher (v{version_number})\n\n"
|
f"PicoDulce Launcher (v{version_number})\n\n"
|
||||||
@ -1134,19 +1135,39 @@ class PicomcVersionSelector(QWidget):
|
|||||||
with open("version.json") as f:
|
with open("version.json") as f:
|
||||||
local_version_info = json.load(f)
|
local_version_info = json.load(f)
|
||||||
local_version = local_version_info.get("version")
|
local_version = local_version_info.get("version")
|
||||||
|
local_version_bleeding = local_version_info.get("versionBleeding")
|
||||||
logging.info(f"Local version: {local_version}")
|
logging.info(f"Local version: {local_version}")
|
||||||
|
logging.info(f"Local bleeding version: {local_version_bleeding}")
|
||||||
|
|
||||||
|
with open("config.json") as config_file:
|
||||||
|
config = json.load(config_file)
|
||||||
|
is_bleeding = config.get("IsBleeding", False)
|
||||||
|
|
||||||
if local_version:
|
if local_version:
|
||||||
remote_version_info = self.fetch_remote_version()
|
remote_version_info = self.fetch_remote_version()
|
||||||
remote_version = remote_version_info.get("version")
|
remote_version = remote_version_info.get("version")
|
||||||
|
remote_version_bleeding = remote_version_info.get("versionBleeding")
|
||||||
logging.info(f"Remote version: {remote_version}")
|
logging.info(f"Remote version: {remote_version}")
|
||||||
if remote_version and remote_version != local_version:
|
logging.info(f"Remote bleeding version: {remote_version_bleeding}")
|
||||||
update_message = f"A new version ({remote_version}) is available!\nDo you want to download it now?"
|
|
||||||
|
if is_bleeding:
|
||||||
|
remote_version_to_check = remote_version_bleeding
|
||||||
|
local_version_to_check = local_version_bleeding
|
||||||
|
else:
|
||||||
|
remote_version_to_check = remote_version
|
||||||
|
local_version_to_check = local_version
|
||||||
|
|
||||||
|
if remote_version_to_check and (remote_version_to_check != local_version_to_check):
|
||||||
|
if is_bleeding:
|
||||||
|
update_message = f"Do you want to update to the bleeding edge version ({remote_version_bleeding})?"
|
||||||
|
else:
|
||||||
|
update_message = f"A new version ({remote_version}) is available!\nDo you want to download it now?"
|
||||||
update_dialog = QMessageBox.question(self, "Update Available", update_message, QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
|
update_dialog = QMessageBox.question(self, "Update Available", update_message, QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
|
||||||
if update_dialog == QMessageBox.Yes:
|
if update_dialog == QMessageBox.Yes:
|
||||||
# Download and apply the update
|
# Download and apply the update
|
||||||
self.download_update(remote_version_info)
|
self.download_update(remote_version_info)
|
||||||
else:
|
else:
|
||||||
print("Up to Date", "You already have the latest version!")
|
print(f"You already have the latest version!")
|
||||||
else:
|
else:
|
||||||
logging.error("Failed to read local version information.")
|
logging.error("Failed to read local version information.")
|
||||||
QMessageBox.critical(self, "Error", "Failed to check for updates.")
|
QMessageBox.critical(self, "Error", "Failed to check for updates.")
|
||||||
@ -1159,13 +1180,33 @@ class PicomcVersionSelector(QWidget):
|
|||||||
with open("version.json") as f:
|
with open("version.json") as f:
|
||||||
local_version_info = json.load(f)
|
local_version_info = json.load(f)
|
||||||
local_version = local_version_info.get("version")
|
local_version = local_version_info.get("version")
|
||||||
|
local_version_bleeding = local_version_info.get("versionBleeding")
|
||||||
logging.info(f"Local version: {local_version}")
|
logging.info(f"Local version: {local_version}")
|
||||||
|
logging.info(f"Local bleeding version: {local_version_bleeding}")
|
||||||
|
|
||||||
|
with open("config.json") as config_file:
|
||||||
|
config = json.load(config_file)
|
||||||
|
is_bleeding = config.get("IsBleeding", False)
|
||||||
|
|
||||||
if local_version:
|
if local_version:
|
||||||
remote_version_info = self.fetch_remote_version()
|
remote_version_info = self.fetch_remote_version()
|
||||||
remote_version = remote_version_info.get("version")
|
remote_version = remote_version_info.get("version")
|
||||||
|
remote_version_bleeding = remote_version_info.get("versionBleeding")
|
||||||
logging.info(f"Remote version: {remote_version}")
|
logging.info(f"Remote version: {remote_version}")
|
||||||
if remote_version and remote_version != local_version:
|
logging.info(f"Remote bleeding version: {remote_version_bleeding}")
|
||||||
update_message = f"A new version ({remote_version}) is available!\nDo you want to download it now?"
|
|
||||||
|
if is_bleeding:
|
||||||
|
remote_version_to_check = remote_version_bleeding
|
||||||
|
local_version_to_check = local_version_bleeding
|
||||||
|
else:
|
||||||
|
remote_version_to_check = remote_version
|
||||||
|
local_version_to_check = local_version
|
||||||
|
|
||||||
|
if remote_version_to_check and (remote_version_to_check != local_version_to_check):
|
||||||
|
if is_bleeding:
|
||||||
|
update_message = f"Do you want to update to the bleeding edge version ({remote_version_bleeding})?"
|
||||||
|
else:
|
||||||
|
update_message = f"A new version ({remote_version}) is available!\nDo you want to download it now?"
|
||||||
update_dialog = QMessageBox.question(self, "Update Available", update_message, QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
|
update_dialog = QMessageBox.question(self, "Update Available", update_message, QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
|
||||||
if update_dialog == QMessageBox.Yes:
|
if update_dialog == QMessageBox.Yes:
|
||||||
# Download and apply the update
|
# Download and apply the update
|
||||||
|
@ -2,3 +2,4 @@ picomc
|
|||||||
PyQt5
|
PyQt5
|
||||||
requests
|
requests
|
||||||
pypresence
|
pypresence
|
||||||
|
tqdm
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
{
|
{
|
||||||
"version": "0.11.7",
|
"version": "0.12.1",
|
||||||
"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/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/healthcheck.py"
|
||||||
|
],
|
||||||
|
"versionBleeding": "0.12.1-188"
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user