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: Checks:
- modernize-use-using - modernize-use-using
- readability-avoid-const-params-in-decls
SystemHeaders: false SystemHeaders: false

View File

@ -24,7 +24,7 @@ jobs:
with: with:
ref: ${{ github.event.pull_request.head.sha }} ref: ${{ github.event.pull_request.head.sha }}
- name: Create backport PRs - name: Create backport PRs
uses: korthout/backport-action@v1.4.0 uses: korthout/backport-action@v2.4.1
with: with:
# Config README: https://github.com/korthout/backport-action#backport-action # Config README: https://github.com/korthout/backport-action#backport-action
pull_description: |- pull_description: |-

View File

@ -21,8 +21,23 @@ on:
WINDOWS_CODESIGN_PASSWORD: WINDOWS_CODESIGN_PASSWORD:
description: Password for signing Windows builds description: Password for signing Windows builds
required: false required: false
CACHIX_AUTH_TOKEN: APPLE_CODESIGN_CERT:
description: Private token for authenticating against Cachix cache 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 required: false
GPG_PRIVATE_KEY: GPG_PRIVATE_KEY:
description: Private key for AppImage signing description: Private key for AppImage signing
@ -61,7 +76,7 @@ jobs:
qt_ver: 6 qt_ver: 6
qt_host: windows qt_host: windows
qt_arch: '' qt_arch: ''
qt_version: '6.6.0' qt_version: '6.6.1'
qt_modules: 'qt5compat qtimageformats' qt_modules: 'qt5compat qtimageformats'
qt_tools: '' qt_tools: ''
@ -73,7 +88,7 @@ jobs:
qt_ver: 6 qt_ver: 6
qt_host: windows qt_host: windows
qt_arch: 'win64_msvc2019_arm64' qt_arch: 'win64_msvc2019_arm64'
qt_version: '6.6.0' qt_version: '6.6.1'
qt_modules: 'qt5compat qtimageformats' qt_modules: 'qt5compat qtimageformats'
qt_tools: '' qt_tools: ''
@ -83,7 +98,7 @@ jobs:
qt_ver: 6 qt_ver: 6
qt_host: mac qt_host: mac
qt_arch: '' qt_arch: ''
qt_version: '6.6.0' qt_version: '6.6.1'
qt_modules: 'qt5compat qtimageformats' qt_modules: 'qt5compat qtimageformats'
qt_tools: '' qt_tools: ''
@ -145,13 +160,13 @@ jobs:
- name: Setup ccache - name: Setup ccache
if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug' 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: with:
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }} key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }}
- name: Retrieve ccache cache (Windows MinGW-w64) - name: Retrieve ccache cache (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug' if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
uses: actions/cache@v3.3.2 uses: actions/cache@v4.0.0
with: with:
path: '${{ github.workspace }}\.ccache' path: '${{ github.workspace }}\.ccache'
key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }} key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }}
@ -336,6 +351,20 @@ jobs:
# PACKAGE BUILDS # 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) - name: Package (macOS)
if: runner.os == 'macOS' if: runner.os == 'macOS'
run: | run: |
@ -343,9 +372,34 @@ jobs:
cd ${{ env.INSTALL_DIR }} cd ${{ env.INSTALL_DIR }}
chmod +x "PrismLauncher.app/Contents/MacOS/prismlauncher" 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" 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) - name: Make Sparkle signature (macOS)
if: matrix.name == 'macOS' if: matrix.name == 'macOS'
@ -517,70 +571,70 @@ jobs:
- name: Upload binary tarball (macOS) - name: Upload binary tarball (macOS)
if: runner.os == 'macOS' if: runner.os == 'macOS'
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: PrismLauncher-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }} name: PrismLauncher-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }}
path: PrismLauncher.tar.gz path: PrismLauncher.zip
- name: Upload binary zip (Windows) - name: Upload binary zip (Windows)
if: runner.os == 'Windows' if: runner.os == 'Windows'
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: PrismLauncher-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }} name: PrismLauncher-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }}
path: ${{ env.INSTALL_DIR }}/** path: ${{ env.INSTALL_DIR }}/**
- name: Upload binary zip (Windows, portable) - name: Upload binary zip (Windows, portable)
if: runner.os == 'Windows' if: runner.os == 'Windows'
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: PrismLauncher-${{ matrix.name }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }} name: PrismLauncher-${{ matrix.name }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
path: ${{ env.INSTALL_PORTABLE_DIR }}/** path: ${{ env.INSTALL_PORTABLE_DIR }}/**
- name: Upload installer (Windows) - name: Upload installer (Windows)
if: runner.os == 'Windows' if: runner.os == 'Windows'
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: PrismLauncher-${{ matrix.name }}-Setup-${{ env.VERSION }}-${{ inputs.build_type }} name: PrismLauncher-${{ matrix.name }}-Setup-${{ env.VERSION }}-${{ inputs.build_type }}
path: PrismLauncher-Setup.exe path: PrismLauncher-Setup.exe
- name: Upload binary tarball (Linux, Qt 5) - name: Upload binary tarball (Linux, Qt 5)
if: runner.os == 'Linux' && matrix.qt_ver != 6 if: runner.os == 'Linux' && matrix.qt_ver != 6
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: PrismLauncher-${{ runner.os }}-Qt5-${{ env.VERSION }}-${{ inputs.build_type }} name: PrismLauncher-${{ runner.os }}-Qt5-${{ env.VERSION }}-${{ inputs.build_type }}
path: PrismLauncher.tar.gz path: PrismLauncher.tar.gz
- name: Upload binary tarball (Linux, portable, Qt 5) - name: Upload binary tarball (Linux, portable, Qt 5)
if: runner.os == 'Linux' && matrix.qt_ver != 6 if: runner.os == 'Linux' && matrix.qt_ver != 6
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: PrismLauncher-${{ runner.os }}-Qt5-Portable-${{ env.VERSION }}-${{ inputs.build_type }} name: PrismLauncher-${{ runner.os }}-Qt5-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
path: PrismLauncher-portable.tar.gz path: PrismLauncher-portable.tar.gz
- name: Upload binary tarball (Linux, Qt 6) - name: Upload binary tarball (Linux, Qt 6)
if: runner.os == 'Linux' && matrix.qt_ver !=5 if: runner.os == 'Linux' && matrix.qt_ver !=5
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: PrismLauncher-${{ runner.os }}-Qt6-${{ env.VERSION }}-${{ inputs.build_type }} name: PrismLauncher-${{ runner.os }}-Qt6-${{ env.VERSION }}-${{ inputs.build_type }}
path: PrismLauncher.tar.gz path: PrismLauncher.tar.gz
- name: Upload binary tarball (Linux, portable, Qt 6) - name: Upload binary tarball (Linux, portable, Qt 6)
if: runner.os == 'Linux' && matrix.qt_ver != 5 if: runner.os == 'Linux' && matrix.qt_ver != 5
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: PrismLauncher-${{ runner.os }}-Qt6-Portable-${{ env.VERSION }}-${{ inputs.build_type }} name: PrismLauncher-${{ runner.os }}-Qt6-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
path: PrismLauncher-portable.tar.gz path: PrismLauncher-portable.tar.gz
- name: Upload AppImage (Linux) - name: Upload AppImage (Linux)
if: runner.os == 'Linux' && matrix.qt_ver != 5 if: runner.os == 'Linux' && matrix.qt_ver != 5
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
path: 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) - name: Upload AppImage Zsync (Linux)
if: runner.os == 'Linux' && matrix.qt_ver != 5 if: runner.os == 'Linux' && matrix.qt_ver != 5
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage.zsync name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage.zsync
path: PrismLauncher-Linux-x86_64.AppImage.zsync path: PrismLauncher-Linux-x86_64.AppImage.zsync
@ -594,7 +648,7 @@ jobs:
flatpak: flatpak:
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: bilelmoussaoui/flatpak-github-actions:kde-5.15-22.08 image: bilelmoussaoui/flatpak-github-actions:kde-5.15-23.08
options: --privileged options: --privileged
steps: steps:
- name: Checkout - name: Checkout

View File

@ -13,7 +13,7 @@ jobs:
submodules: 'true' submodules: 'true'
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v2 uses: github/codeql-action/init@v3
with: with:
config-file: ./.github/codeql/codeql-config.yml config-file: ./.github/codeql/codeql-config.yml
queries: security-and-quality queries: security-and-quality
@ -32,4 +32,4 @@ jobs:
cmake --build build cmake --build build
- name: Perform CodeQL Analysis - 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 }} SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }}
WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }} WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }}
WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }} 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: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }} GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}

View File

@ -16,7 +16,12 @@ jobs:
SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }} SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }}
WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }} WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }}
WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }} 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: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }} GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}
@ -32,7 +37,7 @@ jobs:
submodules: "true" submodules: "true"
path: "PrismLauncher-source" path: "PrismLauncher-source"
- name: Download artifacts - name: Download artifacts
uses: actions/download-artifact@v3 uses: actions/download-artifact@v4
- name: Grab and store version - name: Grab and store version
run: | run: |
tag_name=$(echo ${{ github.ref }} | grep -oE "[^/]+$") 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-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/PrismLauncher-*.AppImage PrismLauncher-Linux-x86_64.AppImage
mv PrismLauncher-*.AppImage.zsync/PrismLauncher-*.AppImage.zsync PrismLauncher-Linux-x86_64.AppImage.zsync 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-Legacy*/PrismLauncher.zip PrismLauncher-macOS-Legacy-${{ env.VERSION }}.zip
mv PrismLauncher-macOS*/PrismLauncher.tar.gz PrismLauncher-macOS-${{ env.VERSION }}.tar.gz mv PrismLauncher-macOS*/PrismLauncher.zip PrismLauncher-macOS-${{ env.VERSION }}.zip
tar --exclude='.git' -czf PrismLauncher-${{ env.VERSION }}.tar.gz PrismLauncher-${{ env.VERSION }} 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-${{ env.VERSION }}.zip
PrismLauncher-Windows-MSVC-Portable-${{ env.VERSION }}.zip PrismLauncher-Windows-MSVC-Portable-${{ env.VERSION }}.zip
PrismLauncher-Windows-MSVC-Setup-${{ env.VERSION }}.exe PrismLauncher-Windows-MSVC-Setup-${{ env.VERSION }}.exe
PrismLauncher-macOS-${{ env.VERSION }}.tar.gz PrismLauncher-macOS-${{ env.VERSION }}.zip
PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz PrismLauncher-macOS-Legacy-${{ env.VERSION }}.zip
PrismLauncher-${{ env.VERSION }}.tar.gz PrismLauncher-${{ env.VERSION }}.tar.gz

View File

@ -17,7 +17,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: cachix/install-nix-action@6a9a9e84a173d90b3ffb42c5ddaf9ea033fad011 # v23 - uses: cachix/install-nix-action@6004951b182f8860210c8d6f0d808ec5b1a33d28 # v25
- uses: DeterminateSystems/update-flake-lock@v20 - uses: DeterminateSystems/update-flake-lock@v20
with: with:

View File

