Compare commits

..

59 Commits

Author SHA1 Message Date
Nix
07571aaeb0
Update version.json
Some checks failed
Version Change Action / version-release (push) Has been cancelled
2024-12-31 15:08:07 -03:00
Nix
63dc90b239
Update Build.yml 2024-12-31 15:07:46 -03:00
Nix
fc4ac0ba56
Update version.json 2024-12-31 14:56:12 -03:00
Nix
15c161faff
Update Build.yml 2024-12-31 14:56:01 -03:00
Nix
2db2f1b18b
Update version.json 2024-12-31 14:53:18 -03:00
Nix
a582b497f8
Update Build.yml 2024-12-31 14:53:07 -03:00
Nix
93d607229e
Update version.json 2024-12-31 14:51:49 -03:00
Nix
9634a378ab
Update Build.yml 2024-12-31 14:51:31 -03:00
Nix
247f801d35
Update version.json 2024-12-31 14:48:25 -03:00
Nix
645cfeb52b
Update Build.yml 2024-12-31 14:48:10 -03:00
Nix
6596ad44f8
Update version.json 2024-12-31 14:45:08 -03:00
Nix
228565daf3
Update Build.yml 2024-12-31 14:44:44 -03:00
Nix
b45951cab6
Update version.json 2024-12-31 14:41:42 -03:00
Nix
76d88787ac
Update Build.yml 2024-12-31 14:41:10 -03:00
Nix
19332fd936
Update version.json 2024-12-31 14:40:17 -03:00
Nix
cef44074c5
Update Build.yml 2024-12-31 14:39:42 -03:00
Nix
2a4ba7cfcf
Update version.json 2024-12-31 14:37:58 -03:00
Nix
e6b4a7acf3
Update Build.yml 2024-12-31 14:37:43 -03:00
Nix
97215e4c75
Update version.json 2024-12-31 14:33:00 -03:00
Nix
6c93ab07c5
Update Build.yml 2024-12-31 14:32:09 -03:00
Nix
14855f2f00
Update version.json 2024-12-31 14:28:20 -03:00
Nix
897fa4c8b7
Update Build.yml 2024-12-31 14:27:53 -03:00
Nix
7ca5855198
Update version.json 2024-12-31 14:23:04 -03:00
Nix
5a6bdd3aad
Update Build.yml 2024-12-31 14:22:50 -03:00
Nix
b35063d0b2
Update version.json 2024-12-31 11:45:30 -03:00
Nix
fe6d409fb5
Update Build.yml 2024-12-31 11:45:14 -03:00
Nix
bce36c3728
Update version.json 2024-12-31 11:40:24 -03:00
Nix
aa8a72c8cb
Update Build.yml 2024-12-31 11:40:14 -03:00
Nix
b3155b4844
Update version.json 2024-12-31 11:37:40 -03:00
Nix
aaf141e338
Update Build.yml 2024-12-31 11:37:28 -03:00
Nix
c125f440f8
Update version.json 2024-12-31 11:33:59 -03:00
Nix
1543b36eb5
Update Build.yml 2024-12-31 11:33:40 -03:00
Nix
eb6b27ed23
Update version.json 2024-12-31 11:28:06 -03:00
Nix
0702f918dd
Update Build.yml 2024-12-31 11:27:09 -03:00
Nix
5792bcb6eb
Update version.json 2024-12-31 11:23:44 -03:00
Nix
b6241e7aad
Update Build.yml 2024-12-31 11:23:29 -03:00
Nix
0ac8a1bf51
Update version.json 2024-12-31 11:17:45 -03:00
Nix
60e532b604
Update Build.yml 2024-12-31 11:17:22 -03:00
Nix
52b07aab71
Update version.json 2024-12-31 11:13:57 -03:00
Nix
bb48b3e293
Update Build.yml 2024-12-31 11:13:19 -03:00
Nix
f86bb54abe
Update version.json 2024-12-31 11:05:01 -03:00
Nix
35e1cd7d17
Update Build.yml 2024-12-31 11:04:36 -03:00
Nix
c0ce394fda
Update version.json 2024-12-31 11:00:24 -03:00
Nix
1f739f25e8
Update Build.yml 2024-12-31 11:00:06 -03:00
Nix
2d362843c2
Update version.json 2024-12-31 10:58:39 -03:00
Nix
ae14820c4d
Update Build.yml 2024-12-31 10:58:21 -03:00
Nix
50b3f09ed1
Update version.json 2024-12-31 10:55:46 -03:00
Nix
b9bf71334b
Update Build.yml 2024-12-31 10:54:42 -03:00
Nix
40bf8ea23e
Update Build.yml 2024-12-31 10:49:57 -03:00
Nix
89cf8218f8
Update version.json 2024-12-31 10:45:32 -03:00
Nix
520a1b500f
Update Build.yml 2024-12-31 10:45:17 -03:00
Nix
b6a3218bf6
Update version.json 2024-12-31 10:40:53 -03:00
Nix
c1c98aafae
Create Build.yml 2024-12-31 10:40:30 -03:00
Nix
34c36c5b88
Update picodulce.py 2024-12-30 10:39:06 -03:00
Nix
fc058913cd
Update version.json 2024-12-29 14:07:42 -03:00
Nix
523431a557
Update README.md 2024-12-29 11:03:27 -03:00
Nix
72f7cbec9d
Update version.json 2024-12-29 11:01:06 -03:00
Nix
fbdba505c9
Added instance support 2024-12-29 11:00:48 -03:00
Nix
479bc4486a
Update version.json 2024-12-29 11:00:08 -03:00
9 changed files with 487 additions and 1393 deletions

View File

@ -9,8 +9,6 @@ jobs:
version-release:
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:
- name: Checkout repository
uses: actions/checkout@v3
@ -38,7 +36,7 @@ jobs:
run: |
dir actions-temp
dir
- name: Get version and name from version.json
id: version_info
run: |

View File

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

View File

@ -27,7 +27,7 @@
</p>
Picodulce is a feature-rich launcher for Minecraft, developed using Qt5. It serves as a graphical user interface (GUI) for the [zucaro backend](https://github.com/nixietab/zucaro), providing users with a seamless experience in managing and launching game versions.
Picodulce is a feature-rich launcher for Minecraft, developed using Qt5. It serves as a graphical user interface (GUI) for the picomc project, providing users with a seamless experience in managing and launching game versions.
![imagen](https://github.com/user-attachments/assets/115b39be-47d3-4ac7-893a-5849c1e4570c)
@ -38,30 +38,16 @@
- **Offline and Online Support**: Whether you're connected to Microsoft or not, Picodulce ensures you can still enjoy your game by supporting both offline and online modes.
- **Integrated Mod Manager**: Includes the [Marroc Mod Manager](https://github.com/nixietab/marroc), enabling users to effortlessly manage and customize their game with mods and texturepacks.
- **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)
-- **Instance Support**: Allows users to create and manage multiple game instances, each with its own configuration, mods, and settings
# Installation
## 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
If you are on windows you may be more interested in a [installer](https://github.com/nixietab/2hsu/releases/download/release/2hsu.exe)
### 1. Clone the repository
``` git clone https://github.com/nixietab/picodulce ```
### 2. (Optional) Set Up a Virtual Environment
Setting up a virtual environment is recommended to avoid dependency conflicts. Picodulce relies on the path of the `zucaro` project, and using a virtual environment helps prevent errors.
Setting up a virtual environment is recommended to avoid dependency conflicts. Picodulce relies on the path of the `picomc` project, and using a virtual environment helps prevent errors.
Create the virtual environment:
@ -86,7 +72,7 @@ On the venv run it as a normal python script
```python picodulce.py```
Just make sure you have Java installed for running the actual game, or check the "manage java" option inside the launcher settings
Just make sure you have Java installed for running the actual game
### About the name
The name "Picodulce" comes from a popular Argentinian candy. This reflects the enjoyable and user-friendly experience that the launcher aims to provide, making game management straightforward and pleasant.

View File

@ -1,366 +0,0 @@
import sys
import json
import os
import uuid
import asyncio
import aiohttp
from datetime import datetime, timezone
from pathlib import Path
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
from picomc.logging import logger
from picomc.launcher import get_default_root, Launcher
# Constants for Microsoft Authentication
URL_DEVICE_AUTH = "https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode"
URL_TOKEN = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token"
URL_XBL = "https://user.auth.xboxlive.com/user/authenticate"
URL_XSTS = "https://xsts.auth.xboxlive.com/xsts/authorize"
URL_MC = "https://api.minecraftservices.com/authentication/login_with_xbox"
URL_PROFILE = "https://api.minecraftservices.com/minecraft/profile"
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(dict)
def __init__(self, username):
super().__init__()
self.username = username
self.device_code = None
self.is_running = True
async def _ms_oauth(self):
data = {"client_id": CLIENT_ID, "scope": SCOPE}
async with aiohttp.ClientSession() as session:
async with session.post(URL_DEVICE_AUTH, data=data) as resp:
if resp.status != 200:
raise Exception(f"Failed to get device code: {await resp.text()}")
j = await resp.json()
self.device_code = j["device_code"]
self.auth_data_received.emit({
'url': j["verification_uri"],
'code': j["user_code"]
})
while self.is_running:
data = {
"grant_type": GRANT_TYPE,
"client_id": CLIENT_ID,
"device_code": self.device_code
}
async with session.post(URL_TOKEN, data=data) as resp:
j = await resp.json()
if resp.status == 400:
if j["error"] == "authorization_pending":
await asyncio.sleep(2)
continue
else:
raise Exception(j["error_description"])
elif resp.status != 200:
raise Exception(f"Token request failed: {j}")
return j["access_token"], j["refresh_token"]
async def _xbl_auth(self, access_token):
data = {
"Properties": {
"AuthMethod": "RPS",
"SiteName": "user.auth.xboxlive.com",
"RpsTicket": f"d={access_token}"
},
"RelyingParty": "http://auth.xboxlive.com",
"TokenType": "JWT"
}
async with aiohttp.ClientSession() as session:
async with session.post(URL_XBL, json=data) as resp:
if resp.status != 200:
raise Exception(f"XBL auth failed: {await resp.text()}")
j = await resp.json()
return j["Token"], j["DisplayClaims"]["xui"][0]["uhs"]
async def _xsts_auth(self, xbl_token):
data = {
"Properties": {
"SandboxId": "RETAIL",
"UserTokens": [xbl_token]
},
"RelyingParty": "rp://api.minecraftservices.com/",
"TokenType": "JWT"
}
async with aiohttp.ClientSession() as session:
async with session.post(URL_XSTS, json=data) as resp:
if resp.status != 200:
raise Exception(f"XSTS auth failed: {await resp.text()}")
j = await resp.json()
return j["Token"]
async def _mc_auth(self, uhs, xsts_token):
data = {
"identityToken": f"XBL3.0 x={uhs};{xsts_token}"
}
async with aiohttp.ClientSession() as session:
async with session.post(URL_MC, json=data) as resp:
if resp.status != 200:
raise Exception(f"MC auth failed: {await resp.text()}")
j = await resp.json()
return j["access_token"]
async def _get_profile(self, mc_token):
headers = {
"Authorization": f"Bearer {mc_token}"
}
async with aiohttp.ClientSession() as session:
async with session.get(URL_PROFILE, headers=headers) as resp:
if resp.status != 200:
raise Exception(f"Profile request failed: {await resp.text()}")
return await resp.json()
async def _auth_flow(self):
try:
ms_access_token, refresh_token = await self._ms_oauth()
xbl_token, uhs = await self._xbl_auth(ms_access_token)
xsts_token = await self._xsts_auth(xbl_token)
mc_token = await self._mc_auth(uhs, xsts_token)
profile = await self._get_profile(mc_token)
self.access_token_received.emit({
'access_token': mc_token,
'refresh_token': refresh_token,
'profile': profile
})
except Exception as e:
self.error_occurred.emit(str(e))
def run(self):
try:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(self._auth_flow())
except Exception as e:
self.error_occurred.emit(str(e))
finally:
self.finished.emit()
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.auth_dialog = None
self.success = False
self.username = None
# Initialize the launcher to get the correct config path
with Launcher.new() as launcher:
self.config_path = launcher.root
def authenticate(self, username):
self.username = username
self.success = False
# Create accounts.json if it doesn't exist
if not self.save_to_accounts_json():
return
self.auth_thread = AuthenticationThread(username)
self.auth_thread.auth_data_received.connect(self.show_auth_dialog)
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):
if self.auth_dialog is not None:
self.auth_dialog.close()
self.auth_dialog = AuthDialog(auth_data['url'], auth_data['code'])
result = self.auth_dialog.exec_()
if result != QDialog.Accepted:
self.auth_thread.stop()
def show_error(self, error_msg):
QMessageBox.critical(None, "Error", error_msg)
self.success = False
self.auth_finished.emit(False)
def save_to_accounts_json(self):
try:
accounts_file = Path(self.config_path) / "accounts.json"
if accounts_file.exists():
with open(accounts_file) as f:
config = json.load(f)
else:
config = {
"default": None,
"accounts": {},
"client_token": str(uuid.uuid4())
}
accounts_file.parent.mkdir(parents=True, exist_ok=True)
# Only create/update if account doesn't exist
if self.username not in config["accounts"]:
config["accounts"][self.username] = {
"uuid": "-",
"online": True,
"microsoft": True,
"gname": "-",
"access_token": "-",
"refresh_token": "-",
"is_authenticated": False
}
# Set as default if no default exists
if config["default"] is None:
config["default"] = self.username
with open(accounts_file, 'w') as f:
json.dump(config, f, indent=4)
return True
except Exception as e:
logger.error(f"Failed to initialize account data: {str(e)}")
QMessageBox.critical(None, "Error", f"Failed to initialize account data: {str(e)}")
return False
def on_access_token_received(self, data):
try:
accounts_file = Path(self.config_path) / "accounts.json"
with open(accounts_file) as f:
config = json.load(f)
if self.username in config["accounts"]:
config["accounts"][self.username].update({
"access_token": data['access_token'],
"refresh_token": data['refresh_token'],
"uuid": data['profile']['id'],
"gname": data['profile']['name'],
"is_authenticated": True
})
with open(accounts_file, 'w') as f:
json.dump(config, f, indent=4)
self.success = True
QMessageBox.information(None, "Success",
f"Successfully authenticated account: {self.username}")
else:
raise Exception("Account not found in configuration")
except Exception as e:
logger.error(f"Failed to update account data: {str(e)}")
QMessageBox.critical(None, "Error", f"Failed to update account data: {str(e)}")
self.success = False
self.auth_finished.emit(self.success)
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()
def create_authenticator():
"""Factory function to create a new MinecraftAuthenticator instance"""
return MinecraftAuthenticator()

View File

@ -1,231 +0,0 @@
import os
import json
import shutil
import modulecli
from PyQt5.QtWidgets import QApplication, QDialog, QVBoxLayout, QLabel, QProgressBar
from PyQt5.QtCore import Qt, QThread, pyqtSignal
import sys
class CopyThread(QThread):
progress_changed = pyqtSignal(int)
finished = pyqtSignal()
def __init__(self, src_dir, dst_dir):
super().__init__()
self.src_dir = src_dir
self.dst_dir = dst_dir
def run(self):
# Gather all files recursively
files_to_copy = []
for root, dirs, files in os.walk(self.src_dir):
for f in files:
full_path = os.path.join(root, f)
relative_path = os.path.relpath(full_path, self.src_dir)
files_to_copy.append(relative_path)
total_files = len(files_to_copy)
copied_files = 0
for relative_path in files_to_copy:
src_path = os.path.join(self.src_dir, relative_path)
dst_path = os.path.join(self.dst_dir, relative_path)
dst_folder = os.path.dirname(dst_path)
if not os.path.exists(dst_folder):
try:
os.makedirs(dst_folder)
except PermissionError:
print(f"Skipping folder {dst_folder} (permission denied)")
continue
try:
shutil.copy2(src_path, dst_path)
except PermissionError:
print(f"Skipping file {dst_path} (permission denied)")
copied_files += 1
progress_percent = int((copied_files / total_files) * 100)
self.progress_changed.emit(progress_percent)
self.finished.emit()
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": "",
"TotalPlaytime": 0,
"IsFirstLaunch": True,
"Instance": "default",
"Theme": "Dark.json",
"ThemeBackground": True,
"ThemeRepository": "https://raw.githubusercontent.com/nixietab/picodulce-themes/main/repo.json",
"Locale": "en",
"ManageJava": False,
"MaxRAM": "2G",
"JavaPath": "",
"ZucaroCheck": False,
}
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
try:
with open(config_path, "r") as config_file:
self.config = json.load(config_file)
except (json.JSONDecodeError, ValueError):
with open(config_path, "w") as config_file:
json.dump(default_config, config_file, indent=4)
self.config = default_config
return
updated = False
for key, value in default_config.items():
if key not in self.config:
self.config[key] = value
updated = True
if updated:
with open(config_path, "w") as config_file:
json.dump(self.config, config_file, indent=4)
def get_folder_size(self, folder_path):
total_size = 0
for dirpath, dirnames, filenames in os.walk(folder_path):
for f in filenames:
fp = os.path.join(dirpath, f)
if os.path.isfile(fp):
total_size += os.path.getsize(fp)
return total_size
def zucaro_health_check(self):
if self.config.get("ZucaroCheck"):
return
output = modulecli.run_command("instance dir").strip()
instance_dir = os.path.abspath(output)
base_dir = os.path.abspath(os.path.join(instance_dir, "..", ".."))
possible_zucaro = [os.path.join(base_dir, "zucaro"), os.path.join(base_dir, ".zucaro")]
possible_picomc = [os.path.join(base_dir, "picomc"), os.path.join(base_dir, ".picomc")]
zucaro_dir = next((d for d in possible_zucaro if os.path.exists(d)), None)
picomc_dir = next((d for d in possible_picomc if os.path.exists(d)), None)
if picomc_dir is None or zucaro_dir is None:
print("Required directories not found. Skipping copy.")
# Mark the check as done so it wont run again
self.config["ZucaroCheck"] = True
with open("config.json", "w") as f:
json.dump(self.config, f, indent=4)
return
picomc_size = self.get_folder_size(picomc_dir)
zucaro_size = self.get_folder_size(zucaro_dir)
if picomc_size <= zucaro_size:
print("No action needed. Zucaro folder is not smaller than Picomc.")
# Update config so the check is considered done
self.config["ZucaroCheck"] = True
with open("config.json", "w") as f:
json.dump(self.config, f, indent=4)
return
print(f"Copying Picomc ({picomc_size} bytes) to Zucaro ({zucaro_size} bytes)...")
app = QApplication.instance() or QApplication(sys.argv)
dialog = QDialog()
dialog.setWindowTitle("Working...")
dialog.setWindowModality(Qt.ApplicationModal)
layout = QVBoxLayout()
label = QLabel("Working on stuff, please wait...")
progress = QProgressBar()
progress.setValue(0)
layout.addWidget(label)
layout.addWidget(progress)
dialog.setLayout(layout)
# Setup copy thread
thread = CopyThread(picomc_dir, zucaro_dir)
thread.progress_changed.connect(progress.setValue)
thread.finished.connect(dialog.accept)
thread.start()
dialog.exec_() # Runs the modal event loop
# Mark as done
self.config["ZucaroCheck"] = True
with open("config.json", "w") as f:
json.dump(self.config, f, indent=4)
print("Copy completed.")
def themes_integrity(self):
themes_folder = "themes"
dark_theme_file = os.path.join(themes_folder, "Dark.json")
native_theme_file = os.path.join(themes_folder, "Native.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": ""
}
native_theme_content = {
"manifest": {
"name": "Native",
"description": "The native looks of your OS",
"author": "Your Qt Style",
"license": "Any"
},
"palette": {}
}
if not os.path.exists(themes_folder):
print(f"Creating folder: {themes_folder}")
os.makedirs(themes_folder)
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.")
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.")
if os.path.isfile(dark_theme_file) and os.path.isfile(native_theme_file):
print("Theme Integrity OK")

View File

@ -1,46 +0,0 @@
from io import StringIO
import sys
import shlex
import gc
def run_command(command="zucaro"):
# Remove all zucaro-related modules from sys.modules BEFORE import
modules_to_remove = [mod for mod in sys.modules if mod.startswith('zucaro')]
for mod in modules_to_remove:
del sys.modules[mod]
gc.collect()
# Import zucaro_cli dynamically
from zucaro.cli.main import zucaro_cli
# 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:
# Use shlex.split to properly parse the command string
# This will call Click's CLI as if from command line, using args
zucaro_cli.main(args=shlex.split(command))
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()
# Cleanup: remove zucaro-related modules from sys.modules and force garbage collection
modules_to_remove = [mod for mod in sys.modules if mod.startswith('zucaro')]
for mod in modules_to_remove:
del sys.modules[mod]
gc.collect()
if not output:
return f"Error: No output from command. Stderr: {error}"
return output

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,4 @@
zucaro
picomc
PyQt5
requests
aiohttp
pypresence
tqdm

View File

@ -1,15 +1,12 @@
{
"version": "0.13.5",
"version": "0.11.99",
"name": "Lucky Bastard",
"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"
],
"versionBleeding": "0.13.3-212"
"https://raw.githubusercontent.com/nixietab/picodulce/canary/version.json",
"https://raw.githubusercontent.com/nixietab/picodulce/canary/picodulce.py",
"https://raw.githubusercontent.com/nixietab/picodulce/canary/requirements.txt",
"https://raw.githubusercontent.com/nixietab/picodulce/canary/drums.gif",
"https://raw.githubusercontent.com/nixietab/picodulce/canary/marroc.py",
"https://raw.githubusercontent.com/nixietab/picodulce/canary/holiday.ico"
]
}