Commit 7855d8b9 authored by Mahmoud Aglan's avatar Mahmoud Aglan

Release packaging, update feed, engine patches and screen polish

- Release builds: scripts/build-macos.sh (app + DMG, engine patches applied
  idempotently) and scripts/cross-build-windows.sh (llvm-mingw cross-compile,
  Qt deploy, NSIS installer and portable zip).
- Engine patches: step over arm64 debug breaks, ignore SIGPIPE, Ctrl+Cmd+F
  fullscreen on macOS, optional non-Retina rendering.
- Launcher updates come from a configurable AL-Arcade release feed
  (ALARCADE_UPDATE_FEED_URL) and show in the sidebar on both platforms.
- Restyled the remaining upstream screens (sign-in, profile editor, game log,
  mods, troubleshooting, versions, storage) onto the shared theme.
- README, NOTICE and the GPL-3.0 LICENSE at the repository root.
Co-Authored-By: 's avatarClaude Opus 5.5 <noreply@anthropic.com>
parent dfa036f8
...@@ -4,3 +4,4 @@ ...@@ -4,3 +4,4 @@
.DS_Store .DS_Store
*.dmg *.dmg
/.cache/ /.cache/
*.log
This diff is collapsed.
# Notices
AL-Arcade MC Launcher is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License version 3 (see `LICENSE`).
**NOT AN OFFICIAL MINECRAFT PRODUCT. NOT APPROVED BY OR ASSOCIATED WITH MOJANG OR
MICROSOFT.** Minecraft is a trademark of Mojang Synergies AB. Players need to own
the game: on macOS through Google Play (Android edition), on Windows through the
Microsoft Store or the Xbox app.
## Based on
| Project | License | Used for |
|---|---|---|
| [mcpelauncher](https://github.com/minecraft-linux/mcpelauncher-manifest) by MCMrARM, ChristopherHX and contributors | GPL-3.0 | Game engine (`engine/`), launcher UI the app grew from (`launcher/`), Google Play libraries (`deps/`) |
| [Qt 6](https://www.qt.io) | LGPL-3.0 | User interface |
| [ANGLE](https://chromium.googlesource.com/angle/angle) | BSD-3-Clause | OpenGL ES on Metal and Vulkan (macOS) |
| [MoltenVK](https://github.com/KhronosGroup/MoltenVK) | Apache-2.0 | Vulkan on Metal (macOS) |
| [SDL 3](https://libsdl.org) | Zlib | Audio in the game engine |
| [OpenSSL](https://www.openssl.org) | Apache-2.0 | Encryption of stored sign-in data, TLS in the engine |
| [protobuf](https://protobuf.dev), [Abseil](https://abseil.io) | BSD-3-Clause, Apache-2.0 | Google Play protocol |
| [libzip](https://libzip.org), [miniz](https://github.com/richgel999/miniz) | BSD-3-Clause, MIT | Zip archives |
| [Inter](https://rsms.me/inter/), [IBM Plex Sans Arabic](https://github.com/IBM/plex) | OFL-1.1 | Typefaces (`launcher/fonts`) |
| [Lucide](https://lucide.dev) | ISC | Icons (`launcher/icons`) |
The ANGLE and MoltenVK binaries in the macOS app are taken unmodified from the
mcpelauncher macOS release named in `scripts/build-macos.sh` and verified by
checksum. License texts ship next to each component (`launcher/fonts/*-OFL.txt`,
`launcher/icons/LICENSE.txt`, `launcher/third_party/miniz/LICENSE`).
# AL-Arcade MC Launcher
A launcher for **Minecraft: Bedrock Edition** on **macOS** and **Windows**, with
world backups, pack management and a redesigned interface. It grew out of
[mcpelauncher](https://github.com/minecraft-linux/mcpelauncher-manifest) and stays
GPL-3.0 like it.
> NOT AN OFFICIAL MINECRAFT PRODUCT. NOT APPROVED BY OR ASSOCIATED WITH MOJANG OR
> MICROSOFT. You need to own Minecraft to play.
## How it plays the game
| | macOS (Apple Silicon) | Windows 10 / 11 (x64) |
|---|---|---|
| Game | The Android edition, run natively by the mcpelauncher engine (no emulator) | The official Minecraft for Windows |
| Ownership | Google Play purchase, checked at sign-in | Microsoft Store / Xbox app purchase |
| Download | From Google Play, inside the launcher | Installed by the Microsoft Store or the Xbox app |
| Keyboard and mouse | Yes. Fullscreen: Ctrl+Cmd+F | Yes (native) |
| Versions and profiles | Any supported version, per-profile data folders | Release or Preview edition |
| Mods | Engine mods (upstream mod system) | No |
Everything else is shared by both builds:
- **Worlds**: list with size, game mode and last played; back up, export and import
`.mcworld`; restore any backup next to the original; move to Trash / Recycle Bin.
- **Automatic backups**: when the game closes, every world that changed is saved.
Keeps the last 5 per world by default (Settings > Backups).
- **Packs**: resource packs, behavior packs and skin packs; import `.mcpack` and
`.mcaddon` (an add-on with several packs is unpacked into each), replace a pack
with a newer copy of the same pack, remove packs.
- **Updates**: optional launcher update check against an AL-Arcade release feed
(see [Update feed](#update-feed)).
- **Recovery**: after a launcher crash it restarts once in safe mode.
- Right-to-left layout when the system language is Arabic or Hebrew. The interface
is translatable with Qt Linguist; the translations carried over from upstream
cover only part of the new screens.
## Installing
**macOS**: open `AL-Arcade-MC-Launcher-<version>-macOS-arm64.dmg` and drag the app
to Applications. Builds are not notarized yet, so the first launch needs
right-click > Open (or System Settings > Privacy & Security > Open Anyway).
Sign in with the Google account that bought Minecraft on Google Play. The
sign-in is stored encrypted on disk; its key lives in the macOS Keychain.
**Windows**: run `AL-Arcade-MC-Launcher-<version>-windows-x64-setup.exe` (per-user
install, no administrator rights needed) or unpack the portable `.zip`. The
installer is not code-signed yet, so SmartScreen asks for More info > Run anyway.
Install Minecraft from the Microsoft Store or the Xbox app first; the launcher
finds it and starts it.
## Building
The repository uses submodules for the engine and the Google Play libraries:
```bash
git clone --recursive https://gitlab.caprover.al-arcade.com/root/al-arcade-mc-launcher.git
# or, in an existing clone:
git submodule update --init --recursive
```
### macOS app and disk image
Requirements: Xcode command line tools and
```bash
brew install cmake ninja pkgconf automake qtbase qtdeclarative qtsvg qttools \
qtshadertools openssl@3 protobuf abseil libzip
```
```bash
scripts/build-macos.sh --dmg # dist/AL-Arcade MC Launcher.app + .dmg
scripts/build-macos.sh --skip-engine # launcher only, reuses the last engine build
```
The script applies the engine patches in `patches/engine/`, builds the engine
and the launcher, compiles the Swift helpers (Xbox sign-in and error windows),
bundles Qt with `macdeployqt`, adds ANGLE and MoltenVK from the upstream macOS
release (fixed version, verified by SHA-256) and signs the app ad hoc.
### Windows (cross-compiled on macOS)
```bash
brew install cmake ninja makensis qtbase qtdeclarative qtsvg qttools qtshadertools
scripts/cross-build-windows.sh
```
The first run downloads [llvm-mingw](https://github.com/mstorsjo/llvm-mingw) and
Qt for Windows into `.cache/` (checksummed; nothing is installed system-wide). It
produces the installer, a portable `.zip` and their SHA-256 sums in `dist/`.
The Homebrew Qt version must match `QT_VERSION` (default 6.11.2).
A native build on Windows uses the same CMake project:
`cmake -B build -G Ninja -DALARCADE_BACKEND=windows -DCMAKE_PREFIX_PATH=<Qt>`.
It is not scripted yet.
### Build options
| CMake option / variable | Effect |
|---|---|
| `ALARCADE_BACKEND` | `android` (engine; default on macOS) or `windows` (default on Windows) |
| `ALARCADE_UPDATE_FEED_URL` | HTTPS URL of the release feed. Empty: no update checks. Both build scripts pass it through from the environment |
| `LAUNCHER_VERSION_NAME`, `LAUNCHER_VERSION_CODE` | Set by the scripts from `VERSION` and the commit count (`BUILD_NUMBER` overrides) |
| `MACOSX_DEPLOYMENT_TARGET` | Requested minimum macOS for the engine and launcher (default 12.0) |
## Update feed
The launcher compares its build number with a JSON file:
```json
{
"version": "1.1.0",
"build": 42,
"notes": "What changed",
"macos": { "url": "https://.../AL-Arcade-MC-Launcher-1.1.0-macOS-arm64.dmg", "sha256": "..." },
"windows": { "url": "https://.../AL-Arcade-MC-Launcher-1.1.0-windows-x64-setup.exe", "sha256": "..." }
}
```
When `build` is higher than the running build and the platform entry has an
`https://` URL, a sidebar entry offers the download. `ALARCADE_UPDATE_FEED=<url>`
overrides the built-in URL for testing.
## Repository layout
| Path | Contents |
|---|---|
| `launcher/` | The launcher (Qt 6 / QML). `qml/main.qml` is the shared shell; `qml/android/` and `qml/windows/` are the two game backends; `qml/pages/` the shared pages; `core/` worlds, packs, zip and level.dat code |
| `launcher/macos/`, `launcher/windows/` | Native code: Keychain and Credential Manager storage, Google sign-in window (WebKit), Minecraft for Windows detection and launch, crash handler |
| `engine/` | mcpelauncher engine (submodule), patched by `patches/engine/` at build time |
| `deps/` | Google Play API and sign-in libraries (submodules) |
| `macos/` | Info.plist template and the Swift helper apps |
| `windows/` | NSIS installer script |
| `branding/`, `tools/brand/` | App icons and the scripts that render them |
| `scripts/` | Release builds for macOS and Windows |
| `tools/dev/screenshots.sh` | Renders every page to PNG for design review |
### Engine patches
| Patch | Fixes |
|---|---|
| `mcpelauncher-core/0001-arm64-step-over-debug-breaks` | Debug break instructions in the game crashed or hung it on Apple Silicon; they are now stepped over |
| `mcpelauncher-client/0001-ignore-sigpipe` | The game quit when a network connection dropped (for example with a VPN) |
| `mcpelauncher-client/0002-macos-fullscreen-shortcut` | Ctrl+Cmd+F toggles fullscreen; F11 is taken by macOS |
| `game-window/0001-optional-non-retina-rendering` | Setting to render at half resolution on Retina screens for more frames per second |
## Known limitations
- macOS builds are Apple Silicon only. Their minimum macOS version follows the
Homebrew libraries they bundle (currently macOS 27); building those
dependencies for an older deployment target is still to do.
- The Windows build is cross-compiled and checked on macOS; it has not yet been
run on a Windows machine.
- Builds are unsigned: no Apple notarization, no Authenticode signature.
- World thumbnails that the game saves as a single color are shown as a
placeholder icon.
## License
GPL-3.0, see `LICENSE`. Credits and third-party licenses are in `NOTICE.md`.
Minecraft is a trademark of Mojang Synergies AB.
2026-10-05 06:50:01,146 - aqt.main - INFO - installer 8471887744 aqtinstall(aqt) v3.3.0 on Python 3.14.7 [CPython Clang 21.0.0 (clang-2100.1.1.101)]
2026-10-05 06:50:01,147 - aqt.helper - DEBUG - helper 8471887744 Attempt to download checksum at https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt6_6112/qt6_6112/Updates.xml.sha256
2026-10-05 06:50:01,740 - aqt.helper - DEBUG - helper 8471887744 Attempt to download checksum at https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt6_6112/qt6_6112/Updates.xml.sha256
2026-10-05 06:50:02,159 - aqt.helper - DEBUG - helper 8471887744 Attempt to download checksum at https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt6_6112/qt6_6112/Updates.xml.sha256
2026-10-05 06:50:02,542 - aqt.helper - DEBUG - helper 8471887744 Attempt to download checksum at https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt6_6112/qt6_6112/Updates.xml.sha256
2026-10-05 06:50:02,975 - aqt.helper - DEBUG - helper 8471887744 Attempt to download checksum at https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt6_6112/qt6_6112/Updates.xml.sha256
2026-10-05 06:50:03,408 - aqt.archives - WARNING - archives 8471887744 Failed to download checksum for the file 'online/qtsdkrepository/windows_x86/desktop/qt6_6112/qt6_6112/Updates.xml'. This may happen on unofficial mirrors.
2026-10-05 06:50:03,828 - aqt.main - ERROR - installer 8471887744 Failed to locate XML data for Qt version '6.11.2'.
==============================Suggested follow-up:==============================
* Please use 'aqt list-qt windows desktop' to show versions available.
2026-10-05 06:51:28,664 - aqt.main - INFO - installer 8471887744 aqtinstall(aqt) v3.3.0 on Python 3.14.7 [CPython Clang 21.0.0 (clang-2100.1.1.101)]
2026-10-05 06:51:29,946 - aqt.helper - DEBUG - helper 8471887744 Attempt to download checksum at https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt6_6112/qt6_6112/Updates.xml.sha256
2026-10-05 06:51:30,766 - aqt.helper - DEBUG - helper 8471887744 Attempt to download checksum at https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt6_6112/qt6_6112/Updates.xml.sha256
2026-10-05 06:51:31,643 - aqt.helper - DEBUG - helper 8471887744 Attempt to download checksum at https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt6_6112/qt6_6112/Updates.xml.sha256
2026-10-05 06:51:32,636 - aqt.helper - DEBUG - helper 8471887744 Attempt to download checksum at https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt6_6112/qt6_6112/Updates.xml.sha256
2026-10-05 06:51:33,909 - aqt.helper - DEBUG - helper 8471887744 Attempt to download checksum at https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt6_6112/qt6_6112/Updates.xml.sha256
2026-10-05 06:51:35,056 - aqt.main - ERROR - installer 8471887744 Failed to download checksum for the file 'Updates.xml' from mirrors '['https://download.qt.io']
==============================Suggested follow-up:==============================
* Check your internet connection
* Consider modifying `requests.max_retries_to_retrieve_hash` in settings.ini
* Consider modifying `mirrors.trusted_mirrors` in settings.ini (see https://aqtinstall.readthedocs.io/en/stable/configuration.html#configuration)
* Please use 'aqt list-qt windows desktop' to show versions of Qt available.
2026-10-05 06:51:35,257 - aqt.helper - DEBUG - helper 8471887744 Attempt to download checksum at https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt6_6111/qt6_6111/Updates.xml.sha256
2026-10-05 06:51:36,334 - aqt.helper - DEBUG - helper 8471887744 Attempt to download checksum at https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt6_6111/qt6_6111/Updates.xml.sha256
2026-10-05 06:51:37,471 - aqt.helper - DEBUG - helper 8471887744 Attempt to download checksum at https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt6_6111/qt6_6111/Updates.xml.sha256
2026-10-05 06:51:38,432 - aqt.helper - DEBUG - helper 8471887744 Attempt to download checksum at https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt6_6111/qt6_6111/Updates.xml.sha256
2026-10-05 06:51:39,525 - aqt.helper - DEBUG - helper 8471887744 Attempt to download checksum at https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt6_6111/qt6_6111/Updates.xml.sha256
2026-10-05 06:51:40,736 - aqt.main - ERROR - installer 8471887744 Failed to download checksum for the file 'Updates.xml' from mirrors '['https://download.qt.io']
==============================Suggested follow-up:==============================
* Check your internet connection
* Consider modifying `requests.max_retries_to_retrieve_hash` in settings.ini
* Consider modifying `mirrors.trusted_mirrors` in settings.ini (see https://aqtinstall.readthedocs.io/en/stable/configuration.html#configuration)
* Please use 'aqt list-qt windows desktop' to show versions of Qt available.
2026-10-05 06:51:40,939 - aqt.helper - DEBUG - helper 8471887744 Attempt to download checksum at https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt6_6110/qt6_6110/Updates.xml.sha256
2026-10-05 06:51:42,121 - aqt.helper - DEBUG - helper 8471887744 Attempt to download checksum at https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt6_6110/qt6_6110/Updates.xml.sha256
2026-10-05 06:51:43,352 - aqt.helper - DEBUG - helper 8471887744 Attempt to download checksum at https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt6_6110/qt6_6110/Updates.xml.sha256
2026-10-05 06:51:44,304 - aqt.helper - DEBUG - helper 8471887744 Attempt to download checksum at https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt6_6110/qt6_6110/Updates.xml.sha256
2026-10-05 06:51:45,443 - aqt.helper - DEBUG - helper 8471887744 Attempt to download checksum at https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt6_6110/qt6_6110/Updates.xml.sha256
2026-10-05 06:51:46,645 - aqt.main - ERROR - installer 8471887744 Failed to download checksum for the file 'Updates.xml' from mirrors '['https://download.qt.io']
==============================Suggested follow-up:==============================
* Check your internet connection
* Consider modifying `requests.max_retries_to_retrieve_hash` in settings.ini
* Consider modifying `mirrors.trusted_mirrors` in settings.ini (see https://aqtinstall.readthedocs.io/en/stable/configuration.html#configuration)
* Please use 'aqt list-qt windows desktop' to show versions of Qt available.
2026-10-05 06:52:03,219 - aqt.helper - DEBUG - helper 8471887744 Attempt to download checksum at https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt6_6112/qt6_6112/Updates.xml.sha256
2026-10-05 06:52:04,357 - aqt.helper - DEBUG - helper 8471887744 Attempt to download checksum at https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt6_6112/qt6_6112/Updates.xml.sha256
2026-10-05 06:52:05,406 - aqt.helper - DEBUG - helper 8471887744 Attempt to download checksum at https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt6_6112/qt6_6112/Updates.xml.sha256
2026-10-05 06:52:06,555 - aqt.helper - DEBUG - helper 8471887744 Attempt to download checksum at https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt6_6112/qt6_6112/Updates.xml.sha256
2026-10-05 06:52:07,486 - aqt.helper - DEBUG - helper 8471887744 Attempt to download checksum at https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt6_6112/qt6_6112/Updates.xml.sha256
2026-10-05 06:52:08,561 - aqt.main - ERROR - installer 8471887744 Failed to download checksum for the file 'Updates.xml' from mirrors '['https://download.qt.io']
==============================Suggested follow-up:==============================
* Check your internet connection
* Consider modifying `requests.max_retries_to_retrieve_hash` in settings.ini
* Consider modifying `mirrors.trusted_mirrors` in settings.ini (see https://aqtinstall.readthedocs.io/en/stable/configuration.html#configuration)
* Please use 'aqt list-qt windows desktop' to show versions of Qt available.
2026-10-05 06:52:08,764 - aqt.helper - DEBUG - helper 8471887744 Attempt to download checksum at https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt6_6112/qt6_6112/Updates.xml.sha256
2026-10-05 06:52:09,865 - aqt.helper - DEBUG - helper 8471887744 Attempt to download checksum at https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt6_6112/qt6_6112/Updates.xml.sha256
2026-10-05 06:52:11,016 - aqt.helper - DEBUG - helper 8471887744 Attempt to download checksum at https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt6_6112/qt6_6112/Updates.xml.sha256
2026-10-05 06:52:12,077 - aqt.helper - DEBUG - helper 8471887744 Attempt to download checksum at https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt6_6112/qt6_6112/Updates.xml.sha256
2026-10-05 06:52:13,231 - aqt.helper - DEBUG - helper 8471887744 Attempt to download checksum at https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt6_6112/qt6_6112/Updates.xml.sha256
2026-10-05 06:52:14,313 - aqt.main - ERROR - installer 8471887744 Failed to download checksum for the file 'Updates.xml' from mirrors '['https://download.qt.io']
==============================Suggested follow-up:==============================
* Check your internet connection
* Consider modifying `requests.max_retries_to_retrieve_hash` in settings.ini
* Consider modifying `mirrors.trusted_mirrors` in settings.ini (see https://aqtinstall.readthedocs.io/en/stable/configuration.html#configuration)
* Please use 'aqt list-qt windows desktop' to show versions of Qt available.
* Please use 'aqt list-qt windows desktop --arch <QT_VERSION>' to list valid architectures.
...@@ -15,6 +15,7 @@ find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets Network Quick QuickControl ...@@ -15,6 +15,7 @@ find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets Network Quick QuickControl
set(LAUNCHER_VERSION_NAME "${PROJECT_VERSION}" CACHE STRING "Version name shown in the app") set(LAUNCHER_VERSION_NAME "${PROJECT_VERSION}" CACHE STRING "Version name shown in the app")
set(LAUNCHER_VERSION_CODE "1" CACHE STRING "Integer build number, used for changelog and update checks") set(LAUNCHER_VERSION_CODE "1" CACHE STRING "Integer build number, used for changelog and update checks")
set(LAUNCHER_CHANGE_LOG "" CACHE STRING "Changelog shown after an update") set(LAUNCHER_CHANGE_LOG "" CACHE STRING "Changelog shown after an update")
set(ALARCADE_UPDATE_FEED_URL "" CACHE STRING "HTTPS URL of the release feed (latest.json); empty disables update checks")
option(LAUNCHER_DISABLE_DEV_MODE "Hide the developer settings unless started with --enable-devmode" ON) option(LAUNCHER_DISABLE_DEV_MODE "Hide the developer settings unless started with --enable-devmode" ON)
# ---------------------------------------------------------------- shared # ---------------------------------------------------------------- shared
...@@ -168,6 +169,9 @@ target_compile_definitions(${APP_TARGET} PRIVATE ...@@ -168,6 +169,9 @@ target_compile_definitions(${APP_TARGET} PRIVATE
if(ALARCADE_BACKEND STREQUAL "android" AND NOT "${LAUNCHER_VERSIONDB_URL}" STREQUAL "") if(ALARCADE_BACKEND STREQUAL "android" AND NOT "${LAUNCHER_VERSIONDB_URL}" STREQUAL "")
target_compile_definitions(${APP_TARGET} PRIVATE LAUNCHER_VERSIONDB_URL="${LAUNCHER_VERSIONDB_URL}") target_compile_definitions(${APP_TARGET} PRIVATE LAUNCHER_VERSIONDB_URL="${LAUNCHER_VERSIONDB_URL}")
endif() endif()
if(NOT "${ALARCADE_UPDATE_FEED_URL}" STREQUAL "")
target_compile_definitions(${APP_TARGET} PRIVATE ALARCADE_UPDATE_FEED_URL="${ALARCADE_UPDATE_FEED_URL}")
endif()
if(NOT "${LAUNCHER_CHANGE_LOG}" STREQUAL "") if(NOT "${LAUNCHER_CHANGE_LOG}" STREQUAL "")
target_compile_definitions(${APP_TARGET} PRIVATE LAUNCHER_CHANGE_LOG="${LAUNCHER_CHANGE_LOG}") target_compile_definitions(${APP_TARGET} PRIVATE LAUNCHER_CHANGE_LOG="${LAUNCHER_CHANGE_LOG}")
endif() endif()
......
#include "gamelauncher.h" #include "gamelauncher.h"
#include "launchersettings.h"
#include "profilemanager.h" #include "profilemanager.h"
#include "EnvPathUtil.h" #include "EnvPathUtil.h"
#include <QFile> #include <QFile>
...@@ -31,6 +32,10 @@ void GameLauncher::start(bool disableGameLog, QString arch, bool hasVerifiedLice ...@@ -31,6 +32,10 @@ void GameLauncher::start(bool disableGameLog, QString arch, bool hasVerifiedLice
QStringList args; QStringList args;
QStringList cargs; QStringList cargs;
QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
#ifdef __APPLE__
if (!LauncherSettings().retinaRendering())
env.insert("ALARCADE_RETINA", "0");
#endif
if (m_gameDir.length() > 0) { if (m_gameDir.length() > 0) {
args.append("-dg"); args.append("-dg");
args.append(m_gameDir); args.append(m_gameDir);
......
...@@ -32,6 +32,7 @@ class LauncherSettings : public QObject { ...@@ -32,6 +32,7 @@ class LauncherSettings : public QObject {
Q_PROPERTY(bool trialMode READ trialMode WRITE setTrialMode NOTIFY settingsChanged) Q_PROPERTY(bool trialMode READ trialMode WRITE setTrialMode NOTIFY settingsChanged)
Q_PROPERTY(bool welcomeDone READ welcomeDone WRITE setWelcomeDone NOTIFY settingsChanged) Q_PROPERTY(bool welcomeDone READ welcomeDone WRITE setWelcomeDone NOTIFY settingsChanged)
Q_PROPERTY(QString windowsEdition READ windowsEdition WRITE setWindowsEdition NOTIFY settingsChanged) Q_PROPERTY(QString windowsEdition READ windowsEdition WRITE setWindowsEdition NOTIFY settingsChanged)
Q_PROPERTY(bool retinaRendering READ retinaRendering WRITE setRetinaRendering NOTIFY settingsChanged)
private: private:
QSettings settings; QSettings settings;
...@@ -113,6 +114,10 @@ public: ...@@ -113,6 +114,10 @@ public:
bool welcomeDone() const { return settings.value("welcomeDone", false).toBool(); } bool welcomeDone() const { return settings.value("welcomeDone", false).toBool(); }
void setWelcomeDone(bool value) { settings.setValue("welcomeDone", value); emit settingsChanged(); } void setWelcomeDone(bool value) { settings.setValue("welcomeDone", value); emit settingsChanged(); }
// macOS engine: render at full Retina resolution (off = 1x, faster).
bool retinaRendering() const { return settings.value("retinaRendering", true).toBool(); }
void setRetinaRendering(bool value) { settings.setValue("retinaRendering", value); emit settingsChanged(); }
// Windows backend: "release" or "preview" // Windows backend: "release" or "preview"
QString windowsEdition() const { return settings.value("windowsEdition", "release").toString(); } QString windowsEdition() const { return settings.value("windowsEdition", "release").toString(); }
void setWindowsEdition(QString value) { settings.setValue("windowsEdition", value); emit settingsChanged(); } void setWindowsEdition(QString value) { settings.setValue("windowsEdition", value); emit settingsChanged(); }
......
...@@ -24,9 +24,9 @@ BaseScreen { ...@@ -24,9 +24,9 @@ BaseScreen {
padding: 15 padding: 15
textFormat: TextEdit.RichText textFormat: TextEdit.RichText
text: "<b>Welcome to the new Minecraft Linux Launcher Update</b><br/><br/>" + LAUNCHER_CHANGE_LOG text: "<b>Welcome to the new Minecraft Linux Launcher Update</b><br/><br/>" + LAUNCHER_CHANGE_LOG
color: "#fff" color: Theme.text
readOnly: true readOnly: true
font.pointSize: 10 font.pixelSize: Theme.fontBody
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
selectByMouse: true selectByMouse: true
} }
...@@ -35,7 +35,7 @@ BaseScreen { ...@@ -35,7 +35,7 @@ BaseScreen {
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumHeight: pbutton.height + 10 * 2 Layout.minimumHeight: pbutton.height + 10 * 2
color: "#242424" color: Theme.background
MButton { MButton {
id: pbutton id: pbutton
text: qsTr("Continue") text: qsTr("Continue")
......
...@@ -2,7 +2,8 @@ import QtQuick ...@@ -2,7 +2,8 @@ import QtQuick
import QtQuick.Templates as T import QtQuick.Templates as T
// Navigation entry in the sidebar. Collapses to an icon with a tooltip when // Navigation entry in the sidebar. Collapses to an icon with a tooltip when
// `showText` is false. // `showText` is false. `highlighted` tints an entry that is an action rather
// than a page (no current-page marker).
T.Button { T.Button {
id: control id: control
...@@ -19,7 +20,7 @@ T.Button { ...@@ -19,7 +20,7 @@ T.Button {
background: Rectangle { background: Rectangle {
radius: Theme.radius radius: Theme.radius
color: control.checked ? Theme.accentSoft : control.down ? Theme.surfacePressed : control.hovered ? Theme.surfaceHover : "transparent" color: control.checked || control.highlighted ? Theme.accentSoft : control.down ? Theme.surfacePressed : control.hovered ? Theme.surfaceHover : "transparent"
Behavior on color { Behavior on color {
ColorAnimation { duration: Theme.durationFast } ColorAnimation { duration: Theme.durationFast }
...@@ -55,16 +56,16 @@ T.Button { ...@@ -55,16 +56,16 @@ T.Button {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
iconName: control.iconName iconName: control.iconName
size: 18 size: 18
color: control.checked ? Theme.accentText : control.hovered ? Theme.text : Theme.textSecondary color: control.checked || control.highlighted ? Theme.accentText : control.hovered ? Theme.text : Theme.textSecondary
} }
Text { Text {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: control.showText visible: control.showText
text: control.text text: control.text
color: control.checked ? Theme.text : control.hovered ? Theme.text : Theme.textSecondary color: control.checked || control.highlighted || control.hovered ? Theme.text : Theme.textSecondary
font.pixelSize: Theme.fontBody font.pixelSize: Theme.fontBody
font.weight: control.checked ? Font.DemiBold : Font.Medium font.weight: control.checked || control.highlighted ? Font.DemiBold : Font.Medium
} }
} }
......
...@@ -67,6 +67,7 @@ QtObject { ...@@ -67,6 +67,7 @@ QtObject {
readonly property int fontBody: 14 readonly property int fontBody: 14
readonly property int fontSmall: 12 readonly property int fontSmall: 12
readonly property int fontCaption: 11 readonly property int fontCaption: 11
readonly property string fontMono: Qt.platform.os === "osx" ? "Menlo" : Qt.platform.os === "windows" ? "Consolas" : "monospace"
// Spacing (4 px grid) // Spacing (4 px grid)
readonly property int space1: 4 readonly property int space1: 4
......
...@@ -26,8 +26,8 @@ BaseScreen { ...@@ -26,8 +26,8 @@ BaseScreen {
Layout.margins: 15 Layout.margins: 15
textFormat: TextEdit.RichText textFormat: TextEdit.RichText
text: errorScreen.message text: errorScreen.message
font.pointSize: 10 font.pixelSize: Theme.fontBody
color: "#fff" color: Theme.text
readOnly: true readOnly: true
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
selectByMouse: true selectByMouse: true
...@@ -37,7 +37,7 @@ BaseScreen { ...@@ -37,7 +37,7 @@ BaseScreen {
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumHeight: pbutton.height + 10 * 2 Layout.minimumHeight: pbutton.height + 10 * 2
color: "#242424" color: Theme.background
MButton { MButton {
id: pbutton id: pbutton
......
...@@ -48,7 +48,7 @@ ColumnLayout { ...@@ -48,7 +48,7 @@ ColumnLayout {
Layout.fillHeight: true Layout.fillHeight: true
Layout.columnSpan: articleImage.ratio > 1.5 ? 2 : 1 Layout.columnSpan: articleImage.ratio > 1.5 ? 2 : 1
Layout.rowSpan: articleImage.ratio < 0.5 ? 2 : 1 Layout.rowSpan: articleImage.ratio < 0.5 ? 2 : 1
color: "#222" color: Theme.surface
Image { Image {
id: articleImage id: articleImage
...@@ -66,7 +66,7 @@ ColumnLayout { ...@@ -66,7 +66,7 @@ ColumnLayout {
width: parent.width width: parent.width
height: descriptionContent.height height: descriptionContent.height
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
color: "#111" color: Theme.background
Column { Column {
id: descriptionContent id: descriptionContent
width: parent.width width: parent.width
...@@ -75,16 +75,16 @@ ColumnLayout { ...@@ -75,16 +75,16 @@ ColumnLayout {
Text { Text {
text: name text: name
width: parent.width - 2 * parent.padding width: parent.width - 2 * parent.padding
color: "#fff" color: Theme.text
font.pointSize: 13 font.pixelSize: Theme.fontHeading
font.weight: Font.Bold font.weight: Font.Bold
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
Text { Text {
text: description text: description
width: parent.width - 2 * parent.padding width: parent.width - 2 * parent.padding
color: "#bbb" color: Theme.textSecondary
font.pointSize: 10 font.pixelSize: Theme.fontBody
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
} }
......
...@@ -50,7 +50,7 @@ ColumnLayout { ...@@ -50,7 +50,7 @@ ColumnLayout {
if (section.revealEmail || mail.length < 4) if (section.revealEmail || mail.length < 4)
return mail return mail
const at = mail.indexOf("@") const at = mail.indexOf("@")
return mail.substring(0, 2) + "•".repeat(Math.max(3, (at > 2 ? at : mail.length) - 2)) + (at > 0 ? mail.substring(at) : "") return mail.substring(0, 2) + "*".repeat(Math.max(3, (at > 2 ? at : mail.length) - 2)) + (at > 0 ? mail.substring(at) : "")
} }
color: Theme.textMuted color: Theme.textMuted
font.pixelSize: Theme.fontSmall + 1 font.pixelSize: Theme.fontSmall + 1
...@@ -132,6 +132,24 @@ ColumnLayout { ...@@ -132,6 +132,24 @@ ColumnLayout {
} }
} }
SectionTitle {
Layout.topMargin: Theme.space2
text: qsTr("Performance")
}
Card {
Layout.fillWidth: true
visible: Platform.isMac
SettingRow {
width: parent.width
title: qsTr("High-resolution rendering")
description: qsTr("Draws the game at full Retina resolution. Turn it off for a smoother frame rate, especially on M1 and M2 Macs. Takes effect the next time the game starts.")
MSwitch {
checked: launcherSettings.retinaRendering
onToggled: launcherSettings.retinaRendering = checked
}
}
}
SectionTitle { SectionTitle {
Layout.topMargin: Theme.space2 Layout.topMargin: Theme.space2
text: qsTr("Troubleshooting") text: qsTr("Troubleshooting")
......
...@@ -15,7 +15,7 @@ Window { ...@@ -15,7 +15,7 @@ Window {
flags: Qt.Dialog flags: Qt.Dialog
title: "Minecraft .apk import" title: "Minecraft .apk import"
visible: apkImportHelper.extractingApk visible: apkImportHelper.extractingApk
color: "#333" color: Theme.surfaceRaised
property bool allowIncompatible: false property bool allowIncompatible: false
......
...@@ -10,70 +10,42 @@ ColumnLayout { ...@@ -10,70 +10,42 @@ ColumnLayout {
spacing: 0 spacing: 0
property var launcher: null property var launcher: null
BaseHeader { function copyLog() {
title: qsTr("Game Log")
MButton {
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: 10
height: 34
width: 34
onClicked: {
var text = "" var text = ""
for (var i = 0; i < gameLog.count; i++) { for (var i = 0; i < gameLog.count; i++)
text += gameLog.get(i).display + "\n" text += gameLog.get(i).display + "\n"
}
launcherSettings.clipboard = text launcherSettings.clipboard = text
window.notify(qsTr("Copied %n line(s) of the game log.", "", gameLog.count), "success")
} }
Image {
height: 18 BaseHeader {
width: 18 title: qsTr("Game Log")
anchors.centerIn: parent actions: [
source: "qrc:/Resources/icon-copy.png" MButton {
smooth: false text: qsTr("Copy log")
} iconName: "copy"
enabled: gameLog.count > 0
onClicked: layout.copyLog()
} }
]
} }
Rectangle { ColumnLayout {
id: rectangle
property int padding: 20
color: "#821"
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: children[0].implicitHeight + padding * 2 Layout.leftMargin: Theme.space8
Layout.alignment: Qt.AlignTop Layout.rightMargin: Theme.space8
Layout.bottomMargin: Theme.space4
visible: launcher.crashed visible: launcher.crashed
z: 2 spacing: Theme.space3
ColumnLayout { NotifyBanner {
x: rectangle.padding
y: rectangle.padding
width: parent.width - rectangle.padding * 2
MText {
Layout.fillWidth: true
Layout.bottomMargin: 4
wrapMode: Text.WordWrap
font.weight: Font.Bold
text: qsTr("Minecraft stopped working")
}
MText {
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: (launcherSettings.disableGameLog || incompatibleStateText.visible) ? qsTr("Please don't report this error.") : qsTr("Game has exited with a non-zero error code. <a href='%1'>Click here if you would like to open an issue.</a>").arg("https://github.com/minecraft-linux/mcpelauncher-manifest/issues/new/choose")
linkColor: hoveredLink ? "#acf" : "#9bf"
onLinkActivated: Qt.openUrlExternally(link)
HoverHandler {
enabled: parent.hoveredLink
cursorShape: Qt.PointingHandCursor
}
}
MText {
id: incompatibleStateText
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.WordWrap tone: "danger"
text: { dismissible: false
var msg = qsTr("You may be running an incompatible version. Uncheck the following toggles in settings and relaunch the game: ") title: qsTr("Minecraft stopped working")
description: {
if (launcherSettings.disableGameLog)
return qsTr("The game log is turned off. Turn it on in Settings > Game and play again to see what went wrong.")
var toggles = [] var toggles = []
if (launcherSettings.showUnsupported) if (launcherSettings.showUnsupported)
toggles.push(qsTr("Show incompatible versions")) toggles.push(qsTr("Show incompatible versions"))
...@@ -81,22 +53,36 @@ ColumnLayout { ...@@ -81,22 +53,36 @@ ColumnLayout {
toggles.push(qsTr("Show unverified versions")) toggles.push(qsTr("Show unverified versions"))
if (launcherSettings.showBetaVersions) if (launcherSettings.showBetaVersions)
toggles.push(qsTr("Show beta versions")) toggles.push(qsTr("Show beta versions"))
return msg + toggles.join(", ") + "." if (toggles.length > 0)
return qsTr("You may be running a version the launcher does not support. Turn off these settings and play again: %1.").arg(toggles.join(", "))
return qsTr("The game exited with an error. The log below shows what happened; copy it into a bug report.")
} }
visible: launcherSettings.showUnsupported || launcherSettings.showUnverified || launcherSettings.showBetaVersions actionText: gameLog.count > 0 ? qsTr("Copy log") : ""
onClicked: layout.copyLog()
} }
MText {
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: qsTr("Enable game log in settings and relaunch the game to get error log.")
visible: launcherSettings.disableGameLog
} }
// A wrapper that fills the page: EmptyState is a layout and cannot grow.
Item {
Layout.fillWidth: true
Layout.fillHeight: true
visible: gameLog.count === 0
EmptyState {
anchors.centerIn: parent
width: Math.min(parent.width - 2 * Theme.space8, 440)
iconName: "square-terminal"
title: qsTr("No game output yet")
description: qsTr("While Minecraft runs, the engine's log shows up here. It helps when something goes wrong: copy it into a bug report.")
} }
} }
ListView { ListView {
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: Theme.space8
Layout.rightMargin: Theme.space4
visible: gameLog.count > 0
id: view id: view
model: gameLog model: gameLog
delegate: TextArea { delegate: TextArea {
...@@ -108,10 +94,11 @@ ColumnLayout { ...@@ -108,10 +94,11 @@ ColumnLayout {
selectByMouse: false selectByMouse: false
selectByKeyboard: false selectByKeyboard: false
property int myIndex: index property int myIndex: index
color: "#ddd" color: Theme.text
background: null background: null
selectionColor: "#842" selectionColor: Theme.accent
font.family: "monospace" font.family: Theme.fontMono
font.pixelSize: Theme.fontSmall
Connections { Connections {
target: selectionArea target: selectionArea
......
...@@ -12,7 +12,7 @@ ApplicationWindow { ...@@ -12,7 +12,7 @@ ApplicationWindow {
minimumWidth: 400 minimumWidth: 400
minimumHeight: 300 minimumHeight: 300
title: qsTr("Gamepad Tool") title: qsTr("Gamepad Tool")
color: "#333" color: Theme.surfaceRaised
property var currentGamepad: GamepadManager.gamepads.length > 0 ? GamepadManager.gamepads[0] : null property var currentGamepad: GamepadManager.gamepads.length > 0 ? GamepadManager.gamepads[0] : null
...@@ -23,7 +23,7 @@ ApplicationWindow { ...@@ -23,7 +23,7 @@ ApplicationWindow {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: qsTr("No Gamepads Found!") text: qsTr("No Gamepads Found!")
font.bold: true font.bold: true
font.pointSize: 12 font.pixelSize: Theme.fontHeading
} }
MText { MText {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
...@@ -80,7 +80,7 @@ ApplicationWindow { ...@@ -80,7 +80,7 @@ ApplicationWindow {
Layout.fillWidth: true Layout.fillWidth: true
readOnly: true readOnly: true
text: currentGamepad ? currentGamepad.guid : "" text: currentGamepad ? currentGamepad.guid : ""
color: "#888" color: Theme.textMuted
} }
} }
ColumnLayout { ColumnLayout {
...@@ -93,7 +93,7 @@ ApplicationWindow { ...@@ -93,7 +93,7 @@ ApplicationWindow {
MTextField { MTextField {
Layout.fillWidth: true Layout.fillWidth: true
readOnly: true readOnly: true
color: "#888" color: Theme.textMuted
text: currentGamepad && currentGamepad.hasMapping ? "True" : "False" text: currentGamepad && currentGamepad.hasMapping ? "True" : "False"
} }
} }
...@@ -113,8 +113,8 @@ ApplicationWindow { ...@@ -113,8 +113,8 @@ ApplicationWindow {
model: ["a", "b", "x", "y", "leftshoulder", "rightshoulder", "righttrigger", "lefttrigger", "back", "start", "leftstick", "rightstick", "guide", "dpleft", "dpdown", "dpright", "dpup", "leftx", "lefty", "rightx", "righty"] model: ["a", "b", "x", "y", "leftshoulder", "rightshoulder", "righttrigger", "lefttrigger", "back", "start", "leftstick", "rightstick", "guide", "dpleft", "dpdown", "dpright", "dpup", "leftx", "lefty", "rightx", "righty"]
Rectangle { Rectangle {
id: field id: field
color: "#222" color: Theme.surface
border.color: "#444" border.color: Theme.border
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumHeight: 48 Layout.minimumHeight: 48
...@@ -137,7 +137,7 @@ ApplicationWindow { ...@@ -137,7 +137,7 @@ ApplicationWindow {
} }
MText { MText {
text: field.waiting ? qsTr("Waiting") : field.key.toUpperCase() text: field.waiting ? qsTr("Waiting") : field.key.toUpperCase()
color: field.waiting ? "#888" : "#af8" color: field.waiting ? Theme.textMuted : Theme.success
Layout.rightMargin: 5 Layout.rightMargin: 5
} }
MButton { MButton {
...@@ -212,7 +212,7 @@ ApplicationWindow { ...@@ -212,7 +212,7 @@ ApplicationWindow {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 40 Layout.preferredHeight: 40
readOnly: true readOnly: true
color: "#888" color: Theme.textMuted
text: { text: {
if (!currentGamepad) if (!currentGamepad)
return "" return ""
...@@ -252,7 +252,7 @@ ApplicationWindow { ...@@ -252,7 +252,7 @@ ApplicationWindow {
footer: Rectangle { footer: Rectangle {
height: pbutton.height + 20 height: pbutton.height + 20
color: "#242424" color: Theme.background
MButton { MButton {
id: pbutton id: pbutton
anchors.right: parent.right anchors.right: parent.right
......
...@@ -335,24 +335,6 @@ Item { ...@@ -335,24 +335,6 @@ Item {
visible: (googleLoginHelper.account !== null) && launcherLatestVersionBase().versionCode > playVerChannel.latestVersionCode && launcherSettings.showNotifications visible: (googleLoginHelper.account !== null) && launcherLatestVersionBase().versionCode > playVerChannel.latestVersionCode && launcherSettings.showNotifications
} }
NotifyBanner {
Layout.fillWidth: true
tone: "accent"
iconName: "download"
visible: hasUpdate && !(progressbarVisible || updateChecker.active) && launcherSettings.showNotifications
title: qsTr("Launcher update available")
description: qsTr("A newer %1 is ready to download.").arg(APP_NAME)
actionText: qsTr("Download")
onClicked: {
if (updateDownloadUrl.length == 0) {
updateCheckerConnectorBase.enabled = true
updateChecker.startUpdate()
} else {
Qt.openUrlExternally(updateDownloadUrl)
}
}
}
NotifyBanner { NotifyBanner {
Layout.fillWidth: true Layout.fillWidth: true
tone: "accent" tone: "accent"
......
...@@ -4,7 +4,8 @@ import QtQuick.Dialogs ...@@ -4,7 +4,8 @@ import QtQuick.Dialogs
import "../Components" import "../Components"
import io.mrarm.mcpelauncher 1.0 import io.mrarm.mcpelauncher 1.0
Item { // First-run Google Play sign-in for the Android backend.
Rectangle {
id: root id: root
property GoogleLoginHelper googleLoginHelper property GoogleLoginHelper googleLoginHelper
...@@ -14,142 +15,157 @@ Item { ...@@ -14,142 +15,157 @@ Item {
signal finished signal finished
Component.onCompleted: { color: Theme.background
launcherSettings.trialMode = false
} Component.onCompleted: launcherSettings.trialMode = false
Image { Image {
anchors.fill: parent anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
height: parent.height * 0.6
source: "qrc:/Resources/hero.png"
fillMode: Image.PreserveAspectCrop
smooth: false smooth: false
fillMode: Image.Tile opacity: 0.8
source: "qrc:/Resources/noise.png"
} }
Rectangle { Rectangle {
width: 420 anchors.fill: parent
height: container.implicitHeight + 70 gradient: Gradient {
GradientStop { position: 0.0; color: "#00000000" }
GradientStop { position: 0.3; color: Qt.rgba(0.055, 0.035, 0.08, 0.4) }
GradientStop { position: 0.62; color: Theme.background }
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
}
Card {
anchors.centerIn: parent anchors.centerIn: parent
radius: 4 width: Math.min(parent.width - 2 * Theme.space8, 460)
visible: !extractingApk padding: Theme.space8
color: "#222" visible: !root.extractingApk
ColumnLayout { ColumnLayout {
id: container width: parent.width
spacing: 10 spacing: Theme.space4
x: 35
y: 35
width: parent.width - 70
Image {
Layout.alignment: Qt.AlignHCenter
source: "qrc:/Resources/logo-mark.svg"
sourceSize: Qt.size(44, 44)
smooth: false
}
Text { Text {
text: qsTr("Sign in") Layout.fillWidth: true
font.pointSize: 16 text: qsTr("Sign in to Google Play")
font.bold: true color: Theme.text
color: "#fff" font.pixelSize: Theme.fontTitle
font.weight: Font.Bold
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter }
Text {
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("Use the Google account that bought Minecraft on Google Play. The launcher checks your purchase and downloads the Android edition from there.")
color: Theme.textSecondary
font.pixelSize: Theme.fontBody
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.Wrap
lineHeight: 1.15
} }
ColumnLayout {
Layout.fillWidth: true
Layout.topMargin: Theme.space1
spacing: Theme.space2
Repeater {
model: [
{ icon: "lock", text: qsTr("Your password goes only to Google.") },
{ icon: "key-round", text: Platform.isMac ? qsTr("The sign-in is stored encrypted, with its key in your Keychain.") : qsTr("The sign-in is stored encrypted on this computer.") },
]
RowLayout {
required property var modelData
Layout.fillWidth: true
spacing: Theme.space2
AppIcon {
iconName: modelData.icon
size: 16
color: Theme.accentText
}
Text { Text {
text: qsTr("To use this launcher, you must either purchase Minecraft Android or claim the free Minecraft Trial on Google Play and sign in.\n\nThe free Minecraft (ChromeOS) Trial Mode can be enabled after sign in inside Settings.")
wrapMode: Text.WordWrap
font.pointSize: 11
color: "#fff"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
Layout.alignment: Qt.AlignTop
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 10 text: modelData.text
color: Theme.textMuted
font.pixelSize: Theme.fontSmall + 1
wrapMode: Text.Wrap
}
}
}
} }
PlayButton { MButton {
text: qsTr("Sign in with Google")
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 10 Layout.topMargin: Theme.space2
onClicked: function () { kind: "primary"
acquiringAccount = true large: true
text: qsTr("Sign in with Google")
iconName: "log-in"
enabled: !root.acquiringAccount
onClicked: {
root.acquiringAccount = true
googleLoginHelper.acquireAccount(window) googleLoginHelper.acquireAccount(window)
} }
} }
RowLayout { RowLayout {
id: alternativeOptions
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
spacing: 15 spacing: Theme.space2
TransparentButton { TransparentButton {
text: qsTr("Use trial .apk").toUpperCase() text: qsTr("Use the free trial")
textColor: "#0aa82f"
Layout.fillWidth: true
font.pointSize: 11
onClicked: apkImportHelper.pickFile() onClicked: apkImportHelper.pickFile()
} }
TransparentButton { TransparentButton {
text: qsTr("Get help").toUpperCase() text: qsTr("Not now")
textColor: "#0aa82f" onClicked: root.finished()
Layout.fillWidth: true
font.pointSize: 11
onClicked: Qt.openUrlExternally("https://minecraft-linux.github.io")
} }
} }
} }
} }
Rectangle { Card {
width: 400 anchors.centerIn: parent
height: extractContainer.height width: Math.min(parent.width - 2 * Theme.space8, 420)
visible: extractingApk visible: root.extractingApk
color: "#222"
ColumnLayout { ColumnLayout {
id: extractContainer
spacing: 0
width: parent.width width: parent.width
spacing: Theme.space3
Text { Text {
text: qsTr("Extracting apk") text: qsTr("Unpacking the trial")
color: "#fff" color: Theme.text
font.pointSize: 15 font.pixelSize: Theme.fontHeading
font.bold: true font.weight: Font.DemiBold
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
Layout.alignment: Qt.AlignTop
Layout.fillWidth: true
Layout.topMargin: 15
} }
MProgressBar { MProgressBar {
id: apkExtractionProgressBar id: apkExtractionProgressBar
indeterminate: true
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: 15 indeterminate: true
Layout.preferredHeight: 20
} }
} }
} }
Text { Text {
text: qsTr("This is an unofficial Linux launcher for the Minecraft Bedrock codebase.\nThis project is not affiliated with Minecraft, Mojang or Microsoft.")
color: "#fff"
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.bottomMargin: 10 anchors.bottomMargin: Theme.space5
width: parent.width anchors.horizontalCenter: parent.horizontalCenter
wrapMode: Text.WordWrap width: Math.min(parent.width - 2 * Theme.space8, 560)
font.pointSize: 10 text: APP_DISCLAIMER
color: Theme.textMuted
font.pixelSize: Theme.fontCaption
font.letterSpacing: 0.3
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter wrapMode: Text.Wrap
}
Rectangle {
anchors.fill: parent
color: "#000"
opacity: 0.3
visible: acquiringAccount
MouseArea {
anchors.fill: parent
hoverEnabled: true
}
} }
ApkImportHelper { ApkImportHelper {
...@@ -178,16 +194,9 @@ Item { ...@@ -178,16 +194,9 @@ Item {
Connections { Connections {
target: googleLoginHelper target: googleLoginHelper
function onAccountAcquireFinished(acc) { function onAccountAcquireFinished(acc) {
acquiringAccount = false root.acquiringAccount = false
if (acc) if (acc)
root.finished() root.finished()
} }
} }
Connections {
target: window
function onClosing() {
application.quit()
}
}
} }
...@@ -33,7 +33,7 @@ ScrollView { ...@@ -33,7 +33,7 @@ ScrollView {
Layout.fillHeight: true Layout.fillHeight: true
Layout.columnSpan: 1 Layout.columnSpan: 1
Layout.rowSpan: 1 Layout.rowSpan: 1
color: "#222" color: Theme.surface
height: iconImage.height + 20 height: iconImage.height + 20
Item { Item {
...@@ -61,8 +61,8 @@ ScrollView { ...@@ -61,8 +61,8 @@ ScrollView {
text: modelData.name text: modelData.name
width: parent.width width: parent.width
font.bold: true font.bold: true
color: "#fff" color: Theme.text
font.pointSize: 13 font.pixelSize: Theme.fontHeading
font.weight: Font.Bold font.weight: Font.Bold
wrapMode: Text.Wrap wrapMode: Text.Wrap
elide: Text.ElideRight elide: Text.ElideRight
...@@ -73,8 +73,8 @@ ScrollView { ...@@ -73,8 +73,8 @@ ScrollView {
text: modelData.description text: modelData.description
width: parent.width width: parent.width
height: parent.height - titleText.height - 20 height: parent.height - titleText.height - 20
color: "#bbb" color: Theme.textSecondary
font.pointSize: 10 font.pixelSize: Theme.fontBody
wrapMode: Text.Wrap wrapMode: Text.Wrap
elide: Text.ElideRight elide: Text.ElideRight
} }
......
...@@ -86,7 +86,7 @@ AnimatedStackLayout { ...@@ -86,7 +86,7 @@ AnimatedStackLayout {
MText { MText {
text: qsTr("Welcome to our experimental mods section.") text: qsTr("Welcome to our experimental mods section.")
font.bold: true font.bold: true
font.pointSize: 12 font.pixelSize: Theme.fontHeading
} }
TextEdit { TextEdit {
focus: true focus: true
...@@ -94,8 +94,8 @@ AnimatedStackLayout { ...@@ -94,8 +94,8 @@ AnimatedStackLayout {
textFormat: Text.RichText textFormat: Text.RichText
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
text: "<style type=\"text/css\">a { color: #6af; }</style>" + qsTr("Managing mods is not yet supported. To contribute your mod, please open a pull request on <a href=\"https://github.com/minecraft-linux/mcpelauncher-moddb\">minecraft-linux/mcpelauncher-moddb</a>.<br/><br/>Mods are a collection of .so files (also on macOS) placed inside the `mods` folder. This folder is located within your data root, which you can find in Settings > Storage. The `mods` folder does not exist by default, so you'll need to create it. Extract zip files directly into the `mods` folder without creating subfolders. .so files should be directly below the `mods` folder.<br/><br/><font color=\"#f66\">Do not report crashes to the launcher's issue tracker when mods are enabled.</font>") text: "<style type=\"text/css\">a { color: #6af; }</style>" + qsTr("Managing mods is not yet supported. To contribute your mod, please open a pull request on <a href=\"https://github.com/minecraft-linux/mcpelauncher-moddb\">minecraft-linux/mcpelauncher-moddb</a>.<br/><br/>Mods are a collection of .so files (also on macOS) placed inside the `mods` folder. This folder is located within your data root, which you can find in Settings > Storage. The `mods` folder does not exist by default, so you'll need to create it. Extract zip files directly into the `mods` folder without creating subfolders. .so files should be directly below the `mods` folder.<br/><br/><font color=\"#f66\">Do not report crashes to the launcher's issue tracker when mods are enabled.</font>")
font.pointSize: 10 font.pixelSize: Theme.fontBody
color: "#fff" color: Theme.text
readOnly: true readOnly: true
selectByMouse: true selectByMouse: true
onLinkActivated: Qt.openUrlExternally(link) onLinkActivated: Qt.openUrlExternally(link)
...@@ -175,7 +175,7 @@ AnimatedStackLayout { ...@@ -175,7 +175,7 @@ AnimatedStackLayout {
} }
MText { MText {
font.bold: true font.bold: true
font.pointSize: 17 font.pixelSize: Theme.fontBody
text: qsTr("Mods") + " / " + (stack.elem && stack.elem.name || qsTr("Untitled Mod")) text: qsTr("Mods") + " / " + (stack.elem && stack.elem.name || qsTr("Untitled Mod"))
} }
} }
...@@ -203,7 +203,7 @@ AnimatedStackLayout { ...@@ -203,7 +203,7 @@ AnimatedStackLayout {
MText { MText {
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: TextEdit.Wrap wrapMode: TextEdit.Wrap
color: "#bbb" color: Theme.textSecondary
text: stack.elem && stack.elem.url || "" text: stack.elem && stack.elem.url || ""
} }
...@@ -218,7 +218,7 @@ AnimatedStackLayout { ...@@ -218,7 +218,7 @@ AnimatedStackLayout {
readOnly: true readOnly: true
selectByMouse: true selectByMouse: true
onLinkActivated: Qt.openUrlExternally(link) onLinkActivated: Qt.openUrlExternally(link)
font.pointSize: 10 font.pixelSize: Theme.fontBody
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
...@@ -244,7 +244,7 @@ AnimatedStackLayout { ...@@ -244,7 +244,7 @@ AnimatedStackLayout {
delegate: ItemDelegate { delegate: ItemDelegate {
id: control id: control
width: parent.width width: parent.width
font.pointSize: 11 font.pixelSize: Theme.fontBody
contentItem: RowLayout { contentItem: RowLayout {
MText { MText {
text: modelData.version text: modelData.version
...@@ -321,7 +321,7 @@ AnimatedStackLayout { ...@@ -321,7 +321,7 @@ AnimatedStackLayout {
onClicked: downloads.currentIndex = index onClicked: downloads.currentIndex = index
highlighted: ListView.isCurrentItem highlighted: ListView.isCurrentItem
background: Rectangle { background: Rectangle {
color: control.highlighted ? "#226322" : (control.down ? "#338833" : (control.hovered ? "#222" : "transparent")) color: control.highlighted ? Theme.accentSoft : (control.down ? Theme.surfacePressed : (control.hovered ? Theme.surfaceHover : "transparent"))
} }
} }
highlightResizeVelocity: -1 highlightResizeVelocity: -1
......
...@@ -55,7 +55,7 @@ MComboBox { ...@@ -55,7 +55,7 @@ MComboBox {
id: separator id: separator
width: parent.width width: parent.width
height: 1 height: 1
color: "#444" color: Theme.border
visible: hasSeparator visible: hasSeparator
radius: 2 radius: 2
} }
...@@ -66,14 +66,14 @@ MComboBox { ...@@ -66,14 +66,14 @@ MComboBox {
anchors.rightMargin: parent.padding anchors.rightMargin: parent.padding
anchors.topMargin: (separator.visible ? separator.height : 0) anchors.topMargin: (separator.visible ? separator.height : 0)
text: modelData text: modelData
font.pointSize: 10 font.pixelSize: Theme.fontBody
color: "#fff" color: Theme.text
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
background: Rectangle { background: Rectangle {
anchors.fill: parent anchors.fill: parent
color: highlighted ? "#283" : (parent.hovered ? "#333" : "#1e1e1e") color: highlighted ? Theme.accentSoft : (parent.hovered ? Theme.surfaceRaised : Theme.surface)
} }
} }
......
...@@ -23,12 +23,15 @@ Popup { ...@@ -23,12 +23,15 @@ Popup {
focus: true focus: true
background: Rectangle { background: Rectangle {
color: "#333" color: Theme.surface
radius: Theme.radiusXL
border.width: 1
border.color: Theme.borderStrong
} }
Overlay.modal: Rectangle { Overlay.modal: Rectangle {
id: popupOverlay id: popupOverlay
color: "#8f181818" color: Theme.overlay
} }
enter: Transition { enter: Transition {
...@@ -80,17 +83,18 @@ Popup { ...@@ -80,17 +83,18 @@ Popup {
BaseHeader { BaseHeader {
id: baseHeader id: baseHeader
title: profile == null ? qsTr("Create profile") : qsTr("Edit profile") title: profile == null ? qsTr("Create profile") : qsTr("Edit profile")
actions: [
MButton { MButton {
kind: "danger"
text: qsTr("Delete") text: qsTr("Delete")
anchors.verticalCenter: parent.verticalCenter iconName: "trash-2"
anchors.right: parent.right
anchors.rightMargin: 10
visible: profile !== null && profile !== profileManager.defaultProfile visible: profile !== null && profile !== profileManager.defaultProfile
onClicked: { onClicked: {
profileManager.deleteProfile(profile) profileManager.deleteProfile(profile)
close() close()
} }
} }
]
} }
ScrollView { ScrollView {
...@@ -353,7 +357,7 @@ Popup { ...@@ -353,7 +357,7 @@ Popup {
} }
MText { MText {
text: "x" text: "x"
font.pointSize: 11 font.pixelSize: Theme.fontBody
opacity: windowSizeCheck.checked ? 1.0 : 0.3 opacity: windowSizeCheck.checked ? 1.0 : 0.3
} }
MTextField { MTextField {
...@@ -386,8 +390,11 @@ Popup { ...@@ -386,8 +390,11 @@ Popup {
verticalAlignment: Text.AlignTop verticalAlignment: Text.AlignTop
} }
Rectangle { Rectangle {
color: "#222" color: Theme.surfaceRaised
Layout.preferredHeight: 120 radius: Theme.radius
border.width: 1
border.color: Theme.border
Layout.preferredHeight: 140
Layout.fillWidth: true Layout.fillWidth: true
ListView { ListView {
id: envs id: envs
...@@ -403,7 +410,7 @@ Popup { ...@@ -403,7 +410,7 @@ Popup {
}) })
} }
delegate: Rectangle { delegate: Rectangle {
color: "#222" color: "transparent"
width: parent.width width: parent.width
height: keyField.implicitHeight height: keyField.implicitHeight
MTextField { MTextField {
...@@ -433,29 +440,22 @@ Popup { ...@@ -433,29 +440,22 @@ Popup {
}) })
} }
} }
MButton { IconButton {
visible: !add visible: !add
id: del id: del
height: parent.height height: parent.height
anchors.right: parent.right anchors.right: parent.right
Image { iconName: "trash-2"
height: 20 tooltip: qsTr("Remove variable")
width: 20 onClicked: envs.model.remove(index, 1)
anchors.centerIn: parent
source: "qrc:/Resources/icon-delete.png"
smooth: false
}
onClicked: {
console.log(index)
envs.model.remove(index, 1)
}
} }
MButton { MButton {
visible: !!add visible: !!add
width: parent.width width: parent.width
text: qsTr("Add New Variable") kind: "ghost"
iconName: "plus"
text: qsTr("Add variable")
onClicked: { onClicked: {
console.log(index)
envs.model.insert(envs.model.count - 1, { envs.model.insert(envs.model.count - 1, {
"key": "", "key": "",
"value": "" "value": ""
...@@ -466,7 +466,7 @@ Popup { ...@@ -466,7 +466,7 @@ Popup {
highlightResizeVelocity: -1 highlightResizeVelocity: -1
highlightMoveVelocity: -1 highlightMoveVelocity: -1
currentIndex: -1 currentIndex: -1
ScrollBar.vertical: ScrollBar {} ScrollBar.vertical: MScrollBar {}
} }
} }
} }
...@@ -474,11 +474,11 @@ Popup { ...@@ -474,11 +474,11 @@ Popup {
RowLayout { RowLayout {
id: buttons id: buttons
Layout.topMargin: 15 Layout.topMargin: 15
TransparentButton { MButton {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
text: advancedColumn.visible ? qsTr("Collapse advanced 🞁") : qsTr("Expand advanced 🞃") kind: "ghost"
textColor: hovered ? "#ccc" : "#888" iconName: advancedColumn.visible ? "chevron-up" : "chevron-down"
font.bold: true text: advancedColumn.visible ? qsTr("Hide advanced options") : qsTr("Show advanced options")
onClicked: { onClicked: {
advancedColumn.visible = !advancedColumn.visible advancedColumn.visible = !advancedColumn.visible
advancedColumn.Layout.preferredHeight = advancedColumn.height advancedColumn.Layout.preferredHeight = advancedColumn.height
...@@ -503,6 +503,7 @@ Popup { ...@@ -503,6 +503,7 @@ Popup {
} }
} }
MButton { MButton {
kind: "primary"
text: qsTr("Save") text: qsTr("Save")
enabled: !profileName.enabled || profileName.text.length > 0 enabled: !profileName.enabled || profileName.text.length > 0
onClicked: { onClicked: {
......
...@@ -13,7 +13,7 @@ Window { ...@@ -13,7 +13,7 @@ Window {
width: 640 width: 640
height: 480 height: 480
title: qsTr("Request Google Credentials") title: qsTr("Request Google Credentials")
color: "#333333" color: Theme.background
property bool hasUpdate: false property bool hasUpdate: false
property bool hasAskedForKey: false property bool hasAskedForKey: false
property string updateDownloadUrl: "" property string updateDownloadUrl: ""
......
...@@ -4,13 +4,13 @@ import "../Components" ...@@ -4,13 +4,13 @@ import "../Components"
import io.mrarm.mcpelauncher 1.0 import io.mrarm.mcpelauncher 1.0
ColumnLayout { ColumnLayout {
property color labelColor: "#fff" property color labelColor: Theme.text
property int labelFontSize: 10 property int labelFontSize: 10
width: parent.width width: parent.width
spacing: 10 spacing: 10
Rectangle { Rectangle {
color: "#30ff8000" color: Theme.warningSoft
Layout.fillWidth: true Layout.fillWidth: true
Layout.bottomMargin: 15 Layout.bottomMargin: 15
Layout.minimumHeight: warningText.height Layout.minimumHeight: warningText.height
......
...@@ -51,7 +51,7 @@ ColumnLayout { ...@@ -51,7 +51,7 @@ ColumnLayout {
MText { MText {
text: qsTr("Game Directories") text: qsTr("Game Directories")
font.bold: true font.bold: true
font.pointSize: 11 font.pixelSize: Theme.fontBody
Layout.topMargin: 10 Layout.topMargin: 10
} }
...@@ -83,7 +83,7 @@ ColumnLayout { ...@@ -83,7 +83,7 @@ ColumnLayout {
MText { MText {
text: qsTr("Default Game Directories") text: qsTr("Default Game Directories")
font.bold: true font.bold: true
font.pointSize: 11 font.pixelSize: Theme.fontBody
Layout.topMargin: 10 Layout.topMargin: 10
Layout.bottomMargin: 10 Layout.bottomMargin: 10
} }
...@@ -118,7 +118,7 @@ ColumnLayout { ...@@ -118,7 +118,7 @@ ColumnLayout {
Layout.fillHeight: true Layout.fillHeight: true
text: QmlUrlUtils.urlToLocalFile(modelData.path) text: QmlUrlUtils.urlToLocalFile(modelData.path)
readOnly: true readOnly: true
color: "#aaa" color: Theme.textSecondary
hoverEnabled: false hoverEnabled: false
} }
MButton { MButton {
......
...@@ -16,7 +16,7 @@ ColumnLayout { ...@@ -16,7 +16,7 @@ ColumnLayout {
Popup { Popup {
id: downloadApk id: downloadApk
background: Rectangle { background: Rectangle {
color: "#333" color: Theme.surfaceRaised
} }
height: Math.min(scope.implicitHeight + 20, layout.outerHeight - 20) height: Math.min(scope.implicitHeight + 20, layout.outerHeight - 20)
width: layout.width width: layout.width
...@@ -33,7 +33,7 @@ ColumnLayout { ...@@ -33,7 +33,7 @@ ColumnLayout {
Overlay.modal: Rectangle { Overlay.modal: Rectangle {
id: popupOverlay id: popupOverlay
color: "#8f181818" color: Theme.overlay
} }
ScrollView { ScrollView {
anchors.fill: parent anchors.fill: parent
...@@ -45,7 +45,7 @@ ColumnLayout { ...@@ -45,7 +45,7 @@ ColumnLayout {
width: downloadApk.width - 40 width: downloadApk.width - 40
Rectangle { Rectangle {
color: "#53ba1300" color: Theme.warningSoft
Layout.fillWidth: true Layout.fillWidth: true
Layout.bottomMargin: 15 Layout.bottomMargin: 15
Layout.minimumHeight: warningText.height Layout.minimumHeight: warningText.height
...@@ -54,7 +54,7 @@ ColumnLayout { ...@@ -54,7 +54,7 @@ ColumnLayout {
id: warningText id: warningText
padding: 10 padding: 10
text: qsTr("You’re most likely mistaken here — to download and install different versions, open the launcher’s Home page and click the Profile Edit pencil icon.") text: qsTr("You’re most likely mistaken here — to download and install different versions, open the launcher’s Home page and click the Profile Edit pencil icon.")
color: "#fff" color: Theme.text
font.pointSize: labelFontSize font.pointSize: labelFontSize
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
width: parent.width width: parent.width
...@@ -342,7 +342,7 @@ ColumnLayout { ...@@ -342,7 +342,7 @@ ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
Layout.minimumHeight: Math.max(window.height - 180, 150) Layout.minimumHeight: Math.max(window.height - 180, 150)
color: "#1e1e1e" color: Theme.surface
ListView { ListView {
id: versions id: versions
...@@ -357,17 +357,17 @@ ColumnLayout { ...@@ -357,17 +357,17 @@ ColumnLayout {
id: control id: control
width: parent.width width: parent.width
height: 32 height: 32
font.pointSize: 11 font.pixelSize: Theme.fontBody
contentItem: Text { contentItem: Text {
text: modelData.versionName + " (" + modelData.archs.join(", ") + ")" text: modelData.versionName + " (" + modelData.archs.join(", ") + ")"
color: "#fff" color: Theme.text
font.pointSize: 10 font.pixelSize: Theme.fontBody
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
onClicked: versions.currentIndex = index onClicked: versions.currentIndex = index
highlighted: ListView.isCurrentItem highlighted: ListView.isCurrentItem
background: Rectangle { background: Rectangle {
color: control.highlighted ? "#226322" : (control.down ? "#338833" : (control.hovered ? "#222" : "transparent")) color: control.highlighted ? Theme.accentSoft : (control.down ? Theme.surfacePressed : (control.hovered ? Theme.surfaceHover : "transparent"))
} }
} }
highlightResizeVelocity: -1 highlightResizeVelocity: -1
......
...@@ -20,7 +20,7 @@ Window { ...@@ -20,7 +20,7 @@ Window {
property GoogleLoginHelper googleLoginHelper property GoogleLoginHelper googleLoginHelper
property GoogleVersionChannel playVerChannel property GoogleVersionChannel playVerChannel
property GooglePlayApi playApi property GooglePlayApi playApi
color: "#333333" color: Theme.background
ColumnLayout { ColumnLayout {
id: layout id: layout
...@@ -52,17 +52,17 @@ Window { ...@@ -52,17 +52,17 @@ Window {
text: modelData.shortDesc text: modelData.shortDesc
Layout.fillWidth: true Layout.fillWidth: true
font.bold: true font.bold: true
font.pointSize: 10 font.pixelSize: Theme.fontBody
color: "#fff" color: Theme.text
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
Text { Text {
text: modelData.longDesc text: modelData.longDesc
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
color: "#fff" color: Theme.text
linkColor: "#57f" linkColor: Theme.accentText
font.pointSize: 10 font.pixelSize: Theme.fontBody
onLinkActivated: Qt.openUrlExternally(link) onLinkActivated: Qt.openUrlExternally(link)
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
...@@ -74,11 +74,11 @@ Window { ...@@ -74,11 +74,11 @@ Window {
text: qsTr("<a href=\"%1\">Go to wiki</a>").arg(modelData.wikiUrl) text: qsTr("<a href=\"%1\">Go to wiki</a>").arg(modelData.wikiUrl)
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
color: "#fff" color: Theme.text
linkColor: "#57f" linkColor: Theme.accentText
onLinkActivated: Qt.openUrlExternally(link) onLinkActivated: Qt.openUrlExternally(link)
visible: modelData.wikiUrl.length > 0 visible: modelData.wikiUrl.length > 0
font.pointSize: 10 font.pixelSize: Theme.fontBody
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
......
...@@ -44,7 +44,7 @@ BaseScreen { ...@@ -44,7 +44,7 @@ BaseScreen {
Rectangle { Rectangle {
id: warning id: warning
opacity: 0 opacity: 0
color: "#30ff8000" color: Theme.warningSoft
Layout.fillWidth: true Layout.fillWidth: true
Layout.bottomMargin: 15 Layout.bottomMargin: 15
Layout.minimumHeight: warningText.height Layout.minimumHeight: warningText.height
...@@ -80,7 +80,7 @@ BaseScreen { ...@@ -80,7 +80,7 @@ BaseScreen {
enabled: !continueInvalidCredentials.checked enabled: !continueInvalidCredentials.checked
onAccepted: attemptUnlock() onAccepted: attemptUnlock()
onTextChanged: { onTextChanged: {
pwd.color = "#fff" pwd.color = Theme.text
} }
NumberAnimation on x { NumberAnimation on x {
id: shakeAnim id: shakeAnim
...@@ -95,7 +95,7 @@ BaseScreen { ...@@ -95,7 +95,7 @@ BaseScreen {
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumHeight: pbutton.height + 10 * 2 Layout.minimumHeight: pbutton.height + 10 * 2
color: "#242424" color: Theme.background
MButton { MButton {
id: pbutton id: pbutton
text: qsTr("Continue") text: qsTr("Continue")
...@@ -114,7 +114,7 @@ BaseScreen { ...@@ -114,7 +114,7 @@ BaseScreen {
warning.opacity = 1 warning.opacity = 1
warningAnim.restart() warningAnim.restart()
shakeAnim.restart() shakeAnim.restart()
pwd.color = "#f88" pwd.color = Theme.danger
pwd.forceActiveFocus() pwd.forceActiveFocus()
} }
} }
......
...@@ -26,8 +26,8 @@ BaseScreen { ...@@ -26,8 +26,8 @@ BaseScreen {
Layout.margins: 15 Layout.margins: 15
textFormat: TextEdit.RichText textFormat: TextEdit.RichText
text: qsTr("<b><font color=\"#f66\">Sorry your Computer cannot run Minecraft with this Launcher</font></b>, this CPU is too old.<br/><br/>Details:<br/>%1").arg(googleLoginHelper.GetSupportReport()) text: qsTr("<b><font color=\"#f66\">Sorry your Computer cannot run Minecraft with this Launcher</font></b>, this CPU is too old.<br/><br/>Details:<br/>%1").arg(googleLoginHelper.GetSupportReport())
font.pointSize: 10 font.pixelSize: Theme.fontBody
color: "#fff" color: Theme.text
readOnly: true readOnly: true
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
selectByMouse: true selectByMouse: true
...@@ -37,7 +37,7 @@ BaseScreen { ...@@ -37,7 +37,7 @@ BaseScreen {
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumHeight: pbutton.height + 10 * 2 Layout.minimumHeight: pbutton.height + 10 * 2
color: "#242424" color: Theme.background
MButton { MButton {
id: pbutton id: pbutton
......
...@@ -93,6 +93,7 @@ Window { ...@@ -93,6 +93,7 @@ Window {
onUpdateAvailable: function (downloadUrl) { onUpdateAvailable: function (downloadUrl) {
window.hasUpdate = true window.hasUpdate = true
window.updateDownloadUrl = downloadUrl window.updateDownloadUrl = downloadUrl
window.notify(qsTr("%1 %2 is available.").arg(APP_NAME).arg(updateChecker.latestVersion), "info")
} }
} }
...@@ -216,6 +217,16 @@ Window { ...@@ -216,6 +217,16 @@ Window {
Item { Layout.fillHeight: true } Item { Layout.fillHeight: true }
MSideBarItem {
Layout.fillWidth: true
visible: window.hasUpdate
text: updateChecker.latestVersion.length > 0 ? qsTr("Update to %1").arg(updateChecker.latestVersion) : qsTr("Update available")
iconName: "download"
showText: !sidebar.compact
highlighted: true
onClicked: updateChecker.startUpdate()
}
// Game status at a glance. // Game status at a glance.
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
......
...@@ -61,8 +61,9 @@ ColumnLayout { ...@@ -61,8 +61,9 @@ ColumnLayout {
onToggled: launcherSettings.showNotifications = checked onToggled: launcherSettings.showNotifications = checked
} }
} }
HorizontalDivider {} HorizontalDivider { visible: updateChecker.enabled }
SettingRow { SettingRow {
visible: updateChecker.enabled
title: qsTr("Check for launcher updates") title: qsTr("Check for launcher updates")
description: qsTr("Looks for a newer AL-Arcade MC Launcher when it starts.") description: qsTr("Looks for a newer AL-Arcade MC Launcher when it starts.")
MSwitch { MSwitch {
......
#include "updatechecker.h" #include "updatechecker.h"
#include <QDesktopServices>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkReply> #include <QNetworkReply>
#include <sstream> #include <QNetworkRequest>
#include <QObject> #include <QUrl>
UpdateChecker::UpdateChecker(QObject* parent) : QObject(parent) { #ifndef ALARCADE_UPDATE_FEED_URL
connect(&netAccessManager, &QNetworkAccessManager::finished, this, &UpdateChecker::onRequestFinished); #define ALARCADE_UPDATE_FEED_URL ""
m_active = false;
}
#ifndef SPARKLE_UPDATE_CHECK
void UpdateChecker::checkForUpdates() {
#ifdef APPIMAGE_UPDATE_CHECK
auto updater = this->updater;
auto oldthread = std::make_shared<std::thread>();
std::swap(*oldthread, updatethread);
updatethread = std::thread([this, updater, oldthread]() mutable {
try {
if (oldthread->joinable()) {
oldthread->join();
}
if (!updater) {
char * appimage = getenv("APPIMAGE");
if (appimage) {
#ifndef NDEBUG
printf("Appimage create updater\n");
#endif
this->updater = updater = std::make_shared<appimage::update::Updater>(appimage, true);
} else {
#ifndef NDEBUG
printf("Appimage cannot be updated\n");
#endif
emit updateError(QObject::tr("Appimage cannot be updated<br/>Expected Environmentvariable 'APPIMAGE' to be set to the path of the AppImage"));
return;
}
}
bool _updateAvailable = false;
#ifndef NDEBUG
printf("Appimage check for changes\n");
#endif
if (!updater->checkForChanges(_updateAvailable)) {
#ifndef NDEBUG
printf("Appimage Error\n");
#endif
std::stringstream errorstream;
std::string nextMessage;
while (updater->nextStatusMessage(nextMessage)) {
#ifndef NDEBUG
printf("appimage update error %s\n", nextMessage.data());
#endif
errorstream << nextMessage << "<br/>";
}
emit updateError(QObject::tr("Appimage cannot be updated") + "<br/>" + QString::fromStdString(errorstream.str()));
return;
}
#ifndef NDEBUG
printf("Appimage Found Update? %d\n", (int)_updateAvailable);
#endif #endif
if (_updateAvailable) { static const char *platformKey() {
emit updateAvailable(""); #if defined(Q_OS_MACOS)
} return "macos";
emit updateCheck(_updateAvailable); #elif defined(Q_OS_WIN)
} catch (...) { return "windows";
emit updateError(QObject::tr("Appimage cannot be updated<br/>Unknown Error"));
}
});
#elif defined(UPDATE_CHECK)
QNetworkRequest request(QStringLiteral(UPDATE_CHECK_URL));
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
netAccessManager.get(request);
#else #else
emit updateError(QObject::tr("Launcher cannot be updated<br/>You have to check your packagemanager for updates or recompile your Open Source build with newer sources")); return "linux";
#endif #endif
} }
#endif UpdateChecker::UpdateChecker(QObject *parent) : QObject(parent) {
connect(&m_network, &QNetworkAccessManager::finished, this, &UpdateChecker::onFinished);
void UpdateChecker::onRequestFinished(QNetworkReply* reply) {
#ifdef UPDATE_CHECK
if (reply->error() != QNetworkReply::NoError) {
emit updateError(QObject::tr("Failed to check for update<br/>Failed to connect to update server"));
return;
}
auto redirect = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
if (redirect.isValid()) {
QNetworkRequest request(redirect);
netAccessManager.get(request);
return;
}
QMap<QString, QString> props;
QString replyText = QString::fromUtf8(reply->readAll());
for (QStringRef const& line : replyText.splitRef('\n')) {
auto iof = line.indexOf('=');
if (iof == -1)
continue;
props[line.left(iof).toString()] = line.mid(iof + 1).trimmed().toString();
}
#ifndef NDEBUG
printf("server build id: %i\n", props["build_id"].toInt());
#endif
bool updateavailable = props["build_id"].toInt() > UPDATE_CHECK_BUILD_ID;
if (updateavailable)
emit updateAvailable(props["download_url"]);
emit updateCheck(updateavailable);
#endif
} }
void UpdateChecker::startUpdate() { QString UpdateChecker::feedUrl() {
#ifdef APPIMAGE_UPDATE_CHECK const QString overrideUrl = qEnvironmentVariable("ALARCADE_UPDATE_FEED");
if (updater) { return overrideUrl.isEmpty() ? QStringLiteral(ALARCADE_UPDATE_FEED_URL) : overrideUrl;
auto oldthread = std::make_shared<std::thread>(); }
std::swap(*oldthread, updatethread);
updatethread = std::thread([this, oldthread] {
if (oldthread->joinable()) {
oldthread->join();
}
m_active = true;
emit activeChanged();
updater->start();
std::stringstream errorstream;
while (!updater->isDone()) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
double _progress; void UpdateChecker::checkForUpdates() {
if (!updater->progress(_progress)) { const QString url = feedUrl();
#ifndef NDEBUG if (url.isEmpty())
printf("appimage startUpdate Call to progress() failed\n");
#endif
m_active = false;
emit activeChanged();
return; return;
} QNetworkRequest request{QUrl(url)};
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
emit progress(_progress); request.setTransferTimeout(15000);
m_network.get(request);
// fetch all status messages }
// this is basically the same as before
std::string nextMessage;
while (updater->nextStatusMessage(nextMessage)) {
#ifndef NDEBUG
printf("appimage startUpdate %s\n", nextMessage.data());
#endif
errorstream << nextMessage << "<br/>";
}
}
m_active = false; void UpdateChecker::onFinished(QNetworkReply *reply) {
emit activeChanged(); reply->deleteLater();
if (updater->hasError()) { if (reply->error() != QNetworkReply::NoError) {
#ifndef NDEBUG emit updateCheck(false);
printf("Error occurred. See previous messages for details.\n"); return;
#endif
emit updateError(QObject::tr("Appimage cannot be updated") + "<br/>" + QString::fromStdString(errorstream.str()));
} else {
updater->copyPermissionsToNewFile();
emit requestRestart();
}
});
} }
#endif const QJsonObject feed = QJsonDocument::fromJson(reply->readAll()).object();
const QJsonObject download = feed.value(QLatin1String(platformKey())).toObject();
const int latestBuild = feed.value("build").toInt();
const int currentBuild = QStringLiteral(LAUNCHER_VERSION_CODE).toInt();
m_latestVersion = feed.value("version").toString();
m_notes = feed.value("notes").toString();
m_downloadUrl = download.value("url").toString();
const bool newer = latestBuild > currentBuild && m_downloadUrl.startsWith(QLatin1String("https://"));
if (newer)
emit updateAvailable(m_downloadUrl);
emit updateCheck(newer);
} }
#ifdef APPIMAGE_UPDATE_CHECK void UpdateChecker::startUpdate() {
UpdateChecker::~UpdateChecker() { // Updates are installed by downloading the new installer or disk image.
if (updatethread.joinable()) { if (!m_downloadUrl.isEmpty())
updatethread.join(); QDesktopServices::openUrl(QUrl(m_downloadUrl));
}
} }
#endif
\ No newline at end of file
#ifndef UPDATECHECKER_H #ifndef UPDATECHECKER_H
#define UPDATECHECKER_H #define UPDATECHECKER_H
#include <QObject>
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
#include <QObject>
#ifdef APPIMAGE_UPDATE_CHECK class QNetworkReply;
#include <thread>
#include <appimage/update.h> // Checks an AL-Arcade release feed (JSON) for a newer launcher build:
#endif //
// { "version": "1.1.0", "build": 42, "notes": "...",
// "macos": { "url": "https://.../AL-Arcade-MC-Launcher-1.1.0-macOS-arm64.dmg", "sha256": "..." },
// "windows": { "url": "https://.../AL-Arcade-MC-Launcher-1.1.0-windows-x64-setup.exe", "sha256": "..." } }
//
// The feed URL is set at build time (ALARCADE_UPDATE_FEED_URL) and can be
// overridden with the ALARCADE_UPDATE_FEED environment variable. Without one,
// update checks are skipped.
class UpdateChecker : public QObject { class UpdateChecker : public QObject {
Q_OBJECT Q_OBJECT
Q_PROPERTY(bool active READ active NOTIFY activeChanged) Q_PROPERTY(bool active READ active NOTIFY activeChanged)
private: Q_PROPERTY(QString latestVersion READ latestVersion NOTIFY updateCheck)
QNetworkAccessManager netAccessManager; Q_PROPERTY(QString releaseNotes READ releaseNotes NOTIFY updateCheck)
#ifdef APPIMAGE_UPDATE_CHECK Q_PROPERTY(bool enabled READ enabled CONSTANT)
std::shared_ptr<appimage::update::Updater> updater;
std::thread updatethread;
#endif
std::atomic_bool m_active;
void onRequestFinished(QNetworkReply* reply);
public: public:
UpdateChecker(QObject* parent = nullptr); explicit UpdateChecker(QObject *parent = nullptr);
#ifdef APPIMAGE_UPDATE_CHECK bool active() const { return false; }
~UpdateChecker(); bool enabled() const { return !feedUrl().isEmpty(); }
#endif QString latestVersion() const { return m_latestVersion; }
QString releaseNotes() const { return m_notes; }
bool active() const { return m_active; }
signals: signals:
void updateAvailable(QString downloadUrl); void updateAvailable(QString downloadUrl);
...@@ -42,6 +41,15 @@ signals: ...@@ -42,6 +41,15 @@ signals:
public slots: public slots:
void checkForUpdates(); void checkForUpdates();
void startUpdate(); void startUpdate();
private:
static QString feedUrl();
void onFinished(QNetworkReply *reply);
QNetworkAccessManager m_network;
QString m_downloadUrl;
QString m_latestVersion;
QString m_notes;
}; };
#endif // UPDATECHECKER_H #endif // UPDATECHECKER_H
Subject: macOS: ALARCADE_RETINA=0 renders at 1x for more frames per second
diff --git a/src/window_glfw.cpp b/src/window_glfw.cpp
index 7e0a85b..9c6b513 100644
--- a/src/window_glfw.cpp
+++ b/src/window_glfw.cpp
@@ -25,6 +25,12 @@ GLFWGameWindow::GLFWGameWindow(const std::string& title, int width, int height,
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
}
+#ifdef __APPLE__
+ // ALARCADE_RETINA=0 renders at 1x on Retina displays: a quarter of the
+ // pixels, for a large frame-rate gain on older Apple Silicon.
+ const char* retina = getenv("ALARCADE_RETINA");
+ glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, (retina && retina[0] == '0') ? GLFW_FALSE : GLFW_TRUE);
+#endif
window = glfwCreateWindow(width, height, title.c_str(), nullptr, nullptr);
if(window == nullptr && api == GraphicsApi::OPENGL_ES2) {
// Failed to get es3 request es2
Subject: Ignore SIGPIPE like Android does, so VPNs cannot kill the game (#2051)
diff --git a/src/main.cpp b/src/main.cpp
index e24c581..5f25e0c 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -40,6 +40,7 @@
#include "thread_mover.h"
#include <FileUtil.h>
#include <properties/property.h>
+#include <csignal>
#include <fstream>
#include "glad/glad.h"
// For getpid
@@ -138,6 +139,10 @@ int main(int argc, char* argv[]) {
}
CrashHandler::registerCrashHandler();
+ // Android ignores SIGPIPE in every app and Minecraft relies on it: a write
+ // to a reset socket (common behind VPNs) must fail with EPIPE instead of
+ // killing the game. https://github.com/minecraft-linux/mcpelauncher-manifest/issues/2051
+ signal(SIGPIPE, SIG_IGN);
MinecraftUtils::workaroundLocaleBug();
argparser::arg_parser p;
Subject: macOS: toggle fullscreen with Control-Command-F (F11 is Show Desktop)
diff --git a/src/window_callbacks.cpp b/src/window_callbacks.cpp
index 2838736..9b2688c 100644
--- a/src/window_callbacks.cpp
+++ b/src/window_callbacks.cpp
@@ -441,6 +441,14 @@ void WindowCallbacks::onKeyboard(KeyCode key, KeyAction action, int mods) {
if(key == KeyCode::FN11 && action == KeyAction::PRESS)
setFullscreen(!Settings::fullscreen);
+#ifdef __APPLE__
+ // macOS binds F11 to Show Desktop; also accept the system's own
+ // full-screen shortcut, Control-Command-F.
+ if(key == KeyCode::F && action == KeyAction::PRESS && (mods & KEY_MOD_SUPER) && (mods & KEY_MOD_CTRL)) {
+ setFullscreen(!Settings::fullscreen);
+ return;
+ }
+#endif
if(useDirectKeyboardInput && (action == KeyAction::PRESS || action == KeyAction::RELEASE)) {
if(Keyboard::useLegacyKeyboard) {
Subject: arm64: step over debug breaks instead of spinning on an ignored SIGTRAP (#1929)
diff --git a/include/mcpelauncher/crash_handler.h b/include/mcpelauncher/crash_handler.h
index 4d3f9a6..b9a91c4 100644
--- a/include/mcpelauncher/crash_handler.h
+++ b/include/mcpelauncher/crash_handler.h
@@ -9,6 +9,9 @@ private:
#if defined(__x86_64__) && defined(__APPLE__)
static void handle_fs_fault(int sig, void *si, void *ucp);
#endif
+#if defined(__aarch64__)
+ static void handle_trap(int sig, void *si, void *ucp);
+#endif
public:
static void registerCrashHandler();
diff --git a/src/crash_handler.cpp b/src/crash_handler.cpp
index 11f0e62..fba0e1d 100644
--- a/src/crash_handler.cpp
+++ b/src/crash_handler.cpp
@@ -8,6 +8,10 @@
#include <execinfo.h>
#include <mcpelauncher/linker.h>
#include <thread>
+#include <cstdint>
+#if defined(__aarch64__) && !defined(__APPLE__)
+#include <sys/ucontext.h> // macOS gets ucontext_t from <signal.h>
+#endif
bool CrashHandler::hasCrashed = false;
@@ -96,6 +100,32 @@ void CrashHandler::handle_fs_fault(int sig, void *si, void *ucp) {
}
#endif
+#if defined(__aarch64__)
+// x86 builds ignore SIGTRAP so the game's leftover debug breaks (int3) are
+// skipped: the CPU has already moved past int3 when the signal arrives. On
+// arm64, brk leaves the pc on the instruction itself, so an ignored SIGTRAP
+// re-executes it forever and pins a core at 100% CPU, the orphaned clients of
+// https://github.com/minecraft-linux/mcpelauncher-manifest/issues/1929.
+// Step over debug breaks (brk #0xf000, what __builtin_debugtrap emits) the way
+// x86 skips int3, and treat any other brk (__builtin_trap emits brk #1) as the
+// crash it is.
+void CrashHandler::handle_trap(int sig, void *si, void *ucp) {
+ ucontext_t *uc = (ucontext_t *)ucp;
+#if defined(__APPLE__)
+ uint64_t *pc = (uint64_t *)&uc->uc_mcontext->__ss.__pc;
+#else
+ uint64_t *pc = (uint64_t *)&uc->uc_mcontext.pc;
+#endif
+ const uint32_t insn = *(const uint32_t *)*pc;
+ const bool isBrk = (insn & 0xFFE0001F) == 0xD4200000;
+ if (isBrk && ((insn >> 5) & 0xFFFF) == 0xF000) {
+ *pc += 4;
+ return;
+ }
+ handleSignal(sig, si);
+}
+#endif
+
void CrashHandler::registerCrashHandler() {
struct sigaction act;
sigemptyset(&act.sa_mask);
@@ -119,7 +149,15 @@ void CrashHandler::registerCrashHandler() {
sigemptyset(&act.sa_mask);
act.sa_handler = SIG_IGN;
act.sa_flags = 0;
+#if defined(__aarch64__)
+ struct sigaction trap;
+ sigemptyset(&trap.sa_mask);
+ trap.sa_sigaction = (void (*)(int, siginfo_t *, void *)) handle_trap;
+ trap.sa_flags = SA_SIGINFO;
+ sigaction(SIGTRAP, &trap, 0);
+#else
sigaction(SIGTRAP, &act, 0);
+#endif
#if defined(__APPLE__)
// Try to ignore this for macOS 10.12 and older and Minecraft 1.16.230+
// macOS 12.3 bug reports, crash with sigsys
...@@ -95,7 +95,8 @@ build_launcher() { ...@@ -95,7 +95,8 @@ build_launcher() {
-DCMAKE_PREFIX_PATH="$BREW" \ -DCMAKE_PREFIX_PATH="$BREW" \
-DOPENSSL_ROOT_DIR="$BREW/opt/openssl@3" \ -DOPENSSL_ROOT_DIR="$BREW/opt/openssl@3" \
-DLAUNCHER_VERSION_NAME="$VERSION" \ -DLAUNCHER_VERSION_NAME="$VERSION" \
-DLAUNCHER_VERSION_CODE="$BUILD_NUMBER" >/dev/null -DLAUNCHER_VERSION_CODE="$BUILD_NUMBER" \
-DALARCADE_UPDATE_FEED_URL="${ALARCADE_UPDATE_FEED_URL:-}" >/dev/null
ninja -C "$BUILD/launcher" -j "$JOBS" >/dev/null ninja -C "$BUILD/launcher" -j "$JOBS" >/dev/null
} }
......
#!/bin/bash
# Builds the Windows (x64) launcher on macOS: llvm-mingw cross-compile against
# Qt for Windows, then deploys Qt next to the .exe and packages
# dist/AL-Arcade-MC-Launcher-<version>-windows-x64-setup.exe (installer)
# dist/AL-Arcade-MC-Launcher-<version>-windows-x64.zip (portable)
#
# usage: scripts/cross-build-windows.sh
# Requirements: cmake, ninja, makensis (brew install makensis), Homebrew Qt of
# the same version as QT_VERSION (its moc/rcc/qmlcachegen are the host tools).
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
CACHE="$ROOT/.cache"
BUILD="$ROOT/build/windows"
DIST="$ROOT/dist"
QT_VERSION="${QT_VERSION:-6.11.2}"
QT_WIN="$CACHE/qt-windows/$QT_VERSION"
VERSION="$(tr -d '[:space:]' < "$ROOT/VERSION")"
BUILD_NUMBER="${BUILD_NUMBER:-$(git -C "$ROOT" rev-list --count HEAD 2>/dev/null || echo 1)}"
BREW="$(brew --prefix)"
APP_NAME="AL-Arcade MC Launcher"
APP_DIR="$DIST/windows/$APP_NAME"
BASENAME="AL-Arcade-MC-Launcher-$VERSION-windows-x64"
step() { printf '\n\033[1;35m==> %s\033[0m\n' "$1"; }
step "Toolchain"
"$ROOT/scripts/fetch-windows-toolchain.sh"
[ -f "$QT_WIN/lib/cmake/Qt6/Qt6Config.cmake" ] || python3 "$ROOT/tools/qt-windows-install.py" "$QT_VERSION" "$QT_WIN"
step "Building"
cmake -S "$ROOT" -B "$BUILD" -G Ninja \
-DCMAKE_TOOLCHAIN_FILE="$ROOT/cmake/toolchains/llvm-mingw-x86_64.cmake" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH="$QT_WIN" \
-DQT_HOST_PATH="$BREW" \
-DQT_HOST_PATH_CMAKE_DIR="$BREW/lib/cmake" \
-DALARCADE_BACKEND=windows \
-DLAUNCHER_VERSION_NAME="$VERSION" \
-DLAUNCHER_VERSION_CODE="$BUILD_NUMBER" \
-DALARCADE_UPDATE_FEED_URL="${ALARCADE_UPDATE_FEED_URL:-}" >/dev/null
ninja -C "$BUILD" >/dev/null
step "Deploying Qt"
python3 "$ROOT/tools/windows-deploy.py" \
--exe "$BUILD/launcher/$APP_NAME.exe" \
--qt "$QT_WIN" \
--mingw "$CACHE/llvm-mingw" \
--qml-root "$ROOT/launcher/qml" \
--scanner "$BREW/share/qt/libexec/qmlimportscanner" \
--out "$APP_DIR"
cp "$ROOT/LICENSE" "$APP_DIR/LICENSE.txt"
cp "$ROOT/NOTICE.md" "$APP_DIR/NOTICE.md"
step "Packaging"
rm -f "$DIST/$BASENAME.zip" "$DIST/$BASENAME-setup.exe"
(cd "$DIST/windows" && zip -qr -9 "$DIST/$BASENAME.zip" "$APP_NAME")
makensis -V2 \
-DAPP_VERSION="$VERSION" \
-DSOURCE_DIR="$APP_DIR" \
-DOUT_FILE="$DIST/$BASENAME-setup.exe" \
-DICON="$ROOT/branding/app.ico" \
-DLICENSE_FILE="$ROOT/LICENSE" \
"$ROOT/windows/installer.nsi"
(cd "$DIST" && shasum -a 256 "$BASENAME-setup.exe" "$BASENAME.zip" | tee "$BASENAME.sha256")
step "Done"
#!/bin/bash #!/bin/bash
# Fetches what scripts/cross-build-windows.sh needs to build the Windows launcher # Fetches what scripts/cross-build-windows.sh needs to build the Windows launcher
# on macOS or Linux: the llvm-mingw toolchain and Qt for Windows (llvm-mingw build). # on macOS: the llvm-mingw toolchain and Qt for Windows (llvm-mingw build).
# Everything goes to .cache/; nothing is installed system-wide. # Everything goes to .cache/; nothing is installed system-wide.
set -euo pipefail set -euo pipefail
...@@ -38,9 +38,8 @@ if [ ! -x "$CACHE/llvm-mingw/bin/x86_64-w64-mingw32-clang++" ]; then ...@@ -38,9 +38,8 @@ if [ ! -x "$CACHE/llvm-mingw/bin/x86_64-w64-mingw32-clang++" ]; then
xattr -cr "$CACHE/llvm-mingw" 2>/dev/null || true xattr -cr "$CACHE/llvm-mingw" 2>/dev/null || true
fi fi
if [ ! -d "$CACHE/qt-windows/$QT_VERSION/llvm-mingw_64" ]; then if [ ! -f "$CACHE/qt-windows/$QT_VERSION/lib/cmake/Qt6/Qt6Config.cmake" ]; then
echo "==> Qt $QT_VERSION for Windows (llvm-mingw)" echo "==> Qt $QT_VERSION for Windows (llvm-mingw)"
[ -x "$CACHE/aqt-venv/bin/aqt" ] || { python3 -m venv "$CACHE/aqt-venv"; "$CACHE/aqt-venv/bin/pip" install -q aqtinstall; } python3 "$ROOT/tools/qt-windows-install.py" "$QT_VERSION" "$CACHE/qt-windows/$QT_VERSION"
"$CACHE/aqt-venv/bin/aqt" install-qt windows desktop "$QT_VERSION" win64_llvm_mingw -O "$CACHE/qt-windows"
fi fi
echo "toolchain ready: $CACHE/llvm-mingw, $CACHE/qt-windows/$QT_VERSION" echo "toolchain ready: $CACHE/llvm-mingw, $CACHE/qt-windows/$QT_VERSION"
#!/usr/bin/env python3
"""Assembles the Windows app folder next to the .exe: Qt DLLs, plugins, QML
modules and the llvm-mingw runtime. Does what windeployqt does, but runs on
macOS and Linux, so the Windows build can be packaged where it is built.
usage: windows-deploy.py --exe app.exe --qt <qt-windows> --mingw <llvm-mingw>
--qml-root launcher/qml --scanner <qmlimportscanner>
--out dist/windows/<App>
"""
import argparse
import json
import os
import shutil
import subprocess
import sys
PLUGINS = [
"platforms/qwindows.dll",
"imageformats/qjpeg.dll",
"imageformats/qsvg.dll",
"imageformats/qico.dll",
"imageformats/qgif.dll",
"iconengines/qsvgicon.dll",
"tls/qschannelbackend.dll",
"tls/qcertonlybackend.dll",
"networkinformation/qnetworklistmanager.dll",
"styles/qmodernwindowsstyle.dll",
]
# The launcher forces the Basic style; the others would only add size.
UNUSED_QML = (
"QtQuick/Controls/FluentWinUI3", "QtQuick/Controls/Fusion", "QtQuick/Controls/Imagine",
"QtQuick/Controls/Material", "QtQuick/Controls/Universal", "QtQuick/Controls/Windows",
"QtQuick/NativeStyle",
)
def imports_of(objdump, path):
result = subprocess.run([objdump, "-p", path], capture_output=True, text=True, check=True)
return [line.split("DLL Name:")[1].strip() for line in result.stdout.splitlines() if "DLL Name:" in line]
def copy_module_dir(src, dst):
"""Copies one QML module directory, without its nested modules."""
os.makedirs(dst, exist_ok=True)
for name in os.listdir(src):
path = os.path.join(src, name)
if os.path.isfile(path) and not name.endswith((".pdb", ".debug")):
shutil.copy2(path, os.path.join(dst, name))
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--exe", required=True)
ap.add_argument("--qt", required=True)
ap.add_argument("--mingw", required=True)
ap.add_argument("--qml-root", required=True)
ap.add_argument("--scanner", required=True)
ap.add_argument("--out", required=True)
args = ap.parse_args()
objdump = os.path.join(args.mingw, "bin", "llvm-objdump")
search_dirs = [os.path.join(args.qt, "bin"), os.path.join(args.mingw, "x86_64-w64-mingw32", "bin")]
if os.path.isdir(args.out):
shutil.rmtree(args.out)
os.makedirs(args.out)
binaries = [shutil.copy2(args.exe, args.out)]
for plugin in PLUGINS:
src = os.path.join(args.qt, "plugins", plugin)
if os.path.exists(src):
dst = os.path.join(args.out, plugin)
os.makedirs(os.path.dirname(dst), exist_ok=True)
binaries.append(shutil.copy2(src, dst))
scan = subprocess.run([args.scanner, "-rootPath", args.qml_root, "-importPath", os.path.join(args.qt, "qml")],
capture_output=True, text=True, check=True)
modules = 0
for entry in json.loads(scan.stdout):
rel = entry.get("relativePath")
if entry.get("type") != "module" or not rel or rel == "QML" or rel.startswith(UNUSED_QML):
continue
src = os.path.join(args.qt, "qml", rel)
if not os.path.isdir(src):
continue
dst = os.path.join(args.out, "qml", rel)
copy_module_dir(src, dst)
modules += 1
binaries += [os.path.join(dst, f) for f in os.listdir(dst) if f.endswith(".dll")]
# Pull in every DLL the binaries need that ships with Qt or the toolchain;
# anything not found there is a Windows system library.
copied = set()
pending = list(binaries)
while pending:
for dll in imports_of(objdump, pending.pop()):
key = dll.lower()
if key in copied:
continue
for directory in search_dirs:
candidate = os.path.join(directory, dll)
if os.path.exists(candidate):
copied.add(key)
pending.append(shutil.copy2(candidate, os.path.join(args.out, dll)))
break
size = sum(os.path.getsize(os.path.join(root, f)) for root, _, files in os.walk(args.out) for f in files)
print(f"deployed {len(copied)} DLLs, {modules} QML modules, {size / 1048576:.1f} MB into {args.out}")
if __name__ == "__main__":
sys.exit(main())
; AL-Arcade MC Launcher installer (NSIS 3). Built by scripts/cross-build-windows.sh
; or scripts/build-windows.ps1, which pass:
; APP_VERSION 1.2.3 SOURCE_DIR deployed app folder
; OUT_FILE setup.exe ICON / LICENSE_FILE paths in the repository
; Installs per user (no administrator prompt) into %LOCALAPPDATA%\Programs.
Unicode true
SetCompressor /SOLID lzma
!include "MUI2.nsh"
!define APP_NAME "AL-Arcade MC Launcher"
!define APP_EXE "AL-Arcade MC Launcher.exe"
!define COMPANY "AL-Arcade"
!define UNINSTALL_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}"
Name "${APP_NAME}"
OutFile "${OUT_FILE}"
InstallDir "$LOCALAPPDATA\Programs\${APP_NAME}"
InstallDirRegKey HKCU "Software\${COMPANY}\${APP_NAME} Installer" "InstallDir"
RequestExecutionLevel user
BrandingText "${COMPANY}"
VIProductVersion "${APP_VERSION}.0"
VIAddVersionKey "ProductName" "${APP_NAME}"
VIAddVersionKey "CompanyName" "${COMPANY}"
VIAddVersionKey "FileDescription" "${APP_NAME} Setup"
VIAddVersionKey "FileVersion" "${APP_VERSION}"
VIAddVersionKey "ProductVersion" "${APP_VERSION}"
VIAddVersionKey "LegalCopyright" "Copyright 2026 AL-Arcade. GPL-3.0. Not an official Minecraft product."
!define MUI_ICON "${ICON}"
!define MUI_UNICON "${ICON}"
!define MUI_ABORTWARNING
!define MUI_FINISHPAGE_RUN "$INSTDIR\${APP_EXE}"
!define MUI_FINISHPAGE_RUN_TEXT "Start ${APP_NAME}"
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_LICENSE "${LICENSE_FILE}"
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_LANGUAGE "English"
!insertmacro MUI_LANGUAGE "Arabic"
Section "Install"
; Replace an earlier version's files completely.
RMDir /r "$INSTDIR\qml"
SetOutPath "$INSTDIR"
File /r "${SOURCE_DIR}/*.*"
WriteUninstaller "$INSTDIR\Uninstall.exe"
CreateShortcut "$SMPROGRAMS\${APP_NAME}.lnk" "$INSTDIR\${APP_EXE}"
CreateShortcut "$DESKTOP\${APP_NAME}.lnk" "$INSTDIR\${APP_EXE}"
WriteRegStr HKCU "Software\${COMPANY}\${APP_NAME} Installer" "InstallDir" "$INSTDIR"
WriteRegStr HKCU "${UNINSTALL_KEY}" "DisplayName" "${APP_NAME}"
WriteRegStr HKCU "${UNINSTALL_KEY}" "DisplayVersion" "${APP_VERSION}"
WriteRegStr HKCU "${UNINSTALL_KEY}" "Publisher" "${COMPANY}"
WriteRegStr HKCU "${UNINSTALL_KEY}" "DisplayIcon" "$INSTDIR\${APP_EXE}"
WriteRegStr HKCU "${UNINSTALL_KEY}" "InstallLocation" "$INSTDIR"
WriteRegStr HKCU "${UNINSTALL_KEY}" "UninstallString" "$\"$INSTDIR\Uninstall.exe$\""
WriteRegStr HKCU "${UNINSTALL_KEY}" "URLInfoAbout" "https://al-arcade.com"
WriteRegDWORD HKCU "${UNINSTALL_KEY}" "NoModify" 1
WriteRegDWORD HKCU "${UNINSTALL_KEY}" "NoRepair" 1
SectionEnd
Section "Uninstall"
Delete "$SMPROGRAMS\${APP_NAME}.lnk"
Delete "$DESKTOP\${APP_NAME}.lnk"
RMDir /r "$INSTDIR"
DeleteRegKey HKCU "${UNINSTALL_KEY}"
DeleteRegKey HKCU "Software\${COMPANY}\${APP_NAME} Installer"
; Settings (HKCU\Software\AL-Arcade\AL-Arcade MC Launcher) and world backups
; in Documents are the player's data and stay.
SectionEnd
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment