Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into feature/java-downloader

This commit is contained in:
Trial97 2024-01-24 18:26:48 +02:00
commit 25bca28707
No known key found for this signature in database
GPG Key ID: 55EF5DA53DB36318
173 changed files with 1380 additions and 878 deletions

View File

@ -1,4 +1,5 @@
Checks:
- modernize-use-using
- readability-avoid-const-params-in-decls
SystemHeaders: false

View File

@ -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: |-

View File

@ -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

View File

@ -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

View File

@ -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 }}

View File

@ -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

View File

@ -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:

View File

@ -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")

View File

@ -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

View File

@ -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**.

View File

@ -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
View File

@ -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"
}

View File

@ -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";

View File

@ -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

View File

@ -1,5 +1,6 @@
builds:
exclude: []
exclude:
- "*.x86_64-darwin.*"
include:
- "checks.x86_64-linux.*"
- "devShells.*.*"

View File

@ -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);
}

View File

@ -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", "[]");

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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()) {

View File

@ -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

View File

@ -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;

View File

@ -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)
{}

View File

@ -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; }

View File

@ -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;

View File

@ -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.

View File

@ -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;
}

View File

@ -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,

View File

@ -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);

View File

@ -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

View File

@ -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;
}

View File

@ -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; }

View File

@ -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; }

View File

@ -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;

View File

@ -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);

View File

@ -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;
}

View File

@ -25,6 +25,7 @@
QString stripVariableEntries(QString name, QString target, QString remove);
QProcessEnvironment CleanEnviroment();
QStringList getMinecraftJavaBundle();
class JavaUtils : public QObject {
Q_OBJECT

View File

@ -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

View File

@ -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);

View File

@ -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)

View File

@ -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;
}

View File

@ -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);

View File

@ -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

View File

@ -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) {

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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: {

View File

@ -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;

View File

@ -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;

View File

@ -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; }

View File

@ -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:

View File

@ -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);

View File

@ -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();

View File

@ -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:

View File

@ -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;

View File

@ -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)

View File

@ -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;

View File

@ -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?

View File

@ -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!";

View File

@ -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!";

View File

@ -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();

View File

@ -122,6 +122,8 @@ struct ExtraPackData {
QString wikiUrl;
QString discordUrl;
QString status;
QString body;
};

View File

@ -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:

View File

@ -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) {

View File

@ -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") } };
}

View File

@ -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();

View File

@ -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;

View File

@ -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));

View File

@ -323,6 +323,7 @@ void FlamePackExportTask::getProjectsInfo()
}
buildZip();
});
connect(projTask.get(), &Task::failed, this, &FlamePackExportTask::emitFailed);
task.reset(projTask);
task->start();
}

View File

@ -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;
}

View File

@ -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") } };
}

View File

@ -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();
}

View File

@ -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());
}

View File

@ -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();

View File

@ -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;

View File

@ -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;
}

View File

@ -77,6 +77,8 @@ struct ModpackExtra {
QString discordUrl;
QList<DonationData> donate;
QString status;
};
struct ModpackVersion {

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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);

View 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());

View File

@ -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)

View File

@ -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;
};

View File

@ -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()

View File

@ -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;
};

View File

@ -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()

View File

@ -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;
};

View File

@ -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));
}
}

View File

@ -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

View File

@ -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 &amp;Instance Folder</string>
<string>&amp;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 &amp;Root Folder</string>
<string>Launcher &amp;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 &amp;Central Mods Folder</string>
<string>&amp;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 &amp;Widget Themes Folder</string>
<string>&amp;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&amp;con Theme Folder</string>
<string>I&amp;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>

View File

@ -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()));

View File

@ -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;
};

View File

@ -13,7 +13,6 @@ enum class ResourceProvider;
class Mod;
class NetJob;
class ModUpdateDialog;
class ChooseProviderDialog : public QDialog {
Q_OBJECT

View File

@ -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);
}

View File

@ -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