Update many files.
Add some description about this project, and now it has a simple UI (made with Qt) If you don't specify the mode of (interface?) the UI. Launcher will show a crude window that lets you select a mode to start the (real) launcher. Test version data fetching functions. Looks great.
This commit is contained in:
+18
@@ -0,0 +1,18 @@
|
|||||||
|
__version__ = "0.0.1"
|
||||||
|
__author__ = "wei"
|
||||||
|
__email__ = "[email protected]"
|
||||||
|
__description__ = """
|
||||||
|
A *Minecraft* launcher that I'm still thinking about its name.
|
||||||
|
|
||||||
|
I made this due to the official Minecraft Launcher is too huge and laggy. I need to wait it over 1 min to launch
|
||||||
|
game, and I also think that other launchers are too complicated for most people to use.
|
||||||
|
|
||||||
|
This launcher should adheres to the following rules:
|
||||||
|
|
||||||
|
1: Be simple
|
||||||
|
2: Be complex at some point (Yeah this rule looks opposite to the first rule)
|
||||||
|
3: Be cool and fun
|
||||||
|
4: No AI slop (Nobody wants to read spaghetti code that made by AI)
|
||||||
|
5: Freedom just like the bird
|
||||||
|
"""
|
||||||
|
__website__ = "https://repo.weispace.net/wei/Launcher"
|
||||||
@@ -1,6 +1,12 @@
|
|||||||
from PySide6.QtWidgets import QApplication, QWidget, QMainWindow
|
import logging
|
||||||
|
import textwrap
|
||||||
|
|
||||||
from core_lib.unclassified import move_window_to_center
|
from PySide6.QtCore import Qt
|
||||||
|
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton, QVBoxLayout, QWidget, QDialog, QPlainTextEdit, \
|
||||||
|
QHBoxLayout
|
||||||
|
from core_lib.qt.hack import move_window_to_center
|
||||||
|
from core_lib.unclassified import get_latest_version, get_version_manifest, get_specific_version_manifest_url, \
|
||||||
|
fetch_specific_version_manifest, find_useful_part_in_specific_version_manifest
|
||||||
|
|
||||||
|
|
||||||
class Launcher(QApplication):
|
class Launcher(QApplication):
|
||||||
@@ -12,6 +18,73 @@ class Launcher(QApplication):
|
|||||||
|
|
||||||
move_window_to_center(self.main_window)
|
move_window_to_center(self.main_window)
|
||||||
|
|
||||||
|
self.logger = logging.getLogger("Launcher.MainWindow")
|
||||||
|
|
||||||
|
self.test_dialog = self.TestDialog(self.main_window)
|
||||||
|
self.test_dialog.show()
|
||||||
|
|
||||||
|
class TestDialog(QDialog):
|
||||||
|
def __init__(self, parent):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.parent = parent
|
||||||
|
self.resize(300, 200)
|
||||||
|
self.setWindowFlags(self.windowFlags() | Qt.WindowMaximizeButtonHint)
|
||||||
|
|
||||||
|
self.button = QPushButton("Fetch Version Data")
|
||||||
|
self.button.setObjectName("ActionButton")
|
||||||
|
self.button.clicked.connect(self.run_test)
|
||||||
|
|
||||||
|
self.output = QPlainTextEdit()
|
||||||
|
self.output.setReadOnly(True)
|
||||||
|
|
||||||
|
self.layout = QHBoxLayout()
|
||||||
|
self.layout.addWidget(self.output)
|
||||||
|
|
||||||
|
self.right_layout = QVBoxLayout()
|
||||||
|
self.right_layout.setObjectName("RightLayout")
|
||||||
|
self.right_layout.setAlignment(Qt.AlignmentFlag.AlignRight)
|
||||||
|
self.right_layout.addWidget(self.button)
|
||||||
|
self.right_layout.addStretch()
|
||||||
|
self.right_layout.setContentsMargins(0, 10, 0, 0)
|
||||||
|
|
||||||
|
self.layout.addLayout(self.right_layout)
|
||||||
|
|
||||||
|
self.setLayout(self.layout)
|
||||||
|
|
||||||
|
self.setStyleSheet(
|
||||||
|
textwrap.dedent("""
|
||||||
|
QPushButton#ActionButton {
|
||||||
|
padding: 6px 12px;
|
||||||
|
}
|
||||||
|
"""))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def app(self):
|
||||||
|
return self.parent.parent
|
||||||
|
|
||||||
|
def run_test(self):
|
||||||
|
data = get_version_manifest()
|
||||||
|
ver = get_latest_version(data)
|
||||||
|
ver_data = fetch_specific_version_manifest(data, ver)
|
||||||
|
arguments, asset_index, downloads, java_version, libraries, main_class = (
|
||||||
|
find_useful_part_in_specific_version_manifest(ver_data))
|
||||||
|
|
||||||
|
content = textwrap.dedent(f"""\
|
||||||
|
Arguments: {arguments}\n
|
||||||
|
|
||||||
|
Asset: {asset_index}\n
|
||||||
|
|
||||||
|
Downloads: {downloads}\n
|
||||||
|
|
||||||
|
Java Version: {java_version}\n
|
||||||
|
|
||||||
|
Libraries: {libraries}\n
|
||||||
|
|
||||||
|
Main Class: {main_class}\n
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.output.setPlainText(content)
|
||||||
|
|
||||||
def exec(self):
|
def exec(self):
|
||||||
self.main_window.show()
|
self.main_window.show()
|
||||||
self.exec_()
|
self.exec_()
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
__version__ = '0.1.0'
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger("Launcher.CoreLib")
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from colorlog import ColoredFormatter
|
||||||
|
|
||||||
|
DEFAULT_FORMAT = '%(asctime)s:%(name)s:%(levelname)s: %(message)s'
|
||||||
|
DEFAULT_DATE_FORMAT = '%H:%M:%S'
|
||||||
|
|
||||||
|
def get_colorful_handler():
|
||||||
|
handler = logging.StreamHandler(sys.stdout)
|
||||||
|
formatter = ColoredFormatter(
|
||||||
|
'%(light_white)s%(asctime)s:%(light_green)s%(name)s:%(log_color)s%(levelname)s%(reset)s: %(message)s',
|
||||||
|
log_colors={
|
||||||
|
'DEBUG': 'cyan',
|
||||||
|
'INFO': 'green',
|
||||||
|
'WARNING': 'yellow',
|
||||||
|
'ERROR': 'light_red',
|
||||||
|
'CRITICAL': 'red',
|
||||||
|
},
|
||||||
|
datefmt=DEFAULT_DATE_FORMAT
|
||||||
|
)
|
||||||
|
handler.setFormatter(formatter)
|
||||||
|
return handler
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
from PySide6.QtGui import QScreen
|
||||||
|
from PySide6.QtWidgets import QMainWindow, QApplication
|
||||||
|
|
||||||
|
|
||||||
|
def move_window_to_center(window: QMainWindow):
|
||||||
|
main_screen = QApplication.primaryScreen()
|
||||||
|
center = QScreen.availableGeometry(main_screen).center()
|
||||||
|
geometry = window.frameGeometry()
|
||||||
|
geometry.moveCenter(center)
|
||||||
|
window.move(geometry.topLeft())
|
||||||
+119
-8
@@ -1,10 +1,121 @@
|
|||||||
from PySide6.QtGui import QScreen
|
import json
|
||||||
from PySide6.QtWidgets import QMainWindow, QApplication
|
|
||||||
|
import requests
|
||||||
|
from .core_init import logger
|
||||||
|
|
||||||
|
MOJANG_VERSION_MANIFEST_V2 = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"
|
||||||
|
|
||||||
|
def get_version_manifest(manifest_url=MOJANG_VERSION_MANIFEST_V2, timeout=10) -> dict | None:
|
||||||
|
logger.debug("Getting version manifest from {}".format(manifest_url))
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = requests.get(manifest_url, timeout=timeout)
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
logger.error("Error getting version manifest from {}".format(manifest_url))
|
||||||
|
logger.error("Exception: {}".format(e))
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = r.json()
|
||||||
|
logger.debug("VersionManifest: {}".format(json.dumps(data, indent=4)))
|
||||||
|
return data
|
||||||
|
except ValueError as e:
|
||||||
|
logger.error("Unable to deserialize response data into dictionary.")
|
||||||
|
logger.error("URL: {}".format(manifest_url))
|
||||||
|
logger.error("Exception: {}".format(e))
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_latest_version(version_manifest: dict, is_snapshot=False) -> None | str:
|
||||||
|
latest = version_manifest.get("latest", {})
|
||||||
|
|
||||||
|
if len(latest) == 0:
|
||||||
|
logger.debug("No latest version section available in version manifest.")
|
||||||
|
logger.debug("Data: {}".format(json.dumps(version_manifest, indent=4)))
|
||||||
|
return None
|
||||||
|
|
||||||
|
latest_ver = latest.get("release" if not is_snapshot else "snapshot", None)
|
||||||
|
|
||||||
|
if latest_ver is None:
|
||||||
|
logger.debug("No latest version available.")
|
||||||
|
else:
|
||||||
|
logger.debug("Latest version: {}".format(latest_ver))
|
||||||
|
|
||||||
|
return latest_ver
|
||||||
|
|
||||||
|
def get_specific_version_manifest_url(version_manifest: dict,
|
||||||
|
version_id: str, is_snapshot=False) -> str | None:
|
||||||
|
versions = version_manifest.get("versions", [])
|
||||||
|
|
||||||
|
if len(versions) == 0:
|
||||||
|
logger.debug("No version section available in version manifest.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
ver_type = "release" if not is_snapshot else "snapshot"
|
||||||
|
versions = [version for version in versions if version.get("type") == ver_type]
|
||||||
|
|
||||||
|
for version in versions:
|
||||||
|
if version.get("id") == version_id:
|
||||||
|
specific_ver_manifest_url = version.get("url", None)
|
||||||
|
|
||||||
|
if specific_ver_manifest_url is None:
|
||||||
|
logger.debug("Specific version manifest URL not available in version manifest.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
return specific_ver_manifest_url
|
||||||
|
|
||||||
|
logger.debug("No version section available in version manifest.")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def fetch_specific_version_manifest(version_manifest: dict, spec_version: str) -> dict | None:
|
||||||
|
ver_url = get_specific_version_manifest_url(version_manifest, spec_version)
|
||||||
|
|
||||||
|
if ver_url is None:
|
||||||
|
logger.debug("Unable to get specific version manifest url from version manifest.\n"
|
||||||
|
"Version: {}".format(spec_version))
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = requests.get(ver_url, timeout=10)
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
logger.error("Error getting version manifest from {}".format(ver_url))
|
||||||
|
logger.error("Exception: {}".format(e))
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = r.json()
|
||||||
|
logger.debug("SpecificVersionManifest: {}".format(json.dumps(data, indent=4)))
|
||||||
|
return data
|
||||||
|
except ValueError as e:
|
||||||
|
logger.error("Unable to deserialize response data into dictionary.")
|
||||||
|
|
||||||
|
|
||||||
def move_window_to_center(window: QMainWindow):
|
def find_useful_part_in_specific_version_manifest(spec_version_manifest: dict)\
|
||||||
main_screen = QApplication.primaryScreen()
|
-> tuple[dict | None, dict | None, dict | None, dict | None, list | None, str | None]:
|
||||||
center = QScreen.availableGeometry(main_screen).center()
|
arguments = spec_version_manifest.get("arguments", {})
|
||||||
geometry = window.frameGeometry()
|
asset_index = spec_version_manifest.get("assetIndex", {})
|
||||||
geometry.moveCenter(center)
|
downloads = spec_version_manifest.get("downloads", {})
|
||||||
window.move(geometry.topLeft())
|
java_version = spec_version_manifest.get("java_version", {})
|
||||||
|
libraries = spec_version_manifest.get("libraries", [])
|
||||||
|
main_class = spec_version_manifest.get("mainClass", None)
|
||||||
|
|
||||||
|
if len(arguments) == 0:
|
||||||
|
logger.debug("No arguments section available in version manifest.")
|
||||||
|
|
||||||
|
if len(asset_index) == 0:
|
||||||
|
logger.debug("No asset index section available in version manifest.")
|
||||||
|
|
||||||
|
if len(downloads) == 0:
|
||||||
|
logger.debug("No downloads section available in version manifest.")
|
||||||
|
|
||||||
|
if len(java_version) == 0:
|
||||||
|
logger.debug("No java version section available in version manifest.")
|
||||||
|
|
||||||
|
if len(libraries) == 0:
|
||||||
|
logger.debug("No libraries section available in version manifest.")
|
||||||
|
|
||||||
|
if main_class is None:
|
||||||
|
logger.debug("No main class section available in version manifest.")
|
||||||
|
|
||||||
|
return arguments, asset_index, downloads, java_version, libraries, main_class
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import os
|
|
||||||
import sys
|
import sys
|
||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from app import Launcher
|
from app import Launcher
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
|
|
||||||
# Logger configure
|
from core_lib.log.default import DEFAULT_FORMAT, DEFAULT_DATE_FORMAT, get_colorful_handler
|
||||||
logging.basicConfig(format='%(asctime)s:%(name)s:%(levelname)s: %(message)s',
|
|
||||||
level=logging.INFO,
|
|
||||||
datefmt='%H:%M:%S')
|
|
||||||
|
|
||||||
def select_mode() -> None | str:
|
def select_mode() -> None | str:
|
||||||
value = None
|
value = None
|
||||||
@@ -49,26 +47,44 @@ def tui_entry():
|
|||||||
logger.info("Wow tui is unfinished")
|
logger.info("Wow tui is unfinished")
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
root_logger = logging.getLogger()
|
||||||
logger = logging.getLogger("Launcher.Main")
|
logger = logging.getLogger("Launcher.Main")
|
||||||
|
|
||||||
|
# parse arguments
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument("-m", "--mode", choices=['lightweight', 'full', 'choice'], default='choice',
|
parser.add_argument("-m", "--mode", choices=['lightweight', 'full', 'choice'], default='choice',
|
||||||
help="Select mode (lightweight for tui, full is gui,"
|
help="Select mode (lightweight for tui, full is gui,"
|
||||||
"choice for a mode select window)", required=False)
|
"choice for a mode select window)", required=False)
|
||||||
|
parser.add_argument("-d", "--debug", action="store_true",
|
||||||
|
help="Enable debug mode", default=False)
|
||||||
|
|
||||||
args, unknown = parser.parse_known_args()
|
args, unknown = parser.parse_known_args()
|
||||||
for arg in unknown:
|
for arg in unknown:
|
||||||
logging.warning("Unknown argument '%s'" % arg)
|
logger.warning("Unknown argument '%s'" % arg)
|
||||||
|
|
||||||
|
# args value set
|
||||||
if not hasattr(args, "mode") or args.mode == "choice":
|
if not hasattr(args, "mode") or args.mode == "choice":
|
||||||
mode = select_mode()
|
mode = select_mode()
|
||||||
|
|
||||||
if mode is None:
|
if mode is None:
|
||||||
logging.warning("No mode selected. Exiting...")
|
logger.warning("No mode selected. Exiting...")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
else:
|
else:
|
||||||
mode = args.mode
|
mode = args.mode
|
||||||
|
|
||||||
|
debug = args.debug
|
||||||
|
|
||||||
|
# Init lib
|
||||||
|
level = logging.DEBUG if debug else logging.INFO
|
||||||
|
|
||||||
|
# Set root logger level and configure handler for colorful log (not sure why I added this)
|
||||||
|
root_logger.setLevel(level)
|
||||||
|
root_logger.addHandler(get_colorful_handler())
|
||||||
|
|
||||||
|
# Logger configure
|
||||||
|
logging.basicConfig(format=DEFAULT_FORMAT, datefmt=DEFAULT_DATE_FORMAT, level=level)
|
||||||
|
|
||||||
|
# Start (the real) launcher
|
||||||
if mode == 'lightweight':
|
if mode == 'lightweight':
|
||||||
sys.exit(tui_entry())
|
sys.exit(tui_entry())
|
||||||
elif mode == 'full':
|
elif mode == 'full':
|
||||||
@@ -78,6 +94,5 @@ def main():
|
|||||||
logger.error("Unknown mode '%s'" % mode)
|
logger.error("Unknown mode '%s'" % mode)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|||||||
Reference in New Issue
Block a user