Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into feature/java-downloader
This commit is contained in:
commit
25bca28707
@ -1,4 +1,5 @@
|
||||
Checks:
|
||||
- modernize-use-using
|
||||
- readability-avoid-const-params-in-decls
|
||||
|
||||
SystemHeaders: false
|
||||
|
2
.github/workflows/backport.yml
vendored
2
.github/workflows/backport.yml
vendored
@ -24,7 +24,7 @@ jobs:
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Create backport PRs
|
||||
uses: korthout/backport-action@v1.4.0
|
||||
uses: korthout/backport-action@v2.4.1
|
||||
with:
|
||||
# Config README: https://github.com/korthout/backport-action#backport-action
|
||||
pull_description: |-
|
||||
|
96
.github/workflows/build.yml
vendored
96
.github/workflows/build.yml
vendored
@ -21,8 +21,23 @@ on:
|
||||
WINDOWS_CODESIGN_PASSWORD:
|
||||
description: Password for signing Windows builds
|
||||
required: false
|
||||
CACHIX_AUTH_TOKEN:
|
||||
description: Private token for authenticating against Cachix cache
|
||||
APPLE_CODESIGN_CERT:
|
||||
description: Certificate for signing macOS builds
|
||||
required: false
|
||||
APPLE_CODESIGN_PASSWORD:
|
||||
description: Password for signing macOS builds
|
||||
required: false
|
||||
APPLE_CODESIGN_ID:
|
||||
description: Certificate ID for signing macOS builds
|
||||
required: false
|
||||
APPLE_NOTARIZE_APPLE_ID:
|
||||
description: Apple ID used for notarizing macOS builds
|
||||
required: false
|
||||
APPLE_NOTARIZE_TEAM_ID:
|
||||
description: Team ID used for notarizing macOS builds
|
||||
required: false
|
||||
APPLE_NOTARIZE_PASSWORD:
|
||||
description: Password used for notarizing macOS builds
|
||||
required: false
|
||||
GPG_PRIVATE_KEY:
|
||||
description: Private key for AppImage signing
|
||||
@ -61,7 +76,7 @@ jobs:
|
||||
qt_ver: 6
|
||||
qt_host: windows
|
||||
qt_arch: ''
|
||||
qt_version: '6.6.0'
|
||||
qt_version: '6.6.1'
|
||||
qt_modules: 'qt5compat qtimageformats'
|
||||
qt_tools: ''
|
||||
|
||||
@ -73,7 +88,7 @@ jobs:
|
||||
qt_ver: 6
|
||||
qt_host: windows
|
||||
qt_arch: 'win64_msvc2019_arm64'
|
||||
qt_version: '6.6.0'
|
||||
qt_version: '6.6.1'
|
||||
qt_modules: 'qt5compat qtimageformats'
|
||||
qt_tools: ''
|
||||
|
||||
@ -83,7 +98,7 @@ jobs:
|
||||
qt_ver: 6
|
||||
qt_host: mac
|
||||
qt_arch: ''
|
||||
qt_version: '6.6.0'
|
||||
qt_version: '6.6.1'
|
||||
qt_modules: 'qt5compat qtimageformats'
|
||||
qt_tools: ''
|
||||
|
||||
@ -145,13 +160,13 @@ jobs:
|
||||
|
||||
- name: Setup ccache
|
||||
if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug'
|
||||
uses: hendrikmuhs/ccache-action@v1.2.10
|
||||
uses: hendrikmuhs/ccache-action@v1.2.11
|
||||
with:
|
||||
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }}
|
||||
|
||||
- name: Retrieve ccache cache (Windows MinGW-w64)
|
||||
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
|
||||
uses: actions/cache@v3.3.2
|
||||
uses: actions/cache@v4.0.0
|
||||
with:
|
||||
path: '${{ github.workspace }}\.ccache'
|
||||
key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }}
|
||||
@ -336,6 +351,20 @@ jobs:
|
||||
# PACKAGE BUILDS
|
||||
##
|
||||
|
||||
- name: Fetch codesign certificate (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
echo '${{ secrets.APPLE_CODESIGN_CERT }}' | base64 --decode > codesign.p12
|
||||
if [ -n '${{ secrets.APPLE_CODESIGN_ID }}' ]; then
|
||||
security create-keychain -p '${{ secrets.APPLE_CODESIGN_PASSWORD }}' build.keychain
|
||||
security default-keychain -s build.keychain
|
||||
security unlock-keychain -p '${{ secrets.APPLE_CODESIGN_PASSWORD }}' build.keychain
|
||||
security import codesign.p12 -k build.keychain -P '${{ secrets.APPLE_CODESIGN_PASSWORD }}' -T /usr/bin/codesign
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k '${{ secrets.APPLE_CODESIGN_PASSWORD }}' build.keychain
|
||||
else
|
||||
echo ":warning: Using ad-hoc code signing for macOS, as certificate was not present." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
- name: Package (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
@ -343,9 +372,34 @@ jobs:
|
||||
|
||||
cd ${{ env.INSTALL_DIR }}
|
||||
chmod +x "PrismLauncher.app/Contents/MacOS/prismlauncher"
|
||||
sudo codesign --sign - --deep --force --entitlements "../program_info/App.entitlements" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher"
|
||||
|
||||
if [ -n '${{ secrets.APPLE_CODESIGN_ID }}' ]; then
|
||||
APPLE_CODESIGN_ID='${{ secrets.APPLE_CODESIGN_ID }}'
|
||||
else
|
||||
APPLE_CODESIGN_ID='-'
|
||||
fi
|
||||
|
||||
sudo codesign --sign "$APPLE_CODESIGN_ID" --deep --force --entitlements "../program_info/App.entitlements" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher"
|
||||
mv "PrismLauncher.app" "Prism Launcher.app"
|
||||
tar -czf ../PrismLauncher.tar.gz *
|
||||
|
||||
- name: Notarize (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
cd ${{ env.INSTALL_DIR }}
|
||||
|
||||
if [ -n '${{ secrets.APPLE_NOTARIZE_PASSWORD }}' ]; then
|
||||
ditto -c -k --sequesterRsrc --keepParent "Prism Launcher.app" ../PrismLauncher.zip
|
||||
xcrun notarytool submit ../PrismLauncher.zip \
|
||||
--wait --progress \
|
||||
--apple-id '${{ secrets.APPLE_NOTARIZE_APPLE_ID }}' \
|
||||
--team-id '${{ secrets.APPLE_NOTARIZE_TEAM_ID }}' \
|
||||
--password '${{ secrets.APPLE_NOTARIZE_PASSWORD }}'
|
||||
|
||||
xcrun stapler staple "Prism Launcher.app"
|
||||
else
|
||||
echo ":warning: Skipping notarization as credentials are not present." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
ditto -c -k --sequesterRsrc --keepParent "Prism Launcher.app" ../PrismLauncher.zip
|
||||
|
||||
- name: Make Sparkle signature (macOS)
|
||||
if: matrix.name == 'macOS'
|
||||
@ -517,70 +571,70 @@ jobs:
|
||||
|
||||
- name: Upload binary tarball (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: PrismLauncher-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||
path: PrismLauncher.tar.gz
|
||||
path: PrismLauncher.zip
|
||||
|
||||
- name: Upload binary zip (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: PrismLauncher-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||
path: ${{ env.INSTALL_DIR }}/**
|
||||
|
||||
- name: Upload binary zip (Windows, portable)
|
||||
if: runner.os == 'Windows'
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: PrismLauncher-${{ matrix.name }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||
path: ${{ env.INSTALL_PORTABLE_DIR }}/**
|
||||
|
||||
- name: Upload installer (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: PrismLauncher-${{ matrix.name }}-Setup-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||
path: PrismLauncher-Setup.exe
|
||||
|
||||
- name: Upload binary tarball (Linux, Qt 5)
|
||||
if: runner.os == 'Linux' && matrix.qt_ver != 6
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: PrismLauncher-${{ runner.os }}-Qt5-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||
path: PrismLauncher.tar.gz
|
||||
|
||||
- name: Upload binary tarball (Linux, portable, Qt 5)
|
||||
if: runner.os == 'Linux' && matrix.qt_ver != 6
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: PrismLauncher-${{ runner.os }}-Qt5-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||
path: PrismLauncher-portable.tar.gz
|
||||
|
||||
- name: Upload binary tarball (Linux, Qt 6)
|
||||
if: runner.os == 'Linux' && matrix.qt_ver !=5
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: PrismLauncher-${{ runner.os }}-Qt6-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||
path: PrismLauncher.tar.gz
|
||||
|
||||
- name: Upload binary tarball (Linux, portable, Qt 6)
|
||||
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: PrismLauncher-${{ runner.os }}-Qt6-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||
path: PrismLauncher-portable.tar.gz
|
||||
|
||||
- name: Upload AppImage (Linux)
|
||||
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
|
||||
path: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
|
||||
|
||||
- name: Upload AppImage Zsync (Linux)
|
||||
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage.zsync
|
||||
path: PrismLauncher-Linux-x86_64.AppImage.zsync
|
||||
@ -594,7 +648,7 @@ jobs:
|
||||
flatpak:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: bilelmoussaoui/flatpak-github-actions:kde-5.15-22.08
|
||||
image: bilelmoussaoui/flatpak-github-actions:kde-5.15-23.08
|
||||
options: --privileged
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
||||
submodules: 'true'
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
config-file: ./.github/codeql/codeql-config.yml
|
||||
queries: security-and-quality
|
||||
@ -32,4 +32,4 @@ jobs:
|
||||
cmake --build build
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@v3
|
||||
|
7
.github/workflows/trigger_builds.yml
vendored
7
.github/workflows/trigger_builds.yml
vendored
@ -32,6 +32,11 @@ jobs:
|
||||
SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }}
|
||||
WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }}
|
||||
WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }}
|
||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
APPLE_CODESIGN_CERT: ${{ secrets.APPLE_CODESIGN_CERT }}
|
||||
APPLE_CODESIGN_PASSWORD: ${{ secrets.APPLE_CODESIGN_PASSWORD }}
|
||||
APPLE_CODESIGN_ID: ${{ secrets.APPLE_CODESIGN_ID }}
|
||||
APPLE_NOTARIZE_APPLE_ID: ${{ secrets.APPLE_NOTARIZE_APPLE_ID }}
|
||||
APPLE_NOTARIZE_TEAM_ID: ${{ secrets.APPLE_NOTARIZE_TEAM_ID }}
|
||||
APPLE_NOTARIZE_PASSWORD: ${{ secrets.APPLE_NOTARIZE_PASSWORD }}
|
||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}
|
||||
|
17
.github/workflows/trigger_release.yml
vendored
17
.github/workflows/trigger_release.yml
vendored
@ -16,7 +16,12 @@ jobs:
|
||||
SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }}
|
||||
WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }}
|
||||
WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }}
|
||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
APPLE_CODESIGN_CERT: ${{ secrets.APPLE_CODESIGN_CERT }}
|
||||
APPLE_CODESIGN_PASSWORD: ${{ secrets.APPLE_CODESIGN_PASSWORD }}
|
||||
APPLE_CODESIGN_ID: ${{ secrets.APPLE_CODESIGN_ID }}
|
||||
APPLE_NOTARIZE_APPLE_ID: ${{ secrets.APPLE_NOTARIZE_APPLE_ID }}
|
||||
APPLE_NOTARIZE_TEAM_ID: ${{ secrets.APPLE_NOTARIZE_TEAM_ID }}
|
||||
APPLE_NOTARIZE_PASSWORD: ${{ secrets.APPLE_NOTARIZE_PASSWORD }}
|
||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}
|
||||
|
||||
@ -32,7 +37,7 @@ jobs:
|
||||
submodules: "true"
|
||||
path: "PrismLauncher-source"
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
- name: Grab and store version
|
||||
run: |
|
||||
tag_name=$(echo ${{ github.ref }} | grep -oE "[^/]+$")
|
||||
@ -46,8 +51,8 @@ jobs:
|
||||
mv PrismLauncher-Linux-Qt5*/PrismLauncher.tar.gz PrismLauncher-Linux-Qt5-${{ env.VERSION }}.tar.gz
|
||||
mv PrismLauncher-*.AppImage/PrismLauncher-*.AppImage PrismLauncher-Linux-x86_64.AppImage
|
||||
mv PrismLauncher-*.AppImage.zsync/PrismLauncher-*.AppImage.zsync PrismLauncher-Linux-x86_64.AppImage.zsync
|
||||
mv PrismLauncher-macOS-Legacy*/PrismLauncher.tar.gz PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz
|
||||
mv PrismLauncher-macOS*/PrismLauncher.tar.gz PrismLauncher-macOS-${{ env.VERSION }}.tar.gz
|
||||
mv PrismLauncher-macOS-Legacy*/PrismLauncher.zip PrismLauncher-macOS-Legacy-${{ env.VERSION }}.zip
|
||||
mv PrismLauncher-macOS*/PrismLauncher.zip PrismLauncher-macOS-${{ env.VERSION }}.zip
|
||||
|
||||
tar --exclude='.git' -czf PrismLauncher-${{ env.VERSION }}.tar.gz PrismLauncher-${{ env.VERSION }}
|
||||
|
||||
@ -102,6 +107,6 @@ jobs:
|
||||
PrismLauncher-Windows-MSVC-${{ env.VERSION }}.zip
|
||||
PrismLauncher-Windows-MSVC-Portable-${{ env.VERSION }}.zip
|
||||
PrismLauncher-Windows-MSVC-Setup-${{ env.VERSION }}.exe
|
||||
PrismLauncher-macOS-${{ env.VERSION }}.tar.gz
|
||||
PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz
|
||||
PrismLauncher-macOS-${{ env.VERSION }}.zip
|
||||
PrismLauncher-macOS-Legacy-${{ env.VERSION }}.zip
|
||||
PrismLauncher-${{ env.VERSION }}.tar.gz
|
||||
|
2
.github/workflows/update-flake.yml
vendored
2
.github/workflows/update-flake.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@6a9a9e84a173d90b3ffb42c5ddaf9ea033fad011 # v23
|
||||
- uses: cachix/install-nix-action@6004951b182f8860210c8d6f0d808ec5b1a33d28 # v25
|
||||
|
||||
- uses: DeterminateSystems/update-flake-lock@v20
|
||||
with:
|
||||
|
@ -377,7 +377,7 @@ if(UNIX AND APPLE)
|
||||
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${Launcher_VERSION_NAME}")
|
||||
set(MACOSX_BUNDLE_LONG_VERSION_STRING "${Launcher_VERSION_NAME}")
|
||||
set(MACOSX_BUNDLE_ICON_FILE ${Launcher_Name}.icns)
|
||||
set(MACOSX_BUNDLE_COPYRIGHT "© 2022-2023 ${Launcher_Copyright_Mac}")
|
||||
set(MACOSX_BUNDLE_COPYRIGHT "${Launcher_Copyright_Mac}")
|
||||
set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "v55ZWWD6QlPoXGV6VLzOTZxZUggWeE51X8cRQyQh6vA=" CACHE STRING "Public key for Sparkle update feed")
|
||||
set(MACOSX_SPARKLE_UPDATE_FEED_URL "https://prismlauncher.org/feed/appcast.xml" CACHE STRING "URL for Sparkle update feed")
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
## Prism Launcher
|
||||
|
||||
Prism Launcher - Minecraft Launcher
|
||||
Copyright (C) 2022-2023 Prism Launcher Contributors
|
||||
Copyright (C) 2022-2024 Prism Launcher Contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@ -436,7 +436,7 @@
|
||||
Copyright (C) 2007 Johann Ollivier Lapeyre <johann@oxygen-icons.org>
|
||||
Copyright (C) 2007 Kenneth Wimer <kwwii@bootsplash.org>
|
||||
Copyright (C) 2007 Riccardo Iaconelli <riccardo@oxygen-icons.org>
|
||||
|
||||
|
||||
and others
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
|
11
README.md
11
README.md
@ -18,11 +18,18 @@
|
||||
</a>
|
||||
|
||||
- All downloads and instructions for Prism Launcher can be found on our [Website](https://prismlauncher.org/download).
|
||||
- Last build status can be found in the [GitHub Actions](https://github.com/PrismLauncher/PrismLauncher/actions).
|
||||
- Last build status can be found in the [GitHub Actions](https://github.com/PrismLauncher/PrismLauncher/actions) tab (this also includes the pull requests status).
|
||||
|
||||
### Development Builds
|
||||
|
||||
There are development builds available [here](https://github.com/PrismLauncher/PrismLauncher/actions). These have debug information in the binaries, so their file sizes are relatively larger.
|
||||
Please understand that these builds are not intended for most users. There may be bugs, and other instabilities. You have been warned.
|
||||
|
||||
There are development builds available through:
|
||||
|
||||
- [GitHub Actions](https://github.com/PrismLauncher/PrismLauncher/actions) (includes builds from pull requests opened by contribuitors)
|
||||
- [nightly.link](https://nightly.link/PrismLauncher/PrismLauncher/workflows/trigger_builds/develop) (this will always point only to the latest version of develop)
|
||||
|
||||
These have debug information in the binaries, so their file sizes are relatively larger.
|
||||
|
||||
Prebuilt Development builds are provided for **Linux**, **Windows** and **macOS**.
|
||||
|
||||
|
@ -68,6 +68,8 @@ function(
|
||||
/w14906 # string literal cast to 'LPWSTR'
|
||||
/w14928 # illegal copy-initialization; more than one user-defined conversion has been implicitly applied
|
||||
/permissive- # standards conformance mode for MSVC compiler.
|
||||
|
||||
/we4062 # forbid omitting a possible value of an enum in a switch statement
|
||||
)
|
||||
endif()
|
||||
|
||||
@ -93,6 +95,8 @@ function(
|
||||
# in a lot of noise. This warning is only notifying us that clang is emulating the GCC behaviour
|
||||
# instead of the exact standard wording so we can safely ignore it
|
||||
-Wno-gnu-zero-variadic-macro-arguments
|
||||
|
||||
-Werror=switch # forbid omitting a possible value of an enum in a switch statement
|
||||
)
|
||||
endif()
|
||||
|
||||
@ -104,6 +108,8 @@ function(
|
||||
-Wduplicated-branches # warn if if / else branches have duplicated code
|
||||
-Wlogical-op # warn about logical operations being used where bitwise were probably wanted
|
||||
-Wuseless-cast # warn if you perform a cast to the same type
|
||||
|
||||
-Werror=switch # forbid omitting a possible value of an enum in a switch statement
|
||||
)
|
||||
endif()
|
||||
|
||||
@ -128,6 +134,8 @@ function(
|
||||
-Woverloaded-virtual
|
||||
-Wuseless-cast
|
||||
-Wextra-semi
|
||||
|
||||
-Werror=switch # forbid omitting a possible value of an enum in a switch statement
|
||||
)
|
||||
|
||||
target_compile_options(
|
||||
|
74
flake.lock
generated
74
flake.lock
generated
@ -18,14 +18,16 @@
|
||||
},
|
||||
"flake-parts": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
"nixpkgs-lib": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1696343447,
|
||||
"narHash": "sha256-B2xAZKLkkeRFG5XcHHSXXcP7To9Xzr59KXeZiRf4vdQ=",
|
||||
"lastModified": 1704982712,
|
||||
"narHash": "sha256-2Ptt+9h8dczgle2Oo6z5ni5rt/uLMG47UFTR1ry/wgg=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "c9afaba3dfa4085dbd2ccb38dfade5141e33d9d4",
|
||||
"rev": "07f6395285469419cf9d078f59b5b49993198c00",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -39,11 +41,11 @@
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1685518550,
|
||||
"narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=",
|
||||
"lastModified": 1701680307,
|
||||
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef",
|
||||
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -60,11 +62,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1660459072,
|
||||
"narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=",
|
||||
"lastModified": 1703887061,
|
||||
"narHash": "sha256-gGPa9qWNc6eCXT/+Z5/zMkyYOuRZqeFZBDbopNZQkuY=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"rev": "a20de23b925fd8264fd7fad6454652e142fd7f73",
|
||||
"rev": "43e1aa1308018f37118e34d3a9cb4f5e75dc11d5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -76,11 +78,11 @@
|
||||
"libnbtplusplus": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1690036783,
|
||||
"narHash": "sha256-A5kTgICnx+Qdq3Fir/bKTfdTt/T1NQP2SC+nhN1ENug=",
|
||||
"lastModified": 1699286814,
|
||||
"narHash": "sha256-yy0q+bky80LtK1GWzz7qpM+aAGrOqLuewbid8WT1ilk=",
|
||||
"owner": "PrismLauncher",
|
||||
"repo": "libnbtplusplus",
|
||||
"rev": "a5e8fd52b8bf4ab5d5bcc042b2a247867589985f",
|
||||
"rev": "23b955121b8217c1c348a9ed2483167a6f3ff4ad",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -89,28 +91,13 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-filter": {
|
||||
"locked": {
|
||||
"lastModified": 1694857738,
|
||||
"narHash": "sha256-bxxNyLHjhu0N8T3REINXQ2ZkJco0ABFPn6PIe2QUfqo=",
|
||||
"owner": "numtide",
|
||||
"repo": "nix-filter",
|
||||
"rev": "41fd48e00c22b4ced525af521ead8792402de0ea",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "nix-filter",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1697886341,
|
||||
"narHash": "sha256-AdE67xPty9M9wn36nPVp6aDntIdigrs7UbyaGv1VAaM=",
|
||||
"lastModified": 1705697961,
|
||||
"narHash": "sha256-XepT3WS516evSFYkme3GrcI3+7uwXHqtHbip+t24J7E=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "44881e03af1c730cbb1d72a4d41274a2c957813a",
|
||||
"rev": "e5d1c87f5813afde2dda384ac807c57a105721cc",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -120,24 +107,6 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"dir": "lib",
|
||||
"lastModified": 1696019113,
|
||||
"narHash": "sha256-X3+DKYWJm93DRSdC5M6K5hLqzSya9BjibtBsuARoPco=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "f5892ddac112a1e9b3612c39af1b72987ee5783a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"dir": "lib",
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"pre-commit-hooks": {
|
||||
"inputs": {
|
||||
"flake-compat": [
|
||||
@ -153,11 +122,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1697746376,
|
||||
"narHash": "sha256-gu77VkgdfaHgNCVufeb6WP9oqFLjwK4jHcoPZmBVF3E=",
|
||||
"lastModified": 1705757126,
|
||||
"narHash": "sha256-Eksr+n4Q8EYZKAN0Scef5JK4H6FcHc+TKNHb95CWm+c=",
|
||||
"owner": "cachix",
|
||||
"repo": "pre-commit-hooks.nix",
|
||||
"rev": "8cc349bfd082da8782b989cad2158c9ad5bd70fd",
|
||||
"rev": "f56597d53fd174f796b5a7d3ee0b494f9e2285cc",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -171,7 +140,6 @@
|
||||
"flake-compat": "flake-compat",
|
||||
"flake-parts": "flake-parts",
|
||||
"libnbtplusplus": "libnbtplusplus",
|
||||
"nix-filter": "nix-filter",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"pre-commit-hooks": "pre-commit-hooks"
|
||||
}
|
||||
|
19
flake.nix
19
flake.nix
@ -1,15 +1,24 @@
|
||||
{
|
||||
description = "A custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once (Fork of MultiMC)";
|
||||
|
||||
nixConfig = {
|
||||
extra-substituters = ["https://cache.garnix.io"];
|
||||
extra-trusted-public-keys = ["cache.garnix.io:CTFPyKSLcx5RMJKfLo5EEPUObbA78b0YQ2DTCJXqr9g="];
|
||||
};
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
|
||||
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||
nix-filter.url = "github:numtide/nix-filter";
|
||||
flake-parts = {
|
||||
url = "github:hercules-ci/flake-parts";
|
||||
inputs.nixpkgs-lib.follows = "nixpkgs";
|
||||
};
|
||||
pre-commit-hooks = {
|
||||
url = "github:cachix/pre-commit-hooks.nix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
inputs.nixpkgs-stable.follows = "nixpkgs";
|
||||
inputs.flake-compat.follows = "flake-compat";
|
||||
inputs = {
|
||||
nixpkgs.follows = "nixpkgs";
|
||||
nixpkgs-stable.follows = "nixpkgs";
|
||||
flake-compat.follows = "flake-compat";
|
||||
};
|
||||
};
|
||||
flake-compat = {
|
||||
url = "github:edolstra/flake-compat";
|
||||
|
@ -1,6 +1,6 @@
|
||||
id: org.prismlauncher.PrismLauncher
|
||||
runtime: org.kde.Platform
|
||||
runtime-version: "5.15-22.08"
|
||||
runtime-version: 5.15-23.08
|
||||
sdk: org.kde.Sdk
|
||||
sdk-extensions:
|
||||
- org.freedesktop.Sdk.Extension.openjdk17
|
||||
@ -104,9 +104,9 @@ modules:
|
||||
- install -Dm755 ../data/gamemoderun -t /app/bin
|
||||
sources:
|
||||
- type: archive
|
||||
archive-type: tar-gzip
|
||||
url: https://api.github.com/repos/FeralInteractive/gamemode/tarball/1.7
|
||||
sha256: 57ce73ba605d1cf12f8d13725006a895182308d93eba0f69f285648449641803
|
||||
dest-filename: gamemode.tar.gz
|
||||
url: https://api.github.com/repos/FeralInteractive/gamemode/tarball/1.8.1
|
||||
sha256: 969cf85b5ca3944f3e315cd73a0ee9bea4f9c968cd7d485e9f4745bc1e679c4e
|
||||
x-checker-data:
|
||||
type: json
|
||||
url: https://api.github.com/repos/FeralInteractive/gamemode/releases/latest
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 45094ca570be383d06df729b6972830ec63bd3df
|
||||
Subproject commit 55a8e460c6343229597a13e973ba4855c27a1c4c
|
@ -1,5 +1,6 @@
|
||||
builds:
|
||||
exclude: []
|
||||
exclude:
|
||||
- "*.x86_64-darwin.*"
|
||||
include:
|
||||
- "checks.x86_64-linux.*"
|
||||
- "devShells.*.*"
|
||||
|
@ -134,6 +134,15 @@
|
||||
#include "gamemode_client.h"
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_LINUX)
|
||||
#include <sys/statvfs.h>
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
||||
#include <sys/mount.h>
|
||||
#include <sys/types.h>
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_MAC)
|
||||
#if defined(SPARKLE_ENABLED)
|
||||
#include "updater/MacSparkleUpdater.h"
|
||||
@ -487,8 +496,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
||||
}
|
||||
|
||||
{
|
||||
qDebug() << qPrintable(BuildConfig.LAUNCHER_DISPLAYNAME) << ", (c) 2022-2023 "
|
||||
<< qPrintable(QString(BuildConfig.LAUNCHER_COPYRIGHT).replace("\n", ", "));
|
||||
qDebug() << qPrintable(BuildConfig.LAUNCHER_DISPLAYNAME + ", " + QString(BuildConfig.LAUNCHER_COPYRIGHT).replace("\n", ", "));
|
||||
qDebug() << "Version : " << BuildConfig.printableVersionString();
|
||||
qDebug() << "Platform : " << BuildConfig.BUILD_PLATFORM;
|
||||
qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT;
|
||||
@ -646,6 +654,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
||||
|
||||
// Minecraft mods
|
||||
m_settings->registerSetting("ModMetadataDisabled", false);
|
||||
m_settings->registerSetting("ModDependenciesDisabled", false);
|
||||
|
||||
// Minecraft offline player name
|
||||
m_settings->registerSetting("LastOfflinePlayerName", "");
|
||||
@ -659,6 +668,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
||||
|
||||
// The cat
|
||||
m_settings->registerSetting("TheCat", false);
|
||||
m_settings->registerSetting("CatOpacity", 100);
|
||||
|
||||
m_settings->registerSetting("StatusBarVisible", true);
|
||||
|
||||
m_settings->registerSetting("ToolbarsLocked", false);
|
||||
|
||||
@ -741,6 +753,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
||||
m_settings->registerSetting("ModrinthToken", "");
|
||||
m_settings->registerSetting("UserAgentOverride", "");
|
||||
|
||||
// FTBApp instances
|
||||
m_settings->registerSetting("FTBAppInstancesPath", "");
|
||||
|
||||
// Init page provider
|
||||
{
|
||||
m_globalSettingsProvider = std::make_shared<GenericPageProvider>(tr("Settings"));
|
||||
@ -989,6 +1004,37 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
||||
}
|
||||
}
|
||||
|
||||
// notify user if /tmp is mounted with `noexec` (#1693)
|
||||
{
|
||||
bool is_tmp_noexec = false;
|
||||
|
||||
#if defined(Q_OS_LINUX)
|
||||
|
||||
struct statvfs tmp_stat;
|
||||
statvfs("/tmp", &tmp_stat);
|
||||
is_tmp_noexec = tmp_stat.f_flag & ST_NOEXEC;
|
||||
|
||||
#elif defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
||||
|
||||
struct statfs tmp_stat;
|
||||
statfs("/tmp", &tmp_stat);
|
||||
is_tmp_noexec = tmp_stat.f_flags & MNT_NOEXEC;
|
||||
|
||||
#endif
|
||||
|
||||
if (is_tmp_noexec) {
|
||||
auto infoMsg =
|
||||
tr("Your /tmp directory is currently mounted with the 'noexec' flag enabled.\n"
|
||||
"Some versions of Minecraft may not launch.\n");
|
||||
auto msgBox = new QMessageBox(QMessageBox::Information, tr("Incompatible system configuration"), infoMsg, QMessageBox::Ok);
|
||||
msgBox->setDefaultButton(QMessageBox::Ok);
|
||||
msgBox->setAttribute(Qt::WA_DeleteOnClose);
|
||||
msgBox->setMinimumWidth(460);
|
||||
msgBox->adjustSize();
|
||||
msgBox->open();
|
||||
}
|
||||
}
|
||||
|
||||
if (createSetupWizard()) {
|
||||
return;
|
||||
}
|
||||
@ -1472,6 +1518,17 @@ InstanceWindow* Application::showInstanceWindow(InstancePtr instance, QString pa
|
||||
auto& window = extras.window;
|
||||
|
||||
if (window) {
|
||||
// If the window is minimized on macOS or Windows, activate and bring it up
|
||||
#ifdef Q_OS_MACOS
|
||||
if (window->isMinimized()) {
|
||||
window->setWindowState(window->windowState() & ~Qt::WindowMinimized);
|
||||
}
|
||||
#elif defined(Q_OS_WIN)
|
||||
if (window->isMinimized()) {
|
||||
window->showNormal();
|
||||
}
|
||||
#endif
|
||||
|
||||
window->raise();
|
||||
window->activateWindow();
|
||||
} else {
|
||||
@ -1479,6 +1536,7 @@ InstanceWindow* Application::showInstanceWindow(InstancePtr instance, QString pa
|
||||
m_openWindows++;
|
||||
connect(window, &InstanceWindow::isClosing, this, &Application::on_windowClose);
|
||||
}
|
||||
|
||||
if (!page.isEmpty()) {
|
||||
window->selectPage(page);
|
||||
}
|
||||
|
@ -64,6 +64,8 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s
|
||||
|
||||
m_settings->registerSetting("lastLaunchTime", 0);
|
||||
m_settings->registerSetting("totalTimePlayed", 0);
|
||||
if (m_settings->get("totalTimePlayed").toLongLong() < 0)
|
||||
m_settings->reset("totalTimePlayed");
|
||||
m_settings->registerSetting("lastTimePlayed", 0);
|
||||
|
||||
m_settings->registerSetting("linkedInstances", "[]");
|
||||
|
@ -18,7 +18,7 @@
|
||||
class DataMigrationTask : public Task {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DataMigrationTask(QObject* parent, const QString& sourcePath, const QString& targetPath, const IPathMatcher::Ptr pathmatcher);
|
||||
explicit DataMigrationTask(QObject* parent, const QString& sourcePath, const QString& targetPath, IPathMatcher::Ptr pathmatcher);
|
||||
~DataMigrationTask() override = default;
|
||||
|
||||
protected:
|
||||
|
@ -37,143 +37,33 @@
|
||||
#include <QDesktopServices>
|
||||
#include <QDir>
|
||||
#include <QProcess>
|
||||
|
||||
/**
|
||||
* This shouldn't exist, but until QTBUG-9328 and other unreported bugs are fixed, it needs to be a thing.
|
||||
*/
|
||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
template <typename T>
|
||||
bool IndirectOpen(T callable, qint64* pid_forked = nullptr)
|
||||
{
|
||||
auto pid = fork();
|
||||
if (pid_forked) {
|
||||
if (pid > 0)
|
||||
*pid_forked = pid;
|
||||
else
|
||||
*pid_forked = 0;
|
||||
}
|
||||
if (pid == -1) {
|
||||
qWarning() << "IndirectOpen failed to fork: " << errno;
|
||||
return false;
|
||||
}
|
||||
// child - do the stuff
|
||||
if (pid == 0) {
|
||||
// unset all this garbage so it doesn't get passed to the child process
|
||||
qunsetenv("LD_PRELOAD");
|
||||
qunsetenv("LD_LIBRARY_PATH");
|
||||
qunsetenv("LD_DEBUG");
|
||||
qunsetenv("QT_PLUGIN_PATH");
|
||||
qunsetenv("QT_FONTPATH");
|
||||
|
||||
// open the URL
|
||||
auto status = callable();
|
||||
|
||||
// detach from the parent process group.
|
||||
setsid();
|
||||
|
||||
// die. now. do not clean up anything, it would just hang forever.
|
||||
_exit(status ? 0 : 1);
|
||||
} else {
|
||||
// parent - assume it worked.
|
||||
int status;
|
||||
while (waitpid(pid, &status, 0)) {
|
||||
if (WIFEXITED(status)) {
|
||||
return WEXITSTATUS(status) == 0;
|
||||
}
|
||||
if (WIFSIGNALED(status)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#include "FileSystem.h"
|
||||
|
||||
namespace DesktopServices {
|
||||
bool openDirectory(const QString& path, [[maybe_unused]] bool ensureExists)
|
||||
bool openPath(const QFileInfo& path, bool ensureFolderPathExists)
|
||||
{
|
||||
qDebug() << "Opening directory" << path;
|
||||
QDir parentPath;
|
||||
QDir dir(path);
|
||||
if (ensureExists && !dir.exists()) {
|
||||
parentPath.mkpath(dir.absolutePath());
|
||||
qDebug() << "Opening path" << path;
|
||||
if (ensureFolderPathExists) {
|
||||
FS::ensureFolderPathExists(path);
|
||||
}
|
||||
auto f = [&]() { return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath())); };
|
||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||
if (!isSandbox()) {
|
||||
return IndirectOpen(f);
|
||||
} else {
|
||||
return f();
|
||||
}
|
||||
#else
|
||||
return f();
|
||||
#endif
|
||||
return openUrl(QUrl::fromLocalFile(QFileInfo(path).absoluteFilePath()));
|
||||
}
|
||||
|
||||
bool openFile(const QString& path)
|
||||
bool openPath(const QString& path, bool ensureFolderPathExists)
|
||||
{
|
||||
qDebug() << "Opening file" << path;
|
||||
auto f = [&]() { return QDesktopServices::openUrl(QUrl::fromLocalFile(path)); };
|
||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||
if (!isSandbox()) {
|
||||
return IndirectOpen(f);
|
||||
} else {
|
||||
return f();
|
||||
}
|
||||
#else
|
||||
return f();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool openFile(const QString& application, const QString& path, const QString& workingDirectory, qint64* pid)
|
||||
{
|
||||
qDebug() << "Opening file" << path << "using" << application;
|
||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
|
||||
if (!isSandbox()) {
|
||||
return IndirectOpen([&]() { return QProcess::startDetached(application, QStringList() << path, workingDirectory); }, pid);
|
||||
} else {
|
||||
return QProcess::startDetached(application, QStringList() << path, workingDirectory, pid);
|
||||
}
|
||||
#else
|
||||
return QProcess::startDetached(application, QStringList() << path, workingDirectory, pid);
|
||||
#endif
|
||||
return openPath(QFileInfo(path), ensureFolderPathExists);
|
||||
}
|
||||
|
||||
bool run(const QString& application, const QStringList& args, const QString& workingDirectory, qint64* pid)
|
||||
{
|
||||
qDebug() << "Running" << application << "with args" << args.join(' ');
|
||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||
if (!isSandbox()) {
|
||||
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
|
||||
return IndirectOpen([&]() { return QProcess::startDetached(application, args, workingDirectory); }, pid);
|
||||
} else {
|
||||
return QProcess::startDetached(application, args, workingDirectory, pid);
|
||||
}
|
||||
#else
|
||||
return QProcess::startDetached(application, args, workingDirectory, pid);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool openUrl(const QUrl& url)
|
||||
{
|
||||
qDebug() << "Opening URL" << url.toString();
|
||||
auto f = [&]() { return QDesktopServices::openUrl(url); };
|
||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||
if (!isSandbox()) {
|
||||
return IndirectOpen(f);
|
||||
} else {
|
||||
return f();
|
||||
}
|
||||
#else
|
||||
return f();
|
||||
#endif
|
||||
return QDesktopServices::openUrl(url);
|
||||
}
|
||||
|
||||
bool isFlatpak()
|
||||
@ -194,9 +84,4 @@ bool isSnap()
|
||||
#endif
|
||||
}
|
||||
|
||||
bool isSandbox()
|
||||
{
|
||||
return isSnap() || isFlatpak();
|
||||
}
|
||||
|
||||
} // namespace DesktopServices
|
||||
|
@ -3,31 +3,30 @@
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
class QFileInfo;
|
||||
|
||||
/**
|
||||
* This wraps around QDesktopServices and adds workarounds where needed
|
||||
* Use this instead of QDesktopServices!
|
||||
*/
|
||||
namespace DesktopServices {
|
||||
/**
|
||||
* Open a file in whatever application is applicable
|
||||
* Open a path in whatever application is applicable.
|
||||
* @param ensureFolderPathExists Make sure the path exists
|
||||
*/
|
||||
bool openFile(const QString& path);
|
||||
bool openPath(const QFileInfo& path, bool ensureFolderPathExists = false);
|
||||
|
||||
/**
|
||||
* Open a file in the specified application
|
||||
* Open a path in whatever application is applicable.
|
||||
* @param ensureFolderPathExists Make sure the path exists
|
||||
*/
|
||||
bool openFile(const QString& application, const QString& path, const QString& workingDirectory = QString(), qint64* pid = 0);
|
||||
bool openPath(const QString& path, bool ensureFolderPathExists = false);
|
||||
|
||||
/**
|
||||
* Run an application
|
||||
*/
|
||||
bool run(const QString& application, const QStringList& args, const QString& workingDirectory = QString(), qint64* pid = 0);
|
||||
|
||||
/**
|
||||
* Open a directory
|
||||
*/
|
||||
bool openDirectory(const QString& path, bool ensureExists = false);
|
||||
|
||||
/**
|
||||
* Open the URL, most likely in a browser. Maybe.
|
||||
*/
|
||||
@ -42,9 +41,4 @@ bool isFlatpak();
|
||||
* Determine whether the launcher is running in a Snap environment
|
||||
*/
|
||||
bool isSnap();
|
||||
|
||||
/**
|
||||
* Determine whether the launcher is running in a sandboxed (Flatpak or Snap) environment
|
||||
*/
|
||||
bool isSandbox();
|
||||
} // namespace DesktopServices
|
||||
|
@ -272,15 +272,19 @@ bool ensureFilePathExists(QString filenamepath)
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ensureFolderPathExists(QString foldernamepath)
|
||||
bool ensureFolderPathExists(const QFileInfo folderPath)
|
||||
{
|
||||
QFileInfo a(foldernamepath);
|
||||
QDir dir;
|
||||
QString ensuredPath = a.filePath();
|
||||
QString ensuredPath = folderPath.filePath();
|
||||
bool success = dir.mkpath(ensuredPath);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ensureFolderPathExists(const QString folderPathName)
|
||||
{
|
||||
return ensureFolderPathExists(QFileInfo(folderPathName));
|
||||
}
|
||||
|
||||
bool copyFileAttributes(QString src, QString dst)
|
||||
{
|
||||
#ifdef Q_OS_WIN32
|
||||
@ -943,6 +947,8 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
|
||||
<< "\n";
|
||||
stream << "Type=Application"
|
||||
<< "\n";
|
||||
stream << "Categories=Game;ActionGame;AdventureGame;Simulation"
|
||||
<< "\n";
|
||||
stream << "Exec=\"" << target.toLocal8Bit() << "\"" << argstring.toLocal8Bit() << "\n";
|
||||
stream << "Name=" << name.toLocal8Bit() << "\n";
|
||||
if (!icon.isEmpty()) {
|
||||
|
@ -91,7 +91,13 @@ bool ensureFilePathExists(QString filenamepath);
|
||||
* Creates all the folders in a path for the specified path
|
||||
* last segment of the path is treated as a folder name and is created!
|
||||
*/
|
||||
bool ensureFolderPathExists(QString filenamepath);
|
||||
bool ensureFolderPathExists(const QFileInfo folderPath);
|
||||
|
||||
/**
|
||||
* Creates all the folders in a path for the specified path
|
||||
* last segment of the path is treated as a folder name and is created!
|
||||
*/
|
||||
bool ensureFolderPathExists(const QString folderPathName);
|
||||
|
||||
/**
|
||||
* @brief Copies a directory and it's contents from src to dest
|
||||
|
@ -142,9 +142,8 @@ void InstanceCopyTask::copyFinished()
|
||||
if (!m_keepPlaytime) {
|
||||
inst->resetTimePlayed();
|
||||
}
|
||||
if (m_useLinks)
|
||||
inst->addLinkedInstanceId(m_origInstance->id());
|
||||
if (m_useLinks) {
|
||||
inst->addLinkedInstanceId(m_origInstance->id());
|
||||
auto allowed_symlinks_file = QFileInfo(FS::PathCombine(inst->gameRoot(), "allowed_symlinks.txt"));
|
||||
|
||||
QByteArray allowed_symlinks;
|
||||
|
@ -59,7 +59,7 @@
|
||||
|
||||
#include <quazip/quazipdir.h>
|
||||
|
||||
InstanceImportTask::InstanceImportTask(const QUrl sourceUrl, QWidget* parent, QMap<QString, QString>&& extra_info)
|
||||
InstanceImportTask::InstanceImportTask(const QUrl& sourceUrl, QWidget* parent, QMap<QString, QString>&& extra_info)
|
||||
: m_sourceUrl(sourceUrl), m_extra_info(extra_info), m_parent(parent)
|
||||
{}
|
||||
|
||||
|
@ -54,7 +54,7 @@ class FileResolvingTask;
|
||||
class InstanceImportTask : public InstanceTask {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit InstanceImportTask(const QUrl sourceUrl, QWidget* parent = nullptr, QMap<QString, QString>&& extra_info = {});
|
||||
explicit InstanceImportTask(const QUrl& sourceUrl, QWidget* parent = nullptr, QMap<QString, QString>&& extra_info = {});
|
||||
|
||||
bool abort() override;
|
||||
const QVector<Flame::File>& getBlockedFiles() const { return m_blockedMods; }
|
||||
|
@ -290,7 +290,7 @@ void InstanceList::deleteGroup(const GroupId& name)
|
||||
qDebug() << "Remove" << instID << "from group" << name;
|
||||
removed = true;
|
||||
auto idx = getInstIndex(instance.get());
|
||||
if (idx > 0)
|
||||
if (idx >= 0)
|
||||
emit dataChanged(index(idx), index(idx), { GroupRole });
|
||||
}
|
||||
}
|
||||
@ -315,7 +315,7 @@ void InstanceList::renameGroup(const QString& src, const QString& dst)
|
||||
qDebug() << "Set" << instID << "group to" << dst;
|
||||
modified = true;
|
||||
auto idx = getInstIndex(instance.get());
|
||||
if (idx > 0)
|
||||
if (idx >= 0)
|
||||
emit dataChanged(index(idx), index(idx), { GroupRole });
|
||||
}
|
||||
}
|
||||
@ -823,6 +823,9 @@ void InstanceList::on_InstFolderChanged([[maybe_unused]] const Setting& setting,
|
||||
}
|
||||
m_instDir = newInstDir;
|
||||
m_groupsLoaded = false;
|
||||
beginRemoveRows(QModelIndex(), 0, count());
|
||||
m_instances.erase(m_instances.begin(), m_instances.end());
|
||||
endRemoveRows();
|
||||
emit instancesChanged();
|
||||
}
|
||||
}
|
||||
@ -947,9 +950,12 @@ QString InstanceList::getStagedInstancePath()
|
||||
|
||||
bool InstanceList::commitStagedInstance(const QString& path,
|
||||
InstanceName const& instanceName,
|
||||
const QString& groupName,
|
||||
QString groupName,
|
||||
InstanceTask const& commiting)
|
||||
{
|
||||
if (groupName.isEmpty() && !groupName.isNull())
|
||||
groupName = QString();
|
||||
|
||||
QDir dir;
|
||||
QString instID;
|
||||
InstancePtr inst;
|
||||
|
@ -130,7 +130,7 @@ class InstanceList : public QAbstractListModel {
|
||||
* should_override is used when another similar instance already exists, and we want to override it
|
||||
* - for instance, when updating it.
|
||||
*/
|
||||
bool commitStagedInstance(const QString& keyPath, const InstanceName& instanceName, const QString& groupName, const InstanceTask&);
|
||||
bool commitStagedInstance(const QString& keyPath, const InstanceName& instanceName, QString groupName, const InstanceTask&);
|
||||
|
||||
/**
|
||||
* Destroy a previously created staging area given by @keyPath - used when creation fails.
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
|
||||
#include <QPushButton>
|
||||
|
||||
InstanceNameChange askForChangingInstanceName(QWidget* parent, const QString& old_name, const QString& new_name)
|
||||
{
|
||||
auto dialog =
|
||||
@ -27,16 +29,15 @@ ShouldUpdate askIfShouldUpdate(QWidget* parent, QString original_version_name)
|
||||
"separate instance, or update the existing one?\n\nNOTE: Make sure you made a backup of your important instance data before "
|
||||
"updating, as worlds can be corrupted and some configuration may be lost (due to pack overrides).")
|
||||
.arg(original_version_name),
|
||||
QMessageBox::Information, QMessageBox::Ok | QMessageBox::Reset | QMessageBox::Abort);
|
||||
info->setButtonText(QMessageBox::Ok, QObject::tr("Update existing instance"));
|
||||
info->setButtonText(QMessageBox::Abort, QObject::tr("Create new instance"));
|
||||
info->setButtonText(QMessageBox::Reset, QObject::tr("Cancel"));
|
||||
QMessageBox::Information, QMessageBox::Cancel);
|
||||
QAbstractButton* update = info->addButton(QObject::tr("Update existing instance"), QMessageBox::AcceptRole);
|
||||
QAbstractButton* skip = info->addButton(QObject::tr("Create new instance"), QMessageBox::ResetRole);
|
||||
|
||||
info->exec();
|
||||
|
||||
if (info->clickedButton() == info->button(QMessageBox::Ok))
|
||||
if (info->clickedButton() == update)
|
||||
return ShouldUpdate::Update;
|
||||
if (info->clickedButton() == info->button(QMessageBox::Abort))
|
||||
if (info->clickedButton() == skip)
|
||||
return ShouldUpdate::SkipUpdating;
|
||||
return ShouldUpdate::Cancel;
|
||||
}
|
||||
|
@ -36,12 +36,12 @@
|
||||
|
||||
#include "LaunchController.h"
|
||||
#include "Application.h"
|
||||
#include "minecraft/auth/AccountData.h"
|
||||
#include "minecraft/auth/AccountList.h"
|
||||
|
||||
#include "ui/InstanceWindow.h"
|
||||
#include "ui/MainWindow.h"
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
#include "ui/dialogs/EditAccountDialog.h"
|
||||
#include "ui/dialogs/ProfileSelectDialog.h"
|
||||
#include "ui/dialogs/ProfileSetupDialog.h"
|
||||
#include "ui/dialogs/ProgressDialog.h"
|
||||
@ -89,7 +89,7 @@ void LaunchController::decideAccount()
|
||||
// Tell the user they need to log in at least one account in order to play.
|
||||
auto reply = CustomMessageBox::selectable(m_parentWidget, tr("No Accounts"),
|
||||
tr("In order to play Minecraft, you must have at least one Microsoft "
|
||||
"account which owns Minecraft logged in."
|
||||
"account which owns Minecraft logged in. "
|
||||
"Would you like to open the account manager to add an account now?"),
|
||||
QMessageBox::Information, QMessageBox::Yes | QMessageBox::No)
|
||||
->exec();
|
||||
@ -143,6 +143,12 @@ void LaunchController::login()
|
||||
bool tryagain = true;
|
||||
unsigned int tries = 0;
|
||||
|
||||
if (m_accountToUse->accountType() != AccountType::Offline && m_accountToUse->accountState() == AccountState::Offline) {
|
||||
// Force account refresh on the account used to launch the instance updating the AccountState
|
||||
// only on first try and if it is not meant to be offline
|
||||
auto accounts = APPLICATION->accounts();
|
||||
accounts->requestRefresh(m_accountToUse->internalId());
|
||||
}
|
||||
while (tryagain) {
|
||||
if (tries > 0 && tries % 3 == 0) {
|
||||
auto result =
|
||||
@ -161,7 +167,7 @@ void LaunchController::login()
|
||||
m_accountToUse->fillSession(m_session);
|
||||
|
||||
// Launch immediately in true offline mode
|
||||
if (m_accountToUse->isOffline()) {
|
||||
if (m_accountToUse->accountType() == AccountType::Offline) {
|
||||
launchInstance();
|
||||
return;
|
||||
}
|
||||
@ -249,12 +255,6 @@ void LaunchController::login()
|
||||
progDialog.execWithTask(task.get());
|
||||
continue;
|
||||
}
|
||||
// FIXME: this is missing - the meaning is that the account is queued for refresh and we should wait for that
|
||||
/*
|
||||
case AccountState::Queued: {
|
||||
return;
|
||||
}
|
||||
*/
|
||||
case AccountState::Expired: {
|
||||
auto errorString = tr("The account has expired and needs to be logged into manually again.");
|
||||
QMessageBox::warning(m_parentWidget, tr("Account refresh failed"), errorString, QMessageBox::StandardButton::Ok,
|
||||
|
@ -50,7 +50,7 @@
|
||||
|
||||
namespace MMCZip {
|
||||
// ours
|
||||
bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet<QString>& contained, const FilterFunction filter)
|
||||
bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet<QString>& contained, const FilterFunction& filter)
|
||||
{
|
||||
QuaZip modZip(from.filePath());
|
||||
modZip.open(QuaZip::mdUnzip);
|
||||
|
@ -60,7 +60,7 @@ using FilterFunction = std::function<bool(const QString&)>;
|
||||
/**
|
||||
* Merge two zip files, using a filter function
|
||||
*/
|
||||
bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet<QString>& contained, const FilterFunction filter = nullptr);
|
||||
bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet<QString>& contained, const FilterFunction& filter = nullptr);
|
||||
|
||||
/**
|
||||
* Compress directory, by providing a list of files to compress
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include <QPixmapCache>
|
||||
#include <QThread>
|
||||
#include <QTime>
|
||||
#include <limits>
|
||||
|
||||
#define GET_TYPE() \
|
||||
Qt::ConnectionType type; \
|
||||
@ -100,10 +101,14 @@ class PixmapCache final : public QObject {
|
||||
*/
|
||||
bool _markCacheMissByEviciton()
|
||||
{
|
||||
static constexpr uint maxInt = static_cast<uint>(std::numeric_limits<int>::max());
|
||||
static constexpr uint step = 10240;
|
||||
static constexpr int oneSecond = 1000;
|
||||
|
||||
auto now = QTime::currentTime();
|
||||
if (!m_last_cache_miss_by_eviciton.isNull()) {
|
||||
auto diff = m_last_cache_miss_by_eviciton.msecsTo(now);
|
||||
if (diff < 1000) { // less than a second ago
|
||||
if (diff < oneSecond) { // less than a second ago
|
||||
++m_consecutive_fast_evicitons;
|
||||
} else {
|
||||
m_consecutive_fast_evicitons = 0;
|
||||
@ -111,11 +116,17 @@ class PixmapCache final : public QObject {
|
||||
}
|
||||
m_last_cache_miss_by_eviciton = now;
|
||||
if (m_consecutive_fast_evicitons >= m_consecutive_fast_evicitons_threshold) {
|
||||
// double the cache size
|
||||
auto newSize = _cacheLimit() * 2;
|
||||
qDebug() << m_consecutive_fast_evicitons << "pixmap cache misses by eviction happened too fast, doubling cache size to"
|
||||
<< newSize;
|
||||
_setCacheLimit(newSize);
|
||||
// increase the cache size
|
||||
uint newSize = _cacheLimit() + step;
|
||||
if (newSize >= maxInt) { // increase it until you overflow :D
|
||||
newSize = maxInt;
|
||||
qDebug() << m_consecutive_fast_evicitons
|
||||
<< tr("pixmap cache misses by eviction happened too fast, doing nothing as the cache size reached it's limit");
|
||||
} else {
|
||||
qDebug() << m_consecutive_fast_evicitons
|
||||
<< tr("pixmap cache misses by eviction happened too fast, increasing cache size to") << static_cast<int>(newSize);
|
||||
}
|
||||
_setCacheLimit(static_cast<int>(newSize));
|
||||
m_consecutive_fast_evicitons = 0;
|
||||
return true;
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ class RecursiveFileSystemWatcher : public QObject {
|
||||
QDir rootDir() const { return m_root; }
|
||||
|
||||
// WARNING: setting this to true may be bad for performance
|
||||
void setWatchFiles(const bool watchFiles);
|
||||
void setWatchFiles(bool watchFiles);
|
||||
bool watchFiles() const { return m_watchFiles; }
|
||||
|
||||
void setMatcher(IPathMatcher::Ptr matcher) { m_matcher = matcher; }
|
||||
|
@ -32,7 +32,7 @@ class ResourceDownloadTask : public SequentialTask {
|
||||
public:
|
||||
explicit ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
|
||||
ModPlatform::IndexedVersion version,
|
||||
const std::shared_ptr<ResourceFolderModel> packs,
|
||||
std::shared_ptr<ResourceFolderModel> packs,
|
||||
bool is_indexed = true,
|
||||
QString custom_target_folder = {});
|
||||
const QString& getFilename() const { return m_pack_version.fileName; }
|
||||
|
@ -28,7 +28,7 @@ class VersionProxyModel : public QAbstractProxyModel {
|
||||
|
||||
const FilterMap& filters() const;
|
||||
const QString& search() const;
|
||||
void setFilter(const BaseVersionList::ModelRoles column, Filter* filter);
|
||||
void setFilter(BaseVersionList::ModelRoles column, Filter* filter);
|
||||
void setSearch(const QString& search);
|
||||
void clearFilters();
|
||||
QModelIndex getRecommended() const;
|
||||
|
@ -67,7 +67,7 @@ class IconList : public QAbstractListModel {
|
||||
virtual Qt::ItemFlags flags(const QModelIndex& index) const override;
|
||||
|
||||
bool addThemeIcon(const QString& key);
|
||||
bool addIcon(const QString& key, const QString& name, const QString& path, const IconType type);
|
||||
bool addIcon(const QString& key, const QString& name, const QString& path, IconType type);
|
||||
void saveIcon(const QString& key, const QString& path, const char* format) const;
|
||||
bool deleteIcon(const QString& key);
|
||||
bool trashIcon(const QString& key);
|
||||
|
@ -34,6 +34,7 @@
|
||||
*/
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
@ -335,6 +336,7 @@ QList<QString> JavaUtils::FindJavaPaths()
|
||||
}
|
||||
}
|
||||
|
||||
candidates.append(getMinecraftJavaBundle());
|
||||
candidates = addJavasFromEnv(candidates);
|
||||
candidates.removeDuplicates();
|
||||
return candidates;
|
||||
@ -360,6 +362,7 @@ QList<QString> JavaUtils::FindJavaPaths()
|
||||
javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java");
|
||||
javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java");
|
||||
}
|
||||
javas.append(getMinecraftJavaBundle());
|
||||
javas = addJavasFromEnv(javas);
|
||||
javas.removeDuplicates();
|
||||
return javas;
|
||||
@ -411,6 +414,7 @@ QList<QString> JavaUtils::FindJavaPaths()
|
||||
// javas downloaded by sdkman
|
||||
scanJavaDirs(FS::PathCombine(home, ".sdkman/candidates/java"));
|
||||
|
||||
javas.append(getMinecraftJavaBundle());
|
||||
javas = addJavasFromEnv(javas);
|
||||
javas.removeDuplicates();
|
||||
return javas;
|
||||
@ -423,6 +427,7 @@ QList<QString> JavaUtils::FindJavaPaths()
|
||||
QList<QString> javas;
|
||||
javas.append(this->GetDefaultJava()->path);
|
||||
|
||||
javas.append(getMinecraftJavaBundle());
|
||||
return addJavasFromEnv(javas);
|
||||
}
|
||||
#endif
|
||||
@ -431,3 +436,50 @@ QString JavaUtils::getJavaCheckPath()
|
||||
{
|
||||
return APPLICATION->getJarPath("JavaCheck.jar");
|
||||
}
|
||||
|
||||
QStringList getMinecraftJavaBundle()
|
||||
{
|
||||
QString partialPath;
|
||||
QString executable = "java";
|
||||
QStringList processpaths;
|
||||
#if defined(Q_OS_OSX)
|
||||
partialPath = FS::PathCombine(QDir::homePath(), "Library/Application Support");
|
||||
#elif defined(Q_OS_WIN32)
|
||||
partialPath = QProcessEnvironment::systemEnvironment().value("LOCALAPPDATA", "");
|
||||
executable += "w.exe";
|
||||
|
||||
// add the microsoft store version of the launcher to the search. the current path is:
|
||||
// C:\Users\USERNAME\AppData\Local\Packages\Microsoft.4297127D64EC6_8wekyb3d8bbwe\LocalCache\Local\runtime
|
||||
auto minecraftMSStorePath =
|
||||
FS::PathCombine(QFileInfo(partialPath).absolutePath(), "Local", "Packages", "Microsoft.4297127D64EC6_8wekyb3d8bbwe");
|
||||
minecraftMSStorePath = FS::PathCombine(minecraftMSStorePath, "LocalCache", "Local", "runtime");
|
||||
processpaths << minecraftMSStorePath;
|
||||
#else
|
||||
partialPath = QDir::homePath();
|
||||
#endif
|
||||
auto minecraftDataPath = FS::PathCombine(partialPath, ".minecraft", "runtime");
|
||||
processpaths << minecraftDataPath;
|
||||
|
||||
QStringList javas;
|
||||
while (!processpaths.isEmpty()) {
|
||||
auto dirPath = processpaths.takeFirst();
|
||||
QDir dir(dirPath);
|
||||
if (!dir.exists())
|
||||
continue;
|
||||
auto entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
auto binFound = false;
|
||||
for (auto& entry : entries) {
|
||||
if (entry.baseName() == "bin") {
|
||||
javas.append(FS::PathCombine(entry.canonicalFilePath(), executable));
|
||||
binFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!binFound) {
|
||||
for (auto& entry : entries) {
|
||||
processpaths << entry.canonicalFilePath();
|
||||
}
|
||||
}
|
||||
}
|
||||
return javas;
|
||||
}
|
||||
|
@ -25,6 +25,7 @@
|
||||
|
||||
QString stripVariableEntries(QString name, QString target, QString remove);
|
||||
QProcessEnvironment CleanEnviroment();
|
||||
QStringList getMinecraftJavaBundle();
|
||||
|
||||
class JavaUtils : public QObject {
|
||||
Q_OBJECT
|
||||
|
@ -55,6 +55,6 @@ class Index : public QAbstractListModel, public BaseEntity {
|
||||
QVector<VersionList::Ptr> m_lists;
|
||||
QHash<QString, VersionList::Ptr> m_uids;
|
||||
|
||||
void connectVersionList(const int row, const VersionList::Ptr& list);
|
||||
void connectVersionList(int row, const VersionList::Ptr& list);
|
||||
};
|
||||
} // namespace Meta
|
||||
|
@ -64,7 +64,7 @@ class Version : public QObject, public BaseVersion, public BaseEntity {
|
||||
|
||||
public: // for usage by format parsers only
|
||||
void setType(const QString& type);
|
||||
void setTime(const qint64 time);
|
||||
void setTime(qint64 time);
|
||||
void setRequires(const Meta::RequireSet& reqs, const Meta::RequireSet& conflicts);
|
||||
void setVolatile(bool volatile_);
|
||||
void setRecommended(bool recommended);
|
||||
|
@ -79,7 +79,7 @@ class VersionList : public BaseVersionList, public BaseEntity {
|
||||
|
||||
Version::Ptr m_recommended;
|
||||
|
||||
void setupAddedVersion(const int row, const Version::Ptr& version);
|
||||
void setupAddedVersion(int row, const Version::Ptr& version);
|
||||
};
|
||||
} // namespace Meta
|
||||
Q_DECLARE_METATYPE(Meta::VersionList::Ptr)
|
||||
|
@ -551,6 +551,7 @@ QMap<QString, QString> MinecraftInstance::getVariables()
|
||||
out.insert("INST_MC_DIR", QDir::toNativeSeparators(QDir(gameRoot()).absolutePath()));
|
||||
out.insert("INST_JAVA", settings()->get("JavaPath").toString());
|
||||
out.insert("INST_JAVA_ARGS", javaArguments().join(' '));
|
||||
out.insert("NO_COLOR", "1");
|
||||
return out;
|
||||
}
|
||||
|
||||
@ -564,43 +565,6 @@ QProcessEnvironment MinecraftInstance::createEnvironment()
|
||||
for (auto it = variables.begin(); it != variables.end(); ++it) {
|
||||
env.insert(it.key(), it.value());
|
||||
}
|
||||
return env;
|
||||
}
|
||||
|
||||
QProcessEnvironment MinecraftInstance::createLaunchEnvironment()
|
||||
{
|
||||
// prepare the process environment
|
||||
QProcessEnvironment env = createEnvironment();
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
if (settings()->get("EnableMangoHud").toBool() && APPLICATION->capabilities() & Application::SupportsMangoHud) {
|
||||
auto preloadList = env.value("LD_PRELOAD").split(QLatin1String(":"));
|
||||
auto libPaths = env.value("LD_LIBRARY_PATH").split(QLatin1String(":"));
|
||||
|
||||
auto mangoHudLibString = MangoHud::getLibraryString();
|
||||
if (!mangoHudLibString.isEmpty()) {
|
||||
QFileInfo mangoHudLib(mangoHudLibString);
|
||||
|
||||
// dlsym variant is only needed for OpenGL and not included in the vulkan layer
|
||||
preloadList << "libMangoHud_dlsym.so" << mangoHudLib.fileName();
|
||||
libPaths << mangoHudLib.absolutePath();
|
||||
}
|
||||
|
||||
env.insert("LD_PRELOAD", preloadList.join(QLatin1String(":")));
|
||||
env.insert("LD_LIBRARY_PATH", libPaths.join(QLatin1String(":")));
|
||||
env.insert("MANGOHUD", "1");
|
||||
}
|
||||
|
||||
if (settings()->get("UseDiscreteGpu").toBool()) {
|
||||
// Open Source Drivers
|
||||
env.insert("DRI_PRIME", "1");
|
||||
// Proprietary Nvidia Drivers
|
||||
env.insert("__NV_PRIME_RENDER_OFFLOAD", "1");
|
||||
env.insert("__VK_LAYER_NV_optimus", "NVIDIA_only");
|
||||
env.insert("__GLX_VENDOR_LIBRARY_NAME", "nvidia");
|
||||
}
|
||||
#endif
|
||||
|
||||
// custom env
|
||||
|
||||
auto insertEnv = [&env](QMap<QString, QVariant> envMap) {
|
||||
@ -617,7 +581,53 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment()
|
||||
insertEnv(APPLICATION->settings()->get("Env").toMap());
|
||||
else
|
||||
insertEnv(settings()->get("Env").toMap());
|
||||
return env;
|
||||
}
|
||||
|
||||
QProcessEnvironment MinecraftInstance::createLaunchEnvironment()
|
||||
{
|
||||
// prepare the process environment
|
||||
QProcessEnvironment env = createEnvironment();
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
if (settings()->get("EnableMangoHud").toBool() && APPLICATION->capabilities() & Application::SupportsMangoHud) {
|
||||
QStringList preloadList;
|
||||
if (auto value = env.value("LD_PRELOAD"); !value.isEmpty())
|
||||
preloadList = value.split(QLatin1String(":"));
|
||||
QStringList libPaths;
|
||||
if (auto value = env.value("LD_LIBRARY_PATH"); !value.isEmpty())
|
||||
libPaths = value.split(QLatin1String(":"));
|
||||
|
||||
auto mangoHudLibString = MangoHud::getLibraryString();
|
||||
if (!mangoHudLibString.isEmpty()) {
|
||||
QFileInfo mangoHudLib(mangoHudLibString);
|
||||
QString libPath = mangoHudLib.absolutePath();
|
||||
auto appendLib = [libPath, &preloadList](QString fileName) {
|
||||
if (QFileInfo(FS::PathCombine(libPath, fileName)).exists())
|
||||
preloadList << fileName;
|
||||
};
|
||||
|
||||
// dlsym variant is only needed for OpenGL and not included in the vulkan layer
|
||||
appendLib("libMangoHud_dlsym.so");
|
||||
appendLib("libMangoHud_opengl.so");
|
||||
appendLib(mangoHudLib.fileName());
|
||||
libPaths << libPath;
|
||||
}
|
||||
|
||||
env.insert("LD_PRELOAD", preloadList.join(QLatin1String(":")));
|
||||
env.insert("LD_LIBRARY_PATH", libPaths.join(QLatin1String(":")));
|
||||
env.insert("MANGOHUD", "1");
|
||||
}
|
||||
|
||||
if (settings()->get("UseDiscreteGpu").toBool()) {
|
||||
// Open Source Drivers
|
||||
env.insert("DRI_PRIME", "1");
|
||||
// Proprietary Nvidia Drivers
|
||||
env.insert("__NV_PRIME_RENDER_OFFLOAD", "1");
|
||||
env.insert("__VK_LAYER_NV_optimus", "NVIDIA_only");
|
||||
env.insert("__GLX_VENDOR_LIBRARY_NAME", "nvidia");
|
||||
}
|
||||
#endif
|
||||
return env;
|
||||
}
|
||||
|
||||
|
@ -157,20 +157,6 @@ void MojangVersionFormat::readVersionProperties(const QJsonObject& in, VersionFi
|
||||
Bits::readString(in, "id", out->minecraftVersion);
|
||||
Bits::readString(in, "mainClass", out->mainClass);
|
||||
Bits::readString(in, "minecraftArguments", out->minecraftArguments);
|
||||
if (out->minecraftArguments.isEmpty()) {
|
||||
QString processArguments;
|
||||
Bits::readString(in, "processArguments", processArguments);
|
||||
QString toCompare = processArguments.toLower();
|
||||
if (toCompare == "legacy") {
|
||||
out->minecraftArguments = " ${auth_player_name} ${auth_session}";
|
||||
} else if (toCompare == "username_session") {
|
||||
out->minecraftArguments = "--username ${auth_player_name} --session ${auth_session}";
|
||||
} else if (toCompare == "username_session_version") {
|
||||
out->minecraftArguments = "--username ${auth_player_name} --session ${auth_session} --version ${profile_name}";
|
||||
} else if (!toCompare.isEmpty()) {
|
||||
out->addProblem(ProblemSeverity::Error, QObject::tr("processArguments is set to unknown value '%1'").arg(processArguments));
|
||||
}
|
||||
}
|
||||
Bits::readString(in, "type", out->type);
|
||||
|
||||
Bits::readString(in, "assets", out->assets);
|
||||
|
@ -9,7 +9,7 @@
|
||||
class OneSixVersionFormat {
|
||||
public:
|
||||
// version files / profile patches
|
||||
static VersionFilePtr versionFileFromJson(const QJsonDocument& doc, const QString& filename, const bool requireOrder);
|
||||
static VersionFilePtr versionFileFromJson(const QJsonDocument& doc, const QString& filename, bool requireOrder);
|
||||
static QJsonDocument versionFileToJson(const VersionFilePtr& patch);
|
||||
|
||||
// libraries
|
||||
|
@ -430,7 +430,7 @@ bool PackProfile::remove(const int index)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PackProfile::remove(const QString id)
|
||||
bool PackProfile::remove(const QString& id)
|
||||
{
|
||||
int i = 0;
|
||||
for (auto patch : d->components) {
|
||||
|
@ -89,13 +89,13 @@ class PackProfile : public QAbstractListModel {
|
||||
|
||||
enum MoveDirection { MoveUp, MoveDown };
|
||||
/// move component file # up or down the list
|
||||
void move(const int index, const MoveDirection direction);
|
||||
void move(int index, MoveDirection direction);
|
||||
|
||||
/// remove component file # - including files/records
|
||||
bool remove(const int index);
|
||||
bool remove(int index);
|
||||
|
||||
/// remove component file by id - including files/records
|
||||
bool remove(const QString id);
|
||||
bool remove(const QString& id);
|
||||
|
||||
bool customize(int index);
|
||||
|
||||
|
@ -140,7 +140,7 @@ VersionFilePtr parseJsonFile(const QFileInfo& fileInfo, const bool requireOrder)
|
||||
return guardedParseJson(doc, fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), requireOrder);
|
||||
}
|
||||
|
||||
bool saveJsonFile(const QJsonDocument doc, const QString& filename)
|
||||
bool saveJsonFile(const QJsonDocument& doc, const QString& filename)
|
||||
{
|
||||
auto data = doc.toJson();
|
||||
QSaveFile jsonFile(filename);
|
||||
|
@ -47,10 +47,10 @@ bool readOverrideOrders(QString path, PatchOrder& order);
|
||||
bool writeOverrideOrders(QString path, const PatchOrder& order);
|
||||
|
||||
/// Parse a version file in JSON format
|
||||
VersionFilePtr parseJsonFile(const QFileInfo& fileInfo, const bool requireOrder);
|
||||
VersionFilePtr parseJsonFile(const QFileInfo& fileInfo, bool requireOrder);
|
||||
|
||||
/// Save a JSON file (in any format)
|
||||
bool saveJsonFile(const QJsonDocument doc, const QString& filename);
|
||||
bool saveJsonFile(const QJsonDocument& doc, const QString& filename);
|
||||
|
||||
/// Remove LWJGL from a patch file. This is applied to all Mojang-like profile files.
|
||||
void removeLwjglFromPatch(VersionFilePtr patch);
|
||||
|
@ -52,8 +52,6 @@
|
||||
#include <FileSystem.h>
|
||||
#include <QSaveFile>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
enum AccountListVersion { MojangMSA = 3 };
|
||||
|
||||
AccountList::AccountList(QObject* parent) : QAbstractListModel(parent)
|
||||
@ -283,9 +281,15 @@ QVariant AccountList::data(const QModelIndex& index, int role) const
|
||||
return account->accountDisplayString();
|
||||
|
||||
case TypeColumn: {
|
||||
auto typeStr = account->typeString();
|
||||
typeStr[0] = typeStr[0].toUpper();
|
||||
return typeStr;
|
||||
switch (account->accountType()) {
|
||||
case AccountType::MSA: {
|
||||
return tr("MSA", "Account type");
|
||||
}
|
||||
case AccountType::Offline: {
|
||||
return tr("Offline", "Account type");
|
||||
}
|
||||
}
|
||||
return tr("Unknown", "Account type");
|
||||
}
|
||||
|
||||
case StatusColumn: {
|
||||
|
@ -75,7 +75,7 @@ class AccountList : public QAbstractListModel {
|
||||
virtual Qt::ItemFlags flags(const QModelIndex& index) const override;
|
||||
virtual bool setData(const QModelIndex& index, const QVariant& value, int role) override;
|
||||
|
||||
void addAccount(const MinecraftAccountPtr account);
|
||||
void addAccount(MinecraftAccountPtr account);
|
||||
void removeAccount(QModelIndex index);
|
||||
int findAccountByProfileId(const QString& profileId) const;
|
||||
MinecraftAccountPtr getAccountByProfileName(const QString& profileName) const;
|
||||
|
@ -52,6 +52,7 @@
|
||||
|
||||
#include "flows/MSA.h"
|
||||
#include "flows/Offline.h"
|
||||
#include "minecraft/auth/AccountData.h"
|
||||
|
||||
MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent)
|
||||
{
|
||||
@ -185,7 +186,7 @@ void MinecraftAccount::authFailed(QString reason)
|
||||
// NOTE: this doesn't do much. There was an error of some sort.
|
||||
} break;
|
||||
case AccountTaskState::STATE_FAILED_HARD: {
|
||||
if (isMSA()) {
|
||||
if (accountType() == AccountType::MSA) {
|
||||
data.msaToken.token = QString();
|
||||
data.msaToken.refresh_token = QString();
|
||||
data.msaToken.validity = Katabasis::Validity::None;
|
||||
|
@ -118,9 +118,7 @@ class MinecraftAccount : public QObject, public Usable {
|
||||
|
||||
bool isActive() const;
|
||||
|
||||
bool isMSA() const { return data.type == AccountType::MSA; }
|
||||
|
||||
bool isOffline() const { return data.type == AccountType::Offline; }
|
||||
[[nodiscard]] AccountType accountType() const noexcept { return data.type; }
|
||||
|
||||
bool ownsMinecraft() const { return data.minecraftEntitlement.ownsMinecraft; }
|
||||
|
||||
|
@ -37,9 +37,9 @@
|
||||
#include "ModFolderModel.h"
|
||||
|
||||
#include <FileSystem.h>
|
||||
#include <qheaderview.h>
|
||||
#include <QDebug>
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QHeaderView>
|
||||
#include <QIcon>
|
||||
#include <QMimeData>
|
||||
#include <QString>
|
||||
@ -65,8 +65,8 @@ ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool
|
||||
m_column_names = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider" });
|
||||
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Version"), tr("Last Modified"), tr("Provider") });
|
||||
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::VERSION, SortType::DATE, SortType::PROVIDER };
|
||||
m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch,
|
||||
QHeaderView::ResizeToContents, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents };
|
||||
m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch,
|
||||
QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive };
|
||||
m_columnsHideable = { false, true, false, true, true, true };
|
||||
}
|
||||
|
||||
@ -131,6 +131,11 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
|
||||
}
|
||||
return {};
|
||||
}
|
||||
case Qt::SizeHintRole:
|
||||
if (column == ImageColumn) {
|
||||
return QSize(32, 32);
|
||||
}
|
||||
return {};
|
||||
case Qt::CheckStateRole:
|
||||
switch (column) {
|
||||
case ActiveColumn:
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QFileInfo>
|
||||
#include <QHeaderView>
|
||||
#include <QIcon>
|
||||
#include <QMenu>
|
||||
#include <QMimeData>
|
||||
@ -45,7 +46,7 @@ ResourceFolderModel::~ResourceFolderModel()
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
|
||||
bool ResourceFolderModel::startWatching(const QStringList paths)
|
||||
bool ResourceFolderModel::startWatching(const QStringList& paths)
|
||||
{
|
||||
if (m_is_watching)
|
||||
return false;
|
||||
@ -64,7 +65,7 @@ bool ResourceFolderModel::startWatching(const QStringList paths)
|
||||
return m_is_watching;
|
||||
}
|
||||
|
||||
bool ResourceFolderModel::stopWatching(const QStringList paths)
|
||||
bool ResourceFolderModel::stopWatching(const QStringList& paths)
|
||||
{
|
||||
if (!m_is_watching)
|
||||
return false;
|
||||
@ -460,9 +461,9 @@ bool ResourceFolderModel::setData(const QModelIndex& index, [[maybe_unused]] con
|
||||
if (role == Qt::CheckStateRole) {
|
||||
if (m_instance != nullptr && m_instance->isRunning()) {
|
||||
auto response =
|
||||
CustomMessageBox::selectable(nullptr, "Confirm toggle",
|
||||
"If you enable/disable this resource while the game is running it may crash your game.\n"
|
||||
"Are you sure you want to do this?",
|
||||
CustomMessageBox::selectable(nullptr, tr("Confirm toggle"),
|
||||
tr("If you enable/disable this resource while the game is running it may crash your game.\n"
|
||||
"Are you sure you want to do this?"),
|
||||
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
|
||||
->exec();
|
||||
|
||||
@ -516,36 +517,22 @@ void ResourceFolderModel::setupHeaderAction(QAction* act, int column)
|
||||
act->setText(columnNames().at(column));
|
||||
}
|
||||
|
||||
void ResourceFolderModel::saveHiddenColumn(int column, bool hidden)
|
||||
void ResourceFolderModel::saveColumns(QTreeView* tree)
|
||||
{
|
||||
auto const setting_name = QString("UI/%1_Page/HiddenColumns").arg(id());
|
||||
auto const setting_name = QString("UI/%1_Page/Columns").arg(id());
|
||||
auto setting = (m_instance->settings()->contains(setting_name)) ? m_instance->settings()->getSetting(setting_name)
|
||||
: m_instance->settings()->registerSetting(setting_name);
|
||||
|
||||
auto hiddenColumns = setting->get().toStringList();
|
||||
auto name = columnNames(false).at(column);
|
||||
auto index = hiddenColumns.indexOf(name);
|
||||
if (index >= 0 && !hidden) {
|
||||
hiddenColumns.removeAt(index);
|
||||
} else if (index < 0 && hidden) {
|
||||
hiddenColumns.append(name);
|
||||
}
|
||||
setting->set(hiddenColumns);
|
||||
setting->set(tree->header()->saveState());
|
||||
}
|
||||
|
||||
void ResourceFolderModel::loadHiddenColumns(QTreeView* tree)
|
||||
void ResourceFolderModel::loadColumns(QTreeView* tree)
|
||||
{
|
||||
auto const setting_name = QString("UI/%1_Page/HiddenColumns").arg(id());
|
||||
auto const setting_name = QString("UI/%1_Page/Columns").arg(id());
|
||||
auto setting = (m_instance->settings()->contains(setting_name)) ? m_instance->settings()->getSetting(setting_name)
|
||||
: m_instance->settings()->registerSetting(setting_name);
|
||||
|
||||
auto hiddenColumns = setting->get().toStringList();
|
||||
auto col_names = columnNames(false);
|
||||
for (auto col_name : hiddenColumns) {
|
||||
auto index = col_names.indexOf(col_name);
|
||||
if (index >= 0)
|
||||
tree->setColumnHidden(index, true);
|
||||
}
|
||||
tree->header()->restoreState(setting->get().toByteArray());
|
||||
}
|
||||
|
||||
QMenu* ResourceFolderModel::createHeaderContextMenu(QTreeView* tree)
|
||||
@ -570,7 +557,7 @@ QMenu* ResourceFolderModel::createHeaderContextMenu(QTreeView* tree)
|
||||
if (m_column_resize_modes.at(c) == QHeaderView::ResizeToContents)
|
||||
tree->resizeColumnToContents(c);
|
||||
}
|
||||
saveHiddenColumn(col, !toggled);
|
||||
saveColumns(tree);
|
||||
});
|
||||
|
||||
menu->addAction(act);
|
||||
|
@ -39,14 +39,14 @@ class ResourceFolderModel : public QAbstractListModel {
|
||||
* Returns whether starting to watch all the paths was successful.
|
||||
* If one or more fails, it returns false.
|
||||
*/
|
||||
bool startWatching(const QStringList paths);
|
||||
bool startWatching(const QStringList& paths);
|
||||
|
||||
/** Stops watching the paths for changes.
|
||||
*
|
||||
* Returns whether stopping to watch all the paths was successful.
|
||||
* If one or more fails, it returns false.
|
||||
*/
|
||||
bool stopWatching(const QStringList paths);
|
||||
bool stopWatching(const QStringList& paths);
|
||||
|
||||
/* Helper methods for subclasses, using a predetermined list of paths. */
|
||||
virtual bool startWatching() { return startWatching({ m_dir.absolutePath() }); }
|
||||
@ -117,8 +117,8 @@ class ResourceFolderModel : public QAbstractListModel {
|
||||
[[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
|
||||
void setupHeaderAction(QAction* act, int column);
|
||||
void saveHiddenColumn(int column, bool hidden);
|
||||
void loadHiddenColumns(QTreeView* tree);
|
||||
void saveColumns(QTreeView* tree);
|
||||
void loadColumns(QTreeView* tree);
|
||||
QMenu* createHeaderContextMenu(QTreeView* tree);
|
||||
|
||||
/** This creates a proxy model to filter / sort the model for a UI.
|
||||
@ -201,8 +201,7 @@ class ResourceFolderModel : public QAbstractListModel {
|
||||
QList<SortType> m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE };
|
||||
QStringList m_column_names = { "Enable", "Name", "Last Modified" };
|
||||
QStringList m_column_names_translated = { tr("Enable"), tr("Name"), tr("Last Modified") };
|
||||
QList<QHeaderView::ResizeMode> m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Stretch,
|
||||
QHeaderView::ResizeToContents };
|
||||
QList<QHeaderView::ResizeMode> m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive };
|
||||
QList<bool> m_columnsHideable = { false, false, true };
|
||||
|
||||
QDir m_dir;
|
||||
@ -307,7 +306,6 @@ void ResourceFolderModel::applyUpdates(QSet<QString>& current_set, QSet<QString>
|
||||
auto removed_it = m_resources.begin() + removed_index;
|
||||
|
||||
Q_ASSERT(removed_it != m_resources.end());
|
||||
Q_ASSERT(removed_set.contains(removed_it->get()->internal_id()));
|
||||
|
||||
if ((*removed_it)->isResolving()) {
|
||||
auto ticket = (*removed_it)->resolutionTicket();
|
||||
|
@ -52,8 +52,8 @@ ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir, BaseInstanc
|
||||
m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified" });
|
||||
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified") });
|
||||
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE };
|
||||
m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents,
|
||||
QHeaderView::ResizeToContents };
|
||||
m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive,
|
||||
QHeaderView::Interactive };
|
||||
m_columnsHideable = { false, true, false, true, true };
|
||||
}
|
||||
|
||||
@ -117,6 +117,11 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
|
||||
}
|
||||
return m_resources[row]->internal_id();
|
||||
}
|
||||
case Qt::SizeHintRole:
|
||||
if (column == ImageColumn) {
|
||||
return QSize(32, 32);
|
||||
}
|
||||
return {};
|
||||
case Qt::CheckStateRole:
|
||||
switch (column) {
|
||||
case ActiveColumn:
|
||||
|
@ -47,8 +47,7 @@ TexturePackFolderModel::TexturePackFolderModel(const QString& dir, BaseInstance*
|
||||
m_column_names = QStringList({ "Enable", "Image", "Name", "Last Modified" });
|
||||
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified") });
|
||||
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE };
|
||||
m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch,
|
||||
QHeaderView::ResizeToContents };
|
||||
m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive };
|
||||
m_columnsHideable = { false, true, false, true };
|
||||
}
|
||||
|
||||
@ -104,6 +103,11 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const
|
||||
}
|
||||
return {};
|
||||
}
|
||||
case Qt::SizeHintRole:
|
||||
if (column == ImageColumn) {
|
||||
return QSize(32, 32);
|
||||
}
|
||||
return {};
|
||||
case Qt::CheckStateRole:
|
||||
if (column == ActiveColumn) {
|
||||
return m_resources[row]->enabled() ? Qt::Checked : Qt::Unchecked;
|
||||
|
@ -137,10 +137,11 @@ Task::Ptr GetModDependenciesTask::getProjectInfoTask(std::shared_ptr<PackDepende
|
||||
auto provider = pDep->pack->provider == m_flame_provider.name ? m_flame_provider : m_modrinth_provider;
|
||||
auto responseInfo = std::make_shared<QByteArray>();
|
||||
auto info = provider.api->getProject(pDep->pack->addonId.toString(), responseInfo);
|
||||
QObject::connect(info.get(), &NetJob::succeeded, [responseInfo, provider, pDep] {
|
||||
QObject::connect(info.get(), &NetJob::succeeded, [this, responseInfo, provider, pDep] {
|
||||
QJsonParseError parse_error{};
|
||||
QJsonDocument doc = QJsonDocument::fromJson(*responseInfo, &parse_error);
|
||||
if (parse_error.error != QJsonParseError::NoError) {
|
||||
removePack(pDep->pack->addonId);
|
||||
qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset
|
||||
<< " reason: " << parse_error.errorString();
|
||||
qDebug() << *responseInfo;
|
||||
@ -151,6 +152,7 @@ Task::Ptr GetModDependenciesTask::getProjectInfoTask(std::shared_ptr<PackDepende
|
||||
: Json::requireObject(doc);
|
||||
provider.mod->loadIndexedPack(*pDep->pack, obj);
|
||||
} catch (const JSONValidationError& e) {
|
||||
removePack(pDep->pack->addonId);
|
||||
qDebug() << doc;
|
||||
qWarning() << "Error while reading mod info: " << e.cause();
|
||||
}
|
||||
@ -180,7 +182,9 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
|
||||
|
||||
ResourceAPI::DependencySearchArgs args = { dep, m_version, m_loaderType };
|
||||
ResourceAPI::DependencySearchCallbacks callbacks;
|
||||
|
||||
callbacks.on_fail = [](QString reason, int) {
|
||||
qCritical() << tr("A network error occurred. Could not load project dependencies:%1").arg(reason);
|
||||
};
|
||||
callbacks.on_succeed = [dep, provider, pDep, level, this](auto& doc, [[maybe_unused]] auto& pack) {
|
||||
try {
|
||||
QJsonArray arr;
|
||||
@ -211,11 +215,13 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
|
||||
pDep->pack->versionsLoaded = true;
|
||||
|
||||
} catch (const JSONValidationError& e) {
|
||||
removePack(dep.addonId);
|
||||
qDebug() << doc;
|
||||
qWarning() << "Error while reading mod version: " << e.cause();
|
||||
return;
|
||||
}
|
||||
if (level == 0) {
|
||||
removePack(dep.addonId);
|
||||
qWarning() << "Dependency cycle exceeded";
|
||||
return;
|
||||
}
|
||||
@ -238,7 +244,7 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
|
||||
return tasks;
|
||||
}
|
||||
|
||||
void GetModDependenciesTask::removePack(const QVariant addonId)
|
||||
void GetModDependenciesTask::removePack(const QVariant& addonId)
|
||||
{
|
||||
auto pred = [addonId](const std::shared_ptr<PackDependency>& v) { return v->pack->addonId == addonId; };
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0)
|
||||
@ -279,4 +285,4 @@ QHash<QString, QStringList> GetModDependenciesTask::getRequiredBy()
|
||||
rby[addonId.toString()] = req;
|
||||
}
|
||||
return rby;
|
||||
}
|
||||
}
|
||||
|
@ -65,13 +65,13 @@ class GetModDependenciesTask : public SequentialTask {
|
||||
QHash<QString, QStringList> getRequiredBy();
|
||||
|
||||
protected slots:
|
||||
Task::Ptr prepareDependencyTask(const ModPlatform::Dependency&, const ModPlatform::ResourceProvider, int);
|
||||
Task::Ptr prepareDependencyTask(const ModPlatform::Dependency&, ModPlatform::ResourceProvider, int);
|
||||
QList<ModPlatform::Dependency> getDependenciesForVersion(const ModPlatform::IndexedVersion&,
|
||||
const ModPlatform::ResourceProvider providerName);
|
||||
ModPlatform::ResourceProvider providerName);
|
||||
void prepare();
|
||||
Task::Ptr getProjectInfoTask(std::shared_ptr<PackDependency> pDep);
|
||||
ModPlatform::Dependency getOverride(const ModPlatform::Dependency&, const ModPlatform::ResourceProvider providerName);
|
||||
void removePack(const QVariant addonId);
|
||||
ModPlatform::Dependency getOverride(const ModPlatform::Dependency&, ModPlatform::ResourceProvider providerName);
|
||||
void removePack(const QVariant& addonId);
|
||||
|
||||
private:
|
||||
QList<std::shared_ptr<PackDependency>> m_pack_dependencies;
|
||||
|
@ -688,6 +688,7 @@ bool loadIconFile(const Mod& mod)
|
||||
return png_invalid(); // icon invalid
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case ResourceType::ZIPFILE: {
|
||||
QuaZip zip(mod.fileinfo().filePath());
|
||||
@ -714,6 +715,7 @@ bool loadIconFile(const Mod& mod)
|
||||
} else {
|
||||
return png_invalid(); // could not set icon as current file.
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case ResourceType::LITEMOD: {
|
||||
return false; // can lightmods even have icons?
|
||||
|
@ -232,10 +232,9 @@ bool processPackPNG(const ResourcePack& pack)
|
||||
} else {
|
||||
return png_invalid(); // pack.png does not exists or is not a valid file.
|
||||
}
|
||||
return false; // not processed correctly; https://github.com/PrismLauncher/PrismLauncher/issues/1740
|
||||
}
|
||||
case ResourceType::ZIPFILE: {
|
||||
Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
|
||||
|
||||
QuaZip zip(pack.fileinfo().filePath());
|
||||
if (!zip.open(QuaZip::mdUnzip))
|
||||
return false; // can't open zip file
|
||||
@ -259,6 +258,7 @@ bool processPackPNG(const ResourcePack& pack)
|
||||
} else {
|
||||
return png_invalid(); // could not set pack.mcmeta as current file.
|
||||
}
|
||||
return false; // not processed correctly; https://github.com/PrismLauncher/PrismLauncher/issues/1740
|
||||
}
|
||||
default:
|
||||
qWarning() << "Invalid type for resource pack parse task!";
|
||||
|
@ -186,10 +186,9 @@ bool processPackPNG(const TexturePack& pack)
|
||||
} else {
|
||||
return png_invalid(); // pack.png does not exists or is not a valid file.
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case ResourceType::ZIPFILE: {
|
||||
Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
|
||||
|
||||
QuaZip zip(pack.fileinfo().filePath());
|
||||
if (!zip.open(QuaZip::mdUnzip))
|
||||
return false; // can't open zip file
|
||||
@ -215,6 +214,7 @@ bool processPackPNG(const TexturePack& pack)
|
||||
zip.close();
|
||||
return png_invalid(); // could not set pack.mcmeta as current file.
|
||||
}
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
qWarning() << "Invalid type for resource pack parse task!";
|
||||
|
@ -149,6 +149,7 @@ void EnsureMetadataTask::executeTask()
|
||||
if (m_current_task)
|
||||
m_current_task.reset();
|
||||
});
|
||||
connect(project_task.get(), &Task::failed, this, &EnsureMetadataTask::emitFailed);
|
||||
|
||||
m_current_task = project_task;
|
||||
project_task->start();
|
||||
|
@ -122,6 +122,8 @@ struct ExtraPackData {
|
||||
QString wikiUrl;
|
||||
QString discordUrl;
|
||||
|
||||
QString status;
|
||||
|
||||
QString body;
|
||||
};
|
||||
|
||||
|
@ -96,6 +96,7 @@ class ResourceAPI {
|
||||
};
|
||||
struct VersionSearchCallbacks {
|
||||
std::function<void(QJsonDocument&, ModPlatform::IndexedPack)> on_succeed;
|
||||
std::function<void(QString const& reason, int network_error_code)> on_fail;
|
||||
};
|
||||
|
||||
struct ProjectInfoArgs {
|
||||
@ -118,6 +119,7 @@ class ResourceAPI {
|
||||
|
||||
struct DependencySearchCallbacks {
|
||||
std::function<void(QJsonDocument&, const ModPlatform::Dependency&)> on_succeed;
|
||||
std::function<void(QString const& reason, int network_error_code)> on_fail;
|
||||
};
|
||||
|
||||
public:
|
||||
|
@ -119,7 +119,6 @@ void Flame::FileResolvingTask::netJobFinished()
|
||||
connect(m_checkJob.get(), &NetJob::failed, this, [this, step_progress](QString reason) {
|
||||
step_progress->state = TaskStepState::Failed;
|
||||
stepProgress(*step_progress);
|
||||
emitFailed(reason);
|
||||
});
|
||||
connect(m_checkJob.get(), &NetJob::stepProgress, this, &FileResolvingTask::propagateStepProgress);
|
||||
connect(m_checkJob.get(), &NetJob::progress, this, [this, step_progress](qint64 current, qint64 total) {
|
||||
|
@ -208,17 +208,15 @@ Task::Ptr FlameAPI::getFile(const QString& addonId, const QString& fileId, std::
|
||||
return netJob;
|
||||
}
|
||||
|
||||
// https://docs.curseforge.com/?python#tocS_ModsSearchSortField
|
||||
static QList<ResourceAPI::SortingMethod> s_sorts = { { 1, "Featured", QObject::tr("Sort by Featured") },
|
||||
{ 2, "Popularity", QObject::tr("Sort by Popularity") },
|
||||
{ 3, "LastUpdated", QObject::tr("Sort by Last Updated") },
|
||||
{ 4, "Name", QObject::tr("Sort by Name") },
|
||||
{ 5, "Author", QObject::tr("Sort by Author") },
|
||||
{ 6, "TotalDownloads", QObject::tr("Sort by Downloads") },
|
||||
{ 7, "Category", QObject::tr("Sort by Category") },
|
||||
{ 8, "GameVersion", QObject::tr("Sort by Game Version") } };
|
||||
|
||||
QList<ResourceAPI::SortingMethod> FlameAPI::getSortingMethods() const
|
||||
{
|
||||
return s_sorts;
|
||||
// https://docs.curseforge.com/?python#tocS_ModsSearchSortField
|
||||
return { { 1, "Featured", QObject::tr("Sort by Featured") },
|
||||
{ 2, "Popularity", QObject::tr("Sort by Popularity") },
|
||||
{ 3, "LastUpdated", QObject::tr("Sort by Last Updated") },
|
||||
{ 4, "Name", QObject::tr("Sort by Name") },
|
||||
{ 5, "Author", QObject::tr("Sort by Author") },
|
||||
{ 6, "TotalDownloads", QObject::tr("Sort by Downloads") },
|
||||
{ 7, "Category", QObject::tr("Sort by Category") },
|
||||
{ 8, "GameVersion", QObject::tr("Sort by Game Version") } };
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ bool FlameCheckUpdate::abort()
|
||||
return true;
|
||||
}
|
||||
|
||||
ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info)
|
||||
ModPlatform::IndexedPack FlameCheckUpdate::getProjectInfo(ModPlatform::IndexedVersion& ver_info)
|
||||
{
|
||||
ModPlatform::IndexedPack pack;
|
||||
|
||||
@ -57,6 +57,7 @@ ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info)
|
||||
}
|
||||
});
|
||||
|
||||
connect(get_project_job, &NetJob::failed, this, &FlameCheckUpdate::emitFailed);
|
||||
QObject::connect(get_project_job, &NetJob::finished, [&loop, get_project_job] {
|
||||
get_project_job->deleteLater();
|
||||
loop.quit();
|
||||
@ -68,7 +69,7 @@ ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info)
|
||||
return pack;
|
||||
}
|
||||
|
||||
ModPlatform::IndexedVersion getFileInfo(int addonId, int fileId)
|
||||
ModPlatform::IndexedVersion FlameCheckUpdate::getFileInfo(int addonId, int fileId)
|
||||
{
|
||||
ModPlatform::IndexedVersion ver;
|
||||
|
||||
@ -100,7 +101,7 @@ ModPlatform::IndexedVersion getFileInfo(int addonId, int fileId)
|
||||
qDebug() << doc;
|
||||
}
|
||||
});
|
||||
|
||||
connect(get_file_info_job, &NetJob::failed, this, &FlameCheckUpdate::emitFailed);
|
||||
QObject::connect(get_file_info_job, &NetJob::finished, [&loop, get_file_info_job] {
|
||||
get_file_info_job->deleteLater();
|
||||
loop.quit();
|
||||
|
@ -22,6 +22,9 @@ class FlameCheckUpdate : public CheckUpdateTask {
|
||||
void executeTask() override;
|
||||
|
||||
private:
|
||||
ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info);
|
||||
ModPlatform::IndexedVersion getFileInfo(int addonId, int fileId);
|
||||
|
||||
NetJob* m_net_job = nullptr;
|
||||
|
||||
bool m_was_aborted = false;
|
||||
|
@ -227,6 +227,7 @@ bool FlameCreationTask::updateInstance()
|
||||
m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(relative_path));
|
||||
}
|
||||
});
|
||||
connect(job.get(), &Task::failed, this, [](QString reason) { qCritical() << "Failed to get files: " << reason; });
|
||||
connect(job.get(), &Task::finished, &loop, &QEventLoop::quit);
|
||||
|
||||
m_process_update_file_info_job = job;
|
||||
@ -427,6 +428,9 @@ bool FlameCreationTask::createInstance()
|
||||
// Don't add managed info to packs without an ID (most likely imported from ZIP)
|
||||
if (!m_managed_id.isEmpty())
|
||||
instance.setManagedPack("flame", m_managed_id, m_pack.name, m_managed_version_id, m_pack.version);
|
||||
else
|
||||
instance.setManagedPack("flame", "", name(), "", "");
|
||||
|
||||
instance.setName(name());
|
||||
|
||||
m_mod_id_resolver.reset(new Flame::FileResolvingTask(APPLICATION->network(), m_pack));
|
||||
|
@ -323,6 +323,7 @@ void FlamePackExportTask::getProjectsInfo()
|
||||
}
|
||||
buildZip();
|
||||
});
|
||||
connect(projTask.get(), &Task::failed, this, &FlamePackExportTask::emitFailed);
|
||||
task.reset(projTask);
|
||||
task->start();
|
||||
}
|
||||
|
@ -102,6 +102,13 @@ Task::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Versi
|
||||
|
||||
callbacks.on_succeed(doc, args.pack);
|
||||
});
|
||||
QObject::connect(netJob.get(), &NetJob::failed, [&netJob, callbacks](QString reason) {
|
||||
int network_error_code = -1;
|
||||
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply)
|
||||
network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
callbacks.on_fail(reason, network_error_code);
|
||||
});
|
||||
|
||||
return netJob;
|
||||
}
|
||||
@ -146,6 +153,12 @@ Task::Ptr NetworkResourceAPI::getDependencyVersion(DependencySearchArgs&& args,
|
||||
|
||||
callbacks.on_succeed(doc, args.dependency);
|
||||
});
|
||||
QObject::connect(netJob.get(), &NetJob::failed, [&netJob, callbacks](QString reason) {
|
||||
int network_error_code = -1;
|
||||
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply)
|
||||
network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
callbacks.on_fail(reason, network_error_code);
|
||||
});
|
||||
return netJob;
|
||||
}
|
||||
|
@ -111,14 +111,12 @@ Task::Ptr ModrinthAPI::getProjects(QStringList addonIds, std::shared_ptr<QByteAr
|
||||
return netJob;
|
||||
}
|
||||
|
||||
// https://docs.modrinth.com/api-spec/#tag/projects/operation/searchProjects
|
||||
static QList<ResourceAPI::SortingMethod> s_sorts = { { 1, "relevance", QObject::tr("Sort by Relevance") },
|
||||
{ 2, "downloads", QObject::tr("Sort by Downloads") },
|
||||
{ 3, "follows", QObject::tr("Sort by Follows") },
|
||||
{ 4, "newest", QObject::tr("Sort by Last Updated") },
|
||||
{ 5, "updated", QObject::tr("Sort by Newest") } };
|
||||
|
||||
QList<ResourceAPI::SortingMethod> ModrinthAPI::getSortingMethods() const
|
||||
{
|
||||
return s_sorts;
|
||||
// https://docs.modrinth.com/api-spec/#tag/projects/operation/searchProjects
|
||||
return { { 1, "relevance", QObject::tr("Sort by Relevance") },
|
||||
{ 2, "downloads", QObject::tr("Sort by Downloads") },
|
||||
{ 3, "follows", QObject::tr("Sort by Follows") },
|
||||
{ 4, "newest", QObject::tr("Sort by Newest") },
|
||||
{ 5, "updated", QObject::tr("Sort by Last Updated") } };
|
||||
}
|
||||
|
@ -72,9 +72,7 @@ void ModrinthCheckUpdate::executeTask()
|
||||
auto response = std::make_shared<QByteArray>();
|
||||
auto job = api.latestVersions(hashes, best_hash_type, m_game_versions, m_loaders, response);
|
||||
|
||||
QEventLoop lock;
|
||||
|
||||
connect(job.get(), &Task::succeeded, this, [this, response, &mappings, best_hash_type, job] {
|
||||
connect(job.get(), &Task::succeeded, this, [this, response, mappings, best_hash_type, job] {
|
||||
QJsonParseError parse_error{};
|
||||
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
||||
if (parse_error.error != QJsonParseError::NoError) {
|
||||
@ -82,7 +80,7 @@ void ModrinthCheckUpdate::executeTask()
|
||||
<< " reason: " << parse_error.errorString();
|
||||
qWarning() << *response;
|
||||
|
||||
failed(parse_error.errorString());
|
||||
emitFailed(parse_error.errorString());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -167,19 +165,17 @@ void ModrinthCheckUpdate::executeTask()
|
||||
m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, project_ver));
|
||||
}
|
||||
} catch (Json::JsonException& e) {
|
||||
failed(e.cause() + " : " + e.what());
|
||||
emitFailed(e.cause() + " : " + e.what());
|
||||
return;
|
||||
}
|
||||
emitSucceeded();
|
||||
});
|
||||
|
||||
connect(job.get(), &Task::finished, &lock, &QEventLoop::quit);
|
||||
connect(job.get(), &Task::failed, this, &ModrinthCheckUpdate::emitFailed);
|
||||
|
||||
setStatus(tr("Waiting for the API response from Modrinth..."));
|
||||
setProgress(1, 3);
|
||||
|
||||
m_net_job = qSharedPointerObjectCast<NetJob, Task>(job);
|
||||
job->start();
|
||||
|
||||
lock.exec();
|
||||
|
||||
emitSucceeded();
|
||||
}
|
||||
|
@ -226,6 +226,9 @@ bool ModrinthCreationTask::createInstance()
|
||||
// Don't add managed info to packs without an ID (most likely imported from ZIP)
|
||||
if (!m_managed_id.isEmpty())
|
||||
instance.setManagedPack("modrinth", m_managed_id, m_managed_name, m_managed_version_id, version());
|
||||
else
|
||||
instance.setManagedPack("modrinth", "", name(), "", "");
|
||||
|
||||
instance.setName(name());
|
||||
instance.saveNow();
|
||||
|
||||
@ -289,7 +292,7 @@ bool ModrinthCreationTask::createInstance()
|
||||
// Only change the name if it didn't use a custom name, so that the previous custom name
|
||||
// is preserved, but if we're using the original one, we update the version string.
|
||||
// NOTE: This needs to come before the copyManagedPack call!
|
||||
if (inst->name().contains(inst->getManagedPackVersionName())) {
|
||||
if (inst->name().contains(inst->getManagedPackVersionName()) && inst->name() != instance.name()) {
|
||||
if (askForChangingInstanceName(m_parent, inst->name(), instance.name()) == InstanceNameChange::ShouldChange)
|
||||
inst->setName(instance.name());
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ class ModrinthPackExportTask : public Task {
|
||||
void collectFiles();
|
||||
void collectHashes();
|
||||
void makeApiRequest();
|
||||
void parseApiResponse(const std::shared_ptr<QByteArray> response);
|
||||
void parseApiResponse(std::shared_ptr<QByteArray> response);
|
||||
void buildZip();
|
||||
|
||||
QByteArray generateIndex();
|
||||
|
@ -104,6 +104,8 @@ void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob
|
||||
pack.extraData.donate.append(donate);
|
||||
}
|
||||
|
||||
pack.extraData.status = Json::ensureString(obj, "status");
|
||||
|
||||
pack.extraData.body = Json::ensureString(obj, "body").remove("<br>");
|
||||
|
||||
pack.extraDataLoaded = true;
|
||||
|
@ -95,6 +95,8 @@ void loadIndexedInfo(Modpack& pack, QJsonObject& obj)
|
||||
pack.extra.donate.append(donate);
|
||||
}
|
||||
|
||||
pack.extra.status = Json::ensureString(obj, "status");
|
||||
|
||||
pack.extraInfoLoaded = true;
|
||||
}
|
||||
|
||||
|
@ -77,6 +77,8 @@ struct ModpackExtra {
|
||||
QString discordUrl;
|
||||
|
||||
QList<DonationData> donate;
|
||||
|
||||
QString status;
|
||||
};
|
||||
|
||||
struct ModpackVersion {
|
||||
|
@ -33,6 +33,6 @@ class TechnicPackProcessor : public QObject {
|
||||
const QString& instIcon,
|
||||
const QString& stagingPath,
|
||||
const QString& minecraftVersion = QString(),
|
||||
const bool isSolder = false);
|
||||
bool isSolder = false);
|
||||
};
|
||||
} // namespace Technic
|
||||
|
@ -36,6 +36,7 @@
|
||||
*/
|
||||
|
||||
#include "NetJob.h"
|
||||
#include "tasks/ConcurrentTask.h"
|
||||
#if defined(LAUNCHER_APPLICATION)
|
||||
#include "Application.h"
|
||||
#endif
|
||||
@ -56,18 +57,15 @@ auto NetJob::addNetAction(NetAction::Ptr action) -> bool
|
||||
return true;
|
||||
}
|
||||
|
||||
void NetJob::startNext()
|
||||
void NetJob::executeNextSubTask()
|
||||
{
|
||||
if (m_queue.isEmpty() && m_doing.isEmpty()) {
|
||||
// We're finished, check for failures and retry if we can (up to 3 times)
|
||||
if (!m_failed.isEmpty() && m_try < 3) {
|
||||
m_try += 1;
|
||||
while (!m_failed.isEmpty())
|
||||
m_queue.enqueue(m_failed.take(*m_failed.keyBegin()));
|
||||
}
|
||||
// We're finished, check for failures and retry if we can (up to 3 times)
|
||||
if (isRunning() && m_queue.isEmpty() && m_doing.isEmpty() && !m_failed.isEmpty() && m_try < 3) {
|
||||
m_try += 1;
|
||||
while (!m_failed.isEmpty())
|
||||
m_queue.enqueue(m_failed.take(*m_failed.keyBegin()));
|
||||
}
|
||||
|
||||
ConcurrentTask::startNext();
|
||||
ConcurrentTask::executeNextSubTask();
|
||||
}
|
||||
|
||||
auto NetJob::size() const -> int
|
||||
|
@ -55,8 +55,6 @@ class NetJob : public ConcurrentTask {
|
||||
explicit NetJob(QString job_name, shared_qobject_ptr<QNetworkAccessManager> network);
|
||||
~NetJob() override = default;
|
||||
|
||||
void startNext() override;
|
||||
|
||||
auto size() const -> int;
|
||||
|
||||
auto canAbort() const -> bool override;
|
||||
@ -69,6 +67,9 @@ class NetJob : public ConcurrentTask {
|
||||
// Qt can't handle auto at the start for some reason?
|
||||
bool abort() override;
|
||||
|
||||
protected slots:
|
||||
void executeNextSubTask() override;
|
||||
|
||||
protected:
|
||||
void updateState() override;
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
qt.*.debug=false
|
||||
# don't log credentials by default
|
||||
launcher.auth.credentials.debug=false
|
||||
katabasis.*.debug=false
|
||||
# remove the debug lines, other log levels still get through
|
||||
launcher.task.net.download.debug=false
|
||||
# enable or disable whole catageries
|
||||
|
@ -58,14 +58,14 @@ void ImgurUpload::init()
|
||||
|
||||
QNetworkReply* ImgurUpload::getReply(QNetworkRequest& request)
|
||||
{
|
||||
auto file = new QFile(m_fileInfo.absoluteFilePath());
|
||||
auto file = new QFile(m_fileInfo.absoluteFilePath(), this);
|
||||
|
||||
if (!file->open(QFile::ReadOnly)) {
|
||||
emitFailed();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QHttpMultiPart* multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
|
||||
QHttpMultiPart* multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType, this);
|
||||
file->setParent(multipart);
|
||||
QHttpPart filePart;
|
||||
filePart.setBodyDevice(file);
|
||||
|
@ -54,6 +54,7 @@ bool INIFile::saveFile(QString fileName)
|
||||
insert("ConfigVersion", "1.2");
|
||||
QSettings _settings_obj{ fileName, QSettings::Format::IniFormat };
|
||||
_settings_obj.setFallbacksEnabled(false);
|
||||
_settings_obj.clear();
|
||||
|
||||
for (Iterator iter = begin(); iter != end(); iter++)
|
||||
_settings_obj.setValue(iter.key(), iter.value());
|
||||
|
@ -35,7 +35,6 @@
|
||||
*/
|
||||
#include "ConcurrentTask.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include "tasks/Task.h"
|
||||
|
||||
@ -47,9 +46,9 @@ ConcurrentTask::ConcurrentTask(QObject* parent, QString task_name, int max_concu
|
||||
|
||||
ConcurrentTask::~ConcurrentTask()
|
||||
{
|
||||
for (auto task : m_queue) {
|
||||
for (auto task : m_doing) {
|
||||
if (task)
|
||||
task->deleteLater();
|
||||
task->disconnect(this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,15 +64,13 @@ void ConcurrentTask::addTask(Task::Ptr task)
|
||||
|
||||
void ConcurrentTask::executeTask()
|
||||
{
|
||||
// Start one task, startNext handles starting the up to the m_total_max_size
|
||||
// while tracking the number currently being done
|
||||
QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection);
|
||||
for (auto i = 0; i < m_total_max_size; i++)
|
||||
QMetaObject::invokeMethod(this, &ConcurrentTask::executeNextSubTask, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
bool ConcurrentTask::abort()
|
||||
{
|
||||
m_queue.clear();
|
||||
m_aborted = true;
|
||||
|
||||
if (m_doing.isEmpty()) {
|
||||
// Don't call emitAborted() here, we want to bypass the 'is the task running' check
|
||||
@ -108,29 +105,36 @@ void ConcurrentTask::clear()
|
||||
m_failed.clear();
|
||||
m_queue.clear();
|
||||
|
||||
m_aborted = false;
|
||||
|
||||
m_progress = 0;
|
||||
m_stepProgress = 0;
|
||||
}
|
||||
|
||||
void ConcurrentTask::startNext()
|
||||
void ConcurrentTask::executeNextSubTask()
|
||||
{
|
||||
if (m_aborted || m_doing.count() > m_total_max_size)
|
||||
if (!isRunning()) {
|
||||
return;
|
||||
|
||||
if (m_queue.isEmpty() && m_doing.isEmpty() && !wasSuccessful()) {
|
||||
emitSucceeded();
|
||||
}
|
||||
if (m_doing.count() >= m_total_max_size) {
|
||||
return;
|
||||
}
|
||||
if (m_queue.isEmpty()) {
|
||||
if (m_doing.isEmpty()) {
|
||||
if (m_failed.isEmpty())
|
||||
emitSucceeded();
|
||||
else
|
||||
emitFailed(tr("One or more subtasks failed"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_queue.isEmpty())
|
||||
return;
|
||||
|
||||
Task::Ptr next = m_queue.dequeue();
|
||||
startSubTask(m_queue.dequeue());
|
||||
}
|
||||
|
||||
void ConcurrentTask::startSubTask(Task::Ptr next)
|
||||
{
|
||||
connect(next.get(), &Task::succeeded, this, [this, next]() { subTaskSucceeded(next); });
|
||||
connect(next.get(), &Task::failed, this, [this, next](QString msg) { subTaskFailed(next, msg); });
|
||||
// this should never happen but if it does, it's better to fail the task than get stuck
|
||||
connect(next.get(), &Task::aborted, this, [this, next] { subTaskFailed(next, "Aborted"); });
|
||||
|
||||
connect(next.get(), &Task::status, this, [this, next](QString msg) { subTaskStatus(next, msg); });
|
||||
@ -140,55 +144,42 @@ void ConcurrentTask::startNext()
|
||||
connect(next.get(), &Task::progress, this, [this, next](qint64 current, qint64 total) { subTaskProgress(next, current, total); });
|
||||
|
||||
m_doing.insert(next.get(), next);
|
||||
qsizetype num_starts = qMin(m_queue.size(), m_total_max_size - m_doing.size());
|
||||
|
||||
auto task_progress = std::make_shared<TaskStepProgress>(next->getUid());
|
||||
m_task_progress.insert(next->getUid(), task_progress);
|
||||
|
||||
updateState();
|
||||
updateStepProgress(*task_progress.get(), Operation::ADDED);
|
||||
|
||||
QCoreApplication::processEvents();
|
||||
|
||||
QMetaObject::invokeMethod(next.get(), &Task::start, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
// Allow going up the number of concurrent tasks in case of tasks being added in the middle of a running task.
|
||||
for (int i = 0; i < num_starts; i++)
|
||||
QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection);
|
||||
void ConcurrentTask::subTaskFinished(Task::Ptr task, TaskStepState state)
|
||||
{
|
||||
m_done.insert(task.get(), task);
|
||||
(state == TaskStepState::Succeeded ? m_succeeded : m_failed).insert(task.get(), task);
|
||||
|
||||
m_doing.remove(task.get());
|
||||
|
||||
auto task_progress = m_task_progress.value(task->getUid());
|
||||
task_progress->state = state;
|
||||
|
||||
disconnect(task.get(), 0, this, 0);
|
||||
|
||||
emit stepProgress(*task_progress);
|
||||
updateState();
|
||||
updateStepProgress(*task_progress, Operation::REMOVED);
|
||||
QMetaObject::invokeMethod(this, &ConcurrentTask::executeNextSubTask, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void ConcurrentTask::subTaskSucceeded(Task::Ptr task)
|
||||
{
|
||||
m_done.insert(task.get(), task);
|
||||
m_succeeded.insert(task.get(), task);
|
||||
|
||||
m_doing.remove(task.get());
|
||||
auto task_progress = m_task_progress.value(task->getUid());
|
||||
task_progress->state = TaskStepState::Succeeded;
|
||||
|
||||
disconnect(task.get(), 0, this, 0);
|
||||
|
||||
emit stepProgress(*task_progress);
|
||||
updateState();
|
||||
updateStepProgress(*task_progress, Operation::REMOVED);
|
||||
startNext();
|
||||
subTaskFinished(task, TaskStepState::Succeeded);
|
||||
}
|
||||
|
||||
void ConcurrentTask::subTaskFailed(Task::Ptr task, [[maybe_unused]] const QString& msg)
|
||||
{
|
||||
m_done.insert(task.get(), task);
|
||||
m_failed.insert(task.get(), task);
|
||||
|
||||
m_doing.remove(task.get());
|
||||
|
||||
auto task_progress = m_task_progress.value(task->getUid());
|
||||
task_progress->state = TaskStepState::Failed;
|
||||
|
||||
disconnect(task.get(), 0, this, 0);
|
||||
|
||||
emit stepProgress(*task_progress);
|
||||
updateState();
|
||||
updateStepProgress(*task_progress, Operation::REMOVED);
|
||||
startNext();
|
||||
subTaskFinished(task, TaskStepState::Failed);
|
||||
}
|
||||
|
||||
void ConcurrentTask::subTaskStatus(Task::Ptr task, const QString& msg)
|
||||
|
@ -72,10 +72,11 @@ class ConcurrentTask : public Task {
|
||||
protected slots:
|
||||
void executeTask() override;
|
||||
|
||||
virtual void startNext();
|
||||
virtual void executeNextSubTask();
|
||||
|
||||
void subTaskSucceeded(Task::Ptr);
|
||||
void subTaskFailed(Task::Ptr, const QString& msg);
|
||||
virtual void subTaskFailed(Task::Ptr, const QString& msg);
|
||||
void subTaskFinished(Task::Ptr, TaskStepState);
|
||||
void subTaskStatus(Task::Ptr task, const QString& msg);
|
||||
void subTaskDetails(Task::Ptr task, const QString& msg);
|
||||
void subTaskProgress(Task::Ptr task, qint64 current, qint64 total);
|
||||
@ -90,6 +91,8 @@ class ConcurrentTask : public Task {
|
||||
|
||||
virtual void updateState();
|
||||
|
||||
void startSubTask(Task::Ptr task);
|
||||
|
||||
protected:
|
||||
QString m_name;
|
||||
QString m_step_status;
|
||||
@ -107,6 +110,4 @@ class ConcurrentTask : public Task {
|
||||
|
||||
qint64 m_stepProgress = 0;
|
||||
qint64 m_stepTotalProgress = 100;
|
||||
|
||||
bool m_aborted = false;
|
||||
};
|
||||
|
@ -36,9 +36,9 @@
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
MultipleOptionsTask::MultipleOptionsTask(QObject* parent, const QString& task_name) : SequentialTask(parent, task_name) {}
|
||||
MultipleOptionsTask::MultipleOptionsTask(QObject* parent, const QString& task_name) : ConcurrentTask(parent, task_name, 1) {}
|
||||
|
||||
void MultipleOptionsTask::startNext()
|
||||
void MultipleOptionsTask::executeNextSubTask()
|
||||
{
|
||||
if (m_done.size() != m_failed.size()) {
|
||||
emitSucceeded();
|
||||
@ -51,7 +51,7 @@ void MultipleOptionsTask::startNext()
|
||||
return;
|
||||
}
|
||||
|
||||
ConcurrentTask::startNext();
|
||||
ConcurrentTask::executeNextSubTask();
|
||||
}
|
||||
|
||||
void MultipleOptionsTask::updateState()
|
||||
|
@ -34,18 +34,18 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "SequentialTask.h"
|
||||
#include "ConcurrentTask.h"
|
||||
|
||||
/* This task type will attempt to do run each of it's subtasks in sequence,
|
||||
* until one of them succeeds. When that happens, the remaining tasks will not run.
|
||||
* */
|
||||
class MultipleOptionsTask : public SequentialTask {
|
||||
class MultipleOptionsTask : public ConcurrentTask {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MultipleOptionsTask(QObject* parent = nullptr, const QString& task_name = "");
|
||||
~MultipleOptionsTask() override = default;
|
||||
|
||||
private slots:
|
||||
void startNext() override;
|
||||
void executeNextSubTask() override;
|
||||
void updateState() override;
|
||||
};
|
||||
|
@ -36,18 +36,15 @@
|
||||
#include "SequentialTask.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include "tasks/ConcurrentTask.h"
|
||||
|
||||
SequentialTask::SequentialTask(QObject* parent, QString task_name) : ConcurrentTask(parent, task_name, 1) {}
|
||||
|
||||
void SequentialTask::startNext()
|
||||
void SequentialTask::subTaskFailed(Task::Ptr task, const QString& msg)
|
||||
{
|
||||
if (m_failed.size() > 0) {
|
||||
emitFailed(tr("One of the tasks failed!"));
|
||||
qWarning() << m_failed.constBegin()->get()->failReason();
|
||||
return;
|
||||
}
|
||||
|
||||
ConcurrentTask::startNext();
|
||||
emitFailed(msg);
|
||||
qWarning() << msg;
|
||||
ConcurrentTask::subTaskFailed(task, msg);
|
||||
}
|
||||
|
||||
void SequentialTask::updateState()
|
||||
|
@ -50,7 +50,9 @@ class SequentialTask : public ConcurrentTask {
|
||||
explicit SequentialTask(QObject* parent = nullptr, QString task_name = "");
|
||||
~SequentialTask() override = default;
|
||||
|
||||
protected slots:
|
||||
virtual void subTaskFailed(Task::Ptr, const QString& msg) override;
|
||||
|
||||
protected:
|
||||
void startNext() override;
|
||||
void updateState() override;
|
||||
};
|
||||
|
@ -186,6 +186,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
|
||||
|
||||
ui->instanceToolBar->addContextMenuAction(ui->newsToolBar->toggleViewAction());
|
||||
ui->instanceToolBar->addContextMenuAction(ui->instanceToolBar->toggleViewAction());
|
||||
ui->instanceToolBar->addContextMenuAction(ui->actionToggleStatusBar);
|
||||
ui->instanceToolBar->addContextMenuAction(ui->actionLockToolbars);
|
||||
}
|
||||
|
||||
@ -319,6 +320,14 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
|
||||
setCatBackground(cat_enable);
|
||||
}
|
||||
|
||||
// Togglable status bar
|
||||
{
|
||||
bool statusBarVisible = APPLICATION->settings()->get("StatusBarVisible").toBool();
|
||||
ui->actionToggleStatusBar->setChecked(statusBarVisible);
|
||||
connect(ui->actionToggleStatusBar, &QAction::toggled, this, &MainWindow::setStatusBarVisibility);
|
||||
setStatusBarVisibility(statusBarVisible);
|
||||
}
|
||||
|
||||
// Lock toolbars
|
||||
{
|
||||
bool toolbarsLocked = APPLICATION->settings()->get("ToolbarsLocked").toBool();
|
||||
@ -451,10 +460,16 @@ QMenu* MainWindow::createPopupMenu()
|
||||
QMenu* filteredMenu = QMainWindow::createPopupMenu();
|
||||
filteredMenu->removeAction(ui->mainToolBar->toggleViewAction());
|
||||
|
||||
filteredMenu->addAction(ui->actionToggleStatusBar);
|
||||
filteredMenu->addAction(ui->actionLockToolbars);
|
||||
|
||||
return filteredMenu;
|
||||
}
|
||||
void MainWindow::setStatusBarVisibility(bool state)
|
||||
{
|
||||
statusBar()->setVisible(state);
|
||||
APPLICATION->settings()->set("StatusBarVisible", state);
|
||||
}
|
||||
void MainWindow::lockToolbars(bool state)
|
||||
{
|
||||
ui->mainToolBar->setMovable(!state);
|
||||
@ -1182,33 +1197,43 @@ void MainWindow::undoTrashInstance()
|
||||
|
||||
void MainWindow::on_actionViewLauncherRootFolder_triggered()
|
||||
{
|
||||
DesktopServices::openDirectory(".");
|
||||
DesktopServices::openPath(".");
|
||||
}
|
||||
|
||||
void MainWindow::on_actionViewInstanceFolder_triggered()
|
||||
{
|
||||
QString str = APPLICATION->settings()->get("InstanceDir").toString();
|
||||
DesktopServices::openDirectory(str);
|
||||
DesktopServices::openPath(str);
|
||||
}
|
||||
|
||||
void MainWindow::on_actionViewCentralModsFolder_triggered()
|
||||
{
|
||||
DesktopServices::openDirectory(APPLICATION->settings()->get("CentralModsDir").toString(), true);
|
||||
DesktopServices::openPath(APPLICATION->settings()->get("CentralModsDir").toString(), true);
|
||||
}
|
||||
|
||||
void MainWindow::on_actionViewIconThemeFolder_triggered()
|
||||
{
|
||||
DesktopServices::openDirectory(APPLICATION->themeManager()->getIconThemesFolder().path());
|
||||
DesktopServices::openPath(APPLICATION->themeManager()->getIconThemesFolder().path(), true);
|
||||
}
|
||||
|
||||
void MainWindow::on_actionViewWidgetThemeFolder_triggered()
|
||||
{
|
||||
DesktopServices::openDirectory(APPLICATION->themeManager()->getApplicationThemesFolder().path());
|
||||
DesktopServices::openPath(APPLICATION->themeManager()->getApplicationThemesFolder().path(), true);
|
||||
}
|
||||
|
||||
void MainWindow::on_actionViewCatPackFolder_triggered()
|
||||
{
|
||||
DesktopServices::openDirectory(APPLICATION->themeManager()->getCatPacksFolder().path());
|
||||
DesktopServices::openPath(APPLICATION->themeManager()->getCatPacksFolder().path(), true);
|
||||
}
|
||||
|
||||
void MainWindow::on_actionViewIconsFolder_triggered()
|
||||
{
|
||||
DesktopServices::openPath(APPLICATION->icons()->getDirectory(), true);
|
||||
}
|
||||
|
||||
void MainWindow::on_actionViewLogsFolder_triggered()
|
||||
{
|
||||
DesktopServices::openPath("logs", true);
|
||||
}
|
||||
|
||||
void MainWindow::refreshInstances()
|
||||
@ -1427,7 +1452,7 @@ void MainWindow::on_actionViewSelectedInstFolder_triggered()
|
||||
{
|
||||
if (m_selectedInstance) {
|
||||
QString str = m_selectedInstance->instanceRoot();
|
||||
DesktopServices::openDirectory(QDir(str).absolutePath());
|
||||
DesktopServices::openPath(QFileInfo(str));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,6 +117,8 @@ class MainWindow : public QMainWindow {
|
||||
void on_actionViewIconThemeFolder_triggered();
|
||||
void on_actionViewWidgetThemeFolder_triggered();
|
||||
void on_actionViewCatPackFolder_triggered();
|
||||
void on_actionViewIconsFolder_triggered();
|
||||
void on_actionViewLogsFolder_triggered();
|
||||
|
||||
void on_actionViewSelectedInstFolder_triggered();
|
||||
|
||||
@ -203,6 +205,8 @@ class MainWindow : public QMainWindow {
|
||||
|
||||
void globalSettingsClosed();
|
||||
|
||||
void setStatusBarVisibility(bool);
|
||||
|
||||
void lockToolbars(bool);
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
|
@ -176,6 +176,7 @@
|
||||
<addaction name="actionChangeTheme"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionCAT"/>
|
||||
<addaction name="actionToggleStatusBar"/>
|
||||
<addaction name="actionLockToolbars"/>
|
||||
<addaction name="separator"/>
|
||||
</widget>
|
||||
@ -194,6 +195,9 @@
|
||||
<addaction name="actionViewIconThemeFolder"/>
|
||||
<addaction name="actionViewWidgetThemeFolder"/>
|
||||
<addaction name="actionViewCatPackFolder"/>
|
||||
<addaction name="actionViewIconsFolder"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionViewLogsFolder"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="accountsMenu">
|
||||
<property name="title">
|
||||
@ -254,6 +258,14 @@
|
||||
<string>It's a fluffy kitty :3</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionToggleStatusBar">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Status Bar</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionLockToolbars">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
@ -545,10 +557,10 @@
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>View &Instance Folder</string>
|
||||
<string>&Instances</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Open the instance folder in a file browser.</string>
|
||||
<string>Open the instances folder in a file browser.</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionViewLauncherRootFolder">
|
||||
@ -557,7 +569,7 @@
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>View Launcher &Root Folder</string>
|
||||
<string>Launcher &Root</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Open the launcher's root folder in a file browser.</string>
|
||||
@ -569,12 +581,36 @@
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>View &Central Mods Folder</string>
|
||||
<string>&Central Mods</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Open the central mods folder in a file browser.</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionViewIconsFolder">
|
||||
<property name="icon">
|
||||
<iconset theme="viewfolder">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Instance Icons</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Open the instance icons folder in a file browser.</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionViewLogsFolder">
|
||||
<property name="icon">
|
||||
<iconset theme="viewfolder">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Logs</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Open the logs folder in a file browser.</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionChangeTheme">
|
||||
<property name="text">
|
||||
<string>Themes</string>
|
||||
@ -718,10 +754,10 @@
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>View &Widget Themes Folder</string>
|
||||
<string>&Widget Themes</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>View Widget Theme Folder</string>
|
||||
<string>Open the widget themes folder in a file browser.</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionViewIconThemeFolder">
|
||||
@ -730,18 +766,22 @@
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>View I&con Theme Folder</string>
|
||||
<string>I&con Theme</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>View Icon Theme Folder</string>
|
||||
<string>Open the icon theme folder in a file browser.</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionViewCatPackFolder">
|
||||
<property name="icon">
|
||||
<iconset theme="viewfolder"/>
|
||||
<iconset theme="viewfolder">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>View Cat Packs Folder</string>
|
||||
<string>Cat Packs</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Open the cat packs folder in a file browser.</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
|
@ -101,7 +101,7 @@ QString getCreditsHtml()
|
||||
stream << "<h3>" << QObject::tr("With thanks to", "About Credits") << "</h3>\n";
|
||||
stream << QString("<p>Boba %1</p>\n").arg(getWebsite("https://bobaonline.neocities.org/"));
|
||||
stream << QString("<p>Davi Rafael %1</p>\n").arg(getWebsite("https://auti.one/"));
|
||||
stream << QString("<p>Fulmine %1</p>\n").arg(getWebsite("https://www.fulmine.xyz/"));
|
||||
stream << QString("<p>Fulmine %1</p>\n").arg(getWebsite("https://fulmine.xyz/"));
|
||||
stream << QString("<p>ely %1</p>\n").arg(getGitHub("elyrodso"));
|
||||
stream << QString("<p>gon sawa %1</p>\n").arg(getGitHub("gonsawa"));
|
||||
stream << QString("<p>Pankakes</p>\n");
|
||||
@ -174,8 +174,7 @@ AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AboutDia
|
||||
QString urlText("<html><head/><body><p><a href=\"%1\">%1</a></p></body></html>");
|
||||
ui->urlLabel->setText(urlText.arg(BuildConfig.LAUNCHER_GIT));
|
||||
|
||||
QString copyText("© 2022-2023 %1");
|
||||
ui->copyLabel->setText(copyText.arg(BuildConfig.LAUNCHER_COPYRIGHT));
|
||||
ui->copyLabel->setText(BuildConfig.LAUNCHER_COPYRIGHT);
|
||||
|
||||
connect(ui->closeButton, SIGNAL(clicked()), SLOT(close()));
|
||||
|
||||
|
@ -15,7 +15,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <net/NetJob.h>
|
||||
#include <QDialog>
|
||||
|
||||
namespace Ui {
|
||||
@ -31,7 +30,4 @@ class AboutDialog : public QDialog {
|
||||
|
||||
private:
|
||||
Ui::AboutDialog* ui;
|
||||
|
||||
NetJob::Ptr netJob;
|
||||
QByteArray dataSink;
|
||||
};
|
||||
|
@ -13,7 +13,6 @@ enum class ResourceProvider;
|
||||
|
||||
class Mod;
|
||||
class NetJob;
|
||||
class ModUpdateDialog;
|
||||
|
||||
class ChooseProviderDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
@ -214,10 +214,11 @@ void ExportToModListDialog::addExtra(ExportToModList::OptionalData option)
|
||||
void ExportToModListDialog::enableCustom(bool enabled)
|
||||
{
|
||||
ui->authorsCheckBox->setHidden(enabled);
|
||||
ui->versionCheckBox->setHidden(enabled);
|
||||
ui->urlCheckBox->setHidden(enabled);
|
||||
|
||||
ui->authorsButton->setHidden(!enabled);
|
||||
|
||||
ui->versionCheckBox->setHidden(enabled);
|
||||
ui->versionButton->setHidden(!enabled);
|
||||
|
||||
ui->urlCheckBox->setHidden(enabled);
|
||||
ui->urlButton->setHidden(!enabled);
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>650</width>
|
||||
<height>446</height>
|
||||
<height>522</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@ -61,18 +61,37 @@
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QGroupBox" name="templateGroup">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Template</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QTextEdit" name="templateText"/>
|
||||
<widget class="QTextEdit" name="templateText">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QGroupBox" name="optionsGroup">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Optional Info</string>
|
||||
</property>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user