@ -377,7 +377,7 @@ if(UNIX AND APPLE)
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${Launcher_VERSION_NAME}") set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${Launcher_VERSION_NAME}")
set(MACOSX_BUNDLE_LONG_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_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_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") 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
Prism Launcher - Minecraft 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 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 it under the terms of the GNU General Public License as published by
@ -436,7 +436,7 @@
Copyright (C) 2007 Johann Ollivier Lapeyre <johann@oxygen-icons.org> Copyright (C) 2007 Johann Ollivier Lapeyre <johann@oxygen-icons.org>
Copyright (C) 2007 Kenneth Wimer <kwwii@bootsplash.org> Copyright (C) 2007 Kenneth Wimer <kwwii@bootsplash.org>
Copyright (C) 2007 Riccardo Iaconelli <riccardo@oxygen-icons.org> Copyright (C) 2007 Riccardo Iaconelli <riccardo@oxygen-icons.org>
and others and others
This library is free software; you can redistribute it and/or This library is free software; you can redistribute it and/or

View File

@ -18,11 +18,18 @@
</a> </a>
- All downloads and instructions for Prism Launcher can be found on our [Website](https://prismlauncher.org/download). - 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 ### 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**. Prebuilt Development builds are provided for **Linux**, **Windows** and **macOS**.

View File

@ -68,6 +68,8 @@ function(
/w14906 # string literal cast to 'LPWSTR' /w14906 # string literal cast to 'LPWSTR'
/w14928 # illegal copy-initialization; more than one user-defined conversion has been implicitly applied /w14928 # illegal copy-initialization; more than one user-defined conversion has been implicitly applied
/permissive- # standards conformance mode for MSVC compiler. /permissive- # standards conformance mode for MSVC compiler.
/we4062 # forbid omitting a possible value of an enum in a switch statement
) )
endif() endif()
@ -93,6 +95,8 @@ function(
# in a lot of noise. This warning is only notifying us that clang is emulating the GCC behaviour # 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 # instead of the exact standard wording so we can safely ignore it
-Wno-gnu-zero-variadic-macro-arguments -Wno-gnu-zero-variadic-macro-arguments
-Werror=switch # forbid omitting a possible value of an enum in a switch statement
) )
endif() endif()
@ -104,6 +108,8 @@ function(
-Wduplicated-branches # warn if if / else branches have duplicated code -Wduplicated-branches # warn if if / else branches have duplicated code
-Wlogical-op # warn about logical operations being used where bitwise were probably wanted -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 -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() endif()
@ -128,6 +134,8 @@ function(
-Woverloaded-virtual -Woverloaded-virtual
-Wuseless-cast -Wuseless-cast
-Wextra-semi -Wextra-semi
-Werror=switch # forbid omitting a possible value of an enum in a switch statement
) )
target_compile_options( target_compile_options(

74
flake.lock generated
View File

@ -18,14 +18,16 @@
}, },
"flake-parts": { "flake-parts": {
"inputs": { "inputs": {
"nixpkgs-lib": "nixpkgs-lib" "nixpkgs-lib": [
"nixpkgs"
]
}, },
"locked": { "locked": {
"lastModified": 1696343447, "lastModified": 1704982712,
"narHash": "sha256-B2xAZKLkkeRFG5XcHHSXXcP7To9Xzr59KXeZiRf4vdQ=", "narHash": "sha256-2Ptt+9h8dczgle2Oo6z5ni5rt/uLMG47UFTR1ry/wgg=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "c9afaba3dfa4085dbd2ccb38dfade5141e33d9d4", "rev": "07f6395285469419cf9d078f59b5b49993198c00",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -39,11 +41,11 @@
"systems": "systems" "systems": "systems"
}, },
"locked": { "locked": {
"lastModified": 1685518550, "lastModified": 1701680307,
"narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=", "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef", "rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -60,11 +62,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1660459072, "lastModified": 1703887061,
"narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=", "narHash": "sha256-gGPa9qWNc6eCXT/+Z5/zMkyYOuRZqeFZBDbopNZQkuY=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "gitignore.nix", "repo": "gitignore.nix",
"rev": "a20de23b925fd8264fd7fad6454652e142fd7f73", "rev": "43e1aa1308018f37118e34d3a9cb4f5e75dc11d5",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -76,11 +78,11 @@
"libnbtplusplus": { "libnbtplusplus": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1690036783, "lastModified": 1699286814,
"narHash": "sha256-A5kTgICnx+Qdq3Fir/bKTfdTt/T1NQP2SC+nhN1ENug=", "narHash": "sha256-yy0q+bky80LtK1GWzz7qpM+aAGrOqLuewbid8WT1ilk=",
"owner": "PrismLauncher", "owner": "PrismLauncher",
"repo": "libnbtplusplus", "repo": "libnbtplusplus",
"rev": "a5e8fd52b8bf4ab5d5bcc042b2a247867589985f", "rev": "23b955121b8217c1c348a9ed2483167a6f3ff4ad",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -89,28 +91,13 @@
"type": "github" "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": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1697886341, "lastModified": 1705697961,
"narHash": "sha256-AdE67xPty9M9wn36nPVp6aDntIdigrs7UbyaGv1VAaM=", "narHash": "sha256-XepT3WS516evSFYkme3GrcI3+7uwXHqtHbip+t24J7E=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "44881e03af1c730cbb1d72a4d41274a2c957813a", "rev": "e5d1c87f5813afde2dda384ac807c57a105721cc",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -120,24 +107,6 @@
"type": "github" "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": { "pre-commit-hooks": {
"inputs": { "inputs": {
"flake-compat": [ "flake-compat": [
@ -153,11 +122,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1697746376, "lastModified": 1705757126,
"narHash": "sha256-gu77VkgdfaHgNCVufeb6WP9oqFLjwK4jHcoPZmBVF3E=", "narHash": "sha256-Eksr+n4Q8EYZKAN0Scef5JK4H6FcHc+TKNHb95CWm+c=",
"owner": "cachix", "owner": "cachix",
"repo": "pre-commit-hooks.nix", "repo": "pre-commit-hooks.nix",
"rev": "8cc349bfd082da8782b989cad2158c9ad5bd70fd", "rev": "f56597d53fd174f796b5a7d3ee0b494f9e2285cc",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -171,7 +140,6 @@
"flake-compat": "flake-compat", "flake-compat": "flake-compat",
"flake-parts": "flake-parts", "flake-parts": "flake-parts",
"libnbtplusplus": "libnbtplusplus", "libnbtplusplus": "libnbtplusplus",
"nix-filter": "nix-filter",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"pre-commit-hooks": "pre-commit-hooks" "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)"; 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 = { inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
flake-parts.url = "github:hercules-ci/flake-parts"; flake-parts = {
nix-filter.url = "github:numtide/nix-filter"; url = "github:hercules-ci/flake-parts";
inputs.nixpkgs-lib.follows = "nixpkgs";
};
pre-commit-hooks = { pre-commit-hooks = {
url = "github:cachix/pre-commit-hooks.nix"; url = "github:cachix/pre-commit-hooks.nix";
inputs.nixpkgs.follows = "nixpkgs"; inputs = {
inputs.nixpkgs-stable.follows = "nixpkgs"; nixpkgs.follows = "nixpkgs";
inputs.flake-compat.follows = "flake-compat"; nixpkgs-stable.follows = "nixpkgs";
flake-compat.follows = "flake-compat";
};
}; };
flake-compat = { flake-compat = {
url = "github:edolstra/flake-compat"; url = "github:edolstra/flake-compat";

View File

@ -1,6 +1,6 @@
id: org.prismlauncher.PrismLauncher id: org.prismlauncher.PrismLauncher
runtime: org.kde.Platform runtime: org.kde.Platform
runtime-version: "5.15-22.08" runtime-version: 5.15-23.08
sdk: org.kde.Sdk sdk: org.kde.Sdk
sdk-extensions: sdk-extensions:
- org.freedesktop.Sdk.Extension.openjdk17 - org.freedesktop.Sdk.Extension.openjdk17
@ -104,9 +104,9 @@ modules:
- install -Dm755 ../data/gamemoderun -t /app/bin - install -Dm755 ../data/gamemoderun -t /app/bin
sources: sources:
- type: archive - type: archive
archive-type: tar-gzip dest-filename: gamemode.tar.gz
url: https://api.github.com/repos/FeralInteractive/gamemode/tarball/1.7 url: https://api.github.com/repos/FeralInteractive/gamemode/tarball/1.8.1
sha256: 57ce73ba605d1cf12f8d13725006a895182308d93eba0f69f285648449641803 sha256: 969cf85b5ca3944f3e315cd73a0ee9bea4f9c968cd7d485e9f4745bc1e679c4e
x-checker-data: x-checker-data:
type: json type: json
url: https://api.github.com/repos/FeralInteractive/gamemode/releases/latest 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: builds:
exclude: [] exclude:
- "*.x86_64-darwin.*"
include: include:
- "checks.x86_64-linux.*" - "checks.x86_64-linux.*"
- "devShells.*.*" - "devShells.*.*"

View File

@ -134,6 +134,15 @@
#include "gamemode_client.h" #include "gamemode_client.h"
#endif #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(Q_OS_MAC)
#if defined(SPARKLE_ENABLED) #if defined(SPARKLE_ENABLED)
#include "updater/MacSparkleUpdater.h" #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 " qDebug() << qPrintable(BuildConfig.LAUNCHER_DISPLAYNAME + ", " + QString(BuildConfig.LAUNCHER_COPYRIGHT).replace("\n", ", "));
<< qPrintable(QString(BuildConfig.LAUNCHER_COPYRIGHT).replace("\n", ", "));
qDebug() << "Version : " << BuildConfig.printableVersionString(); qDebug() << "Version : " << BuildConfig.printableVersionString();
qDebug() << "Platform : " << BuildConfig.BUILD_PLATFORM; qDebug() << "Platform : " << BuildConfig.BUILD_PLATFORM;
qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT; qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT;
@ -646,6 +654,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
// Minecraft mods // Minecraft mods
m_settings->registerSetting("ModMetadataDisabled", false); m_settings->registerSetting("ModMetadataDisabled", false);
m_settings->registerSetting("ModDependenciesDisabled", false);
// Minecraft offline player name // Minecraft offline player name
m_settings->registerSetting("LastOfflinePlayerName", ""); m_settings->registerSetting("LastOfflinePlayerName", "");
@ -659,6 +668,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
// The cat // The cat
m_settings->registerSetting("TheCat", false); m_settings->registerSetting("TheCat", false);
m_settings->registerSetting("CatOpacity", 100);
m_settings->registerSetting("StatusBarVisible", true);
m_settings->registerSetting("ToolbarsLocked", false); 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("ModrinthToken", "");
m_settings->registerSetting("UserAgentOverride", ""); m_settings->registerSetting("UserAgentOverride", "");
// FTBApp instances
m_settings->registerSetting("FTBAppInstancesPath", "");
// Init page provider // Init page provider
{ {
m_globalSettingsProvider = std::make_shared<GenericPageProvider>(tr("Settings")); 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()) { if (createSetupWizard()) {
return; return;
} }
@ -1472,6 +1518,17 @@ InstanceWindow* Application::showInstanceWindow(InstancePtr instance, QString pa
auto& window = extras.window; auto& window = extras.window;
if (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->raise();
window->activateWindow(); window->activateWindow();
} else { } else {
@ -1479,6 +1536,7 @@ InstanceWindow* Application::showInstanceWindow(InstancePtr instance, QString pa
m_openWindows++; m_openWindows++;
connect(window, &InstanceWindow::isClosing, this, &Application::on_windowClose); connect(window, &InstanceWindow::isClosing, this, &Application::on_windowClose);
} }
if (!page.isEmpty()) { if (!page.isEmpty()) {
window->selectPage(page); window->selectPage(page);
} }

View File

@ -64,6 +64,8 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s
m_settings->registerSetting("lastLaunchTime", 0); m_settings->registerSetting("lastLaunchTime", 0);
m_settings->registerSetting("totalTimePlayed", 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("lastTimePlayed", 0);
m_settings->registerSetting("linkedInstances", "[]"); m_settings->registerSetting("linkedInstances", "[]");

View File

@ -18,7 +18,7 @@
class DataMigrationTask : public Task { class DataMigrationTask : public Task {
Q_OBJECT Q_OBJECT
public: 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; ~DataMigrationTask() override = default;
protected: protected:

View File

@ -37,143 +37,33 @@
#include <QDesktopServices> #include <QDesktopServices>
#include <QDir> #include <QDir>
#include <QProcess> #include <QProcess>
#include "FileSystem.h"
/**
* 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
namespace DesktopServices { namespace DesktopServices {
bool openDirectory(const QString& path, [[maybe_unused]] bool ensureExists) bool openPath(const QFileInfo& path, bool ensureFolderPathExists)
{ {
qDebug() << "Opening directory" << path; qDebug() << "Opening path" << path;
QDir parentPath; if (ensureFolderPathExists) {
QDir dir(path); FS::ensureFolderPathExists(path);
if (ensureExists && !dir.exists()) {
parentPath.mkpath(dir.absolutePath());
} }
auto f = [&]() { return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath())); }; return openUrl(QUrl::fromLocalFile(QFileInfo(path).absoluteFilePath()));
#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& path) bool openPath(const QString& path, bool ensureFolderPathExists)
{ {
qDebug() << "Opening file" << path; return openPath(QFileInfo(path), ensureFolderPathExists);
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
} }
bool run(const QString& application, const QStringList& args, const QString& workingDirectory, qint64* pid) bool run(const QString& application, const QStringList& args, const QString& workingDirectory, qint64* pid)
{ {
qDebug() << "Running" << application << "with args" << args.join(' '); 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); return QProcess::startDetached(application, args, workingDirectory, pid);
#endif
} }
bool openUrl(const QUrl& url) bool openUrl(const QUrl& url)
{ {
qDebug() << "Opening URL" << url.toString(); qDebug() << "Opening URL" << url.toString();
auto f = [&]() { return QDesktopServices::openUrl(url); }; 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
} }
bool isFlatpak() bool isFlatpak()
@ -194,9 +84,4 @@ bool isSnap()
#endif #endif
} }
bool isSandbox()
{
return isSnap() || isFlatpak();
}
} // namespace DesktopServices } // namespace DesktopServices

View File

@ -3,31 +3,30 @@
#include <QString> #include <QString>
#include <QUrl> #include <QUrl>
class QFileInfo;
/** /**
* This wraps around QDesktopServices and adds workarounds where needed * This wraps around QDesktopServices and adds workarounds where needed
* Use this instead of QDesktopServices! * Use this instead of QDesktopServices!
*/ */
namespace DesktopServices { 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 * Run an application
*/ */
bool run(const QString& application, const QStringList& args, const QString& workingDirectory = QString(), qint64* pid = 0); 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. * 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 * Determine whether the launcher is running in a Snap environment
*/ */
bool isSnap(); bool isSnap();
/**
* Determine whether the launcher is running in a sandboxed (Flatpak or Snap) environment
*/
bool isSandbox();
} // namespace DesktopServices } // namespace DesktopServices

View File

@ -272,15 +272,19 @@ bool ensureFilePathExists(QString filenamepath)
return success; return success;
} }
bool ensureFolderPathExists(QString foldernamepath) bool ensureFolderPathExists(const QFileInfo folderPath)
{ {
QFileInfo a(foldernamepath);
QDir dir; QDir dir;
QString ensuredPath = a.filePath(); QString ensuredPath = folderPath.filePath();
bool success = dir.mkpath(ensuredPath); bool success = dir.mkpath(ensuredPath);
return success; return success;
} }
bool ensureFolderPathExists(const QString folderPathName)
{
return ensureFolderPathExists(QFileInfo(folderPathName));
}
bool copyFileAttributes(QString src, QString dst) bool copyFileAttributes(QString src, QString dst)
{ {
#ifdef Q_OS_WIN32 #ifdef Q_OS_WIN32
@ -943,6 +947,8 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
<< "\n"; << "\n";
stream << "Type=Application" stream << "Type=Application"
<< "\n"; << "\n";
stream << "Categories=Game;ActionGame;AdventureGame;Simulation"
<< "\n";
stream << "Exec=\"" << target.toLocal8Bit() << "\"" << argstring.toLocal8Bit() << "\n"; stream << "Exec=\"" << target.toLocal8Bit() << "\"" << argstring.toLocal8Bit() << "\n";
stream << "Name=" << name.toLocal8Bit() << "\n"; stream << "Name=" << name.toLocal8Bit() << "\n";
if (!icon.isEmpty()) { if (!icon.isEmpty()) {

View File

@ -91,7 +91,13 @@ bool ensureFilePathExists(QString filenamepath);
* Creates all the folders in a path for the specified path * 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! * 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 * @brief Copies a directory and it's contents from src to dest

View File

@ -142,9 +142,8 @@ void InstanceCopyTask::copyFinished()
if (!m_keepPlaytime) { if (!m_keepPlaytime) {
inst->resetTimePlayed(); inst->resetTimePlayed();
} }
if (m_useLinks)
inst->addLinkedInstanceId(m_origInstance->id());
if (m_useLinks) { if (m_useLinks) {
inst->addLinkedInstanceId(m_origInstance->id());
auto allowed_symlinks_file = QFileInfo(FS::PathCombine(inst->gameRoot(), "allowed_symlinks.txt")); auto allowed_symlinks_file = QFileInfo(FS::PathCombine(inst->gameRoot(), "allowed_symlinks.txt"));
QByteArray allowed_symlinks; QByteArray allowed_symlinks;

View File

@ -59,7 +59,7 @@
#include <quazip/quazipdir.h> #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) : m_sourceUrl(sourceUrl), m_extra_info(extra_info), m_parent(parent)
{} {}

View File

@ -54,7 +54,7 @@ class FileResolvingTask;
class InstanceImportTask : public InstanceTask { class InstanceImportTask : public InstanceTask {
Q_OBJECT Q_OBJECT
public: 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; bool abort() override;
const QVector<Flame::File>& getBlockedFiles() const { return m_blockedMods; } 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; qDebug() << "Remove" << instID << "from group" << name;
removed = true; removed = true;
auto idx = getInstIndex(instance.get()); auto idx = getInstIndex(instance.get());
if (idx > 0) if (idx >= 0)
emit dataChanged(index(idx), index(idx), { GroupRole }); 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; qDebug() << "Set" << instID << "group to" << dst;
modified = true; modified = true;
auto idx = getInstIndex(instance.get()); auto idx = getInstIndex(instance.get());
if (idx > 0) if (idx >= 0)
emit dataChanged(index(idx), index(idx), { GroupRole }); emit dataChanged(index(idx), index(idx), { GroupRole });
} }
} }
@ -823,6 +823,9 @@ void InstanceList::on_InstFolderChanged([[maybe_unused]] const Setting& setting,
} }
m_instDir = newInstDir; m_instDir = newInstDir;
m_groupsLoaded = false; m_groupsLoaded = false;
beginRemoveRows(QModelIndex(), 0, count());
m_instances.erase(m_instances.begin(), m_instances.end());
endRemoveRows();
emit instancesChanged(); emit instancesChanged();
} }
} }
@ -947,9 +950,12 @@ QString InstanceList::getStagedInstancePath()
bool InstanceList::commitStagedInstance(const QString& path, bool InstanceList::commitStagedInstance(const QString& path,
InstanceName const& instanceName, InstanceName const& instanceName,
const QString& groupName, QString groupName,
InstanceTask const& commiting) InstanceTask const& commiting)
{ {
if (groupName.isEmpty() && !groupName.isNull())
groupName = QString();
QDir dir; QDir dir;
QString instID; QString instID;
InstancePtr inst; 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 * should_override is used when another similar instance already exists, and we want to override it
* - for instance, when updating 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. * 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 "ui/dialogs/CustomMessageBox.h"
#include <QPushButton>
InstanceNameChange askForChangingInstanceName(QWidget* parent, const QString& old_name, const QString& new_name) InstanceNameChange askForChangingInstanceName(QWidget* parent, const QString& old_name, const QString& new_name)
{ {
auto dialog = 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 " "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).") "updating, as worlds can be corrupted and some configuration may be lost (due to pack overrides).")
.arg(original_version_name), .arg(original_version_name),
QMessageBox::Information, QMessageBox::Ok | QMessageBox::Reset | QMessageBox::Abort); QMessageBox::Information, QMessageBox::Cancel);
info->setButtonText(QMessageBox::Ok, QObject::tr("Update existing instance")); QAbstractButton* update = info->addButton(QObject::tr("Update existing instance"), QMessageBox::AcceptRole);
info->setButtonText(QMessageBox::Abort, QObject::tr("Create new instance")); QAbstractButton* skip = info->addButton(QObject::tr("Create new instance"), QMessageBox::ResetRole);
info->setButtonText(QMessageBox::Reset, QObject::tr("Cancel"));
info->exec(); info->exec();
if (info->clickedButton() == info->button(QMessageBox::Ok)) if (info->clickedButton() == update)
return ShouldUpdate::Update; return ShouldUpdate::Update;
if (info->clickedButton() == info->button(QMessageBox::Abort)) if (info->clickedButton() == skip)
return ShouldUpdate::SkipUpdating; return ShouldUpdate::SkipUpdating;
return ShouldUpdate::Cancel; return ShouldUpdate::Cancel;
} }

View File

@ -36,12 +36,12 @@
#include "LaunchController.h" #include "LaunchController.h"
#include "Application.h" #include "Application.h"
#include "minecraft/auth/AccountData.h"
#include "minecraft/auth/AccountList.h" #include "minecraft/auth/AccountList.h"
#include "ui/InstanceWindow.h" #include "ui/InstanceWindow.h"
#include "ui/MainWindow.h" #include "ui/MainWindow.h"
#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/EditAccountDialog.h"
#include "ui/dialogs/ProfileSelectDialog.h" #include "ui/dialogs/ProfileSelectDialog.h"
#include "ui/dialogs/ProfileSetupDialog.h" #include "ui/dialogs/ProfileSetupDialog.h"
#include "ui/dialogs/ProgressDialog.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. // 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"), auto reply = CustomMessageBox::selectable(m_parentWidget, tr("No Accounts"),
tr("In order to play Minecraft, you must have at least one Microsoft " 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?"), "Would you like to open the account manager to add an account now?"),
QMessageBox::Information, QMessageBox::Yes | QMessageBox::No) QMessageBox::Information, QMessageBox::Yes | QMessageBox::No)
->exec(); ->exec();
@ -143,6 +143,12 @@ void LaunchController::login()
bool tryagain = true; bool tryagain = true;
unsigned int tries = 0; 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) { while (tryagain) {
if (tries > 0 && tries % 3 == 0) { if (tries > 0 && tries % 3 == 0) {
auto result = auto result =
@ -161,7 +167,7 @@ void LaunchController::login()
m_accountToUse->fillSession(m_session); m_accountToUse->fillSession(m_session);
// Launch immediately in true offline mode // Launch immediately in true offline mode
if (m_accountToUse->isOffline()) { if (m_accountToUse->accountType() == AccountType::Offline) {
launchInstance(); launchInstance();
return; return;
} }
@ -249,12 +255,6 @@ void LaunchController::login()
progDialog.execWithTask(task.get()); progDialog.execWithTask(task.get());
continue; 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: { case AccountState::Expired: {
auto errorString = tr("The account has expired and needs to be logged into manually again."); 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, QMessageBox::warning(m_parentWidget, tr("Account refresh failed"), errorString, QMessageBox::StandardButton::Ok,

View File

@ -50,7 +50,7 @@
namespace MMCZip { namespace MMCZip {
// ours // 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()); QuaZip modZip(from.filePath());
modZip.open(QuaZip::mdUnzip); 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 * 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 * Compress directory, by providing a list of files to compress

View File

@ -5,6 +5,7 @@
#include <QPixmapCache> #include <QPixmapCache>
#include <QThread> #include <QThread>
#include <QTime> #include <QTime>
#include <limits>
#define GET_TYPE() \ #define GET_TYPE() \
Qt::ConnectionType type; \ Qt::ConnectionType type; \
@ -100,10 +101,14 @@ class PixmapCache final : public QObject {
*/ */
bool _markCacheMissByEviciton() 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(); auto now = QTime::currentTime();
if (!m_last_cache_miss_by_eviciton.isNull()) { if (!m_last_cache_miss_by_eviciton.isNull()) {
auto diff = m_last_cache_miss_by_eviciton.msecsTo(now); 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; ++m_consecutive_fast_evicitons;
} else { } else {
m_consecutive_fast_evicitons = 0; m_consecutive_fast_evicitons = 0;
@ -111,11 +116,17 @@ class PixmapCache final : public QObject {
} }
m_last_cache_miss_by_eviciton = now; m_last_cache_miss_by_eviciton = now;
if (m_consecutive_fast_evicitons >= m_consecutive_fast_evicitons_threshold) { if (m_consecutive_fast_evicitons >= m_consecutive_fast_evicitons_threshold) {
// double the cache size // increase the cache size
auto newSize = _cacheLimit() * 2; uint newSize = _cacheLimit() + step;
qDebug() << m_consecutive_fast_evicitons << "pixmap cache misses by eviction happened too fast, doubling cache size to" if (newSize >= maxInt) { // increase it until you overflow :D
<< newSize; newSize = maxInt;
_setCacheLimit(newSize); 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; m_consecutive_fast_evicitons = 0;
return true; return true;
} }

View File

@ -13,7 +13,7 @@ class RecursiveFileSystemWatcher : public QObject {
QDir rootDir() const { return m_root; } QDir rootDir() const { return m_root; }
// WARNING: setting this to true may be bad for performance // 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; } bool watchFiles() const { return m_watchFiles; }
void setMatcher(IPathMatcher::Ptr matcher) { m_matcher = matcher; } void setMatcher(IPathMatcher::Ptr matcher) { m_matcher = matcher; }

View File

@ -32,7 +32,7 @@ class ResourceDownloadTask : public SequentialTask {
public: public:
explicit ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack, explicit ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
ModPlatform::IndexedVersion version, ModPlatform::IndexedVersion version,
const std::shared_ptr<ResourceFolderModel> packs, std::shared_ptr<ResourceFolderModel> packs,
bool is_indexed = true, bool is_indexed = true,
QString custom_target_folder = {}); QString custom_target_folder = {});
const QString& getFilename() const { return m_pack_version.fileName; } const QString& getFilename() const { return m_pack_version.fileName; }

View File

@ -28,7 +28,7 @@ class VersionProxyModel : public QAbstractProxyModel {
const FilterMap& filters() const; const FilterMap& filters() const;
const QString& search() 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 setSearch(const QString& search);
void clearFilters(); void clearFilters();
QModelIndex getRecommended() const; QModelIndex getRecommended() const;

View File

@ -67,7 +67,7 @@ class IconList : public QAbstractListModel {
virtual Qt::ItemFlags flags(const QModelIndex& index) const override; virtual Qt::ItemFlags flags(const QModelIndex& index) const override;
bool addThemeIcon(const QString& key); 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; void saveIcon(const QString& key, const QString& path, const char* format) const;
bool deleteIcon(const QString& key); bool deleteIcon(const QString& key);
bool trashIcon(const QString& key); bool trashIcon(const QString& key);

View File

@ -34,6 +34,7 @@
*/ */
#include <QDir> #include <QDir>
#include <QFileInfo>
#include <QString> #include <QString>
#include <QStringList> #include <QStringList>
@ -335,6 +336,7 @@ QList<QString> JavaUtils::FindJavaPaths()
} }
} }
candidates.append(getMinecraftJavaBundle());
candidates = addJavasFromEnv(candidates); candidates = addJavasFromEnv(candidates);
candidates.removeDuplicates(); candidates.removeDuplicates();
return candidates; 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/Home/bin/java");
javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java"); javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java");
} }
javas.append(getMinecraftJavaBundle());
javas = addJavasFromEnv(javas); javas = addJavasFromEnv(javas);
javas.removeDuplicates(); javas.removeDuplicates();
return javas; return javas;
@ -411,6 +414,7 @@ QList<QString> JavaUtils::FindJavaPaths()
// javas downloaded by sdkman // javas downloaded by sdkman
scanJavaDirs(FS::PathCombine(home, ".sdkman/candidates/java")); scanJavaDirs(FS::PathCombine(home, ".sdkman/candidates/java"));
javas.append(getMinecraftJavaBundle());
javas = addJavasFromEnv(javas); javas = addJavasFromEnv(javas);
javas.removeDuplicates(); javas.removeDuplicates();
return javas; return javas;
@ -423,6 +427,7 @@ QList<QString> JavaUtils::FindJavaPaths()
QList<QString> javas; QList<QString> javas;
javas.append(this->GetDefaultJava()->path); javas.append(this->GetDefaultJava()->path);
javas.append(getMinecraftJavaBundle());
return addJavasFromEnv(javas); return addJavasFromEnv(javas);
} }
#endif #endif
@ -431,3 +436,50 @@ QString JavaUtils::getJavaCheckPath()
{ {
return APPLICATION->getJarPath("JavaCheck.jar"); 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); QString stripVariableEntries(QString name, QString target, QString remove);
QProcessEnvironment CleanEnviroment(); QProcessEnvironment CleanEnviroment();
QStringList getMinecraftJavaBundle();
class JavaUtils : public QObject { class JavaUtils : public QObject {
Q_OBJECT Q_OBJECT

View File

@ -55,6 +55,6 @@ class Index : public QAbstractListModel, public BaseEntity {
QVector<VersionList::Ptr> m_lists; QVector<VersionList::Ptr> m_lists;
QHash<QString, VersionList::Ptr> m_uids; 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 } // namespace Meta

View File

@ -64,7 +64,7 @@ class Version : public QObject, public BaseVersion, public BaseEntity {
public: // for usage by format parsers only public: // for usage by format parsers only
void setType(const QString& type); 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 setRequires(const Meta::RequireSet& reqs, const Meta::RequireSet& conflicts);
void setVolatile(bool volatile_); void setVolatile(bool volatile_);
void setRecommended(bool recommended); void setRecommended(bool recommended);

View File

@ -79,7 +79,7 @@ class VersionList : public BaseVersionList, public BaseEntity {
Version::Ptr m_recommended; Version::Ptr m_recommended;
void setupAddedVersion(const int row, const Version::Ptr& version); void setupAddedVersion(int row, const Version::Ptr& version);
}; };
} // namespace Meta } // namespace Meta
Q_DECLARE_METATYPE(Meta::VersionList::Ptr) 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_MC_DIR", QDir::toNativeSeparators(QDir(gameRoot()).absolutePath()));
out.insert("INST_JAVA", settings()->get("JavaPath").toString()); out.insert("INST_JAVA", settings()->get("JavaPath").toString());
out.insert("INST_JAVA_ARGS", javaArguments().join(' ')); out.insert("INST_JAVA_ARGS", javaArguments().join(' '));
out.insert("NO_COLOR", "1");
return out; return out;
} }
@ -564,43 +565,6 @@ QProcessEnvironment MinecraftInstance::createEnvironment()
for (auto it = variables.begin(); it != variables.end(); ++it) { for (auto it = variables.begin(); it != variables.end(); ++it) {
env.insert(it.key(), it.value()); 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 // custom env
auto insertEnv = [&env](QMap<QString, QVariant> envMap) { auto insertEnv = [&env](QMap<QString, QVariant> envMap) {
@ -617,7 +581,53 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment()
insertEnv(APPLICATION->settings()->get("Env").toMap()); insertEnv(APPLICATION->settings()->get("Env").toMap());
else else
insertEnv(settings()->get("Env").toMap()); 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; return env;
} }

View File

@ -157,20 +157,6 @@ void MojangVersionFormat::readVersionProperties(const QJsonObject& in, VersionFi
Bits::readString(in, "id", out->minecraftVersion); Bits::readString(in, "id", out->minecraftVersion);
Bits::readString(in, "mainClass", out->mainClass); Bits::readString(in, "mainClass", out->mainClass);
Bits::readString(in, "minecraftArguments", out->minecraftArguments); 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, "type", out->type);
Bits::readString(in, "assets", out->assets); Bits::readString(in, "assets", out->assets);

View File

@ -9,7 +9,7 @@
class OneSixVersionFormat { class OneSixVersionFormat {
public: public:
// version files / profile patches // 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); static QJsonDocument versionFileToJson(const VersionFilePtr& patch);
// libraries // libraries

View File

@ -430,7 +430,7 @@ bool PackProfile::remove(const int index)
return true; return true;
} }
bool PackProfile::remove(const QString id) bool PackProfile::remove(const QString& id)
{ {
int i = 0; int i = 0;
for (auto patch : d->components) { for (auto patch : d->components) {

View File

@ -89,13 +89,13 @@ class PackProfile : public QAbstractListModel {
enum MoveDirection { MoveUp, MoveDown }; enum MoveDirection { MoveUp, MoveDown };
/// move component file # up or down the list /// 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 /// remove component file # - including files/records
bool remove(const int index); bool remove(int index);
/// remove component file by id - including files/records /// remove component file by id - including files/records
bool remove(const QString id); bool remove(const QString& id);
bool customize(int index); 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); 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(); auto data = doc.toJson();
QSaveFile jsonFile(filename); QSaveFile jsonFile(filename);

View File

@ -47,10 +47,10 @@ bool readOverrideOrders(QString path, PatchOrder& order);
bool writeOverrideOrders(QString path, const PatchOrder& order); bool writeOverrideOrders(QString path, const PatchOrder& order);
/// Parse a version file in JSON format /// 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) /// 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. /// Remove LWJGL from a patch file. This is applied to all Mojang-like profile files.
void removeLwjglFromPatch(VersionFilePtr patch); void removeLwjglFromPatch(VersionFilePtr patch);

View File

@ -52,8 +52,6 @@
#include <FileSystem.h> #include <FileSystem.h>
#include <QSaveFile> #include <QSaveFile>
#include <chrono>
enum AccountListVersion { MojangMSA = 3 }; enum AccountListVersion { MojangMSA = 3 };
AccountList::AccountList(QObject* parent) : QAbstractListModel(parent) AccountList::AccountList(QObject* parent) : QAbstractListModel(parent)
@ -283,9 +281,15 @@ QVariant AccountList::data(const QModelIndex& index, int role) const
return account->accountDisplayString(); return account->accountDisplayString();
case TypeColumn: { case TypeColumn: {
auto typeStr = account->typeString(); switch (account->accountType()) {
typeStr[0] = typeStr[0].toUpper(); case AccountType::MSA: {
return typeStr; return tr("MSA", "Account type");
}
case AccountType::Offline: {
return tr("Offline", "Account type");
}
}
return tr("Unknown", "Account type");
} }
case StatusColumn: { case StatusColumn: {

View File

@ -75,7 +75,7 @@ class AccountList : public QAbstractListModel {
virtual Qt::ItemFlags flags(const QModelIndex& index) const override; virtual Qt::ItemFlags flags(const QModelIndex& index) const override;
virtual bool setData(const QModelIndex& index, const QVariant& value, int role) 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); void removeAccount(QModelIndex index);
int findAccountByProfileId(const QString& profileId) const; int findAccountByProfileId(const QString& profileId) const;
MinecraftAccountPtr getAccountByProfileName(const QString& profileName) const; MinecraftAccountPtr getAccountByProfileName(const QString& profileName) const;

View File

@ -52,6 +52,7 @@
#include "flows/MSA.h" #include "flows/MSA.h"
#include "flows/Offline.h" #include "flows/Offline.h"
#include "minecraft/auth/AccountData.h"
MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent) 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. // NOTE: this doesn't do much. There was an error of some sort.
} break; } break;
case AccountTaskState::STATE_FAILED_HARD: { case AccountTaskState::STATE_FAILED_HARD: {
if (isMSA()) { if (accountType() == AccountType::MSA) {
data.msaToken.token = QString(); data.msaToken.token = QString();
data.msaToken.refresh_token = QString(); data.msaToken.refresh_token = QString();
data.msaToken.validity = Katabasis::Validity::None; data.msaToken.validity = Katabasis::Validity::None;

View File

@ -118,9 +118,7 @@ class MinecraftAccount : public QObject, public Usable {
bool isActive() const; bool isActive() const;
bool isMSA() const { return data.type == AccountType::MSA; } [[nodiscard]] AccountType accountType() const noexcept { return data.type; }
bool isOffline() const { return data.type == AccountType::Offline; }
bool ownsMinecraft() const { return data.minecraftEntitlement.ownsMinecraft; } bool ownsMinecraft() const { return data.minecraftEntitlement.ownsMinecraft; }

View File

@ -37,9 +37,9 @@
#include "ModFolderModel.h" #include "ModFolderModel.h"
#include <FileSystem.h> #include <FileSystem.h>
#include <qheaderview.h>
#include <QDebug> #include <QDebug>
#include <QFileSystemWatcher> #include <QFileSystemWatcher>
#include <QHeaderView>
#include <QIcon> #include <QIcon>
#include <QMimeData> #include <QMimeData>
#include <QString> #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 = 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_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_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::VERSION, SortType::DATE, SortType::PROVIDER };
m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch,
QHeaderView::ResizeToContents, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents }; QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive };
m_columnsHideable = { false, true, false, true, true, true }; m_columnsHideable = { false, true, false, true, true, true };
} }
@ -131,6 +131,11 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
} }
return {}; return {};
} }
case Qt::SizeHintRole:
if (column == ImageColumn) {
return QSize(32, 32);
}
return {};
case Qt::CheckStateRole: case Qt::CheckStateRole:
switch (column) { switch (column) {
case ActiveColumn: case ActiveColumn:

View File

@ -4,6 +4,7 @@
#include <QCoreApplication> #include <QCoreApplication>
#include <QDebug> #include <QDebug>
#include <QFileInfo> #include <QFileInfo>
#include <QHeaderView>
#include <QIcon> #include <QIcon>
#include <QMenu> #include <QMenu>
#include <QMimeData> #include <QMimeData>
@ -45,7 +46,7 @@ ResourceFolderModel::~ResourceFolderModel()
QCoreApplication::processEvents(); QCoreApplication::processEvents();
} }
bool ResourceFolderModel::startWatching(const QStringList paths) bool ResourceFolderModel::startWatching(const QStringList& paths)
{ {
if (m_is_watching) if (m_is_watching)
return false; return false;
@ -64,7 +65,7 @@ bool ResourceFolderModel::startWatching(const QStringList paths)
return m_is_watching; return m_is_watching;
} }
bool ResourceFolderModel::stopWatching(const QStringList paths) bool ResourceFolderModel::stopWatching(const QStringList& paths)
{ {
if (!m_is_watching) if (!m_is_watching)
return false; return false;
@ -460,9 +461,9 @@ bool ResourceFolderModel::setData(const QModelIndex& index, [[maybe_unused]] con
if (role == Qt::CheckStateRole) { if (role == Qt::CheckStateRole) {
if (m_instance != nullptr && m_instance->isRunning()) { if (m_instance != nullptr && m_instance->isRunning()) {
auto response = auto response =
CustomMessageBox::selectable(nullptr, "Confirm toggle", CustomMessageBox::selectable(nullptr, tr("Confirm toggle"),
"If you enable/disable this resource while the game is running it may crash your game.\n" 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?", "Are you sure you want to do this?"),
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
->exec(); ->exec();
@ -516,36 +517,22 @@ void ResourceFolderModel::setupHeaderAction(QAction* act, int column)
act->setText(columnNames().at(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) auto setting = (m_instance->settings()->contains(setting_name)) ? m_instance->settings()->getSetting(setting_name)
: m_instance->settings()->registerSetting(setting_name); : m_instance->settings()->registerSetting(setting_name);
auto hiddenColumns = setting->get().toStringList(); setting->set(tree->header()->saveState());
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);
} }
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) auto setting = (m_instance->settings()->contains(setting_name)) ? m_instance->settings()->getSetting(setting_name)
: m_instance->settings()->registerSetting(setting_name); : m_instance->settings()->registerSetting(setting_name);
auto hiddenColumns = setting->get().toStringList(); tree->header()->restoreState(setting->get().toByteArray());
auto col_names = columnNames(false);
for (auto col_name : hiddenColumns) {
auto index = col_names.indexOf(col_name);
if (index >= 0)
tree->setColumnHidden(index, true);
}
} }
QMenu* ResourceFolderModel::createHeaderContextMenu(QTreeView* tree) QMenu* ResourceFolderModel::createHeaderContextMenu(QTreeView* tree)
@ -570,7 +557,7 @@ QMenu* ResourceFolderModel::createHeaderContextMenu(QTreeView* tree)
if (m_column_resize_modes.at(c) == QHeaderView::ResizeToContents) if (m_column_resize_modes.at(c) == QHeaderView::ResizeToContents)
tree->resizeColumnToContents(c); tree->resizeColumnToContents(c);
} }
saveHiddenColumn(col, !toggled); saveColumns(tree);
}); });
menu->addAction(act); menu->addAction(act);

View File

@ -39,14 +39,14 @@ class ResourceFolderModel : public QAbstractListModel {
* Returns whether starting to watch all the paths was successful. * Returns whether starting to watch all the paths was successful.
* If one or more fails, it returns false. * If one or more fails, it returns false.
*/ */
bool startWatching(const QStringList paths); bool startWatching(const QStringList& paths);
/** Stops watching the paths for changes. /** Stops watching the paths for changes.
* *
* Returns whether stopping to watch all the paths was successful. * Returns whether stopping to watch all the paths was successful.
* If one or more fails, it returns false. * 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. */ /* Helper methods for subclasses, using a predetermined list of paths. */
virtual bool startWatching() { return startWatching({ m_dir.absolutePath() }); } 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; [[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
void setupHeaderAction(QAction* act, int column); void setupHeaderAction(QAction* act, int column);
void saveHiddenColumn(int column, bool hidden); void saveColumns(QTreeView* tree);
void loadHiddenColumns(QTreeView* tree); void loadColumns(QTreeView* tree);
QMenu* createHeaderContextMenu(QTreeView* tree); QMenu* createHeaderContextMenu(QTreeView* tree);
/** This creates a proxy model to filter / sort the model for a UI. /** 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 }; QList<SortType> m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE };
QStringList m_column_names = { "Enable", "Name", "Last Modified" }; QStringList m_column_names = { "Enable", "Name", "Last Modified" };
QStringList m_column_names_translated = { tr("Enable"), tr("Name"), tr("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, QList<QHeaderView::ResizeMode> m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive };
QHeaderView::ResizeToContents };
QList<bool> m_columnsHideable = { false, false, true }; QList<bool> m_columnsHideable = { false, false, true };
QDir m_dir; QDir m_dir;
@ -307,7 +306,6 @@ void ResourceFolderModel::applyUpdates(QSet<QString>& current_set, QSet<QString>
auto removed_it = m_resources.begin() + removed_index; auto removed_it = m_resources.begin() + removed_index;
Q_ASSERT(removed_it != m_resources.end()); Q_ASSERT(removed_it != m_resources.end());
Q_ASSERT(removed_set.contains(removed_it->get()->internal_id()));
if ((*removed_it)->isResolving()) { if ((*removed_it)->isResolving()) {
auto ticket = (*removed_it)->resolutionTicket(); 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 = 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_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_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, m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive,
QHeaderView::ResizeToContents }; QHeaderView::Interactive };
m_columnsHideable = { false, true, false, true, true }; 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(); return m_resources[row]->internal_id();
} }
case Qt::SizeHintRole:
if (column == ImageColumn) {
return QSize(32, 32);
}
return {};
case Qt::CheckStateRole: case Qt::CheckStateRole:
switch (column) { switch (column) {
case ActiveColumn: 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 = QStringList({ "Enable", "Image", "Name", "Last Modified" });
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("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_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE };
m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive };
QHeaderView::ResizeToContents };
m_columnsHideable = { false, true, false, true }; m_columnsHideable = { false, true, false, true };
} }
@ -104,6 +103,11 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const
} }
return {}; return {};
} }
case Qt::SizeHintRole:
if (column == ImageColumn) {
return QSize(32, 32);
}
return {};
case Qt::CheckStateRole: case Qt::CheckStateRole:
if (column == ActiveColumn) { if (column == ActiveColumn) {
return m_resources[row]->enabled() ? Qt::Checked : Qt::Unchecked; 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 provider = pDep->pack->provider == m_flame_provider.name ? m_flame_provider : m_modrinth_provider;
auto responseInfo = std::make_shared<QByteArray>(); auto responseInfo = std::make_shared<QByteArray>();
auto info = provider.api->getProject(pDep->pack->addonId.toString(), responseInfo); 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{}; QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*responseInfo, &parse_error); QJsonDocument doc = QJsonDocument::fromJson(*responseInfo, &parse_error);
if (parse_error.error != QJsonParseError::NoError) { if (parse_error.error != QJsonParseError::NoError) {
removePack(pDep->pack->addonId);
qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset
<< " reason: " << parse_error.errorString(); << " reason: " << parse_error.errorString();
qDebug() << *responseInfo; qDebug() << *responseInfo;
@ -151,6 +152,7 @@ Task::Ptr GetModDependenciesTask::getProjectInfoTask(std::shared_ptr<PackDepende
: Json::requireObject(doc); : Json::requireObject(doc);
provider.mod->loadIndexedPack(*pDep->pack, obj); provider.mod->loadIndexedPack(*pDep->pack, obj);
} catch (const JSONValidationError& e) { } catch (const JSONValidationError& e) {
removePack(pDep->pack->addonId);
qDebug() << doc; qDebug() << doc;
qWarning() << "Error while reading mod info: " << e.cause(); 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::DependencySearchArgs args = { dep, m_version, m_loaderType };
ResourceAPI::DependencySearchCallbacks callbacks; 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) { callbacks.on_succeed = [dep, provider, pDep, level, this](auto& doc, [[maybe_unused]] auto& pack) {
try { try {
QJsonArray arr; QJsonArray arr;
@ -211,11 +215,13 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
pDep->pack->versionsLoaded = true; pDep->pack->versionsLoaded = true;
} catch (const JSONValidationError& e) { } catch (const JSONValidationError& e) {
removePack(dep.addonId);
qDebug() << doc; qDebug() << doc;
qWarning() << "Error while reading mod version: " << e.cause(); qWarning() << "Error while reading mod version: " << e.cause();
return; return;
} }
if (level == 0) { if (level == 0) {
removePack(dep.addonId);
qWarning() << "Dependency cycle exceeded"; qWarning() << "Dependency cycle exceeded";
return; return;
} }
@ -238,7 +244,7 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
return tasks; 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; }; auto pred = [addonId](const std::shared_ptr<PackDependency>& v) { return v->pack->addonId == addonId; };
#if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0) #if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0)
@ -279,4 +285,4 @@ QHash<QString, QStringList> GetModDependenciesTask::getRequiredBy()
rby[addonId.toString()] = req; rby[addonId.toString()] = req;
} }
return rby; return rby;
} }

View File

@ -65,13 +65,13 @@ class GetModDependenciesTask : public SequentialTask {
QHash<QString, QStringList> getRequiredBy(); QHash<QString, QStringList> getRequiredBy();
protected slots: 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&, QList<ModPlatform::Dependency> getDependenciesForVersion(const ModPlatform::IndexedVersion&,
const ModPlatform::ResourceProvider providerName); ModPlatform::ResourceProvider providerName);
void prepare(); void prepare();
Task::Ptr getProjectInfoTask(std::shared_ptr<PackDependency> pDep); Task::Ptr getProjectInfoTask(std::shared_ptr<PackDependency> pDep);
ModPlatform::Dependency getOverride(const ModPlatform::Dependency&, const ModPlatform::ResourceProvider providerName); ModPlatform::Dependency getOverride(const ModPlatform::Dependency&, ModPlatform::ResourceProvider providerName);
void removePack(const QVariant addonId); void removePack(const QVariant& addonId);
private: private:
QList<std::shared_ptr<PackDependency>> m_pack_dependencies; 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 png_invalid(); // icon invalid
} }
} }
return false;
} }
case ResourceType::ZIPFILE: { case ResourceType::ZIPFILE: {
QuaZip zip(mod.fileinfo().filePath()); QuaZip zip(mod.fileinfo().filePath());
@ -714,6 +715,7 @@ bool loadIconFile(const Mod& mod)
} else { } else {
return png_invalid(); // could not set icon as current file. return png_invalid(); // could not set icon as current file.
} }
return false;
} }
case ResourceType::LITEMOD: { case ResourceType::LITEMOD: {
return false; // can lightmods even have icons? return false; // can lightmods even have icons?

View File

@ -232,10 +232,9 @@ bool processPackPNG(const ResourcePack& pack)
} else { } else {
return png_invalid(); // pack.png does not exists or is not a valid file. 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: { case ResourceType::ZIPFILE: {
Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
QuaZip zip(pack.fileinfo().filePath()); QuaZip zip(pack.fileinfo().filePath());
if (!zip.open(QuaZip::mdUnzip)) if (!zip.open(QuaZip::mdUnzip))
return false; // can't open zip file return false; // can't open zip file
@ -259,6 +258,7 @@ bool processPackPNG(const ResourcePack& pack)
} else { } else {
return png_invalid(); // could not set pack.mcmeta as current file. return png_invalid(); // could not set pack.mcmeta as current file.
} }
return false; // not processed correctly; https://github.com/PrismLauncher/PrismLauncher/issues/1740
} }
default: default:
qWarning() << "Invalid type for resource pack parse task!"; qWarning() << "Invalid type for resource pack parse task!";

View File

@ -186,10 +186,9 @@ bool processPackPNG(const TexturePack& pack)
} else { } else {
return png_invalid(); // pack.png does not exists or is not a valid file. return png_invalid(); // pack.png does not exists or is not a valid file.
} }
return false;
} }
case ResourceType::ZIPFILE: { case ResourceType::ZIPFILE: {
Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
QuaZip zip(pack.fileinfo().filePath()); QuaZip zip(pack.fileinfo().filePath());
if (!zip.open(QuaZip::mdUnzip)) if (!zip.open(QuaZip::mdUnzip))
return false; // can't open zip file return false; // can't open zip file
@ -215,6 +214,7 @@ bool processPackPNG(const TexturePack& pack)
zip.close(); zip.close();
return png_invalid(); // could not set pack.mcmeta as current file. return png_invalid(); // could not set pack.mcmeta as current file.
} }
return false;
} }
default: default:
qWarning() << "Invalid type for resource pack parse task!"; qWarning() << "Invalid type for resource pack parse task!";

View File

@ -149,6 +149,7 @@ void EnsureMetadataTask::executeTask()
if (m_current_task) if (m_current_task)
m_current_task.reset(); m_current_task.reset();
}); });
connect(project_task.get(), &Task::failed, this, &EnsureMetadataTask::emitFailed);
m_current_task = project_task; m_current_task = project_task;
project_task->start(); project_task->start();

View File

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

View File

@ -96,6 +96,7 @@ class ResourceAPI {
}; };
struct VersionSearchCallbacks { struct VersionSearchCallbacks {
std::function<void(QJsonDocument&, ModPlatform::IndexedPack)> on_succeed; std::function<void(QJsonDocument&, ModPlatform::IndexedPack)> on_succeed;
std::function<void(QString const& reason, int network_error_code)> on_fail;
}; };
struct ProjectInfoArgs { struct ProjectInfoArgs {
@ -118,6 +119,7 @@ class ResourceAPI {
struct DependencySearchCallbacks { struct DependencySearchCallbacks {
std::function<void(QJsonDocument&, const ModPlatform::Dependency&)> on_succeed; std::function<void(QJsonDocument&, const ModPlatform::Dependency&)> on_succeed;
std::function<void(QString const& reason, int network_error_code)> on_fail;
}; };
public: public:

View File

@ -119,7 +119,6 @@ void Flame::FileResolvingTask::netJobFinished()
connect(m_checkJob.get(), &NetJob::failed, this, [this, step_progress](QString reason) { connect(m_checkJob.get(), &NetJob::failed, this, [this, step_progress](QString reason) {
step_progress->state = TaskStepState::Failed; step_progress->state = TaskStepState::Failed;
stepProgress(*step_progress); stepProgress(*step_progress);
emitFailed(reason);
}); });
connect(m_checkJob.get(), &NetJob::stepProgress, this, &FileResolvingTask::propagateStepProgress); connect(m_checkJob.get(), &NetJob::stepProgress, this, &FileResolvingTask::propagateStepProgress);
connect(m_checkJob.get(), &NetJob::progress, this, [this, step_progress](qint64 current, qint64 total) { 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; 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 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; return true;
} }
ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info) ModPlatform::IndexedPack FlameCheckUpdate::getProjectInfo(ModPlatform::IndexedVersion& ver_info)
{ {
ModPlatform::IndexedPack pack; 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] { QObject::connect(get_project_job, &NetJob::finished, [&loop, get_project_job] {
get_project_job->deleteLater(); get_project_job->deleteLater();
loop.quit(); loop.quit();
@ -68,7 +69,7 @@ ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info)
return pack; return pack;
} }
ModPlatform::IndexedVersion getFileInfo(int addonId, int fileId) ModPlatform::IndexedVersion FlameCheckUpdate::getFileInfo(int addonId, int fileId)
{ {
ModPlatform::IndexedVersion ver; ModPlatform::IndexedVersion ver;
@ -100,7 +101,7 @@ ModPlatform::IndexedVersion getFileInfo(int addonId, int fileId)
qDebug() << doc; 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] { QObject::connect(get_file_info_job, &NetJob::finished, [&loop, get_file_info_job] {
get_file_info_job->deleteLater(); get_file_info_job->deleteLater();
loop.quit(); loop.quit();

View File

@ -22,6 +22,9 @@ class FlameCheckUpdate : public CheckUpdateTask {
void executeTask() override; void executeTask() override;
private: private:
ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info);
ModPlatform::IndexedVersion getFileInfo(int addonId, int fileId);
NetJob* m_net_job = nullptr; NetJob* m_net_job = nullptr;
bool m_was_aborted = false; 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)); 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); connect(job.get(), &Task::finished, &loop, &QEventLoop::quit);
m_process_update_file_info_job = job; 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) // Don't add managed info to packs without an ID (most likely imported from ZIP)
if (!m_managed_id.isEmpty()) if (!m_managed_id.isEmpty())
instance.setManagedPack("flame", m_managed_id, m_pack.name, m_managed_version_id, m_pack.version); instance.setManagedPack("flame", m_managed_id, m_pack.name, m_managed_version_id, m_pack.version);
else
instance.setManagedPack("flame", "", name(), "", "");
instance.setName(name()); instance.setName(name());
m_mod_id_resolver.reset(new Flame::FileResolvingTask(APPLICATION->network(), m_pack)); m_mod_id_resolver.reset(new Flame::FileResolvingTask(APPLICATION->network(), m_pack));

View File

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

View File

@ -102,6 +102,13 @@ Task::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Versi
callbacks.on_succeed(doc, args.pack); 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; return netJob;
} }
@ -146,6 +153,12 @@ Task::Ptr NetworkResourceAPI::getDependencyVersion(DependencySearchArgs&& args,
callbacks.on_succeed(doc, args.dependency); 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; return netJob;
} }

View File

@ -111,14 +111,12 @@ Task::Ptr ModrinthAPI::getProjects(QStringList addonIds, std::shared_ptr<QByteAr
return netJob; 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 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 response = std::make_shared<QByteArray>();
auto job = api.latestVersions(hashes, best_hash_type, m_game_versions, m_loaders, response); 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{}; QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) { if (parse_error.error != QJsonParseError::NoError) {
@ -82,7 +80,7 @@ void ModrinthCheckUpdate::executeTask()
<< " reason: " << parse_error.errorString(); << " reason: " << parse_error.errorString();
qWarning() << *response; qWarning() << *response;
failed(parse_error.errorString()); emitFailed(parse_error.errorString());
return; return;
} }
@ -167,19 +165,17 @@ void ModrinthCheckUpdate::executeTask()
m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, project_ver)); m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, project_ver));
} }
} catch (Json::JsonException& e) { } 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...")); setStatus(tr("Waiting for the API response from Modrinth..."));
setProgress(1, 3); setProgress(1, 3);
m_net_job = qSharedPointerObjectCast<NetJob, Task>(job); m_net_job = qSharedPointerObjectCast<NetJob, Task>(job);
job->start(); 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) // Don't add managed info to packs without an ID (most likely imported from ZIP)
if (!m_managed_id.isEmpty()) if (!m_managed_id.isEmpty())
instance.setManagedPack("modrinth", m_managed_id, m_managed_name, m_managed_version_id, version()); instance.setManagedPack("modrinth", m_managed_id, m_managed_name, m_managed_version_id, version());
else
instance.setManagedPack("modrinth", "", name(), "", "");
instance.setName(name()); instance.setName(name());
instance.saveNow(); 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 // 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. // is preserved, but if we're using the original one, we update the version string.
// NOTE: This needs to come before the copyManagedPack call! // 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) if (askForChangingInstanceName(m_parent, inst->name(), instance.name()) == InstanceNameChange::ShouldChange)
inst->setName(instance.name()); inst->setName(instance.name());
} }

View File

@ -68,7 +68,7 @@ class ModrinthPackExportTask : public Task {
void collectFiles(); void collectFiles();
void collectHashes(); void collectHashes();
void makeApiRequest(); void makeApiRequest();
void parseApiResponse(const std::shared_ptr<QByteArray> response); void parseApiResponse(std::shared_ptr<QByteArray> response);
void buildZip(); void buildZip();
QByteArray generateIndex(); QByteArray generateIndex();

View File

@ -104,6 +104,8 @@ void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob
pack.extraData.donate.append(donate); pack.extraData.donate.append(donate);
} }
pack.extraData.status = Json::ensureString(obj, "status");
pack.extraData.body = Json::ensureString(obj, "body").remove("<br>"); pack.extraData.body = Json::ensureString(obj, "body").remove("<br>");
pack.extraDataLoaded = true; pack.extraDataLoaded = true;

View File

@ -95,6 +95,8 @@ void loadIndexedInfo(Modpack& pack, QJsonObject& obj)
pack.extra.donate.append(donate); pack.extra.donate.append(donate);
} }
pack.extra.status = Json::ensureString(obj, "status");
pack.extraInfoLoaded = true; pack.extraInfoLoaded = true;
} }

View File

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

View File

@ -33,6 +33,6 @@ class TechnicPackProcessor : public QObject {
const QString& instIcon, const QString& instIcon,
const QString& stagingPath, const QString& stagingPath,
const QString& minecraftVersion = QString(), const QString& minecraftVersion = QString(),
const bool isSolder = false); bool isSolder = false);
}; };
} // namespace Technic } // namespace Technic

View File

@ -36,6 +36,7 @@
*/ */
#include "NetJob.h" #include "NetJob.h"
#include "tasks/ConcurrentTask.h"
#if defined(LAUNCHER_APPLICATION) #if defined(LAUNCHER_APPLICATION)
#include "Application.h" #include "Application.h"
#endif #endif
@ -56,18 +57,15 @@ auto NetJob::addNetAction(NetAction::Ptr action) -> bool
return true; 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)
// 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) {
if (!m_failed.isEmpty() && m_try < 3) { m_try += 1;
m_try += 1; while (!m_failed.isEmpty())
while (!m_failed.isEmpty()) m_queue.enqueue(m_failed.take(*m_failed.keyBegin()));
m_queue.enqueue(m_failed.take(*m_failed.keyBegin()));
}
} }
ConcurrentTask::executeNextSubTask();
ConcurrentTask::startNext();
} }
auto NetJob::size() const -> int 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); explicit NetJob(QString job_name, shared_qobject_ptr<QNetworkAccessManager> network);
~NetJob() override = default; ~NetJob() override = default;
void startNext() override;
auto size() const -> int; auto size() const -> int;
auto canAbort() const -> bool override; auto canAbort() const -> bool override;
@ -69,6 +67,9 @@ class NetJob : public ConcurrentTask {
// Qt can't handle auto at the start for some reason? // Qt can't handle auto at the start for some reason?
bool abort() override; bool abort() override;
protected slots:
void executeNextSubTask() override;
protected: protected:
void updateState() override; void updateState() override;

View File

@ -5,6 +5,7 @@
qt.*.debug=false qt.*.debug=false
# don't log credentials by default # don't log credentials by default
launcher.auth.credentials.debug=false launcher.auth.credentials.debug=false
katabasis.*.debug=false
# remove the debug lines, other log levels still get through # remove the debug lines, other log levels still get through
launcher.task.net.download.debug=false launcher.task.net.download.debug=false
# enable or disable whole catageries # enable or disable whole catageries

View File

@ -58,14 +58,14 @@ void ImgurUpload::init()
QNetworkReply* ImgurUpload::getReply(QNetworkRequest& request) 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)) { if (!file->open(QFile::ReadOnly)) {
emitFailed(); emitFailed();
return nullptr; return nullptr;
} }
QHttpMultiPart* multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType); QHttpMultiPart* multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType, this);
file->setParent(multipart); file->setParent(multipart);
QHttpPart filePart; QHttpPart filePart;
filePart.setBodyDevice(file); filePart.setBodyDevice(file);

View File

@ -54,6 +54,7 @@ bool INIFile::saveFile(QString fileName)
insert("ConfigVersion", "1.2"); insert("ConfigVersion", "1.2");
QSettings _settings_obj{ fileName, QSettings::Format::IniFormat }; QSettings _settings_obj{ fileName, QSettings::Format::IniFormat };
_settings_obj.setFallbacksEnabled(false); _settings_obj.setFallbacksEnabled(false);
_settings_obj.clear();
for (Iterator iter = begin(); iter != end(); iter++) for (Iterator iter = begin(); iter != end(); iter++)
_settings_obj.setValue(iter.key(), iter.value()); _settings_obj.setValue(iter.key(), iter.value());

View File

@ -35,7 +35,6 @@
*/ */
#include "ConcurrentTask.h" #include "ConcurrentTask.h"
#include <QCoreApplication>
#include <QDebug> #include <QDebug>
#include "tasks/Task.h" #include "tasks/Task.h"
@ -47,9 +46,9 @@ ConcurrentTask::ConcurrentTask(QObject* parent, QString task_name, int max_concu
ConcurrentTask::~ConcurrentTask() ConcurrentTask::~ConcurrentTask()
{ {
for (auto task : m_queue) { for (auto task : m_doing) {
if (task) if (task)
task->deleteLater(); task->disconnect(this);
} }
} }
@ -65,15 +64,13 @@ void ConcurrentTask::addTask(Task::Ptr task)
void ConcurrentTask::executeTask() void ConcurrentTask::executeTask()
{ {
// Start one task, startNext handles starting the up to the m_total_max_size for (auto i = 0; i < m_total_max_size; i++)
// while tracking the number currently being done QMetaObject::invokeMethod(this, &ConcurrentTask::executeNextSubTask, Qt::QueuedConnection);
QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection);
} }
bool ConcurrentTask::abort() bool ConcurrentTask::abort()
{ {
m_queue.clear(); m_queue.clear();
m_aborted = true;
if (m_doing.isEmpty()) { if (m_doing.isEmpty()) {
// Don't call emitAborted() here, we want to bypass the 'is the task running' check // 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_failed.clear();
m_queue.clear(); m_queue.clear();
m_aborted = false;
m_progress = 0; m_progress = 0;
m_stepProgress = 0; m_stepProgress = 0;
} }
void ConcurrentTask::startNext() void ConcurrentTask::executeNextSubTask()
{ {
if (m_aborted || m_doing.count() > m_total_max_size) if (!isRunning()) {
return; return;
}
if (m_queue.isEmpty() && m_doing.isEmpty() && !wasSuccessful()) { if (m_doing.count() >= m_total_max_size) {
emitSucceeded(); return;
}
if (m_queue.isEmpty()) {
if (m_doing.isEmpty()) {
if (m_failed.isEmpty())
emitSucceeded();
else
emitFailed(tr("One or more subtasks failed"));
}
return; return;
} }
if (m_queue.isEmpty()) startSubTask(m_queue.dequeue());
return; }
Task::Ptr next = m_queue.dequeue();
void ConcurrentTask::startSubTask(Task::Ptr next)
{
connect(next.get(), &Task::succeeded, this, [this, next]() { subTaskSucceeded(next); }); connect(next.get(), &Task::succeeded, this, [this, next]() { subTaskSucceeded(next); });
connect(next.get(), &Task::failed, this, [this, next](QString msg) { subTaskFailed(next, msg); }); 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::aborted, this, [this, next] { subTaskFailed(next, "Aborted"); });
connect(next.get(), &Task::status, this, [this, next](QString msg) { subTaskStatus(next, msg); }); 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); }); connect(next.get(), &Task::progress, this, [this, next](qint64 current, qint64 total) { subTaskProgress(next, current, total); });
m_doing.insert(next.get(), next); 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()); auto task_progress = std::make_shared<TaskStepProgress>(next->getUid());
m_task_progress.insert(next->getUid(), task_progress); m_task_progress.insert(next->getUid(), task_progress);
updateState(); updateState();
updateStepProgress(*task_progress.get(), Operation::ADDED); updateStepProgress(*task_progress.get(), Operation::ADDED);
QCoreApplication::processEvents();
QMetaObject::invokeMethod(next.get(), &Task::start, Qt::QueuedConnection); 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. void ConcurrentTask::subTaskFinished(Task::Ptr task, TaskStepState state)
for (int i = 0; i < num_starts; i++) {
QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection); 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) void ConcurrentTask::subTaskSucceeded(Task::Ptr task)
{ {
m_done.insert(task.get(), task); subTaskFinished(task, TaskStepState::Succeeded);
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();
} }
void ConcurrentTask::subTaskFailed(Task::Ptr task, [[maybe_unused]] const QString& msg) void ConcurrentTask::subTaskFailed(Task::Ptr task, [[maybe_unused]] const QString& msg)
{ {
m_done.insert(task.get(), task); subTaskFinished(task, TaskStepState::Failed);
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();
} }
void ConcurrentTask::subTaskStatus(Task::Ptr task, const QString& msg) void ConcurrentTask::subTaskStatus(Task::Ptr task, const QString& msg)

View File

@ -72,10 +72,11 @@ class ConcurrentTask : public Task {
protected slots: protected slots:
void executeTask() override; void executeTask() override;
virtual void startNext(); virtual void executeNextSubTask();
void subTaskSucceeded(Task::Ptr); 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 subTaskStatus(Task::Ptr task, const QString& msg);
void subTaskDetails(Task::Ptr task, const QString& msg); void subTaskDetails(Task::Ptr task, const QString& msg);
void subTaskProgress(Task::Ptr task, qint64 current, qint64 total); void subTaskProgress(Task::Ptr task, qint64 current, qint64 total);
@ -90,6 +91,8 @@ class ConcurrentTask : public Task {
virtual void updateState(); virtual void updateState();
void startSubTask(Task::Ptr task);
protected: protected:
QString m_name; QString m_name;
QString m_step_status; QString m_step_status;
@ -107,6 +110,4 @@ class ConcurrentTask : public Task {
qint64 m_stepProgress = 0; qint64 m_stepProgress = 0;
qint64 m_stepTotalProgress = 100; qint64 m_stepTotalProgress = 100;
bool m_aborted = false;
}; };

View File

@ -36,9 +36,9 @@
#include <QDebug> #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()) { if (m_done.size() != m_failed.size()) {
emitSucceeded(); emitSucceeded();
@ -51,7 +51,7 @@ void MultipleOptionsTask::startNext()
return; return;
} }
ConcurrentTask::startNext(); ConcurrentTask::executeNextSubTask();
} }
void MultipleOptionsTask::updateState() void MultipleOptionsTask::updateState()

View File

@ -34,18 +34,18 @@
*/ */
#pragma once #pragma once
#include "SequentialTask.h" #include "ConcurrentTask.h"
/* This task type will attempt to do run each of it's subtasks in sequence, /* 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. * until one of them succeeds. When that happens, the remaining tasks will not run.
* */ * */
class MultipleOptionsTask : public SequentialTask { class MultipleOptionsTask : public ConcurrentTask {
Q_OBJECT Q_OBJECT
public: public:
explicit MultipleOptionsTask(QObject* parent = nullptr, const QString& task_name = ""); explicit MultipleOptionsTask(QObject* parent = nullptr, const QString& task_name = "");
~MultipleOptionsTask() override = default; ~MultipleOptionsTask() override = default;
private slots: private slots:
void startNext() override; void executeNextSubTask() override;
void updateState() override; void updateState() override;
}; };

View File

@ -36,18 +36,15 @@
#include "SequentialTask.h" #include "SequentialTask.h"
#include <QDebug> #include <QDebug>
#include "tasks/ConcurrentTask.h"
SequentialTask::SequentialTask(QObject* parent, QString task_name) : ConcurrentTask(parent, task_name, 1) {} 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(msg);
emitFailed(tr("One of the tasks failed!")); qWarning() << msg;
qWarning() << m_failed.constBegin()->get()->failReason(); ConcurrentTask::subTaskFailed(task, msg);
return;
}
ConcurrentTask::startNext();
} }
void SequentialTask::updateState() void SequentialTask::updateState()

View File

@ -50,7 +50,9 @@ class SequentialTask : public ConcurrentTask {
explicit SequentialTask(QObject* parent = nullptr, QString task_name = ""); explicit SequentialTask(QObject* parent = nullptr, QString task_name = "");
~SequentialTask() override = default; ~SequentialTask() override = default;
protected slots:
virtual void subTaskFailed(Task::Ptr, const QString& msg) override;
protected: protected:
void startNext() override;
void updateState() 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->newsToolBar->toggleViewAction());
ui->instanceToolBar->addContextMenuAction(ui->instanceToolBar->toggleViewAction()); ui->instanceToolBar->addContextMenuAction(ui->instanceToolBar->toggleViewAction());
ui->instanceToolBar->addContextMenuAction(ui->actionToggleStatusBar);
ui->instanceToolBar->addContextMenuAction(ui->actionLockToolbars); ui->instanceToolBar->addContextMenuAction(ui->actionLockToolbars);
} }
@ -319,6 +320,14 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
setCatBackground(cat_enable); 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 // Lock toolbars
{ {
bool toolbarsLocked = APPLICATION->settings()->get("ToolbarsLocked").toBool(); bool toolbarsLocked = APPLICATION->settings()->get("ToolbarsLocked").toBool();
@ -451,10 +460,16 @@ QMenu* MainWindow::createPopupMenu()
QMenu* filteredMenu = QMainWindow::createPopupMenu(); QMenu* filteredMenu = QMainWindow::createPopupMenu();
filteredMenu->removeAction(ui->mainToolBar->toggleViewAction()); filteredMenu->removeAction(ui->mainToolBar->toggleViewAction());
filteredMenu->addAction(ui->actionToggleStatusBar);
filteredMenu->addAction(ui->actionLockToolbars); filteredMenu->addAction(ui->actionLockToolbars);
return filteredMenu; return filteredMenu;
} }
void MainWindow::setStatusBarVisibility(bool state)
{
statusBar()->setVisible(state);
APPLICATION->settings()->set("StatusBarVisible", state);
}
void MainWindow::lockToolbars(bool state) void MainWindow::lockToolbars(bool state)
{ {
ui->mainToolBar->setMovable(!state); ui->mainToolBar->setMovable(!state);
@ -1182,33 +1197,43 @@ void MainWindow::undoTrashInstance()
void MainWindow::on_actionViewLauncherRootFolder_triggered() void MainWindow::on_actionViewLauncherRootFolder_triggered()
{ {
DesktopServices::openDirectory("."); DesktopServices::openPath(".");
} }
void MainWindow::on_actionViewInstanceFolder_triggered() void MainWindow::on_actionViewInstanceFolder_triggered()
{ {
QString str = APPLICATION->settings()->get("InstanceDir").toString(); QString str = APPLICATION->settings()->get("InstanceDir").toString();
DesktopServices::openDirectory(str); DesktopServices::openPath(str);
} }
void MainWindow::on_actionViewCentralModsFolder_triggered() 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() void MainWindow::on_actionViewIconThemeFolder_triggered()
{ {
DesktopServices::openDirectory(APPLICATION->themeManager()->getIconThemesFolder().path()); DesktopServices::openPath(APPLICATION->themeManager()->getIconThemesFolder().path(), true);
} }
void MainWindow::on_actionViewWidgetThemeFolder_triggered() void MainWindow::on_actionViewWidgetThemeFolder_triggered()
{ {
DesktopServices::openDirectory(APPLICATION->themeManager()->getApplicationThemesFolder().path()); DesktopServices::openPath(APPLICATION->themeManager()->getApplicationThemesFolder().path(), true);
} }
void MainWindow::on_actionViewCatPackFolder_triggered() 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() void MainWindow::refreshInstances()
@ -1427,7 +1452,7 @@ void MainWindow::on_actionViewSelectedInstFolder_triggered()
{ {
if (m_selectedInstance) { if (m_selectedInstance) {
QString str = m_selectedInstance->instanceRoot(); 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_actionViewIconThemeFolder_triggered();
void on_actionViewWidgetThemeFolder_triggered(); void on_actionViewWidgetThemeFolder_triggered();
void on_actionViewCatPackFolder_triggered(); void on_actionViewCatPackFolder_triggered();
void on_actionViewIconsFolder_triggered();
void on_actionViewLogsFolder_triggered();
void on_actionViewSelectedInstFolder_triggered(); void on_actionViewSelectedInstFolder_triggered();
@ -203,6 +205,8 @@ class MainWindow : public QMainWindow {
void globalSettingsClosed(); void globalSettingsClosed();
void setStatusBarVisibility(bool);
void lockToolbars(bool); void lockToolbars(bool);
#ifndef Q_OS_MAC #ifndef Q_OS_MAC

View File

@ -176,6 +176,7 @@
<addaction name="actionChangeTheme"/> <addaction name="actionChangeTheme"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="actionCAT"/> <addaction name="actionCAT"/>
<addaction name="actionToggleStatusBar"/>
<addaction name="actionLockToolbars"/> <addaction name="actionLockToolbars"/>
<addaction name="separator"/> <addaction name="separator"/>
</widget> </widget>
@ -194,6 +195,9 @@
<addaction name="actionViewIconThemeFolder"/> <addaction name="actionViewIconThemeFolder"/>
<addaction name="actionViewWidgetThemeFolder"/> <addaction name="actionViewWidgetThemeFolder"/>
<addaction name="actionViewCatPackFolder"/> <addaction name="actionViewCatPackFolder"/>
<addaction name="actionViewIconsFolder"/>
<addaction name="separator"/>
<addaction name="actionViewLogsFolder"/>
</widget> </widget>
<widget class="QMenu" name="accountsMenu"> <widget class="QMenu" name="accountsMenu">
<property name="title"> <property name="title">
@ -254,6 +258,14 @@
<string>It's a fluffy kitty :3</string> <string>It's a fluffy kitty :3</string>
</property> </property>
</action> </action>
<action name="actionToggleStatusBar">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Status Bar</string>
</property>
</action>
<action name="actionLockToolbars"> <action name="actionLockToolbars">
<property name="checkable"> <property name="checkable">
<bool>true</bool> <bool>true</bool>
@ -545,10 +557,10 @@
<normaloff>.</normaloff>.</iconset> <normaloff>.</normaloff>.</iconset>
</property> </property>
<property name="text"> <property name="text">
<string>View &amp;Instance Folder</string> <string>&amp;Instances</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Open the instance folder in a file browser.</string> <string>Open the instances folder in a file browser.</string>
</property> </property>
</action> </action>
<action name="actionViewLauncherRootFolder"> <action name="actionViewLauncherRootFolder">
@ -557,7 +569,7 @@
<normaloff>.</normaloff>.</iconset> <normaloff>.</normaloff>.</iconset>
</property> </property>
<property name="text"> <property name="text">
<string>View Launcher &amp;Root Folder</string> <string>Launcher &amp;Root</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Open the launcher's root folder in a file browser.</string> <string>Open the launcher's root folder in a file browser.</string>
@ -569,12 +581,36 @@
<normaloff>.</normaloff>.</iconset> <normaloff>.</normaloff>.</iconset>
</property> </property>
<property name="text"> <property name="text">
<string>View &amp;Central Mods Folder</string> <string>&amp;Central Mods</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Open the central mods folder in a file browser.</string> <string>Open the central mods folder in a file browser.</string>
</property> </property>
</action> </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"> <action name="actionChangeTheme">
<property name="text"> <property name="text">
<string>Themes</string> <string>Themes</string>
@ -718,10 +754,10 @@
<normaloff>.</normaloff>.</iconset> <normaloff>.</normaloff>.</iconset>
</property> </property>
<property name="text"> <property name="text">
<string>View &amp;Widget Themes Folder</string> <string>&amp;Widget Themes</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>View Widget Theme Folder</string> <string>Open the widget themes folder in a file browser.</string>
</property> </property>
</action> </action>
<action name="actionViewIconThemeFolder"> <action name="actionViewIconThemeFolder">
@ -730,18 +766,22 @@
<normaloff>.</normaloff>.</iconset> <normaloff>.</normaloff>.</iconset>
</property> </property>
<property name="text"> <property name="text">
<string>View I&amp;con Theme Folder</string> <string>I&amp;con Theme</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>View Icon Theme Folder</string> <string>Open the icon theme folder in a file browser.</string>
</property> </property>
</action> </action>
<action name="actionViewCatPackFolder"> <action name="actionViewCatPackFolder">
<property name="icon"> <property name="icon">
<iconset theme="viewfolder"/> <iconset theme="viewfolder">
<normaloff>.</normaloff>.</iconset>
</property> </property>
<property name="text"> <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> </property>
</action> </action>
</widget> </widget>

View File

@ -101,7 +101,7 @@ QString getCreditsHtml()
stream << "<h3>" << QObject::tr("With thanks to", "About Credits") << "</h3>\n"; 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>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>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>ely %1</p>\n").arg(getGitHub("elyrodso"));
stream << QString("<p>gon sawa %1</p>\n").arg(getGitHub("gonsawa")); stream << QString("<p>gon sawa %1</p>\n").arg(getGitHub("gonsawa"));
stream << QString("<p>Pankakes</p>\n"); 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>"); QString urlText("<html><head/><body><p><a href=\"%1\">%1</a></p></body></html>");
ui->urlLabel->setText(urlText.arg(BuildConfig.LAUNCHER_GIT)); ui->urlLabel->setText(urlText.arg(BuildConfig.LAUNCHER_GIT));
QString copyText("© 2022-2023 %1"); ui->copyLabel->setText(BuildConfig.LAUNCHER_COPYRIGHT);
ui->copyLabel->setText(copyText.arg(BuildConfig.LAUNCHER_COPYRIGHT));
connect(ui->closeButton, SIGNAL(clicked()), SLOT(close())); connect(ui->closeButton, SIGNAL(clicked()), SLOT(close()));

View File

@ -15,7 +15,6 @@
#pragma once #pragma once
#include <net/NetJob.h>
#include <QDialog> #include <QDialog>
namespace Ui { namespace Ui {
@ -31,7 +30,4 @@ class AboutDialog : public QDialog {
private: private:
Ui::AboutDialog* ui; Ui::AboutDialog* ui;
NetJob::Ptr netJob;
QByteArray dataSink;
}; };

View File

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

View File

@ -214,10 +214,11 @@ void ExportToModListDialog::addExtra(ExportToModList::OptionalData option)
void ExportToModListDialog::enableCustom(bool enabled) void ExportToModListDialog::enableCustom(bool enabled)
{ {
ui->authorsCheckBox->setHidden(enabled); ui->authorsCheckBox->setHidden(enabled);
ui->versionCheckBox->setHidden(enabled);
ui->urlCheckBox->setHidden(enabled);
ui->authorsButton->setHidden(!enabled); ui->authorsButton->setHidden(!enabled);
ui->versionCheckBox->setHidden(enabled);
ui->versionButton->setHidden(!enabled); ui->versionButton->setHidden(!enabled);
ui->urlCheckBox->setHidden(enabled);
ui->urlButton->setHidden(!enabled); ui->urlButton->setHidden(!enabled);
} }

View File

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>650</width> <width>650</width>
<height>446</height> <height>522</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -61,18 +61,37 @@
</item> </item>
<item row="1" column="0"> <item row="1" column="0">
<widget class="QGroupBox" name="templateGroup"> <widget class="QGroupBox" name="templateGroup">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title"> <property name="title">
<string>Template</string> <string>Template</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_4"> <layout class="QVBoxLayout" name="verticalLayout_4">
<item> <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> </item>
</layout> </layout>
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="1" column="1">
<widget class="QGroupBox" name="optionsGroup"> <widget class="QGroupBox" name="optionsGroup">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title"> <property name="title">
<string>Optional Info</string> <string>Optional Info</string>
</property> </property>

Some files were not shown because too many files have changed in this diff Show More