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
|
||||
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,
|
||||
"prHeadSha": .head.sha,
|
||||
"prHeadLabel": .head.label,
|
||||
"prBody": .body,
|
||||
"prBody": (.body // ""),
|
||||
"prLabels": (reduce .labels[].name as $l ([]; . + [$l]))
|
||||
}
|
||||
' <<< "$PR_JSON")"
|
||||
|
@ -125,6 +125,7 @@ jobs:
|
|||
"type": $type,
|
||||
"number": .number,
|
||||
"merged": .merged,
|
||||
"state": (if .state == "open" then "Open" elif .merged then "Merged" else "Closed" end),
|
||||
"labels": (reduce .labels[].name as $l ([]; . + [$l])),
|
||||
"basePrUrl": .html_url,
|
||||
"baseRepoName": .head.repo.name,
|
||||
|
@ -138,11 +139,16 @@ jobs:
|
|||
)
|
||||
{
|
||||
echo "data=$blocked_pr_data";
|
||||
echo "all_merged=$(jq -r 'all(.[].merged; .)' <<< "$blocked_pr_data")";
|
||||
echo "current_blocking=$(jq -c 'map( select( .merged | not ) | .number )' <<< "$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(
|
||||
(.type == "Stacked on" and (.merged | not)) or
|
||||
(.type == "Blocked on" and (.state == "Open"))
|
||||
) | .number
|
||||
)' <<< "$blocked_pr_data" )";
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Add 'blocked' Label is Missing
|
||||
- name: Add 'blocked' Label if Missing
|
||||
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)
|
||||
continue-on-error: true
|
||||
|
@ -184,14 +190,18 @@ jobs:
|
|||
# create commit Status, overwrites previous identical context
|
||||
while read -r pr_data ; do
|
||||
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 \
|
||||
--method POST \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
"/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 "description=$DESC" \
|
||||
-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_name=$(jq -r '.baseRepoName' <<< "$pr_data")
|
||||
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")
|
||||
echo " - $type #$base_pr $status [(compare)]($compare_url)" >> "$COMMENT_PATH"
|
||||
done < <(jq -c '.[]' <<< "$BLOCKING_DATA")
|
||||
|
|
700
.github/workflows/build.yml
vendored
700
.github/workflows/build.yml
vendored
|
@ -1,619 +1,199 @@
|
|||
name: Build
|
||||
|
||||
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:
|
||||
inputs:
|
||||
build_type:
|
||||
description: Type of build (Debug, Release, RelWithDebInfo, MinSizeRel)
|
||||
build-type:
|
||||
description: Type of build (Debug or Release)
|
||||
type: string
|
||||
default: Debug
|
||||
is_qt_cached:
|
||||
description: Enable Qt caching or not
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
build-type:
|
||||
description: Type of build (Debug or Release)
|
||||
type: string
|
||||
default: true
|
||||
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
|
||||
default: Debug
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build (${{ matrix.artifact-name }})
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-22.04
|
||||
qt_ver: 6
|
||||
qt_host: 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"
|
||||
artifact-name: Linux
|
||||
base-cmake-preset: linux
|
||||
|
||||
- os: windows-2022
|
||||
name: "Windows-MinGW-w64"
|
||||
msystem: clang64
|
||||
vcvars_arch: "amd64_x86"
|
||||
artifact-name: Windows-MinGW-w64
|
||||
base-cmake-preset: windows_mingw
|
||||
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
|
||||
name: "Windows-MSVC"
|
||||
msystem: ""
|
||||
architecture: "x64"
|
||||
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"
|
||||
artifact-name: Windows-MSVC
|
||||
base-cmake-preset: windows_msvc
|
||||
# TODO(@getchoo): This is the default in setup-dependencies/windows. Why isn't it working?!?!
|
||||
vcvars-arch: amd64
|
||||
|
||||
- os: windows-2022
|
||||
name: "Windows-MSVC-arm64"
|
||||
msystem: ""
|
||||
architecture: "arm64"
|
||||
vcvars_arch: "amd64_arm64"
|
||||
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"
|
||||
artifact-name: Windows-MSVC-arm64
|
||||
base-cmake-preset: windows_msvc_arm64_cross
|
||||
vcvars-arch: amd64_arm64
|
||||
qt-architecture: win64_msvc2022_arm64_cross_compiled
|
||||
|
||||
- os: macos-14
|
||||
name: macOS
|
||||
macosx_deployment_target: 11.0
|
||||
qt_ver: 6
|
||||
qt_host: mac
|
||||
qt_arch: ""
|
||||
qt_version: "6.8.1"
|
||||
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
||||
artifact-name: macOS
|
||||
base-cmake-preset: ${{ (inputs.build-type || 'Debug') == 'Debug' && 'macos_universal' || 'macos' }}
|
||||
macosx-deployment-target: 12.0
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: ${{ matrix.msystem != '' && 'msys2 {0}' || 'bash' }}
|
||||
|
||||
env:
|
||||
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
|
||||
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx-deployment-target }}
|
||||
|
||||
steps:
|
||||
##
|
||||
# PREPARE
|
||||
# SETUP
|
||||
##
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: "true"
|
||||
submodules: true
|
||||
|
||||
- name: "Setup MSYS2"
|
||||
if: runner.os == 'Windows' && matrix.msystem != ''
|
||||
uses: msys2/setup-msys2@v2
|
||||
- name: Setup dependencies
|
||||
id: setup-dependencies
|
||||
uses: ./.github/actions/setup-dependencies
|
||||
with:
|
||||
build-type: ${{ inputs.build-type || 'Debug' }}
|
||||
msystem: ${{ matrix.msystem }}
|
||||
update: true
|
||||
install: >-
|
||||
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
|
||||
vcvars-arch: ${{ matrix.vcvars-arch }}
|
||||
qt-architecture: ${{ matrix.qt-architecture }}
|
||||
|
||||
##
|
||||
# BUILD
|
||||
##
|
||||
|
||||
- name: Build
|
||||
if: runner.os != 'Windows'
|
||||
- name: Get CMake preset
|
||||
id: cmake-preset
|
||||
env:
|
||||
BASE_CMAKE_PRESET: ${{ matrix.base-cmake-preset }}
|
||||
PRESET_TYPE: ${{ (inputs.build-type || 'Debug') == 'Debug' && 'debug' || 'ci' }}
|
||||
run: |
|
||||
cmake --build ${{ env.BUILD_DIR }}
|
||||
echo preset="$BASE_CMAKE_PRESET"_"$PRESET_TYPE" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Build (Windows MinGW-w64)
|
||||
if: runner.os == 'Windows' && matrix.msystem != ''
|
||||
shell: msys2 {0}
|
||||
- name: Run CMake workflow
|
||||
env:
|
||||
CMAKE_PRESET: ${{ steps.cmake-preset.outputs.preset }}
|
||||
run: |
|
||||
cmake --build ${{ env.BUILD_DIR }}
|
||||
|
||||
- name: Build (Windows MSVC)
|
||||
if: runner.os == 'Windows' && matrix.msystem == ''
|
||||
run: |
|
||||
cmake --build ${{ env.BUILD_DIR }} --config ${{ inputs.build_type }}
|
||||
cmake --workflow --preset "$CMAKE_PRESET"
|
||||
|
||||
##
|
||||
# TEST
|
||||
# PACKAGE
|
||||
##
|
||||
|
||||
- name: Test
|
||||
if: runner.os != 'Windows'
|
||||
- name: Get short version
|
||||
id: short-version
|
||||
shell: bash
|
||||
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)
|
||||
if: runner.os == 'Windows' && matrix.msystem != ''
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
ctest -E "^example64|example$" --test-dir build --output-on-failure
|
||||
- name: Package (Linux)
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
uses: ./.github/actions/package/linux
|
||||
with:
|
||||
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)
|
||||
if: runner.os == 'Windows' && matrix.msystem == '' && matrix.architecture != 'arm64'
|
||||
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
|
||||
gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
gpg-private-key-id: ${{ secrets.GPG_PRIVATE_KEY_ID }}
|
||||
|
||||
- name: Package (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
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
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
uses: ./.github/actions/package/macos
|
||||
with:
|
||||
name: PrismLauncher-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||
path: PrismLauncher.zip
|
||||
version: ${{ steps.short-version.outputs.version }}
|
||||
build-type: ${{ steps.setup-dependencies.outputs.build-type }}
|
||||
artifact-name: ${{ matrix.artifact-name }}
|
||||
|
||||
- name: Upload binary zip (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
uses: actions/upload-artifact@v4
|
||||
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 }}
|
||||
sparkle-ed25519-key: ${{ secrets.SPARKLE-ED25519_KEY }}
|
||||
|
||||
- name: Package (Windows)
|
||||
if: ${{ runner.os == 'Windows' }}
|
||||
uses: ./.github/actions/package/windows
|
||||
with:
|
||||
name: PrismLauncher-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||
path: ${{ env.INSTALL_DIR }}/**
|
||||
version: ${{ steps.short-version.outputs.version }}
|
||||
build-type: ${{ steps.setup-dependencies.outputs.build-type }}
|
||||
artifact-name: ${{ matrix.artifact-name }}
|
||||
msystem: ${{ matrix.msystem }}
|
||||
|
||||
- name: Upload binary zip (Windows, portable)
|
||||
if: runner.os == 'Windows'
|
||||
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
|
||||
windows-codesign-cert: ${{ secrets.WINDOWS_CODESIGN_CERT }}
|
||||
windows-codesign-password: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }}
|
||||
|
|
84
.github/workflows/codeql.yml
vendored
84
.github/workflows/codeql.yml
vendored
|
@ -2,37 +2,50 @@ name: "CodeQL Code Scanning"
|
|||
|
||||
on:
|
||||
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:
|
||||
- "**"
|
||||
- "!.github/**"
|
||||
- ".github/workflows/codeql.yml"
|
||||
- "!flatpak/"
|
||||
- "!nix/"
|
||||
- "!scripts/"
|
||||
# File types
|
||||
- "**.cpp"
|
||||
- "**.h"
|
||||
- "**.java"
|
||||
|
||||
- "!.git*"
|
||||
- "!.envrc"
|
||||
- "!**.md"
|
||||
# Directories
|
||||
- "buildconfig/"
|
||||
- "cmake/"
|
||||
- "launcher/"
|
||||
- "libraries/"
|
||||
- "program_info/"
|
||||
- "tests/"
|
||||
|
||||
# Files
|
||||
- "CMakeLists.txt"
|
||||
- "COPYING.md"
|
||||
- "!renovate.json"
|
||||
|
||||
# Workflows
|
||||
- ".github/codeql"
|
||||
- ".github/workflows/codeql.yml"
|
||||
- ".github/actions/setup-dependencies/"
|
||||
pull_request:
|
||||
# See above
|
||||
paths:
|
||||
- "**"
|
||||
- "!.github/**"
|
||||
- ".github/workflows/codeql.yml"
|
||||
- "!flatpak/"
|
||||
- "!nix/"
|
||||
- "!scripts/"
|
||||
# File types
|
||||
- "**.cpp"
|
||||
- "**.h"
|
||||
|
||||
- "!.git*"
|
||||
- "!.envrc"
|
||||
- "!**.md"
|
||||
# Directories
|
||||
- "buildconfig/"
|
||||
- "cmake/"
|
||||
- "launcher/"
|
||||
- "libraries/"
|
||||
- "program_info/"
|
||||
- "tests/"
|
||||
|
||||
# Files
|
||||
- "CMakeLists.txt"
|
||||
- "COPYING.md"
|
||||
- "!renovate.json"
|
||||
|
||||
# Workflows
|
||||
- ".github/codeql"
|
||||
- ".github/workflows/codeql.yml"
|
||||
- ".github/actions/setup-dependencies/"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
|
@ -52,28 +65,15 @@ jobs:
|
|||
queries: security-and-quality
|
||||
languages: cpp, java
|
||||
|
||||
- name: Install Dependencies
|
||||
run: sudo apt-get -y update
|
||||
|
||||
sudo apt-get -y install ninja-build extra-cmake-modules scdoc
|
||||
|
||||
- name: Install Qt
|
||||
uses: jurplel/install-qt-action@v3
|
||||
- name: Setup dependencies
|
||||
uses: ./.github/actions/setup-dependencies
|
||||
with:
|
||||
aqtversion: "==3.1.*"
|
||||
py7zrversion: ">=0.20.2"
|
||||
version: "6.8.1"
|
||||
host: "linux"
|
||||
target: "desktop"
|
||||
arch: ""
|
||||
modules: "qt5compat qtimageformats qtnetworkauth"
|
||||
tools: ""
|
||||
build-type: Debug
|
||||
|
||||
- name: Configure and Build
|
||||
run: |
|
||||
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/usr -G Ninja
|
||||
|
||||
cmake --build build
|
||||
cmake --preset linux_debug
|
||||
cmake --build --preset linux_debug
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
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
|
||||
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:
|
||||
- "**"
|
||||
- "!.github/**"
|
||||
- ".github/workflows/flatpak.yml"
|
||||
- "!nix/"
|
||||
- "!scripts/"
|
||||
# File types
|
||||
- "**.cpp"
|
||||
- "**.h"
|
||||
- "**.java"
|
||||
|
||||
- "!.git*"
|
||||
- "!.envrc"
|
||||
- "!**.md"
|
||||
# Build files
|
||||
- "flatpak/"
|
||||
|
||||
# Directories
|
||||
- "buildconfig/"
|
||||
- "cmake/"
|
||||
- "launcher/"
|
||||
- "libraries/"
|
||||
- "program_info/"
|
||||
- "tests/"
|
||||
|
||||
# Files
|
||||
- "CMakeLists.txt"
|
||||
- "COPYING.md"
|
||||
- "!renovate.json"
|
||||
|
||||
# Workflows
|
||||
- ".github/workflows/flatpak.yml"
|
||||
pull_request:
|
||||
# See above
|
||||
paths:
|
||||
- "**"
|
||||
- "!.github/**"
|
||||
- ".github/workflows/flatpak.yml"
|
||||
- "!nix/"
|
||||
- "!scripts/"
|
||||
# File types
|
||||
- "**.cpp"
|
||||
- "**.h"
|
||||
|
||||
- "!.git*"
|
||||
- "!.envrc"
|
||||
- "!**.md"
|
||||
# Build files
|
||||
- "flatpak/"
|
||||
|
||||
# Directories
|
||||
- "buildconfig/"
|
||||
- "cmake/"
|
||||
- "launcher/"
|
||||
- "libraries/"
|
||||
- "program_info/"
|
||||
- "tests/"
|
||||
|
||||
# Files
|
||||
- "CMakeLists.txt"
|
||||
- "COPYING.md"
|
||||
- "!renovate.json"
|
||||
|
||||
# Workflows
|
||||
- ".github/workflows/flatpak.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
|
|
66
.github/workflows/nix.yml
vendored
66
.github/workflows/nix.yml
vendored
|
@ -4,34 +4,56 @@ on:
|
|||
push:
|
||||
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:
|
||||
- "**"
|
||||
- "!.github/**"
|
||||
- ".github/workflows/nix.yml"
|
||||
- "!flatpak/"
|
||||
- "!scripts/"
|
||||
# File types
|
||||
- "**.cpp"
|
||||
- "**.h"
|
||||
- "**.java"
|
||||
|
||||
- "!.git*"
|
||||
- "!.envrc"
|
||||
- "!**.md"
|
||||
# Build files
|
||||
- "**.nix"
|
||||
- "nix/"
|
||||
- "flake.lock"
|
||||
|
||||
# Directories
|
||||
- "buildconfig/"
|
||||
- "cmake/"
|
||||
- "launcher/"
|
||||
- "libraries/"
|
||||
- "program_info/"
|
||||
- "tests/"
|
||||
|
||||
# Files
|
||||
- "CMakeLists.txt"
|
||||
- "COPYING.md"
|
||||
- "!renovate.json"
|
||||
|
||||
# Workflows
|
||||
- ".github/workflows/nix.yml"
|
||||
pull_request_target:
|
||||
paths:
|
||||
- "**"
|
||||
- "!.github/**"
|
||||
- ".github/workflows/nix.yml"
|
||||
- "!flatpak/"
|
||||
- "!scripts/"
|
||||
# File types
|
||||
- "**.cpp"
|
||||
- "**.h"
|
||||
|
||||
- "!.git*"
|
||||
- "!.envrc"
|
||||
- "!**.md"
|
||||
# Build files
|
||||
- "**.nix"
|
||||
- "nix/"
|
||||
- "flake.lock"
|
||||
|
||||
# Directories
|
||||
- "buildconfig/"
|
||||
- "cmake/"
|
||||
- "launcher/"
|
||||
- "libraries/"
|
||||
- "program_info/"
|
||||
- "tests/"
|
||||
|
||||
# Files
|
||||
- "CMakeLists.txt"
|
||||
- "COPYING.md"
|
||||
- "!renovate.json"
|
||||
|
||||
# Workflows
|
||||
- ".github/workflows/nix.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
|
@ -89,7 +111,7 @@ jobs:
|
|||
# For PRs
|
||||
- name: Setup Nix Magic Cache
|
||||
if: ${{ env.USE_DETERMINATE == 'true' }}
|
||||
uses: DeterminateSystems/flakehub-cache-action@v1
|
||||
uses: DeterminateSystems/flakehub-cache-action@v2
|
||||
|
||||
# For in-tree builds
|
||||
- name: Setup Cachix
|
||||
|
|
|
@ -10,20 +10,8 @@ jobs:
|
|||
name: Build Release
|
||||
uses: ./.github/workflows/build.yml
|
||||
with:
|
||||
build_type: Release
|
||||
is_qt_cached: false
|
||||
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 }}
|
||||
build-type: Release
|
||||
secrets: inherit
|
||||
|
||||
create_release:
|
||||
needs: build_release
|
||||
|
@ -78,6 +66,17 @@ jobs:
|
|||
cd ..
|
||||
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
|
||||
id: create_release
|
||||
uses: softprops/action-gh-release@v2
|
||||
|
@ -94,6 +93,9 @@ jobs:
|
|||
PrismLauncher-Windows-MinGW-w64-${{ env.VERSION }}.zip
|
||||
PrismLauncher-Windows-MinGW-w64-Portable-${{ env.VERSION }}.zip
|
||||
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-Portable-${{ env.VERSION }}.zip
|
||||
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:
|
||||
- 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:
|
||||
commit-msg: "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
|
||||
/CMakeFiles
|
||||
CMakeCache.txt
|
||||
CMakeUserPresets.json
|
||||
/.project
|
||||
/.settings
|
||||
/.idea
|
||||
|
|
4
.gitmodules
vendored
4
.gitmodules
vendored
|
@ -19,6 +19,6 @@
|
|||
[submodule "flatpak/shared-modules"]
|
||||
path = flatpak/shared-modules
|
||||
url = https://github.com/flathub/shared-modules.git
|
||||
[submodule "libraries/qt-qrcodegenerator/QR-Code-generator"]
|
||||
path = libraries/qt-qrcodegenerator/QR-Code-generator
|
||||
[submodule "libraries/qrcodegenerator"]
|
||||
path = libraries/qrcodegenerator
|
||||
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/launcher) # java based launcher part for Minecraft
|
||||
add_subdirectory(libraries/javacheck) # java compatibility checker
|
||||
add_subdirectory(libraries/qt-qrcodegenerator) # qr code generator
|
||||
if(FORCE_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/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 ###############################
|
||||
|
||||
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
|
||||
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)
|
||||
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/).
|
||||
|
||||
[](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.
|
||||
|
||||
|
|
|
@ -53,7 +53,6 @@ Config::Config()
|
|||
LAUNCHER_SVGFILENAME = "@Launcher_SVGFileName@";
|
||||
|
||||
USER_AGENT = "@Launcher_UserAgent@";
|
||||
USER_AGENT_UNCACHED = USER_AGENT + " (Uncached)";
|
||||
|
||||
// Version information
|
||||
VERSION_MAJOR = @Launcher_VERSION_MAJOR@;
|
||||
|
|
|
@ -107,9 +107,6 @@ class Config {
|
|||
/// User-Agent to use.
|
||||
QString USER_AGENT;
|
||||
|
||||
/// User-Agent to use for uncached requests.
|
||||
QString USER_AGENT_UNCACHED;
|
||||
|
||||
/// The git commit hash of this build
|
||||
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": {
|
||||
"locked": {
|
||||
"lastModified": 1744932701,
|
||||
"narHash": "sha256-fusHbZCyv126cyArUwwKrLdCkgVAIaa/fQJYFlCEqiU=",
|
||||
"lastModified": 1748460289,
|
||||
"narHash": "sha256-7doLyJBzCllvqX4gszYtmZUToxKvMUrg45EUWaUYmBg=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "b024ced1aac25639f8ca8fdfc2f8c4fbd66c48ef",
|
||||
"rev": "96ec055edbe5ee227f28cdbc3f1ddf1df5965102",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -32,7 +32,7 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"qt-qrcodegenerator": {
|
||||
"qrcodegenerator": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1737616857,
|
||||
|
@ -52,7 +52,7 @@
|
|||
"inputs": {
|
||||
"libnbtplusplus": "libnbtplusplus",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"qt-qrcodegenerator": "qt-qrcodegenerator"
|
||||
"qrcodegenerator": "qrcodegenerator"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
flake = false;
|
||||
};
|
||||
|
||||
qt-qrcodegenerator = {
|
||||
qrcodegenerator = {
|
||||
url = "github:nayuki/QR-Code-generator";
|
||||
flake = false;
|
||||
};
|
||||
|
@ -27,7 +27,7 @@
|
|||
self,
|
||||
nixpkgs,
|
||||
libnbtplusplus,
|
||||
qt-qrcodegenerator,
|
||||
qrcodegenerator,
|
||||
}:
|
||||
|
||||
let
|
||||
|
@ -175,7 +175,7 @@
|
|||
prismlauncher-unwrapped = prev.callPackage ./nix/unwrapped.nix {
|
||||
inherit
|
||||
libnbtplusplus
|
||||
qt-qrcodegenerator
|
||||
qrcodegenerator
|
||||
self
|
||||
;
|
||||
};
|
||||
|
|
|
@ -128,6 +128,7 @@
|
|||
|
||||
#include <stdlib.h>
|
||||
#include <sys.h>
|
||||
#include <QStringLiteral>
|
||||
#include "SysInfo.h"
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
|
@ -1887,17 +1888,6 @@ QString Application::getUserAgent()
|
|||
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,
|
||||
const QString& oldData,
|
||||
const QString& name,
|
||||
|
|
|
@ -160,7 +160,6 @@ class Application : public QApplication {
|
|||
QString getFlameAPIKey();
|
||||
QString getModrinthAPIToken();
|
||||
QString getUserAgent();
|
||||
QString getUserAgentUncached();
|
||||
|
||||
/// this is the root of the 'installation'. Used for automatic updates
|
||||
const QString& root() { return m_rootPath; }
|
||||
|
|
|
@ -310,6 +310,8 @@ set(MINECRAFT_SOURCES
|
|||
minecraft/ParseUtils.h
|
||||
minecraft/ProfileUtils.cpp
|
||||
minecraft/ProfileUtils.h
|
||||
minecraft/ShortcutUtils.cpp
|
||||
minecraft/ShortcutUtils.h
|
||||
minecraft/Library.cpp
|
||||
minecraft/Library.h
|
||||
minecraft/MojangDownloadInfo.h
|
||||
|
@ -834,6 +836,10 @@ SET(LAUNCHER_SOURCES
|
|||
icons/IconList.h
|
||||
icons/IconList.cpp
|
||||
|
||||
# log utils
|
||||
logs/AnonymizeLog.cpp
|
||||
logs/AnonymizeLog.h
|
||||
|
||||
# GUI - windows
|
||||
ui/GuiUtil.h
|
||||
ui/GuiUtil.cpp
|
||||
|
@ -1048,6 +1054,8 @@ SET(LAUNCHER_SOURCES
|
|||
ui/dialogs/ProfileSetupDialog.h
|
||||
ui/dialogs/CopyInstanceDialog.cpp
|
||||
ui/dialogs/CopyInstanceDialog.h
|
||||
ui/dialogs/CreateShortcutDialog.cpp
|
||||
ui/dialogs/CreateShortcutDialog.h
|
||||
ui/dialogs/CustomMessageBox.cpp
|
||||
ui/dialogs/CustomMessageBox.h
|
||||
ui/dialogs/ExportInstanceDialog.cpp
|
||||
|
@ -1230,6 +1238,7 @@ qt_wrap_ui(LAUNCHER_UI
|
|||
ui/widgets/MinecraftSettingsWidget.ui
|
||||
ui/widgets/JavaSettingsWidget.ui
|
||||
ui/dialogs/CopyInstanceDialog.ui
|
||||
ui/dialogs/CreateShortcutDialog.ui
|
||||
ui/dialogs/ProfileSetupDialog.ui
|
||||
ui/dialogs/ProgressDialog.ui
|
||||
ui/dialogs/NewInstanceDialog.ui
|
||||
|
@ -1306,7 +1315,7 @@ target_link_libraries(Launcher_logic
|
|||
qdcss
|
||||
BuildConfig
|
||||
Qt${QT_VERSION_MAJOR}::Widgets
|
||||
qrcode
|
||||
qrcodegenerator
|
||||
)
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
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)
|
||||
|
|
|
@ -69,7 +69,7 @@ class FileIgnoreProxy : public QSortFilterProxyModel {
|
|||
// list of relative paths that need to be removed completely from model
|
||||
inline SeparatorPrefixTree<'/'>& ignoreFilesWithPath() { return m_ignoreFilePaths; }
|
||||
|
||||
bool filterFile(const QString& fileName) const;
|
||||
bool filterFile(const QFileInfo& fileName) const;
|
||||
|
||||
void loadBlockedPathsFromFile(const QString& fileName);
|
||||
|
||||
|
|
|
@ -107,6 +107,10 @@ namespace fs = std::filesystem;
|
|||
|
||||
#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 {
|
||||
HANDLE FileHandle;
|
||||
LARGE_INTEGER SourceFileOffset;
|
||||
|
@ -116,6 +120,7 @@ struct _DUPLICATE_EXTENTS_DATA {
|
|||
|
||||
using DUPLICATE_EXTENTS_DATA = _DUPLICATE_EXTENTS_DATA;
|
||||
using PDUPLICATE_EXTENTS_DATA = _DUPLICATE_EXTENTS_DATA*;
|
||||
#endif
|
||||
|
||||
struct _FSCTL_GET_INTEGRITY_INFORMATION_BUFFER {
|
||||
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);
|
||||
}
|
||||
|
||||
QString getApplicationsDir()
|
||||
{
|
||||
return QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation);
|
||||
}
|
||||
|
||||
// Cross-platform Shortcut creation
|
||||
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;
|
||||
}
|
||||
#if defined(Q_OS_MACOS)
|
||||
// Create the Application
|
||||
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/";
|
||||
QDir application = destination + ".app/";
|
||||
|
||||
if (application.exists()) {
|
||||
qWarning() << "Application already exists!";
|
||||
|
|
|
@ -353,6 +353,9 @@ bool checkProblemticPathJava(QDir folder);
|
|||
// Get the Directory representing the User's Desktop
|
||||
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
|
||||
// Equivalent to doing QDir::rename, but allowing for overrides
|
||||
bool overrideFolder(QString overwritten_path, QString override_path);
|
||||
|
|
|
@ -72,7 +72,6 @@ bool InstanceImportTask::abort()
|
|||
bool wasAborted = false;
|
||||
if (m_task)
|
||||
wasAborted = m_task->abort();
|
||||
Task::abort();
|
||||
return wasAborted;
|
||||
}
|
||||
|
||||
|
@ -212,6 +211,7 @@ void InstanceImportTask::processZipPack()
|
|||
progressStep->status = status;
|
||||
stepProgress(*progressStep);
|
||||
});
|
||||
connect(zipTask.get(), &Task::warningLogged, this, [this](const QString& line) { m_Warnings.append(line); });
|
||||
m_task.reset(zipTask);
|
||||
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()
|
||||
{
|
||||
shared_qobject_ptr<FlameCreationTask> inst_creation_task = nullptr;
|
||||
|
@ -288,6 +307,14 @@ void InstanceImportTask::processFlame()
|
|||
}
|
||||
|
||||
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->setGroup(m_instGroup);
|
||||
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::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::warningLogged, this, [this](const QString& line) { m_Warnings.append(line); });
|
||||
|
||||
m_task.reset(inst_creation_task);
|
||||
setAbortable(true);
|
||||
m_task->start();
|
||||
|
@ -340,17 +369,7 @@ void InstanceImportTask::processMultiMC()
|
|||
} else {
|
||||
m_instIcon = instance.iconKey();
|
||||
|
||||
auto importIconPath = IconUtils::findBestIconIn(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);
|
||||
}
|
||||
installIcon(instance.instanceRoot(), m_instIcon);
|
||||
}
|
||||
emitSucceeded();
|
||||
}
|
||||
|
@ -387,6 +406,14 @@ void InstanceImportTask::processModrinth()
|
|||
}
|
||||
|
||||
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->setGroup(m_instGroup);
|
||||
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::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::warningLogged, this, [this](const QString& line) { m_Warnings.append(line); });
|
||||
|
||||
m_task.reset(inst_creation_task);
|
||||
setAbortable(true);
|
||||
m_task->start();
|
||||
|
|
|
@ -418,7 +418,7 @@ bool extractFile(QString fileCompressed, QString file, QString 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);
|
||||
if (!rootDirectory.exists())
|
||||
|
@ -443,8 +443,8 @@ bool collectFileListRecursively(const QString& rootDir, const QString& subDir, Q
|
|||
// collect files
|
||||
entries = directory.entryInfoList(QDir::Files);
|
||||
for (const auto& e : entries) {
|
||||
if (excludeFilter && excludeFilter(e)) {
|
||||
QString relativeFilePath = rootDirectory.relativeFilePath(e.absoluteFilePath());
|
||||
if (excludeFilter && excludeFilter(relativeFilePath)) {
|
||||
qDebug() << "Skipping file " << relativeFilePath;
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -56,6 +56,7 @@
|
|||
|
||||
namespace MMCZip {
|
||||
using FilterFunction = std::function<bool(const QString&)>;
|
||||
using FilterFileFunction = std::function<bool(const QFileInfo&)>;
|
||||
|
||||
/**
|
||||
* 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)
|
||||
* \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)
|
||||
class ExportToZipTask : public Task {
|
||||
|
|
|
@ -365,13 +365,13 @@ QList<QString> JavaUtils::FindJavaPaths()
|
|||
javas.append("/System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/java");
|
||||
QDir libraryJVMDir("/Library/Java/JavaVirtualMachines/");
|
||||
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/jre/bin/java");
|
||||
}
|
||||
QDir systemLibraryJVMDir("/System/Library/Java/JavaVirtualMachines/");
|
||||
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/Commands/java");
|
||||
}
|
||||
|
@ -381,14 +381,14 @@ QList<QString> JavaUtils::FindJavaPaths()
|
|||
// javas downloaded by sdkman
|
||||
QDir sdkmanDir(FS::PathCombine(home, ".sdkman/candidates/java"));
|
||||
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");
|
||||
}
|
||||
|
||||
// java in user library folder (like from intellij downloads)
|
||||
QDir userLibraryJVMDir(FS::PathCombine(home, "Library/Java/JavaVirtualMachines/"));
|
||||
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/Commands/java");
|
||||
}
|
||||
|
|
|
@ -55,6 +55,7 @@ void ArchiveDownloadTask::executeTask()
|
|||
connect(download.get(), &Task::stepProgress, this, &ArchiveDownloadTask::propagateStepProgress);
|
||||
connect(download.get(), &Task::status, this, &ArchiveDownloadTask::setStatus);
|
||||
connect(download.get(), &Task::details, this, &ArchiveDownloadTask::setDetails);
|
||||
connect(download.get(), &Task::aborted, this, &ArchiveDownloadTask::emitAborted);
|
||||
connect(download.get(), &Task::succeeded, [this, fullPath] {
|
||||
// This should do all of the extracting and creating folders
|
||||
extractJava(fullPath);
|
||||
|
@ -135,7 +136,6 @@ bool ArchiveDownloadTask::abort()
|
|||
auto aborted = canAbort();
|
||||
if (m_task)
|
||||
aborted = m_task->abort();
|
||||
emitAborted();
|
||||
return aborted;
|
||||
};
|
||||
} // namespace Java
|
|
@ -167,8 +167,8 @@ bool LogModel::isOverFlow()
|
|||
return m_numLines >= m_maxLines && m_stopOnOverflow;
|
||||
}
|
||||
|
||||
|
||||
MessageLevel::Enum LogModel::previousLevel() {
|
||||
MessageLevel::Enum LogModel::previousLevel()
|
||||
{
|
||||
if (!m_content.isEmpty()) {
|
||||
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);
|
|
@ -107,7 +107,7 @@ std::optional<LogParser::ParsedItem> LogParser::parseNext()
|
|||
if (m_buffer.trimmed().isEmpty()) {
|
||||
auto text = QString(m_buffer);
|
||||
m_buffer.clear();
|
||||
return LogParser::PlainText { text };
|
||||
return LogParser::PlainText{ text };
|
||||
}
|
||||
|
||||
// check if we have a full xml log4j event
|
||||
|
|
|
@ -248,6 +248,7 @@ void MinecraftInstance::loadSpecificSettings()
|
|||
m_settings->registerSetting("ExportSummary", "");
|
||||
m_settings->registerSetting("ExportAuthor", "");
|
||||
m_settings->registerSetting("ExportOptionalFiles", true);
|
||||
m_settings->registerSetting("ExportRecommendedRAM");
|
||||
|
||||
auto dataPacksEnabled = m_settings->registerSetting("GlobalDataPacksEnabled", false);
|
||||
auto dataPacksPath = m_settings->registerSetting("GlobalDataPacksPath", "");
|
||||
|
@ -1019,7 +1020,6 @@ QMap<QString, QString> MinecraftInstance::createCensorFilterFromSession(AuthSess
|
|||
return filter;
|
||||
}
|
||||
|
||||
|
||||
QStringList MinecraftInstance::getLogFileSearchPaths()
|
||||
{
|
||||
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
|
|
@ -84,7 +84,7 @@ class Mod : public Resource {
|
|||
|
||||
bool valid() const override;
|
||||
|
||||
[[nodiscard]] int compare(const Resource & other, SortType type) const override;
|
||||
[[nodiscard]] int compare(const Resource& other, SortType type) const override;
|
||||
[[nodiscard]] bool applyFilter(QRegularExpression filter) const override;
|
||||
|
||||
// Delete all the files of this mod
|
||||
|
|
|
@ -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_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_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_columnsHiddenByDefault = { false, false, false, false, false, true };
|
||||
}
|
||||
|
|
|
@ -268,6 +268,26 @@ void SkinList::installSkins(const QStringList& iconFiles)
|
|||
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)
|
||||
{
|
||||
if (file.isEmpty())
|
||||
|
@ -282,7 +302,7 @@ QString SkinList::installSkin(const QString& file, const QString& name)
|
|||
if (fileinfo.suffix() != "png" && !SkinModel(fileinfo.absoluteFilePath()).isValid())
|
||||
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");
|
||||
}
|
||||
|
@ -371,7 +391,8 @@ bool SkinList::setData(const QModelIndex& idx, const QVariant& value, int role)
|
|||
auto& skin = m_skinList[row];
|
||||
auto newName = value.toString();
|
||||
if (skin.name() != newName) {
|
||||
skin.rename(newName);
|
||||
if (!skin.rename(newName))
|
||||
return false;
|
||||
save();
|
||||
}
|
||||
return true;
|
||||
|
|
|
@ -122,7 +122,11 @@ QString SkinModel::name() const
|
|||
bool SkinModel::rename(QString newName)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
|
||||
#include "settings/INISettingsObject.h"
|
||||
|
||||
#include "sys.h"
|
||||
#include "tasks/ConcurrentTask.h"
|
||||
#include "ui/dialogs/BlockedModsDialog.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");
|
||||
QFileInfo jarmodsInfo(jarmodsPath);
|
||||
if (jarmodsInfo.isDir()) {
|
||||
|
|
|
@ -41,22 +41,8 @@
|
|||
const QString FlamePackExportTask::TEMPLATE = "<li><a href=\"{url}\">{name}{authors}</a></li>\n";
|
||||
const QStringList FlamePackExportTask::FILE_EXTENSIONS({ "jar", "zip" });
|
||||
|
||||
FlamePackExportTask::FlamePackExportTask(const QString& name,
|
||||
const QString& version,
|
||||
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)
|
||||
FlamePackExportTask::FlamePackExportTask(FlamePackExportOptions&& options)
|
||||
: m_options(std::move(options)), m_gameRoot(m_options.instance->gameRoot())
|
||||
{}
|
||||
|
||||
void FlamePackExportTask::executeTask()
|
||||
|
@ -70,7 +56,6 @@ bool FlamePackExportTask::abort()
|
|||
{
|
||||
if (task) {
|
||||
task->abort();
|
||||
emitAborted();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -82,7 +67,7 @@ void FlamePackExportTask::collectFiles()
|
|||
QCoreApplication::processEvents();
|
||||
|
||||
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"));
|
||||
return;
|
||||
}
|
||||
|
@ -90,11 +75,8 @@ void FlamePackExportTask::collectFiles()
|
|||
pendingHashes.clear();
|
||||
resolvedFiles.clear();
|
||||
|
||||
if (mcInstance != nullptr) {
|
||||
mcInstance->loaderModList()->update();
|
||||
connect(mcInstance->loaderModList().get(), &ModFolderModel::updateFinished, this, &FlamePackExportTask::collectHashes);
|
||||
} else
|
||||
collectHashes();
|
||||
m_options.instance->loaderModList()->update();
|
||||
connect(m_options.instance->loaderModList().get(), &ModFolderModel::updateFinished, this, &FlamePackExportTask::collectHashes);
|
||||
}
|
||||
|
||||
void FlamePackExportTask::collectHashes()
|
||||
|
@ -102,11 +84,11 @@ void FlamePackExportTask::collectHashes()
|
|||
setAbortable(true);
|
||||
setStatus(tr("Finding file hashes..."));
|
||||
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()));
|
||||
task.reset(hashingTask);
|
||||
for (const QFileInfo& file : files) {
|
||||
const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath());
|
||||
const QString relative = m_gameRoot.relativeFilePath(file.absoluteFilePath());
|
||||
// require sensible file types
|
||||
if (!std::any_of(FILE_EXTENSIONS.begin(), FILE_EXTENSIONS.end(), [&relative](const QString& extension) {
|
||||
return relative.endsWith('.' + extension) || relative.endsWith('.' + extension + ".disabled");
|
||||
|
@ -171,6 +153,7 @@ void FlamePackExportTask::collectHashes()
|
|||
progressStep->status = status;
|
||||
stepProgress(*progressStep);
|
||||
});
|
||||
connect(hashingTask.get(), &Task::aborted, this, &FlamePackExportTask::emitAborted);
|
||||
hashingTask->start();
|
||||
}
|
||||
|
||||
|
@ -246,6 +229,7 @@ void FlamePackExportTask::makeApiRequest()
|
|||
getProjectsInfo();
|
||||
});
|
||||
connect(task.get(), &Task::failed, this, &FlamePackExportTask::getProjectsInfo);
|
||||
connect(task.get(), &Task::aborted, this, &FlamePackExportTask::emitAborted);
|
||||
task->start();
|
||||
}
|
||||
|
||||
|
@ -324,6 +308,7 @@ void FlamePackExportTask::getProjectsInfo()
|
|||
buildZip();
|
||||
});
|
||||
connect(projTask.get(), &Task::failed, this, &FlamePackExportTask::emitFailed);
|
||||
connect(projTask.get(), &Task::aborted, this, &FlamePackExportTask::emitAborted);
|
||||
task.reset(projTask);
|
||||
task->start();
|
||||
}
|
||||
|
@ -333,13 +318,13 @@ void FlamePackExportTask::buildZip()
|
|||
setStatus(tr("Adding files..."));
|
||||
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("modlist.html", generateHTML());
|
||||
|
||||
QStringList 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);
|
||||
|
||||
auto progressStep = std::make_shared<TaskStepProgress>();
|
||||
|
@ -374,13 +359,14 @@ QByteArray FlamePackExportTask::generateIndex()
|
|||
QJsonObject obj;
|
||||
obj["manifestType"] = "minecraftModpack";
|
||||
obj["manifestVersion"] = 1;
|
||||
obj["name"] = name;
|
||||
obj["version"] = version;
|
||||
obj["author"] = author;
|
||||
obj["name"] = m_options.name;
|
||||
obj["version"] = m_options.version;
|
||||
obj["author"] = m_options.author;
|
||||
obj["overrides"] = "overrides";
|
||||
if (mcInstance) {
|
||||
|
||||
QJsonObject version;
|
||||
auto profile = mcInstance->getPackProfile();
|
||||
|
||||
auto profile = m_options.instance->getPackProfile();
|
||||
// collect all supported components
|
||||
const ComponentPtr minecraft = profile->getComponent("net.minecraft");
|
||||
const ComponentPtr quilt = profile->getComponent("org.quiltmc.quilt-loader");
|
||||
|
@ -411,15 +397,18 @@ QByteArray FlamePackExportTask::generateIndex()
|
|||
loader["primary"] = true;
|
||||
version["modLoaders"] = QJsonArray({ loader });
|
||||
}
|
||||
|
||||
if (m_options.recommendedRAM > 0)
|
||||
version["recommendedRam"] = m_options.recommendedRAM;
|
||||
|
||||
obj["minecraft"] = version;
|
||||
}
|
||||
|
||||
QJsonArray files;
|
||||
for (auto mod : resolvedFiles) {
|
||||
QJsonObject file;
|
||||
file["projectID"] = mod.addonId;
|
||||
file["fileID"] = mod.version;
|
||||
file["required"] = mod.enabled || !optionalFiles;
|
||||
file["required"] = mod.enabled || !m_options.optionalFiles;
|
||||
files << file;
|
||||
}
|
||||
obj["files"] = files;
|
||||
|
|
|
@ -19,22 +19,26 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "BaseInstance.h"
|
||||
#include "MMCZip.h"
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "modplatform/flame/FlameAPI.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 {
|
||||
Q_OBJECT
|
||||
public:
|
||||
FlamePackExportTask(const QString& name,
|
||||
const QString& version,
|
||||
const QString& author,
|
||||
bool optionalFiles,
|
||||
InstancePtr instance,
|
||||
const QString& output,
|
||||
MMCZip::FilterFunction filter);
|
||||
FlamePackExportTask(FlamePackExportOptions&& options);
|
||||
|
||||
protected:
|
||||
void executeTask() override;
|
||||
|
@ -45,13 +49,6 @@ class FlamePackExportTask : public Task {
|
|||
static const QStringList FILE_EXTENSIONS;
|
||||
|
||||
// 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 {
|
||||
int addonId;
|
||||
|
@ -70,6 +67,9 @@ class FlamePackExportTask : public Task {
|
|||
bool isMod;
|
||||
};
|
||||
|
||||
FlamePackExportOptions m_options;
|
||||
QDir m_gameRoot;
|
||||
|
||||
FlameAPI api;
|
||||
|
||||
QFileInfoList files;
|
||||
|
|
|
@ -27,6 +27,7 @@ static void loadMinecraftV1(Flame::Minecraft& m, QJsonObject& minecraft)
|
|||
loadModloaderV1(loader, obj);
|
||||
m.modLoaders.append(loader);
|
||||
}
|
||||
m.recommendedRAM = Json::ensureInteger(minecraft, "recommendedRam", 0);
|
||||
}
|
||||
|
||||
static void loadManifestV1(Flame::Manifest& pack, QJsonObject& manifest)
|
||||
|
|
|
@ -67,6 +67,7 @@ struct Minecraft {
|
|||
QString version;
|
||||
QString libraries;
|
||||
QList<Flame::Modloader> modLoaders;
|
||||
int recommendedRAM;
|
||||
};
|
||||
|
||||
struct Manifest {
|
||||
|
|
|
@ -40,7 +40,7 @@ ModrinthPackExportTask::ModrinthPackExportTask(const QString& name,
|
|||
bool optionalFiles,
|
||||
InstancePtr instance,
|
||||
const QString& output,
|
||||
MMCZip::FilterFunction filter)
|
||||
MMCZip::FilterFileFunction filter)
|
||||
: name(name)
|
||||
, version(version)
|
||||
, summary(summary)
|
||||
|
@ -63,7 +63,6 @@ bool ModrinthPackExportTask::abort()
|
|||
{
|
||||
if (task) {
|
||||
task->abort();
|
||||
emitAborted();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -158,6 +157,7 @@ void ModrinthPackExportTask::makeApiRequest()
|
|||
task = api.currentVersions(pendingHashes.values(), "sha512", response);
|
||||
connect(task.get(), &Task::succeeded, [this, response]() { parseApiResponse(response); });
|
||||
connect(task.get(), &Task::failed, this, &ModrinthPackExportTask::emitFailed);
|
||||
connect(task.get(), &Task::aborted, this, &ModrinthPackExportTask::emitAborted);
|
||||
task->start();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ class ModrinthPackExportTask : public Task {
|
|||
bool optionalFiles,
|
||||
InstancePtr instance,
|
||||
const QString& output,
|
||||
MMCZip::FilterFunction filter);
|
||||
MMCZip::FilterFileFunction filter);
|
||||
|
||||
protected:
|
||||
void executeTask() override;
|
||||
|
@ -58,7 +58,7 @@ class ModrinthPackExportTask : public Task {
|
|||
MinecraftInstance* mcInstance;
|
||||
const QDir gameRoot;
|
||||
const QString output;
|
||||
const MMCZip::FilterFunction filter;
|
||||
const MMCZip::FilterFileFunction filter;
|
||||
|
||||
ModrinthAPI api;
|
||||
QFileInfoList files;
|
||||
|
|
|
@ -58,6 +58,7 @@ class ByteArraySink : public Sink {
|
|||
qWarning() << "ByteArraySink did not initialize the buffer because it's not addressable";
|
||||
if (initAllValidators(request))
|
||||
return Task::State::Running;
|
||||
m_fail_reason = "Failed to initialize validators";
|
||||
return Task::State::Failed;
|
||||
};
|
||||
|
||||
|
@ -69,12 +70,14 @@ class ByteArraySink : public Sink {
|
|||
qWarning() << "ByteArraySink did not write the buffer because it's not addressable";
|
||||
if (writeAllValidators(data))
|
||||
return Task::State::Running;
|
||||
m_fail_reason = "Failed to write validators";
|
||||
return Task::State::Failed;
|
||||
}
|
||||
|
||||
auto abort() -> Task::State override
|
||||
{
|
||||
failAllValidators();
|
||||
m_fail_reason = "Aborted";
|
||||
return Task::State::Failed;
|
||||
}
|
||||
|
||||
|
@ -82,12 +85,13 @@ class ByteArraySink : public Sink {
|
|||
{
|
||||
if (finalizeAllValidators(reply))
|
||||
return Task::State::Succeeded;
|
||||
m_fail_reason = "Failed to finalize validators";
|
||||
return Task::State::Failed;
|
||||
}
|
||||
|
||||
auto hasLocalData() -> bool override { return false; }
|
||||
|
||||
private:
|
||||
protected:
|
||||
std::shared_ptr<QByteArray> m_output;
|
||||
};
|
||||
} // namespace Net
|
||||
|
|
|
@ -51,6 +51,7 @@ Task::State FileSink::init(QNetworkRequest& request)
|
|||
// create a new save file and open it for writing
|
||||
if (!FS::ensureFilePathExists(m_filename)) {
|
||||
qCCritical(taskNetLogC) << "Could not create folder for " + m_filename;
|
||||
m_fail_reason = "Could not create folder";
|
||||
return Task::State::Failed;
|
||||
}
|
||||
|
||||
|
@ -58,11 +59,13 @@ Task::State FileSink::init(QNetworkRequest& request)
|
|||
m_output_file.reset(new PSaveFile(m_filename));
|
||||
if (!m_output_file->open(QIODevice::WriteOnly)) {
|
||||
qCCritical(taskNetLogC) << "Could not open " + m_filename + " for writing";
|
||||
m_fail_reason = "Could not open file";
|
||||
return Task::State::Failed;
|
||||
}
|
||||
|
||||
if (initAllValidators(request))
|
||||
return Task::State::Running;
|
||||
m_fail_reason = "Failed to initialize validators";
|
||||
return Task::State::Failed;
|
||||
}
|
||||
|
||||
|
@ -73,6 +76,7 @@ Task::State FileSink::write(QByteArray& data)
|
|||
m_output_file->cancelWriting();
|
||||
m_output_file.reset();
|
||||
m_wroteAnyData = false;
|
||||
m_fail_reason = "Failed to write validators";
|
||||
return Task::State::Failed;
|
||||
}
|
||||
|
||||
|
@ -105,13 +109,16 @@ Task::State FileSink::finalize(QNetworkReply& reply)
|
|||
if (gotFile || m_wroteAnyData) {
|
||||
// ask validators for data consistency
|
||||
// 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;
|
||||
}
|
||||
|
||||
// nothing went wrong...
|
||||
if (!m_output_file->commit()) {
|
||||
qCCritical(taskNetLogC) << "Failed to commit changes to " << m_filename;
|
||||
m_output_file->cancelWriting();
|
||||
m_fail_reason = "Failed to commit changes";
|
||||
return Task::State::Failed;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -166,7 +166,7 @@ auto HttpMetaCache::evictEntry(MetaEntryPtr entry) -> bool
|
|||
return true;
|
||||
}
|
||||
|
||||
//returns true on success, false otherwise
|
||||
// returns true on success, false otherwise
|
||||
auto HttpMetaCache::evictAll() -> bool
|
||||
{
|
||||
bool ret = true;
|
||||
|
@ -178,7 +178,7 @@ auto HttpMetaCache::evictAll() -> bool
|
|||
qCWarning(taskHttpMetaCacheLogC) << "Unexpected missing cache entry" << entry->m_basePath;
|
||||
}
|
||||
map.entry_list.clear();
|
||||
//AND all return codes together so the result is true iff all runs of deletePath() are true
|
||||
// AND all return codes together so the result is true iff all runs of deletePath() are true
|
||||
ret &= FS::deletePath(map.base_path);
|
||||
}
|
||||
return ret;
|
||||
|
|
|
@ -84,7 +84,8 @@ void NetRequest::executeTask()
|
|||
break;
|
||||
case State::Inactive:
|
||||
case State::Failed:
|
||||
emit failed("Failed to initialize sink");
|
||||
m_failReason = m_sink->failReason();
|
||||
emit failed(m_sink->failReason());
|
||||
emit finished();
|
||||
return;
|
||||
case State::AbortedByUser:
|
||||
|
@ -259,6 +260,7 @@ void NetRequest::downloadFinished()
|
|||
} else if (m_state == State::Failed) {
|
||||
qCDebug(logCat) << getUid().toString() << "Request failed in previous step:" << m_url.toString();
|
||||
m_sink->abort();
|
||||
m_failReason = m_reply->errorString();
|
||||
emit failed(m_reply->errorString());
|
||||
emit finished();
|
||||
return;
|
||||
|
@ -278,7 +280,8 @@ void NetRequest::downloadFinished()
|
|||
if (m_state != State::Succeeded) {
|
||||
qCDebug(logCat) << getUid().toString() << "Request failed to write:" << m_url.toString();
|
||||
m_sink->abort();
|
||||
emit failed("failed to write in sink");
|
||||
m_failReason = m_sink->failReason();
|
||||
emit failed(m_sink->failReason());
|
||||
emit finished();
|
||||
return;
|
||||
}
|
||||
|
@ -289,7 +292,8 @@ void NetRequest::downloadFinished()
|
|||
if (m_state != State::Succeeded) {
|
||||
qCDebug(logCat) << getUid().toString() << "Request failed to finalize:" << m_url.toString();
|
||||
m_sink->abort();
|
||||
emit failed("failed to finalize the request");
|
||||
m_failReason = m_sink->failReason();
|
||||
emit failed(m_sink->failReason());
|
||||
emit finished();
|
||||
return;
|
||||
}
|
||||
|
@ -305,7 +309,7 @@ void NetRequest::downloadReadyRead()
|
|||
auto data = m_reply->readAll();
|
||||
m_state = m_sink->write(data);
|
||||
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";
|
||||
} else {
|
||||
|
|
|
@ -36,74 +36,45 @@
|
|||
*/
|
||||
|
||||
#include "PasteUpload.h"
|
||||
#include "Application.h"
|
||||
#include "BuildConfig.h"
|
||||
#include <qobject.h>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QHttpPart>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QRegularExpression>
|
||||
#include <QUrlQuery>
|
||||
#include "logs/AnonymizeLog.h"
|
||||
|
||||
#include "net/Logging.h"
|
||||
|
||||
std::array<PasteUpload::PasteTypeInfo, 4> PasteUpload::PasteTypes = { { { "0x0.st", "https://0x0.st", "" },
|
||||
const std::array<PasteUpload::PasteTypeInfo, 4> PasteUpload::PasteTypes = { { { "0x0.st", "https://0x0.st", "" },
|
||||
{ "hastebin", "https://hst.sh", "/documents" },
|
||||
{ "paste.gg", "https://paste.gg", "/api/v1/pastes" },
|
||||
{ "mclo.gs", "https://api.mclo.gs", "/1/log" } } };
|
||||
|
||||
PasteUpload::PasteUpload(QWidget* window, QString text, QString baseUrl, PasteType pasteType)
|
||||
: m_window(window), m_baseUrl(baseUrl), m_pasteType(pasteType), m_text(text.toUtf8())
|
||||
QNetworkReply* PasteUpload::getReply(QNetworkRequest& request)
|
||||
{
|
||||
if (m_baseUrl == "")
|
||||
m_baseUrl = PasteTypes.at(pasteType).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 == 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 };
|
||||
switch (m_paste_type) {
|
||||
case PasteUpload::NullPointer: {
|
||||
QHttpMultiPart* multiPart = new QHttpMultiPart{ QHttpMultiPart::FormDataType, this };
|
||||
|
||||
QHttpPart filePart;
|
||||
filePart.setBody(m_text);
|
||||
filePart.setBody(m_log.toUtf8());
|
||||
filePart.setHeader(QNetworkRequest::ContentTypeHeader, "text/plain");
|
||||
filePart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"file\"; filename=\"log.txt\"");
|
||||
multiPart->append(filePart);
|
||||
|
||||
rep = APPLICATION->network()->post(request, multiPart);
|
||||
multiPart->setParent(rep);
|
||||
|
||||
break;
|
||||
return m_network->post(request, multiPart);
|
||||
}
|
||||
case Hastebin: {
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgentUncached().toUtf8());
|
||||
rep = APPLICATION->network()->post(request, m_text);
|
||||
break;
|
||||
case PasteUpload::Hastebin: {
|
||||
return m_network->post(request, m_log.toUtf8());
|
||||
}
|
||||
case Mclogs: {
|
||||
case PasteUpload::Mclogs: {
|
||||
QUrlQuery postData;
|
||||
postData.addQueryItem("content", m_text);
|
||||
postData.addQueryItem("content", m_log);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
rep = APPLICATION->network()->post(request, postData.toString().toUtf8());
|
||||
break;
|
||||
return m_network->post(request, postData.toString().toUtf8());
|
||||
}
|
||||
case PasteGG: {
|
||||
case PasteUpload::PasteGG: {
|
||||
QJsonObject obj;
|
||||
QJsonDocument doc;
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
@ -114,7 +85,7 @@ void PasteUpload::executeTask()
|
|||
QJsonObject logFileInfo;
|
||||
QJsonObject logFileContentInfo;
|
||||
logFileContentInfo.insert("format", "text");
|
||||
logFileContentInfo.insert("value", QString::fromUtf8(m_text));
|
||||
logFileContentInfo.insert("value", m_log);
|
||||
logFileInfo.insert("name", "log.txt");
|
||||
logFileInfo.insert("content", logFileContentInfo);
|
||||
files.append(logFileInfo);
|
||||
|
@ -122,108 +93,127 @@ void PasteUpload::executeTask()
|
|||
obj.insert("files", files);
|
||||
|
||||
doc.setObject(obj);
|
||||
rep = APPLICATION->network()->post(request, doc.toJson());
|
||||
break;
|
||||
return m_network->post(request, doc.toJson());
|
||||
}
|
||||
}
|
||||
|
||||
connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress);
|
||||
connect(rep, &QNetworkReply::finished, this, &PasteUpload::downloadFinished);
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
connect(rep, &QNetworkReply::errorOccurred, this, &PasteUpload::downloadError);
|
||||
|
||||
m_reply = std::shared_ptr<QNetworkReply>(rep);
|
||||
|
||||
setStatus(tr("Uploading to %1").arg(m_uploadUrl));
|
||||
}
|
||||
|
||||
void PasteUpload::downloadError(QNetworkReply::NetworkError error)
|
||||
auto PasteUpload::Sink::finalize(QNetworkReply& reply) -> Task::State
|
||||
{
|
||||
// error happened during download.
|
||||
qCCritical(taskUploadLogC) << getUid().toString() << "Network error: " << error;
|
||||
emitFailed(m_reply->errorString());
|
||||
}
|
||||
if (!finalizeAllValidators(reply)) {
|
||||
m_fail_reason = "Failed to finalize validators";
|
||||
return Task::State::Failed;
|
||||
}
|
||||
int statusCode = reply.attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
void PasteUpload::downloadFinished()
|
||||
{
|
||||
QByteArray data = m_reply->readAll();
|
||||
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;
|
||||
if (reply.error() != QNetworkReply::NetworkError::NoError) {
|
||||
m_fail_reason = QObject::tr("Network error: %1").arg(reply.errorString());
|
||||
return Task::State::Failed;
|
||||
} else if (statusCode != 200 && statusCode != 201) {
|
||||
QString reasonPhrase = m_reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
|
||||
emitFailed(tr("Error: %1 returned unexpected status code %2 %3").arg(m_uploadUrl).arg(statusCode).arg(reasonPhrase));
|
||||
qCCritical(taskUploadLogC) << getUid().toString() << m_uploadUrl << " returned unexpected status code " << statusCode
|
||||
<< " with body: " << data;
|
||||
m_reply.reset();
|
||||
return;
|
||||
QString reasonPhrase = reply.attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
|
||||
m_fail_reason =
|
||||
QObject::tr("Error: %1 returned unexpected status code %2 %3").arg(m_d->url().toString()).arg(statusCode).arg(reasonPhrase);
|
||||
return Task::State::Failed;
|
||||
}
|
||||
|
||||
switch (m_pasteType) {
|
||||
case NullPointer:
|
||||
m_pasteLink = QString::fromUtf8(data).trimmed();
|
||||
switch (m_d->m_paste_type) {
|
||||
case PasteUpload::NullPointer:
|
||||
m_d->m_pasteLink = QString::fromUtf8(*m_output).trimmed();
|
||||
break;
|
||||
case Hastebin: {
|
||||
QJsonDocument jsonDoc{ QJsonDocument::fromJson(data) };
|
||||
QJsonObject jsonObj{ jsonDoc.object() };
|
||||
if (jsonObj.contains("key") && jsonObj["key"].isString()) {
|
||||
QString key = jsonDoc.object()["key"].toString();
|
||||
m_pasteLink = m_baseUrl + "/" + key;
|
||||
case PasteUpload::Hastebin: {
|
||||
QJsonParseError jsonError;
|
||||
auto doc = QJsonDocument::fromJson(*m_output, &jsonError);
|
||||
if (jsonError.error != QJsonParseError::NoError) {
|
||||
qDebug() << "hastebin server did not reply with JSON" << jsonError.errorString();
|
||||
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 {
|
||||
emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl));
|
||||
qCCritical(taskUploadLogC) << getUid().toString() << getUid().toString() << m_uploadUrl
|
||||
<< " returned malformed response body: " << data;
|
||||
return;
|
||||
qDebug() << "Log upload failed:" << doc.toJson();
|
||||
m_fail_reason = QObject::tr("Error: %1 returned a malformed response body").arg(m_d->url().toString());
|
||||
return Task::State::Failed;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Mclogs: {
|
||||
QJsonDocument jsonDoc{ QJsonDocument::fromJson(data) };
|
||||
QJsonObject jsonObj{ jsonDoc.object() };
|
||||
if (jsonObj.contains("success") && jsonObj["success"].isBool()) {
|
||||
bool success = jsonObj["success"].toBool();
|
||||
case PasteUpload::Mclogs: {
|
||||
QJsonParseError jsonError;
|
||||
auto doc = QJsonDocument::fromJson(*m_output, &jsonError);
|
||||
if (jsonError.error != QJsonParseError::NoError) {
|
||||
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) {
|
||||
m_pasteLink = jsonObj["url"].toString();
|
||||
m_d->m_pasteLink = obj["url"].toString();
|
||||
} else {
|
||||
QString error = jsonObj["error"].toString();
|
||||
emitFailed(tr("Error: %1 returned an error: %2").arg(m_uploadUrl, error));
|
||||
qCCritical(taskUploadLogC) << getUid().toString() << m_uploadUrl << " returned error: " << error;
|
||||
qCCritical(taskUploadLogC) << getUid().toString() << "Response body: " << data;
|
||||
return;
|
||||
QString error = obj["error"].toString();
|
||||
m_fail_reason = QObject::tr("Error: %1 returned an error: %2").arg(m_d->url().toString(), error);
|
||||
return Task::State::Failed;
|
||||
}
|
||||
} else {
|
||||
emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl));
|
||||
qCCritical(taskUploadLogC) << getUid().toString() << m_uploadUrl << " returned malformed response body: " << data;
|
||||
return;
|
||||
qDebug() << "Log upload failed:" << doc.toJson();
|
||||
m_fail_reason = QObject::tr("Error: %1 returned a malformed response body").arg(m_d->url().toString());
|
||||
return Task::State::Failed;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PasteGG:
|
||||
QJsonDocument jsonDoc{ QJsonDocument::fromJson(data) };
|
||||
QJsonObject jsonObj{ jsonDoc.object() };
|
||||
if (jsonObj.contains("status") && jsonObj["status"].isString()) {
|
||||
QString status = jsonObj["status"].toString();
|
||||
case PasteUpload::PasteGG:
|
||||
QJsonParseError jsonError;
|
||||
auto doc = QJsonDocument::fromJson(*m_output, &jsonError);
|
||||
if (jsonError.error != QJsonParseError::NoError) {
|
||||
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") {
|
||||
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 {
|
||||
QString error = jsonObj["error"].toString();
|
||||
QString message =
|
||||
(jsonObj.contains("message") && jsonObj["message"].isString()) ? jsonObj["message"].toString() : "none";
|
||||
emitFailed(tr("Error: %1 returned an error code: %2\nError message: %3").arg(m_uploadUrl, error, message));
|
||||
qCCritical(taskUploadLogC) << getUid().toString() << m_uploadUrl << " returned error: " << error;
|
||||
qCCritical(taskUploadLogC) << getUid().toString() << "Error message: " << message;
|
||||
qCCritical(taskUploadLogC) << getUid().toString() << "Response body: " << data;
|
||||
return;
|
||||
QString error = obj["error"].toString();
|
||||
QString message = (obj.contains("message") && obj["message"].isString()) ? obj["message"].toString() : "none";
|
||||
m_fail_reason =
|
||||
QObject::tr("Error: %1 returned an error code: %2\nError message: %3").arg(m_d->url().toString(), error, message);
|
||||
return Task::State::Failed;
|
||||
}
|
||||
} else {
|
||||
emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl));
|
||||
qCCritical(taskUploadLogC) << getUid().toString() << m_uploadUrl << " returned malformed response body: " << data;
|
||||
return;
|
||||
qDebug() << "Log upload failed:" << doc.toJson();
|
||||
m_fail_reason = QObject::tr("Error: %1 returned a malformed response body").arg(m_d->url().toString());
|
||||
return Task::State::Failed;
|
||||
}
|
||||
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
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QNetworkReply>
|
||||
#include <QString>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include "net/ByteArraySink.h"
|
||||
#include "net/NetRequest.h"
|
||||
#include "tasks/Task.h"
|
||||
|
||||
class PasteUpload : public Task {
|
||||
Q_OBJECT
|
||||
#include <QNetworkReply>
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
class PasteUpload : public Net::NetRequest {
|
||||
public:
|
||||
enum PasteType : int {
|
||||
// 0x0.st
|
||||
|
@ -58,32 +61,36 @@ class PasteUpload : public Task {
|
|||
First = NullPointer,
|
||||
Last = Mclogs
|
||||
};
|
||||
|
||||
struct PasteTypeInfo {
|
||||
const QString name;
|
||||
const QString defaultBase;
|
||||
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);
|
||||
virtual ~PasteUpload();
|
||||
class Sink : public Net::ByteArraySink {
|
||||
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; }
|
||||
|
||||
protected:
|
||||
virtual void executeTask();
|
||||
|
||||
private:
|
||||
QWidget* m_window;
|
||||
virtual QNetworkReply* getReply(QNetworkRequest&) override;
|
||||
QString m_log;
|
||||
QString m_pasteLink;
|
||||
QString m_baseUrl;
|
||||
QString m_uploadUrl;
|
||||
PasteType m_pasteType;
|
||||
QByteArray m_text;
|
||||
std::shared_ptr<QNetworkReply> m_reply;
|
||||
public slots:
|
||||
void downloadError(QNetworkReply::NetworkError);
|
||||
void downloadFinished();
|
||||
const PasteType m_paste_type;
|
||||
};
|
||||
|
|
|
@ -52,6 +52,8 @@ class Sink {
|
|||
|
||||
virtual auto hasLocalData() -> bool = 0;
|
||||
|
||||
QString failReason() const { return m_fail_reason; }
|
||||
|
||||
void addValidator(Validator* validator)
|
||||
{
|
||||
if (validator) {
|
||||
|
@ -95,5 +97,6 @@ class Sink {
|
|||
|
||||
protected:
|
||||
std::vector<std::shared_ptr<Validator>> validators;
|
||||
QString m_fail_reason;
|
||||
};
|
||||
} // namespace Net
|
||||
|
|
|
@ -86,6 +86,7 @@ auto ImgurAlbumCreation::Sink::write(QByteArray& data) -> Task::State
|
|||
auto ImgurAlbumCreation::Sink::abort() -> Task::State
|
||||
{
|
||||
m_output.clear();
|
||||
m_fail_reason = "Aborted";
|
||||
return Task::State::Failed;
|
||||
}
|
||||
|
||||
|
@ -95,11 +96,13 @@ auto ImgurAlbumCreation::Sink::finalize(QNetworkReply&) -> Task::State
|
|||
QJsonDocument doc = QJsonDocument::fromJson(m_output, &jsonError);
|
||||
if (jsonError.error != QJsonParseError::NoError) {
|
||||
qDebug() << jsonError.errorString();
|
||||
m_fail_reason = "Invalid json reply";
|
||||
return Task::State::Failed;
|
||||
}
|
||||
auto object = doc.object();
|
||||
if (!object.value("success").toBool()) {
|
||||
qDebug() << doc.toJson();
|
||||
m_fail_reason = "Failed to create album";
|
||||
return Task::State::Failed;
|
||||
}
|
||||
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
|
||||
{
|
||||
m_output.clear();
|
||||
m_fail_reason = "Aborted";
|
||||
return Task::State::Failed;
|
||||
}
|
||||
|
||||
|
@ -99,11 +100,13 @@ auto ImgurUpload::Sink::finalize(QNetworkReply&) -> Task::State
|
|||
QJsonDocument doc = QJsonDocument::fromJson(m_output, &jsonError);
|
||||
if (jsonError.error != QJsonParseError::NoError) {
|
||||
qDebug() << "imgur server did not reply with JSON" << jsonError.errorString();
|
||||
m_fail_reason = "Invalid json reply";
|
||||
return Task::State::Failed;
|
||||
}
|
||||
auto object = doc.object();
|
||||
if (!object.value("success").toBool()) {
|
||||
qDebug() << "Screenshot upload not successful:" << doc.toJson();
|
||||
m_fail_reason = "Screenshot was not uploaded successfully";
|
||||
return Task::State::Failed;
|
||||
}
|
||||
m_shot->m_imgurId = object.value("data").toObject().value("id").toString();
|
||||
|
|
|
@ -118,10 +118,29 @@ void ConcurrentTask::executeNextSubTask()
|
|||
}
|
||||
if (m_queue.isEmpty()) {
|
||||
if (m_doing.isEmpty()) {
|
||||
if (m_failed.isEmpty())
|
||||
if (m_failed.isEmpty()) {
|
||||
emitSucceeded();
|
||||
else
|
||||
emitFailed(tr("One or more subtasks failed"));
|
||||
} else if (m_failed.count() == 1) {
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -196,6 +196,8 @@ void Task::logWarning(const QString& line)
|
|||
{
|
||||
qWarning() << line;
|
||||
m_Warnings.append(line);
|
||||
|
||||
emit warningLogged(line);
|
||||
}
|
||||
|
||||
QStringList Task::warnings() const
|
||||
|
|
|
@ -79,7 +79,6 @@ Q_DECLARE_METATYPE(TaskStepProgress)
|
|||
|
||||
using TaskStepProgressList = QList<std::shared_ptr<TaskStepProgress>>;
|
||||
|
||||
|
||||
/*!
|
||||
* Represents a task that has to be done.
|
||||
* 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 status(QString status);
|
||||
void details(QString details);
|
||||
void warningLogged(const QString& warning);
|
||||
void stepProgress(TaskStepProgress const& task_progress);
|
||||
|
||||
//! Emitted when the canAbort() status has changed. */
|
||||
|
|
|
@ -38,10 +38,15 @@
|
|||
#include "GuiUtil.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QBuffer>
|
||||
#include <QClipboard>
|
||||
#include <QFileDialog>
|
||||
#include <QStandardPaths>
|
||||
|
||||
#include "FileSystem.h"
|
||||
#include "logs/AnonymizeLog.h"
|
||||
#include "net/NetJob.h"
|
||||
#include "net/NetRequest.h"
|
||||
#include "net/PasteUpload.h"
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
#include "ui/dialogs/ProgressDialog.h"
|
||||
|
@ -74,33 +79,34 @@ QString truncateLogForMclogs(const QString& 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)
|
||||
{
|
||||
ProgressDialog dialog(parentWidget);
|
||||
auto pasteTypeSetting = static_cast<PasteUpload::PasteType>(APPLICATION->settings()->get("PastebinType").toInt());
|
||||
auto pasteCustomAPIBaseSetting = APPLICATION->settings()->get("PastebinCustomAPIBase").toString();
|
||||
auto pasteType = static_cast<PasteUpload::PasteType>(APPLICATION->settings()->get("PastebinType").toInt());
|
||||
auto baseURL = APPLICATION->settings()->get("PastebinCustomAPIBase").toString();
|
||||
bool shouldTruncate = false;
|
||||
|
||||
{
|
||||
QUrl baseUrl;
|
||||
if (pasteCustomAPIBaseSetting.isEmpty())
|
||||
baseUrl = PasteUpload::PasteTypes[pasteTypeSetting].defaultBase;
|
||||
else
|
||||
baseUrl = pasteCustomAPIBaseSetting;
|
||||
if (baseURL.isEmpty())
|
||||
baseURL = PasteUpload::PasteTypes[pasteType].defaultBase;
|
||||
|
||||
if (baseUrl.isValid()) {
|
||||
if (auto url = QUrl(baseURL); url.isValid()) {
|
||||
auto response = CustomMessageBox::selectable(parentWidget, QObject::tr("Confirm Upload"),
|
||||
QObject::tr("You are about to upload \"%1\" to %2.\n"
|
||||
"You should double-check for personal information.\n\n"
|
||||
"Are you sure?")
|
||||
.arg(name, baseUrl.host()),
|
||||
.arg(name, url.host()),
|
||||
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
|
||||
->exec();
|
||||
|
||||
if (response != QMessageBox::Yes)
|
||||
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(
|
||||
parentWidget, QObject::tr("Confirm Truncation"),
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString textToUpload = text;
|
||||
if (shouldTruncate) {
|
||||
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());
|
||||
if (!paste->wasSuccessful()) {
|
||||
CustomMessageBox::selectable(parentWidget, QObject::tr("Upload failed"), paste->failReason(), QMessageBox::Critical)->exec();
|
||||
return QString();
|
||||
} else {
|
||||
const QString link = paste->pasteLink();
|
||||
setClipboardText(link);
|
||||
auto pasteJob = new PasteUpload(textToUpload, baseURL, pasteType);
|
||||
job->addNetAction(Net::NetRequest::Ptr(pasteJob));
|
||||
QObject::connect(job.get(), &Task::failed, [parentWidget](QString reason) {
|
||||
CustomMessageBox::selectable(parentWidget, QObject::tr("Failed to upload logs!"), reason, QMessageBox::Critical)->show();
|
||||
});
|
||||
QObject::connect(job.get(), &Task::aborted, [parentWidget] {
|
||||
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(
|
||||
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)
|
||||
->exec();
|
||||
return link;
|
||||
return pasteJob->pasteLink();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void GuiUtil::setClipboardText(const QString& text)
|
||||
void GuiUtil::setClipboardText(QString text)
|
||||
{
|
||||
anonymizeLog(text);
|
||||
QApplication::clipboard()->setText(text);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QWidget>
|
||||
#include <optional>
|
||||
|
||||
namespace GuiUtil {
|
||||
std::optional<QString> uploadPaste(const QString& name, const QString& text, QWidget* parentWidget);
|
||||
void setClipboardText(const QString& text);
|
||||
std::optional<QString> uploadPaste(const QString& name, const QFileInfo& filePath, QWidget* parentWidget);
|
||||
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);
|
||||
QString BrowseForFile(QString context, QString caption, QString filter, QString defaultPath, QWidget* parentWidget);
|
||||
} // namespace GuiUtil
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
#include <QToolButton>
|
||||
#include <QWidget>
|
||||
#include <QWidgetAction>
|
||||
#include <memory>
|
||||
|
||||
#include <BaseInstance.h>
|
||||
#include <BuildConfig.h>
|
||||
|
@ -90,8 +91,10 @@
|
|||
#include <updater/ExternalUpdater.h>
|
||||
#include "InstanceWindow.h"
|
||||
|
||||
#include "ui/GuiUtil.h"
|
||||
#include "ui/dialogs/AboutDialog.h"
|
||||
#include "ui/dialogs/CopyInstanceDialog.h"
|
||||
#include "ui/dialogs/CreateShortcutDialog.h"
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
#include "ui/dialogs/ExportInstanceDialog.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);
|
||||
}
|
||||
|
||||
{ // 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
|
||||
ui->viewMenu->addAction(ui->instanceToolBar->toggleViewAction());
|
||||
ui->viewMenu->addAction(ui->newsToolBar->toggleViewAction());
|
||||
|
@ -1311,7 +1324,7 @@ void MainWindow::on_actionReportBug_triggered()
|
|||
|
||||
void MainWindow::on_actionClearMetadata_triggered()
|
||||
{
|
||||
//This if contains side effects!
|
||||
// This if contains side effects!
|
||||
if (!APPLICATION->metacache()->evictAll()) {
|
||||
CustomMessageBox::selectable(this, tr("Error"),
|
||||
tr("Metadata cache clear Failed!\nTo clear the metadata cache manually, press Folders -> View "
|
||||
|
@ -1383,6 +1396,14 @@ void MainWindow::on_actionDeleteInstance_triggered()
|
|||
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 response = CustomMessageBox::selectable(this, tr("Confirm Deletion"),
|
||||
|
@ -1419,15 +1440,18 @@ void MainWindow::on_actionExportInstanceZip_triggered()
|
|||
void MainWindow::on_actionExportInstanceMrPack_triggered()
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::on_actionExportInstanceFlamePack_triggered()
|
||||
{
|
||||
if (m_selectedInstance) {
|
||||
auto instance = dynamic_cast<MinecraftInstance*>(m_selectedInstance.get());
|
||||
auto instance = std::dynamic_pointer_cast<MinecraftInstance>(m_selectedInstance);
|
||||
if (instance) {
|
||||
if (auto cmp = instance->getPackProfile()->getComponent("net.minecraft");
|
||||
cmp && cmp->getVersionFile() && cmp->getVersionFile()->type == "snapshot") {
|
||||
|
@ -1436,7 +1460,7 @@ void MainWindow::on_actionExportInstanceFlamePack_triggered()
|
|||
msgBox.exec();
|
||||
return;
|
||||
}
|
||||
ExportPackDialog dlg(m_selectedInstance, this, ModPlatform::ResourceProvider::FLAME);
|
||||
ExportPackDialog dlg(instance, this, ModPlatform::ResourceProvider::FLAME);
|
||||
dlg.exec();
|
||||
}
|
||||
}
|
||||
|
@ -1510,139 +1534,11 @@ void MainWindow::on_actionCreateInstanceShortcut_triggered()
|
|||
{
|
||||
if (!m_selectedInstance)
|
||||
return;
|
||||
auto desktopPath = FS::getDesktopDir();
|
||||
if (desktopPath.isEmpty()) {
|
||||
// TODO come up with an alternative solution (open "save file" dialog)
|
||||
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Couldn't find desktop?!"));
|
||||
|
||||
CreateShortcutDialog shortcutDlg(m_selectedInstance, this);
|
||||
if (!shortcutDlg.exec())
|
||||
return;
|
||||
}
|
||||
|
||||
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!"));
|
||||
}
|
||||
shortcutDlg.createShortcut();
|
||||
}
|
||||
|
||||
void MainWindow::taskEnd()
|
||||
|
|
|
@ -131,7 +131,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>800</width>
|
||||
<height>27</height>
|
||||
<height>22</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="fileMenu">
|
||||
|
@ -215,6 +215,7 @@
|
|||
</property>
|
||||
<addaction name="actionClearMetadata"/>
|
||||
<addaction name="actionReportBug"/>
|
||||
<addaction name="actionUploadLog"/>
|
||||
<addaction name="actionAddToPATH"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionMATRIX"/>
|
||||
|
@ -235,8 +236,7 @@
|
|||
</widget>
|
||||
<action name="actionMoreNews">
|
||||
<property name="icon">
|
||||
<iconset theme="news">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="news"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>More news...</string>
|
||||
|
@ -250,8 +250,7 @@
|
|||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="cat">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="cat"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Meow</string>
|
||||
|
@ -286,8 +285,7 @@
|
|||
</action>
|
||||
<action name="actionAddInstance">
|
||||
<property name="icon">
|
||||
<iconset theme="new">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="new"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Add Instanc&e...</string>
|
||||
|
@ -298,8 +296,7 @@
|
|||
</action>
|
||||
<action name="actionCheckUpdate">
|
||||
<property name="icon">
|
||||
<iconset theme="checkupdate">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="checkupdate"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Update...</string>
|
||||
|
@ -313,8 +310,7 @@
|
|||
</action>
|
||||
<action name="actionSettings">
|
||||
<property name="icon">
|
||||
<iconset theme="settings">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="settings"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Setti&ngs...</string>
|
||||
|
@ -328,8 +324,7 @@
|
|||
</action>
|
||||
<action name="actionManageAccounts">
|
||||
<property name="icon">
|
||||
<iconset theme="accounts">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="accounts"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Manage Accounts...</string>
|
||||
|
@ -337,8 +332,7 @@
|
|||
</action>
|
||||
<action name="actionLaunchInstance">
|
||||
<property name="icon">
|
||||
<iconset theme="launch">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="launch"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Launch</string>
|
||||
|
@ -349,8 +343,7 @@
|
|||
</action>
|
||||
<action name="actionKillInstance">
|
||||
<property name="icon">
|
||||
<iconset theme="status-bad">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="status-bad"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Kill</string>
|
||||
|
@ -364,8 +357,7 @@
|
|||
</action>
|
||||
<action name="actionRenameInstance">
|
||||
<property name="icon">
|
||||
<iconset theme="rename">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="rename"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Rename</string>
|
||||
|
@ -376,8 +368,7 @@
|
|||
</action>
|
||||
<action name="actionChangeInstGroup">
|
||||
<property name="icon">
|
||||
<iconset theme="tag">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="tag"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Change Group...</string>
|
||||
|
@ -399,8 +390,7 @@
|
|||
</action>
|
||||
<action name="actionEditInstance">
|
||||
<property name="icon">
|
||||
<iconset theme="settings">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="settings"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Edit...</string>
|
||||
|
@ -414,8 +404,7 @@
|
|||
</action>
|
||||
<action name="actionViewSelectedInstFolder">
|
||||
<property name="icon">
|
||||
<iconset theme="viewfolder">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="viewfolder"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Folder</string>
|
||||
|
@ -426,8 +415,7 @@
|
|||
</action>
|
||||
<action name="actionDeleteInstance">
|
||||
<property name="icon">
|
||||
<iconset theme="delete">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="delete"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Dele&te</string>
|
||||
|
@ -441,8 +429,7 @@
|
|||
</action>
|
||||
<action name="actionCopyInstance">
|
||||
<property name="icon">
|
||||
<iconset theme="copy">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="copy"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Cop&y...</string>
|
||||
|
@ -456,8 +443,7 @@
|
|||
</action>
|
||||
<action name="actionExportInstance">
|
||||
<property name="icon">
|
||||
<iconset theme="export">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="export"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>E&xport...</string>
|
||||
|
@ -468,8 +454,7 @@
|
|||
</action>
|
||||
<action name="actionExportInstanceZip">
|
||||
<property name="icon">
|
||||
<iconset theme="launcher">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="launcher"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Prism Launcher (zip)</string>
|
||||
|
@ -477,8 +462,7 @@
|
|||
</action>
|
||||
<action name="actionExportInstanceMrPack">
|
||||
<property name="icon">
|
||||
<iconset theme="modrinth">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="modrinth"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Modrinth (mrpack)</string>
|
||||
|
@ -486,8 +470,7 @@
|
|||
</action>
|
||||
<action name="actionExportInstanceFlamePack">
|
||||
<property name="icon">
|
||||
<iconset theme="flame">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="flame"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>CurseForge (zip)</string>
|
||||
|
@ -495,20 +478,18 @@
|
|||
</action>
|
||||
<action name="actionCreateInstanceShortcut">
|
||||
<property name="icon">
|
||||
<iconset theme="shortcut">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="shortcut"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Create Shortcut</string>
|
||||
</property>
|
||||
<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>
|
||||
</action>
|
||||
<action name="actionNoAccountsAdded">
|
||||
<property name="icon">
|
||||
<iconset theme="noaccount">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="noaccount"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>No accounts added!</string>
|
||||
|
@ -519,8 +500,7 @@
|
|||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="noaccount">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="noaccount"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>No Default Account</string>
|
||||
|
@ -531,8 +511,7 @@
|
|||
</action>
|
||||
<action name="actionCloseWindow">
|
||||
<property name="icon">
|
||||
<iconset theme="status-bad">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="status-bad"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Close &Window</string>
|
||||
|
@ -546,8 +525,7 @@
|
|||
</action>
|
||||
<action name="actionViewInstanceFolder">
|
||||
<property name="icon">
|
||||
<iconset theme="viewfolder">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="viewfolder"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Instances</string>
|
||||
|
@ -558,8 +536,7 @@
|
|||
</action>
|
||||
<action name="actionViewLauncherRootFolder">
|
||||
<property name="icon">
|
||||
<iconset theme="viewfolder">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="viewfolder"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Launcher &Root</string>
|
||||
|
@ -570,8 +547,7 @@
|
|||
</action>
|
||||
<action name="actionViewCentralModsFolder">
|
||||
<property name="icon">
|
||||
<iconset theme="centralmods">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="centralmods"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Central Mods</string>
|
||||
|
@ -582,8 +558,7 @@
|
|||
</action>
|
||||
<action name="actionViewSkinsFolder">
|
||||
<property name="icon">
|
||||
<iconset theme="viewfolder">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="viewfolder"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Skins</string>
|
||||
|
@ -594,8 +569,7 @@
|
|||
</action>
|
||||
<action name="actionViewIconsFolder">
|
||||
<property name="icon">
|
||||
<iconset theme="viewfolder">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="viewfolder"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Instance Icons</string>
|
||||
|
@ -606,8 +580,7 @@
|
|||
</action>
|
||||
<action name="actionViewLogsFolder">
|
||||
<property name="icon">
|
||||
<iconset theme="viewfolder">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="viewfolder"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Logs</string>
|
||||
|
@ -623,8 +596,7 @@
|
|||
</action>
|
||||
<action name="actionReportBug">
|
||||
<property name="icon">
|
||||
<iconset theme="bug">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="bug"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Report a Bug or Suggest a Feature</string>
|
||||
|
@ -635,8 +607,7 @@
|
|||
</action>
|
||||
<action name="actionDISCORD">
|
||||
<property name="icon">
|
||||
<iconset theme="discord">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="discord"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Discord Guild</string>
|
||||
|
@ -647,8 +618,7 @@
|
|||
</action>
|
||||
<action name="actionMATRIX">
|
||||
<property name="icon">
|
||||
<iconset theme="matrix">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="matrix"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Matrix Space</string>
|
||||
|
@ -659,8 +629,7 @@
|
|||
</action>
|
||||
<action name="actionREDDIT">
|
||||
<property name="icon">
|
||||
<iconset theme="reddit-alien">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="reddit-alien"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Sub&reddit</string>
|
||||
|
@ -671,8 +640,7 @@
|
|||
</action>
|
||||
<action name="actionAbout">
|
||||
<property name="icon">
|
||||
<iconset theme="about">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="about"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&About %1</string>
|
||||
|
@ -686,8 +654,7 @@
|
|||
</action>
|
||||
<action name="actionClearMetadata">
|
||||
<property name="icon">
|
||||
<iconset theme="refresh">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="refresh"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Clear Metadata Cache</string>
|
||||
|
@ -696,10 +663,21 @@
|
|||
<string>Clear cached metadata</string>
|
||||
</property>
|
||||
</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">
|
||||
<property name="icon">
|
||||
<iconset theme="custom-commands">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="custom-commands"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Install to &PATH</string>
|
||||
|
@ -710,8 +688,7 @@
|
|||
</action>
|
||||
<action name="actionFoldersButton">
|
||||
<property name="icon">
|
||||
<iconset theme="viewfolder">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="viewfolder"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Folders</string>
|
||||
|
@ -722,8 +699,7 @@
|
|||
</action>
|
||||
<action name="actionHelpButton">
|
||||
<property name="icon">
|
||||
<iconset theme="help">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="help"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Help</string>
|
||||
|
@ -734,8 +710,7 @@
|
|||
</action>
|
||||
<action name="actionAccountsButton">
|
||||
<property name="icon">
|
||||
<iconset theme="noaccount">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="noaccount"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Accounts</string>
|
||||
|
@ -743,8 +718,7 @@
|
|||
</action>
|
||||
<action name="actionOpenWiki">
|
||||
<property name="icon">
|
||||
<iconset theme="help">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="help"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>%1 &Help</string>
|
||||
|
@ -755,8 +729,7 @@
|
|||
</action>
|
||||
<action name="actionViewWidgetThemeFolder">
|
||||
<property name="icon">
|
||||
<iconset theme="viewfolder">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="viewfolder"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Widget Themes</string>
|
||||
|
@ -767,8 +740,7 @@
|
|||
</action>
|
||||
<action name="actionViewIconThemeFolder">
|
||||
<property name="icon">
|
||||
<iconset theme="viewfolder">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="viewfolder"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>I&con Theme</string>
|
||||
|
@ -779,8 +751,7 @@
|
|||
</action>
|
||||
<action name="actionViewCatPackFolder">
|
||||
<property name="icon">
|
||||
<iconset theme="viewfolder">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="viewfolder"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Cat Packs</string>
|
||||
|
@ -791,8 +762,7 @@
|
|||
</action>
|
||||
<action name="actionViewJavaFolder">
|
||||
<property name="icon">
|
||||
<iconset theme="viewfolder">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
<iconset theme="viewfolder"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<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 "minecraft/mod/ModFolderModel.h"
|
||||
#include "minecraft/mod/ResourceFolderModel.h"
|
||||
#include "modplatform/ModIndex.h"
|
||||
#include "modplatform/flame/FlamePackExportTask.h"
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
|
@ -33,7 +33,7 @@
|
|||
#include "MMCZip.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)
|
||||
{
|
||||
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->optionalFiles->setChecked(instance->settings()->get("ExportOptionalFiles").toBool());
|
||||
|
||||
connect(m_ui->recommendedMemoryCheckBox, &QCheckBox::toggled, m_ui->recommendedMemory, &QWidget::setEnabled);
|
||||
|
||||
if (m_provider == ModPlatform::ResourceProvider::MODRINTH) {
|
||||
setWindowTitle(tr("Export Modrinth Pack"));
|
||||
|
||||
m_ui->authorLabel->hide();
|
||||
m_ui->author->hide();
|
||||
|
||||
m_ui->recommendedMemoryWidget->hide();
|
||||
|
||||
m_ui->summary->setPlainText(instance->settings()->get("ExportSummary").toString());
|
||||
} else {
|
||||
setWindowTitle(tr("Export CurseForge Pack"));
|
||||
|
@ -57,6 +61,19 @@ ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPla
|
|||
m_ui->summaryLabel->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());
|
||||
}
|
||||
|
||||
|
@ -120,9 +137,15 @@ void ExportPackDialog::done(int result)
|
|||
|
||||
if (m_provider == ModPlatform::ResourceProvider::MODRINTH)
|
||||
settings->set("ExportSummary", m_ui->summary->toPlainText());
|
||||
else
|
||||
else {
|
||||
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) {
|
||||
const QString name = m_ui->name->text().isEmpty() ? m_instance->name() : m_ui->name->text();
|
||||
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(),
|
||||
m_instance, output, std::bind(&FileIgnoreProxy::filterFile, m_proxy, std::placeholders::_1));
|
||||
} else {
|
||||
task = new FlamePackExportTask(name, m_ui->version->text(), m_ui->author->text(), m_ui->optionalFiles->isChecked(), m_instance,
|
||||
output, std::bind(&FileIgnoreProxy::filterFile, m_proxy, std::placeholders::_1));
|
||||
FlamePackExportOptions options{};
|
||||
|
||||
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,
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include "BaseInstance.h"
|
||||
#include "FastFileIconProvider.h"
|
||||
#include "FileIgnoreProxy.h"
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "modplatform/ModIndex.h"
|
||||
|
||||
namespace Ui {
|
||||
|
@ -32,7 +33,7 @@ class ExportPackDialog : public QDialog {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ExportPackDialog(InstancePtr instance,
|
||||
explicit ExportPackDialog(MinecraftInstancePtr instance,
|
||||
QWidget* parent = nullptr,
|
||||
ModPlatform::ResourceProvider provider = ModPlatform::ResourceProvider::MODRINTH);
|
||||
~ExportPackDialog();
|
||||
|
@ -44,7 +45,7 @@ class ExportPackDialog : public QDialog {
|
|||
QString ignoreFileName();
|
||||
|
||||
private:
|
||||
const InstancePtr m_instance;
|
||||
const MinecraftInstancePtr m_instance;
|
||||
Ui::ExportPackDialog* m_ui;
|
||||
FileIgnoreProxy* m_proxy;
|
||||
FastFileIconProvider m_icons;
|
||||
|
|
|
@ -19,37 +19,57 @@
|
|||
<property name="title">
|
||||
<string>&Description</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<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">
|
||||
<property name="text">
|
||||
<string>&Name</string>
|
||||
<string>&Name:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>name</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="name"/>
|
||||
</item>
|
||||
<item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="versionLabel">
|
||||
<property name="text">
|
||||
<string>&Version</string>
|
||||
<string>&Version:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>version</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="version">
|
||||
<property name="text">
|
||||
<string>1.0.0</string>
|
||||
</property>
|
||||
</widget>
|
||||
</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>
|
||||
<widget class="QLabel" name="summaryLabel">
|
||||
<property name="text">
|
||||
|
@ -62,24 +82,29 @@
|
|||
</item>
|
||||
<item>
|
||||
<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">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</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>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -88,7 +113,70 @@
|
|||
<property name="title">
|
||||
<string>&Options</string>
|
||||
</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>
|
||||
<widget class="QLabel" name="filesLabel">
|
||||
<property name="text">
|
||||
|
@ -138,10 +226,6 @@
|
|||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>name</tabstop>
|
||||
<tabstop>version</tabstop>
|
||||
<tabstop>summary</tabstop>
|
||||
<tabstop>author</tabstop>
|
||||
<tabstop>files</tabstop>
|
||||
<tabstop>optionalFiles</tabstop>
|
||||
</tabstops>
|
||||
|
|
|
@ -36,7 +36,6 @@
|
|||
#include "MSALoginDialog.h"
|
||||
#include "Application.h"
|
||||
|
||||
#include "qr.h"
|
||||
#include "ui_MSALoginDialog.h"
|
||||
|
||||
#include "DesktopServices.h"
|
||||
|
@ -44,10 +43,15 @@
|
|||
|
||||
#include <QApplication>
|
||||
#include <QClipboard>
|
||||
#include <QColor>
|
||||
#include <QPainter>
|
||||
#include <QPixmap>
|
||||
#include <QSize>
|
||||
#include <QUrl>
|
||||
#include <QtWidgets/QPushButton>
|
||||
|
||||
#include "qrcodegen.hpp"
|
||||
|
||||
MSALoginDialog::MSALoginDialog(QWidget* parent) : QDialog(parent), ui(new Ui::MSALoginDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
@ -139,6 +143,33 @@ void MSALoginDialog::authorizeWithBrowser(const QUrl& 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)
|
||||
{
|
||||
ui->stackedWidget->setCurrentIndex(1);
|
||||
|
|
|
@ -92,6 +92,10 @@ SkinManageDialog::SkinManageDialog(QWidget* parent, MinecraftAccountPtr acct)
|
|||
connect(contentsWidget->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
|
||||
SLOT(selectionChanged(QItemSelection, QItemSelection)));
|
||||
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();
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
void SkinManageDialog::setupCapes()
|
||||
{
|
||||
// FIXME: add a model for this, download/refresh the capes on demand
|
||||
|
@ -208,7 +226,7 @@ void SkinManageDialog::setupCapes()
|
|||
}
|
||||
}
|
||||
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 {
|
||||
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 cape = m_capes.value(id.toString(), {});
|
||||
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 {
|
||||
m_ui->capeImage->clear();
|
||||
}
|
||||
|
@ -319,14 +338,14 @@ bool SkinManageDialog::eventFilter(QObject* obj, QEvent* 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()) {
|
||||
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())
|
||||
return;
|
||||
|
@ -523,7 +542,7 @@ void SkinManageDialog::resizeEvent(QResizeEvent* event)
|
|||
auto id = m_ui->capeCombo->currentData();
|
||||
auto cape = m_capes.value(id.toString(), {});
|
||||
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 {
|
||||
m_ui->capeImage->clear();
|
||||
}
|
||||
|
|
|
@ -59,6 +59,13 @@
|
|||
<string>Cape</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="elytraCB">
|
||||
<property name="text">
|
||||
<string>Preview Elytra</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="capeCombo"/>
|
||||
</item>
|
||||
|
|
|
@ -180,7 +180,8 @@ QList<QVector2D> getCubeUVs(float u, float v, float width, float height, float d
|
|||
}
|
||||
|
||||
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();
|
||||
|
||||
|
@ -274,4 +275,9 @@ BoxGeometry* BoxGeometry::Plane()
|
|||
|
||||
return b;
|
||||
}
|
||||
|
||||
void BoxGeometry::scale(const QVector3D& vector)
|
||||
{
|
||||
m_matrix.scale(vector);
|
||||
}
|
||||
} // 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 rotate(float angle, const QVector3D& vector);
|
||||
void scale(const QVector3D& vector);
|
||||
|
||||
private:
|
||||
QOpenGLBuffer m_vertexBuf;
|
||||
|
|
|
@ -18,9 +18,16 @@
|
|||
*/
|
||||
|
||||
#include "ui/dialogs/skins/draw/Scene.h"
|
||||
|
||||
#include <QOpenGLFunctions>
|
||||
#include <QOpenGLShaderProgram>
|
||||
#include <QOpenGLTexture>
|
||||
#include <QOpenGLWindow>
|
||||
|
||||
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 = {
|
||||
// head
|
||||
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(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
|
||||
m_skinTexture = new QOpenGLTexture(skin.mirrored());
|
||||
m_skinTexture->setMinificationFilter(QOpenGLTexture::Nearest);
|
||||
|
@ -68,7 +88,7 @@ Scene::Scene(const QImage& skin, bool slim, const QImage& cape) : m_slim(slim),
|
|||
}
|
||||
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) {
|
||||
delete g;
|
||||
}
|
||||
|
@ -95,7 +115,15 @@ void Scene::draw(QOpenGLShaderProgram* program)
|
|||
if (m_capeVisible) {
|
||||
m_capeTexture->bind();
|
||||
program->setUniformValue("texture", 0);
|
||||
if (!m_elytraVisible) {
|
||||
m_cape->draw(program);
|
||||
} else {
|
||||
glDisable(GL_CULL_FACE);
|
||||
for (auto e : m_elytra) {
|
||||
e->draw(program);
|
||||
}
|
||||
glEnable(GL_CULL_FACE);
|
||||
}
|
||||
m_capeTexture->release();
|
||||
}
|
||||
}
|
||||
|
@ -131,4 +159,8 @@ void Scene::setCapeVisible(bool visible)
|
|||
{
|
||||
m_capeVisible = visible;
|
||||
}
|
||||
void Scene::setElytraVisible(bool elytraVisible)
|
||||
{
|
||||
m_elytraVisible = elytraVisible;
|
||||
}
|
||||
} // namespace opengl
|
|
@ -22,7 +22,7 @@
|
|||
|
||||
#include <QOpenGLTexture>
|
||||
namespace opengl {
|
||||
class Scene {
|
||||
class Scene : protected QOpenGLFunctions {
|
||||
public:
|
||||
Scene(const QImage& skin, bool slim, const QImage& cape);
|
||||
virtual ~Scene();
|
||||
|
@ -32,15 +32,18 @@ class Scene {
|
|||
void setCape(const QImage& cape);
|
||||
void setMode(bool slim);
|
||||
void setCapeVisible(bool visible);
|
||||
void setElytraVisible(bool elytraVisible);
|
||||
|
||||
private:
|
||||
QList<BoxGeometry*> m_staticComponents;
|
||||
QList<BoxGeometry*> m_normalArms;
|
||||
QList<BoxGeometry*> m_slimArms;
|
||||
BoxGeometry* m_cape = nullptr;
|
||||
QList<BoxGeometry*> m_elytra;
|
||||
QOpenGLTexture* m_skinTexture = nullptr;
|
||||
QOpenGLTexture* m_capeTexture = nullptr;
|
||||
bool m_slim = false;
|
||||
bool m_capeVisible = false;
|
||||
bool m_elytraVisible = false;
|
||||
};
|
||||
} // namespace opengl
|
|
@ -263,3 +263,8 @@ void SkinOpenGLWindow::wheelEvent(QWheelEvent* event)
|
|||
m_distance = qMax(16.f, m_distance); // Clamp distance
|
||||
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 updateCape(const QImage& cape);
|
||||
void setElytraVisible(bool visible);
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent* e) override;
|
||||
|
|
|
@ -37,11 +37,11 @@
|
|||
|
||||
#include <Application.h>
|
||||
#include <QObjectPtr.h>
|
||||
#include "ui/widgets/JavaSettingsWidget.h"
|
||||
#include <QDialog>
|
||||
#include <QStringListModel>
|
||||
#include "JavaCommon.h"
|
||||
#include "ui/pages/BasePage.h"
|
||||
#include "ui/widgets/JavaSettingsWidget.h"
|
||||
|
||||
class SettingsObject;
|
||||
|
||||
|
|
|
@ -35,17 +35,18 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include "Application.h"
|
||||
#include "BaseInstance.h"
|
||||
#include "ui/pages/BasePage.h"
|
||||
#include "ui/widgets/MinecraftSettingsWidget.h"
|
||||
#include <QWidget>
|
||||
|
||||
class InstanceSettingsPage : public MinecraftSettingsWidget, public BasePage {
|
||||
Q_OBJECT
|
||||
|
||||
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::globalSettingsClosed, this, &InstanceSettingsPage::loadSettings);
|
||||
|
|
|
@ -347,13 +347,18 @@ void ManagedPackPage::onUpdateTaskCompleted(bool did_succeed) const
|
|||
if (m_instance_window != nullptr)
|
||||
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();
|
||||
} 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ModrinthManagedPackPage::update()
|
||||
|
|
|
@ -1,21 +1,22 @@
|
|||
#include <QObject>
|
||||
#include <QTcpSocket>
|
||||
#include <qtconcurrentrun.h>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <qtconcurrentrun.h>
|
||||
#include <QObject>
|
||||
#include <QTcpSocket>
|
||||
|
||||
#include <Exception.h>
|
||||
#include "McClient.h"
|
||||
#include "Json.h"
|
||||
#include "McClient.h"
|
||||
|
||||
// 7 first bits
|
||||
#define SEGMENT_BITS 0x7F
|
||||
// last bit
|
||||
#define CONTINUE_BIT 0x80
|
||||
|
||||
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..";
|
||||
|
||||
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::errorOccurred, this, [this]() {
|
||||
emitFail("Socket disconnected: " + m_socket.errorString());
|
||||
});
|
||||
connect(&m_socket, &QTcpSocket::errorOccurred, this, [this]() { emitFail("Socket disconnected: " + m_socket.errorString()); });
|
||||
|
||||
m_socket.connectToHost(m_ip, m_port);
|
||||
}
|
||||
|
||||
void McClient::sendRequest() {
|
||||
void McClient::sendRequest()
|
||||
{
|
||||
QByteArray data;
|
||||
writeVarInt(data, 0x00); // packet ID
|
||||
writeVarInt(data, 763); // hardcoded protocol version (763 = 1.20.1)
|
||||
|
@ -46,7 +46,8 @@ void McClient::sendRequest() {
|
|||
writePacketToSocket(data); // send status packet
|
||||
}
|
||||
|
||||
void McClient::readRawResponse() {
|
||||
void McClient::readRawResponse()
|
||||
{
|
||||
if (m_responseReadState == 2) {
|
||||
return;
|
||||
}
|
||||
|
@ -59,22 +60,21 @@ void McClient::readRawResponse() {
|
|||
|
||||
if (m_responseReadState == 1 && 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();
|
||||
m_responseReadState = 2;
|
||||
}
|
||||
}
|
||||
|
||||
void McClient::parseResponse() {
|
||||
void McClient::parseResponse()
|
||||
{
|
||||
qDebug() << "Received response successfully";
|
||||
|
||||
int packetID = readVarInt(m_resp);
|
||||
if (packetID != 0x00) {
|
||||
throw Exception(
|
||||
QString("Packet ID doesn't match expected value (0x00 vs 0x%1)")
|
||||
.arg(packetID, 0, 16)
|
||||
);
|
||||
throw Exception(QString("Packet ID doesn't match expected value (0x00 vs 0x%1)").arg(packetID, 0, 16));
|
||||
}
|
||||
|
||||
Q_UNUSED(readVarInt(m_resp)); // json length
|
||||
|
@ -85,7 +85,8 @@ void McClient::parseResponse() {
|
|||
}
|
||||
|
||||
// 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
|
||||
// Write 7 bits
|
||||
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
|
||||
int McClient::readVarInt(QByteArray &data) {
|
||||
int McClient::readVarInt(QByteArray& data)
|
||||
{
|
||||
int value = 0;
|
||||
int position = 0;
|
||||
char currentByte;
|
||||
|
@ -107,17 +109,20 @@ int McClient::readVarInt(QByteArray &data) {
|
|||
currentByte = readByte(data);
|
||||
value |= (currentByte & SEGMENT_BITS) << position;
|
||||
|
||||
if ((currentByte & CONTINUE_BIT) == 0) break;
|
||||
if ((currentByte & CONTINUE_BIT) == 0)
|
||||
break;
|
||||
|
||||
position += 7;
|
||||
}
|
||||
|
||||
if (position >= 32) throw Exception("VarInt is too big");
|
||||
if (position >= 32)
|
||||
throw Exception("VarInt is too big");
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
char McClient::readByte(QByteArray &data) {
|
||||
char McClient::readByte(QByteArray& data)
|
||||
{
|
||||
if (data.isEmpty()) {
|
||||
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
|
||||
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--) {
|
||||
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());
|
||||
}
|
||||
|
||||
void McClient::writePacketToSocket(QByteArray &data) {
|
||||
void McClient::writePacketToSocket(QByteArray& data)
|
||||
{
|
||||
// we prefix the packet with its length
|
||||
QByteArray dataWithSize;
|
||||
writeVarInt(dataWithSize, data.size());
|
||||
|
@ -151,14 +159,15 @@ void McClient::writePacketToSocket(QByteArray &data) {
|
|||
data.clear();
|
||||
}
|
||||
|
||||
|
||||
void McClient::emitFail(QString error) {
|
||||
void McClient::emitFail(QString error)
|
||||
{
|
||||
qDebug() << "Minecraft server ping for status error:" << error;
|
||||
emit failed(error);
|
||||
emit finished();
|
||||
}
|
||||
|
||||
void McClient::emitSucceed(QJsonObject data) {
|
||||
void McClient::emitSucceed(QJsonObject data)
|
||||
{
|
||||
emit succeeded(data);
|
||||
emit finished();
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#include <QObject>
|
||||
#include <QTcpSocket>
|
||||
#include <QFuture>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QFuture>
|
||||
#include <QObject>
|
||||
#include <QTcpSocket>
|
||||
|
||||
#include <Exception.h>
|
||||
|
||||
|
@ -22,29 +22,30 @@ class McClient : public QObject {
|
|||
unsigned m_wantedRespLength = 0;
|
||||
QByteArray m_resp;
|
||||
|
||||
public:
|
||||
explicit McClient(QObject *parent, QString domain, QString ip, short port);
|
||||
public:
|
||||
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
|
||||
void getStatusData();
|
||||
private:
|
||||
|
||||
private:
|
||||
void sendRequest();
|
||||
//! Accumulate data until we have a full response, then call parseResponse() once
|
||||
void readRawResponse();
|
||||
void parseResponse();
|
||||
|
||||
void writeVarInt(QByteArray &data, int value);
|
||||
int readVarInt(QByteArray &data);
|
||||
char readByte(QByteArray &data);
|
||||
void writeVarInt(QByteArray& data, int value);
|
||||
int readVarInt(QByteArray& data);
|
||||
char readByte(QByteArray& data);
|
||||
//! write number with specified size in big endian format
|
||||
void writeFixedInt(QByteArray &data, int value, int size);
|
||||
void writeString(QByteArray &data, const std::string &value);
|
||||
void writeFixedInt(QByteArray& data, int value, int size);
|
||||
void writeString(QByteArray& data, const std::string& value);
|
||||
|
||||
void writePacketToSocket(QByteArray &data);
|
||||
void writePacketToSocket(QByteArray& data);
|
||||
|
||||
void emitFail(QString error);
|
||||
void emitSucceed(QJsonObject data);
|
||||
|
||||
signals:
|
||||
signals:
|
||||
void succeeded(QJsonObject data);
|
||||
void failed(QString error);
|
||||
void finished();
|
||||
|
|
|
@ -1,23 +1,25 @@
|
|||
#include <QObject>
|
||||
#include <QDnsLookup>
|
||||
#include <QtNetwork/qtcpsocket.h>
|
||||
#include <QDnsLookup>
|
||||
#include <QHostInfo>
|
||||
#include <QObject>
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
void McResolver::pingWithDomainSRV(QString domain, int port) {
|
||||
QDnsLookup *lookup = new QDnsLookup(this);
|
||||
void McResolver::pingWithDomainSRV(QString domain, int port)
|
||||
{
|
||||
QDnsLookup* lookup = new QDnsLookup(this);
|
||||
lookup->setName(QString("_minecraft._tcp.%1").arg(domain));
|
||||
lookup->setType(QDnsLookup::SRV);
|
||||
|
||||
connect(lookup, &QDnsLookup::finished, this, [this, domain, port]() {
|
||||
QDnsLookup *lookup = qobject_cast<QDnsLookup *>(sender());
|
||||
QDnsLookup* lookup = qobject_cast<QDnsLookup*>(sender());
|
||||
|
||||
lookup->deleteLater();
|
||||
|
||||
|
@ -43,8 +45,9 @@ void McResolver::pingWithDomainSRV(QString domain, int port) {
|
|||
lookup->lookup();
|
||||
}
|
||||
|
||||
void McResolver::pingWithDomainA(QString domain, int port) {
|
||||
QHostInfo::lookupHost(domain, this, [this, port](const QHostInfo &hostInfo){
|
||||
void McResolver::pingWithDomainA(QString domain, int port)
|
||||
{
|
||||
QHostInfo::lookupHost(domain, this, [this, port](const QHostInfo& hostInfo) {
|
||||
if (hostInfo.error() != QHostInfo::NoError) {
|
||||
emitFail("A record lookup failed");
|
||||
return;
|
||||
|
@ -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;
|
||||
emit failed(error);
|
||||
emit finished();
|
||||
}
|
||||
|
||||
void McResolver::emitSucceed(QString ip, int port) {
|
||||
void McResolver::emitSucceed(QString ip, int port)
|
||||
{
|
||||
emit succeeded(ip, port);
|
||||
emit finished();
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#include <QtNetwork/qtcpsocket.h>
|
||||
#include <QDnsLookup>
|
||||
#include <QHostInfo>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QDnsLookup>
|
||||
#include <QtNetwork/qtcpsocket.h>
|
||||
#include <QHostInfo>
|
||||
|
||||
// resolve the IP and port of a Minecraft server
|
||||
class McResolver : public QObject {
|
||||
|
@ -11,17 +11,17 @@ class McResolver : public QObject {
|
|||
QString m_constrDomain;
|
||||
int m_constrPort;
|
||||
|
||||
public:
|
||||
explicit McResolver(QObject *parent, QString domain, int port);
|
||||
public:
|
||||
explicit McResolver(QObject* parent, QString domain, int port);
|
||||
void ping();
|
||||
|
||||
private:
|
||||
private:
|
||||
void pingWithDomainSRV(QString domain, int port);
|
||||
void pingWithDomainA(QString domain, int port);
|
||||
void emitFail(QString error);
|
||||
void emitSucceed(QString ip, int port);
|
||||
|
||||
signals:
|
||||
signals:
|
||||
void succeeded(QString ip, int port);
|
||||
void failed(QString error);
|
||||
void finished();
|
||||
|
|
|
@ -1,47 +1,41 @@
|
|||
#include <QFutureWatcher>
|
||||
|
||||
#include "ServerPingTask.h"
|
||||
#include "McResolver.h"
|
||||
#include "McClient.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");
|
||||
}
|
||||
|
||||
void ServerPingTask::executeTask() {
|
||||
void ServerPingTask::executeTask()
|
||||
{
|
||||
qDebug() << "Querying status of " << QString("%1:%2").arg(m_domain).arg(m_port);
|
||||
|
||||
// Resolve the actual IP and port for the server
|
||||
McResolver *resolver = new McResolver(nullptr, m_domain, m_port);
|
||||
McResolver* resolver = new McResolver(nullptr, m_domain, m_port);
|
||||
QObject::connect(resolver, &McResolver::succeeded, this, [this, resolver](QString ip, int port) {
|
||||
qDebug() << "Resolved Address for" << m_domain << ": " << ip << ":" << port;
|
||||
|
||||
// Now that we have the IP and port, query the server
|
||||
McClient *client = new McClient(nullptr, m_domain, ip, port);
|
||||
McClient* client = new McClient(nullptr, m_domain, ip, port);
|
||||
|
||||
QObject::connect(client, &McClient::succeeded, this, [this](QJsonObject data) {
|
||||
m_outputOnlinePlayers = getOnlinePlayers(data);
|
||||
qDebug() << "Online players: " << m_outputOnlinePlayers;
|
||||
emitSucceeded();
|
||||
});
|
||||
QObject::connect(client, &McClient::failed, this, [this](QString error) {
|
||||
emitFailed(error);
|
||||
});
|
||||
QObject::connect(client, &McClient::failed, this, [this](QString error) { emitFailed(error); });
|
||||
|
||||
// Delete McClient object when done
|
||||
QObject::connect(client, &McClient::finished, this, [this, client]() {
|
||||
client->deleteLater();
|
||||
});
|
||||
QObject::connect(client, &McClient::finished, this, [this, client]() { client->deleteLater(); });
|
||||
client->getStatusData();
|
||||
});
|
||||
QObject::connect(resolver, &McResolver::failed, this, [this](QString error) {
|
||||
emitFailed(error);
|
||||
});
|
||||
QObject::connect(resolver, &McResolver::failed, this, [this](QString error) { emitFailed(error); });
|
||||
|
||||
// Delete McResolver object when done
|
||||
QObject::connect(resolver, &McResolver::finished, [resolver]() {
|
||||
resolver->deleteLater();
|
||||
});
|
||||
QObject::connect(resolver, &McResolver::finished, [resolver]() { resolver->deleteLater(); });
|
||||
resolver->ping();
|
||||
}
|
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