Merge branch 'PrismLauncher:develop' into data-packs
This commit is contained in:
commit
e4ed3b4546
125 changed files with 3925 additions and 1596 deletions
|
@ -5,3 +5,9 @@ bbb3b3e6f6e3c0f95873f22e6d0a4aaf350f49d9
|
||||||
|
|
||||||
# (nix) alejandra -> nixfmt
|
# (nix) alejandra -> nixfmt
|
||||||
4c81d8c53d09196426568c4a31a4e752ed05397a
|
4c81d8c53d09196426568c4a31a4e752ed05397a
|
||||||
|
|
||||||
|
# reformat codebase
|
||||||
|
1d468ac35ad88d8c77cc83f25e3704d9bd7df01b
|
||||||
|
|
||||||
|
# format a part of codebase
|
||||||
|
5c8481a118c8fefbfe901001d7828eaf6866eac4
|
||||||
|
|
124
.github/actions/package/linux/action.yml
vendored
Normal file
124
.github/actions/package/linux/action.yml
vendored
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
name: Package for Linux
|
||||||
|
description: Create Linux packages for Prism Launcher
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: Launcher version
|
||||||
|
required: true
|
||||||
|
build-type:
|
||||||
|
description: Type for the build
|
||||||
|
required: true
|
||||||
|
default: Debug
|
||||||
|
artifact-name:
|
||||||
|
description: Name of the uploaded artifact
|
||||||
|
required: true
|
||||||
|
default: Linux
|
||||||
|
cmake-preset:
|
||||||
|
description: Base CMake preset previously used for the build
|
||||||
|
required: true
|
||||||
|
default: linux
|
||||||
|
qt-version:
|
||||||
|
description: Version of Qt to use
|
||||||
|
required: true
|
||||||
|
gpg-private-key:
|
||||||
|
description: Private key for AppImage signing
|
||||||
|
required: false
|
||||||
|
gpg-private-key-id:
|
||||||
|
description: ID for the gpg-private-key, to select the signing key
|
||||||
|
required: false
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Package AppImage
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
VERSION: ${{ inputs.version }}
|
||||||
|
BUILD_DIR: build
|
||||||
|
INSTALL_APPIMAGE_DIR: install-appdir
|
||||||
|
|
||||||
|
GPG_PRIVATE_KEY: ${{ inputs.gpg-private-key }}
|
||||||
|
run: |
|
||||||
|
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_APPIMAGE_DIR }}/usr
|
||||||
|
|
||||||
|
mv ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/metainfo/org.prismlauncher.PrismLauncher.metainfo.xml ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/metainfo/org.prismlauncher.PrismLauncher.appdata.xml
|
||||||
|
export "NO_APPSTREAM=1" # we have to skip appstream checking because appstream on ubuntu 20.04 is outdated
|
||||||
|
|
||||||
|
export OUTPUT="PrismLauncher-Linux-x86_64.AppImage"
|
||||||
|
|
||||||
|
chmod +x linuxdeploy-*.AppImage
|
||||||
|
|
||||||
|
mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib
|
||||||
|
mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines
|
||||||
|
|
||||||
|
cp -r ${{ runner.workspace }}/Qt/${{ inputs.qt-version }}/gcc_64/plugins/iconengines/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines
|
||||||
|
|
||||||
|
cp /usr/lib/x86_64-linux-gnu/libcrypto.so.* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
|
||||||
|
cp /usr/lib/x86_64-linux-gnu/libssl.so.* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
|
||||||
|
cp /usr/lib/x86_64-linux-gnu/libOpenGL.so.0* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
|
||||||
|
|
||||||
|
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib"
|
||||||
|
export LD_LIBRARY_PATH
|
||||||
|
|
||||||
|
chmod +x AppImageUpdate-x86_64.AppImage
|
||||||
|
cp AppImageUpdate-x86_64.AppImage ${{ env.INSTALL_APPIMAGE_DIR }}/usr/bin
|
||||||
|
|
||||||
|
export UPDATE_INFORMATION="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|PrismLauncher-Linux-x86_64.AppImage.zsync"
|
||||||
|
|
||||||
|
if [ '${{ inputs.gpg-private-key-id }}' != '' ]; then
|
||||||
|
export SIGN=1
|
||||||
|
export SIGN_KEY=${{ inputs.gpg-private-key-id }}
|
||||||
|
mkdir -p ~/.gnupg/
|
||||||
|
echo "$GPG_PRIVATE_KEY" > ~/.gnupg/private.key
|
||||||
|
gpg --import ~/.gnupg/private.key
|
||||||
|
else
|
||||||
|
echo ":warning: Skipped code signing for Linux AppImage, as gpg key was not present." >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
./linuxdeploy-x86_64.AppImage --appdir ${{ env.INSTALL_APPIMAGE_DIR }} --output appimage --plugin qt -i ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/icons/hicolor/scalable/apps/org.prismlauncher.PrismLauncher.svg
|
||||||
|
|
||||||
|
mv "PrismLauncher-Linux-x86_64.AppImage" "PrismLauncher-Linux-${{ env.VERSION }}-${{ inputs.build-type }}-x86_64.AppImage"
|
||||||
|
|
||||||
|
- name: Package portable tarball
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
BUILD_DIR: build
|
||||||
|
|
||||||
|
CMAKE_PRESET: ${{ inputs.cmake-preset }}
|
||||||
|
|
||||||
|
INSTALL_PORTABLE_DIR: install-portable
|
||||||
|
run: |
|
||||||
|
cmake --preset "$CMAKE_PRESET" -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_PORTABLE_DIR }} -DINSTALL_BUNDLE=full
|
||||||
|
cmake --install ${{ env.BUILD_DIR }}
|
||||||
|
cmake --install ${{ env.BUILD_DIR }} --component portable
|
||||||
|
|
||||||
|
mkdir ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||||
|
cp /lib/x86_64-linux-gnu/libbz2.so.1.0 ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||||
|
cp /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0 ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||||
|
cp /usr/lib/x86_64-linux-gnu/libcrypto.so.* ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||||
|
cp /usr/lib/x86_64-linux-gnu/libssl.so.* ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||||
|
cp /usr/lib/x86_64-linux-gnu/libffi.so.*.* ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||||
|
mv ${{ env.INSTALL_PORTABLE_DIR }}/bin/*.so* ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||||
|
|
||||||
|
for l in $(find ${{ env.INSTALL_PORTABLE_DIR }} -type f); do l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_PORTABLE_DIR }}/}; l=${l#./}; echo $l; done > ${{ env.INSTALL_PORTABLE_DIR }}/manifest.txt
|
||||||
|
cd ${{ env.INSTALL_PORTABLE_DIR }}
|
||||||
|
tar -czf ../PrismLauncher-portable.tar.gz *
|
||||||
|
|
||||||
|
- name: Upload binary tarball
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: PrismLauncher-${{ inputs.artifact-name }}-Qt6-Portable-${{ inputs.version }}-${{ inputs.build-type }}
|
||||||
|
path: PrismLauncher-portable.tar.gz
|
||||||
|
|
||||||
|
- name: Upload AppImage
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: PrismLauncher-${{ inputs.artifact-name }}-${{ inputs.version }}-${{ inputs.build-type }}-x86_64.AppImage
|
||||||
|
path: PrismLauncher-${{ runner.os }}-${{ inputs.version }}-${{ inputs.build-type }}-x86_64.AppImage
|
||||||
|
|
||||||
|
- name: Upload AppImage Zsync
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: PrismLauncher-${{ inputs.artifact-name }}-${{ inputs.version }}-${{ inputs.build-type }}-x86_64.AppImage.zsync
|
||||||
|
path: PrismLauncher-Linux-x86_64.AppImage.zsync
|
121
.github/actions/package/macos/action.yml
vendored
Normal file
121
.github/actions/package/macos/action.yml
vendored
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
name: Package for macOS
|
||||||
|
description: Create a macOS package for Prism Launcher
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: Launcher version
|
||||||
|
required: true
|
||||||
|
build-type:
|
||||||
|
description: Type for the build
|
||||||
|
required: true
|
||||||
|
default: Debug
|
||||||
|
artifact-name:
|
||||||
|
description: Name of the uploaded artifact
|
||||||
|
required: true
|
||||||
|
default: macOS
|
||||||
|
apple-codesign-cert:
|
||||||
|
description: Certificate for signing macOS builds
|
||||||
|
required: false
|
||||||
|
apple-codesign-password:
|
||||||
|
description: Password for signing macOS builds
|
||||||
|
required: false
|
||||||
|
apple-codesign-id:
|
||||||
|
description: Certificate ID for signing macOS builds
|
||||||
|
required: false
|
||||||
|
apple-notarize-apple-id:
|
||||||
|
description: Apple ID used for notarizing macOS builds
|
||||||
|
required: false
|
||||||
|
apple-notarize-team-id:
|
||||||
|
description: Team ID used for notarizing macOS builds
|
||||||
|
required: false
|
||||||
|
apple-notarize-password:
|
||||||
|
description: Password used for notarizing macOS builds
|
||||||
|
required: false
|
||||||
|
sparkle-ed25519-key:
|
||||||
|
description: Private key for signing Sparkle updates
|
||||||
|
required: false
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Fetch codesign certificate
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo '${{ inputs.apple-codesign-cert }}' | base64 --decode > codesign.p12
|
||||||
|
if [ -n '${{ inputs.apple-codesign-id }}' ]; then
|
||||||
|
security create-keychain -p '${{ inputs.apple-codesign-password }}' build.keychain
|
||||||
|
security default-keychain -s build.keychain
|
||||||
|
security unlock-keychain -p '${{ inputs.apple-codesign-password }}' build.keychain
|
||||||
|
security import codesign.p12 -k build.keychain -P '${{ inputs.apple-codesign-password }}' -T /usr/bin/codesign
|
||||||
|
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k '${{ inputs.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
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
BUILD_DIR: build
|
||||||
|
INSTALL_DIR: install
|
||||||
|
run: |
|
||||||
|
cmake --install ${{ env.BUILD_DIR }}
|
||||||
|
|
||||||
|
cd ${{ env.INSTALL_DIR }}
|
||||||
|
chmod +x "PrismLauncher.app/Contents/MacOS/prismlauncher"
|
||||||
|
|
||||||
|
if [ -n '${{ inputs.apple-codesign-id }}' ]; then
|
||||||
|
APPLE_CODESIGN_ID='${{ inputs.apple-codesign-id }}'
|
||||||
|
ENTITLEMENTS_FILE='../program_info/App.entitlements'
|
||||||
|
else
|
||||||
|
APPLE_CODESIGN_ID='-'
|
||||||
|
ENTITLEMENTS_FILE='../program_info/AdhocSignedApp.entitlements'
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo codesign --sign "$APPLE_CODESIGN_ID" --deep --force --entitlements "$ENTITLEMENTS_FILE" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher"
|
||||||
|
mv "PrismLauncher.app" "Prism Launcher.app"
|
||||||
|
|
||||||
|
- name: Notarize
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
INSTALL_DIR: install
|
||||||
|
run: |
|
||||||
|
cd ${{ env.INSTALL_DIR }}
|
||||||
|
|
||||||
|
if [ -n '${{ inputs.apple-notarize-password }}' ]; then
|
||||||
|
ditto -c -k --sequesterRsrc --keepParent "Prism Launcher.app" ../PrismLauncher.zip
|
||||||
|
xcrun notarytool submit ../PrismLauncher.zip \
|
||||||
|
--wait --progress \
|
||||||
|
--apple-id '${{ inputs.apple-notarize-apple-id }}' \
|
||||||
|
--team-id '${{ inputs.apple-notarize-team-id }}' \
|
||||||
|
--password '${{ inputs.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
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
if [ '${{ inputs.sparkle-ed25519-key }}' != '' ]; then
|
||||||
|
echo '${{ inputs.sparkle-ed25519-key }}' > ed25519-priv.pem
|
||||||
|
signature=$(/opt/homebrew/opt/openssl@3/bin/openssl pkeyutl -sign -rawin -in ${{ github.workspace }}/PrismLauncher.zip -inkey ed25519-priv.pem | openssl base64 | tr -d \\n)
|
||||||
|
rm ed25519-priv.pem
|
||||||
|
cat >> $GITHUB_STEP_SUMMARY << EOF
|
||||||
|
### Artifact Information :information_source:
|
||||||
|
- :memo: Sparkle Signature (ed25519): \`$signature\`
|
||||||
|
EOF
|
||||||
|
else
|
||||||
|
cat >> $GITHUB_STEP_SUMMARY << EOF
|
||||||
|
### Artifact Information :information_source:
|
||||||
|
- :warning: Sparkle Signature (ed25519): No private key available (likely a pull request or fork)
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Upload binary tarball
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: PrismLauncher-${{ inputs.artifact-name }}-${{ inputs.version }}-${{ inputs.build-type }}
|
||||||
|
path: PrismLauncher.zip
|
143
.github/actions/package/windows/action.yml
vendored
Normal file
143
.github/actions/package/windows/action.yml
vendored
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
name: Package for Windows
|
||||||
|
description: Create a Windows package for Prism Launcher
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: Launcher version
|
||||||
|
required: true
|
||||||
|
build-type:
|
||||||
|
description: Type for the build
|
||||||
|
required: true
|
||||||
|
default: Debug
|
||||||
|
artifact-name:
|
||||||
|
description: Name of the uploaded artifact
|
||||||
|
required: true
|
||||||
|
msystem:
|
||||||
|
description: MSYS2 subsystem to use
|
||||||
|
required: true
|
||||||
|
default: false
|
||||||
|
windows-codesign-cert:
|
||||||
|
description: Certificate for signing Windows builds
|
||||||
|
required: false
|
||||||
|
windows-codesign-password:
|
||||||
|
description: Password for signing Windows builds
|
||||||
|
required: false
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Package (MinGW)
|
||||||
|
if: ${{ inputs.msystem != '' }}
|
||||||
|
shell: msys2 {0}
|
||||||
|
env:
|
||||||
|
BUILD_DIR: build
|
||||||
|
INSTALL_DIR: install
|
||||||
|
run: |
|
||||||
|
cmake --install ${{ env.BUILD_DIR }}
|
||||||
|
touch ${{ env.INSTALL_DIR }}/manifest.txt
|
||||||
|
for l in $(find ${{ env.INSTALL_DIR }} -type f); do l=$(cygpath -u $l); l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_DIR }}/}; l=${l#./}; echo $l; done >> ${{ env.INSTALL_DIR }}/manifest.txt
|
||||||
|
|
||||||
|
- name: Package (MSVC)
|
||||||
|
if: ${{ inputs.msystem == '' }}
|
||||||
|
shell: pwsh
|
||||||
|
env:
|
||||||
|
BUILD_DIR: build
|
||||||
|
INSTALL_DIR: install
|
||||||
|
run: |
|
||||||
|
cmake --install ${{ env.BUILD_DIR }} --config ${{ inputs.build-type }}
|
||||||
|
|
||||||
|
cd ${{ github.workspace }}
|
||||||
|
|
||||||
|
Get-ChildItem ${{ env.INSTALL_DIR }} -Recurse | ForEach FullName | Resolve-Path -Relative | %{ $_.TrimStart('.\') } | %{ $_.TrimStart('${{ env.INSTALL_DIR }}') } | %{ $_.TrimStart('\') } | Out-File -FilePath ${{ env.INSTALL_DIR }}/manifest.txt
|
||||||
|
|
||||||
|
- name: Fetch codesign certificate
|
||||||
|
shell: bash # yes, we are not using MSYS2 or PowerShell here
|
||||||
|
run: |
|
||||||
|
echo '${{ inputs.windows-codesign-cert }}' | base64 --decode > codesign.pfx
|
||||||
|
|
||||||
|
- name: Sign executable
|
||||||
|
shell: pwsh
|
||||||
|
env:
|
||||||
|
INSTALL_DIR: install
|
||||||
|
run: |
|
||||||
|
if (Get-Content ./codesign.pfx){
|
||||||
|
cd ${{ env.INSTALL_DIR }}
|
||||||
|
# We ship the exact same executable for portable and non-portable editions, so signing just once is fine
|
||||||
|
SignTool sign /fd sha256 /td sha256 /f ../codesign.pfx /p '${{ inputs.windows-codesign-password }}' /tr http://timestamp.digicert.com prismlauncher.exe prismlauncher_updater.exe prismlauncher_filelink.exe
|
||||||
|
} else {
|
||||||
|
":warning: Skipped code signing for Windows, as certificate was not present." >> $env:GITHUB_STEP_SUMMARY
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Package (MinGW, portable)
|
||||||
|
if: ${{ inputs.msystem != '' }}
|
||||||
|
shell: msys2 {0}
|
||||||
|
env:
|
||||||
|
BUILD_DIR: build
|
||||||
|
INSTALL_DIR: install
|
||||||
|
INSTALL_PORTABLE_DIR: install-portable
|
||||||
|
run: |
|
||||||
|
cp -r ${{ env.INSTALL_DIR }} ${{ env.INSTALL_PORTABLE_DIR }} # cmake install on Windows is slow, let's just copy instead
|
||||||
|
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable
|
||||||
|
for l in $(find ${{ env.INSTALL_PORTABLE_DIR }} -type f); do l=$(cygpath -u $l); l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_PORTABLE_DIR }}/}; l=${l#./}; echo $l; done >> ${{ env.INSTALL_PORTABLE_DIR }}/manifest.txt
|
||||||
|
|
||||||
|
- name: Package (MSVC, portable)
|
||||||
|
if: ${{ inputs.msystem == '' }}
|
||||||
|
shell: pwsh
|
||||||
|
env:
|
||||||
|
BUILD_DIR: build
|
||||||
|
INSTALL_DIR: install
|
||||||
|
INSTALL_PORTABLE_DIR: install-portable
|
||||||
|
run: |
|
||||||
|
cp -r ${{ env.INSTALL_DIR }} ${{ env.INSTALL_PORTABLE_DIR }} # cmake install on Windows is slow, let's just copy instead
|
||||||
|
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable
|
||||||
|
|
||||||
|
Get-ChildItem ${{ env.INSTALL_PORTABLE_DIR }} -Recurse | ForEach FullName | Resolve-Path -Relative | %{ $_.TrimStart('.\') } | %{ $_.TrimStart('${{ env.INSTALL_PORTABLE_DIR }}') } | %{ $_.TrimStart('\') } | Out-File -FilePath ${{ env.INSTALL_DIR }}/manifest.txt
|
||||||
|
|
||||||
|
- name: Package (installer)
|
||||||
|
shell: pwsh
|
||||||
|
env:
|
||||||
|
BUILD_DIR: build
|
||||||
|
INSTALL_DIR: install
|
||||||
|
|
||||||
|
NSCURL_VERSION: "v24.9.26.122"
|
||||||
|
NSCURL_SHA256: "AEE6C4BE3CB6455858E9C1EE4B3AFE0DB9960FA03FE99CCDEDC28390D57CCBB0"
|
||||||
|
run: |
|
||||||
|
New-Item -Name NSISPlugins -ItemType Directory
|
||||||
|
Invoke-Webrequest https://github.com/negrutiu/nsis-nscurl/releases/download/"${{ env.NSCURL_VERSION }}"/NScurl.zip -OutFile NSISPlugins\NScurl.zip
|
||||||
|
$nscurl_hash = Get-FileHash NSISPlugins\NScurl.zip -Algorithm Sha256 | Select-Object -ExpandProperty Hash
|
||||||
|
if ( $nscurl_hash -ne "${{ env.nscurl_sha256 }}") {
|
||||||
|
echo "::error:: NSCurl.zip sha256 mismatch"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
Expand-Archive -Path NSISPlugins\NScurl.zip -DestinationPath NSISPlugins\NScurl
|
||||||
|
|
||||||
|
cd ${{ env.INSTALL_DIR }}
|
||||||
|
makensis -NOCD "${{ github.workspace }}/${{ env.BUILD_DIR }}/program_info/win_install.nsi"
|
||||||
|
|
||||||
|
- name: Sign installer
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
if (Get-Content ./codesign.pfx){
|
||||||
|
SignTool sign /fd sha256 /td sha256 /f codesign.pfx /p '${{ inputs.windows-codesign-password }}' /tr http://timestamp.digicert.com PrismLauncher-Setup.exe
|
||||||
|
} else {
|
||||||
|
":warning: Skipped code signing for Windows, as certificate was not present." >> $env:GITHUB_STEP_SUMMARY
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Upload binary zip
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: PrismLauncher-${{ inputs.artifact-name }}-${{ inputs.version }}-${{ inputs.build-type }}
|
||||||
|
path: install/**
|
||||||
|
|
||||||
|
- name: Upload portable zip
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: PrismLauncher-${{ inputs.artifact-name }}-Portable-${{ inputs.version }}-${{ inputs.build-type }}
|
||||||
|
path: install-portable/**
|
||||||
|
|
||||||
|
- name: Upload installer
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: PrismLauncher-${{ inputs.artifact-name }}-Setup-${{ inputs.version }}-${{ inputs.build-type }}
|
||||||
|
path: PrismLauncher-Setup.exe
|
78
.github/actions/setup-dependencies/action.yml
vendored
Normal file
78
.github/actions/setup-dependencies/action.yml
vendored
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
name: Setup Dependencies
|
||||||
|
description: Install and setup dependencies for building Prism Launcher
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
build-type:
|
||||||
|
description: Type for the build
|
||||||
|
required: true
|
||||||
|
default: Debug
|
||||||
|
msystem:
|
||||||
|
description: MSYS2 subsystem to use
|
||||||
|
required: false
|
||||||
|
vcvars-arch:
|
||||||
|
description: Visual Studio architecture to use
|
||||||
|
required: false
|
||||||
|
qt-architecture:
|
||||||
|
description: Qt architecture
|
||||||
|
required: false
|
||||||
|
qt-version:
|
||||||
|
description: Version of Qt to use
|
||||||
|
required: true
|
||||||
|
default: 6.8.1
|
||||||
|
|
||||||
|
outputs:
|
||||||
|
build-type:
|
||||||
|
description: Type of build used
|
||||||
|
value: ${{ inputs.build-type }}
|
||||||
|
qt-version:
|
||||||
|
description: Version of Qt used
|
||||||
|
value: ${{ inputs.qt-version }}
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Setup Linux dependencies
|
||||||
|
if: ${{ runner.os == 'Linux' }}
|
||||||
|
uses: ./.github/actions/setup-dependencies/linux
|
||||||
|
|
||||||
|
- name: Setup macOS dependencies
|
||||||
|
if: ${{ runner.os == 'macOS' }}
|
||||||
|
uses: ./.github/actions/setup-dependencies/macos
|
||||||
|
|
||||||
|
- name: Setup Windows dependencies
|
||||||
|
if: ${{ runner.os == 'Windows' }}
|
||||||
|
uses: ./.github/actions/setup-dependencies/windows
|
||||||
|
with:
|
||||||
|
build-type: ${{ inputs.build-type }}
|
||||||
|
msystem: ${{ inputs.msystem }}
|
||||||
|
vcvars-arch: ${{ inputs.vcvars-arch }}
|
||||||
|
|
||||||
|
# TODO(@getchoo): Get this working on MSYS2!
|
||||||
|
- name: Setup ccache
|
||||||
|
if: ${{ (runner.os != 'Windows' || inputs.msystem == '') && inputs.build-type == 'Debug' }}
|
||||||
|
uses: hendrikmuhs/ccache-action@v1.2.18
|
||||||
|
with:
|
||||||
|
variant: ${{ runner.os == 'Windows' && 'sccache' || 'ccache' }}
|
||||||
|
create-symlink: ${{ runner.os != 'Windows' }}
|
||||||
|
key: ${{ runner.os }}-qt${{ inputs.qt_ver }}-${{ inputs.architecture }}
|
||||||
|
|
||||||
|
- name: Use ccache on debug builds
|
||||||
|
if: ${{ inputs.build-type == 'Debug' }}
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
# Only use sccache on MSVC
|
||||||
|
CCACHE_VARIANT: ${{ (runner.os == 'Windows' && inputs.msystem == '') && 'sccache' || 'ccache' }}
|
||||||
|
run: |
|
||||||
|
echo "CMAKE_C_COMPILER_LAUNCHER=$CCACHE_VARIANT" >> "$GITHUB_ENV"
|
||||||
|
echo "CMAKE_CXX_COMPILER_LAUNCHER=$CCACHE_VARIANT" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Install Qt
|
||||||
|
if: ${{ inputs.msystem == '' }}
|
||||||
|
uses: jurplel/install-qt-action@v4
|
||||||
|
with:
|
||||||
|
aqtversion: "==3.1.*"
|
||||||
|
version: ${{ inputs.qt-version }}
|
||||||
|
arch: ${{ inputs.qt-architecture }}
|
||||||
|
modules: qt5compat qtimageformats qtnetworkauth
|
||||||
|
cache: ${{ inputs.build-type == 'Debug' }}
|
26
.github/actions/setup-dependencies/linux/action.yml
vendored
Normal file
26
.github/actions/setup-dependencies/linux/action.yml
vendored
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
name: Setup Linux dependencies
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Install host dependencies
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
sudo apt-get -y update
|
||||||
|
sudo apt-get -y install ninja-build extra-cmake-modules scdoc appstream libxcb-cursor-dev
|
||||||
|
|
||||||
|
- name: Setup AppImage tooling
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
declare -A appimage_deps
|
||||||
|
appimage_deps["https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20250213-2/linuxdeploy-x86_64.AppImage"]="4648f278ab3ef31f819e67c30d50f462640e5365a77637d7e6f2ad9fd0b4522a linuxdeploy-x86_64.AppImage"
|
||||||
|
appimage_deps["https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/1-alpha-20250213-1/linuxdeploy-plugin-qt-x86_64.AppImage"]="15106be885c1c48a021198e7e1e9a48ce9d02a86dd0a1848f00bdbf3c1c92724 linuxdeploy-plugin-qt-x86_64.AppImage"
|
||||||
|
appimage_deps["https://github.com/AppImageCommunity/AppImageUpdate/releases/download/2.0.0-alpha-1-20241225/AppImageUpdate-x86_64.AppImage"]="f1747cf60058e99f1bb9099ee9787d16c10241313b7acec81810ea1b1e568c11 AppImageUpdate-x86_64.AppImage"
|
||||||
|
|
||||||
|
for url in "${!appimage_deps[@]}"; do
|
||||||
|
curl -LO "$url"
|
||||||
|
sha256sum -c - <<< "${appimage_deps[$url]}"
|
||||||
|
done
|
||||||
|
|
||||||
|
sudo apt -y install libopengl0
|
16
.github/actions/setup-dependencies/macos/action.yml
vendored
Normal file
16
.github/actions/setup-dependencies/macos/action.yml
vendored
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
name: Setup macOS dependencies
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Install dependencies
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
brew update
|
||||||
|
brew install ninja extra-cmake-modules temurin@17
|
||||||
|
|
||||||
|
- name: Set JAVA_HOME
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "JAVA_HOME=$(/usr/libexec/java_home -v 17)" >> "$GITHUB_ENV"
|
73
.github/actions/setup-dependencies/windows/action.yml
vendored
Normal file
73
.github/actions/setup-dependencies/windows/action.yml
vendored
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
name: Setup Windows Dependencies
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
build-type:
|
||||||
|
description: Type for the build
|
||||||
|
required: true
|
||||||
|
default: Debug
|
||||||
|
msystem:
|
||||||
|
description: MSYS2 subsystem to use
|
||||||
|
required: false
|
||||||
|
vcvars-arch:
|
||||||
|
description: Visual Studio architecture to use
|
||||||
|
required: true
|
||||||
|
default: amd64
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
|
||||||
|
steps:
|
||||||
|
# NOTE: Installed on MinGW as well for SignTool
|
||||||
|
- name: Enter VS Developer shell
|
||||||
|
if: ${{ runner.os == 'Windows' }}
|
||||||
|
uses: ilammy/msvc-dev-cmd@v1
|
||||||
|
with:
|
||||||
|
arch: ${{ inputs.vcvars-arch }}
|
||||||
|
vsversion: 2022
|
||||||
|
|
||||||
|
- name: Setup MSYS2 (MinGW)
|
||||||
|
if: ${{ inputs.msystem != '' }}
|
||||||
|
uses: msys2/setup-msys2@v2
|
||||||
|
with:
|
||||||
|
msystem: ${{ inputs.msystem }}
|
||||||
|
update: true
|
||||||
|
install: >-
|
||||||
|
git
|
||||||
|
pacboy: >-
|
||||||
|
toolchain:p
|
||||||
|
ccache:p
|
||||||
|
cmake:p
|
||||||
|
extra-cmake-modules:p
|
||||||
|
ninja:p
|
||||||
|
qt6-base:p
|
||||||
|
qt6-svg:p
|
||||||
|
qt6-imageformats:p
|
||||||
|
qt6-5compat:p
|
||||||
|
qt6-networkauth:p
|
||||||
|
cmark:p
|
||||||
|
tomlplusplus:p
|
||||||
|
quazip-qt6:p
|
||||||
|
|
||||||
|
- name: List pacman packages (MinGW)
|
||||||
|
if: ${{ inputs.msystem != '' }}
|
||||||
|
shell: msys2 {0}
|
||||||
|
run: |
|
||||||
|
pacman -Qe
|
||||||
|
|
||||||
|
- name: Retrieve ccache cache (MinGW)
|
||||||
|
if: ${{ inputs.msystem != '' && inputs.build-type == 'Debug' }}
|
||||||
|
uses: actions/cache@v4.2.3
|
||||||
|
with:
|
||||||
|
path: '${{ github.workspace }}\.ccache'
|
||||||
|
key: ${{ runner.os }}-mingw-w64-ccache-${{ github.run_id }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-mingw-w64-ccache
|
||||||
|
|
||||||
|
- name: Setup ccache (MinGW)
|
||||||
|
if: ${{ inputs.msystem != '' && inputs.build-type == 'Debug' }}
|
||||||
|
shell: msys2 {0}
|
||||||
|
run: |
|
||||||
|
ccache --set-config=cache_dir='${{ github.workspace }}\.ccache'
|
||||||
|
ccache --set-config=max_size='500M'
|
||||||
|
ccache --set-config=compression=true
|
||||||
|
ccache -p # Show config
|
30
.github/workflows/blocked-prs.yml
vendored
30
.github/workflows/blocked-prs.yml
vendored
|
@ -64,7 +64,7 @@ jobs:
|
||||||
"prNumber": .number,
|
"prNumber": .number,
|
||||||
"prHeadSha": .head.sha,
|
"prHeadSha": .head.sha,
|
||||||
"prHeadLabel": .head.label,
|
"prHeadLabel": .head.label,
|
||||||
"prBody": .body,
|
"prBody": (.body // ""),
|
||||||
"prLabels": (reduce .labels[].name as $l ([]; . + [$l]))
|
"prLabels": (reduce .labels[].name as $l ([]; . + [$l]))
|
||||||
}
|
}
|
||||||
' <<< "$PR_JSON")"
|
' <<< "$PR_JSON")"
|
||||||
|
@ -125,6 +125,7 @@ jobs:
|
||||||
"type": $type,
|
"type": $type,
|
||||||
"number": .number,
|
"number": .number,
|
||||||
"merged": .merged,
|
"merged": .merged,
|
||||||
|
"state": (if .state == "open" then "Open" elif .merged then "Merged" else "Closed" end),
|
||||||
"labels": (reduce .labels[].name as $l ([]; . + [$l])),
|
"labels": (reduce .labels[].name as $l ([]; . + [$l])),
|
||||||
"basePrUrl": .html_url,
|
"basePrUrl": .html_url,
|
||||||
"baseRepoName": .head.repo.name,
|
"baseRepoName": .head.repo.name,
|
||||||
|
@ -138,11 +139,16 @@ jobs:
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
echo "data=$blocked_pr_data";
|
echo "data=$blocked_pr_data";
|
||||||
echo "all_merged=$(jq -r 'all(.[].merged; .)' <<< "$blocked_pr_data")";
|
echo "all_merged=$(jq -r 'all(.[] | (.type == "Stacked on" and .merged) or (.type == "Blocked on" and (.state != "Open")); .)' <<< "$blocked_pr_data")";
|
||||||
echo "current_blocking=$(jq -c 'map( select( .merged | not ) | .number )' <<< "$blocked_pr_data" )";
|
echo "current_blocking=$(jq -c 'map(
|
||||||
|
select(
|
||||||
|
(.type == "Stacked on" and (.merged | not)) or
|
||||||
|
(.type == "Blocked on" and (.state == "Open"))
|
||||||
|
) | .number
|
||||||
|
)' <<< "$blocked_pr_data" )";
|
||||||
} >> "$GITHUB_OUTPUT"
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Add 'blocked' Label is Missing
|
- name: Add 'blocked' Label if Missing
|
||||||
id: label_blocked
|
id: label_blocked
|
||||||
if: (fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0) && !contains(fromJSON(env.JOB_DATA).prLabels, 'blocked') && !fromJSON(steps.blocking_data.outputs.all_merged)
|
if: (fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0) && !contains(fromJSON(env.JOB_DATA).prLabels, 'blocked') && !fromJSON(steps.blocking_data.outputs.all_merged)
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
@ -184,14 +190,18 @@ jobs:
|
||||||
# create commit Status, overwrites previous identical context
|
# create commit Status, overwrites previous identical context
|
||||||
while read -r pr_data ; do
|
while read -r pr_data ; do
|
||||||
DESC=$(
|
DESC=$(
|
||||||
jq -r ' "Blocking PR #" + (.number | tostring) + " is " + (if .merged then "" else "not yet " end) + "merged"' <<< "$pr_data"
|
jq -r 'if .type == "Stacked on" then
|
||||||
|
"Stacked PR #" + (.number | tostring) + " is " + (if .merged then "" else "not yet " end) + "merged"
|
||||||
|
else
|
||||||
|
"Blocking PR #" + (.number | tostring) + " is " + (if .state == "Open" then "" else "not yet " end) + "merged or closed"
|
||||||
|
end ' <<< "$pr_data"
|
||||||
)
|
)
|
||||||
gh api \
|
gh api \
|
||||||
--method POST \
|
--method POST \
|
||||||
-H "Accept: application/vnd.github+json" \
|
-H "Accept: application/vnd.github+json" \
|
||||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||||
"/repos/${OWNER}/${REPO}/statuses/${pr_head_sha}" \
|
"/repos/${OWNER}/${REPO}/statuses/${pr_head_sha}" \
|
||||||
-f "state=$(jq -r 'if .merged then "success" else "failure" end' <<< "$pr_data")" \
|
-f "state=$(jq -r 'if (.type == "Stacked on" and .merged) or (.type == "Blocked on" and (.state != "Open")) then "success" else "failure" end' <<< "$pr_data")" \
|
||||||
-f "target_url=$(jq -r '.basePrUrl' <<< "$pr_data" )" \
|
-f "target_url=$(jq -r '.basePrUrl' <<< "$pr_data" )" \
|
||||||
-f "description=$DESC" \
|
-f "description=$DESC" \
|
||||||
-f "context=ci/blocking-pr-check:$(jq '.number' <<< "$pr_data")"
|
-f "context=ci/blocking-pr-check:$(jq '.number' <<< "$pr_data")"
|
||||||
|
@ -214,7 +224,13 @@ jobs:
|
||||||
base_repo_owner=$(jq -r '.baseRepoOwner' <<< "$pr_data")
|
base_repo_owner=$(jq -r '.baseRepoOwner' <<< "$pr_data")
|
||||||
base_repo_name=$(jq -r '.baseRepoName' <<< "$pr_data")
|
base_repo_name=$(jq -r '.baseRepoName' <<< "$pr_data")
|
||||||
compare_url="https://github.com/$base_repo_owner/$base_repo_name/compare/$base_ref_name...$pr_head_label"
|
compare_url="https://github.com/$base_repo_owner/$base_repo_name/compare/$base_ref_name...$pr_head_label"
|
||||||
status=$(jq -r 'if .merged then ":heavy_check_mark: Merged" else ":x: Not Merged" end' <<< "$pr_data")
|
status=$(jq -r '
|
||||||
|
if .type == "Stacked on" then
|
||||||
|
if .merged then ":heavy_check_mark: Merged" else ":x: Not Merged (" + .state + ")" end
|
||||||
|
else
|
||||||
|
if .state != "Open" then ":white_check_mark: " + .state else ":x: Open" end
|
||||||
|
end
|
||||||
|
' <<< "$pr_data")
|
||||||
type=$(jq -r '.type' <<< "$pr_data")
|
type=$(jq -r '.type' <<< "$pr_data")
|
||||||
echo " - $type #$base_pr $status [(compare)]($compare_url)" >> "$COMMENT_PATH"
|
echo " - $type #$base_pr $status [(compare)]($compare_url)" >> "$COMMENT_PATH"
|
||||||
done < <(jq -c '.[]' <<< "$BLOCKING_DATA")
|
done < <(jq -c '.[]' <<< "$BLOCKING_DATA")
|
||||||
|
|
700
.github/workflows/build.yml
vendored
700
.github/workflows/build.yml
vendored
|
@ -1,619 +1,199 @@
|
||||||
name: Build
|
name: Build
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
push:
|
||||||
|
branches-ignore:
|
||||||
|
- "renovate/**"
|
||||||
|
paths:
|
||||||
|
# File types
|
||||||
|
- "**.cpp"
|
||||||
|
- "**.h"
|
||||||
|
- "**.java"
|
||||||
|
|
||||||
|
# Directories
|
||||||
|
- "buildconfig/"
|
||||||
|
- "cmake/"
|
||||||
|
- "launcher/"
|
||||||
|
- "libraries/"
|
||||||
|
- "program_info/"
|
||||||
|
- "tests/"
|
||||||
|
|
||||||
|
# Files
|
||||||
|
- "CMakeLists.txt"
|
||||||
|
- "COPYING.md"
|
||||||
|
|
||||||
|
# Workflows
|
||||||
|
- ".github/workflows/build.yml"
|
||||||
|
- ".github/actions/package/"
|
||||||
|
- ".github/actions/setup-dependencies/"
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
# File types
|
||||||
|
- "**.cpp"
|
||||||
|
- "**.h"
|
||||||
|
|
||||||
|
# Directories
|
||||||
|
- "buildconfig/"
|
||||||
|
- "cmake/"
|
||||||
|
- "launcher/"
|
||||||
|
- "libraries/"
|
||||||
|
- "program_info/"
|
||||||
|
- "tests/"
|
||||||
|
|
||||||
|
# Files
|
||||||
|
- "CMakeLists.txt"
|
||||||
|
- "COPYING.md"
|
||||||
|
|
||||||
|
# Workflows
|
||||||
|
- ".github/workflows/build.yml"
|
||||||
|
- ".github/actions/package/"
|
||||||
|
- ".github/actions/setup-dependencies/"
|
||||||
workflow_call:
|
workflow_call:
|
||||||
inputs:
|
inputs:
|
||||||
build_type:
|
build-type:
|
||||||
description: Type of build (Debug, Release, RelWithDebInfo, MinSizeRel)
|
description: Type of build (Debug or Release)
|
||||||
type: string
|
type: string
|
||||||
default: Debug
|
default: Debug
|
||||||
is_qt_cached:
|
workflow_dispatch:
|
||||||
description: Enable Qt caching or not
|
inputs:
|
||||||
|
build-type:
|
||||||
|
description: Type of build (Debug or Release)
|
||||||
type: string
|
type: string
|
||||||
default: true
|
default: Debug
|
||||||
secrets:
|
|
||||||
SPARKLE_ED25519_KEY:
|
|
||||||
description: Private key for signing Sparkle updates
|
|
||||||
required: false
|
|
||||||
WINDOWS_CODESIGN_CERT:
|
|
||||||
description: Certificate for signing Windows builds
|
|
||||||
required: false
|
|
||||||
WINDOWS_CODESIGN_PASSWORD:
|
|
||||||
description: Password for signing Windows builds
|
|
||||||
required: false
|
|
||||||
APPLE_CODESIGN_CERT:
|
|
||||||
description: Certificate for signing macOS builds
|
|
||||||
required: false
|
|
||||||
APPLE_CODESIGN_PASSWORD:
|
|
||||||
description: Password for signing macOS builds
|
|
||||||
required: false
|
|
||||||
APPLE_CODESIGN_ID:
|
|
||||||
description: Certificate ID for signing macOS builds
|
|
||||||
required: false
|
|
||||||
APPLE_NOTARIZE_APPLE_ID:
|
|
||||||
description: Apple ID used for notarizing macOS builds
|
|
||||||
required: false
|
|
||||||
APPLE_NOTARIZE_TEAM_ID:
|
|
||||||
description: Team ID used for notarizing macOS builds
|
|
||||||
required: false
|
|
||||||
APPLE_NOTARIZE_PASSWORD:
|
|
||||||
description: Password used for notarizing macOS builds
|
|
||||||
required: false
|
|
||||||
GPG_PRIVATE_KEY:
|
|
||||||
description: Private key for AppImage signing
|
|
||||||
required: false
|
|
||||||
GPG_PRIVATE_KEY_ID:
|
|
||||||
description: ID for the GPG_PRIVATE_KEY, to select the signing key
|
|
||||||
required: false
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
name: Build (${{ matrix.artifact-name }})
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: ubuntu-22.04
|
- os: ubuntu-22.04
|
||||||
qt_ver: 6
|
artifact-name: Linux
|
||||||
qt_host: linux
|
base-cmake-preset: linux
|
||||||
qt_arch: ""
|
|
||||||
qt_version: "6.8.1"
|
|
||||||
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
|
||||||
linuxdeploy_hash: "4648f278ab3ef31f819e67c30d50f462640e5365a77637d7e6f2ad9fd0b4522a linuxdeploy-x86_64.AppImage"
|
|
||||||
linuxdeploy_qt_hash: "15106be885c1c48a021198e7e1e9a48ce9d02a86dd0a1848f00bdbf3c1c92724 linuxdeploy-plugin-qt-x86_64.AppImage"
|
|
||||||
appimageupdate_hash: "f1747cf60058e99f1bb9099ee9787d16c10241313b7acec81810ea1b1e568c11 AppImageUpdate-x86_64.AppImage"
|
|
||||||
|
|
||||||
- os: windows-2022
|
- os: windows-2022
|
||||||
name: "Windows-MinGW-w64"
|
artifact-name: Windows-MinGW-w64
|
||||||
msystem: clang64
|
base-cmake-preset: windows_mingw
|
||||||
vcvars_arch: "amd64_x86"
|
msystem: CLANG64
|
||||||
|
vcvars-arch: amd64_x86
|
||||||
|
|
||||||
|
- os: windows-11-arm
|
||||||
|
artifact-name: Windows-MinGW-arm64
|
||||||
|
base-cmake-preset: windows_mingw
|
||||||
|
msystem: CLANGARM64
|
||||||
|
vcvars-arch: arm64
|
||||||
|
|
||||||
- os: windows-2022
|
- os: windows-2022
|
||||||
name: "Windows-MSVC"
|
artifact-name: Windows-MSVC
|
||||||
msystem: ""
|
base-cmake-preset: windows_msvc
|
||||||
architecture: "x64"
|
# TODO(@getchoo): This is the default in setup-dependencies/windows. Why isn't it working?!?!
|
||||||
vcvars_arch: "amd64"
|
vcvars-arch: amd64
|
||||||
qt_ver: 6
|
|
||||||
qt_host: "windows"
|
|
||||||
qt_arch: "win64_msvc2022_64"
|
|
||||||
qt_version: "6.8.1"
|
|
||||||
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
|
||||||
nscurl_tag: "v24.9.26.122"
|
|
||||||
nscurl_sha256: "AEE6C4BE3CB6455858E9C1EE4B3AFE0DB9960FA03FE99CCDEDC28390D57CCBB0"
|
|
||||||
|
|
||||||
- os: windows-2022
|
- os: windows-2022
|
||||||
name: "Windows-MSVC-arm64"
|
artifact-name: Windows-MSVC-arm64
|
||||||
msystem: ""
|
base-cmake-preset: windows_msvc_arm64_cross
|
||||||
architecture: "arm64"
|
vcvars-arch: amd64_arm64
|
||||||
vcvars_arch: "amd64_arm64"
|
qt-architecture: win64_msvc2022_arm64_cross_compiled
|
||||||
qt_ver: 6
|
|
||||||
qt_host: "windows"
|
|
||||||
qt_arch: "win64_msvc2022_arm64_cross_compiled"
|
|
||||||
qt_version: "6.8.1"
|
|
||||||
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
|
||||||
nscurl_tag: "v24.9.26.122"
|
|
||||||
nscurl_sha256: "AEE6C4BE3CB6455858E9C1EE4B3AFE0DB9960FA03FE99CCDEDC28390D57CCBB0"
|
|
||||||
|
|
||||||
- os: macos-14
|
- os: macos-14
|
||||||
name: macOS
|
artifact-name: macOS
|
||||||
macosx_deployment_target: 11.0
|
base-cmake-preset: ${{ (inputs.build-type || 'Debug') == 'Debug' && 'macos_universal' || 'macos' }}
|
||||||
qt_ver: 6
|
macosx-deployment-target: 12.0
|
||||||
qt_host: mac
|
|
||||||
qt_arch: ""
|
|
||||||
qt_version: "6.8.1"
|
|
||||||
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: ${{ matrix.msystem != '' && 'msys2 {0}' || 'bash' }}
|
||||||
|
|
||||||
env:
|
env:
|
||||||
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
|
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx-deployment-target }}
|
||||||
INSTALL_DIR: "install"
|
|
||||||
INSTALL_PORTABLE_DIR: "install-portable"
|
|
||||||
INSTALL_APPIMAGE_DIR: "install-appdir"
|
|
||||||
BUILD_DIR: "build"
|
|
||||||
CCACHE_VAR: ""
|
|
||||||
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
##
|
##
|
||||||
# PREPARE
|
# SETUP
|
||||||
##
|
##
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: "true"
|
submodules: true
|
||||||
|
|
||||||
- name: "Setup MSYS2"
|
- name: Setup dependencies
|
||||||
if: runner.os == 'Windows' && matrix.msystem != ''
|
id: setup-dependencies
|
||||||
uses: msys2/setup-msys2@v2
|
uses: ./.github/actions/setup-dependencies
|
||||||
with:
|
with:
|
||||||
|
build-type: ${{ inputs.build-type || 'Debug' }}
|
||||||
msystem: ${{ matrix.msystem }}
|
msystem: ${{ matrix.msystem }}
|
||||||
update: true
|
vcvars-arch: ${{ matrix.vcvars-arch }}
|
||||||
install: >-
|
qt-architecture: ${{ matrix.qt-architecture }}
|
||||||
git
|
|
||||||
mingw-w64-x86_64-binutils
|
|
||||||
pacboy: >-
|
|
||||||
toolchain:p
|
|
||||||
cmake:p
|
|
||||||
extra-cmake-modules:p
|
|
||||||
ninja:p
|
|
||||||
qt6-base:p
|
|
||||||
qt6-svg:p
|
|
||||||
qt6-imageformats:p
|
|
||||||
quazip-qt6:p
|
|
||||||
ccache:p
|
|
||||||
qt6-5compat:p
|
|
||||||
qt6-networkauth:p
|
|
||||||
cmark:p
|
|
||||||
|
|
||||||
- name: Force newer ccache
|
|
||||||
if: runner.os == 'Windows' && matrix.msystem == '' && inputs.build_type == 'Debug'
|
|
||||||
run: |
|
|
||||||
choco install ccache --version 4.7.1
|
|
||||||
|
|
||||||
- name: Setup ccache
|
|
||||||
if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug'
|
|
||||||
uses: hendrikmuhs/ccache-action@v1.2.18
|
|
||||||
with:
|
|
||||||
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }}
|
|
||||||
|
|
||||||
- name: Use ccache on Debug builds only
|
|
||||||
if: inputs.build_type == 'Debug'
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
echo "CCACHE_VAR=ccache" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Retrieve ccache cache (Windows MinGW-w64)
|
|
||||||
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
|
|
||||||
uses: actions/cache@v4.2.3
|
|
||||||
with:
|
|
||||||
path: '${{ github.workspace }}\.ccache'
|
|
||||||
key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ matrix.os }}-mingw-w64-ccache
|
|
||||||
|
|
||||||
- name: Setup ccache (Windows MinGW-w64)
|
|
||||||
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
|
|
||||||
shell: msys2 {0}
|
|
||||||
run: |
|
|
||||||
ccache --set-config=cache_dir='${{ github.workspace }}\.ccache'
|
|
||||||
ccache --set-config=max_size='500M'
|
|
||||||
ccache --set-config=compression=true
|
|
||||||
ccache -p # Show config
|
|
||||||
ccache -z # Zero stats
|
|
||||||
|
|
||||||
- name: Configure ccache (Windows MSVC)
|
|
||||||
if: ${{ runner.os == 'Windows' && matrix.msystem == '' && inputs.build_type == 'Debug' }}
|
|
||||||
run: |
|
|
||||||
# https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix)
|
|
||||||
Copy-Item C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/ccache.exe -Destination C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/cl.exe
|
|
||||||
echo "CLToolExe=cl.exe" >> $env:GITHUB_ENV
|
|
||||||
echo "CLToolPath=C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/" >> $env:GITHUB_ENV
|
|
||||||
echo "TrackFileAccess=false" >> $env:GITHUB_ENV
|
|
||||||
# Needed for ccache, but also speeds up compile
|
|
||||||
echo "UseMultiToolTask=true" >> $env:GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Set short version
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
ver_short=`git rev-parse --short HEAD`
|
|
||||||
echo "VERSION=$ver_short" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Install Dependencies (Linux)
|
|
||||||
if: runner.os == 'Linux'
|
|
||||||
run: |
|
|
||||||
sudo apt-get -y update
|
|
||||||
sudo apt-get -y install ninja-build extra-cmake-modules scdoc appstream libxcb-cursor-dev
|
|
||||||
|
|
||||||
- name: Install Dependencies (macOS)
|
|
||||||
if: runner.os == 'macOS'
|
|
||||||
run: |
|
|
||||||
brew update
|
|
||||||
brew install ninja extra-cmake-modules
|
|
||||||
|
|
||||||
- name: Install host Qt (Windows MSVC arm64)
|
|
||||||
if: runner.os == 'Windows' && matrix.architecture == 'arm64'
|
|
||||||
uses: jurplel/install-qt-action@v4
|
|
||||||
with:
|
|
||||||
aqtversion: "==3.1.*"
|
|
||||||
py7zrversion: ">=0.20.2"
|
|
||||||
version: ${{ matrix.qt_version }}
|
|
||||||
host: "windows"
|
|
||||||
target: "desktop"
|
|
||||||
arch: ${{ matrix.qt_arch }}
|
|
||||||
modules: ${{ matrix.qt_modules }}
|
|
||||||
cache: ${{ inputs.is_qt_cached }}
|
|
||||||
cache-key-prefix: host-qt-arm64-windows
|
|
||||||
dir: ${{ github.workspace }}\HostQt
|
|
||||||
set-env: false
|
|
||||||
|
|
||||||
- name: Install Qt (macOS, Linux & Windows MSVC)
|
|
||||||
if: matrix.msystem == ''
|
|
||||||
uses: jurplel/install-qt-action@v4
|
|
||||||
with:
|
|
||||||
aqtversion: "==3.1.*"
|
|
||||||
py7zrversion: ">=0.20.2"
|
|
||||||
version: ${{ matrix.qt_version }}
|
|
||||||
target: "desktop"
|
|
||||||
arch: ${{ matrix.qt_arch }}
|
|
||||||
modules: ${{ matrix.qt_modules }}
|
|
||||||
tools: ${{ matrix.qt_tools }}
|
|
||||||
cache: ${{ inputs.is_qt_cached }}
|
|
||||||
|
|
||||||
- name: Install MSVC (Windows MSVC)
|
|
||||||
if: runner.os == 'Windows' # We want this for MinGW builds as well, as we need SignTool
|
|
||||||
uses: ilammy/msvc-dev-cmd@v1
|
|
||||||
with:
|
|
||||||
vsversion: 2022
|
|
||||||
arch: ${{ matrix.vcvars_arch }}
|
|
||||||
|
|
||||||
- name: Prepare AppImage (Linux)
|
|
||||||
if: runner.os == 'Linux'
|
|
||||||
env:
|
|
||||||
APPIMAGEUPDATE_HASH: ${{ matrix.appimageupdate_hash }}
|
|
||||||
LINUXDEPLOY_HASH: ${{ matrix.linuxdeploy_hash }}
|
|
||||||
LINUXDEPLOY_QT_HASH: ${{ matrix.linuxdeploy_qt_hash }}
|
|
||||||
run: |
|
|
||||||
wget "https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20250213-2/linuxdeploy-x86_64.AppImage"
|
|
||||||
wget "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/1-alpha-20250213-1/linuxdeploy-plugin-qt-x86_64.AppImage"
|
|
||||||
|
|
||||||
wget "https://github.com/AppImageCommunity/AppImageUpdate/releases/download/2.0.0-alpha-1-20241225/AppImageUpdate-x86_64.AppImage"
|
|
||||||
|
|
||||||
sha256sum -c - <<< "$LINUXDEPLOY_HASH"
|
|
||||||
sha256sum -c - <<< "$LINUXDEPLOY_QT_HASH"
|
|
||||||
sha256sum -c - <<< "$APPIMAGEUPDATE_HASH"
|
|
||||||
|
|
||||||
sudo apt install libopengl0
|
|
||||||
|
|
||||||
- name: Add QT_HOST_PATH var (Windows MSVC arm64)
|
|
||||||
if: runner.os == 'Windows' && matrix.architecture == 'arm64'
|
|
||||||
run: |
|
|
||||||
echo "QT_HOST_PATH=${{ github.workspace }}\HostQt\Qt\${{ matrix.qt_version }}\msvc2022_64" >> $env:GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Setup java (macOS)
|
|
||||||
if: runner.os == 'macOS'
|
|
||||||
uses: actions/setup-java@v4
|
|
||||||
with:
|
|
||||||
distribution: "temurin"
|
|
||||||
java-version: "17"
|
|
||||||
##
|
|
||||||
# CONFIGURE
|
|
||||||
##
|
|
||||||
|
|
||||||
- name: Configure CMake (macOS)
|
|
||||||
if: runner.os == 'macOS'
|
|
||||||
run: |
|
|
||||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -G Ninja
|
|
||||||
|
|
||||||
- name: Configure CMake (Windows MinGW-w64)
|
|
||||||
if: runner.os == 'Windows' && matrix.msystem != ''
|
|
||||||
shell: msys2 {0}
|
|
||||||
run: |
|
|
||||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=6 -DCMAKE_OBJDUMP=/mingw64/bin/objdump.exe -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }} -G Ninja
|
|
||||||
|
|
||||||
- name: Configure CMake (Windows MSVC)
|
|
||||||
if: runner.os == 'Windows' && matrix.msystem == '' && matrix.architecture != 'arm64'
|
|
||||||
run: |
|
|
||||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -DLauncher_FORCE_BUNDLED_LIBS=ON -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }} -G Ninja
|
|
||||||
|
|
||||||
- name: Configure CMake (Windows MSVC arm64)
|
|
||||||
if: runner.os == 'Windows' && matrix.msystem == '' && matrix.architecture == 'arm64'
|
|
||||||
run: |
|
|
||||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -A${{ matrix.architecture}} -DLauncher_FORCE_BUNDLED_LIBS=ON -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }}
|
|
||||||
|
|
||||||
- name: Configure CMake (Linux)
|
|
||||||
if: runner.os == 'Linux'
|
|
||||||
run: |
|
|
||||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DLauncher_BUILD_ARTIFACT=Linux-Qt${{ matrix.qt_ver }} -G Ninja
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# BUILD
|
# BUILD
|
||||||
##
|
##
|
||||||
|
|
||||||
- name: Build
|
- name: Get CMake preset
|
||||||
if: runner.os != 'Windows'
|
id: cmake-preset
|
||||||
|
env:
|
||||||
|
BASE_CMAKE_PRESET: ${{ matrix.base-cmake-preset }}
|
||||||
|
PRESET_TYPE: ${{ (inputs.build-type || 'Debug') == 'Debug' && 'debug' || 'ci' }}
|
||||||
run: |
|
run: |
|
||||||
cmake --build ${{ env.BUILD_DIR }}
|
echo preset="$BASE_CMAKE_PRESET"_"$PRESET_TYPE" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Build (Windows MinGW-w64)
|
- name: Run CMake workflow
|
||||||
if: runner.os == 'Windows' && matrix.msystem != ''
|
env:
|
||||||
shell: msys2 {0}
|
CMAKE_PRESET: ${{ steps.cmake-preset.outputs.preset }}
|
||||||
run: |
|
run: |
|
||||||
cmake --build ${{ env.BUILD_DIR }}
|
cmake --workflow --preset "$CMAKE_PRESET"
|
||||||
|
|
||||||
- name: Build (Windows MSVC)
|
|
||||||
if: runner.os == 'Windows' && matrix.msystem == ''
|
|
||||||
run: |
|
|
||||||
cmake --build ${{ env.BUILD_DIR }} --config ${{ inputs.build_type }}
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# TEST
|
# PACKAGE
|
||||||
##
|
##
|
||||||
|
|
||||||
- name: Test
|
- name: Get short version
|
||||||
if: runner.os != 'Windows'
|
id: short-version
|
||||||
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
ctest -E "^example64|example$" --test-dir build --output-on-failure
|
echo "version=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Test (Windows MinGW-w64)
|
- name: Package (Linux)
|
||||||
if: runner.os == 'Windows' && matrix.msystem != ''
|
if: ${{ runner.os == 'Linux' }}
|
||||||
shell: msys2 {0}
|
uses: ./.github/actions/package/linux
|
||||||
run: |
|
with:
|
||||||
ctest -E "^example64|example$" --test-dir build --output-on-failure
|
version: ${{ steps.short-version.outputs.version }}
|
||||||
|
build-type: ${{ steps.setup-dependencies.outputs.build-type }}
|
||||||
|
cmake-preset: ${{ steps.cmake-preset.outputs.preset }}
|
||||||
|
qt-version: ${{ steps.setup-dependencies.outputs.qt-version }}
|
||||||
|
|
||||||
- name: Test (Windows MSVC)
|
gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||||
if: runner.os == 'Windows' && matrix.msystem == '' && matrix.architecture != 'arm64'
|
gpg-private-key-id: ${{ secrets.GPG_PRIVATE_KEY_ID }}
|
||||||
run: |
|
|
||||||
ctest -E "^example64|example$" --test-dir build --output-on-failure -C ${{ inputs.build_type }}
|
|
||||||
|
|
||||||
##
|
|
||||||
# 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: |
|
uses: ./.github/actions/package/macos
|
||||||
cmake --install ${{ env.BUILD_DIR }}
|
|
||||||
|
|
||||||
cd ${{ env.INSTALL_DIR }}
|
|
||||||
chmod +x "PrismLauncher.app/Contents/MacOS/prismlauncher"
|
|
||||||
|
|
||||||
if [ -n '${{ secrets.APPLE_CODESIGN_ID }}' ]; then
|
|
||||||
APPLE_CODESIGN_ID='${{ secrets.APPLE_CODESIGN_ID }}'
|
|
||||||
ENTITLEMENTS_FILE='../program_info/App.entitlements'
|
|
||||||
else
|
|
||||||
APPLE_CODESIGN_ID='-'
|
|
||||||
ENTITLEMENTS_FILE='../program_info/AdhocSignedApp.entitlements'
|
|
||||||
fi
|
|
||||||
|
|
||||||
sudo codesign --sign "$APPLE_CODESIGN_ID" --deep --force --entitlements "$ENTITLEMENTS_FILE" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher"
|
|
||||||
mv "PrismLauncher.app" "Prism Launcher.app"
|
|
||||||
|
|
||||||
- name: Notarize (macOS)
|
|
||||||
if: runner.os == 'macOS'
|
|
||||||
run: |
|
|
||||||
cd ${{ env.INSTALL_DIR }}
|
|
||||||
|
|
||||||
if [ -n '${{ secrets.APPLE_NOTARIZE_PASSWORD }}' ]; then
|
|
||||||
ditto -c -k --sequesterRsrc --keepParent "Prism Launcher.app" ../PrismLauncher.zip
|
|
||||||
xcrun notarytool submit ../PrismLauncher.zip \
|
|
||||||
--wait --progress \
|
|
||||||
--apple-id '${{ secrets.APPLE_NOTARIZE_APPLE_ID }}' \
|
|
||||||
--team-id '${{ secrets.APPLE_NOTARIZE_TEAM_ID }}' \
|
|
||||||
--password '${{ secrets.APPLE_NOTARIZE_PASSWORD }}'
|
|
||||||
|
|
||||||
xcrun stapler staple "Prism Launcher.app"
|
|
||||||
else
|
|
||||||
echo ":warning: Skipping notarization as credentials are not present." >> $GITHUB_STEP_SUMMARY
|
|
||||||
fi
|
|
||||||
ditto -c -k --sequesterRsrc --keepParent "Prism Launcher.app" ../PrismLauncher.zip
|
|
||||||
|
|
||||||
- name: Make Sparkle signature (macOS)
|
|
||||||
if: matrix.name == 'macOS'
|
|
||||||
run: |
|
|
||||||
if [ '${{ secrets.SPARKLE_ED25519_KEY }}' != '' ]; then
|
|
||||||
echo '${{ secrets.SPARKLE_ED25519_KEY }}' > ed25519-priv.pem
|
|
||||||
signature=$(/opt/homebrew/opt/openssl@3/bin/openssl pkeyutl -sign -rawin -in ${{ github.workspace }}/PrismLauncher.zip -inkey ed25519-priv.pem | openssl base64 | tr -d \\n)
|
|
||||||
rm ed25519-priv.pem
|
|
||||||
cat >> $GITHUB_STEP_SUMMARY << EOF
|
|
||||||
### Artifact Information :information_source:
|
|
||||||
- :memo: Sparkle Signature (ed25519): \`$signature\`
|
|
||||||
EOF
|
|
||||||
else
|
|
||||||
cat >> $GITHUB_STEP_SUMMARY << EOF
|
|
||||||
### Artifact Information :information_source:
|
|
||||||
- :warning: Sparkle Signature (ed25519): No private key available (likely a pull request or fork)
|
|
||||||
EOF
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Package (Windows MinGW-w64)
|
|
||||||
if: runner.os == 'Windows' && matrix.msystem != ''
|
|
||||||
shell: msys2 {0}
|
|
||||||
run: |
|
|
||||||
cmake --install ${{ env.BUILD_DIR }}
|
|
||||||
touch ${{ env.INSTALL_DIR }}/manifest.txt
|
|
||||||
for l in $(find ${{ env.INSTALL_DIR }} -type f); do l=$(cygpath -u $l); l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_DIR }}/}; l=${l#./}; echo $l; done >> ${{ env.INSTALL_DIR }}/manifest.txt
|
|
||||||
|
|
||||||
- name: Package (Windows MSVC)
|
|
||||||
if: runner.os == 'Windows' && matrix.msystem == ''
|
|
||||||
run: |
|
|
||||||
cmake --install ${{ env.BUILD_DIR }} --config ${{ inputs.build_type }}
|
|
||||||
|
|
||||||
cd ${{ github.workspace }}
|
|
||||||
|
|
||||||
Get-ChildItem ${{ env.INSTALL_DIR }} -Recurse | ForEach FullName | Resolve-Path -Relative | %{ $_.TrimStart('.\') } | %{ $_.TrimStart('${{ env.INSTALL_DIR }}') } | %{ $_.TrimStart('\') } | Out-File -FilePath ${{ env.INSTALL_DIR }}/manifest.txt
|
|
||||||
|
|
||||||
- name: Fetch codesign certificate (Windows)
|
|
||||||
if: runner.os == 'Windows'
|
|
||||||
shell: bash # yes, we are not using MSYS2 or PowerShell here
|
|
||||||
run: |
|
|
||||||
echo '${{ secrets.WINDOWS_CODESIGN_CERT }}' | base64 --decode > codesign.pfx
|
|
||||||
|
|
||||||
- name: Sign executable (Windows)
|
|
||||||
if: runner.os == 'Windows'
|
|
||||||
run: |
|
|
||||||
if (Get-Content ./codesign.pfx){
|
|
||||||
cd ${{ env.INSTALL_DIR }}
|
|
||||||
# We ship the exact same executable for portable and non-portable editions, so signing just once is fine
|
|
||||||
SignTool sign /fd sha256 /td sha256 /f ../codesign.pfx /p '${{ secrets.WINDOWS_CODESIGN_PASSWORD }}' /tr http://timestamp.digicert.com prismlauncher.exe prismlauncher_updater.exe prismlauncher_filelink.exe
|
|
||||||
} else {
|
|
||||||
":warning: Skipped code signing for Windows, as certificate was not present." >> $env:GITHUB_STEP_SUMMARY
|
|
||||||
}
|
|
||||||
|
|
||||||
- name: Package (Windows MinGW-w64, portable)
|
|
||||||
if: runner.os == 'Windows' && matrix.msystem != ''
|
|
||||||
shell: msys2 {0}
|
|
||||||
run: |
|
|
||||||
cp -r ${{ env.INSTALL_DIR }} ${{ env.INSTALL_PORTABLE_DIR }} # cmake install on Windows is slow, let's just copy instead
|
|
||||||
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable
|
|
||||||
for l in $(find ${{ env.INSTALL_PORTABLE_DIR }} -type f); do l=$(cygpath -u $l); l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_PORTABLE_DIR }}/}; l=${l#./}; echo $l; done >> ${{ env.INSTALL_PORTABLE_DIR }}/manifest.txt
|
|
||||||
|
|
||||||
- name: Package (Windows MSVC, portable)
|
|
||||||
if: runner.os == 'Windows' && matrix.msystem == ''
|
|
||||||
run: |
|
|
||||||
cp -r ${{ env.INSTALL_DIR }} ${{ env.INSTALL_PORTABLE_DIR }} # cmake install on Windows is slow, let's just copy instead
|
|
||||||
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable
|
|
||||||
|
|
||||||
Get-ChildItem ${{ env.INSTALL_PORTABLE_DIR }} -Recurse | ForEach FullName | Resolve-Path -Relative | %{ $_.TrimStart('.\') } | %{ $_.TrimStart('${{ env.INSTALL_PORTABLE_DIR }}') } | %{ $_.TrimStart('\') } | Out-File -FilePath ${{ env.INSTALL_DIR }}/manifest.txt
|
|
||||||
|
|
||||||
- name: Package (Windows, installer)
|
|
||||||
if: runner.os == 'Windows'
|
|
||||||
run: |
|
|
||||||
if ('${{ matrix.nscurl_tag }}') {
|
|
||||||
New-Item -Name NSISPlugins -ItemType Directory
|
|
||||||
Invoke-Webrequest https://github.com/negrutiu/nsis-nscurl/releases/download/${{ matrix.nscurl_tag }}/NScurl.zip -OutFile NSISPlugins\NScurl.zip
|
|
||||||
$nscurl_hash = Get-FileHash NSISPlugins\NScurl.zip -Algorithm Sha256 | Select-Object -ExpandProperty Hash
|
|
||||||
if ( $nscurl_hash -ne "${{ matrix.nscurl_sha256 }}") {
|
|
||||||
echo "::error:: NSCurl.zip sha256 mismatch"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
Expand-Archive -Path NSISPlugins\NScurl.zip -DestinationPath NSISPlugins\NScurl
|
|
||||||
}
|
|
||||||
cd ${{ env.INSTALL_DIR }}
|
|
||||||
makensis -NOCD "${{ github.workspace }}/${{ env.BUILD_DIR }}/program_info/win_install.nsi"
|
|
||||||
|
|
||||||
- name: Sign installer (Windows)
|
|
||||||
if: runner.os == 'Windows'
|
|
||||||
run: |
|
|
||||||
if (Get-Content ./codesign.pfx){
|
|
||||||
SignTool sign /fd sha256 /td sha256 /f codesign.pfx /p '${{ secrets.WINDOWS_CODESIGN_PASSWORD }}' /tr http://timestamp.digicert.com PrismLauncher-Setup.exe
|
|
||||||
} else {
|
|
||||||
":warning: Skipped code signing for Windows, as certificate was not present." >> $env:GITHUB_STEP_SUMMARY
|
|
||||||
}
|
|
||||||
|
|
||||||
- name: Package AppImage (Linux)
|
|
||||||
if: runner.os == 'Linux'
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
|
||||||
run: |
|
|
||||||
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_APPIMAGE_DIR }}/usr
|
|
||||||
|
|
||||||
mv ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/metainfo/org.prismlauncher.PrismLauncher.metainfo.xml ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/metainfo/org.prismlauncher.PrismLauncher.appdata.xml
|
|
||||||
export "NO_APPSTREAM=1" # we have to skip appstream checking because appstream on ubuntu 20.04 is outdated
|
|
||||||
|
|
||||||
export OUTPUT="PrismLauncher-Linux-x86_64.AppImage"
|
|
||||||
|
|
||||||
chmod +x linuxdeploy-*.AppImage
|
|
||||||
|
|
||||||
mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib
|
|
||||||
mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines
|
|
||||||
|
|
||||||
cp -r ${{ runner.workspace }}/Qt/${{ matrix.qt_version }}/gcc_64/plugins/iconengines/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines
|
|
||||||
|
|
||||||
cp /usr/lib/x86_64-linux-gnu/libcrypto.so.* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
|
|
||||||
cp /usr/lib/x86_64-linux-gnu/libssl.so.* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
|
|
||||||
cp /usr/lib/x86_64-linux-gnu/libOpenGL.so.0* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
|
|
||||||
|
|
||||||
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib"
|
|
||||||
export LD_LIBRARY_PATH
|
|
||||||
|
|
||||||
chmod +x AppImageUpdate-x86_64.AppImage
|
|
||||||
cp AppImageUpdate-x86_64.AppImage ${{ env.INSTALL_APPIMAGE_DIR }}/usr/bin
|
|
||||||
|
|
||||||
export UPDATE_INFORMATION="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|PrismLauncher-Linux-x86_64.AppImage.zsync"
|
|
||||||
|
|
||||||
if [ '${{ secrets.GPG_PRIVATE_KEY_ID }}' != '' ]; then
|
|
||||||
export SIGN=1
|
|
||||||
export SIGN_KEY=${{ secrets.GPG_PRIVATE_KEY_ID }}
|
|
||||||
mkdir -p ~/.gnupg/
|
|
||||||
echo "$GPG_PRIVATE_KEY" > ~/.gnupg/private.key
|
|
||||||
gpg --import ~/.gnupg/private.key
|
|
||||||
else
|
|
||||||
echo ":warning: Skipped code signing for Linux AppImage, as gpg key was not present." >> $GITHUB_STEP_SUMMARY
|
|
||||||
fi
|
|
||||||
|
|
||||||
./linuxdeploy-x86_64.AppImage --appdir ${{ env.INSTALL_APPIMAGE_DIR }} --output appimage --plugin qt -i ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/icons/hicolor/scalable/apps/org.prismlauncher.PrismLauncher.svg
|
|
||||||
|
|
||||||
mv "PrismLauncher-Linux-x86_64.AppImage" "PrismLauncher-Linux-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage"
|
|
||||||
|
|
||||||
- name: Package (Linux, portable)
|
|
||||||
if: runner.os == 'Linux'
|
|
||||||
run: |
|
|
||||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_PORTABLE_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DLauncher_BUILD_ARTIFACT=Linux-Qt${{ matrix.qt_ver }} -DINSTALL_BUNDLE=full -G Ninja
|
|
||||||
cmake --install ${{ env.BUILD_DIR }}
|
|
||||||
cmake --install ${{ env.BUILD_DIR }} --component portable
|
|
||||||
|
|
||||||
mkdir ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
|
||||||
cp /lib/x86_64-linux-gnu/libbz2.so.1.0 ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
|
||||||
cp /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0 ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
|
||||||
cp /usr/lib/x86_64-linux-gnu/libcrypto.so.* ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
|
||||||
cp /usr/lib/x86_64-linux-gnu/libssl.so.* ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
|
||||||
cp /usr/lib/x86_64-linux-gnu/libffi.so.*.* ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
|
||||||
mv ${{ env.INSTALL_PORTABLE_DIR }}/bin/*.so* ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
|
||||||
|
|
||||||
for l in $(find ${{ env.INSTALL_PORTABLE_DIR }} -type f); do l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_PORTABLE_DIR }}/}; l=${l#./}; echo $l; done > ${{ env.INSTALL_PORTABLE_DIR }}/manifest.txt
|
|
||||||
cd ${{ env.INSTALL_PORTABLE_DIR }}
|
|
||||||
tar -czf ../PrismLauncher-portable.tar.gz *
|
|
||||||
|
|
||||||
##
|
|
||||||
# UPLOAD BUILDS
|
|
||||||
##
|
|
||||||
|
|
||||||
- name: Upload binary tarball (macOS)
|
|
||||||
if: runner.os == 'macOS'
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
with:
|
||||||
name: PrismLauncher-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }}
|
version: ${{ steps.short-version.outputs.version }}
|
||||||
path: PrismLauncher.zip
|
build-type: ${{ steps.setup-dependencies.outputs.build-type }}
|
||||||
|
artifact-name: ${{ matrix.artifact-name }}
|
||||||
|
|
||||||
- name: Upload binary zip (Windows)
|
apple-codesign-cert: ${{ secrets.APPLE-CODESIGN-CERT }}
|
||||||
if: runner.os == 'Windows'
|
apple-codesign-password: ${{ secrets.APPLE-CODESIGN_PASSWORD }}
|
||||||
uses: actions/upload-artifact@v4
|
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 }}
|
||||||
|
sparkle-ed25519-key: ${{ secrets.SPARKLE-ED25519_KEY }}
|
||||||
|
|
||||||
|
- name: Package (Windows)
|
||||||
|
if: ${{ runner.os == 'Windows' }}
|
||||||
|
uses: ./.github/actions/package/windows
|
||||||
with:
|
with:
|
||||||
name: PrismLauncher-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }}
|
version: ${{ steps.short-version.outputs.version }}
|
||||||
path: ${{ env.INSTALL_DIR }}/**
|
build-type: ${{ steps.setup-dependencies.outputs.build-type }}
|
||||||
|
artifact-name: ${{ matrix.artifact-name }}
|
||||||
|
msystem: ${{ matrix.msystem }}
|
||||||
|
|
||||||
- name: Upload binary zip (Windows, portable)
|
windows-codesign-cert: ${{ secrets.WINDOWS_CODESIGN_CERT }}
|
||||||
if: runner.os == 'Windows'
|
windows-codesign-password: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }}
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: PrismLauncher-${{ matrix.name }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
|
|
||||||
path: ${{ env.INSTALL_PORTABLE_DIR }}/**
|
|
||||||
|
|
||||||
- name: Upload installer (Windows)
|
|
||||||
if: runner.os == 'Windows'
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: PrismLauncher-${{ matrix.name }}-Setup-${{ env.VERSION }}-${{ inputs.build_type }}
|
|
||||||
path: PrismLauncher-Setup.exe
|
|
||||||
|
|
||||||
- name: Upload binary tarball (Linux, portable)
|
|
||||||
if: runner.os == 'Linux'
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: PrismLauncher-${{ runner.os }}-Qt6-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
|
|
||||||
path: PrismLauncher-portable.tar.gz
|
|
||||||
|
|
||||||
- name: Upload AppImage (Linux)
|
|
||||||
if: runner.os == 'Linux'
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
|
|
||||||
path: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
|
|
||||||
|
|
||||||
- name: Upload AppImage Zsync (Linux)
|
|
||||||
if: runner.os == 'Linux'
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage.zsync
|
|
||||||
path: PrismLauncher-Linux-x86_64.AppImage.zsync
|
|
||||||
|
|
||||||
- name: ccache stats (Windows MinGW-w64)
|
|
||||||
if: runner.os == 'Windows' && matrix.msystem != ''
|
|
||||||
shell: msys2 {0}
|
|
||||||
run: |
|
|
||||||
ccache -s
|
|
||||||
|
|
84
.github/workflows/codeql.yml
vendored
84
.github/workflows/codeql.yml
vendored
|
@ -2,37 +2,50 @@ name: "CodeQL Code Scanning"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
# NOTE: `!` doesn't work with `paths-ignore` :(
|
|
||||||
# So we a catch-all glob instead
|
|
||||||
# https://github.com/orgs/community/discussions/25369#discussioncomment-3247674
|
|
||||||
paths:
|
paths:
|
||||||
- "**"
|
# File types
|
||||||
- "!.github/**"
|
- "**.cpp"
|
||||||
- ".github/workflows/codeql.yml"
|
- "**.h"
|
||||||
- "!flatpak/"
|
- "**.java"
|
||||||
- "!nix/"
|
|
||||||
- "!scripts/"
|
|
||||||
|
|
||||||
- "!.git*"
|
# Directories
|
||||||
- "!.envrc"
|
- "buildconfig/"
|
||||||
- "!**.md"
|
- "cmake/"
|
||||||
|
- "launcher/"
|
||||||
|
- "libraries/"
|
||||||
|
- "program_info/"
|
||||||
|
- "tests/"
|
||||||
|
|
||||||
|
# Files
|
||||||
|
- "CMakeLists.txt"
|
||||||
- "COPYING.md"
|
- "COPYING.md"
|
||||||
- "!renovate.json"
|
|
||||||
|
# Workflows
|
||||||
|
- ".github/codeql"
|
||||||
|
- ".github/workflows/codeql.yml"
|
||||||
|
- ".github/actions/setup-dependencies/"
|
||||||
pull_request:
|
pull_request:
|
||||||
# See above
|
|
||||||
paths:
|
paths:
|
||||||
- "**"
|
# File types
|
||||||
- "!.github/**"
|
- "**.cpp"
|
||||||
- ".github/workflows/codeql.yml"
|
- "**.h"
|
||||||
- "!flatpak/"
|
|
||||||
- "!nix/"
|
|
||||||
- "!scripts/"
|
|
||||||
|
|
||||||
- "!.git*"
|
# Directories
|
||||||
- "!.envrc"
|
- "buildconfig/"
|
||||||
- "!**.md"
|
- "cmake/"
|
||||||
|
- "launcher/"
|
||||||
|
- "libraries/"
|
||||||
|
- "program_info/"
|
||||||
|
- "tests/"
|
||||||
|
|
||||||
|
# Files
|
||||||
|
- "CMakeLists.txt"
|
||||||
- "COPYING.md"
|
- "COPYING.md"
|
||||||
- "!renovate.json"
|
|
||||||
|
# Workflows
|
||||||
|
- ".github/codeql"
|
||||||
|
- ".github/workflows/codeql.yml"
|
||||||
|
- ".github/actions/setup-dependencies/"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
@ -52,28 +65,15 @@ jobs:
|
||||||
queries: security-and-quality
|
queries: security-and-quality
|
||||||
languages: cpp, java
|
languages: cpp, java
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Setup dependencies
|
||||||
run: sudo apt-get -y update
|
uses: ./.github/actions/setup-dependencies
|
||||||
|
|
||||||
sudo apt-get -y install ninja-build extra-cmake-modules scdoc
|
|
||||||
|
|
||||||
- name: Install Qt
|
|
||||||
uses: jurplel/install-qt-action@v3
|
|
||||||
with:
|
with:
|
||||||
aqtversion: "==3.1.*"
|
build-type: Debug
|
||||||
py7zrversion: ">=0.20.2"
|
|
||||||
version: "6.8.1"
|
|
||||||
host: "linux"
|
|
||||||
target: "desktop"
|
|
||||||
arch: ""
|
|
||||||
modules: "qt5compat qtimageformats qtnetworkauth"
|
|
||||||
tools: ""
|
|
||||||
|
|
||||||
- name: Configure and Build
|
- name: Configure and Build
|
||||||
run: |
|
run: |
|
||||||
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/usr -G Ninja
|
cmake --preset linux_debug
|
||||||
|
cmake --build --preset linux_debug
|
||||||
cmake --build build
|
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v3
|
uses: github/codeql-action/analyze@v3
|
||||||
|
|
61
.github/workflows/flatpak.yml
vendored
61
.github/workflows/flatpak.yml
vendored
|
@ -5,35 +5,52 @@ on:
|
||||||
# We don't do anything with these artifacts on releases. They go to Flathub
|
# We don't do anything with these artifacts on releases. They go to Flathub
|
||||||
tags-ignore:
|
tags-ignore:
|
||||||
- "*"
|
- "*"
|
||||||
# NOTE: `!` doesn't work with `paths-ignore` :(
|
|
||||||
# So we a catch-all glob instead
|
|
||||||
# https://github.com/orgs/community/discussions/25369#discussioncomment-3247674
|
|
||||||
paths:
|
paths:
|
||||||
- "**"
|
# File types
|
||||||
- "!.github/**"
|
- "**.cpp"
|
||||||
- ".github/workflows/flatpak.yml"
|
- "**.h"
|
||||||
- "!nix/"
|
- "**.java"
|
||||||
- "!scripts/"
|
|
||||||
|
|
||||||
- "!.git*"
|
# Build files
|
||||||
- "!.envrc"
|
- "flatpak/"
|
||||||
- "!**.md"
|
|
||||||
|
# Directories
|
||||||
|
- "buildconfig/"
|
||||||
|
- "cmake/"
|
||||||
|
- "launcher/"
|
||||||
|
- "libraries/"
|
||||||
|
- "program_info/"
|
||||||
|
- "tests/"
|
||||||
|
|
||||||
|
# Files
|
||||||
|
- "CMakeLists.txt"
|
||||||
- "COPYING.md"
|
- "COPYING.md"
|
||||||
- "!renovate.json"
|
|
||||||
|
# Workflows
|
||||||
|
- ".github/workflows/flatpak.yml"
|
||||||
pull_request:
|
pull_request:
|
||||||
# See above
|
|
||||||
paths:
|
paths:
|
||||||
- "**"
|
# File types
|
||||||
- "!.github/**"
|
- "**.cpp"
|
||||||
- ".github/workflows/flatpak.yml"
|
- "**.h"
|
||||||
- "!nix/"
|
|
||||||
- "!scripts/"
|
|
||||||
|
|
||||||
- "!.git*"
|
# Build files
|
||||||
- "!.envrc"
|
- "flatpak/"
|
||||||
- "!**.md"
|
|
||||||
|
# Directories
|
||||||
|
- "buildconfig/"
|
||||||
|
- "cmake/"
|
||||||
|
- "launcher/"
|
||||||
|
- "libraries/"
|
||||||
|
- "program_info/"
|
||||||
|
- "tests/"
|
||||||
|
|
||||||
|
# Files
|
||||||
|
- "CMakeLists.txt"
|
||||||
- "COPYING.md"
|
- "COPYING.md"
|
||||||
- "!renovate.json"
|
|
||||||
|
# Workflows
|
||||||
|
- ".github/workflows/flatpak.yml"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
|
66
.github/workflows/nix.yml
vendored
66
.github/workflows/nix.yml
vendored
|
@ -4,34 +4,56 @@ on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- "*"
|
- "*"
|
||||||
# NOTE: `!` doesn't work with `paths-ignore` :(
|
|
||||||
# So we a catch-all glob instead
|
|
||||||
# https://github.com/orgs/community/discussions/25369#discussioncomment-3247674
|
|
||||||
paths:
|
paths:
|
||||||
- "**"
|
# File types
|
||||||
- "!.github/**"
|
- "**.cpp"
|
||||||
- ".github/workflows/nix.yml"
|
- "**.h"
|
||||||
- "!flatpak/"
|
- "**.java"
|
||||||
- "!scripts/"
|
|
||||||
|
|
||||||
- "!.git*"
|
# Build files
|
||||||
- "!.envrc"
|
- "**.nix"
|
||||||
- "!**.md"
|
- "nix/"
|
||||||
|
- "flake.lock"
|
||||||
|
|
||||||
|
# Directories
|
||||||
|
- "buildconfig/"
|
||||||
|
- "cmake/"
|
||||||
|
- "launcher/"
|
||||||
|
- "libraries/"
|
||||||
|
- "program_info/"
|
||||||
|
- "tests/"
|
||||||
|
|
||||||
|
# Files
|
||||||
|
- "CMakeLists.txt"
|
||||||
- "COPYING.md"
|
- "COPYING.md"
|
||||||
- "!renovate.json"
|
|
||||||
|
# Workflows
|
||||||
|
- ".github/workflows/nix.yml"
|
||||||
pull_request_target:
|
pull_request_target:
|
||||||
paths:
|
paths:
|
||||||
- "**"
|
# File types
|
||||||
- "!.github/**"
|
- "**.cpp"
|
||||||
- ".github/workflows/nix.yml"
|
- "**.h"
|
||||||
- "!flatpak/"
|
|
||||||
- "!scripts/"
|
|
||||||
|
|
||||||
- "!.git*"
|
# Build files
|
||||||
- "!.envrc"
|
- "**.nix"
|
||||||
- "!**.md"
|
- "nix/"
|
||||||
|
- "flake.lock"
|
||||||
|
|
||||||
|
# Directories
|
||||||
|
- "buildconfig/"
|
||||||
|
- "cmake/"
|
||||||
|
- "launcher/"
|
||||||
|
- "libraries/"
|
||||||
|
- "program_info/"
|
||||||
|
- "tests/"
|
||||||
|
|
||||||
|
# Files
|
||||||
|
- "CMakeLists.txt"
|
||||||
- "COPYING.md"
|
- "COPYING.md"
|
||||||
- "!renovate.json"
|
|
||||||
|
# Workflows
|
||||||
|
- ".github/workflows/nix.yml"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
@ -89,7 +111,7 @@ jobs:
|
||||||
# For PRs
|
# For PRs
|
||||||
- name: Setup Nix Magic Cache
|
- name: Setup Nix Magic Cache
|
||||||
if: ${{ env.USE_DETERMINATE == 'true' }}
|
if: ${{ env.USE_DETERMINATE == 'true' }}
|
||||||
uses: DeterminateSystems/flakehub-cache-action@v1
|
uses: DeterminateSystems/flakehub-cache-action@v2
|
||||||
|
|
||||||
# For in-tree builds
|
# For in-tree builds
|
||||||
- name: Setup Cachix
|
- name: Setup Cachix
|
||||||
|
|
|
@ -10,20 +10,8 @@ jobs:
|
||||||
name: Build Release
|
name: Build Release
|
||||||
uses: ./.github/workflows/build.yml
|
uses: ./.github/workflows/build.yml
|
||||||
with:
|
with:
|
||||||
build_type: Release
|
build-type: Release
|
||||||
is_qt_cached: false
|
secrets: inherit
|
||||||
secrets:
|
|
||||||
SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }}
|
|
||||||
WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }}
|
|
||||||
WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }}
|
|
||||||
APPLE_CODESIGN_CERT: ${{ secrets.APPLE_CODESIGN_CERT }}
|
|
||||||
APPLE_CODESIGN_PASSWORD: ${{ secrets.APPLE_CODESIGN_PASSWORD }}
|
|
||||||
APPLE_CODESIGN_ID: ${{ secrets.APPLE_CODESIGN_ID }}
|
|
||||||
APPLE_NOTARIZE_APPLE_ID: ${{ secrets.APPLE_NOTARIZE_APPLE_ID }}
|
|
||||||
APPLE_NOTARIZE_TEAM_ID: ${{ secrets.APPLE_NOTARIZE_TEAM_ID }}
|
|
||||||
APPLE_NOTARIZE_PASSWORD: ${{ secrets.APPLE_NOTARIZE_PASSWORD }}
|
|
||||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
|
||||||
GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}
|
|
||||||
|
|
||||||
create_release:
|
create_release:
|
||||||
needs: build_release
|
needs: build_release
|
||||||
|
@ -78,6 +66,17 @@ jobs:
|
||||||
cd ..
|
cd ..
|
||||||
done
|
done
|
||||||
|
|
||||||
|
for d in PrismLauncher-Windows-MinGW-arm64*; do
|
||||||
|
cd "${d}" || continue
|
||||||
|
INST="$(echo -n ${d} | grep -o Setup || true)"
|
||||||
|
PORT="$(echo -n ${d} | grep -o Portable || true)"
|
||||||
|
NAME="PrismLauncher-Windows-MinGW-arm64"
|
||||||
|
test -z "${PORT}" || NAME="${NAME}-Portable"
|
||||||
|
test -z "${INST}" || mv PrismLauncher-*.exe ../${NAME}-Setup-${{ env.VERSION }}.exe
|
||||||
|
test -n "${INST}" || zip -r -9 "../${NAME}-${{ env.VERSION }}.zip" *
|
||||||
|
cd ..
|
||||||
|
done
|
||||||
|
|
||||||
- name: Create release
|
- name: Create release
|
||||||
id: create_release
|
id: create_release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
|
@ -94,6 +93,9 @@ jobs:
|
||||||
PrismLauncher-Windows-MinGW-w64-${{ env.VERSION }}.zip
|
PrismLauncher-Windows-MinGW-w64-${{ env.VERSION }}.zip
|
||||||
PrismLauncher-Windows-MinGW-w64-Portable-${{ env.VERSION }}.zip
|
PrismLauncher-Windows-MinGW-w64-Portable-${{ env.VERSION }}.zip
|
||||||
PrismLauncher-Windows-MinGW-w64-Setup-${{ env.VERSION }}.exe
|
PrismLauncher-Windows-MinGW-w64-Setup-${{ env.VERSION }}.exe
|
||||||
|
PrismLauncher-Windows-MinGW-arm64-${{ env.VERSION }}.zip
|
||||||
|
PrismLauncher-Windows-MinGW-arm64-Portable-${{ env.VERSION }}.zip
|
||||||
|
PrismLauncher-Windows-MinGW-arm64-Setup-${{ env.VERSION }}.exe
|
||||||
PrismLauncher-Windows-MSVC-arm64-${{ env.VERSION }}.zip
|
PrismLauncher-Windows-MSVC-arm64-${{ env.VERSION }}.zip
|
||||||
PrismLauncher-Windows-MSVC-arm64-Portable-${{ env.VERSION }}.zip
|
PrismLauncher-Windows-MSVC-arm64-Portable-${{ env.VERSION }}.zip
|
||||||
PrismLauncher-Windows-MSVC-arm64-Setup-${{ env.VERSION }}.exe
|
PrismLauncher-Windows-MSVC-arm64-Setup-${{ env.VERSION }}.exe
|
60
.github/workflows/trigger_builds.yml
vendored
60
.github/workflows/trigger_builds.yml
vendored
|
@ -1,60 +0,0 @@
|
||||||
name: Build Application
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches-ignore:
|
|
||||||
- "renovate/**"
|
|
||||||
# NOTE: `!` doesn't work with `paths-ignore` :(
|
|
||||||
# So we a catch-all glob instead
|
|
||||||
# https://github.com/orgs/community/discussions/25369#discussioncomment-3247674
|
|
||||||
paths:
|
|
||||||
- "**"
|
|
||||||
- "!.github/**"
|
|
||||||
- ".github/workflows/build.yml"
|
|
||||||
- ".github/workflows/trigger_builds.yml"
|
|
||||||
- "!flatpak/"
|
|
||||||
- "!nix/"
|
|
||||||
- "!scripts/"
|
|
||||||
|
|
||||||
- "!.git*"
|
|
||||||
- "!.envrc"
|
|
||||||
- "!**.md"
|
|
||||||
- "COPYING.md"
|
|
||||||
- "!renovate.json"
|
|
||||||
pull_request:
|
|
||||||
# See above
|
|
||||||
paths:
|
|
||||||
- "**"
|
|
||||||
- "!.github/**"
|
|
||||||
- ".github/workflows/build.yml"
|
|
||||||
- ".github/workflows/trigger_builds.yml"
|
|
||||||
- "!flatpak/"
|
|
||||||
- "!nix/"
|
|
||||||
- "!scripts/"
|
|
||||||
|
|
||||||
- "!.git*"
|
|
||||||
- "!.envrc"
|
|
||||||
- "!**.md"
|
|
||||||
- "COPYING.md"
|
|
||||||
- "!renovate.json"
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build_debug:
|
|
||||||
name: Build Debug
|
|
||||||
uses: ./.github/workflows/build.yml
|
|
||||||
with:
|
|
||||||
build_type: Debug
|
|
||||||
is_qt_cached: true
|
|
||||||
secrets:
|
|
||||||
SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }}
|
|
||||||
WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }}
|
|
||||||
WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }}
|
|
||||||
APPLE_CODESIGN_CERT: ${{ secrets.APPLE_CODESIGN_CERT }}
|
|
||||||
APPLE_CODESIGN_PASSWORD: ${{ secrets.APPLE_CODESIGN_PASSWORD }}
|
|
||||||
APPLE_CODESIGN_ID: ${{ secrets.APPLE_CODESIGN_ID }}
|
|
||||||
APPLE_NOTARIZE_APPLE_ID: ${{ secrets.APPLE_NOTARIZE_APPLE_ID }}
|
|
||||||
APPLE_NOTARIZE_TEAM_ID: ${{ secrets.APPLE_NOTARIZE_TEAM_ID }}
|
|
||||||
APPLE_NOTARIZE_PASSWORD: ${{ secrets.APPLE_NOTARIZE_PASSWORD }}
|
|
||||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
|
||||||
GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}
|
|
4
.github/workflows/update-flake.yml
vendored
4
.github/workflows/update-flake.yml
vendored
|
@ -17,9 +17,9 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@754537aaedb35f72ab11a60cc162c49ef3016495 # v31
|
- uses: cachix/install-nix-action@17fe5fb4a23ad6cbbe47d6b3f359611ad276644c # v31
|
||||||
|
|
||||||
- uses: DeterminateSystems/update-flake-lock@v24
|
- uses: DeterminateSystems/update-flake-lock@v25
|
||||||
with:
|
with:
|
||||||
commit-msg: "chore(nix): update lockfile"
|
commit-msg: "chore(nix): update lockfile"
|
||||||
pr-title: "chore(nix): update lockfile"
|
pr-title: "chore(nix): update lockfile"
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -14,6 +14,7 @@ CMakeLists.txt.user.*
|
||||||
CMakeSettings.json
|
CMakeSettings.json
|
||||||
/CMakeFiles
|
/CMakeFiles
|
||||||
CMakeCache.txt
|
CMakeCache.txt
|
||||||
|
CMakeUserPresets.json
|
||||||
/.project
|
/.project
|
||||||
/.settings
|
/.settings
|
||||||
/.idea
|
/.idea
|
||||||
|
|
4
.gitmodules
vendored
4
.gitmodules
vendored
|
@ -19,6 +19,6 @@
|
||||||
[submodule "flatpak/shared-modules"]
|
[submodule "flatpak/shared-modules"]
|
||||||
path = flatpak/shared-modules
|
path = flatpak/shared-modules
|
||||||
url = https://github.com/flathub/shared-modules.git
|
url = https://github.com/flathub/shared-modules.git
|
||||||
[submodule "libraries/qt-qrcodegenerator/QR-Code-generator"]
|
[submodule "libraries/qrcodegenerator"]
|
||||||
path = libraries/qt-qrcodegenerator/QR-Code-generator
|
path = libraries/qrcodegenerator
|
||||||
url = https://github.com/nayuki/QR-Code-generator
|
url = https://github.com/nayuki/QR-Code-generator
|
||||||
|
|
|
@ -475,7 +475,6 @@ add_subdirectory(libraries/libnbtplusplus)
|
||||||
add_subdirectory(libraries/systeminfo) # system information library
|
add_subdirectory(libraries/systeminfo) # system information library
|
||||||
add_subdirectory(libraries/launcher) # java based launcher part for Minecraft
|
add_subdirectory(libraries/launcher) # java based launcher part for Minecraft
|
||||||
add_subdirectory(libraries/javacheck) # java compatibility checker
|
add_subdirectory(libraries/javacheck) # java compatibility checker
|
||||||
add_subdirectory(libraries/qt-qrcodegenerator) # qr code generator
|
|
||||||
if(FORCE_BUNDLED_ZLIB)
|
if(FORCE_BUNDLED_ZLIB)
|
||||||
message(STATUS "Using bundled zlib")
|
message(STATUS "Using bundled zlib")
|
||||||
|
|
||||||
|
@ -533,6 +532,15 @@ add_subdirectory(libraries/gamemode)
|
||||||
add_subdirectory(libraries/murmur2) # Hash for usage with the CurseForge API
|
add_subdirectory(libraries/murmur2) # Hash for usage with the CurseForge API
|
||||||
add_subdirectory(libraries/qdcss) # css parser
|
add_subdirectory(libraries/qdcss) # css parser
|
||||||
|
|
||||||
|
# qr code generator
|
||||||
|
set(QRCODE_SOURCES
|
||||||
|
libraries/qrcodegenerator/cpp/qrcodegen.cpp
|
||||||
|
libraries/qrcodegenerator/cpp/qrcodegen.hpp
|
||||||
|
)
|
||||||
|
add_library(qrcodegenerator STATIC ${QRCODE_SOURCES})
|
||||||
|
target_include_directories(qrcodegenerator PUBLIC "libraries/qrcodegenerator/cpp/" )
|
||||||
|
generate_export_header(qrcodegenerator)
|
||||||
|
|
||||||
############################### Built Artifacts ###############################
|
############################### Built Artifacts ###############################
|
||||||
|
|
||||||
add_subdirectory(buildconfig)
|
add_subdirectory(buildconfig)
|
||||||
|
|
14
CMakePresets.json
Normal file
14
CMakePresets.json
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://cmake.org/cmake/help/latest/_downloads/3e2d73bff478d88a7de0de736ba5e361/schema.json",
|
||||||
|
"version": 8,
|
||||||
|
"cmakeMinimumRequired": {
|
||||||
|
"major": 3,
|
||||||
|
"minor": 28
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"cmake/linuxPreset.json",
|
||||||
|
"cmake/macosPreset.json",
|
||||||
|
"cmake/windowsMinGWPreset.json",
|
||||||
|
"cmake/windowsMSVCPreset.json"
|
||||||
|
]
|
||||||
|
}
|
|
@ -404,7 +404,7 @@
|
||||||
You should have received a copy of the GNU Lesser General Public
|
You should have received a copy of the GNU Lesser General Public
|
||||||
License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
## qt-qrcodegenerator (`libraries/qt-qrcodegenerator`)
|
## QR-Code-generator (`libraries/qrcodegenerator`)
|
||||||
|
|
||||||
Copyright © 2024 Project Nayuki. (MIT License)
|
Copyright © 2024 Project Nayuki. (MIT License)
|
||||||
https://www.nayuki.io/page/qr-code-generator-library
|
https://www.nayuki.io/page/qr-code-generator-library
|
||||||
|
|
|
@ -76,7 +76,13 @@ We thank all the wonderful backers over at Open Collective! Support Prism Launch
|
||||||
|
|
||||||
Thanks to JetBrains for providing us a few licenses for all their products, as part of their [Open Source program](https://www.jetbrains.com/opensource/).
|
Thanks to JetBrains for providing us a few licenses for all their products, as part of their [Open Source program](https://www.jetbrains.com/opensource/).
|
||||||
|
|
||||||
[](https://www.jetbrains.com/opensource/)
|
<a href="https://jb.gg/OpenSource">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://www.jetbrains.com/company/brand/img/logo_jb_dos_4.svg">
|
||||||
|
<source media="(prefers-color-scheme: light)" srcset="https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg">
|
||||||
|
<img alt="JetBrains logo" src="https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg" width="40%">
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
|
||||||
Thanks to Weblate for hosting our translation efforts.
|
Thanks to Weblate for hosting our translation efforts.
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,6 @@ Config::Config()
|
||||||
LAUNCHER_SVGFILENAME = "@Launcher_SVGFileName@";
|
LAUNCHER_SVGFILENAME = "@Launcher_SVGFileName@";
|
||||||
|
|
||||||
USER_AGENT = "@Launcher_UserAgent@";
|
USER_AGENT = "@Launcher_UserAgent@";
|
||||||
USER_AGENT_UNCACHED = USER_AGENT + " (Uncached)";
|
|
||||||
|
|
||||||
// Version information
|
// Version information
|
||||||
VERSION_MAJOR = @Launcher_VERSION_MAJOR@;
|
VERSION_MAJOR = @Launcher_VERSION_MAJOR@;
|
||||||
|
|
|
@ -107,9 +107,6 @@ class Config {
|
||||||
/// User-Agent to use.
|
/// User-Agent to use.
|
||||||
QString USER_AGENT;
|
QString USER_AGENT;
|
||||||
|
|
||||||
/// User-Agent to use for uncached requests.
|
|
||||||
QString USER_AGENT_UNCACHED;
|
|
||||||
|
|
||||||
/// The git commit hash of this build
|
/// The git commit hash of this build
|
||||||
QString GIT_COMMIT;
|
QString GIT_COMMIT;
|
||||||
|
|
||||||
|
|
81
cmake/commonPresets.json
Normal file
81
cmake/commonPresets.json
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://cmake.org/cmake/help/latest/_downloads/3e2d73bff478d88a7de0de736ba5e361/schema.json",
|
||||||
|
"version": 8,
|
||||||
|
"configurePresets": [
|
||||||
|
{
|
||||||
|
"name": "base",
|
||||||
|
"hidden": true,
|
||||||
|
"binaryDir": "build",
|
||||||
|
"installDir": "install",
|
||||||
|
"cacheVariables": {
|
||||||
|
"Launcher_BUILD_PLATFORM": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "base_debug",
|
||||||
|
"hidden": true,
|
||||||
|
"inherits": [
|
||||||
|
"base"
|
||||||
|
],
|
||||||
|
"cacheVariables": {
|
||||||
|
"CMAKE_BUILD_TYPE": "Debug"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "base_release",
|
||||||
|
"hidden": true,
|
||||||
|
"inherits": [
|
||||||
|
"base"
|
||||||
|
],
|
||||||
|
"cacheVariables": {
|
||||||
|
"CMAKE_BUILD_TYPE": "Release",
|
||||||
|
"ENABLE_LTO": "ON"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "base_ci",
|
||||||
|
"hidden": true,
|
||||||
|
"inherits": [
|
||||||
|
"base_release"
|
||||||
|
],
|
||||||
|
"cacheVariables": {
|
||||||
|
"Launcher_BUILD_PLATFORM": "official",
|
||||||
|
"Launcher_FORCE_BUNDLED_LIBS": "ON"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"testPresets": [
|
||||||
|
{
|
||||||
|
"name": "base",
|
||||||
|
"hidden": true,
|
||||||
|
"output": {
|
||||||
|
"outputOnFailure": true
|
||||||
|
},
|
||||||
|
"execution": {
|
||||||
|
"noTestsAction": "error"
|
||||||
|
},
|
||||||
|
"filter": {
|
||||||
|
"exclude": {
|
||||||
|
"name": "^example64|example$"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "base_debug",
|
||||||
|
"hidden": true,
|
||||||
|
"inherits": [
|
||||||
|
"base"
|
||||||
|
],
|
||||||
|
"output": {
|
||||||
|
"debug": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "base_release",
|
||||||
|
"hidden": true,
|
||||||
|
"inherits": [
|
||||||
|
"base"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
180
cmake/linuxPreset.json
Normal file
180
cmake/linuxPreset.json
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://cmake.org/cmake/help/latest/_downloads/3e2d73bff478d88a7de0de736ba5e361/schema.json",
|
||||||
|
"version": 8,
|
||||||
|
"include": [
|
||||||
|
"commonPresets.json"
|
||||||
|
],
|
||||||
|
"configurePresets": [
|
||||||
|
{
|
||||||
|
"name": "linux_base",
|
||||||
|
"hidden": true,
|
||||||
|
"condition": {
|
||||||
|
"type": "equals",
|
||||||
|
"lhs": "${hostSystemName}",
|
||||||
|
"rhs": "Linux"
|
||||||
|
},
|
||||||
|
"generator": "Ninja",
|
||||||
|
"cacheVariables": {
|
||||||
|
"Launcher_BUILD_ARTIFACT": "Linux-Qt6",
|
||||||
|
"Launcher_ENABLE_JAVA_DOWNLOADER": "ON"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "linux_debug",
|
||||||
|
"inherits": [
|
||||||
|
"base_debug",
|
||||||
|
"linux_base"
|
||||||
|
],
|
||||||
|
"displayName": "Linux (Debug)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "linux_release",
|
||||||
|
"inherits": [
|
||||||
|
"base_release",
|
||||||
|
"linux_base"
|
||||||
|
],
|
||||||
|
"displayName": "Linux (Release)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "linux_ci",
|
||||||
|
"inherits": [
|
||||||
|
"base_ci",
|
||||||
|
"linux_base"
|
||||||
|
],
|
||||||
|
"displayName": "Linux (CI)",
|
||||||
|
"cacheVariables": {
|
||||||
|
"Launcher_BUILD_ARTIFACT": "Linux-Qt6"
|
||||||
|
},
|
||||||
|
"installDir": "/usr"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"buildPresets": [
|
||||||
|
{
|
||||||
|
"name": "linux_base",
|
||||||
|
"hidden": true,
|
||||||
|
"condition": {
|
||||||
|
"type": "equals",
|
||||||
|
"lhs": "${hostSystemName}",
|
||||||
|
"rhs": "Linux"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "linux_debug",
|
||||||
|
"inherits": [
|
||||||
|
"linux_base"
|
||||||
|
],
|
||||||
|
"displayName": "Linux (Debug)",
|
||||||
|
"configurePreset": "linux_debug"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "linux_release",
|
||||||
|
"inherits": [
|
||||||
|
"linux_base"
|
||||||
|
],
|
||||||
|
"displayName": "Linux (Release)",
|
||||||
|
"configurePreset": "linux_release"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "linux_ci",
|
||||||
|
"inherits": [
|
||||||
|
"linux_base"
|
||||||
|
],
|
||||||
|
"displayName": "Linux (CI)",
|
||||||
|
"configurePreset": "linux_ci"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"testPresets": [
|
||||||
|
{
|
||||||
|
"name": "linux_base",
|
||||||
|
"hidden": true,
|
||||||
|
"condition": {
|
||||||
|
"type": "equals",
|
||||||
|
"lhs": "${hostSystemName}",
|
||||||
|
"rhs": "Linux"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "linux_debug",
|
||||||
|
"inherits": [
|
||||||
|
"base_debug",
|
||||||
|
"linux_base"
|
||||||
|
],
|
||||||
|
"displayName": "Linux (Debug)",
|
||||||
|
"configurePreset": "linux_debug"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "linux_release",
|
||||||
|
"inherits": [
|
||||||
|
"base_release",
|
||||||
|
"linux_base"
|
||||||
|
],
|
||||||
|
"displayName": "Linux (Release)",
|
||||||
|
"configurePreset": "linux_release"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "linux_ci",
|
||||||
|
"inherits": [
|
||||||
|
"base_release",
|
||||||
|
"linux_base"
|
||||||
|
],
|
||||||
|
"displayName": "Linux (CI)",
|
||||||
|
"configurePreset": "linux_ci"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"workflowPresets": [
|
||||||
|
{
|
||||||
|
"name": "linux_debug",
|
||||||
|
"displayName": "Linux (Debug)",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"type": "configure",
|
||||||
|
"name": "linux_debug"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"name": "linux_debug"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "test",
|
||||||
|
"name": "linux_debug"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "linux",
|
||||||
|
"displayName": "Linux (Release)",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"type": "configure",
|
||||||
|
"name": "linux_release"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"name": "linux_release"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "test",
|
||||||
|
"name": "linux_release"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "linux_ci",
|
||||||
|
"displayName": "Linux (CI)",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"type": "configure",
|
||||||
|
"name": "linux_ci"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"name": "linux_ci"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "test",
|
||||||
|
"name": "linux_ci"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
272
cmake/macosPreset.json
Normal file
272
cmake/macosPreset.json
Normal file
|
@ -0,0 +1,272 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://cmake.org/cmake/help/latest/_downloads/3e2d73bff478d88a7de0de736ba5e361/schema.json",
|
||||||
|
"version": 8,
|
||||||
|
"include": [
|
||||||
|
"commonPresets.json"
|
||||||
|
],
|
||||||
|
"configurePresets": [
|
||||||
|
{
|
||||||
|
"name": "macos_base",
|
||||||
|
"hidden": true,
|
||||||
|
"condition": {
|
||||||
|
"type": "equals",
|
||||||
|
"lhs": "${hostSystemName}",
|
||||||
|
"rhs": "Darwin"
|
||||||
|
},
|
||||||
|
"generator": "Ninja"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "macos_universal_base",
|
||||||
|
"hidden": true,
|
||||||
|
"inherits": [
|
||||||
|
"macos_base"
|
||||||
|
],
|
||||||
|
"cacheVariables": {
|
||||||
|
"CMAKE_OSX_ARCHITECTURES": "x86_64;arm64",
|
||||||
|
"Launcher_BUILD_ARTIFACT": "macOS-Qt6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "macos_debug",
|
||||||
|
"inherits": [
|
||||||
|
"base_debug",
|
||||||
|
"macos_base"
|
||||||
|
],
|
||||||
|
"displayName": "macOS (Debug)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "macos_release",
|
||||||
|
"inherits": [
|
||||||
|
"base_release",
|
||||||
|
"macos_base"
|
||||||
|
],
|
||||||
|
"displayName": "macOS (Release)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "macos_universal_debug",
|
||||||
|
"inherits": [
|
||||||
|
"base_debug",
|
||||||
|
"macos_universal_base"
|
||||||
|
],
|
||||||
|
"displayName": "macOS (Universal Binary, Debug)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "macos_universal_release",
|
||||||
|
"inherits": [
|
||||||
|
"base_release",
|
||||||
|
"macos_universal_base"
|
||||||
|
],
|
||||||
|
"displayName": "macOS (Universal Binary, Release)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "macos_ci",
|
||||||
|
"inherits": [
|
||||||
|
"base_ci",
|
||||||
|
"macos_universal_base"
|
||||||
|
],
|
||||||
|
"displayName": "macOS (CI)",
|
||||||
|
"cacheVariables": {
|
||||||
|
"Launcher_BUILD_ARTIFACT": "macOS-Qt6"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"buildPresets": [
|
||||||
|
{
|
||||||
|
"name": "macos_base",
|
||||||
|
"hidden": true,
|
||||||
|
"condition": {
|
||||||
|
"type": "equals",
|
||||||
|
"lhs": "${hostSystemName}",
|
||||||
|
"rhs": "Darwin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "macos_debug",
|
||||||
|
"inherits": [
|
||||||
|
"macos_base"
|
||||||
|
],
|
||||||
|
"displayName": "macOS (Debug)",
|
||||||
|
"configurePreset": "macos_debug"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "macos_release",
|
||||||
|
"inherits": [
|
||||||
|
"macos_base"
|
||||||
|
],
|
||||||
|
"displayName": "macOS (Release)",
|
||||||
|
"configurePreset": "macos_release"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "macos_universal_debug",
|
||||||
|
"inherits": [
|
||||||
|
"macos_base"
|
||||||
|
],
|
||||||
|
"displayName": "macOS (Universal Binary, Debug)",
|
||||||
|
"configurePreset": "macos_universal_debug"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "macos_universal_release",
|
||||||
|
"inherits": [
|
||||||
|
"macos_base"
|
||||||
|
],
|
||||||
|
"displayName": "macOS (Universal Binary, Release)",
|
||||||
|
"configurePreset": "macos_universal_release"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "macos_ci",
|
||||||
|
"inherits": [
|
||||||
|
"macos_base"
|
||||||
|
],
|
||||||
|
"displayName": "macOS (CI)",
|
||||||
|
"configurePreset": "macos_ci"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"testPresets": [
|
||||||
|
{
|
||||||
|
"name": "macos_base",
|
||||||
|
"hidden": true,
|
||||||
|
"condition": {
|
||||||
|
"type": "equals",
|
||||||
|
"lhs": "${hostSystemName}",
|
||||||
|
"rhs": "Darwin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "macos_debug",
|
||||||
|
"inherits": [
|
||||||
|
"base_debug",
|
||||||
|
"macos_base"
|
||||||
|
],
|
||||||
|
"displayName": "MacOS (Debug)",
|
||||||
|
"configurePreset": "macos_debug"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "macos_release",
|
||||||
|
"inherits": [
|
||||||
|
"base_release",
|
||||||
|
"macos_base"
|
||||||
|
],
|
||||||
|
"displayName": "macOS (Release)",
|
||||||
|
"configurePreset": "macos_release"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "macos_universal_debug",
|
||||||
|
"inherits": [
|
||||||
|
"base_debug",
|
||||||
|
"macos_base"
|
||||||
|
],
|
||||||
|
"displayName": "MacOS (Universal Binary, Debug)",
|
||||||
|
"configurePreset": "macos_universal_debug"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "macos_universal_release",
|
||||||
|
"inherits": [
|
||||||
|
"base_release",
|
||||||
|
"macos_base"
|
||||||
|
],
|
||||||
|
"displayName": "macOS (Universal Binary, Release)",
|
||||||
|
"configurePreset": "macos_universal_release"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "macos_ci",
|
||||||
|
"inherits": [
|
||||||
|
"base_release",
|
||||||
|
"macos_base"
|
||||||
|
],
|
||||||
|
"displayName": "macOS (CI)",
|
||||||
|
"configurePreset": "macos_ci"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"workflowPresets": [
|
||||||
|
{
|
||||||
|
"name": "macos_debug",
|
||||||
|
"displayName": "macOS (Debug)",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"type": "configure",
|
||||||
|
"name": "macos_debug"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"name": "macos_debug"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "test",
|
||||||
|
"name": "macos_debug"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "macos",
|
||||||
|
"displayName": "macOS (Release)",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"type": "configure",
|
||||||
|
"name": "macos_release"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"name": "macos_release"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "test",
|
||||||
|
"name": "macos_release"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "macos_universal_debug",
|
||||||
|
"displayName": "macOS (Universal Binary, Debug)",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"type": "configure",
|
||||||
|
"name": "macos_universal_debug"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"name": "macos_universal_debug"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "test",
|
||||||
|
"name": "macos_universal_debug"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "macos_universal",
|
||||||
|
"displayName": "macOS (Universal Binary, Release)",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"type": "configure",
|
||||||
|
"name": "macos_universal_release"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"name": "macos_universal_release"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "test",
|
||||||
|
"name": "macos_universal_release"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "macos_ci",
|
||||||
|
"displayName": "macOS (CI)",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"type": "configure",
|
||||||
|
"name": "macos_ci"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"name": "macos_ci"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "test",
|
||||||
|
"name": "macos_ci"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
311
cmake/windowsMSVCPreset.json
Normal file
311
cmake/windowsMSVCPreset.json
Normal file
|
@ -0,0 +1,311 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://cmake.org/cmake/help/latest/_downloads/3e2d73bff478d88a7de0de736ba5e361/schema.json",
|
||||||
|
"version": 8,
|
||||||
|
"include": [
|
||||||
|
"commonPresets.json"
|
||||||
|
],
|
||||||
|
"configurePresets": [
|
||||||
|
{
|
||||||
|
"name": "windows_msvc_base",
|
||||||
|
"hidden": true,
|
||||||
|
"condition": {
|
||||||
|
"type": "equals",
|
||||||
|
"lhs": "${hostSystemName}",
|
||||||
|
"rhs": "Windows"
|
||||||
|
},
|
||||||
|
"cacheVariables": {
|
||||||
|
"Launcher_BUILD_ARTIFACT": "Windows-MSVC-Qt6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "windows_msvc_arm64_cross_base",
|
||||||
|
"hidden": true,
|
||||||
|
"inherits": [
|
||||||
|
"windows_msvc_base"
|
||||||
|
],
|
||||||
|
"architecture": "arm64",
|
||||||
|
"cacheVariables": {
|
||||||
|
"Launcher_BUILD_ARTIFACT": "Windows-MSVC-arm64-Qt6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "windows_msvc_debug",
|
||||||
|
"inherits": [
|
||||||
|
"base_debug",
|
||||||
|
"windows_msvc_base"
|
||||||
|
],
|
||||||
|
"displayName": "Windows MSVC (Debug)",
|
||||||
|
"generator": "Ninja"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "windows_msvc_release",
|
||||||
|
"inherits": [
|
||||||
|
"base_release",
|
||||||
|
"windows_msvc_base"
|
||||||
|
],
|
||||||
|
"displayName": "Windows MSVC (Release)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "windows_msvc_arm64_cross_debug",
|
||||||
|
"inherits": [
|
||||||
|
"base_debug",
|
||||||
|
"windows_msvc_arm64_cross_base"
|
||||||
|
],
|
||||||
|
"displayName": "Windows MSVC (ARM64 cross, Debug)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "windows_msvc_arm64_cross_release",
|
||||||
|
"inherits": [
|
||||||
|
"base_release",
|
||||||
|
"windows_msvc_arm64_cross_base"
|
||||||
|
],
|
||||||
|
"displayName": "Windows MSVC (ARM64 cross, Release)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "windows_msvc_ci",
|
||||||
|
"inherits": [
|
||||||
|
"base_ci",
|
||||||
|
"windows_msvc_base"
|
||||||
|
],
|
||||||
|
"displayName": "Windows MSVC (CI)",
|
||||||
|
"cacheVariables": {
|
||||||
|
"Launcher_BUILD_ARTIFACT": "Windows-MSVC-Qt6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "windows_msvc_arm64_cross_ci",
|
||||||
|
"inherits": [
|
||||||
|
"base_ci",
|
||||||
|
"windows_msvc_arm64_cross_base"
|
||||||
|
],
|
||||||
|
"displayName": "Windows MSVC (ARM64 cross, CI)",
|
||||||
|
"cacheVariables": {
|
||||||
|
"Launcher_BUILD_ARTIFACT": "Windows-MSVC-arm64-Qt6"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"buildPresets": [
|
||||||
|
{
|
||||||
|
"name": "windows_msvc_base",
|
||||||
|
"hidden": true,
|
||||||
|
"condition": {
|
||||||
|
"type": "equals",
|
||||||
|
"lhs": "${hostSystemName}",
|
||||||
|
"rhs": "Windows"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "windows_msvc_debug",
|
||||||
|
"inherits": [
|
||||||
|
"windows_msvc_base"
|
||||||
|
],
|
||||||
|
"displayName": "Windows MSVC (Debug)",
|
||||||
|
"configurePreset": "windows_msvc_debug",
|
||||||
|
"configuration": "Debug"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "windows_msvc_release",
|
||||||
|
"inherits": [
|
||||||
|
"windows_msvc_base"
|
||||||
|
],
|
||||||
|
"displayName": "Windows MSVC (Release)",
|
||||||
|
"configurePreset": "windows_msvc_release",
|
||||||
|
"configuration": "Release",
|
||||||
|
"nativeToolOptions": [
|
||||||
|
"/p:UseMultiToolTask=true",
|
||||||
|
"/p:EnforceProcessCountAcrossBuilds=true"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "windows_msvc_arm64_cross_debug",
|
||||||
|
"inherits": [
|
||||||
|
"windows_msvc_base"
|
||||||
|
],
|
||||||
|
"displayName": "Windows MSVC (ARM64 cross, Debug)",
|
||||||
|
"configurePreset": "windows_msvc_arm64_cross_debug",
|
||||||
|
"configuration": "Debug",
|
||||||
|
"nativeToolOptions": [
|
||||||
|
"/p:UseMultiToolTask=true",
|
||||||
|
"/p:EnforceProcessCountAcrossBuilds=true"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "windows_msvc_arm64_cross_release",
|
||||||
|
"inherits": [
|
||||||
|
"windows_msvc_base"
|
||||||
|
],
|
||||||
|
"displayName": "Windows MSVC (ARM64 cross, Release)",
|
||||||
|
"configurePreset": "windows_msvc_arm64_cross_release",
|
||||||
|
"configuration": "Release",
|
||||||
|
"nativeToolOptions": [
|
||||||
|
"/p:UseMultiToolTask=true",
|
||||||
|
"/p:EnforceProcessCountAcrossBuilds=true"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "windows_msvc_ci",
|
||||||
|
"inherits": [
|
||||||
|
"windows_msvc_base"
|
||||||
|
],
|
||||||
|
"displayName": "Windows MSVC (CI)",
|
||||||
|
"configurePreset": "windows_msvc_ci",
|
||||||
|
"configuration": "Release",
|
||||||
|
"nativeToolOptions": [
|
||||||
|
"/p:UseMultiToolTask=true",
|
||||||
|
"/p:EnforceProcessCountAcrossBuilds=true"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "windows_msvc_arm64_cross_ci",
|
||||||
|
"inherits": [
|
||||||
|
"windows_msvc_base"
|
||||||
|
],
|
||||||
|
"displayName": "Windows MSVC (ARM64 cross, CI)",
|
||||||
|
"configurePreset": "windows_msvc_arm64_cross_ci",
|
||||||
|
"configuration": "Release",
|
||||||
|
"nativeToolOptions": [
|
||||||
|
"/p:UseMultiToolTask=true",
|
||||||
|
"/p:EnforceProcessCountAcrossBuilds=true"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"testPresets": [
|
||||||
|
{
|
||||||
|
"name": "windows_msvc_base",
|
||||||
|
"hidden": true,
|
||||||
|
"condition": {
|
||||||
|
"type": "equals",
|
||||||
|
"lhs": "${hostSystemName}",
|
||||||
|
"rhs": "Windows"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "windows_msvc_debug",
|
||||||
|
"inherits": [
|
||||||
|
"base_debug",
|
||||||
|
"windows_msvc_base"
|
||||||
|
],
|
||||||
|
"displayName": "Windows MSVC (Debug)",
|
||||||
|
"configurePreset": "windows_msvc_debug",
|
||||||
|
"configuration": "Debug"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "windows_msvc_release",
|
||||||
|
"inherits": [
|
||||||
|
"base_release",
|
||||||
|
"windows_msvc_base"
|
||||||
|
],
|
||||||
|
"displayName": "Windows MSVC (Release)",
|
||||||
|
"configurePreset": "windows_msvc_release",
|
||||||
|
"configuration": "Release"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "windows_msvc_ci",
|
||||||
|
"inherits": [
|
||||||
|
"base_release",
|
||||||
|
"windows_msvc_base"
|
||||||
|
],
|
||||||
|
"displayName": "Windows MSVC (CI)",
|
||||||
|
"configurePreset": "windows_msvc_ci",
|
||||||
|
"configuration": "Release"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"workflowPresets": [
|
||||||
|
{
|
||||||
|
"name": "windows_msvc_debug",
|
||||||
|
"displayName": "Windows MSVC (Debug)",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"type": "configure",
|
||||||
|
"name": "windows_msvc_debug"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"name": "windows_msvc_debug"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "test",
|
||||||
|
"name": "windows_msvc_debug"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "windows_msvc",
|
||||||
|
"displayName": "Windows MSVC (Release)",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"type": "configure",
|
||||||
|
"name": "windows_msvc_release"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"name": "windows_msvc_release"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "test",
|
||||||
|
"name": "windows_msvc_release"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "windows_msvc_arm64_cross_debug",
|
||||||
|
"displayName": "Windows MSVC (ARM64 cross, Debug)",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"type": "configure",
|
||||||
|
"name": "windows_msvc_arm64_cross_debug"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"name": "windows_msvc_arm64_cross_debug"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "windows_msvc_arm64_cross",
|
||||||
|
"displayName": "Windows MSVC (ARM64 cross, Release)",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"type": "configure",
|
||||||
|
"name": "windows_msvc_arm64_cross_release"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"name": "windows_msvc_arm64_cross_release"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "windows_msvc_ci",
|
||||||
|
"displayName": "Windows MSVC (CI)",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"type": "configure",
|
||||||
|
"name": "windows_msvc_ci"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"name": "windows_msvc_ci"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "test",
|
||||||
|
"name": "windows_msvc_ci"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "windows_msvc_arm64_cross_ci",
|
||||||
|
"displayName": "Windows MSVC (ARM64 cross, CI)",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"type": "configure",
|
||||||
|
"name": "windows_msvc_arm64_cross_ci"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"name": "windows_msvc_arm64_cross_ci"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
183
cmake/windowsMinGWPreset.json
Normal file
183
cmake/windowsMinGWPreset.json
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://cmake.org/cmake/help/latest/_downloads/3e2d73bff478d88a7de0de736ba5e361/schema.json",
|
||||||
|
"version": 8,
|
||||||
|
"include": [
|
||||||
|
"commonPresets.json"
|
||||||
|
],
|
||||||
|
"configurePresets": [
|
||||||
|
{
|
||||||
|
"name": "windows_mingw_base",
|
||||||
|
"hidden": true,
|
||||||
|
"condition": {
|
||||||
|
"type": "equals",
|
||||||
|
"lhs": "${hostSystemName}",
|
||||||
|
"rhs": "Windows"
|
||||||
|
},
|
||||||
|
"generator": "Ninja",
|
||||||
|
"cacheVariables": {
|
||||||
|
"Launcher_BUILD_ARTIFACT": "Windows-MinGW-w64-Qt6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "windows_mingw_debug",
|
||||||
|
"inherits": [
|
||||||
|
"base_debug",
|
||||||
|
"windows_mingw_base"
|
||||||
|
],
|
||||||
|
"displayName": "Windows MinGW (Debug)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "windows_mingw_release",
|
||||||
|
"inherits": [
|
||||||
|
"base_release",
|
||||||
|
"windows_mingw_base"
|
||||||
|
],
|
||||||
|
"displayName": "Windows MinGW (Release)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "windows_mingw_ci",
|
||||||
|
"inherits": [
|
||||||
|
"base_ci",
|
||||||
|
"windows_mingw_base"
|
||||||
|
],
|
||||||
|
"displayName": "Windows MinGW (CI)",
|
||||||
|
"cacheVariables": {
|
||||||
|
"Launcher_BUILD_ARTIFACT": "Windows-MinGW-w64-Qt6"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"buildPresets": [
|
||||||
|
{
|
||||||
|
"name": "windows_mingw_base",
|
||||||
|
"hidden": true,
|
||||||
|
"condition": {
|
||||||
|
"type": "equals",
|
||||||
|
"lhs": "${hostSystemName}",
|
||||||
|
"rhs": "Windows"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "windows_mingw_debug",
|
||||||
|
"inherits": [
|
||||||
|
"windows_mingw_base"
|
||||||
|
],
|
||||||
|
"displayName": "Windows MinGW (Debug)",
|
||||||
|
"configurePreset": "windows_mingw_debug"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "windows_mingw_release",
|
||||||
|
"inherits": [
|
||||||
|
"windows_mingw_base"
|
||||||
|
],
|
||||||
|
"displayName": "Windows MinGW (Release)",
|
||||||
|
"configurePreset": "windows_mingw_release"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "windows_mingw_ci",
|
||||||
|
"inherits": [
|
||||||
|
"windows_mingw_base"
|
||||||
|
],
|
||||||
|
"displayName": "Windows MinGW (CI)",
|
||||||
|
"configurePreset": "windows_mingw_ci"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"testPresets": [
|
||||||
|
{
|
||||||
|
"name": "windows_mingw_base",
|
||||||
|
"hidden": true,
|
||||||
|
"condition": {
|
||||||
|
"type": "equals",
|
||||||
|
"lhs": "${hostSystemName}",
|
||||||
|
"rhs": "Windows"
|
||||||
|
},
|
||||||
|
"filter": {
|
||||||
|
"exclude": {
|
||||||
|
"name": "^example64|example$"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "windows_mingw_debug",
|
||||||
|
"inherits": [
|
||||||
|
"base_debug",
|
||||||
|
"windows_mingw_base"
|
||||||
|
],
|
||||||
|
"displayName": "Windows MinGW (Debug)",
|
||||||
|
"configurePreset": "windows_mingw_debug"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "windows_mingw_release",
|
||||||
|
"inherits": [
|
||||||
|
"base_release",
|
||||||
|
"windows_mingw_base"
|
||||||
|
],
|
||||||
|
"displayName": "Windows MinGW (Release)",
|
||||||
|
"configurePreset": "windows_mingw_release"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "windows_mingw_ci",
|
||||||
|
"inherits": [
|
||||||
|
"base_release",
|
||||||
|
"windows_mingw_base"
|
||||||
|
],
|
||||||
|
"displayName": "Windows MinGW (CI)",
|
||||||
|
"configurePreset": "windows_mingw_ci"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"workflowPresets": [
|
||||||
|
{
|
||||||
|
"name": "windows_mingw_debug",
|
||||||
|
"displayName": "Windows MinGW (Debug)",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"type": "configure",
|
||||||
|
"name": "windows_mingw_debug"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"name": "windows_mingw_debug"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "test",
|
||||||
|
"name": "windows_mingw_debug"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "windows_mingw",
|
||||||
|
"displayName": "Windows MinGW (Release)",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"type": "configure",
|
||||||
|
"name": "windows_mingw_release"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"name": "windows_mingw_release"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "test",
|
||||||
|
"name": "windows_mingw_release"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "windows_mingw_ci",
|
||||||
|
"displayName": "Windows MinGW (CI)",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"type": "configure",
|
||||||
|
"name": "windows_mingw_ci"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"name": "windows_mingw_ci"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "test",
|
||||||
|
"name": "windows_mingw_ci"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
10
flake.lock
generated
10
flake.lock
generated
|
@ -18,11 +18,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1744932701,
|
"lastModified": 1748460289,
|
||||||
"narHash": "sha256-fusHbZCyv126cyArUwwKrLdCkgVAIaa/fQJYFlCEqiU=",
|
"narHash": "sha256-7doLyJBzCllvqX4gszYtmZUToxKvMUrg45EUWaUYmBg=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "b024ced1aac25639f8ca8fdfc2f8c4fbd66c48ef",
|
"rev": "96ec055edbe5ee227f28cdbc3f1ddf1df5965102",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -32,7 +32,7 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"qt-qrcodegenerator": {
|
"qrcodegenerator": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1737616857,
|
"lastModified": 1737616857,
|
||||||
|
@ -52,7 +52,7 @@
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"libnbtplusplus": "libnbtplusplus",
|
"libnbtplusplus": "libnbtplusplus",
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs",
|
||||||
"qt-qrcodegenerator": "qt-qrcodegenerator"
|
"qrcodegenerator": "qrcodegenerator"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
flake = false;
|
flake = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
qt-qrcodegenerator = {
|
qrcodegenerator = {
|
||||||
url = "github:nayuki/QR-Code-generator";
|
url = "github:nayuki/QR-Code-generator";
|
||||||
flake = false;
|
flake = false;
|
||||||
};
|
};
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
self,
|
self,
|
||||||
nixpkgs,
|
nixpkgs,
|
||||||
libnbtplusplus,
|
libnbtplusplus,
|
||||||
qt-qrcodegenerator,
|
qrcodegenerator,
|
||||||
}:
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
|
@ -175,7 +175,7 @@
|
||||||
prismlauncher-unwrapped = prev.callPackage ./nix/unwrapped.nix {
|
prismlauncher-unwrapped = prev.callPackage ./nix/unwrapped.nix {
|
||||||
inherit
|
inherit
|
||||||
libnbtplusplus
|
libnbtplusplus
|
||||||
qt-qrcodegenerator
|
qrcodegenerator
|
||||||
self
|
self
|
||||||
;
|
;
|
||||||
};
|
};
|
||||||
|
|
|
@ -128,6 +128,7 @@
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <sys.h>
|
#include <sys.h>
|
||||||
|
#include <QStringLiteral>
|
||||||
#include "SysInfo.h"
|
#include "SysInfo.h"
|
||||||
|
|
||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
|
@ -1887,17 +1888,6 @@ QString Application::getUserAgent()
|
||||||
return BuildConfig.USER_AGENT;
|
return BuildConfig.USER_AGENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Application::getUserAgentUncached()
|
|
||||||
{
|
|
||||||
QString uaOverride = m_settings->get("UserAgentOverride").toString();
|
|
||||||
if (!uaOverride.isEmpty()) {
|
|
||||||
uaOverride += " (Uncached)";
|
|
||||||
return uaOverride.replace("$LAUNCHER_VER", BuildConfig.printableVersionString());
|
|
||||||
}
|
|
||||||
|
|
||||||
return BuildConfig.USER_AGENT_UNCACHED;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Application::handleDataMigration(const QString& currentData,
|
bool Application::handleDataMigration(const QString& currentData,
|
||||||
const QString& oldData,
|
const QString& oldData,
|
||||||
const QString& name,
|
const QString& name,
|
||||||
|
|
|
@ -160,7 +160,6 @@ class Application : public QApplication {
|
||||||
QString getFlameAPIKey();
|
QString getFlameAPIKey();
|
||||||
QString getModrinthAPIToken();
|
QString getModrinthAPIToken();
|
||||||
QString getUserAgent();
|
QString getUserAgent();
|
||||||
QString getUserAgentUncached();
|
|
||||||
|
|
||||||
/// this is the root of the 'installation'. Used for automatic updates
|
/// this is the root of the 'installation'. Used for automatic updates
|
||||||
const QString& root() { return m_rootPath; }
|
const QString& root() { return m_rootPath; }
|
||||||
|
|
|
@ -310,6 +310,8 @@ set(MINECRAFT_SOURCES
|
||||||
minecraft/ParseUtils.h
|
minecraft/ParseUtils.h
|
||||||
minecraft/ProfileUtils.cpp
|
minecraft/ProfileUtils.cpp
|
||||||
minecraft/ProfileUtils.h
|
minecraft/ProfileUtils.h
|
||||||
|
minecraft/ShortcutUtils.cpp
|
||||||
|
minecraft/ShortcutUtils.h
|
||||||
minecraft/Library.cpp
|
minecraft/Library.cpp
|
||||||
minecraft/Library.h
|
minecraft/Library.h
|
||||||
minecraft/MojangDownloadInfo.h
|
minecraft/MojangDownloadInfo.h
|
||||||
|
@ -834,6 +836,10 @@ SET(LAUNCHER_SOURCES
|
||||||
icons/IconList.h
|
icons/IconList.h
|
||||||
icons/IconList.cpp
|
icons/IconList.cpp
|
||||||
|
|
||||||
|
# log utils
|
||||||
|
logs/AnonymizeLog.cpp
|
||||||
|
logs/AnonymizeLog.h
|
||||||
|
|
||||||
# GUI - windows
|
# GUI - windows
|
||||||
ui/GuiUtil.h
|
ui/GuiUtil.h
|
||||||
ui/GuiUtil.cpp
|
ui/GuiUtil.cpp
|
||||||
|
@ -1048,6 +1054,8 @@ SET(LAUNCHER_SOURCES
|
||||||
ui/dialogs/ProfileSetupDialog.h
|
ui/dialogs/ProfileSetupDialog.h
|
||||||
ui/dialogs/CopyInstanceDialog.cpp
|
ui/dialogs/CopyInstanceDialog.cpp
|
||||||
ui/dialogs/CopyInstanceDialog.h
|
ui/dialogs/CopyInstanceDialog.h
|
||||||
|
ui/dialogs/CreateShortcutDialog.cpp
|
||||||
|
ui/dialogs/CreateShortcutDialog.h
|
||||||
ui/dialogs/CustomMessageBox.cpp
|
ui/dialogs/CustomMessageBox.cpp
|
||||||
ui/dialogs/CustomMessageBox.h
|
ui/dialogs/CustomMessageBox.h
|
||||||
ui/dialogs/ExportInstanceDialog.cpp
|
ui/dialogs/ExportInstanceDialog.cpp
|
||||||
|
@ -1230,6 +1238,7 @@ qt_wrap_ui(LAUNCHER_UI
|
||||||
ui/widgets/MinecraftSettingsWidget.ui
|
ui/widgets/MinecraftSettingsWidget.ui
|
||||||
ui/widgets/JavaSettingsWidget.ui
|
ui/widgets/JavaSettingsWidget.ui
|
||||||
ui/dialogs/CopyInstanceDialog.ui
|
ui/dialogs/CopyInstanceDialog.ui
|
||||||
|
ui/dialogs/CreateShortcutDialog.ui
|
||||||
ui/dialogs/ProfileSetupDialog.ui
|
ui/dialogs/ProfileSetupDialog.ui
|
||||||
ui/dialogs/ProgressDialog.ui
|
ui/dialogs/ProgressDialog.ui
|
||||||
ui/dialogs/NewInstanceDialog.ui
|
ui/dialogs/NewInstanceDialog.ui
|
||||||
|
@ -1306,7 +1315,7 @@ target_link_libraries(Launcher_logic
|
||||||
qdcss
|
qdcss
|
||||||
BuildConfig
|
BuildConfig
|
||||||
Qt${QT_VERSION_MAJOR}::Widgets
|
Qt${QT_VERSION_MAJOR}::Widgets
|
||||||
qrcode
|
qrcodegenerator
|
||||||
)
|
)
|
||||||
|
|
||||||
if (UNIX AND NOT CYGWIN AND NOT APPLE)
|
if (UNIX AND NOT CYGWIN AND NOT APPLE)
|
||||||
|
|
|
@ -269,9 +269,9 @@ bool FileIgnoreProxy::ignoreFile(QFileInfo fileInfo) const
|
||||||
return m_ignoreFiles.contains(fileInfo.fileName()) || m_ignoreFilePaths.covers(relPath(fileInfo.absoluteFilePath()));
|
return m_ignoreFiles.contains(fileInfo.fileName()) || m_ignoreFilePaths.covers(relPath(fileInfo.absoluteFilePath()));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FileIgnoreProxy::filterFile(const QString& fileName) const
|
bool FileIgnoreProxy::filterFile(const QFileInfo& file) const
|
||||||
{
|
{
|
||||||
return m_blocked.covers(fileName) || ignoreFile(QFileInfo(QDir(m_root), fileName));
|
return m_blocked.covers(relPath(file.absoluteFilePath())) || ignoreFile(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileIgnoreProxy::loadBlockedPathsFromFile(const QString& fileName)
|
void FileIgnoreProxy::loadBlockedPathsFromFile(const QString& fileName)
|
||||||
|
|
|
@ -69,7 +69,7 @@ class FileIgnoreProxy : public QSortFilterProxyModel {
|
||||||
// list of relative paths that need to be removed completely from model
|
// list of relative paths that need to be removed completely from model
|
||||||
inline SeparatorPrefixTree<'/'>& ignoreFilesWithPath() { return m_ignoreFilePaths; }
|
inline SeparatorPrefixTree<'/'>& ignoreFilesWithPath() { return m_ignoreFilePaths; }
|
||||||
|
|
||||||
bool filterFile(const QString& fileName) const;
|
bool filterFile(const QFileInfo& fileName) const;
|
||||||
|
|
||||||
void loadBlockedPathsFromFile(const QString& fileName);
|
void loadBlockedPathsFromFile(const QString& fileName);
|
||||||
|
|
||||||
|
|
|
@ -107,6 +107,10 @@ namespace fs = std::filesystem;
|
||||||
|
|
||||||
#if defined(__MINGW32__)
|
#if defined(__MINGW32__)
|
||||||
|
|
||||||
|
// Avoid re-defining structs retroactively added to MinGW
|
||||||
|
// https://github.com/mingw-w64/mingw-w64/issues/90#issuecomment-2829284729
|
||||||
|
#if __MINGW64_VERSION_MAJOR < 13
|
||||||
|
|
||||||
struct _DUPLICATE_EXTENTS_DATA {
|
struct _DUPLICATE_EXTENTS_DATA {
|
||||||
HANDLE FileHandle;
|
HANDLE FileHandle;
|
||||||
LARGE_INTEGER SourceFileOffset;
|
LARGE_INTEGER SourceFileOffset;
|
||||||
|
@ -116,6 +120,7 @@ struct _DUPLICATE_EXTENTS_DATA {
|
||||||
|
|
||||||
using DUPLICATE_EXTENTS_DATA = _DUPLICATE_EXTENTS_DATA;
|
using DUPLICATE_EXTENTS_DATA = _DUPLICATE_EXTENTS_DATA;
|
||||||
using PDUPLICATE_EXTENTS_DATA = _DUPLICATE_EXTENTS_DATA*;
|
using PDUPLICATE_EXTENTS_DATA = _DUPLICATE_EXTENTS_DATA*;
|
||||||
|
#endif
|
||||||
|
|
||||||
struct _FSCTL_GET_INTEGRITY_INFORMATION_BUFFER {
|
struct _FSCTL_GET_INTEGRITY_INFORMATION_BUFFER {
|
||||||
WORD ChecksumAlgorithm; // Checksum algorithm. e.g. CHECKSUM_TYPE_UNCHANGED, CHECKSUM_TYPE_NONE, CHECKSUM_TYPE_CRC32
|
WORD ChecksumAlgorithm; // Checksum algorithm. e.g. CHECKSUM_TYPE_UNCHANGED, CHECKSUM_TYPE_NONE, CHECKSUM_TYPE_CRC32
|
||||||
|
@ -887,6 +892,11 @@ QString getDesktopDir()
|
||||||
return QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
|
return QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString getApplicationsDir()
|
||||||
|
{
|
||||||
|
return QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation);
|
||||||
|
}
|
||||||
|
|
||||||
// Cross-platform Shortcut creation
|
// Cross-platform Shortcut creation
|
||||||
bool createShortcut(QString destination, QString target, QStringList args, QString name, QString icon)
|
bool createShortcut(QString destination, QString target, QStringList args, QString name, QString icon)
|
||||||
{
|
{
|
||||||
|
@ -898,16 +908,7 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#if defined(Q_OS_MACOS)
|
#if defined(Q_OS_MACOS)
|
||||||
// Create the Application
|
QDir application = destination + ".app/";
|
||||||
QDir applicationDirectory =
|
|
||||||
QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) + "/" + BuildConfig.LAUNCHER_NAME + " Instances/";
|
|
||||||
|
|
||||||
if (!applicationDirectory.mkpath(".")) {
|
|
||||||
qWarning() << "Couldn't create application directory";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QDir application = applicationDirectory.path() + "/" + name + ".app/";
|
|
||||||
|
|
||||||
if (application.exists()) {
|
if (application.exists()) {
|
||||||
qWarning() << "Application already exists!";
|
qWarning() << "Application already exists!";
|
||||||
|
|
|
@ -353,6 +353,9 @@ bool checkProblemticPathJava(QDir folder);
|
||||||
// Get the Directory representing the User's Desktop
|
// Get the Directory representing the User's Desktop
|
||||||
QString getDesktopDir();
|
QString getDesktopDir();
|
||||||
|
|
||||||
|
// Get the Directory representing the User's Applications directory
|
||||||
|
QString getApplicationsDir();
|
||||||
|
|
||||||
// Overrides one folder with the contents of another, preserving items exclusive to the first folder
|
// Overrides one folder with the contents of another, preserving items exclusive to the first folder
|
||||||
// Equivalent to doing QDir::rename, but allowing for overrides
|
// Equivalent to doing QDir::rename, but allowing for overrides
|
||||||
bool overrideFolder(QString overwritten_path, QString override_path);
|
bool overrideFolder(QString overwritten_path, QString override_path);
|
||||||
|
|
|
@ -72,7 +72,6 @@ bool InstanceImportTask::abort()
|
||||||
bool wasAborted = false;
|
bool wasAborted = false;
|
||||||
if (m_task)
|
if (m_task)
|
||||||
wasAborted = m_task->abort();
|
wasAborted = m_task->abort();
|
||||||
Task::abort();
|
|
||||||
return wasAborted;
|
return wasAborted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,6 +211,7 @@ void InstanceImportTask::processZipPack()
|
||||||
progressStep->status = status;
|
progressStep->status = status;
|
||||||
stepProgress(*progressStep);
|
stepProgress(*progressStep);
|
||||||
});
|
});
|
||||||
|
connect(zipTask.get(), &Task::warningLogged, this, [this](const QString& line) { m_Warnings.append(line); });
|
||||||
m_task.reset(zipTask);
|
m_task.reset(zipTask);
|
||||||
zipTask->start();
|
zipTask->start();
|
||||||
}
|
}
|
||||||
|
@ -263,6 +263,25 @@ void InstanceImportTask::extractFinished()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool installIcon(QString root, QString instIcon)
|
||||||
|
{
|
||||||
|
auto importIconPath = IconUtils::findBestIconIn(root, instIcon);
|
||||||
|
if (importIconPath.isNull() || !QFile::exists(importIconPath))
|
||||||
|
importIconPath = IconUtils::findBestIconIn(root, "icon.png");
|
||||||
|
if (importIconPath.isNull() || !QFile::exists(importIconPath))
|
||||||
|
importIconPath = IconUtils::findBestIconIn(FS::PathCombine(root, "overrides"), "icon.png");
|
||||||
|
if (!importIconPath.isNull() && QFile::exists(importIconPath)) {
|
||||||
|
// import icon
|
||||||
|
auto iconList = APPLICATION->icons();
|
||||||
|
if (iconList->iconFileExists(instIcon)) {
|
||||||
|
iconList->deleteIcon(instIcon);
|
||||||
|
}
|
||||||
|
iconList->installIcon(importIconPath, instIcon);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void InstanceImportTask::processFlame()
|
void InstanceImportTask::processFlame()
|
||||||
{
|
{
|
||||||
shared_qobject_ptr<FlameCreationTask> inst_creation_task = nullptr;
|
shared_qobject_ptr<FlameCreationTask> inst_creation_task = nullptr;
|
||||||
|
@ -288,6 +307,14 @@ void InstanceImportTask::processFlame()
|
||||||
}
|
}
|
||||||
|
|
||||||
inst_creation_task->setName(*this);
|
inst_creation_task->setName(*this);
|
||||||
|
// if the icon was specified by user, use that. otherwise pull icon from the pack
|
||||||
|
if (m_instIcon == "default") {
|
||||||
|
auto iconKey = QString("Flame_%1_Icon").arg(name());
|
||||||
|
|
||||||
|
if (installIcon(m_stagingPath, iconKey)) {
|
||||||
|
m_instIcon = iconKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
inst_creation_task->setIcon(m_instIcon);
|
inst_creation_task->setIcon(m_instIcon);
|
||||||
inst_creation_task->setGroup(m_instGroup);
|
inst_creation_task->setGroup(m_instGroup);
|
||||||
inst_creation_task->setConfirmUpdate(shouldConfirmUpdate());
|
inst_creation_task->setConfirmUpdate(shouldConfirmUpdate());
|
||||||
|
@ -305,9 +332,11 @@ void InstanceImportTask::processFlame()
|
||||||
connect(inst_creation_task.get(), &Task::status, this, &InstanceImportTask::setStatus);
|
connect(inst_creation_task.get(), &Task::status, this, &InstanceImportTask::setStatus);
|
||||||
connect(inst_creation_task.get(), &Task::details, this, &InstanceImportTask::setDetails);
|
connect(inst_creation_task.get(), &Task::details, this, &InstanceImportTask::setDetails);
|
||||||
|
|
||||||
connect(inst_creation_task.get(), &Task::aborted, this, &Task::abort);
|
connect(inst_creation_task.get(), &Task::aborted, this, &InstanceImportTask::emitAborted);
|
||||||
connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable);
|
connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable);
|
||||||
|
|
||||||
|
connect(inst_creation_task.get(), &Task::warningLogged, this, [this](const QString& line) { m_Warnings.append(line); });
|
||||||
|
|
||||||
m_task.reset(inst_creation_task);
|
m_task.reset(inst_creation_task);
|
||||||
setAbortable(true);
|
setAbortable(true);
|
||||||
m_task->start();
|
m_task->start();
|
||||||
|
@ -340,17 +369,7 @@ void InstanceImportTask::processMultiMC()
|
||||||
} else {
|
} else {
|
||||||
m_instIcon = instance.iconKey();
|
m_instIcon = instance.iconKey();
|
||||||
|
|
||||||
auto importIconPath = IconUtils::findBestIconIn(instance.instanceRoot(), m_instIcon);
|
installIcon(instance.instanceRoot(), m_instIcon);
|
||||||
if (importIconPath.isNull() || !QFile::exists(importIconPath))
|
|
||||||
importIconPath = IconUtils::findBestIconIn(instance.instanceRoot(), "icon.png");
|
|
||||||
if (!importIconPath.isNull() && QFile::exists(importIconPath)) {
|
|
||||||
// import icon
|
|
||||||
auto iconList = APPLICATION->icons();
|
|
||||||
if (iconList->iconFileExists(m_instIcon)) {
|
|
||||||
iconList->deleteIcon(m_instIcon);
|
|
||||||
}
|
|
||||||
iconList->installIcon(importIconPath, m_instIcon);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
}
|
}
|
||||||
|
@ -387,6 +406,14 @@ void InstanceImportTask::processModrinth()
|
||||||
}
|
}
|
||||||
|
|
||||||
inst_creation_task->setName(*this);
|
inst_creation_task->setName(*this);
|
||||||
|
// if the icon was specified by user, use that. otherwise pull icon from the pack
|
||||||
|
if (m_instIcon == "default") {
|
||||||
|
auto iconKey = QString("Modrinth_%1_Icon").arg(name());
|
||||||
|
|
||||||
|
if (installIcon(m_stagingPath, iconKey)) {
|
||||||
|
m_instIcon = iconKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
inst_creation_task->setIcon(m_instIcon);
|
inst_creation_task->setIcon(m_instIcon);
|
||||||
inst_creation_task->setGroup(m_instGroup);
|
inst_creation_task->setGroup(m_instGroup);
|
||||||
inst_creation_task->setConfirmUpdate(shouldConfirmUpdate());
|
inst_creation_task->setConfirmUpdate(shouldConfirmUpdate());
|
||||||
|
@ -404,9 +431,11 @@ void InstanceImportTask::processModrinth()
|
||||||
connect(inst_creation_task.get(), &Task::status, this, &InstanceImportTask::setStatus);
|
connect(inst_creation_task.get(), &Task::status, this, &InstanceImportTask::setStatus);
|
||||||
connect(inst_creation_task.get(), &Task::details, this, &InstanceImportTask::setDetails);
|
connect(inst_creation_task.get(), &Task::details, this, &InstanceImportTask::setDetails);
|
||||||
|
|
||||||
connect(inst_creation_task.get(), &Task::aborted, this, &Task::abort);
|
connect(inst_creation_task.get(), &Task::aborted, this, &InstanceImportTask::emitAborted);
|
||||||
connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable);
|
connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable);
|
||||||
|
|
||||||
|
connect(inst_creation_task.get(), &Task::warningLogged, this, [this](const QString& line) { m_Warnings.append(line); });
|
||||||
|
|
||||||
m_task.reset(inst_creation_task);
|
m_task.reset(inst_creation_task);
|
||||||
setAbortable(true);
|
setAbortable(true);
|
||||||
m_task->start();
|
m_task->start();
|
||||||
|
|
|
@ -418,7 +418,7 @@ bool extractFile(QString fileCompressed, QString file, QString target)
|
||||||
return extractRelFile(&zip, file, target);
|
return extractRelFile(&zip, file, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFunction excludeFilter)
|
bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFileFunction excludeFilter)
|
||||||
{
|
{
|
||||||
QDir rootDirectory(rootDir);
|
QDir rootDirectory(rootDir);
|
||||||
if (!rootDirectory.exists())
|
if (!rootDirectory.exists())
|
||||||
|
@ -443,8 +443,8 @@ bool collectFileListRecursively(const QString& rootDir, const QString& subDir, Q
|
||||||
// collect files
|
// collect files
|
||||||
entries = directory.entryInfoList(QDir::Files);
|
entries = directory.entryInfoList(QDir::Files);
|
||||||
for (const auto& e : entries) {
|
for (const auto& e : entries) {
|
||||||
|
if (excludeFilter && excludeFilter(e)) {
|
||||||
QString relativeFilePath = rootDirectory.relativeFilePath(e.absoluteFilePath());
|
QString relativeFilePath = rootDirectory.relativeFilePath(e.absoluteFilePath());
|
||||||
if (excludeFilter && excludeFilter(relativeFilePath)) {
|
|
||||||
qDebug() << "Skipping file " << relativeFilePath;
|
qDebug() << "Skipping file " << relativeFilePath;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,7 @@
|
||||||
|
|
||||||
namespace MMCZip {
|
namespace MMCZip {
|
||||||
using FilterFunction = std::function<bool(const QString&)>;
|
using FilterFunction = std::function<bool(const QString&)>;
|
||||||
|
using FilterFileFunction = std::function<bool(const QFileInfo&)>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Merge two zip files, using a filter function
|
* Merge two zip files, using a filter function
|
||||||
|
@ -149,7 +150,7 @@ bool extractFile(QString fileCompressed, QString file, QString dir);
|
||||||
* \param excludeFilter function to excludeFilter which files shouldn't be included (returning true means to excude)
|
* \param excludeFilter function to excludeFilter which files shouldn't be included (returning true means to excude)
|
||||||
* \return true for success or false for failure
|
* \return true for success or false for failure
|
||||||
*/
|
*/
|
||||||
bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFunction excludeFilter);
|
bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFileFunction excludeFilter);
|
||||||
|
|
||||||
#if defined(LAUNCHER_APPLICATION)
|
#if defined(LAUNCHER_APPLICATION)
|
||||||
class ExportToZipTask : public Task {
|
class ExportToZipTask : public Task {
|
||||||
|
|
|
@ -365,13 +365,13 @@ QList<QString> JavaUtils::FindJavaPaths()
|
||||||
javas.append("/System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/java");
|
javas.append("/System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/java");
|
||||||
QDir libraryJVMDir("/Library/Java/JavaVirtualMachines/");
|
QDir libraryJVMDir("/Library/Java/JavaVirtualMachines/");
|
||||||
QStringList libraryJVMJavas = libraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
QStringList libraryJVMJavas = libraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||||
foreach (const QString& java, libraryJVMJavas) {
|
for (const QString& java : libraryJVMJavas) {
|
||||||
javas.append(libraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java");
|
javas.append(libraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java");
|
||||||
javas.append(libraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/jre/bin/java");
|
javas.append(libraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/jre/bin/java");
|
||||||
}
|
}
|
||||||
QDir systemLibraryJVMDir("/System/Library/Java/JavaVirtualMachines/");
|
QDir systemLibraryJVMDir("/System/Library/Java/JavaVirtualMachines/");
|
||||||
QStringList systemLibraryJVMJavas = systemLibraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
QStringList systemLibraryJVMJavas = systemLibraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||||
foreach (const QString& java, systemLibraryJVMJavas) {
|
for (const QString& java : systemLibraryJVMJavas) {
|
||||||
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");
|
||||||
}
|
}
|
||||||
|
@ -381,14 +381,14 @@ QList<QString> JavaUtils::FindJavaPaths()
|
||||||
// javas downloaded by sdkman
|
// javas downloaded by sdkman
|
||||||
QDir sdkmanDir(FS::PathCombine(home, ".sdkman/candidates/java"));
|
QDir sdkmanDir(FS::PathCombine(home, ".sdkman/candidates/java"));
|
||||||
QStringList sdkmanJavas = sdkmanDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
QStringList sdkmanJavas = sdkmanDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||||
foreach (const QString& java, sdkmanJavas) {
|
for (const QString& java : sdkmanJavas) {
|
||||||
javas.append(sdkmanDir.absolutePath() + "/" + java + "/bin/java");
|
javas.append(sdkmanDir.absolutePath() + "/" + java + "/bin/java");
|
||||||
}
|
}
|
||||||
|
|
||||||
// java in user library folder (like from intellij downloads)
|
// java in user library folder (like from intellij downloads)
|
||||||
QDir userLibraryJVMDir(FS::PathCombine(home, "Library/Java/JavaVirtualMachines/"));
|
QDir userLibraryJVMDir(FS::PathCombine(home, "Library/Java/JavaVirtualMachines/"));
|
||||||
QStringList userLibraryJVMJavas = userLibraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
QStringList userLibraryJVMJavas = userLibraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||||
foreach (const QString& java, userLibraryJVMJavas) {
|
for (const QString& java : userLibraryJVMJavas) {
|
||||||
javas.append(userLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java");
|
javas.append(userLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java");
|
||||||
javas.append(userLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java");
|
javas.append(userLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java");
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,7 @@ void ArchiveDownloadTask::executeTask()
|
||||||
connect(download.get(), &Task::stepProgress, this, &ArchiveDownloadTask::propagateStepProgress);
|
connect(download.get(), &Task::stepProgress, this, &ArchiveDownloadTask::propagateStepProgress);
|
||||||
connect(download.get(), &Task::status, this, &ArchiveDownloadTask::setStatus);
|
connect(download.get(), &Task::status, this, &ArchiveDownloadTask::setStatus);
|
||||||
connect(download.get(), &Task::details, this, &ArchiveDownloadTask::setDetails);
|
connect(download.get(), &Task::details, this, &ArchiveDownloadTask::setDetails);
|
||||||
|
connect(download.get(), &Task::aborted, this, &ArchiveDownloadTask::emitAborted);
|
||||||
connect(download.get(), &Task::succeeded, [this, fullPath] {
|
connect(download.get(), &Task::succeeded, [this, fullPath] {
|
||||||
// This should do all of the extracting and creating folders
|
// This should do all of the extracting and creating folders
|
||||||
extractJava(fullPath);
|
extractJava(fullPath);
|
||||||
|
@ -135,7 +136,6 @@ bool ArchiveDownloadTask::abort()
|
||||||
auto aborted = canAbort();
|
auto aborted = canAbort();
|
||||||
if (m_task)
|
if (m_task)
|
||||||
aborted = m_task->abort();
|
aborted = m_task->abort();
|
||||||
emitAborted();
|
|
||||||
return aborted;
|
return aborted;
|
||||||
};
|
};
|
||||||
} // namespace Java
|
} // namespace Java
|
|
@ -167,8 +167,8 @@ bool LogModel::isOverFlow()
|
||||||
return m_numLines >= m_maxLines && m_stopOnOverflow;
|
return m_numLines >= m_maxLines && m_stopOnOverflow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MessageLevel::Enum LogModel::previousLevel()
|
||||||
MessageLevel::Enum LogModel::previousLevel() {
|
{
|
||||||
if (!m_content.isEmpty()) {
|
if (!m_content.isEmpty()) {
|
||||||
return m_content.last().level;
|
return m_content.last().level;
|
||||||
}
|
}
|
||||||
|
|
68
launcher/logs/AnonymizeLog.cpp
Normal file
68
launcher/logs/AnonymizeLog.cpp
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (c) 2025 Trial97 <alexandru.tripon97@gmail.com>
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
#include "AnonymizeLog.h"
|
||||||
|
|
||||||
|
#include <QRegularExpression>
|
||||||
|
|
||||||
|
struct RegReplace {
|
||||||
|
RegReplace(QRegularExpression r, QString w) : reg(r), with(w) { reg.optimize(); }
|
||||||
|
QRegularExpression reg;
|
||||||
|
QString with;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const QVector<RegReplace> anonymizeRules = {
|
||||||
|
RegReplace(QRegularExpression("C:\\\\Users\\\\([^\\\\]+)\\\\", QRegularExpression::CaseInsensitiveOption),
|
||||||
|
"C:\\Users\\********\\"), // windows
|
||||||
|
RegReplace(QRegularExpression("C:\\/Users\\/([^\\/]+)\\/", QRegularExpression::CaseInsensitiveOption),
|
||||||
|
"C:/Users/********/"), // windows with forward slashes
|
||||||
|
RegReplace(QRegularExpression("(?<!\\\\w)\\/home\\/[^\\/]+\\/", QRegularExpression::CaseInsensitiveOption),
|
||||||
|
"/home/********/"), // linux
|
||||||
|
RegReplace(QRegularExpression("(?<!\\\\w)\\/Users\\/[^\\/]+\\/", QRegularExpression::CaseInsensitiveOption),
|
||||||
|
"/Users/********/"), // macos
|
||||||
|
RegReplace(QRegularExpression("\\(Session ID is [^\\)]+\\)", QRegularExpression::CaseInsensitiveOption),
|
||||||
|
"(Session ID is <SESSION_TOKEN>)"), // SESSION_TOKEN
|
||||||
|
RegReplace(QRegularExpression("new refresh token: \"[^\"]+\"", QRegularExpression::CaseInsensitiveOption),
|
||||||
|
"new refresh token: \"<TOKEN>\""), // refresh token
|
||||||
|
RegReplace(QRegularExpression("\"device_code\" : \"[^\"]+\"", QRegularExpression::CaseInsensitiveOption),
|
||||||
|
"\"device_code\" : \"<DEVICE_CODE>\""), // device code
|
||||||
|
};
|
||||||
|
|
||||||
|
void anonymizeLog(QString& log)
|
||||||
|
{
|
||||||
|
for (auto rule : anonymizeRules) {
|
||||||
|
log.replace(rule.reg, rule.with);
|
||||||
|
}
|
||||||
|
}
|
40
launcher/logs/AnonymizeLog.h
Normal file
40
launcher/logs/AnonymizeLog.h
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (c) 2025 Trial97 <alexandru.tripon97@gmail.com>
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
void anonymizeLog(QString& log);
|
|
@ -248,6 +248,7 @@ void MinecraftInstance::loadSpecificSettings()
|
||||||
m_settings->registerSetting("ExportSummary", "");
|
m_settings->registerSetting("ExportSummary", "");
|
||||||
m_settings->registerSetting("ExportAuthor", "");
|
m_settings->registerSetting("ExportAuthor", "");
|
||||||
m_settings->registerSetting("ExportOptionalFiles", true);
|
m_settings->registerSetting("ExportOptionalFiles", true);
|
||||||
|
m_settings->registerSetting("ExportRecommendedRAM");
|
||||||
|
|
||||||
auto dataPacksEnabled = m_settings->registerSetting("GlobalDataPacksEnabled", false);
|
auto dataPacksEnabled = m_settings->registerSetting("GlobalDataPacksEnabled", false);
|
||||||
auto dataPacksPath = m_settings->registerSetting("GlobalDataPacksPath", "");
|
auto dataPacksPath = m_settings->registerSetting("GlobalDataPacksPath", "");
|
||||||
|
@ -1019,7 +1020,6 @@ QMap<QString, QString> MinecraftInstance::createCensorFilterFromSession(AuthSess
|
||||||
return filter;
|
return filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
QStringList MinecraftInstance::getLogFileSearchPaths()
|
QStringList MinecraftInstance::getLogFileSearchPaths()
|
||||||
{
|
{
|
||||||
return { FS::PathCombine(gameRoot(), "crash-reports"), FS::PathCombine(gameRoot(), "logs"), gameRoot() };
|
return { FS::PathCombine(gameRoot(), "crash-reports"), FS::PathCombine(gameRoot(), "logs"), gameRoot() };
|
||||||
|
|
237
launcher/minecraft/ShortcutUtils.cpp
Normal file
237
launcher/minecraft/ShortcutUtils.cpp
Normal file
|
@ -0,0 +1,237 @@
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
|
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||||
|
* Copyright (C) 2025 Yihe Li <winmikedows@hotmail.com>
|
||||||
|
*
|
||||||
|
* parent program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* parent program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with parent program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* parent file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use parent file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ShortcutUtils.h"
|
||||||
|
|
||||||
|
#include "FileSystem.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QFileDialog>
|
||||||
|
|
||||||
|
#include <BuildConfig.h>
|
||||||
|
#include <DesktopServices.h>
|
||||||
|
#include <icons/IconList.h>
|
||||||
|
|
||||||
|
namespace ShortcutUtils {
|
||||||
|
|
||||||
|
void createInstanceShortcut(const Shortcut& shortcut, const QString& filePath)
|
||||||
|
{
|
||||||
|
if (!shortcut.instance)
|
||||||
|
return;
|
||||||
|
|
||||||
|
QString appPath = QApplication::applicationFilePath();
|
||||||
|
auto icon = APPLICATION->icons()->icon(shortcut.iconKey.isEmpty() ? shortcut.instance->iconKey() : shortcut.iconKey);
|
||||||
|
if (icon == nullptr) {
|
||||||
|
icon = APPLICATION->icons()->icon("grass");
|
||||||
|
}
|
||||||
|
QString iconPath;
|
||||||
|
QStringList args;
|
||||||
|
#if defined(Q_OS_MACOS)
|
||||||
|
if (appPath.startsWith("/private/var/")) {
|
||||||
|
QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"),
|
||||||
|
QObject::tr("The launcher is in the folder it was extracted from, therefore it cannot create shortcuts."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
iconPath = FS::PathCombine(shortcut.instance->instanceRoot(), "Icon.icns");
|
||||||
|
|
||||||
|
QFile iconFile(iconPath);
|
||||||
|
if (!iconFile.open(QFile::WriteOnly)) {
|
||||||
|
QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for application."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QIcon iconObj = icon->icon();
|
||||||
|
bool success = iconObj.pixmap(1024, 1024).save(iconPath, "ICNS");
|
||||||
|
iconFile.close();
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
iconFile.remove();
|
||||||
|
QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for application."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
||||||
|
if (appPath.startsWith("/tmp/.mount_")) {
|
||||||
|
// AppImage!
|
||||||
|
appPath = QProcessEnvironment::systemEnvironment().value(QStringLiteral("APPIMAGE"));
|
||||||
|
if (appPath.isEmpty()) {
|
||||||
|
QMessageBox::critical(
|
||||||
|
shortcut.parent, QObject::tr("Create Shortcut"),
|
||||||
|
QObject::tr("Launcher is running as misconfigured AppImage? ($APPIMAGE environment variable is missing)"));
|
||||||
|
} else if (appPath.endsWith("/")) {
|
||||||
|
appPath.chop(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iconPath = FS::PathCombine(shortcut.instance->instanceRoot(), "icon.png");
|
||||||
|
|
||||||
|
QFile iconFile(iconPath);
|
||||||
|
if (!iconFile.open(QFile::WriteOnly)) {
|
||||||
|
QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for shortcut."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bool success = icon->icon().pixmap(64, 64).save(&iconFile, "PNG");
|
||||||
|
iconFile.close();
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
iconFile.remove();
|
||||||
|
QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for shortcut."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DesktopServices::isFlatpak()) {
|
||||||
|
appPath = "flatpak";
|
||||||
|
args.append({ "run", BuildConfig.LAUNCHER_APPID });
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif defined(Q_OS_WIN)
|
||||||
|
iconPath = FS::PathCombine(shortcut.instance->instanceRoot(), "icon.ico");
|
||||||
|
|
||||||
|
// part of fix for weird bug involving the window icon being replaced
|
||||||
|
// dunno why it happens, but parent 2-line fix seems to be enough, so w/e
|
||||||
|
auto appIcon = APPLICATION->getThemedIcon("logo");
|
||||||
|
|
||||||
|
QFile iconFile(iconPath);
|
||||||
|
if (!iconFile.open(QFile::WriteOnly)) {
|
||||||
|
QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for shortcut."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bool success = icon->icon().pixmap(64, 64).save(&iconFile, "ICO");
|
||||||
|
iconFile.close();
|
||||||
|
|
||||||
|
// restore original window icon
|
||||||
|
QGuiApplication::setWindowIcon(appIcon);
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
iconFile.remove();
|
||||||
|
QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for shortcut."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Not supported on your platform!"));
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
args.append({ "--launch", shortcut.instance->id() });
|
||||||
|
args.append(shortcut.extraArgs);
|
||||||
|
|
||||||
|
if (!FS::createShortcut(filePath, appPath, args, shortcut.name, iconPath)) {
|
||||||
|
#if not defined(Q_OS_MACOS)
|
||||||
|
iconFile.remove();
|
||||||
|
#endif
|
||||||
|
QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"),
|
||||||
|
QObject::tr("Failed to create %1 shortcut!").arg(shortcut.targetString));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void createInstanceShortcutOnDesktop(const Shortcut& shortcut)
|
||||||
|
{
|
||||||
|
if (!shortcut.instance)
|
||||||
|
return;
|
||||||
|
|
||||||
|
QString desktopDir = FS::getDesktopDir();
|
||||||
|
if (desktopDir.isEmpty()) {
|
||||||
|
QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Couldn't find desktop?!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString shortcutFilePath = FS::PathCombine(desktopDir, FS::RemoveInvalidFilenameChars(shortcut.name));
|
||||||
|
createInstanceShortcut(shortcut, shortcutFilePath);
|
||||||
|
QMessageBox::information(shortcut.parent, QObject::tr("Create Shortcut"),
|
||||||
|
QObject::tr("Created a shortcut to this %1 on your desktop!").arg(shortcut.targetString));
|
||||||
|
}
|
||||||
|
|
||||||
|
void createInstanceShortcutInApplications(const Shortcut& shortcut)
|
||||||
|
{
|
||||||
|
if (!shortcut.instance)
|
||||||
|
return;
|
||||||
|
|
||||||
|
QString applicationsDir = FS::getApplicationsDir();
|
||||||
|
if (applicationsDir.isEmpty()) {
|
||||||
|
QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Couldn't find applications folder?!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
|
||||||
|
applicationsDir = FS::PathCombine(applicationsDir, BuildConfig.LAUNCHER_DISPLAYNAME + " Instances");
|
||||||
|
|
||||||
|
QDir applicationsDirQ(applicationsDir);
|
||||||
|
if (!applicationsDirQ.mkpath(".")) {
|
||||||
|
QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"),
|
||||||
|
QObject::tr("Failed to create instances folder in applications folder!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QString shortcutFilePath = FS::PathCombine(applicationsDir, FS::RemoveInvalidFilenameChars(shortcut.name));
|
||||||
|
createInstanceShortcut(shortcut, shortcutFilePath);
|
||||||
|
QMessageBox::information(shortcut.parent, QObject::tr("Create Shortcut"),
|
||||||
|
QObject::tr("Created a shortcut to this %1 in your applications folder!").arg(shortcut.targetString));
|
||||||
|
}
|
||||||
|
|
||||||
|
void createInstanceShortcutInOther(const Shortcut& shortcut)
|
||||||
|
{
|
||||||
|
if (!shortcut.instance)
|
||||||
|
return;
|
||||||
|
|
||||||
|
QString defaultedDir = FS::getDesktopDir();
|
||||||
|
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
||||||
|
QString extension = ".desktop";
|
||||||
|
#elif defined(Q_OS_WINDOWS)
|
||||||
|
QString extension = ".lnk";
|
||||||
|
#else
|
||||||
|
QString extension = "";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QString shortcutFilePath = FS::PathCombine(defaultedDir, FS::RemoveInvalidFilenameChars(shortcut.name) + extension);
|
||||||
|
QFileDialog fileDialog;
|
||||||
|
// workaround to make sure the portal file dialog opens in the desktop directory
|
||||||
|
fileDialog.setDirectoryUrl(defaultedDir);
|
||||||
|
|
||||||
|
shortcutFilePath = fileDialog.getSaveFileName(shortcut.parent, QObject::tr("Create Shortcut"), shortcutFilePath,
|
||||||
|
QObject::tr("Desktop Entries") + " (*" + extension + ")");
|
||||||
|
if (shortcutFilePath.isEmpty())
|
||||||
|
return; // file dialog canceled by user
|
||||||
|
|
||||||
|
if (shortcutFilePath.endsWith(extension))
|
||||||
|
shortcutFilePath = shortcutFilePath.mid(0, shortcutFilePath.length() - extension.length());
|
||||||
|
createInstanceShortcut(shortcut, shortcutFilePath);
|
||||||
|
QMessageBox::information(shortcut.parent, QObject::tr("Create Shortcut"),
|
||||||
|
QObject::tr("Created a shortcut to this %1!").arg(shortcut.targetString));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ShortcutUtils
|
66
launcher/minecraft/ShortcutUtils.h
Normal file
66
launcher/minecraft/ShortcutUtils.h
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
|
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||||
|
* Copyright (C) 2025 Yihe Li <winmikedows@hotmail.com>
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "Application.h"
|
||||||
|
|
||||||
|
#include <QMessageBox>
|
||||||
|
|
||||||
|
namespace ShortcutUtils {
|
||||||
|
/// A struct to hold parameters for creating a shortcut
|
||||||
|
struct Shortcut {
|
||||||
|
BaseInstance* instance;
|
||||||
|
QString name;
|
||||||
|
QString targetString;
|
||||||
|
QWidget* parent = nullptr;
|
||||||
|
QStringList extraArgs = {};
|
||||||
|
QString iconKey = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Create an instance shortcut on the specified file path
|
||||||
|
void createInstanceShortcut(const Shortcut& shortcut, const QString& filePath);
|
||||||
|
|
||||||
|
/// Create an instance shortcut on the desktop
|
||||||
|
void createInstanceShortcutOnDesktop(const Shortcut& shortcut);
|
||||||
|
|
||||||
|
/// Create an instance shortcut in the Applications directory
|
||||||
|
void createInstanceShortcutInApplications(const Shortcut& shortcut);
|
||||||
|
|
||||||
|
/// Create an instance shortcut in other directories
|
||||||
|
void createInstanceShortcutInOther(const Shortcut& shortcut);
|
||||||
|
|
||||||
|
} // namespace ShortcutUtils
|
|
@ -48,7 +48,8 @@ TexturePackFolderModel::TexturePackFolderModel(const QDir& dir, BaseInstance* in
|
||||||
m_column_names = QStringList({ "Enable", "Image", "Name", "Last Modified", "Provider", "Size" });
|
m_column_names = QStringList({ "Enable", "Image", "Name", "Last Modified", "Provider", "Size" });
|
||||||
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified"), tr("Provider"), tr("Size") });
|
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified"), tr("Provider"), tr("Size") });
|
||||||
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE, SortType::PROVIDER, SortType::SIZE };
|
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE, SortType::PROVIDER, SortType::SIZE };
|
||||||
m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive };
|
m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch,
|
||||||
|
QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive };
|
||||||
m_columnsHideable = { false, true, false, true, true, true };
|
m_columnsHideable = { false, true, false, true, true, true };
|
||||||
m_columnsHiddenByDefault = { false, false, false, false, false, true };
|
m_columnsHiddenByDefault = { false, false, false, false, false, true };
|
||||||
}
|
}
|
||||||
|
|
|
@ -268,6 +268,26 @@ void SkinList::installSkins(const QStringList& iconFiles)
|
||||||
installSkin(file);
|
installSkin(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString getUniqueFile(const QString& root, const QString& file)
|
||||||
|
{
|
||||||
|
auto result = FS::PathCombine(root, file);
|
||||||
|
if (!QFileInfo::exists(result)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString baseName = QFileInfo(file).completeBaseName();
|
||||||
|
QString extension = QFileInfo(file).suffix();
|
||||||
|
int tries = 0;
|
||||||
|
while (QFileInfo::exists(result)) {
|
||||||
|
if (++tries > 256)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
QString key = QString("%1%2.%3").arg(baseName).arg(tries).arg(extension);
|
||||||
|
result = FS::PathCombine(root, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
QString SkinList::installSkin(const QString& file, const QString& name)
|
QString SkinList::installSkin(const QString& file, const QString& name)
|
||||||
{
|
{
|
||||||
if (file.isEmpty())
|
if (file.isEmpty())
|
||||||
|
@ -282,7 +302,7 @@ QString SkinList::installSkin(const QString& file, const QString& name)
|
||||||
if (fileinfo.suffix() != "png" && !SkinModel(fileinfo.absoluteFilePath()).isValid())
|
if (fileinfo.suffix() != "png" && !SkinModel(fileinfo.absoluteFilePath()).isValid())
|
||||||
return tr("Skin images must be 64x64 or 64x32 pixel PNG files.");
|
return tr("Skin images must be 64x64 or 64x32 pixel PNG files.");
|
||||||
|
|
||||||
QString target = FS::PathCombine(m_dir.absolutePath(), name.isEmpty() ? fileinfo.fileName() : name);
|
QString target = getUniqueFile(m_dir.absolutePath(), name.isEmpty() ? fileinfo.fileName() : name);
|
||||||
|
|
||||||
return QFile::copy(file, target) ? "" : tr("Unable to copy file");
|
return QFile::copy(file, target) ? "" : tr("Unable to copy file");
|
||||||
}
|
}
|
||||||
|
@ -371,7 +391,8 @@ bool SkinList::setData(const QModelIndex& idx, const QVariant& value, int role)
|
||||||
auto& skin = m_skinList[row];
|
auto& skin = m_skinList[row];
|
||||||
auto newName = value.toString();
|
auto newName = value.toString();
|
||||||
if (skin.name() != newName) {
|
if (skin.name() != newName) {
|
||||||
skin.rename(newName);
|
if (!skin.rename(newName))
|
||||||
|
return false;
|
||||||
save();
|
save();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -122,7 +122,11 @@ QString SkinModel::name() const
|
||||||
bool SkinModel::rename(QString newName)
|
bool SkinModel::rename(QString newName)
|
||||||
{
|
{
|
||||||
auto info = QFileInfo(m_path);
|
auto info = QFileInfo(m_path);
|
||||||
m_path = FS::PathCombine(info.absolutePath(), newName + ".png");
|
auto new_path = FS::PathCombine(info.absolutePath(), newName + ".png");
|
||||||
|
if (QFileInfo::exists(new_path)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_path = new_path;
|
||||||
return FS::move(info.absoluteFilePath(), m_path);
|
return FS::move(info.absoluteFilePath(), m_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,7 @@
|
||||||
|
|
||||||
#include "settings/INISettingsObject.h"
|
#include "settings/INISettingsObject.h"
|
||||||
|
|
||||||
|
#include "sys.h"
|
||||||
#include "tasks/ConcurrentTask.h"
|
#include "tasks/ConcurrentTask.h"
|
||||||
#include "ui/dialogs/BlockedModsDialog.h"
|
#include "ui/dialogs/BlockedModsDialog.h"
|
||||||
#include "ui/dialogs/CustomMessageBox.h"
|
#include "ui/dialogs/CustomMessageBox.h"
|
||||||
|
@ -418,6 +419,24 @@ bool FlameCreationTask::createInstance()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int recommendedRAM = m_pack.minecraft.recommendedRAM;
|
||||||
|
|
||||||
|
// only set memory if this is a fresh instance
|
||||||
|
if (m_instance == nullptr && recommendedRAM > 0) {
|
||||||
|
const uint64_t sysMiB = Sys::getSystemRam() / Sys::mebibyte;
|
||||||
|
const uint64_t max = sysMiB * 0.9;
|
||||||
|
|
||||||
|
if (recommendedRAM > max) {
|
||||||
|
logWarning(tr("The recommended memory of the modpack exceeds 90% of your system RAM—reducing it from %1 MiB to %2 MiB!")
|
||||||
|
.arg(recommendedRAM)
|
||||||
|
.arg(max));
|
||||||
|
recommendedRAM = max;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.settings()->set("OverrideMemory", true);
|
||||||
|
instance.settings()->set("MaxMemAlloc", recommendedRAM);
|
||||||
|
}
|
||||||
|
|
||||||
QString jarmodsPath = FS::PathCombine(m_stagingPath, "minecraft", "jarmods");
|
QString jarmodsPath = FS::PathCombine(m_stagingPath, "minecraft", "jarmods");
|
||||||
QFileInfo jarmodsInfo(jarmodsPath);
|
QFileInfo jarmodsInfo(jarmodsPath);
|
||||||
if (jarmodsInfo.isDir()) {
|
if (jarmodsInfo.isDir()) {
|
||||||
|
|
|
@ -41,22 +41,8 @@
|
||||||
const QString FlamePackExportTask::TEMPLATE = "<li><a href=\"{url}\">{name}{authors}</a></li>\n";
|
const QString FlamePackExportTask::TEMPLATE = "<li><a href=\"{url}\">{name}{authors}</a></li>\n";
|
||||||
const QStringList FlamePackExportTask::FILE_EXTENSIONS({ "jar", "zip" });
|
const QStringList FlamePackExportTask::FILE_EXTENSIONS({ "jar", "zip" });
|
||||||
|
|
||||||
FlamePackExportTask::FlamePackExportTask(const QString& name,
|
FlamePackExportTask::FlamePackExportTask(FlamePackExportOptions&& options)
|
||||||
const QString& version,
|
: m_options(std::move(options)), m_gameRoot(m_options.instance->gameRoot())
|
||||||
const QString& author,
|
|
||||||
bool optionalFiles,
|
|
||||||
InstancePtr instance,
|
|
||||||
const QString& output,
|
|
||||||
MMCZip::FilterFunction filter)
|
|
||||||
: name(name)
|
|
||||||
, version(version)
|
|
||||||
, author(author)
|
|
||||||
, optionalFiles(optionalFiles)
|
|
||||||
, instance(instance)
|
|
||||||
, mcInstance(dynamic_cast<MinecraftInstance*>(instance.get()))
|
|
||||||
, gameRoot(instance->gameRoot())
|
|
||||||
, output(output)
|
|
||||||
, filter(filter)
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
void FlamePackExportTask::executeTask()
|
void FlamePackExportTask::executeTask()
|
||||||
|
@ -70,7 +56,6 @@ bool FlamePackExportTask::abort()
|
||||||
{
|
{
|
||||||
if (task) {
|
if (task) {
|
||||||
task->abort();
|
task->abort();
|
||||||
emitAborted();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -82,7 +67,7 @@ void FlamePackExportTask::collectFiles()
|
||||||
QCoreApplication::processEvents();
|
QCoreApplication::processEvents();
|
||||||
|
|
||||||
files.clear();
|
files.clear();
|
||||||
if (!MMCZip::collectFileListRecursively(instance->gameRoot(), nullptr, &files, filter)) {
|
if (!MMCZip::collectFileListRecursively(m_options.instance->gameRoot(), nullptr, &files, m_options.filter)) {
|
||||||
emitFailed(tr("Could not search for files"));
|
emitFailed(tr("Could not search for files"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -90,11 +75,8 @@ void FlamePackExportTask::collectFiles()
|
||||||
pendingHashes.clear();
|
pendingHashes.clear();
|
||||||
resolvedFiles.clear();
|
resolvedFiles.clear();
|
||||||
|
|
||||||
if (mcInstance != nullptr) {
|
m_options.instance->loaderModList()->update();
|
||||||
mcInstance->loaderModList()->update();
|
connect(m_options.instance->loaderModList().get(), &ModFolderModel::updateFinished, this, &FlamePackExportTask::collectHashes);
|
||||||
connect(mcInstance->loaderModList().get(), &ModFolderModel::updateFinished, this, &FlamePackExportTask::collectHashes);
|
|
||||||
} else
|
|
||||||
collectHashes();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlamePackExportTask::collectHashes()
|
void FlamePackExportTask::collectHashes()
|
||||||
|
@ -102,11 +84,11 @@ void FlamePackExportTask::collectHashes()
|
||||||
setAbortable(true);
|
setAbortable(true);
|
||||||
setStatus(tr("Finding file hashes..."));
|
setStatus(tr("Finding file hashes..."));
|
||||||
setProgress(1, 5);
|
setProgress(1, 5);
|
||||||
auto allMods = mcInstance->loaderModList()->allMods();
|
auto allMods = m_options.instance->loaderModList()->allMods();
|
||||||
ConcurrentTask::Ptr hashingTask(new ConcurrentTask("MakeHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()));
|
ConcurrentTask::Ptr hashingTask(new ConcurrentTask("MakeHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()));
|
||||||
task.reset(hashingTask);
|
task.reset(hashingTask);
|
||||||
for (const QFileInfo& file : files) {
|
for (const QFileInfo& file : files) {
|
||||||
const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath());
|
const QString relative = m_gameRoot.relativeFilePath(file.absoluteFilePath());
|
||||||
// require sensible file types
|
// require sensible file types
|
||||||
if (!std::any_of(FILE_EXTENSIONS.begin(), FILE_EXTENSIONS.end(), [&relative](const QString& extension) {
|
if (!std::any_of(FILE_EXTENSIONS.begin(), FILE_EXTENSIONS.end(), [&relative](const QString& extension) {
|
||||||
return relative.endsWith('.' + extension) || relative.endsWith('.' + extension + ".disabled");
|
return relative.endsWith('.' + extension) || relative.endsWith('.' + extension + ".disabled");
|
||||||
|
@ -171,6 +153,7 @@ void FlamePackExportTask::collectHashes()
|
||||||
progressStep->status = status;
|
progressStep->status = status;
|
||||||
stepProgress(*progressStep);
|
stepProgress(*progressStep);
|
||||||
});
|
});
|
||||||
|
connect(hashingTask.get(), &Task::aborted, this, &FlamePackExportTask::emitAborted);
|
||||||
hashingTask->start();
|
hashingTask->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,6 +229,7 @@ void FlamePackExportTask::makeApiRequest()
|
||||||
getProjectsInfo();
|
getProjectsInfo();
|
||||||
});
|
});
|
||||||
connect(task.get(), &Task::failed, this, &FlamePackExportTask::getProjectsInfo);
|
connect(task.get(), &Task::failed, this, &FlamePackExportTask::getProjectsInfo);
|
||||||
|
connect(task.get(), &Task::aborted, this, &FlamePackExportTask::emitAborted);
|
||||||
task->start();
|
task->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -324,6 +308,7 @@ void FlamePackExportTask::getProjectsInfo()
|
||||||
buildZip();
|
buildZip();
|
||||||
});
|
});
|
||||||
connect(projTask.get(), &Task::failed, this, &FlamePackExportTask::emitFailed);
|
connect(projTask.get(), &Task::failed, this, &FlamePackExportTask::emitFailed);
|
||||||
|
connect(projTask.get(), &Task::aborted, this, &FlamePackExportTask::emitAborted);
|
||||||
task.reset(projTask);
|
task.reset(projTask);
|
||||||
task->start();
|
task->start();
|
||||||
}
|
}
|
||||||
|
@ -333,13 +318,13 @@ void FlamePackExportTask::buildZip()
|
||||||
setStatus(tr("Adding files..."));
|
setStatus(tr("Adding files..."));
|
||||||
setProgress(4, 5);
|
setProgress(4, 5);
|
||||||
|
|
||||||
auto zipTask = makeShared<MMCZip::ExportToZipTask>(output, gameRoot, files, "overrides/", true, false);
|
auto zipTask = makeShared<MMCZip::ExportToZipTask>(m_options.output, m_gameRoot, files, "overrides/", true, false);
|
||||||
zipTask->addExtraFile("manifest.json", generateIndex());
|
zipTask->addExtraFile("manifest.json", generateIndex());
|
||||||
zipTask->addExtraFile("modlist.html", generateHTML());
|
zipTask->addExtraFile("modlist.html", generateHTML());
|
||||||
|
|
||||||
QStringList exclude;
|
QStringList exclude;
|
||||||
std::transform(resolvedFiles.keyBegin(), resolvedFiles.keyEnd(), std::back_insert_iterator(exclude),
|
std::transform(resolvedFiles.keyBegin(), resolvedFiles.keyEnd(), std::back_insert_iterator(exclude),
|
||||||
[this](QString file) { return gameRoot.relativeFilePath(file); });
|
[this](QString file) { return m_gameRoot.relativeFilePath(file); });
|
||||||
zipTask->setExcludeFiles(exclude);
|
zipTask->setExcludeFiles(exclude);
|
||||||
|
|
||||||
auto progressStep = std::make_shared<TaskStepProgress>();
|
auto progressStep = std::make_shared<TaskStepProgress>();
|
||||||
|
@ -374,13 +359,14 @@ QByteArray FlamePackExportTask::generateIndex()
|
||||||
QJsonObject obj;
|
QJsonObject obj;
|
||||||
obj["manifestType"] = "minecraftModpack";
|
obj["manifestType"] = "minecraftModpack";
|
||||||
obj["manifestVersion"] = 1;
|
obj["manifestVersion"] = 1;
|
||||||
obj["name"] = name;
|
obj["name"] = m_options.name;
|
||||||
obj["version"] = version;
|
obj["version"] = m_options.version;
|
||||||
obj["author"] = author;
|
obj["author"] = m_options.author;
|
||||||
obj["overrides"] = "overrides";
|
obj["overrides"] = "overrides";
|
||||||
if (mcInstance) {
|
|
||||||
QJsonObject version;
|
QJsonObject version;
|
||||||
auto profile = mcInstance->getPackProfile();
|
|
||||||
|
auto profile = m_options.instance->getPackProfile();
|
||||||
// collect all supported components
|
// collect all supported components
|
||||||
const ComponentPtr minecraft = profile->getComponent("net.minecraft");
|
const ComponentPtr minecraft = profile->getComponent("net.minecraft");
|
||||||
const ComponentPtr quilt = profile->getComponent("org.quiltmc.quilt-loader");
|
const ComponentPtr quilt = profile->getComponent("org.quiltmc.quilt-loader");
|
||||||
|
@ -411,15 +397,18 @@ QByteArray FlamePackExportTask::generateIndex()
|
||||||
loader["primary"] = true;
|
loader["primary"] = true;
|
||||||
version["modLoaders"] = QJsonArray({ loader });
|
version["modLoaders"] = QJsonArray({ loader });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_options.recommendedRAM > 0)
|
||||||
|
version["recommendedRam"] = m_options.recommendedRAM;
|
||||||
|
|
||||||
obj["minecraft"] = version;
|
obj["minecraft"] = version;
|
||||||
}
|
|
||||||
|
|
||||||
QJsonArray files;
|
QJsonArray files;
|
||||||
for (auto mod : resolvedFiles) {
|
for (auto mod : resolvedFiles) {
|
||||||
QJsonObject file;
|
QJsonObject file;
|
||||||
file["projectID"] = mod.addonId;
|
file["projectID"] = mod.addonId;
|
||||||
file["fileID"] = mod.version;
|
file["fileID"] = mod.version;
|
||||||
file["required"] = mod.enabled || !optionalFiles;
|
file["required"] = mod.enabled || !m_options.optionalFiles;
|
||||||
files << file;
|
files << file;
|
||||||
}
|
}
|
||||||
obj["files"] = files;
|
obj["files"] = files;
|
||||||
|
|
|
@ -19,22 +19,26 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "BaseInstance.h"
|
|
||||||
#include "MMCZip.h"
|
#include "MMCZip.h"
|
||||||
#include "minecraft/MinecraftInstance.h"
|
#include "minecraft/MinecraftInstance.h"
|
||||||
#include "modplatform/flame/FlameAPI.h"
|
#include "modplatform/flame/FlameAPI.h"
|
||||||
#include "tasks/Task.h"
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
|
struct FlamePackExportOptions {
|
||||||
|
QString name;
|
||||||
|
QString version;
|
||||||
|
QString author;
|
||||||
|
bool optionalFiles;
|
||||||
|
MinecraftInstancePtr instance;
|
||||||
|
QString output;
|
||||||
|
MMCZip::FilterFileFunction filter;
|
||||||
|
int recommendedRAM;
|
||||||
|
};
|
||||||
|
|
||||||
class FlamePackExportTask : public Task {
|
class FlamePackExportTask : public Task {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
FlamePackExportTask(const QString& name,
|
FlamePackExportTask(FlamePackExportOptions&& options);
|
||||||
const QString& version,
|
|
||||||
const QString& author,
|
|
||||||
bool optionalFiles,
|
|
||||||
InstancePtr instance,
|
|
||||||
const QString& output,
|
|
||||||
MMCZip::FilterFunction filter);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void executeTask() override;
|
void executeTask() override;
|
||||||
|
@ -45,13 +49,6 @@ class FlamePackExportTask : public Task {
|
||||||
static const QStringList FILE_EXTENSIONS;
|
static const QStringList FILE_EXTENSIONS;
|
||||||
|
|
||||||
// inputs
|
// inputs
|
||||||
const QString name, version, author;
|
|
||||||
const bool optionalFiles;
|
|
||||||
const InstancePtr instance;
|
|
||||||
MinecraftInstance* mcInstance;
|
|
||||||
const QDir gameRoot;
|
|
||||||
const QString output;
|
|
||||||
const MMCZip::FilterFunction filter;
|
|
||||||
|
|
||||||
struct ResolvedFile {
|
struct ResolvedFile {
|
||||||
int addonId;
|
int addonId;
|
||||||
|
@ -70,6 +67,9 @@ class FlamePackExportTask : public Task {
|
||||||
bool isMod;
|
bool isMod;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
FlamePackExportOptions m_options;
|
||||||
|
QDir m_gameRoot;
|
||||||
|
|
||||||
FlameAPI api;
|
FlameAPI api;
|
||||||
|
|
||||||
QFileInfoList files;
|
QFileInfoList files;
|
||||||
|
|
|
@ -27,6 +27,7 @@ static void loadMinecraftV1(Flame::Minecraft& m, QJsonObject& minecraft)
|
||||||
loadModloaderV1(loader, obj);
|
loadModloaderV1(loader, obj);
|
||||||
m.modLoaders.append(loader);
|
m.modLoaders.append(loader);
|
||||||
}
|
}
|
||||||
|
m.recommendedRAM = Json::ensureInteger(minecraft, "recommendedRam", 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void loadManifestV1(Flame::Manifest& pack, QJsonObject& manifest)
|
static void loadManifestV1(Flame::Manifest& pack, QJsonObject& manifest)
|
||||||
|
|
|
@ -67,6 +67,7 @@ struct Minecraft {
|
||||||
QString version;
|
QString version;
|
||||||
QString libraries;
|
QString libraries;
|
||||||
QList<Flame::Modloader> modLoaders;
|
QList<Flame::Modloader> modLoaders;
|
||||||
|
int recommendedRAM;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Manifest {
|
struct Manifest {
|
||||||
|
|
|
@ -40,7 +40,7 @@ ModrinthPackExportTask::ModrinthPackExportTask(const QString& name,
|
||||||
bool optionalFiles,
|
bool optionalFiles,
|
||||||
InstancePtr instance,
|
InstancePtr instance,
|
||||||
const QString& output,
|
const QString& output,
|
||||||
MMCZip::FilterFunction filter)
|
MMCZip::FilterFileFunction filter)
|
||||||
: name(name)
|
: name(name)
|
||||||
, version(version)
|
, version(version)
|
||||||
, summary(summary)
|
, summary(summary)
|
||||||
|
@ -63,7 +63,6 @@ bool ModrinthPackExportTask::abort()
|
||||||
{
|
{
|
||||||
if (task) {
|
if (task) {
|
||||||
task->abort();
|
task->abort();
|
||||||
emitAborted();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -158,6 +157,7 @@ void ModrinthPackExportTask::makeApiRequest()
|
||||||
task = api.currentVersions(pendingHashes.values(), "sha512", response);
|
task = api.currentVersions(pendingHashes.values(), "sha512", response);
|
||||||
connect(task.get(), &Task::succeeded, [this, response]() { parseApiResponse(response); });
|
connect(task.get(), &Task::succeeded, [this, response]() { parseApiResponse(response); });
|
||||||
connect(task.get(), &Task::failed, this, &ModrinthPackExportTask::emitFailed);
|
connect(task.get(), &Task::failed, this, &ModrinthPackExportTask::emitFailed);
|
||||||
|
connect(task.get(), &Task::aborted, this, &ModrinthPackExportTask::emitAborted);
|
||||||
task->start();
|
task->start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ class ModrinthPackExportTask : public Task {
|
||||||
bool optionalFiles,
|
bool optionalFiles,
|
||||||
InstancePtr instance,
|
InstancePtr instance,
|
||||||
const QString& output,
|
const QString& output,
|
||||||
MMCZip::FilterFunction filter);
|
MMCZip::FilterFileFunction filter);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void executeTask() override;
|
void executeTask() override;
|
||||||
|
@ -58,7 +58,7 @@ class ModrinthPackExportTask : public Task {
|
||||||
MinecraftInstance* mcInstance;
|
MinecraftInstance* mcInstance;
|
||||||
const QDir gameRoot;
|
const QDir gameRoot;
|
||||||
const QString output;
|
const QString output;
|
||||||
const MMCZip::FilterFunction filter;
|
const MMCZip::FilterFileFunction filter;
|
||||||
|
|
||||||
ModrinthAPI api;
|
ModrinthAPI api;
|
||||||
QFileInfoList files;
|
QFileInfoList files;
|
||||||
|
|
|
@ -58,6 +58,7 @@ class ByteArraySink : public Sink {
|
||||||
qWarning() << "ByteArraySink did not initialize the buffer because it's not addressable";
|
qWarning() << "ByteArraySink did not initialize the buffer because it's not addressable";
|
||||||
if (initAllValidators(request))
|
if (initAllValidators(request))
|
||||||
return Task::State::Running;
|
return Task::State::Running;
|
||||||
|
m_fail_reason = "Failed to initialize validators";
|
||||||
return Task::State::Failed;
|
return Task::State::Failed;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -69,12 +70,14 @@ class ByteArraySink : public Sink {
|
||||||
qWarning() << "ByteArraySink did not write the buffer because it's not addressable";
|
qWarning() << "ByteArraySink did not write the buffer because it's not addressable";
|
||||||
if (writeAllValidators(data))
|
if (writeAllValidators(data))
|
||||||
return Task::State::Running;
|
return Task::State::Running;
|
||||||
|
m_fail_reason = "Failed to write validators";
|
||||||
return Task::State::Failed;
|
return Task::State::Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto abort() -> Task::State override
|
auto abort() -> Task::State override
|
||||||
{
|
{
|
||||||
failAllValidators();
|
failAllValidators();
|
||||||
|
m_fail_reason = "Aborted";
|
||||||
return Task::State::Failed;
|
return Task::State::Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,12 +85,13 @@ class ByteArraySink : public Sink {
|
||||||
{
|
{
|
||||||
if (finalizeAllValidators(reply))
|
if (finalizeAllValidators(reply))
|
||||||
return Task::State::Succeeded;
|
return Task::State::Succeeded;
|
||||||
|
m_fail_reason = "Failed to finalize validators";
|
||||||
return Task::State::Failed;
|
return Task::State::Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto hasLocalData() -> bool override { return false; }
|
auto hasLocalData() -> bool override { return false; }
|
||||||
|
|
||||||
private:
|
protected:
|
||||||
std::shared_ptr<QByteArray> m_output;
|
std::shared_ptr<QByteArray> m_output;
|
||||||
};
|
};
|
||||||
} // namespace Net
|
} // namespace Net
|
||||||
|
|
|
@ -51,6 +51,7 @@ Task::State FileSink::init(QNetworkRequest& request)
|
||||||
// create a new save file and open it for writing
|
// create a new save file and open it for writing
|
||||||
if (!FS::ensureFilePathExists(m_filename)) {
|
if (!FS::ensureFilePathExists(m_filename)) {
|
||||||
qCCritical(taskNetLogC) << "Could not create folder for " + m_filename;
|
qCCritical(taskNetLogC) << "Could not create folder for " + m_filename;
|
||||||
|
m_fail_reason = "Could not create folder";
|
||||||
return Task::State::Failed;
|
return Task::State::Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,11 +59,13 @@ Task::State FileSink::init(QNetworkRequest& request)
|
||||||
m_output_file.reset(new PSaveFile(m_filename));
|
m_output_file.reset(new PSaveFile(m_filename));
|
||||||
if (!m_output_file->open(QIODevice::WriteOnly)) {
|
if (!m_output_file->open(QIODevice::WriteOnly)) {
|
||||||
qCCritical(taskNetLogC) << "Could not open " + m_filename + " for writing";
|
qCCritical(taskNetLogC) << "Could not open " + m_filename + " for writing";
|
||||||
|
m_fail_reason = "Could not open file";
|
||||||
return Task::State::Failed;
|
return Task::State::Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (initAllValidators(request))
|
if (initAllValidators(request))
|
||||||
return Task::State::Running;
|
return Task::State::Running;
|
||||||
|
m_fail_reason = "Failed to initialize validators";
|
||||||
return Task::State::Failed;
|
return Task::State::Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,6 +76,7 @@ Task::State FileSink::write(QByteArray& data)
|
||||||
m_output_file->cancelWriting();
|
m_output_file->cancelWriting();
|
||||||
m_output_file.reset();
|
m_output_file.reset();
|
||||||
m_wroteAnyData = false;
|
m_wroteAnyData = false;
|
||||||
|
m_fail_reason = "Failed to write validators";
|
||||||
return Task::State::Failed;
|
return Task::State::Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,13 +109,16 @@ Task::State FileSink::finalize(QNetworkReply& reply)
|
||||||
if (gotFile || m_wroteAnyData) {
|
if (gotFile || m_wroteAnyData) {
|
||||||
// ask validators for data consistency
|
// ask validators for data consistency
|
||||||
// we only do this for actual downloads, not 'your data is still the same' cache hits
|
// we only do this for actual downloads, not 'your data is still the same' cache hits
|
||||||
if (!finalizeAllValidators(reply))
|
if (!finalizeAllValidators(reply)) {
|
||||||
|
m_fail_reason = "Failed to finalize validators";
|
||||||
return Task::State::Failed;
|
return Task::State::Failed;
|
||||||
|
}
|
||||||
|
|
||||||
// nothing went wrong...
|
// nothing went wrong...
|
||||||
if (!m_output_file->commit()) {
|
if (!m_output_file->commit()) {
|
||||||
qCCritical(taskNetLogC) << "Failed to commit changes to " << m_filename;
|
qCCritical(taskNetLogC) << "Failed to commit changes to " << m_filename;
|
||||||
m_output_file->cancelWriting();
|
m_output_file->cancelWriting();
|
||||||
|
m_fail_reason = "Failed to commit changes";
|
||||||
return Task::State::Failed;
|
return Task::State::Failed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,7 +84,8 @@ void NetRequest::executeTask()
|
||||||
break;
|
break;
|
||||||
case State::Inactive:
|
case State::Inactive:
|
||||||
case State::Failed:
|
case State::Failed:
|
||||||
emit failed("Failed to initialize sink");
|
m_failReason = m_sink->failReason();
|
||||||
|
emit failed(m_sink->failReason());
|
||||||
emit finished();
|
emit finished();
|
||||||
return;
|
return;
|
||||||
case State::AbortedByUser:
|
case State::AbortedByUser:
|
||||||
|
@ -259,6 +260,7 @@ void NetRequest::downloadFinished()
|
||||||
} else if (m_state == State::Failed) {
|
} else if (m_state == State::Failed) {
|
||||||
qCDebug(logCat) << getUid().toString() << "Request failed in previous step:" << m_url.toString();
|
qCDebug(logCat) << getUid().toString() << "Request failed in previous step:" << m_url.toString();
|
||||||
m_sink->abort();
|
m_sink->abort();
|
||||||
|
m_failReason = m_reply->errorString();
|
||||||
emit failed(m_reply->errorString());
|
emit failed(m_reply->errorString());
|
||||||
emit finished();
|
emit finished();
|
||||||
return;
|
return;
|
||||||
|
@ -278,7 +280,8 @@ void NetRequest::downloadFinished()
|
||||||
if (m_state != State::Succeeded) {
|
if (m_state != State::Succeeded) {
|
||||||
qCDebug(logCat) << getUid().toString() << "Request failed to write:" << m_url.toString();
|
qCDebug(logCat) << getUid().toString() << "Request failed to write:" << m_url.toString();
|
||||||
m_sink->abort();
|
m_sink->abort();
|
||||||
emit failed("failed to write in sink");
|
m_failReason = m_sink->failReason();
|
||||||
|
emit failed(m_sink->failReason());
|
||||||
emit finished();
|
emit finished();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -289,7 +292,8 @@ void NetRequest::downloadFinished()
|
||||||
if (m_state != State::Succeeded) {
|
if (m_state != State::Succeeded) {
|
||||||
qCDebug(logCat) << getUid().toString() << "Request failed to finalize:" << m_url.toString();
|
qCDebug(logCat) << getUid().toString() << "Request failed to finalize:" << m_url.toString();
|
||||||
m_sink->abort();
|
m_sink->abort();
|
||||||
emit failed("failed to finalize the request");
|
m_failReason = m_sink->failReason();
|
||||||
|
emit failed(m_sink->failReason());
|
||||||
emit finished();
|
emit finished();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -305,7 +309,7 @@ void NetRequest::downloadReadyRead()
|
||||||
auto data = m_reply->readAll();
|
auto data = m_reply->readAll();
|
||||||
m_state = m_sink->write(data);
|
m_state = m_sink->write(data);
|
||||||
if (m_state == State::Failed) {
|
if (m_state == State::Failed) {
|
||||||
qCCritical(logCat) << getUid().toString() << "Failed to process response chunk";
|
qCCritical(logCat) << getUid().toString() << "Failed to process response chunk:" << m_sink->failReason();
|
||||||
}
|
}
|
||||||
// qDebug() << "Request" << m_url.toString() << "gained" << data.size() << "bytes";
|
// qDebug() << "Request" << m_url.toString() << "gained" << data.size() << "bytes";
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -36,74 +36,45 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "PasteUpload.h"
|
#include "PasteUpload.h"
|
||||||
#include "Application.h"
|
#include <qobject.h>
|
||||||
#include "BuildConfig.h"
|
|
||||||
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QFile>
|
|
||||||
#include <QHttpPart>
|
#include <QHttpPart>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
#include <QRegularExpression>
|
||||||
#include <QUrlQuery>
|
#include <QUrlQuery>
|
||||||
|
#include "logs/AnonymizeLog.h"
|
||||||
|
|
||||||
#include "net/Logging.h"
|
const std::array<PasteUpload::PasteTypeInfo, 4> PasteUpload::PasteTypes = { { { "0x0.st", "https://0x0.st", "" },
|
||||||
|
|
||||||
std::array<PasteUpload::PasteTypeInfo, 4> PasteUpload::PasteTypes = { { { "0x0.st", "https://0x0.st", "" },
|
|
||||||
{ "hastebin", "https://hst.sh", "/documents" },
|
{ "hastebin", "https://hst.sh", "/documents" },
|
||||||
{ "paste.gg", "https://paste.gg", "/api/v1/pastes" },
|
{ "paste.gg", "https://paste.gg", "/api/v1/pastes" },
|
||||||
{ "mclo.gs", "https://api.mclo.gs", "/1/log" } } };
|
{ "mclo.gs", "https://api.mclo.gs", "/1/log" } } };
|
||||||
|
|
||||||
PasteUpload::PasteUpload(QWidget* window, QString text, QString baseUrl, PasteType pasteType)
|
QNetworkReply* PasteUpload::getReply(QNetworkRequest& request)
|
||||||
: m_window(window), m_baseUrl(baseUrl), m_pasteType(pasteType), m_text(text.toUtf8())
|
|
||||||
{
|
{
|
||||||
if (m_baseUrl == "")
|
switch (m_paste_type) {
|
||||||
m_baseUrl = PasteTypes.at(pasteType).defaultBase;
|
case PasteUpload::NullPointer: {
|
||||||
|
QHttpMultiPart* multiPart = new QHttpMultiPart{ QHttpMultiPart::FormDataType, this };
|
||||||
// HACK: Paste's docs say the standard API path is at /api/<version> but the official instance paste.gg doesn't follow that??
|
|
||||||
if (pasteType == PasteGG && m_baseUrl == PasteTypes.at(pasteType).defaultBase)
|
|
||||||
m_uploadUrl = "https://api.paste.gg/v1/pastes";
|
|
||||||
else
|
|
||||||
m_uploadUrl = m_baseUrl + PasteTypes.at(pasteType).endpointPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
PasteUpload::~PasteUpload() {}
|
|
||||||
|
|
||||||
void PasteUpload::executeTask()
|
|
||||||
{
|
|
||||||
QNetworkRequest request{ QUrl(m_uploadUrl) };
|
|
||||||
QNetworkReply* rep{};
|
|
||||||
|
|
||||||
request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgentUncached().toUtf8());
|
|
||||||
|
|
||||||
switch (m_pasteType) {
|
|
||||||
case NullPointer: {
|
|
||||||
QHttpMultiPart* multiPart = new QHttpMultiPart{ QHttpMultiPart::FormDataType };
|
|
||||||
|
|
||||||
QHttpPart filePart;
|
QHttpPart filePart;
|
||||||
filePart.setBody(m_text);
|
filePart.setBody(m_log.toUtf8());
|
||||||
filePart.setHeader(QNetworkRequest::ContentTypeHeader, "text/plain");
|
filePart.setHeader(QNetworkRequest::ContentTypeHeader, "text/plain");
|
||||||
filePart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"file\"; filename=\"log.txt\"");
|
filePart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"file\"; filename=\"log.txt\"");
|
||||||
multiPart->append(filePart);
|
multiPart->append(filePart);
|
||||||
|
|
||||||
rep = APPLICATION->network()->post(request, multiPart);
|
return m_network->post(request, multiPart);
|
||||||
multiPart->setParent(rep);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case Hastebin: {
|
case PasteUpload::Hastebin: {
|
||||||
request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgentUncached().toUtf8());
|
return m_network->post(request, m_log.toUtf8());
|
||||||
rep = APPLICATION->network()->post(request, m_text);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case Mclogs: {
|
case PasteUpload::Mclogs: {
|
||||||
QUrlQuery postData;
|
QUrlQuery postData;
|
||||||
postData.addQueryItem("content", m_text);
|
postData.addQueryItem("content", m_log);
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||||
rep = APPLICATION->network()->post(request, postData.toString().toUtf8());
|
return m_network->post(request, postData.toString().toUtf8());
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case PasteGG: {
|
case PasteUpload::PasteGG: {
|
||||||
QJsonObject obj;
|
QJsonObject obj;
|
||||||
QJsonDocument doc;
|
QJsonDocument doc;
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
@ -114,7 +85,7 @@ void PasteUpload::executeTask()
|
||||||
QJsonObject logFileInfo;
|
QJsonObject logFileInfo;
|
||||||
QJsonObject logFileContentInfo;
|
QJsonObject logFileContentInfo;
|
||||||
logFileContentInfo.insert("format", "text");
|
logFileContentInfo.insert("format", "text");
|
||||||
logFileContentInfo.insert("value", QString::fromUtf8(m_text));
|
logFileContentInfo.insert("value", m_log);
|
||||||
logFileInfo.insert("name", "log.txt");
|
logFileInfo.insert("name", "log.txt");
|
||||||
logFileInfo.insert("content", logFileContentInfo);
|
logFileInfo.insert("content", logFileContentInfo);
|
||||||
files.append(logFileInfo);
|
files.append(logFileInfo);
|
||||||
|
@ -122,108 +93,127 @@ void PasteUpload::executeTask()
|
||||||
obj.insert("files", files);
|
obj.insert("files", files);
|
||||||
|
|
||||||
doc.setObject(obj);
|
doc.setObject(obj);
|
||||||
rep = APPLICATION->network()->post(request, doc.toJson());
|
return m_network->post(request, doc.toJson());
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress);
|
return nullptr;
|
||||||
connect(rep, &QNetworkReply::finished, this, &PasteUpload::downloadFinished);
|
};
|
||||||
|
|
||||||
connect(rep, &QNetworkReply::errorOccurred, this, &PasteUpload::downloadError);
|
auto PasteUpload::Sink::finalize(QNetworkReply& reply) -> Task::State
|
||||||
|
|
||||||
m_reply = std::shared_ptr<QNetworkReply>(rep);
|
|
||||||
|
|
||||||
setStatus(tr("Uploading to %1").arg(m_uploadUrl));
|
|
||||||
}
|
|
||||||
|
|
||||||
void PasteUpload::downloadError(QNetworkReply::NetworkError error)
|
|
||||||
{
|
{
|
||||||
// error happened during download.
|
if (!finalizeAllValidators(reply)) {
|
||||||
qCCritical(taskUploadLogC) << getUid().toString() << "Network error: " << error;
|
m_fail_reason = "Failed to finalize validators";
|
||||||
emitFailed(m_reply->errorString());
|
return Task::State::Failed;
|
||||||
}
|
}
|
||||||
|
int statusCode = reply.attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
|
|
||||||
void PasteUpload::downloadFinished()
|
if (reply.error() != QNetworkReply::NetworkError::NoError) {
|
||||||
{
|
m_fail_reason = QObject::tr("Network error: %1").arg(reply.errorString());
|
||||||
QByteArray data = m_reply->readAll();
|
return Task::State::Failed;
|
||||||
int statusCode = m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
||||||
|
|
||||||
if (m_reply->error() != QNetworkReply::NetworkError::NoError) {
|
|
||||||
emitFailed(tr("Network error: %1").arg(m_reply->errorString()));
|
|
||||||
m_reply.reset();
|
|
||||||
return;
|
|
||||||
} else if (statusCode != 200 && statusCode != 201) {
|
} else if (statusCode != 200 && statusCode != 201) {
|
||||||
QString reasonPhrase = m_reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
|
QString reasonPhrase = reply.attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
|
||||||
emitFailed(tr("Error: %1 returned unexpected status code %2 %3").arg(m_uploadUrl).arg(statusCode).arg(reasonPhrase));
|
m_fail_reason =
|
||||||
qCCritical(taskUploadLogC) << getUid().toString() << m_uploadUrl << " returned unexpected status code " << statusCode
|
QObject::tr("Error: %1 returned unexpected status code %2 %3").arg(m_d->url().toString()).arg(statusCode).arg(reasonPhrase);
|
||||||
<< " with body: " << data;
|
return Task::State::Failed;
|
||||||
m_reply.reset();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (m_pasteType) {
|
switch (m_d->m_paste_type) {
|
||||||
case NullPointer:
|
case PasteUpload::NullPointer:
|
||||||
m_pasteLink = QString::fromUtf8(data).trimmed();
|
m_d->m_pasteLink = QString::fromUtf8(*m_output).trimmed();
|
||||||
break;
|
break;
|
||||||
case Hastebin: {
|
case PasteUpload::Hastebin: {
|
||||||
QJsonDocument jsonDoc{ QJsonDocument::fromJson(data) };
|
QJsonParseError jsonError;
|
||||||
QJsonObject jsonObj{ jsonDoc.object() };
|
auto doc = QJsonDocument::fromJson(*m_output, &jsonError);
|
||||||
if (jsonObj.contains("key") && jsonObj["key"].isString()) {
|
if (jsonError.error != QJsonParseError::NoError) {
|
||||||
QString key = jsonDoc.object()["key"].toString();
|
qDebug() << "hastebin server did not reply with JSON" << jsonError.errorString();
|
||||||
m_pasteLink = m_baseUrl + "/" + key;
|
m_fail_reason =
|
||||||
|
QObject::tr("Failed to parse response from hastebin server: expected JSON but got an invalid response. Error: %1")
|
||||||
|
.arg(jsonError.errorString());
|
||||||
|
return Task::State::Failed;
|
||||||
|
}
|
||||||
|
auto obj = doc.object();
|
||||||
|
if (obj.contains("key") && obj["key"].isString()) {
|
||||||
|
QString key = doc.object()["key"].toString();
|
||||||
|
m_d->m_pasteLink = m_d->m_baseUrl + "/" + key;
|
||||||
} else {
|
} else {
|
||||||
emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl));
|
qDebug() << "Log upload failed:" << doc.toJson();
|
||||||
qCCritical(taskUploadLogC) << getUid().toString() << getUid().toString() << m_uploadUrl
|
m_fail_reason = QObject::tr("Error: %1 returned a malformed response body").arg(m_d->url().toString());
|
||||||
<< " returned malformed response body: " << data;
|
return Task::State::Failed;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Mclogs: {
|
case PasteUpload::Mclogs: {
|
||||||
QJsonDocument jsonDoc{ QJsonDocument::fromJson(data) };
|
QJsonParseError jsonError;
|
||||||
QJsonObject jsonObj{ jsonDoc.object() };
|
auto doc = QJsonDocument::fromJson(*m_output, &jsonError);
|
||||||
if (jsonObj.contains("success") && jsonObj["success"].isBool()) {
|
if (jsonError.error != QJsonParseError::NoError) {
|
||||||
bool success = jsonObj["success"].toBool();
|
qDebug() << "mclogs server did not reply with JSON" << jsonError.errorString();
|
||||||
|
m_fail_reason =
|
||||||
|
QObject::tr("Failed to parse response from mclogs server: expected JSON but got an invalid response. Error: %1")
|
||||||
|
.arg(jsonError.errorString());
|
||||||
|
return Task::State::Failed;
|
||||||
|
}
|
||||||
|
auto obj = doc.object();
|
||||||
|
if (obj.contains("success") && obj["success"].isBool()) {
|
||||||
|
bool success = obj["success"].toBool();
|
||||||
if (success) {
|
if (success) {
|
||||||
m_pasteLink = jsonObj["url"].toString();
|
m_d->m_pasteLink = obj["url"].toString();
|
||||||
} else {
|
} else {
|
||||||
QString error = jsonObj["error"].toString();
|
QString error = obj["error"].toString();
|
||||||
emitFailed(tr("Error: %1 returned an error: %2").arg(m_uploadUrl, error));
|
m_fail_reason = QObject::tr("Error: %1 returned an error: %2").arg(m_d->url().toString(), error);
|
||||||
qCCritical(taskUploadLogC) << getUid().toString() << m_uploadUrl << " returned error: " << error;
|
return Task::State::Failed;
|
||||||
qCCritical(taskUploadLogC) << getUid().toString() << "Response body: " << data;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl));
|
qDebug() << "Log upload failed:" << doc.toJson();
|
||||||
qCCritical(taskUploadLogC) << getUid().toString() << m_uploadUrl << " returned malformed response body: " << data;
|
m_fail_reason = QObject::tr("Error: %1 returned a malformed response body").arg(m_d->url().toString());
|
||||||
return;
|
return Task::State::Failed;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PasteGG:
|
case PasteUpload::PasteGG:
|
||||||
QJsonDocument jsonDoc{ QJsonDocument::fromJson(data) };
|
QJsonParseError jsonError;
|
||||||
QJsonObject jsonObj{ jsonDoc.object() };
|
auto doc = QJsonDocument::fromJson(*m_output, &jsonError);
|
||||||
if (jsonObj.contains("status") && jsonObj["status"].isString()) {
|
if (jsonError.error != QJsonParseError::NoError) {
|
||||||
QString status = jsonObj["status"].toString();
|
qDebug() << "pastegg server did not reply with JSON" << jsonError.errorString();
|
||||||
|
m_fail_reason =
|
||||||
|
QObject::tr("Failed to parse response from pasteGG server: expected JSON but got an invalid response. Error: %1")
|
||||||
|
.arg(jsonError.errorString());
|
||||||
|
return Task::State::Failed;
|
||||||
|
}
|
||||||
|
auto obj = doc.object();
|
||||||
|
if (obj.contains("status") && obj["status"].isString()) {
|
||||||
|
QString status = obj["status"].toString();
|
||||||
if (status == "success") {
|
if (status == "success") {
|
||||||
m_pasteLink = m_baseUrl + "/p/anonymous/" + jsonObj["result"].toObject()["id"].toString();
|
m_d->m_pasteLink = m_d->m_baseUrl + "/p/anonymous/" + obj["result"].toObject()["id"].toString();
|
||||||
} else {
|
} else {
|
||||||
QString error = jsonObj["error"].toString();
|
QString error = obj["error"].toString();
|
||||||
QString message =
|
QString message = (obj.contains("message") && obj["message"].isString()) ? obj["message"].toString() : "none";
|
||||||
(jsonObj.contains("message") && jsonObj["message"].isString()) ? jsonObj["message"].toString() : "none";
|
m_fail_reason =
|
||||||
emitFailed(tr("Error: %1 returned an error code: %2\nError message: %3").arg(m_uploadUrl, error, message));
|
QObject::tr("Error: %1 returned an error code: %2\nError message: %3").arg(m_d->url().toString(), error, message);
|
||||||
qCCritical(taskUploadLogC) << getUid().toString() << m_uploadUrl << " returned error: " << error;
|
return Task::State::Failed;
|
||||||
qCCritical(taskUploadLogC) << getUid().toString() << "Error message: " << message;
|
|
||||||
qCCritical(taskUploadLogC) << getUid().toString() << "Response body: " << data;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl));
|
qDebug() << "Log upload failed:" << doc.toJson();
|
||||||
qCCritical(taskUploadLogC) << getUid().toString() << m_uploadUrl << " returned malformed response body: " << data;
|
m_fail_reason = QObject::tr("Error: %1 returned a malformed response body").arg(m_d->url().toString());
|
||||||
return;
|
return Task::State::Failed;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
emitSucceeded();
|
return Task::State::Succeeded;
|
||||||
|
}
|
||||||
|
|
||||||
|
PasteUpload::PasteUpload(const QString& log, QString url, PasteType pasteType) : m_log(log), m_baseUrl(url), m_paste_type(pasteType)
|
||||||
|
{
|
||||||
|
anonymizeLog(m_log);
|
||||||
|
auto base = PasteUpload::PasteTypes.at(pasteType);
|
||||||
|
if (m_baseUrl.isEmpty())
|
||||||
|
m_baseUrl = base.defaultBase;
|
||||||
|
|
||||||
|
// HACK: Paste's docs say the standard API path is at /api/<version> but the official instance paste.gg doesn't follow that??
|
||||||
|
if (pasteType == PasteUpload::PasteGG && m_baseUrl == base.defaultBase)
|
||||||
|
m_url = "https://api.paste.gg/v1/pastes";
|
||||||
|
else
|
||||||
|
m_url = m_baseUrl + base.endpointPath;
|
||||||
|
|
||||||
|
m_sink.reset(new Sink(this));
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,15 +35,18 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QBuffer>
|
#include "net/ByteArraySink.h"
|
||||||
#include <QNetworkReply>
|
#include "net/NetRequest.h"
|
||||||
#include <QString>
|
|
||||||
#include <array>
|
|
||||||
#include <memory>
|
|
||||||
#include "tasks/Task.h"
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
class PasteUpload : public Task {
|
#include <QNetworkReply>
|
||||||
Q_OBJECT
|
#include <QRegularExpression>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
class PasteUpload : public Net::NetRequest {
|
||||||
public:
|
public:
|
||||||
enum PasteType : int {
|
enum PasteType : int {
|
||||||
// 0x0.st
|
// 0x0.st
|
||||||
|
@ -58,32 +61,36 @@ class PasteUpload : public Task {
|
||||||
First = NullPointer,
|
First = NullPointer,
|
||||||
Last = Mclogs
|
Last = Mclogs
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PasteTypeInfo {
|
struct PasteTypeInfo {
|
||||||
const QString name;
|
const QString name;
|
||||||
const QString defaultBase;
|
const QString defaultBase;
|
||||||
const QString endpointPath;
|
const QString endpointPath;
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::array<PasteTypeInfo, 4> PasteTypes;
|
static const std::array<PasteTypeInfo, 4> PasteTypes;
|
||||||
|
|
||||||
PasteUpload(QWidget* window, QString text, QString url, PasteType pasteType);
|
class Sink : public Net::ByteArraySink {
|
||||||
virtual ~PasteUpload();
|
public:
|
||||||
|
Sink(PasteUpload* p) : Net::ByteArraySink(std::make_shared<QByteArray>()), m_d(p) {};
|
||||||
|
virtual ~Sink() = default;
|
||||||
|
|
||||||
|
public:
|
||||||
|
auto finalize(QNetworkReply& reply) -> Task::State override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
PasteUpload* m_d;
|
||||||
|
};
|
||||||
|
friend Sink;
|
||||||
|
|
||||||
|
PasteUpload(const QString& log, QString url, PasteType pasteType);
|
||||||
|
virtual ~PasteUpload() = default;
|
||||||
|
|
||||||
QString pasteLink() { return m_pasteLink; }
|
QString pasteLink() { return m_pasteLink; }
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void executeTask();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QWidget* m_window;
|
virtual QNetworkReply* getReply(QNetworkRequest&) override;
|
||||||
|
QString m_log;
|
||||||
QString m_pasteLink;
|
QString m_pasteLink;
|
||||||
QString m_baseUrl;
|
QString m_baseUrl;
|
||||||
QString m_uploadUrl;
|
const PasteType m_paste_type;
|
||||||
PasteType m_pasteType;
|
|
||||||
QByteArray m_text;
|
|
||||||
std::shared_ptr<QNetworkReply> m_reply;
|
|
||||||
public slots:
|
|
||||||
void downloadError(QNetworkReply::NetworkError);
|
|
||||||
void downloadFinished();
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -52,6 +52,8 @@ class Sink {
|
||||||
|
|
||||||
virtual auto hasLocalData() -> bool = 0;
|
virtual auto hasLocalData() -> bool = 0;
|
||||||
|
|
||||||
|
QString failReason() const { return m_fail_reason; }
|
||||||
|
|
||||||
void addValidator(Validator* validator)
|
void addValidator(Validator* validator)
|
||||||
{
|
{
|
||||||
if (validator) {
|
if (validator) {
|
||||||
|
@ -95,5 +97,6 @@ class Sink {
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::vector<std::shared_ptr<Validator>> validators;
|
std::vector<std::shared_ptr<Validator>> validators;
|
||||||
|
QString m_fail_reason;
|
||||||
};
|
};
|
||||||
} // namespace Net
|
} // namespace Net
|
||||||
|
|
|
@ -86,6 +86,7 @@ auto ImgurAlbumCreation::Sink::write(QByteArray& data) -> Task::State
|
||||||
auto ImgurAlbumCreation::Sink::abort() -> Task::State
|
auto ImgurAlbumCreation::Sink::abort() -> Task::State
|
||||||
{
|
{
|
||||||
m_output.clear();
|
m_output.clear();
|
||||||
|
m_fail_reason = "Aborted";
|
||||||
return Task::State::Failed;
|
return Task::State::Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,11 +96,13 @@ auto ImgurAlbumCreation::Sink::finalize(QNetworkReply&) -> Task::State
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(m_output, &jsonError);
|
QJsonDocument doc = QJsonDocument::fromJson(m_output, &jsonError);
|
||||||
if (jsonError.error != QJsonParseError::NoError) {
|
if (jsonError.error != QJsonParseError::NoError) {
|
||||||
qDebug() << jsonError.errorString();
|
qDebug() << jsonError.errorString();
|
||||||
|
m_fail_reason = "Invalid json reply";
|
||||||
return Task::State::Failed;
|
return Task::State::Failed;
|
||||||
}
|
}
|
||||||
auto object = doc.object();
|
auto object = doc.object();
|
||||||
if (!object.value("success").toBool()) {
|
if (!object.value("success").toBool()) {
|
||||||
qDebug() << doc.toJson();
|
qDebug() << doc.toJson();
|
||||||
|
m_fail_reason = "Failed to create album";
|
||||||
return Task::State::Failed;
|
return Task::State::Failed;
|
||||||
}
|
}
|
||||||
m_result->deleteHash = object.value("data").toObject().value("deletehash").toString();
|
m_result->deleteHash = object.value("data").toObject().value("deletehash").toString();
|
||||||
|
|
|
@ -90,6 +90,7 @@ auto ImgurUpload::Sink::write(QByteArray& data) -> Task::State
|
||||||
auto ImgurUpload::Sink::abort() -> Task::State
|
auto ImgurUpload::Sink::abort() -> Task::State
|
||||||
{
|
{
|
||||||
m_output.clear();
|
m_output.clear();
|
||||||
|
m_fail_reason = "Aborted";
|
||||||
return Task::State::Failed;
|
return Task::State::Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,11 +100,13 @@ auto ImgurUpload::Sink::finalize(QNetworkReply&) -> Task::State
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(m_output, &jsonError);
|
QJsonDocument doc = QJsonDocument::fromJson(m_output, &jsonError);
|
||||||
if (jsonError.error != QJsonParseError::NoError) {
|
if (jsonError.error != QJsonParseError::NoError) {
|
||||||
qDebug() << "imgur server did not reply with JSON" << jsonError.errorString();
|
qDebug() << "imgur server did not reply with JSON" << jsonError.errorString();
|
||||||
|
m_fail_reason = "Invalid json reply";
|
||||||
return Task::State::Failed;
|
return Task::State::Failed;
|
||||||
}
|
}
|
||||||
auto object = doc.object();
|
auto object = doc.object();
|
||||||
if (!object.value("success").toBool()) {
|
if (!object.value("success").toBool()) {
|
||||||
qDebug() << "Screenshot upload not successful:" << doc.toJson();
|
qDebug() << "Screenshot upload not successful:" << doc.toJson();
|
||||||
|
m_fail_reason = "Screenshot was not uploaded successfully";
|
||||||
return Task::State::Failed;
|
return Task::State::Failed;
|
||||||
}
|
}
|
||||||
m_shot->m_imgurId = object.value("data").toObject().value("id").toString();
|
m_shot->m_imgurId = object.value("data").toObject().value("id").toString();
|
||||||
|
|
|
@ -118,10 +118,29 @@ void ConcurrentTask::executeNextSubTask()
|
||||||
}
|
}
|
||||||
if (m_queue.isEmpty()) {
|
if (m_queue.isEmpty()) {
|
||||||
if (m_doing.isEmpty()) {
|
if (m_doing.isEmpty()) {
|
||||||
if (m_failed.isEmpty())
|
if (m_failed.isEmpty()) {
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
else
|
} else if (m_failed.count() == 1) {
|
||||||
emitFailed(tr("One or more subtasks failed"));
|
auto task = m_failed.keys().first();
|
||||||
|
auto reason = task->failReason();
|
||||||
|
if (reason.isEmpty()) { // clearly a bug somewhere
|
||||||
|
reason = tr("Task failed");
|
||||||
|
}
|
||||||
|
emitFailed(reason);
|
||||||
|
} else {
|
||||||
|
QStringList failReason;
|
||||||
|
for (auto t : m_failed) {
|
||||||
|
auto reason = t->failReason();
|
||||||
|
if (!reason.isEmpty()) {
|
||||||
|
failReason << reason;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (failReason.isEmpty()) {
|
||||||
|
emitFailed(tr("Multiple subtasks failed"));
|
||||||
|
} else {
|
||||||
|
emitFailed(tr("Multiple subtasks failed\n%1").arg(failReason.join("\n")));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -196,6 +196,8 @@ void Task::logWarning(const QString& line)
|
||||||
{
|
{
|
||||||
qWarning() << line;
|
qWarning() << line;
|
||||||
m_Warnings.append(line);
|
m_Warnings.append(line);
|
||||||
|
|
||||||
|
emit warningLogged(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList Task::warnings() const
|
QStringList Task::warnings() const
|
||||||
|
|
|
@ -79,7 +79,6 @@ Q_DECLARE_METATYPE(TaskStepProgress)
|
||||||
|
|
||||||
using TaskStepProgressList = QList<std::shared_ptr<TaskStepProgress>>;
|
using TaskStepProgressList = QList<std::shared_ptr<TaskStepProgress>>;
|
||||||
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Represents a task that has to be done.
|
* Represents a task that has to be done.
|
||||||
* To create a task, you need to subclass this class, implement the executeTask() method and call
|
* To create a task, you need to subclass this class, implement the executeTask() method and call
|
||||||
|
@ -146,6 +145,7 @@ class Task : public QObject, public QRunnable {
|
||||||
void failed(QString reason);
|
void failed(QString reason);
|
||||||
void status(QString status);
|
void status(QString status);
|
||||||
void details(QString details);
|
void details(QString details);
|
||||||
|
void warningLogged(const QString& warning);
|
||||||
void stepProgress(TaskStepProgress const& task_progress);
|
void stepProgress(TaskStepProgress const& task_progress);
|
||||||
|
|
||||||
//! Emitted when the canAbort() status has changed. */
|
//! Emitted when the canAbort() status has changed. */
|
||||||
|
|
|
@ -38,10 +38,15 @@
|
||||||
#include "GuiUtil.h"
|
#include "GuiUtil.h"
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
#include <QBuffer>
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
|
|
||||||
|
#include "FileSystem.h"
|
||||||
|
#include "logs/AnonymizeLog.h"
|
||||||
|
#include "net/NetJob.h"
|
||||||
|
#include "net/NetRequest.h"
|
||||||
#include "net/PasteUpload.h"
|
#include "net/PasteUpload.h"
|
||||||
#include "ui/dialogs/CustomMessageBox.h"
|
#include "ui/dialogs/CustomMessageBox.h"
|
||||||
#include "ui/dialogs/ProgressDialog.h"
|
#include "ui/dialogs/ProgressDialog.h"
|
||||||
|
@ -74,33 +79,34 @@ QString truncateLogForMclogs(const QString& logContent)
|
||||||
return logContent;
|
return logContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<QString> GuiUtil::uploadPaste(const QString& name, const QFileInfo& filePath, QWidget* parentWidget)
|
||||||
|
{
|
||||||
|
return uploadPaste(name, FS::read(filePath.absoluteFilePath()), parentWidget);
|
||||||
|
};
|
||||||
|
|
||||||
std::optional<QString> GuiUtil::uploadPaste(const QString& name, const QString& text, QWidget* parentWidget)
|
std::optional<QString> GuiUtil::uploadPaste(const QString& name, const QString& text, QWidget* parentWidget)
|
||||||
{
|
{
|
||||||
ProgressDialog dialog(parentWidget);
|
ProgressDialog dialog(parentWidget);
|
||||||
auto pasteTypeSetting = static_cast<PasteUpload::PasteType>(APPLICATION->settings()->get("PastebinType").toInt());
|
auto pasteType = static_cast<PasteUpload::PasteType>(APPLICATION->settings()->get("PastebinType").toInt());
|
||||||
auto pasteCustomAPIBaseSetting = APPLICATION->settings()->get("PastebinCustomAPIBase").toString();
|
auto baseURL = APPLICATION->settings()->get("PastebinCustomAPIBase").toString();
|
||||||
bool shouldTruncate = false;
|
bool shouldTruncate = false;
|
||||||
|
|
||||||
{
|
if (baseURL.isEmpty())
|
||||||
QUrl baseUrl;
|
baseURL = PasteUpload::PasteTypes[pasteType].defaultBase;
|
||||||
if (pasteCustomAPIBaseSetting.isEmpty())
|
|
||||||
baseUrl = PasteUpload::PasteTypes[pasteTypeSetting].defaultBase;
|
|
||||||
else
|
|
||||||
baseUrl = pasteCustomAPIBaseSetting;
|
|
||||||
|
|
||||||
if (baseUrl.isValid()) {
|
if (auto url = QUrl(baseURL); url.isValid()) {
|
||||||
auto response = CustomMessageBox::selectable(parentWidget, QObject::tr("Confirm Upload"),
|
auto response = CustomMessageBox::selectable(parentWidget, QObject::tr("Confirm Upload"),
|
||||||
QObject::tr("You are about to upload \"%1\" to %2.\n"
|
QObject::tr("You are about to upload \"%1\" to %2.\n"
|
||||||
"You should double-check for personal information.\n\n"
|
"You should double-check for personal information.\n\n"
|
||||||
"Are you sure?")
|
"Are you sure?")
|
||||||
.arg(name, baseUrl.host()),
|
.arg(name, url.host()),
|
||||||
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
|
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
|
||||||
->exec();
|
->exec();
|
||||||
|
|
||||||
if (response != QMessageBox::Yes)
|
if (response != QMessageBox::Yes)
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
if (baseUrl.toString() == "https://api.mclo.gs" && text.count("\n") > MaxMclogsLines) {
|
if (baseURL == "https://api.mclo.gs" && text.count("\n") > MaxMclogsLines) {
|
||||||
auto truncateResponse = CustomMessageBox::selectable(
|
auto truncateResponse = CustomMessageBox::selectable(
|
||||||
parentWidget, QObject::tr("Confirm Truncation"),
|
parentWidget, QObject::tr("Confirm Truncation"),
|
||||||
QObject::tr("The log has %1 lines, exceeding mclo.gs' limit of %2.\n"
|
QObject::tr("The log has %1 lines, exceeding mclo.gs' limit of %2.\n"
|
||||||
|
@ -121,33 +127,46 @@ std::optional<QString> GuiUtil::uploadPaste(const QString& name, const QString&
|
||||||
shouldTruncate = truncateResponse == QMessageBox::Yes;
|
shouldTruncate = truncateResponse == QMessageBox::Yes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
QString textToUpload = text;
|
QString textToUpload = text;
|
||||||
if (shouldTruncate) {
|
if (shouldTruncate) {
|
||||||
textToUpload = truncateLogForMclogs(text);
|
textToUpload = truncateLogForMclogs(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<PasteUpload> paste(new PasteUpload(parentWidget, textToUpload, pasteCustomAPIBaseSetting, pasteTypeSetting));
|
auto job = NetJob::Ptr(new NetJob("Log Upload", APPLICATION->network()));
|
||||||
|
|
||||||
dialog.execWithTask(paste.get());
|
auto pasteJob = new PasteUpload(textToUpload, baseURL, pasteType);
|
||||||
if (!paste->wasSuccessful()) {
|
job->addNetAction(Net::NetRequest::Ptr(pasteJob));
|
||||||
CustomMessageBox::selectable(parentWidget, QObject::tr("Upload failed"), paste->failReason(), QMessageBox::Critical)->exec();
|
QObject::connect(job.get(), &Task::failed, [parentWidget](QString reason) {
|
||||||
return QString();
|
CustomMessageBox::selectable(parentWidget, QObject::tr("Failed to upload logs!"), reason, QMessageBox::Critical)->show();
|
||||||
} else {
|
});
|
||||||
const QString link = paste->pasteLink();
|
QObject::connect(job.get(), &Task::aborted, [parentWidget] {
|
||||||
setClipboardText(link);
|
CustomMessageBox::selectable(parentWidget, QObject::tr("Logs upload aborted"),
|
||||||
|
QObject::tr("The task has been aborted by the user."), QMessageBox::Information)
|
||||||
|
->show();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (dialog.execWithTask(job.get()) == QDialog::Accepted) {
|
||||||
|
if (pasteJob->pasteLink().isEmpty()) {
|
||||||
|
CustomMessageBox::selectable(parentWidget, QObject::tr("Failed to upload logs!"), "The upload link is empty",
|
||||||
|
QMessageBox::Critical)
|
||||||
|
->show();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
setClipboardText(pasteJob->pasteLink());
|
||||||
CustomMessageBox::selectable(
|
CustomMessageBox::selectable(
|
||||||
parentWidget, QObject::tr("Upload finished"),
|
parentWidget, QObject::tr("Upload finished"),
|
||||||
QObject::tr("The <a href=\"%1\">link to the uploaded log</a> has been placed in your clipboard.").arg(link),
|
QObject::tr("The <a href=\"%1\">link to the uploaded log</a> has been placed in your clipboard.").arg(pasteJob->pasteLink()),
|
||||||
QMessageBox::Information)
|
QMessageBox::Information)
|
||||||
->exec();
|
->exec();
|
||||||
return link;
|
return pasteJob->pasteLink();
|
||||||
}
|
}
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void GuiUtil::setClipboardText(const QString& text)
|
void GuiUtil::setClipboardText(QString text)
|
||||||
{
|
{
|
||||||
|
anonymizeLog(text);
|
||||||
QApplication::clipboard()->setText(text);
|
QApplication::clipboard()->setText(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <QFileInfo>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
namespace GuiUtil {
|
namespace GuiUtil {
|
||||||
std::optional<QString> uploadPaste(const QString& name, const QString& text, QWidget* parentWidget);
|
std::optional<QString> uploadPaste(const QString& name, const QFileInfo& filePath, QWidget* parentWidget);
|
||||||
void setClipboardText(const QString& text);
|
std::optional<QString> uploadPaste(const QString& name, const QString& data, QWidget* parentWidget);
|
||||||
|
void setClipboardText(QString text);
|
||||||
QStringList BrowseForFiles(QString context, QString caption, QString filter, QString defaultPath, QWidget* parentWidget);
|
QStringList BrowseForFiles(QString context, QString caption, QString filter, QString defaultPath, QWidget* parentWidget);
|
||||||
QString BrowseForFile(QString context, QString caption, QString filter, QString defaultPath, QWidget* parentWidget);
|
QString BrowseForFile(QString context, QString caption, QString filter, QString defaultPath, QWidget* parentWidget);
|
||||||
} // namespace GuiUtil
|
} // namespace GuiUtil
|
||||||
|
|
|
@ -71,6 +71,7 @@
|
||||||
#include <QToolButton>
|
#include <QToolButton>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <QWidgetAction>
|
#include <QWidgetAction>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include <BaseInstance.h>
|
#include <BaseInstance.h>
|
||||||
#include <BuildConfig.h>
|
#include <BuildConfig.h>
|
||||||
|
@ -90,8 +91,10 @@
|
||||||
#include <updater/ExternalUpdater.h>
|
#include <updater/ExternalUpdater.h>
|
||||||
#include "InstanceWindow.h"
|
#include "InstanceWindow.h"
|
||||||
|
|
||||||
|
#include "ui/GuiUtil.h"
|
||||||
#include "ui/dialogs/AboutDialog.h"
|
#include "ui/dialogs/AboutDialog.h"
|
||||||
#include "ui/dialogs/CopyInstanceDialog.h"
|
#include "ui/dialogs/CopyInstanceDialog.h"
|
||||||
|
#include "ui/dialogs/CreateShortcutDialog.h"
|
||||||
#include "ui/dialogs/CustomMessageBox.h"
|
#include "ui/dialogs/CustomMessageBox.h"
|
||||||
#include "ui/dialogs/ExportInstanceDialog.h"
|
#include "ui/dialogs/ExportInstanceDialog.h"
|
||||||
#include "ui/dialogs/ExportPackDialog.h"
|
#include "ui/dialogs/ExportPackDialog.h"
|
||||||
|
@ -235,6 +238,16 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
|
||||||
ui->actionViewJavaFolder->setEnabled(BuildConfig.JAVA_DOWNLOADER_ENABLED);
|
ui->actionViewJavaFolder->setEnabled(BuildConfig.JAVA_DOWNLOADER_ENABLED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{ // logs upload
|
||||||
|
|
||||||
|
auto menu = new QMenu(this);
|
||||||
|
for (auto file : QDir("logs").entryInfoList(QDir::Files)) {
|
||||||
|
auto action = menu->addAction(file.fileName());
|
||||||
|
connect(action, &QAction::triggered, this, [this, file] { GuiUtil::uploadPaste(file.fileName(), file, this); });
|
||||||
|
}
|
||||||
|
ui->actionUploadLog->setMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
// add the toolbar toggles to the view menu
|
// add the toolbar toggles to the view menu
|
||||||
ui->viewMenu->addAction(ui->instanceToolBar->toggleViewAction());
|
ui->viewMenu->addAction(ui->instanceToolBar->toggleViewAction());
|
||||||
ui->viewMenu->addAction(ui->newsToolBar->toggleViewAction());
|
ui->viewMenu->addAction(ui->newsToolBar->toggleViewAction());
|
||||||
|
@ -1383,6 +1396,14 @@ void MainWindow::on_actionDeleteInstance_triggered()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_selectedInstance->isRunning()) {
|
||||||
|
CustomMessageBox::selectable(this, tr("Cannot Delete Running Instance"),
|
||||||
|
tr("The selected instance is currently running and cannot be deleted. Please stop the instance before "
|
||||||
|
"attempting to delete it."),
|
||||||
|
QMessageBox::Warning, QMessageBox::Ok)
|
||||||
|
->exec();
|
||||||
|
return;
|
||||||
|
}
|
||||||
auto id = m_selectedInstance->id();
|
auto id = m_selectedInstance->id();
|
||||||
|
|
||||||
auto response = CustomMessageBox::selectable(this, tr("Confirm Deletion"),
|
auto response = CustomMessageBox::selectable(this, tr("Confirm Deletion"),
|
||||||
|
@ -1419,15 +1440,18 @@ void MainWindow::on_actionExportInstanceZip_triggered()
|
||||||
void MainWindow::on_actionExportInstanceMrPack_triggered()
|
void MainWindow::on_actionExportInstanceMrPack_triggered()
|
||||||
{
|
{
|
||||||
if (m_selectedInstance) {
|
if (m_selectedInstance) {
|
||||||
ExportPackDialog dlg(m_selectedInstance, this);
|
auto instance = std::dynamic_pointer_cast<MinecraftInstance>(m_selectedInstance);
|
||||||
|
if (instance != nullptr) {
|
||||||
|
ExportPackDialog dlg(instance, this);
|
||||||
dlg.exec();
|
dlg.exec();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::on_actionExportInstanceFlamePack_triggered()
|
void MainWindow::on_actionExportInstanceFlamePack_triggered()
|
||||||
{
|
{
|
||||||
if (m_selectedInstance) {
|
if (m_selectedInstance) {
|
||||||
auto instance = dynamic_cast<MinecraftInstance*>(m_selectedInstance.get());
|
auto instance = std::dynamic_pointer_cast<MinecraftInstance>(m_selectedInstance);
|
||||||
if (instance) {
|
if (instance) {
|
||||||
if (auto cmp = instance->getPackProfile()->getComponent("net.minecraft");
|
if (auto cmp = instance->getPackProfile()->getComponent("net.minecraft");
|
||||||
cmp && cmp->getVersionFile() && cmp->getVersionFile()->type == "snapshot") {
|
cmp && cmp->getVersionFile() && cmp->getVersionFile()->type == "snapshot") {
|
||||||
|
@ -1436,7 +1460,7 @@ void MainWindow::on_actionExportInstanceFlamePack_triggered()
|
||||||
msgBox.exec();
|
msgBox.exec();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ExportPackDialog dlg(m_selectedInstance, this, ModPlatform::ResourceProvider::FLAME);
|
ExportPackDialog dlg(instance, this, ModPlatform::ResourceProvider::FLAME);
|
||||||
dlg.exec();
|
dlg.exec();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1510,139 +1534,11 @@ void MainWindow::on_actionCreateInstanceShortcut_triggered()
|
||||||
{
|
{
|
||||||
if (!m_selectedInstance)
|
if (!m_selectedInstance)
|
||||||
return;
|
return;
|
||||||
auto desktopPath = FS::getDesktopDir();
|
|
||||||
if (desktopPath.isEmpty()) {
|
CreateShortcutDialog shortcutDlg(m_selectedInstance, this);
|
||||||
// TODO come up with an alternative solution (open "save file" dialog)
|
if (!shortcutDlg.exec())
|
||||||
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Couldn't find desktop?!"));
|
|
||||||
return;
|
return;
|
||||||
}
|
shortcutDlg.createShortcut();
|
||||||
|
|
||||||
QString desktopFilePath;
|
|
||||||
QString appPath = QApplication::applicationFilePath();
|
|
||||||
QString iconPath;
|
|
||||||
QStringList args;
|
|
||||||
#if defined(Q_OS_MACOS)
|
|
||||||
appPath = QApplication::applicationFilePath();
|
|
||||||
if (appPath.startsWith("/private/var/")) {
|
|
||||||
QMessageBox::critical(this, tr("Create instance shortcut"),
|
|
||||||
tr("The launcher is in the folder it was extracted from, therefore it cannot create shortcuts."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto pIcon = APPLICATION->icons()->icon(m_selectedInstance->iconKey());
|
|
||||||
if (pIcon == nullptr) {
|
|
||||||
pIcon = APPLICATION->icons()->icon("grass");
|
|
||||||
}
|
|
||||||
|
|
||||||
iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "Icon.icns");
|
|
||||||
|
|
||||||
QFile iconFile(iconPath);
|
|
||||||
if (!iconFile.open(QFile::WriteOnly)) {
|
|
||||||
QMessageBox::critical(this, tr("Create instance Application"), tr("Failed to create icon for Application."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QIcon icon = pIcon->icon();
|
|
||||||
|
|
||||||
bool success = icon.pixmap(1024, 1024).save(iconPath, "ICNS");
|
|
||||||
iconFile.close();
|
|
||||||
|
|
||||||
if (!success) {
|
|
||||||
iconFile.remove();
|
|
||||||
QMessageBox::critical(this, tr("Create instance Application"), tr("Failed to create icon for Application."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
|
||||||
if (appPath.startsWith("/tmp/.mount_")) {
|
|
||||||
// AppImage!
|
|
||||||
appPath = QProcessEnvironment::systemEnvironment().value(QStringLiteral("APPIMAGE"));
|
|
||||||
if (appPath.isEmpty()) {
|
|
||||||
QMessageBox::critical(this, tr("Create instance shortcut"),
|
|
||||||
tr("Launcher is running as misconfigured AppImage? ($APPIMAGE environment variable is missing)"));
|
|
||||||
} else if (appPath.endsWith("/")) {
|
|
||||||
appPath.chop(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto icon = APPLICATION->icons()->icon(m_selectedInstance->iconKey());
|
|
||||||
if (icon == nullptr) {
|
|
||||||
icon = APPLICATION->icons()->icon("grass");
|
|
||||||
}
|
|
||||||
|
|
||||||
iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "icon.png");
|
|
||||||
|
|
||||||
QFile iconFile(iconPath);
|
|
||||||
if (!iconFile.open(QFile::WriteOnly)) {
|
|
||||||
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
bool success = icon->icon().pixmap(64, 64).save(&iconFile, "PNG");
|
|
||||||
iconFile.close();
|
|
||||||
|
|
||||||
if (!success) {
|
|
||||||
iconFile.remove();
|
|
||||||
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DesktopServices::isFlatpak()) {
|
|
||||||
desktopFilePath = FS::PathCombine(desktopPath, FS::RemoveInvalidFilenameChars(m_selectedInstance->name()) + ".desktop");
|
|
||||||
QFileDialog fileDialog;
|
|
||||||
// workaround to make sure the portal file dialog opens in the desktop directory
|
|
||||||
fileDialog.setDirectoryUrl(desktopPath);
|
|
||||||
desktopFilePath = fileDialog.getSaveFileName(this, tr("Create Shortcut"), desktopFilePath, tr("Desktop Entries") + " (*.desktop)");
|
|
||||||
if (desktopFilePath.isEmpty())
|
|
||||||
return; // file dialog canceled by user
|
|
||||||
appPath = "flatpak";
|
|
||||||
args.append({ "run", BuildConfig.LAUNCHER_APPID });
|
|
||||||
}
|
|
||||||
|
|
||||||
#elif defined(Q_OS_WIN)
|
|
||||||
auto icon = APPLICATION->icons()->icon(m_selectedInstance->iconKey());
|
|
||||||
if (icon == nullptr) {
|
|
||||||
icon = APPLICATION->icons()->icon("grass");
|
|
||||||
}
|
|
||||||
|
|
||||||
iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "icon.ico");
|
|
||||||
|
|
||||||
// part of fix for weird bug involving the window icon being replaced
|
|
||||||
// dunno why it happens, but this 2-line fix seems to be enough, so w/e
|
|
||||||
auto appIcon = APPLICATION->getThemedIcon("logo");
|
|
||||||
|
|
||||||
QFile iconFile(iconPath);
|
|
||||||
if (!iconFile.open(QFile::WriteOnly)) {
|
|
||||||
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
bool success = icon->icon().pixmap(64, 64).save(&iconFile, "ICO");
|
|
||||||
iconFile.close();
|
|
||||||
|
|
||||||
// restore original window icon
|
|
||||||
QGuiApplication::setWindowIcon(appIcon);
|
|
||||||
|
|
||||||
if (!success) {
|
|
||||||
iconFile.remove();
|
|
||||||
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
#else
|
|
||||||
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Not supported on your platform!"));
|
|
||||||
return;
|
|
||||||
#endif
|
|
||||||
args.append({ "--launch", m_selectedInstance->id() });
|
|
||||||
if (FS::createShortcut(desktopFilePath, appPath, args, m_selectedInstance->name(), iconPath)) {
|
|
||||||
#if not defined(Q_OS_MACOS)
|
|
||||||
QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance on your desktop!"));
|
|
||||||
#else
|
|
||||||
QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance!"));
|
|
||||||
#endif
|
|
||||||
} else {
|
|
||||||
#if not defined(Q_OS_MACOS)
|
|
||||||
iconFile.remove();
|
|
||||||
#endif
|
|
||||||
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create instance shortcut!"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::taskEnd()
|
void MainWindow::taskEnd()
|
||||||
|
|
|
@ -131,7 +131,7 @@
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>800</width>
|
<width>800</width>
|
||||||
<height>27</height>
|
<height>22</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QMenu" name="fileMenu">
|
<widget class="QMenu" name="fileMenu">
|
||||||
|
@ -215,6 +215,7 @@
|
||||||
</property>
|
</property>
|
||||||
<addaction name="actionClearMetadata"/>
|
<addaction name="actionClearMetadata"/>
|
||||||
<addaction name="actionReportBug"/>
|
<addaction name="actionReportBug"/>
|
||||||
|
<addaction name="actionUploadLog"/>
|
||||||
<addaction name="actionAddToPATH"/>
|
<addaction name="actionAddToPATH"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionMATRIX"/>
|
<addaction name="actionMATRIX"/>
|
||||||
|
@ -235,8 +236,7 @@
|
||||||
</widget>
|
</widget>
|
||||||
<action name="actionMoreNews">
|
<action name="actionMoreNews">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="news">
|
<iconset theme="news"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>More news...</string>
|
<string>More news...</string>
|
||||||
|
@ -250,8 +250,7 @@
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="cat">
|
<iconset theme="cat"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Meow</string>
|
<string>&Meow</string>
|
||||||
|
@ -286,8 +285,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionAddInstance">
|
<action name="actionAddInstance">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="new">
|
<iconset theme="new"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Add Instanc&e...</string>
|
<string>Add Instanc&e...</string>
|
||||||
|
@ -298,8 +296,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionCheckUpdate">
|
<action name="actionCheckUpdate">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="checkupdate">
|
<iconset theme="checkupdate"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Update...</string>
|
<string>&Update...</string>
|
||||||
|
@ -313,8 +310,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionSettings">
|
<action name="actionSettings">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="settings">
|
<iconset theme="settings"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Setti&ngs...</string>
|
<string>Setti&ngs...</string>
|
||||||
|
@ -328,8 +324,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionManageAccounts">
|
<action name="actionManageAccounts">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="accounts">
|
<iconset theme="accounts"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Manage Accounts...</string>
|
<string>&Manage Accounts...</string>
|
||||||
|
@ -337,8 +332,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionLaunchInstance">
|
<action name="actionLaunchInstance">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="launch">
|
<iconset theme="launch"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Launch</string>
|
<string>&Launch</string>
|
||||||
|
@ -349,8 +343,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionKillInstance">
|
<action name="actionKillInstance">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="status-bad">
|
<iconset theme="status-bad"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Kill</string>
|
<string>&Kill</string>
|
||||||
|
@ -364,8 +357,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionRenameInstance">
|
<action name="actionRenameInstance">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="rename">
|
<iconset theme="rename"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Rename</string>
|
<string>Rename</string>
|
||||||
|
@ -376,8 +368,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionChangeInstGroup">
|
<action name="actionChangeInstGroup">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="tag">
|
<iconset theme="tag"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Change Group...</string>
|
<string>&Change Group...</string>
|
||||||
|
@ -399,8 +390,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionEditInstance">
|
<action name="actionEditInstance">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="settings">
|
<iconset theme="settings"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Edit...</string>
|
<string>&Edit...</string>
|
||||||
|
@ -414,8 +404,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionViewSelectedInstFolder">
|
<action name="actionViewSelectedInstFolder">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="viewfolder">
|
<iconset theme="viewfolder"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Folder</string>
|
<string>&Folder</string>
|
||||||
|
@ -426,8 +415,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionDeleteInstance">
|
<action name="actionDeleteInstance">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="delete">
|
<iconset theme="delete"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Dele&te</string>
|
<string>Dele&te</string>
|
||||||
|
@ -441,8 +429,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionCopyInstance">
|
<action name="actionCopyInstance">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="copy">
|
<iconset theme="copy"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Cop&y...</string>
|
<string>Cop&y...</string>
|
||||||
|
@ -456,8 +443,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionExportInstance">
|
<action name="actionExportInstance">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="export">
|
<iconset theme="export"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>E&xport...</string>
|
<string>E&xport...</string>
|
||||||
|
@ -468,8 +454,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionExportInstanceZip">
|
<action name="actionExportInstanceZip">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="launcher">
|
<iconset theme="launcher"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Prism Launcher (zip)</string>
|
<string>Prism Launcher (zip)</string>
|
||||||
|
@ -477,8 +462,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionExportInstanceMrPack">
|
<action name="actionExportInstanceMrPack">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="modrinth">
|
<iconset theme="modrinth"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Modrinth (mrpack)</string>
|
<string>Modrinth (mrpack)</string>
|
||||||
|
@ -486,8 +470,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionExportInstanceFlamePack">
|
<action name="actionExportInstanceFlamePack">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="flame">
|
<iconset theme="flame"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>CurseForge (zip)</string>
|
<string>CurseForge (zip)</string>
|
||||||
|
@ -495,20 +478,18 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionCreateInstanceShortcut">
|
<action name="actionCreateInstanceShortcut">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="shortcut">
|
<iconset theme="shortcut"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Create Shortcut</string>
|
<string>Create Shortcut</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Creates a shortcut on your desktop to launch the selected instance.</string>
|
<string>Creates a shortcut on a selected folder to launch the selected instance.</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionNoAccountsAdded">
|
<action name="actionNoAccountsAdded">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="noaccount">
|
<iconset theme="noaccount"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>No accounts added!</string>
|
<string>No accounts added!</string>
|
||||||
|
@ -519,8 +500,7 @@
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="noaccount">
|
<iconset theme="noaccount"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>No Default Account</string>
|
<string>No Default Account</string>
|
||||||
|
@ -531,8 +511,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionCloseWindow">
|
<action name="actionCloseWindow">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="status-bad">
|
<iconset theme="status-bad"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Close &Window</string>
|
<string>Close &Window</string>
|
||||||
|
@ -546,8 +525,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionViewInstanceFolder">
|
<action name="actionViewInstanceFolder">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="viewfolder">
|
<iconset theme="viewfolder"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Instances</string>
|
<string>&Instances</string>
|
||||||
|
@ -558,8 +536,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionViewLauncherRootFolder">
|
<action name="actionViewLauncherRootFolder">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="viewfolder">
|
<iconset theme="viewfolder"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Launcher &Root</string>
|
<string>Launcher &Root</string>
|
||||||
|
@ -570,8 +547,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionViewCentralModsFolder">
|
<action name="actionViewCentralModsFolder">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="centralmods">
|
<iconset theme="centralmods"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Central Mods</string>
|
<string>&Central Mods</string>
|
||||||
|
@ -582,8 +558,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionViewSkinsFolder">
|
<action name="actionViewSkinsFolder">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="viewfolder">
|
<iconset theme="viewfolder"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Skins</string>
|
<string>&Skins</string>
|
||||||
|
@ -594,8 +569,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionViewIconsFolder">
|
<action name="actionViewIconsFolder">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="viewfolder">
|
<iconset theme="viewfolder"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Instance Icons</string>
|
<string>Instance Icons</string>
|
||||||
|
@ -606,8 +580,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionViewLogsFolder">
|
<action name="actionViewLogsFolder">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="viewfolder">
|
<iconset theme="viewfolder"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Logs</string>
|
<string>Logs</string>
|
||||||
|
@ -623,8 +596,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionReportBug">
|
<action name="actionReportBug">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="bug">
|
<iconset theme="bug"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Report a Bug or Suggest a Feature</string>
|
<string>Report a Bug or Suggest a Feature</string>
|
||||||
|
@ -635,8 +607,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionDISCORD">
|
<action name="actionDISCORD">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="discord">
|
<iconset theme="discord"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Discord Guild</string>
|
<string>&Discord Guild</string>
|
||||||
|
@ -647,8 +618,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionMATRIX">
|
<action name="actionMATRIX">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="matrix">
|
<iconset theme="matrix"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Matrix Space</string>
|
<string>&Matrix Space</string>
|
||||||
|
@ -659,8 +629,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionREDDIT">
|
<action name="actionREDDIT">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="reddit-alien">
|
<iconset theme="reddit-alien"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Sub&reddit</string>
|
<string>Sub&reddit</string>
|
||||||
|
@ -671,8 +640,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionAbout">
|
<action name="actionAbout">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="about">
|
<iconset theme="about"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&About %1</string>
|
<string>&About %1</string>
|
||||||
|
@ -686,8 +654,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionClearMetadata">
|
<action name="actionClearMetadata">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="refresh">
|
<iconset theme="refresh"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Clear Metadata Cache</string>
|
<string>&Clear Metadata Cache</string>
|
||||||
|
@ -696,10 +663,21 @@
|
||||||
<string>Clear cached metadata</string>
|
<string>Clear cached metadata</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionUploadLog">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset theme="log">
|
||||||
|
<normaloff>.</normaloff>.</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Upload logs</string>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Upload launcher logs to the selected log provider</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
<action name="actionAddToPATH">
|
<action name="actionAddToPATH">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="custom-commands">
|
<iconset theme="custom-commands"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Install to &PATH</string>
|
<string>Install to &PATH</string>
|
||||||
|
@ -710,8 +688,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionFoldersButton">
|
<action name="actionFoldersButton">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="viewfolder">
|
<iconset theme="viewfolder"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Folders</string>
|
<string>Folders</string>
|
||||||
|
@ -722,8 +699,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionHelpButton">
|
<action name="actionHelpButton">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="help">
|
<iconset theme="help"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Help</string>
|
<string>Help</string>
|
||||||
|
@ -734,8 +710,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionAccountsButton">
|
<action name="actionAccountsButton">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="noaccount">
|
<iconset theme="noaccount"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Accounts</string>
|
<string>Accounts</string>
|
||||||
|
@ -743,8 +718,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionOpenWiki">
|
<action name="actionOpenWiki">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="help">
|
<iconset theme="help"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>%1 &Help</string>
|
<string>%1 &Help</string>
|
||||||
|
@ -755,8 +729,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionViewWidgetThemeFolder">
|
<action name="actionViewWidgetThemeFolder">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="viewfolder">
|
<iconset theme="viewfolder"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Widget Themes</string>
|
<string>&Widget Themes</string>
|
||||||
|
@ -767,8 +740,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionViewIconThemeFolder">
|
<action name="actionViewIconThemeFolder">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="viewfolder">
|
<iconset theme="viewfolder"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>I&con Theme</string>
|
<string>I&con Theme</string>
|
||||||
|
@ -779,8 +751,7 @@
|
||||||
</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>Cat Packs</string>
|
<string>Cat Packs</string>
|
||||||
|
@ -791,8 +762,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action name="actionViewJavaFolder">
|
<action name="actionViewJavaFolder">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="viewfolder">
|
<iconset theme="viewfolder"/>
|
||||||
<normaloff>.</normaloff>.</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Java</string>
|
<string>Java</string>
|
||||||
|
|
223
launcher/ui/dialogs/CreateShortcutDialog.cpp
Normal file
223
launcher/ui/dialogs/CreateShortcutDialog.cpp
Normal file
|
@ -0,0 +1,223 @@
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
|
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||||
|
* Copyright (C) 2025 Yihe Li <winmikedows@hotmail.com>
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <QLayout>
|
||||||
|
#include <QPushButton>
|
||||||
|
|
||||||
|
#include "Application.h"
|
||||||
|
#include "BuildConfig.h"
|
||||||
|
#include "CreateShortcutDialog.h"
|
||||||
|
#include "ui_CreateShortcutDialog.h"
|
||||||
|
|
||||||
|
#include "ui/dialogs/IconPickerDialog.h"
|
||||||
|
|
||||||
|
#include "BaseInstance.h"
|
||||||
|
#include "DesktopServices.h"
|
||||||
|
#include "FileSystem.h"
|
||||||
|
#include "InstanceList.h"
|
||||||
|
#include "icons/IconList.h"
|
||||||
|
|
||||||
|
#include "minecraft/MinecraftInstance.h"
|
||||||
|
#include "minecraft/ShortcutUtils.h"
|
||||||
|
#include "minecraft/WorldList.h"
|
||||||
|
#include "minecraft/auth/AccountList.h"
|
||||||
|
|
||||||
|
CreateShortcutDialog::CreateShortcutDialog(InstancePtr instance, QWidget* parent)
|
||||||
|
: QDialog(parent), ui(new Ui::CreateShortcutDialog), m_instance(instance)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
|
||||||
|
InstIconKey = instance->iconKey();
|
||||||
|
ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey));
|
||||||
|
ui->instNameTextBox->setPlaceholderText(instance->name());
|
||||||
|
|
||||||
|
auto mInst = std::dynamic_pointer_cast<MinecraftInstance>(instance);
|
||||||
|
m_QuickJoinSupported = mInst && mInst->traits().contains("feature:is_quick_play_singleplayer");
|
||||||
|
auto worldList = mInst->worldList();
|
||||||
|
worldList->update();
|
||||||
|
if (!m_QuickJoinSupported || worldList->empty()) {
|
||||||
|
ui->worldTarget->hide();
|
||||||
|
ui->worldSelectionBox->hide();
|
||||||
|
ui->serverTarget->setChecked(true);
|
||||||
|
ui->serverTarget->hide();
|
||||||
|
ui->serverLabel->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate save targets
|
||||||
|
if (!DesktopServices::isFlatpak()) {
|
||||||
|
QString desktopDir = FS::getDesktopDir();
|
||||||
|
QString applicationDir = FS::getApplicationsDir();
|
||||||
|
|
||||||
|
if (!desktopDir.isEmpty())
|
||||||
|
ui->saveTargetSelectionBox->addItem(tr("Desktop"), QVariant::fromValue(SaveTarget::Desktop));
|
||||||
|
|
||||||
|
if (!applicationDir.isEmpty())
|
||||||
|
ui->saveTargetSelectionBox->addItem(tr("Applications"), QVariant::fromValue(SaveTarget::Applications));
|
||||||
|
}
|
||||||
|
ui->saveTargetSelectionBox->addItem(tr("Other..."), QVariant::fromValue(SaveTarget::Other));
|
||||||
|
|
||||||
|
// Populate worlds
|
||||||
|
if (m_QuickJoinSupported) {
|
||||||
|
for (const auto& world : worldList->allWorlds()) {
|
||||||
|
// Entry name: World Name [Game Mode] - Last Played: DateTime
|
||||||
|
QString entry_name = tr("%1 [%2] - Last Played: %3")
|
||||||
|
.arg(world.name(), world.gameType().toTranslatedString(), world.lastPlayed().toString(Qt::ISODate));
|
||||||
|
ui->worldSelectionBox->addItem(entry_name, world.name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate accounts
|
||||||
|
auto accounts = APPLICATION->accounts();
|
||||||
|
MinecraftAccountPtr defaultAccount = accounts->defaultAccount();
|
||||||
|
if (accounts->count() <= 0) {
|
||||||
|
ui->overrideAccountCheckbox->setEnabled(false);
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < accounts->count(); i++) {
|
||||||
|
MinecraftAccountPtr account = accounts->at(i);
|
||||||
|
auto profileLabel = account->profileName();
|
||||||
|
if (account->isInUse())
|
||||||
|
profileLabel = tr("%1 (in use)").arg(profileLabel);
|
||||||
|
auto face = account->getFace();
|
||||||
|
QIcon icon = face.isNull() ? APPLICATION->getThemedIcon("noaccount") : face;
|
||||||
|
ui->accountSelectionBox->addItem(profileLabel, account->profileName());
|
||||||
|
ui->accountSelectionBox->setItemIcon(i, icon);
|
||||||
|
if (defaultAccount == account)
|
||||||
|
ui->accountSelectionBox->setCurrentIndex(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateShortcutDialog::~CreateShortcutDialog()
|
||||||
|
{
|
||||||
|
delete ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreateShortcutDialog::on_iconButton_clicked()
|
||||||
|
{
|
||||||
|
IconPickerDialog dlg(this);
|
||||||
|
dlg.execWithSelection(InstIconKey);
|
||||||
|
|
||||||
|
if (dlg.result() == QDialog::Accepted) {
|
||||||
|
InstIconKey = dlg.selectedIconKey;
|
||||||
|
ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreateShortcutDialog::on_overrideAccountCheckbox_stateChanged(int state)
|
||||||
|
{
|
||||||
|
ui->accountOptionsGroup->setEnabled(state == Qt::Checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreateShortcutDialog::on_targetCheckbox_stateChanged(int state)
|
||||||
|
{
|
||||||
|
ui->targetOptionsGroup->setEnabled(state == Qt::Checked);
|
||||||
|
ui->worldSelectionBox->setEnabled(ui->worldTarget->isChecked());
|
||||||
|
ui->serverAddressBox->setEnabled(ui->serverTarget->isChecked());
|
||||||
|
stateChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreateShortcutDialog::on_worldTarget_toggled(bool checked)
|
||||||
|
{
|
||||||
|
ui->worldSelectionBox->setEnabled(checked);
|
||||||
|
stateChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreateShortcutDialog::on_serverTarget_toggled(bool checked)
|
||||||
|
{
|
||||||
|
ui->serverAddressBox->setEnabled(checked);
|
||||||
|
stateChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreateShortcutDialog::on_worldSelectionBox_currentIndexChanged(int index)
|
||||||
|
{
|
||||||
|
stateChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreateShortcutDialog::on_serverAddressBox_textChanged(const QString& text)
|
||||||
|
{
|
||||||
|
stateChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreateShortcutDialog::stateChanged()
|
||||||
|
{
|
||||||
|
QString result = m_instance->name();
|
||||||
|
if (ui->targetCheckbox->isChecked()) {
|
||||||
|
if (ui->worldTarget->isChecked())
|
||||||
|
result = tr("%1 - %2").arg(result, ui->worldSelectionBox->currentData().toString());
|
||||||
|
else if (ui->serverTarget->isChecked())
|
||||||
|
result = tr("%1 - Server %2").arg(result, ui->serverAddressBox->text());
|
||||||
|
}
|
||||||
|
ui->instNameTextBox->setPlaceholderText(result);
|
||||||
|
if (!ui->targetCheckbox->isChecked())
|
||||||
|
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
|
||||||
|
else {
|
||||||
|
ui->buttonBox->button(QDialogButtonBox::Ok)
|
||||||
|
->setEnabled((ui->worldTarget->isChecked() && ui->worldSelectionBox->currentIndex() != -1) ||
|
||||||
|
(ui->serverTarget->isChecked() && !ui->serverAddressBox->text().isEmpty()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Real work
|
||||||
|
void CreateShortcutDialog::createShortcut()
|
||||||
|
{
|
||||||
|
QString targetString = tr("instance");
|
||||||
|
QStringList extraArgs;
|
||||||
|
if (ui->targetCheckbox->isChecked()) {
|
||||||
|
if (ui->worldTarget->isChecked()) {
|
||||||
|
targetString = tr("world");
|
||||||
|
extraArgs = { "--world", ui->worldSelectionBox->currentData().toString() };
|
||||||
|
} else if (ui->serverTarget->isChecked()) {
|
||||||
|
targetString = tr("server");
|
||||||
|
extraArgs = { "--server", ui->serverAddressBox->text() };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto target = ui->saveTargetSelectionBox->currentData().value<SaveTarget>();
|
||||||
|
auto name = ui->instNameTextBox->text();
|
||||||
|
if (name.isEmpty())
|
||||||
|
name = ui->instNameTextBox->placeholderText();
|
||||||
|
if (ui->overrideAccountCheckbox->isChecked())
|
||||||
|
extraArgs.append({ "--profile", ui->accountSelectionBox->currentData().toString() });
|
||||||
|
|
||||||
|
ShortcutUtils::Shortcut args{ m_instance.get(), name, targetString, this, extraArgs, InstIconKey };
|
||||||
|
if (target == SaveTarget::Desktop)
|
||||||
|
ShortcutUtils::createInstanceShortcutOnDesktop(args);
|
||||||
|
else if (target == SaveTarget::Applications)
|
||||||
|
ShortcutUtils::createInstanceShortcutInApplications(args);
|
||||||
|
else
|
||||||
|
ShortcutUtils::createInstanceShortcutInOther(args);
|
||||||
|
}
|
62
launcher/ui/dialogs/CreateShortcutDialog.h
Normal file
62
launcher/ui/dialogs/CreateShortcutDialog.h
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
/* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include "BaseInstance.h"
|
||||||
|
|
||||||
|
class BaseInstance;
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class CreateShortcutDialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
class CreateShortcutDialog : public QDialog {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit CreateShortcutDialog(InstancePtr instance, QWidget* parent = nullptr);
|
||||||
|
~CreateShortcutDialog();
|
||||||
|
|
||||||
|
void createShortcut();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
// Icon, target and name
|
||||||
|
void on_iconButton_clicked();
|
||||||
|
|
||||||
|
// Override account
|
||||||
|
void on_overrideAccountCheckbox_stateChanged(int state);
|
||||||
|
|
||||||
|
// Override target (world, server)
|
||||||
|
void on_targetCheckbox_stateChanged(int state);
|
||||||
|
void on_worldTarget_toggled(bool checked);
|
||||||
|
void on_serverTarget_toggled(bool checked);
|
||||||
|
void on_worldSelectionBox_currentIndexChanged(int index);
|
||||||
|
void on_serverAddressBox_textChanged(const QString& text);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Data
|
||||||
|
Ui::CreateShortcutDialog* ui;
|
||||||
|
QString InstIconKey;
|
||||||
|
InstancePtr m_instance;
|
||||||
|
bool m_QuickJoinSupported = false;
|
||||||
|
|
||||||
|
// Index representations
|
||||||
|
enum class SaveTarget { Desktop, Applications, Other };
|
||||||
|
|
||||||
|
// Functions
|
||||||
|
void stateChanged();
|
||||||
|
};
|
250
launcher/ui/dialogs/CreateShortcutDialog.ui
Normal file
250
launcher/ui/dialogs/CreateShortcutDialog.ui
Normal file
|
@ -0,0 +1,250 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>CreateShortcutDialog</class>
|
||||||
|
<widget class="QDialog" name="CreateShortcutDialog">
|
||||||
|
<property name="windowModality">
|
||||||
|
<enum>Qt::WindowModality::ApplicationModal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>450</width>
|
||||||
|
<height>370</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Create Instance Shortcut</string>
|
||||||
|
</property>
|
||||||
|
<property name="modal">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="iconBtnLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="iconButton">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset>
|
||||||
|
<normaloff>:/icons/instances/grass</normaloff>:/icons/instances/grass</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="iconSize">
|
||||||
|
<size>
|
||||||
|
<width>80</width>
|
||||||
|
<height>80</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QGridLayout" name="iconBtnGridLayout">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="saveToLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Save To:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QComboBox" name="saveTargetSelectionBox">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="nameLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Name:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QLineEdit" name="instNameTextBox">
|
||||||
|
<property name="placeholderText">
|
||||||
|
<string>Name</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="overrideAccountCheckbox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Use a different account than the default specified.</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Override the default account</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="accountOptionsGroup">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="accountOptionsLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="accountSelectionBox">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="targetCheckbox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Specify a world or server to automatically join on launch.</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Select a target to join on launch</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="targetOptionsGroup">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="targetOptionsGridLayout">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<layout class="QVBoxLayout" name="worldOverlap">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QRadioButton" name="worldTarget">
|
||||||
|
<property name="text">
|
||||||
|
<string>World:</string>
|
||||||
|
</property>
|
||||||
|
<attribute name="buttonGroup">
|
||||||
|
<string notr="true">targetBtnGroup</string>
|
||||||
|
</attribute>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QComboBox" name="worldSelectionBox">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<layout class="QVBoxLayout" name="serverOverlap">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QRadioButton" name="serverTarget">
|
||||||
|
<property name="text">
|
||||||
|
<string>Server Address:</string>
|
||||||
|
</property>
|
||||||
|
<attribute name="buttonGroup">
|
||||||
|
<string notr="true">targetBtnGroup</string>
|
||||||
|
</attribute>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="serverLabel">
|
||||||
|
<property name="visible">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Server Address:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QLineEdit" name="serverAddressBox">
|
||||||
|
<property name="placeholderText">
|
||||||
|
<string>Server Address</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<tabstops>
|
||||||
|
<tabstop>iconButton</tabstop>
|
||||||
|
</tabstops>
|
||||||
|
<resources/>
|
||||||
|
<connections>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>accepted()</signal>
|
||||||
|
<receiver>CreateShortcutDialog</receiver>
|
||||||
|
<slot>accept()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>20</x>
|
||||||
|
<y>20</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>20</x>
|
||||||
|
<y>20</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>rejected()</signal>
|
||||||
|
<receiver>CreateShortcutDialog</receiver>
|
||||||
|
<slot>reject()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>20</x>
|
||||||
|
<y>20</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>20</x>
|
||||||
|
<y>20</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
</connections>
|
||||||
|
<buttongroups>
|
||||||
|
<buttongroup name="targetBtnGroup"/>
|
||||||
|
</buttongroups>
|
||||||
|
</ui>
|
|
@ -17,7 +17,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "ExportPackDialog.h"
|
#include "ExportPackDialog.h"
|
||||||
#include "minecraft/mod/ModFolderModel.h"
|
#include "minecraft/mod/ResourceFolderModel.h"
|
||||||
#include "modplatform/ModIndex.h"
|
#include "modplatform/ModIndex.h"
|
||||||
#include "modplatform/flame/FlamePackExportTask.h"
|
#include "modplatform/flame/FlamePackExportTask.h"
|
||||||
#include "ui/dialogs/CustomMessageBox.h"
|
#include "ui/dialogs/CustomMessageBox.h"
|
||||||
|
@ -33,7 +33,7 @@
|
||||||
#include "MMCZip.h"
|
#include "MMCZip.h"
|
||||||
#include "modplatform/modrinth/ModrinthPackExportTask.h"
|
#include "modplatform/modrinth/ModrinthPackExportTask.h"
|
||||||
|
|
||||||
ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPlatform::ResourceProvider provider)
|
ExportPackDialog::ExportPackDialog(MinecraftInstancePtr instance, QWidget* parent, ModPlatform::ResourceProvider provider)
|
||||||
: QDialog(parent), m_instance(instance), m_ui(new Ui::ExportPackDialog), m_provider(provider)
|
: QDialog(parent), m_instance(instance), m_ui(new Ui::ExportPackDialog), m_provider(provider)
|
||||||
{
|
{
|
||||||
Q_ASSERT(m_provider == ModPlatform::ResourceProvider::MODRINTH || m_provider == ModPlatform::ResourceProvider::FLAME);
|
Q_ASSERT(m_provider == ModPlatform::ResourceProvider::MODRINTH || m_provider == ModPlatform::ResourceProvider::FLAME);
|
||||||
|
@ -44,12 +44,16 @@ ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPla
|
||||||
m_ui->version->setText(instance->settings()->get("ExportVersion").toString());
|
m_ui->version->setText(instance->settings()->get("ExportVersion").toString());
|
||||||
m_ui->optionalFiles->setChecked(instance->settings()->get("ExportOptionalFiles").toBool());
|
m_ui->optionalFiles->setChecked(instance->settings()->get("ExportOptionalFiles").toBool());
|
||||||
|
|
||||||
|
connect(m_ui->recommendedMemoryCheckBox, &QCheckBox::toggled, m_ui->recommendedMemory, &QWidget::setEnabled);
|
||||||
|
|
||||||
if (m_provider == ModPlatform::ResourceProvider::MODRINTH) {
|
if (m_provider == ModPlatform::ResourceProvider::MODRINTH) {
|
||||||
setWindowTitle(tr("Export Modrinth Pack"));
|
setWindowTitle(tr("Export Modrinth Pack"));
|
||||||
|
|
||||||
m_ui->authorLabel->hide();
|
m_ui->authorLabel->hide();
|
||||||
m_ui->author->hide();
|
m_ui->author->hide();
|
||||||
|
|
||||||
|
m_ui->recommendedMemoryWidget->hide();
|
||||||
|
|
||||||
m_ui->summary->setPlainText(instance->settings()->get("ExportSummary").toString());
|
m_ui->summary->setPlainText(instance->settings()->get("ExportSummary").toString());
|
||||||
} else {
|
} else {
|
||||||
setWindowTitle(tr("Export CurseForge Pack"));
|
setWindowTitle(tr("Export CurseForge Pack"));
|
||||||
|
@ -57,6 +61,19 @@ ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPla
|
||||||
m_ui->summaryLabel->hide();
|
m_ui->summaryLabel->hide();
|
||||||
m_ui->summary->hide();
|
m_ui->summary->hide();
|
||||||
|
|
||||||
|
const int recommendedRAM = instance->settings()->get("ExportRecommendedRAM").toInt();
|
||||||
|
|
||||||
|
if (recommendedRAM > 0) {
|
||||||
|
m_ui->recommendedMemoryCheckBox->setChecked(true);
|
||||||
|
m_ui->recommendedMemory->setValue(recommendedRAM);
|
||||||
|
} else {
|
||||||
|
m_ui->recommendedMemoryCheckBox->setChecked(false);
|
||||||
|
|
||||||
|
// recommend based on setting - limited to 12 GiB (CurseForge warns above this amount)
|
||||||
|
const int defaultRecommendation = qMin(m_instance->settings()->get("MaxMemAlloc").toInt(), 1024 * 12);
|
||||||
|
m_ui->recommendedMemory->setValue(defaultRecommendation);
|
||||||
|
}
|
||||||
|
|
||||||
m_ui->author->setText(instance->settings()->get("ExportAuthor").toString());
|
m_ui->author->setText(instance->settings()->get("ExportAuthor").toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,9 +137,15 @@ void ExportPackDialog::done(int result)
|
||||||
|
|
||||||
if (m_provider == ModPlatform::ResourceProvider::MODRINTH)
|
if (m_provider == ModPlatform::ResourceProvider::MODRINTH)
|
||||||
settings->set("ExportSummary", m_ui->summary->toPlainText());
|
settings->set("ExportSummary", m_ui->summary->toPlainText());
|
||||||
else
|
else {
|
||||||
settings->set("ExportAuthor", m_ui->author->text());
|
settings->set("ExportAuthor", m_ui->author->text());
|
||||||
|
|
||||||
|
if (m_ui->recommendedMemoryCheckBox->isChecked())
|
||||||
|
settings->set("ExportRecommendedRAM", m_ui->recommendedMemory->value());
|
||||||
|
else
|
||||||
|
settings->reset("ExportRecommendedRAM");
|
||||||
|
}
|
||||||
|
|
||||||
if (result == Accepted) {
|
if (result == Accepted) {
|
||||||
const QString name = m_ui->name->text().isEmpty() ? m_instance->name() : m_ui->name->text();
|
const QString name = m_ui->name->text().isEmpty() ? m_instance->name() : m_ui->name->text();
|
||||||
const QString filename = FS::RemoveInvalidFilenameChars(name);
|
const QString filename = FS::RemoveInvalidFilenameChars(name);
|
||||||
|
@ -149,8 +172,18 @@ void ExportPackDialog::done(int result)
|
||||||
task = new ModrinthPackExportTask(name, m_ui->version->text(), m_ui->summary->toPlainText(), m_ui->optionalFiles->isChecked(),
|
task = new ModrinthPackExportTask(name, m_ui->version->text(), m_ui->summary->toPlainText(), m_ui->optionalFiles->isChecked(),
|
||||||
m_instance, output, std::bind(&FileIgnoreProxy::filterFile, m_proxy, std::placeholders::_1));
|
m_instance, output, std::bind(&FileIgnoreProxy::filterFile, m_proxy, std::placeholders::_1));
|
||||||
} else {
|
} else {
|
||||||
task = new FlamePackExportTask(name, m_ui->version->text(), m_ui->author->text(), m_ui->optionalFiles->isChecked(), m_instance,
|
FlamePackExportOptions options{};
|
||||||
output, std::bind(&FileIgnoreProxy::filterFile, m_proxy, std::placeholders::_1));
|
|
||||||
|
options.name = name;
|
||||||
|
options.version = m_ui->version->text();
|
||||||
|
options.author = m_ui->author->text();
|
||||||
|
options.optionalFiles = m_ui->optionalFiles->isChecked();
|
||||||
|
options.instance = m_instance;
|
||||||
|
options.output = output;
|
||||||
|
options.filter = std::bind(&FileIgnoreProxy::filterFile, m_proxy, std::placeholders::_1);
|
||||||
|
options.recommendedRAM = m_ui->recommendedMemoryCheckBox->isChecked() ? m_ui->recommendedMemory->value() : 0;
|
||||||
|
|
||||||
|
task = new FlamePackExportTask(std::move(options));
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(task, &Task::failed,
|
connect(task, &Task::failed,
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
#include "BaseInstance.h"
|
#include "BaseInstance.h"
|
||||||
#include "FastFileIconProvider.h"
|
#include "FastFileIconProvider.h"
|
||||||
#include "FileIgnoreProxy.h"
|
#include "FileIgnoreProxy.h"
|
||||||
|
#include "minecraft/MinecraftInstance.h"
|
||||||
#include "modplatform/ModIndex.h"
|
#include "modplatform/ModIndex.h"
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
|
@ -32,7 +33,7 @@ class ExportPackDialog : public QDialog {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ExportPackDialog(InstancePtr instance,
|
explicit ExportPackDialog(MinecraftInstancePtr instance,
|
||||||
QWidget* parent = nullptr,
|
QWidget* parent = nullptr,
|
||||||
ModPlatform::ResourceProvider provider = ModPlatform::ResourceProvider::MODRINTH);
|
ModPlatform::ResourceProvider provider = ModPlatform::ResourceProvider::MODRINTH);
|
||||||
~ExportPackDialog();
|
~ExportPackDialog();
|
||||||
|
@ -44,7 +45,7 @@ class ExportPackDialog : public QDialog {
|
||||||
QString ignoreFileName();
|
QString ignoreFileName();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const InstancePtr m_instance;
|
const MinecraftInstancePtr m_instance;
|
||||||
Ui::ExportPackDialog* m_ui;
|
Ui::ExportPackDialog* m_ui;
|
||||||
FileIgnoreProxy* m_proxy;
|
FileIgnoreProxy* m_proxy;
|
||||||
FastFileIconProvider m_icons;
|
FastFileIconProvider m_icons;
|
||||||
|
|
|
@ -19,37 +19,57 @@
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>&Description</string>
|
<string>&Description</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
<item>
|
<item>
|
||||||
|
<layout class="QFormLayout" name="formLayout">
|
||||||
|
<property name="labelAlignment">
|
||||||
|
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||||
|
</property>
|
||||||
|
<item row="0" column="0">
|
||||||
<widget class="QLabel" name="nameLabel">
|
<widget class="QLabel" name="nameLabel">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Name</string>
|
<string>&Name:</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="buddy">
|
<property name="buddy">
|
||||||
<cstring>name</cstring>
|
<cstring>name</cstring>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item row="0" column="1">
|
||||||
<widget class="QLineEdit" name="name"/>
|
<widget class="QLineEdit" name="name"/>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item row="1" column="0">
|
||||||
<widget class="QLabel" name="versionLabel">
|
<widget class="QLabel" name="versionLabel">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Version</string>
|
<string>&Version:</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="buddy">
|
<property name="buddy">
|
||||||
<cstring>version</cstring>
|
<cstring>version</cstring>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item row="1" column="1">
|
||||||
<widget class="QLineEdit" name="version">
|
<widget class="QLineEdit" name="version">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>1.0.0</string>
|
<string>1.0.0</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="authorLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Author:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>author</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QLineEdit" name="author"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="summaryLabel">
|
<widget class="QLabel" name="summaryLabel">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
@ -62,24 +82,29 @@
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPlainTextEdit" name="summary">
|
<widget class="QPlainTextEdit" name="summary">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>100</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>100</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
<property name="tabChangesFocus">
|
<property name="tabChangesFocus">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="authorLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string>&Author</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>author</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLineEdit" name="author"/>
|
|
||||||
</item>
|
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -88,7 +113,70 @@
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>&Options</string>
|
<string>&Options</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||||
|
<item>
|
||||||
|
<widget class="QWidget" name="recommendedMemoryWidget" native="true">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="recommendedMemoryCheckBox">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Recommended Memory:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="recommendedMemory">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="suffix">
|
||||||
|
<string> MiB</string>
|
||||||
|
</property>
|
||||||
|
<property name="minimum">
|
||||||
|
<number>8</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>32768</number>
|
||||||
|
</property>
|
||||||
|
<property name="singleStep">
|
||||||
|
<number>128</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="filesLabel">
|
<widget class="QLabel" name="filesLabel">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
@ -138,10 +226,6 @@
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<tabstops>
|
<tabstops>
|
||||||
<tabstop>name</tabstop>
|
|
||||||
<tabstop>version</tabstop>
|
|
||||||
<tabstop>summary</tabstop>
|
|
||||||
<tabstop>author</tabstop>
|
|
||||||
<tabstop>files</tabstop>
|
<tabstop>files</tabstop>
|
||||||
<tabstop>optionalFiles</tabstop>
|
<tabstop>optionalFiles</tabstop>
|
||||||
</tabstops>
|
</tabstops>
|
||||||
|
|
|
@ -36,7 +36,6 @@
|
||||||
#include "MSALoginDialog.h"
|
#include "MSALoginDialog.h"
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
|
||||||
#include "qr.h"
|
|
||||||
#include "ui_MSALoginDialog.h"
|
#include "ui_MSALoginDialog.h"
|
||||||
|
|
||||||
#include "DesktopServices.h"
|
#include "DesktopServices.h"
|
||||||
|
@ -44,10 +43,15 @@
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
|
#include <QColor>
|
||||||
|
#include <QPainter>
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
|
#include <QSize>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QtWidgets/QPushButton>
|
#include <QtWidgets/QPushButton>
|
||||||
|
|
||||||
|
#include "qrcodegen.hpp"
|
||||||
|
|
||||||
MSALoginDialog::MSALoginDialog(QWidget* parent) : QDialog(parent), ui(new Ui::MSALoginDialog)
|
MSALoginDialog::MSALoginDialog(QWidget* parent) : QDialog(parent), ui(new Ui::MSALoginDialog)
|
||||||
{
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
@ -139,6 +143,33 @@ void MSALoginDialog::authorizeWithBrowser(const QUrl& url)
|
||||||
m_url = url;
|
m_url = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/21400254/how-to-draw-a-qr-code-with-qt-in-native-c-c
|
||||||
|
void paintQR(QPainter& painter, const QSize sz, const QString& data, QColor fg)
|
||||||
|
{
|
||||||
|
// NOTE: At this point you will use the API to get the encoding and format you want, instead of my hardcoded stuff:
|
||||||
|
qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(data.toUtf8().constData(), qrcodegen::QrCode::Ecc::LOW);
|
||||||
|
const int s = qr.getSize() > 0 ? qr.getSize() : 1;
|
||||||
|
const double w = sz.width();
|
||||||
|
const double h = sz.height();
|
||||||
|
const double aspect = w / h;
|
||||||
|
const double size = ((aspect > 1.0) ? h : w);
|
||||||
|
const double scale = size / (s + 2);
|
||||||
|
// NOTE: For performance reasons my implementation only draws the foreground parts in supplied color.
|
||||||
|
// It expects background to be prepared already (in white or whatever is preferred).
|
||||||
|
painter.setPen(Qt::NoPen);
|
||||||
|
painter.setBrush(fg);
|
||||||
|
for (int y = 0; y < s; y++) {
|
||||||
|
for (int x = 0; x < s; x++) {
|
||||||
|
const int color = qr.getModule(x, y); // 0 for white, 1 for black
|
||||||
|
if (0 != color) {
|
||||||
|
const double rx1 = (x + 1) * scale, ry1 = (y + 1) * scale;
|
||||||
|
QRectF r(rx1, ry1, scale, scale);
|
||||||
|
painter.drawRects(&r, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MSALoginDialog::authorizeWithBrowserWithExtra(QString url, QString code, [[maybe_unused]] int expiresIn)
|
void MSALoginDialog::authorizeWithBrowserWithExtra(QString url, QString code, [[maybe_unused]] int expiresIn)
|
||||||
{
|
{
|
||||||
ui->stackedWidget->setCurrentIndex(1);
|
ui->stackedWidget->setCurrentIndex(1);
|
||||||
|
|
|
@ -92,6 +92,10 @@ SkinManageDialog::SkinManageDialog(QWidget* parent, MinecraftAccountPtr acct)
|
||||||
connect(contentsWidget->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
|
connect(contentsWidget->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
|
||||||
SLOT(selectionChanged(QItemSelection, QItemSelection)));
|
SLOT(selectionChanged(QItemSelection, QItemSelection)));
|
||||||
connect(m_ui->listView, &QListView::customContextMenuRequested, this, &SkinManageDialog::show_context_menu);
|
connect(m_ui->listView, &QListView::customContextMenuRequested, this, &SkinManageDialog::show_context_menu);
|
||||||
|
connect(m_ui->elytraCB, &QCheckBox::stateChanged, this, [this]() {
|
||||||
|
m_skinPreview->setElytraVisible(m_ui->elytraCB->isChecked());
|
||||||
|
on_capeCombo_currentIndexChanged(0);
|
||||||
|
});
|
||||||
|
|
||||||
setupCapes();
|
setupCapes();
|
||||||
|
|
||||||
|
@ -159,10 +163,24 @@ void SkinManageDialog::on_fileBtn_clicked()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QPixmap previewCape(QImage capeImage)
|
QPixmap previewCape(QImage capeImage, bool elytra = false)
|
||||||
{
|
{
|
||||||
|
if (elytra) {
|
||||||
|
auto wing = capeImage.copy(34, 0, 12, 22);
|
||||||
|
QImage mirrored = wing.mirrored(true, false);
|
||||||
|
|
||||||
|
QImage combined(wing.width() * 2 - 2, wing.height(), capeImage.format());
|
||||||
|
combined.fill(Qt::transparent);
|
||||||
|
|
||||||
|
QPainter painter(&combined);
|
||||||
|
painter.drawImage(0, 0, wing);
|
||||||
|
painter.drawImage(wing.width() - 2, 0, mirrored);
|
||||||
|
painter.end();
|
||||||
|
return QPixmap::fromImage(combined.scaled(96, 176, Qt::IgnoreAspectRatio, Qt::FastTransformation));
|
||||||
|
}
|
||||||
return QPixmap::fromImage(capeImage.copy(1, 1, 10, 16).scaled(80, 128, Qt::IgnoreAspectRatio, Qt::FastTransformation));
|
return QPixmap::fromImage(capeImage.copy(1, 1, 10, 16).scaled(80, 128, Qt::IgnoreAspectRatio, Qt::FastTransformation));
|
||||||
}
|
}
|
||||||
|
|
||||||
void SkinManageDialog::setupCapes()
|
void SkinManageDialog::setupCapes()
|
||||||
{
|
{
|
||||||
// FIXME: add a model for this, download/refresh the capes on demand
|
// FIXME: add a model for this, download/refresh the capes on demand
|
||||||
|
@ -208,7 +226,7 @@ void SkinManageDialog::setupCapes()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!capeImage.isNull()) {
|
if (!capeImage.isNull()) {
|
||||||
m_ui->capeCombo->addItem(previewCape(capeImage), cape.alias, cape.id);
|
m_ui->capeCombo->addItem(previewCape(capeImage, m_ui->elytraCB->isChecked()), cape.alias, cape.id);
|
||||||
} else {
|
} else {
|
||||||
m_ui->capeCombo->addItem(cape.alias, cape.id);
|
m_ui->capeCombo->addItem(cape.alias, cape.id);
|
||||||
}
|
}
|
||||||
|
@ -222,7 +240,8 @@ void SkinManageDialog::on_capeCombo_currentIndexChanged(int index)
|
||||||
auto id = m_ui->capeCombo->currentData();
|
auto id = m_ui->capeCombo->currentData();
|
||||||
auto cape = m_capes.value(id.toString(), {});
|
auto cape = m_capes.value(id.toString(), {});
|
||||||
if (!cape.isNull()) {
|
if (!cape.isNull()) {
|
||||||
m_ui->capeImage->setPixmap(previewCape(cape).scaled(size() * (1. / 3), Qt::KeepAspectRatio, Qt::FastTransformation));
|
m_ui->capeImage->setPixmap(
|
||||||
|
previewCape(cape, m_ui->elytraCB->isChecked()).scaled(size() * (1. / 3), Qt::KeepAspectRatio, Qt::FastTransformation));
|
||||||
} else {
|
} else {
|
||||||
m_ui->capeImage->clear();
|
m_ui->capeImage->clear();
|
||||||
}
|
}
|
||||||
|
@ -319,14 +338,14 @@ bool SkinManageDialog::eventFilter(QObject* obj, QEvent* ev)
|
||||||
return QDialog::eventFilter(obj, ev);
|
return QDialog::eventFilter(obj, ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SkinManageDialog::on_action_Rename_Skin_triggered(bool checked)
|
void SkinManageDialog::on_action_Rename_Skin_triggered(bool)
|
||||||
{
|
{
|
||||||
if (!m_selectedSkinKey.isEmpty()) {
|
if (!m_selectedSkinKey.isEmpty()) {
|
||||||
m_ui->listView->edit(m_ui->listView->currentIndex());
|
m_ui->listView->edit(m_ui->listView->currentIndex());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SkinManageDialog::on_action_Delete_Skin_triggered(bool checked)
|
void SkinManageDialog::on_action_Delete_Skin_triggered(bool)
|
||||||
{
|
{
|
||||||
if (m_selectedSkinKey.isEmpty())
|
if (m_selectedSkinKey.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
@ -523,7 +542,7 @@ void SkinManageDialog::resizeEvent(QResizeEvent* event)
|
||||||
auto id = m_ui->capeCombo->currentData();
|
auto id = m_ui->capeCombo->currentData();
|
||||||
auto cape = m_capes.value(id.toString(), {});
|
auto cape = m_capes.value(id.toString(), {});
|
||||||
if (!cape.isNull()) {
|
if (!cape.isNull()) {
|
||||||
m_ui->capeImage->setPixmap(previewCape(cape).scaled(s, Qt::KeepAspectRatio, Qt::FastTransformation));
|
m_ui->capeImage->setPixmap(previewCape(cape, m_ui->elytraCB->isChecked()).scaled(s, Qt::KeepAspectRatio, Qt::FastTransformation));
|
||||||
} else {
|
} else {
|
||||||
m_ui->capeImage->clear();
|
m_ui->capeImage->clear();
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,13 @@
|
||||||
<string>Cape</string>
|
<string>Cape</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="elytraCB">
|
||||||
|
<property name="text">
|
||||||
|
<string>Preview Elytra</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QComboBox" name="capeCombo"/>
|
<widget class="QComboBox" name="capeCombo"/>
|
||||||
</item>
|
</item>
|
||||||
|
|
|
@ -180,7 +180,8 @@ QList<QVector2D> getCubeUVs(float u, float v, float width, float height, float d
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace opengl {
|
namespace opengl {
|
||||||
BoxGeometry::BoxGeometry(QVector3D size, QVector3D position) : m_indexBuf(QOpenGLBuffer::IndexBuffer), m_size(size), m_position(position)
|
BoxGeometry::BoxGeometry(QVector3D size, QVector3D position)
|
||||||
|
: QOpenGLFunctions(), m_indexBuf(QOpenGLBuffer::IndexBuffer), m_size(size), m_position(position)
|
||||||
{
|
{
|
||||||
initializeOpenGLFunctions();
|
initializeOpenGLFunctions();
|
||||||
|
|
||||||
|
@ -274,4 +275,9 @@ BoxGeometry* BoxGeometry::Plane()
|
||||||
|
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BoxGeometry::scale(const QVector3D& vector)
|
||||||
|
{
|
||||||
|
m_matrix.scale(vector);
|
||||||
|
}
|
||||||
} // namespace opengl
|
} // namespace opengl
|
|
@ -36,6 +36,7 @@ class BoxGeometry : protected QOpenGLFunctions {
|
||||||
|
|
||||||
void initGeometry(float u, float v, float width, float height, float depth, float textureWidth = 64, float textureHeight = 64);
|
void initGeometry(float u, float v, float width, float height, float depth, float textureWidth = 64, float textureHeight = 64);
|
||||||
void rotate(float angle, const QVector3D& vector);
|
void rotate(float angle, const QVector3D& vector);
|
||||||
|
void scale(const QVector3D& vector);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QOpenGLBuffer m_vertexBuf;
|
QOpenGLBuffer m_vertexBuf;
|
||||||
|
|
|
@ -18,9 +18,16 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "ui/dialogs/skins/draw/Scene.h"
|
#include "ui/dialogs/skins/draw/Scene.h"
|
||||||
|
|
||||||
|
#include <QOpenGLFunctions>
|
||||||
|
#include <QOpenGLShaderProgram>
|
||||||
|
#include <QOpenGLTexture>
|
||||||
|
#include <QOpenGLWindow>
|
||||||
|
|
||||||
namespace opengl {
|
namespace opengl {
|
||||||
Scene::Scene(const QImage& skin, bool slim, const QImage& cape) : m_slim(slim), m_capeVisible(!cape.isNull())
|
Scene::Scene(const QImage& skin, bool slim, const QImage& cape) : QOpenGLFunctions(), m_slim(slim), m_capeVisible(!cape.isNull())
|
||||||
{
|
{
|
||||||
|
initializeOpenGLFunctions();
|
||||||
m_staticComponents = {
|
m_staticComponents = {
|
||||||
// head
|
// head
|
||||||
new opengl::BoxGeometry(QVector3D(8, 8, 8), QVector3D(0, 4, 0), QPoint(0, 0), QVector3D(8, 8, 8)),
|
new opengl::BoxGeometry(QVector3D(8, 8, 8), QVector3D(0, 4, 0), QPoint(0, 0), QVector3D(8, 8, 8)),
|
||||||
|
@ -57,6 +64,19 @@ Scene::Scene(const QImage& skin, bool slim, const QImage& cape) : m_slim(slim),
|
||||||
m_cape->rotate(10.8, QVector3D(1, 0, 0));
|
m_cape->rotate(10.8, QVector3D(1, 0, 0));
|
||||||
m_cape->rotate(180, QVector3D(0, 1, 0));
|
m_cape->rotate(180, QVector3D(0, 1, 0));
|
||||||
|
|
||||||
|
auto leftWing =
|
||||||
|
new opengl::BoxGeometry(QVector3D(12, 22, 4), QVector3D(0, -13, -2), QPoint(22, 0), QVector3D(10, 20, 2), QSize(64, 32));
|
||||||
|
leftWing->rotate(15, QVector3D(1, 0, 0));
|
||||||
|
leftWing->rotate(15, QVector3D(0, 0, 1));
|
||||||
|
leftWing->rotate(1, QVector3D(1, 0, 0));
|
||||||
|
auto rightWing =
|
||||||
|
new opengl::BoxGeometry(QVector3D(12, 22, 4), QVector3D(0, -13, -2), QPoint(22, 0), QVector3D(10, 20, 2), QSize(64, 32));
|
||||||
|
rightWing->scale(QVector3D(-1, 1, 1));
|
||||||
|
rightWing->rotate(15, QVector3D(1, 0, 0));
|
||||||
|
rightWing->rotate(15, QVector3D(0, 0, 1));
|
||||||
|
rightWing->rotate(1, QVector3D(1, 0, 0));
|
||||||
|
m_elytra << leftWing << rightWing;
|
||||||
|
|
||||||
// texture init
|
// texture init
|
||||||
m_skinTexture = new QOpenGLTexture(skin.mirrored());
|
m_skinTexture = new QOpenGLTexture(skin.mirrored());
|
||||||
m_skinTexture->setMinificationFilter(QOpenGLTexture::Nearest);
|
m_skinTexture->setMinificationFilter(QOpenGLTexture::Nearest);
|
||||||
|
@ -68,7 +88,7 @@ Scene::Scene(const QImage& skin, bool slim, const QImage& cape) : m_slim(slim),
|
||||||
}
|
}
|
||||||
Scene::~Scene()
|
Scene::~Scene()
|
||||||
{
|
{
|
||||||
for (auto array : { m_staticComponents, m_normalArms, m_slimArms }) {
|
for (auto array : { m_staticComponents, m_normalArms, m_slimArms, m_elytra }) {
|
||||||
for (auto g : array) {
|
for (auto g : array) {
|
||||||
delete g;
|
delete g;
|
||||||
}
|
}
|
||||||
|
@ -95,7 +115,15 @@ void Scene::draw(QOpenGLShaderProgram* program)
|
||||||
if (m_capeVisible) {
|
if (m_capeVisible) {
|
||||||
m_capeTexture->bind();
|
m_capeTexture->bind();
|
||||||
program->setUniformValue("texture", 0);
|
program->setUniformValue("texture", 0);
|
||||||
|
if (!m_elytraVisible) {
|
||||||
m_cape->draw(program);
|
m_cape->draw(program);
|
||||||
|
} else {
|
||||||
|
glDisable(GL_CULL_FACE);
|
||||||
|
for (auto e : m_elytra) {
|
||||||
|
e->draw(program);
|
||||||
|
}
|
||||||
|
glEnable(GL_CULL_FACE);
|
||||||
|
}
|
||||||
m_capeTexture->release();
|
m_capeTexture->release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,4 +159,8 @@ void Scene::setCapeVisible(bool visible)
|
||||||
{
|
{
|
||||||
m_capeVisible = visible;
|
m_capeVisible = visible;
|
||||||
}
|
}
|
||||||
|
void Scene::setElytraVisible(bool elytraVisible)
|
||||||
|
{
|
||||||
|
m_elytraVisible = elytraVisible;
|
||||||
|
}
|
||||||
} // namespace opengl
|
} // namespace opengl
|
|
@ -22,7 +22,7 @@
|
||||||
|
|
||||||
#include <QOpenGLTexture>
|
#include <QOpenGLTexture>
|
||||||
namespace opengl {
|
namespace opengl {
|
||||||
class Scene {
|
class Scene : protected QOpenGLFunctions {
|
||||||
public:
|
public:
|
||||||
Scene(const QImage& skin, bool slim, const QImage& cape);
|
Scene(const QImage& skin, bool slim, const QImage& cape);
|
||||||
virtual ~Scene();
|
virtual ~Scene();
|
||||||
|
@ -32,15 +32,18 @@ class Scene {
|
||||||
void setCape(const QImage& cape);
|
void setCape(const QImage& cape);
|
||||||
void setMode(bool slim);
|
void setMode(bool slim);
|
||||||
void setCapeVisible(bool visible);
|
void setCapeVisible(bool visible);
|
||||||
|
void setElytraVisible(bool elytraVisible);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QList<BoxGeometry*> m_staticComponents;
|
QList<BoxGeometry*> m_staticComponents;
|
||||||
QList<BoxGeometry*> m_normalArms;
|
QList<BoxGeometry*> m_normalArms;
|
||||||
QList<BoxGeometry*> m_slimArms;
|
QList<BoxGeometry*> m_slimArms;
|
||||||
BoxGeometry* m_cape = nullptr;
|
BoxGeometry* m_cape = nullptr;
|
||||||
|
QList<BoxGeometry*> m_elytra;
|
||||||
QOpenGLTexture* m_skinTexture = nullptr;
|
QOpenGLTexture* m_skinTexture = nullptr;
|
||||||
QOpenGLTexture* m_capeTexture = nullptr;
|
QOpenGLTexture* m_capeTexture = nullptr;
|
||||||
bool m_slim = false;
|
bool m_slim = false;
|
||||||
bool m_capeVisible = false;
|
bool m_capeVisible = false;
|
||||||
|
bool m_elytraVisible = false;
|
||||||
};
|
};
|
||||||
} // namespace opengl
|
} // namespace opengl
|
|
@ -263,3 +263,8 @@ void SkinOpenGLWindow::wheelEvent(QWheelEvent* event)
|
||||||
m_distance = qMax(16.f, m_distance); // Clamp distance
|
m_distance = qMax(16.f, m_distance); // Clamp distance
|
||||||
update(); // Trigger a repaint
|
update(); // Trigger a repaint
|
||||||
}
|
}
|
||||||
|
void SkinOpenGLWindow::setElytraVisible(bool visible)
|
||||||
|
{
|
||||||
|
if (m_scene)
|
||||||
|
m_scene->setElytraVisible(visible);
|
||||||
|
}
|
||||||
|
|
|
@ -43,6 +43,7 @@ class SkinOpenGLWindow : public QOpenGLWindow, protected QOpenGLFunctions {
|
||||||
|
|
||||||
void updateScene(SkinModel* skin);
|
void updateScene(SkinModel* skin);
|
||||||
void updateCape(const QImage& cape);
|
void updateCape(const QImage& cape);
|
||||||
|
void setElytraVisible(bool visible);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void mousePressEvent(QMouseEvent* e) override;
|
void mousePressEvent(QMouseEvent* e) override;
|
||||||
|
|
|
@ -37,11 +37,11 @@
|
||||||
|
|
||||||
#include <Application.h>
|
#include <Application.h>
|
||||||
#include <QObjectPtr.h>
|
#include <QObjectPtr.h>
|
||||||
#include "ui/widgets/JavaSettingsWidget.h"
|
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
#include <QStringListModel>
|
#include <QStringListModel>
|
||||||
#include "JavaCommon.h"
|
#include "JavaCommon.h"
|
||||||
#include "ui/pages/BasePage.h"
|
#include "ui/pages/BasePage.h"
|
||||||
|
#include "ui/widgets/JavaSettingsWidget.h"
|
||||||
|
|
||||||
class SettingsObject;
|
class SettingsObject;
|
||||||
|
|
||||||
|
|
|
@ -35,17 +35,18 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
#include "BaseInstance.h"
|
#include "BaseInstance.h"
|
||||||
#include "ui/pages/BasePage.h"
|
#include "ui/pages/BasePage.h"
|
||||||
#include "ui/widgets/MinecraftSettingsWidget.h"
|
#include "ui/widgets/MinecraftSettingsWidget.h"
|
||||||
#include <QWidget>
|
|
||||||
|
|
||||||
class InstanceSettingsPage : public MinecraftSettingsWidget, public BasePage {
|
class InstanceSettingsPage : public MinecraftSettingsWidget, public BasePage {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit InstanceSettingsPage(MinecraftInstancePtr instance, QWidget* parent = nullptr) : MinecraftSettingsWidget(std::move(instance), parent)
|
explicit InstanceSettingsPage(MinecraftInstancePtr instance, QWidget* parent = nullptr)
|
||||||
|
: MinecraftSettingsWidget(std::move(instance), parent)
|
||||||
{
|
{
|
||||||
connect(APPLICATION, &Application::globalSettingsAboutToOpen, this, &InstanceSettingsPage::saveSettings);
|
connect(APPLICATION, &Application::globalSettingsAboutToOpen, this, &InstanceSettingsPage::saveSettings);
|
||||||
connect(APPLICATION, &Application::globalSettingsClosed, this, &InstanceSettingsPage::loadSettings);
|
connect(APPLICATION, &Application::globalSettingsClosed, this, &InstanceSettingsPage::loadSettings);
|
||||||
|
|
|
@ -347,13 +347,18 @@ void ManagedPackPage::onUpdateTaskCompleted(bool did_succeed) const
|
||||||
if (m_instance_window != nullptr)
|
if (m_instance_window != nullptr)
|
||||||
m_instance_window->close();
|
m_instance_window->close();
|
||||||
|
|
||||||
CustomMessageBox::selectable(nullptr, tr("Update Successful"), tr("The instance updated to pack version %1 successfully.").arg(m_inst->getManagedPackVersionName()), QMessageBox::Information)
|
CustomMessageBox::selectable(nullptr, tr("Update Successful"),
|
||||||
|
tr("The instance updated to pack version %1 successfully.").arg(m_inst->getManagedPackVersionName()),
|
||||||
|
QMessageBox::Information)
|
||||||
->show();
|
->show();
|
||||||
} else {
|
} else {
|
||||||
CustomMessageBox::selectable(nullptr, tr("Update Failed"), tr("The instance failed to update to pack version %1. Please check launcher logs for more information.").arg(m_inst->getManagedPackVersionName()), QMessageBox::Critical)
|
CustomMessageBox::selectable(
|
||||||
|
nullptr, tr("Update Failed"),
|
||||||
|
tr("The instance failed to update to pack version %1. Please check launcher logs for more information.")
|
||||||
|
.arg(m_inst->getManagedPackVersionName()),
|
||||||
|
QMessageBox::Critical)
|
||||||
->show();
|
->show();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModrinthManagedPackPage::update()
|
void ModrinthManagedPackPage::update()
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
#include <QObject>
|
#include <qtconcurrentrun.h>
|
||||||
#include <QTcpSocket>
|
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <qtconcurrentrun.h>
|
#include <QObject>
|
||||||
|
#include <QTcpSocket>
|
||||||
|
|
||||||
#include <Exception.h>
|
#include <Exception.h>
|
||||||
#include "McClient.h"
|
|
||||||
#include "Json.h"
|
#include "Json.h"
|
||||||
|
#include "McClient.h"
|
||||||
|
|
||||||
// 7 first bits
|
// 7 first bits
|
||||||
#define SEGMENT_BITS 0x7F
|
#define SEGMENT_BITS 0x7F
|
||||||
|
@ -15,7 +15,8 @@
|
||||||
|
|
||||||
McClient::McClient(QObject* parent, QString domain, QString ip, short port) : QObject(parent), m_domain(domain), m_ip(ip), m_port(port) {}
|
McClient::McClient(QObject* parent, QString domain, QString ip, short port) : QObject(parent), m_domain(domain), m_ip(ip), m_port(port) {}
|
||||||
|
|
||||||
void McClient::getStatusData() {
|
void McClient::getStatusData()
|
||||||
|
{
|
||||||
qDebug() << "Connecting to socket..";
|
qDebug() << "Connecting to socket..";
|
||||||
|
|
||||||
connect(&m_socket, &QTcpSocket::connected, this, [this]() {
|
connect(&m_socket, &QTcpSocket::connected, this, [this]() {
|
||||||
|
@ -25,14 +26,13 @@ void McClient::getStatusData() {
|
||||||
connect(&m_socket, &QTcpSocket::readyRead, this, &McClient::readRawResponse);
|
connect(&m_socket, &QTcpSocket::readyRead, this, &McClient::readRawResponse);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(&m_socket, &QTcpSocket::errorOccurred, this, [this]() {
|
connect(&m_socket, &QTcpSocket::errorOccurred, this, [this]() { emitFail("Socket disconnected: " + m_socket.errorString()); });
|
||||||
emitFail("Socket disconnected: " + m_socket.errorString());
|
|
||||||
});
|
|
||||||
|
|
||||||
m_socket.connectToHost(m_ip, m_port);
|
m_socket.connectToHost(m_ip, m_port);
|
||||||
}
|
}
|
||||||
|
|
||||||
void McClient::sendRequest() {
|
void McClient::sendRequest()
|
||||||
|
{
|
||||||
QByteArray data;
|
QByteArray data;
|
||||||
writeVarInt(data, 0x00); // packet ID
|
writeVarInt(data, 0x00); // packet ID
|
||||||
writeVarInt(data, 763); // hardcoded protocol version (763 = 1.20.1)
|
writeVarInt(data, 763); // hardcoded protocol version (763 = 1.20.1)
|
||||||
|
@ -46,7 +46,8 @@ void McClient::sendRequest() {
|
||||||
writePacketToSocket(data); // send status packet
|
writePacketToSocket(data); // send status packet
|
||||||
}
|
}
|
||||||
|
|
||||||
void McClient::readRawResponse() {
|
void McClient::readRawResponse()
|
||||||
|
{
|
||||||
if (m_responseReadState == 2) {
|
if (m_responseReadState == 2) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -59,22 +60,21 @@ void McClient::readRawResponse() {
|
||||||
|
|
||||||
if (m_responseReadState == 1 && m_resp.size() >= m_wantedRespLength) {
|
if (m_responseReadState == 1 && m_resp.size() >= m_wantedRespLength) {
|
||||||
if (m_resp.size() > m_wantedRespLength) {
|
if (m_resp.size() > m_wantedRespLength) {
|
||||||
qDebug() << "Warning: Packet length doesn't match actual packet size (" << m_wantedRespLength << " expected vs " << m_resp.size() << " received)";
|
qDebug() << "Warning: Packet length doesn't match actual packet size (" << m_wantedRespLength << " expected vs "
|
||||||
|
<< m_resp.size() << " received)";
|
||||||
}
|
}
|
||||||
parseResponse();
|
parseResponse();
|
||||||
m_responseReadState = 2;
|
m_responseReadState = 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void McClient::parseResponse() {
|
void McClient::parseResponse()
|
||||||
|
{
|
||||||
qDebug() << "Received response successfully";
|
qDebug() << "Received response successfully";
|
||||||
|
|
||||||
int packetID = readVarInt(m_resp);
|
int packetID = readVarInt(m_resp);
|
||||||
if (packetID != 0x00) {
|
if (packetID != 0x00) {
|
||||||
throw Exception(
|
throw Exception(QString("Packet ID doesn't match expected value (0x00 vs 0x%1)").arg(packetID, 0, 16));
|
||||||
QString("Packet ID doesn't match expected value (0x00 vs 0x%1)")
|
|
||||||
.arg(packetID, 0, 16)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Q_UNUSED(readVarInt(m_resp)); // json length
|
Q_UNUSED(readVarInt(m_resp)); // json length
|
||||||
|
@ -85,7 +85,8 @@ void McClient::parseResponse() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// From https://wiki.vg/Protocol#VarInt_and_VarLong
|
// From https://wiki.vg/Protocol#VarInt_and_VarLong
|
||||||
void McClient::writeVarInt(QByteArray &data, int value) {
|
void McClient::writeVarInt(QByteArray& data, int value)
|
||||||
|
{
|
||||||
while ((value & ~SEGMENT_BITS)) { // check if the value is too big to fit in 7 bits
|
while ((value & ~SEGMENT_BITS)) { // check if the value is too big to fit in 7 bits
|
||||||
// Write 7 bits
|
// Write 7 bits
|
||||||
data.append((value & SEGMENT_BITS) | CONTINUE_BIT);
|
data.append((value & SEGMENT_BITS) | CONTINUE_BIT);
|
||||||
|
@ -98,7 +99,8 @@ void McClient::writeVarInt(QByteArray &data, int value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// From https://wiki.vg/Protocol#VarInt_and_VarLong
|
// From https://wiki.vg/Protocol#VarInt_and_VarLong
|
||||||
int McClient::readVarInt(QByteArray &data) {
|
int McClient::readVarInt(QByteArray& data)
|
||||||
|
{
|
||||||
int value = 0;
|
int value = 0;
|
||||||
int position = 0;
|
int position = 0;
|
||||||
char currentByte;
|
char currentByte;
|
||||||
|
@ -107,17 +109,20 @@ int McClient::readVarInt(QByteArray &data) {
|
||||||
currentByte = readByte(data);
|
currentByte = readByte(data);
|
||||||
value |= (currentByte & SEGMENT_BITS) << position;
|
value |= (currentByte & SEGMENT_BITS) << position;
|
||||||
|
|
||||||
if ((currentByte & CONTINUE_BIT) == 0) break;
|
if ((currentByte & CONTINUE_BIT) == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
position += 7;
|
position += 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (position >= 32) throw Exception("VarInt is too big");
|
if (position >= 32)
|
||||||
|
throw Exception("VarInt is too big");
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
char McClient::readByte(QByteArray &data) {
|
char McClient::readByte(QByteArray& data)
|
||||||
|
{
|
||||||
if (data.isEmpty()) {
|
if (data.isEmpty()) {
|
||||||
throw Exception("No more bytes to read");
|
throw Exception("No more bytes to read");
|
||||||
}
|
}
|
||||||
|
@ -128,17 +133,20 @@ char McClient::readByte(QByteArray &data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// write number with specified size in big endian format
|
// write number with specified size in big endian format
|
||||||
void McClient::writeFixedInt(QByteArray &data, int value, int size) {
|
void McClient::writeFixedInt(QByteArray& data, int value, int size)
|
||||||
|
{
|
||||||
for (int i = size - 1; i >= 0; i--) {
|
for (int i = size - 1; i >= 0; i--) {
|
||||||
data.append((value >> (i * 8)) & 0xFF);
|
data.append((value >> (i * 8)) & 0xFF);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void McClient::writeString(QByteArray &data, const std::string &value) {
|
void McClient::writeString(QByteArray& data, const std::string& value)
|
||||||
|
{
|
||||||
data.append(value.c_str());
|
data.append(value.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void McClient::writePacketToSocket(QByteArray &data) {
|
void McClient::writePacketToSocket(QByteArray& data)
|
||||||
|
{
|
||||||
// we prefix the packet with its length
|
// we prefix the packet with its length
|
||||||
QByteArray dataWithSize;
|
QByteArray dataWithSize;
|
||||||
writeVarInt(dataWithSize, data.size());
|
writeVarInt(dataWithSize, data.size());
|
||||||
|
@ -151,14 +159,15 @@ void McClient::writePacketToSocket(QByteArray &data) {
|
||||||
data.clear();
|
data.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void McClient::emitFail(QString error)
|
||||||
void McClient::emitFail(QString error) {
|
{
|
||||||
qDebug() << "Minecraft server ping for status error:" << error;
|
qDebug() << "Minecraft server ping for status error:" << error;
|
||||||
emit failed(error);
|
emit failed(error);
|
||||||
emit finished();
|
emit finished();
|
||||||
}
|
}
|
||||||
|
|
||||||
void McClient::emitSucceed(QJsonObject data) {
|
void McClient::emitSucceed(QJsonObject data)
|
||||||
|
{
|
||||||
emit succeeded(data);
|
emit succeeded(data);
|
||||||
emit finished();
|
emit finished();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
#include <QObject>
|
#include <QFuture>
|
||||||
#include <QTcpSocket>
|
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QFuture>
|
#include <QObject>
|
||||||
|
#include <QTcpSocket>
|
||||||
|
|
||||||
#include <Exception.h>
|
#include <Exception.h>
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ public:
|
||||||
explicit McClient(QObject* parent, QString domain, QString ip, short port);
|
explicit McClient(QObject* parent, QString domain, QString ip, short port);
|
||||||
//! Read status data of the server, and calls the succeeded() signal with the parsed JSON data
|
//! Read status data of the server, and calls the succeeded() signal with the parsed JSON data
|
||||||
void getStatusData();
|
void getStatusData();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void sendRequest();
|
void sendRequest();
|
||||||
//! Accumulate data until we have a full response, then call parseResponse() once
|
//! Accumulate data until we have a full response, then call parseResponse() once
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
#include <QObject>
|
|
||||||
#include <QDnsLookup>
|
|
||||||
#include <QtNetwork/qtcpsocket.h>
|
#include <QtNetwork/qtcpsocket.h>
|
||||||
|
#include <QDnsLookup>
|
||||||
#include <QHostInfo>
|
#include <QHostInfo>
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
#include "McResolver.h"
|
#include "McResolver.h"
|
||||||
|
|
||||||
McResolver::McResolver(QObject* parent, QString domain, int port) : QObject(parent), m_constrDomain(domain), m_constrPort(port) {}
|
McResolver::McResolver(QObject* parent, QString domain, int port) : QObject(parent), m_constrDomain(domain), m_constrPort(port) {}
|
||||||
|
|
||||||
void McResolver::ping() {
|
void McResolver::ping()
|
||||||
|
{
|
||||||
pingWithDomainSRV(m_constrDomain, m_constrPort);
|
pingWithDomainSRV(m_constrDomain, m_constrPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
void McResolver::pingWithDomainSRV(QString domain, int port) {
|
void McResolver::pingWithDomainSRV(QString domain, int port)
|
||||||
|
{
|
||||||
QDnsLookup* lookup = new QDnsLookup(this);
|
QDnsLookup* lookup = new QDnsLookup(this);
|
||||||
lookup->setName(QString("_minecraft._tcp.%1").arg(domain));
|
lookup->setName(QString("_minecraft._tcp.%1").arg(domain));
|
||||||
lookup->setType(QDnsLookup::SRV);
|
lookup->setType(QDnsLookup::SRV);
|
||||||
|
@ -43,7 +45,8 @@ void McResolver::pingWithDomainSRV(QString domain, int port) {
|
||||||
lookup->lookup();
|
lookup->lookup();
|
||||||
}
|
}
|
||||||
|
|
||||||
void McResolver::pingWithDomainA(QString domain, int port) {
|
void McResolver::pingWithDomainA(QString domain, int port)
|
||||||
|
{
|
||||||
QHostInfo::lookupHost(domain, this, [this, port](const QHostInfo& hostInfo) {
|
QHostInfo::lookupHost(domain, this, [this, port](const QHostInfo& hostInfo) {
|
||||||
if (hostInfo.error() != QHostInfo::NoError) {
|
if (hostInfo.error() != QHostInfo::NoError) {
|
||||||
emitFail("A record lookup failed");
|
emitFail("A record lookup failed");
|
||||||
|
@ -61,13 +64,15 @@ void McResolver::pingWithDomainA(QString domain, int port) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void McResolver::emitFail(QString error) {
|
void McResolver::emitFail(QString error)
|
||||||
|
{
|
||||||
qDebug() << "DNS resolver error:" << error;
|
qDebug() << "DNS resolver error:" << error;
|
||||||
emit failed(error);
|
emit failed(error);
|
||||||
emit finished();
|
emit finished();
|
||||||
}
|
}
|
||||||
|
|
||||||
void McResolver::emitSucceed(QString ip, int port) {
|
void McResolver::emitSucceed(QString ip, int port)
|
||||||
|
{
|
||||||
emit succeeded(ip, port);
|
emit succeeded(ip, port);
|
||||||
emit finished();
|
emit finished();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
|
#include <QtNetwork/qtcpsocket.h>
|
||||||
|
#include <QDnsLookup>
|
||||||
|
#include <QHostInfo>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QDnsLookup>
|
|
||||||
#include <QtNetwork/qtcpsocket.h>
|
|
||||||
#include <QHostInfo>
|
|
||||||
|
|
||||||
// resolve the IP and port of a Minecraft server
|
// resolve the IP and port of a Minecraft server
|
||||||
class McResolver : public QObject {
|
class McResolver : public QObject {
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
#include <QFutureWatcher>
|
#include <QFutureWatcher>
|
||||||
|
|
||||||
#include "ServerPingTask.h"
|
|
||||||
#include "McResolver.h"
|
|
||||||
#include "McClient.h"
|
|
||||||
#include <Json.h>
|
#include <Json.h>
|
||||||
|
#include "McClient.h"
|
||||||
|
#include "McResolver.h"
|
||||||
|
#include "ServerPingTask.h"
|
||||||
|
|
||||||
unsigned getOnlinePlayers(QJsonObject data) {
|
unsigned getOnlinePlayers(QJsonObject data)
|
||||||
|
{
|
||||||
return Json::requireInteger(Json::requireObject(data, "players"), "online");
|
return Json::requireInteger(Json::requireObject(data, "players"), "online");
|
||||||
}
|
}
|
||||||
|
|
||||||
void ServerPingTask::executeTask() {
|
void ServerPingTask::executeTask()
|
||||||
|
{
|
||||||
qDebug() << "Querying status of " << QString("%1:%2").arg(m_domain).arg(m_port);
|
qDebug() << "Querying status of " << QString("%1:%2").arg(m_domain).arg(m_port);
|
||||||
|
|
||||||
// Resolve the actual IP and port for the server
|
// Resolve the actual IP and port for the server
|
||||||
|
@ -25,23 +27,15 @@ void ServerPingTask::executeTask() {
|
||||||
qDebug() << "Online players: " << m_outputOnlinePlayers;
|
qDebug() << "Online players: " << m_outputOnlinePlayers;
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
});
|
});
|
||||||
QObject::connect(client, &McClient::failed, this, [this](QString error) {
|
QObject::connect(client, &McClient::failed, this, [this](QString error) { emitFailed(error); });
|
||||||
emitFailed(error);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Delete McClient object when done
|
// Delete McClient object when done
|
||||||
QObject::connect(client, &McClient::finished, this, [this, client]() {
|
QObject::connect(client, &McClient::finished, this, [this, client]() { client->deleteLater(); });
|
||||||
client->deleteLater();
|
|
||||||
});
|
|
||||||
client->getStatusData();
|
client->getStatusData();
|
||||||
});
|
});
|
||||||
QObject::connect(resolver, &McResolver::failed, this, [this](QString error) {
|
QObject::connect(resolver, &McResolver::failed, this, [this](QString error) { emitFailed(error); });
|
||||||
emitFailed(error);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Delete McResolver object when done
|
// Delete McResolver object when done
|
||||||
QObject::connect(resolver, &McResolver::finished, [resolver]() {
|
QObject::connect(resolver, &McResolver::finished, [resolver]() { resolver->deleteLater(); });
|
||||||
resolver->deleteLater();
|
|
||||||
});
|
|
||||||
resolver->ping();
|
resolver->ping();
|
||||||
}
|
}
|
|
@ -5,7 +5,6 @@
|
||||||
|
|
||||||
#include <tasks/Task.h>
|
#include <tasks/Task.h>
|
||||||
|
|
||||||
|
|
||||||
class ServerPingTask : public Task {
|
class ServerPingTask : public Task {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -61,7 +61,7 @@ ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance& instance) : ResourcePa
|
||||||
connect(m_ui->resourceFilterButton, &QPushButton::clicked, this, &ModPage::filterMods);
|
connect(m_ui->resourceFilterButton, &QPushButton::clicked, this, &ModPage::filterMods);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModPage::setFilterWidget(unique_qobject_ptr<ModFilterWidget>& widget)
|
void ModPage::setFilterWidget(std::unique_ptr<ModFilterWidget>& widget)
|
||||||
{
|
{
|
||||||
if (m_filter_widget)
|
if (m_filter_widget)
|
||||||
disconnect(m_filter_widget.get(), nullptr, nullptr, nullptr);
|
disconnect(m_filter_widget.get(), nullptr, nullptr, nullptr);
|
||||||
|
|
|
@ -51,11 +51,11 @@ class ModPage : public ResourcePage {
|
||||||
|
|
||||||
void addResourceToPage(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, std::shared_ptr<ResourceFolderModel>) override;
|
void addResourceToPage(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, std::shared_ptr<ResourceFolderModel>) override;
|
||||||
|
|
||||||
virtual unique_qobject_ptr<ModFilterWidget> createFilterWidget() = 0;
|
virtual std::unique_ptr<ModFilterWidget> createFilterWidget() = 0;
|
||||||
|
|
||||||
[[nodiscard]] bool supportsFiltering() const override { return true; };
|
[[nodiscard]] bool supportsFiltering() const override { return true; };
|
||||||
auto getFilter() const -> const std::shared_ptr<ModFilterWidget::Filter> { return m_filter; }
|
auto getFilter() const -> const std::shared_ptr<ModFilterWidget::Filter> { return m_filter; }
|
||||||
void setFilterWidget(unique_qobject_ptr<ModFilterWidget>&);
|
void setFilterWidget(std::unique_ptr<ModFilterWidget>&);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
ModPage(ModDownloadDialog* dialog, BaseInstance& instance);
|
ModPage(ModDownloadDialog* dialog, BaseInstance& instance);
|
||||||
|
@ -67,7 +67,7 @@ class ModPage : public ResourcePage {
|
||||||
void triggerSearch() override;
|
void triggerSearch() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
unique_qobject_ptr<ModFilterWidget> m_filter_widget;
|
std::unique_ptr<ModFilterWidget> m_filter_widget;
|
||||||
std::shared_ptr<ModFilterWidget::Filter> m_filter;
|
std::shared_ptr<ModFilterWidget::Filter> m_filter;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue