diff options
237 files changed, 3986 insertions, 1081 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b6400791..c0e5b50a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,6 +7,10 @@ on: description: Type of build (Debug, Release, RelWithDebInfo, MinSizeRel) type: string default: Debug + is_qt_cached: + description: Enable Qt caching or not + type: string + default: true secrets: SPARKLE_ED25519_KEY: description: Private key for signing Sparkle updates @@ -25,26 +29,54 @@ jobs: - os: ubuntu-20.04 qt_ver: 6 qt_host: linux + qt_arch: '' qt_version: '6.2.4' qt_modules: 'qt5compat qtimageformats' + qt_tools: '' - os: windows-2022 name: "Windows-Legacy" - msystem: mingw32 + msystem: clang32 qt_ver: 5 - os: windows-2022 name: "Windows" - msystem: mingw32 + msystem: clang64 + qt_ver: 6 + + - os: windows-2022 + name: "Windows-Legacy-MSVC" + msystem: '' + architecture: 'win32' + vcvars_arch: 'amd64_x86' + qt_ver: 5 + qt_host: windows + qt_arch: 'win32_msvc2019' + qt_version: '5.15.2' + qt_modules: '' + qt_tools: 'tools_openssl_x86' + + - os: windows-2022 + name: "Windows-MSVC" + msystem: '' + architecture: 'x64' + vcvars_arch: 'amd64' qt_ver: 6 + qt_host: windows + qt_arch: '' + qt_version: '6.4.1' + qt_modules: 'qt5compat qtimageformats' + qt_tools: '' - os: macos-12 name: macOS macosx_deployment_target: 10.15 qt_ver: 6 qt_host: mac + qt_arch: '' qt_version: '6.3.0' qt_modules: 'qt5compat qtimageformats' + qt_tools: '' - os: macos-12 name: macOS-Legacy @@ -53,6 +85,7 @@ jobs: qt_host: mac qt_version: '5.15.2' qt_modules: '' + qt_tools: '' runs-on: ${{ matrix.os }} @@ -73,22 +106,15 @@ jobs: with: submodules: 'true' - - name: Initialize CodeQL - if: runner.os == 'Linux' && matrix.qt_ver == 6 - uses: github/codeql-action/init@v2 - with: - config-file: ./.github/codeql/codeql-config.yml - queries: security-and-quality - languages: cpp, java - - name: 'Setup MSYS2' - if: runner.os == 'Windows' + if: runner.os == 'Windows' && matrix.msystem != '' uses: msys2/setup-msys2@v2 with: msystem: ${{ matrix.msystem }} update: true install: >- git + mingw-w64-x86_64-binutils pacboy: >- toolchain:p cmake:p @@ -99,17 +125,21 @@ jobs: qt${{ matrix.qt_ver }}-imageformats:p quazip-qt${{ matrix.qt_ver }}:p ccache:p - nsis:p ${{ matrix.qt_ver == 6 && 'qt6-5compat: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' && inputs.build_type == 'Debug' - uses: hendrikmuhs/ccache-action@v1.2.3 + if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug' + uses: hendrikmuhs/ccache-action@v1.2.5 with: key: ${{ matrix.os }}-qt${{ matrix.qt_ver }} - - name: Setup ccache (Windows) - if: runner.os == 'Windows' && inputs.build_type == 'Debug' + - 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' @@ -124,8 +154,8 @@ jobs: run: | echo "CCACHE_VAR=ccache" >> $GITHUB_ENV - - name: Retrieve ccache cache (Windows) - if: runner.os == 'Windows' && inputs.build_type == 'Debug' + - name: Retrieve ccache cache (Windows MinGW-w64) + if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug' uses: actions/cache@v3.0.11 with: path: '${{ github.workspace }}\.ccache' @@ -156,16 +186,17 @@ jobs: run: | sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 - - name: Install Qt (macOS and AppImage) - if: runner.os == 'Linux' && matrix.qt_ver == 6 || runner.os == 'macOS' + - name: Install Qt (macOS, AppImage & Windows MSVC) + if: runner.os == 'Linux' && matrix.qt_ver == 6 || runner.os == 'macOS' || (runner.os == 'Windows' && matrix.msystem == '') uses: jurplel/install-qt-action@v3 with: version: ${{ matrix.qt_version }} host: ${{ matrix.qt_host }} target: 'desktop' + arch: ${{ matrix.qt_arch }} modules: ${{ matrix.qt_modules }} - cache: true - cache-key-prefix: ${{ matrix.qt_host }}-${{ matrix.qt_version }}-"${{ matrix.qt_modules }}"-qt_cache + tools: ${{ matrix.qt_tools }} + cache: ${{ inputs.is_qt_cached }} - name: Prepare AppImage (Linux) if: runner.os == 'Linux' && matrix.qt_ver != 5 @@ -190,11 +221,26 @@ jobs: run: | cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DMACOSX_SPARKLE_UPDATE_PUBLIC_KEY="" -DMACOSX_SPARKLE_UPDATE_FEED_URL="" -G Ninja - - name: Configure CMake (Windows) - if: runner.os == 'Windows' + - 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_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -G Ninja + cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_OBJDUMP=/mingw64/bin/objdump.exe -G Ninja + + - name: Configure CMake (Windows MSVC) + if: runner.os == 'Windows' && matrix.msystem == '' + run: | + cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -A${{ matrix.architecture}} + # https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix) + if ("${{ env.CCACHE_VAR }}") + { + 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: Configure CMake (Linux) if: runner.os == 'Linux' @@ -210,12 +256,17 @@ jobs: run: | cmake --build ${{ env.BUILD_DIR }} - - name: Build (Windows) - if: runner.os == 'Windows' + - name: Build (Windows MinGW-w64) + if: runner.os == 'Windows' && matrix.msystem != '' shell: msys2 {0} 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 }} + ## # TEST ## @@ -223,21 +274,18 @@ jobs: - name: Test if: runner.os != 'Windows' run: | - ctest --test-dir build --output-on-failure + ctest -E "^example64|example$" --test-dir build --output-on-failure - - name: Test (Windows) - if: runner.os == 'Windows' + - name: Test (Windows MinGW-w64) + if: runner.os == 'Windows' && matrix.msystem != '' shell: msys2 {0} run: | - ctest --test-dir build --output-on-failure - - ## - # CODE SCAN - ## + ctest -E "^example64|example$" --test-dir build --output-on-failure - - name: Perform CodeQL Analysis - if: runner.os == 'Linux' && matrix.qt_ver == 6 - uses: github/codeql-action/analyze@v2 + - name: Test (Windows MSVC) + if: runner.os == 'Windows' && matrix.msystem == '' + run: | + ctest -E "^example64|example$" --test-dir build --output-on-failure -C ${{ inputs.build_type }} ## # PACKAGE BUILDS @@ -251,6 +299,7 @@ jobs: cd ${{ env.INSTALL_DIR }} chmod +x "PrismLauncher.app/Contents/MacOS/prismlauncher" sudo codesign --sign - --deep --force --entitlements "../program_info/App.entitlements" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher" + mv "PrismLauncher.app" "Prism Launcher.app" tar -czf ../PrismLauncher.tar.gz * - name: Make Sparkle signature (macOS) @@ -272,27 +321,50 @@ jobs: EOF fi - - name: Package (Windows) - if: runner.os == 'Windows' + - name: Add VC Enviroment Variables + if: runner.os == 'Windows' && matrix.msystem == '' + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: ${{ matrix.vcvars_arch }} + + - name: Package (Windows MinGW-w64) + if: runner.os == 'Windows' && matrix.msystem != '' shell: msys2 {0} run: | cmake --install ${{ env.BUILD_DIR }} cd ${{ env.INSTALL_DIR }} if [ "${{ matrix.qt_ver }}" == "5" ]; then - cp /mingw32/bin/libcrypto-1_1.dll /mingw32/bin/libssl-1_1.dll ./ + cp /clang32/bin/libcrypto-1_1.dll /clang32/bin/libssl-1_1.dll ./ fi - - name: Package (Windows, portable) - if: runner.os == 'Windows' + - name: Package (Windows MSVC) + if: runner.os == 'Windows' && matrix.msystem == '' + run: | + cmake --install ${{ env.BUILD_DIR }} --config ${{ inputs.build_type }} + + cd ${{ env.INSTALL_DIR }} + if ("${{ matrix.qt_ver }}" -eq "5") + { + Copy-Item D:/a/PrismLauncher/Qt/Tools/OpenSSL/Win_x86/bin/libcrypto-1_1.dll -Destination libcrypto-1_1.dll + Copy-Item D:/a/PrismLauncher/Qt/Tools/OpenSSL/Win_x86/bin/libssl-1_1.dll -Destination libssl-1_1.dll + } + + - 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 + - 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 + - name: Package (Windows, installer) if: runner.os == 'Windows' - shell: msys2 {0} run: | cd ${{ env.INSTALL_DIR }} makensis -NOCD "${{ github.workspace }}/${{ env.BUILD_DIR }}/program_info/win_install.nsi" @@ -411,5 +483,46 @@ jobs: 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 - - + snap: + runs-on: ubuntu-20.04 + steps: + - name: Checkout + if: inputs.build_type == 'Debug' + uses: actions/checkout@v3 + with: + submodules: 'true' + - name: Set short version + shell: bash + if: inputs.build_type == 'Debug' + run: | + ver_short=`git rev-parse --short HEAD` + echo "VERSION=$ver_short" >> $GITHUB_ENV + - name: Package Snap (Linux) + id: snapcraft + if: inputs.build_type == 'Debug' + uses: snapcore/action-build@v1 + - name: Upload Snap (Linux) + if: inputs.build_type == 'Debug' + uses: actions/upload-artifact@v3 + with: + name: prismlauncher_${{ env.VERSION }}_amd64.snap + path: ${{ steps.snapcraft.outputs.snap }} + + flatpak: + runs-on: ubuntu-latest + container: + image: bilelmoussaoui/flatpak-github-actions:kde-5.15-22.08 + options: --privileged + steps: + - name: Checkout + uses: actions/checkout@v3 + if: inputs.build_type == 'Debug' + with: + submodules: 'true' + - name: Build Flatpak (Linux) + if: inputs.build_type == 'Debug' + uses: flatpak/flatpak-github-actions/flatpak-builder@v4 + with: + bundle: "Prism Launcher.flatpak" + manifest-path: flatpak/org.prismlauncher.PrismLauncher.yml + cache-key: flatpak-${{ github.sha }}-x86_64 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000..0cd1f6e4 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,35 @@ +name: "CodeQL Code Scanning" + +on: [ push, pull_request, workflow_dispatch ] + +jobs: + CodeQL: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + submodules: 'true' + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + config-file: ./.github/codeql/codeql-config.yml + 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 qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 + + - name: Configure and Build + run: | + cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/usr -DLauncher_QT_VERSION_MAJOR=5 -G Ninja + + cmake --build build + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/trigger_builds.yml b/.github/workflows/trigger_builds.yml index 8adaa5e5..44751fbc 100644 --- a/.github/workflows/trigger_builds.yml +++ b/.github/workflows/trigger_builds.yml @@ -30,5 +30,6 @@ jobs: uses: ./.github/workflows/build.yml with: build_type: Debug + is_qt_cached: true secrets: SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }} diff --git a/.github/workflows/trigger_release.yml b/.github/workflows/trigger_release.yml index e74e870a..8baa9693 100644 --- a/.github/workflows/trigger_release.yml +++ b/.github/workflows/trigger_release.yml @@ -12,6 +12,7 @@ jobs: uses: ./.github/workflows/build.yml with: build_type: Release + is_qt_cached: false secrets: SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }} @@ -47,10 +48,12 @@ jobs: for d in PrismLauncher-Windows-*; do cd "${d}" || continue + MSVC="$(echo -n ${d} | grep -o MSVC || true)" LEGACY="$(echo -n ${d} | grep -o Legacy || true)" INST="$(echo -n ${d} | grep -o Setup || true)" PORT="$(echo -n ${d} | grep -o Portable || true)" NAME="PrismLauncher-Windows" + test -z "${MSVC}" && NAME="${NAME}-MinGW" || NAME="${NAME}-MSVC" test -z "${LEGACY}" || NAME="${NAME}-Legacy" test -z "${PORT}" || NAME="${NAME}-Portable" test -z "${INST}" || mv PrismLauncher-*.exe ../${NAME}-Setup-${{ env.VERSION }}.exe @@ -72,14 +75,20 @@ jobs: PrismLauncher-Linux-${{ env.VERSION }}.tar.gz PrismLauncher-Linux-Portable-${{ env.VERSION }}.tar.gz PrismLauncher-Linux-${{ env.VERSION }}-x86_64.AppImage - PrismLauncher-Windows-Legacy-${{ env.VERSION }}.zip PrismLauncher-Linux-Qt6-${{ env.VERSION }}.tar.gz PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz - PrismLauncher-Windows-Legacy-Portable-${{ env.VERSION }}.zip - PrismLauncher-Windows-Legacy-Setup-${{ env.VERSION }}.exe - PrismLauncher-Windows-${{ env.VERSION }}.zip - PrismLauncher-Windows-Portable-${{ env.VERSION }}.zip - PrismLauncher-Windows-Setup-${{ env.VERSION }}.exe + PrismLauncher-Windows-MinGW-Legacy-${{ env.VERSION }}.zip + PrismLauncher-Windows-MinGW-Legacy-Portable-${{ env.VERSION }}.zip + PrismLauncher-Windows-MinGW-Legacy-Setup-${{ env.VERSION }}.exe + PrismLauncher-Windows-MinGW-${{ env.VERSION }}.zip + PrismLauncher-Windows-MinGW-Portable-${{ env.VERSION }}.zip + PrismLauncher-Windows-MinGW-Setup-${{ env.VERSION }}.exe + PrismLauncher-Windows-MSVC-Legacy-${{ env.VERSION }}.zip + PrismLauncher-Windows-MSVC-Legacy-Portable-${{ env.VERSION }}.zip + PrismLauncher-Windows-MSVC-Legacy-Setup-${{ env.VERSION }}.exe + PrismLauncher-Windows-MSVC-${{ env.VERSION }}.zip + PrismLauncher-Windows-MSVC-Portable-${{ env.VERSION }}.zip + PrismLauncher-Windows-MSVC-Setup-${{ env.VERSION }}.exe PrismLauncher-macOS-${{ env.VERSION }}.tar.gz PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz PrismLauncher-${{ env.VERSION }}.tar.gz @@ -47,3 +47,6 @@ result/ # Flatpak .flatpak-builder flatbuild + +# Snap +*.snap diff --git a/.gitmodules b/.gitmodules index 8d034354..95274f15 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,9 @@ [submodule "libraries/libnbtplusplus"] path = libraries/libnbtplusplus url = https://github.com/PrismLauncher/libnbtplusplus.git +[submodule "libraries/zlib"] + path = libraries/zlib + url = https://github.com/madler/zlib.git +[submodule "libraries/extra-cmake-modules"] + path = libraries/extra-cmake-modules + url = https://github.com/KDE/extra-cmake-modules diff --git a/CMakeLists.txt b/CMakeLists.txt index 97bad31b..8fc0d326 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,5 @@ cmake_minimum_required(VERSION 3.15) # minimum version required by QuaZip -if(WIN32) - # In Qt 5.1+ we have our own main() function, don't autolink to qtmain on Windows - cmake_policy(SET CMP0020 OLD) -endif() - project(Launcher) string(COMPARE EQUAL "${CMAKE_SOURCE_DIR}" "${CMAKE_BUILD_DIR}" IS_IN_SOURCE_BUILD) @@ -32,13 +27,52 @@ set(CMAKE_C_STANDARD_REQUIRED true) set(CMAKE_CXX_STANDARD 17) set(CMAKE_C_STANDARD 11) include(GenerateExportHeader) -set(CMAKE_CXX_FLAGS "-Wall -pedantic -fstack-protector-strong --param=ssp-buffer-size=4 ${CMAKE_CXX_FLAGS}") +if(MSVC) + # Use /W4 as /Wall includes unnesserey warnings such as added padding to structs + # /permissive- specify standards-conforming compiler behavior, also enabled by Qt6, default on with std:c++20 + # /GS Adds buffer security checks, default on but incuded anyway to mirror gcc's fstack-protector flag + set(CMAKE_CXX_FLAGS "/W4 /permissive- /GS ${CMAKE_CXX_FLAGS}") + + # LINK accepts /SUBSYSTEM whics sets if we are a WINDOWS (gui) or a CONSOLE programs + # This implicitly selects an entrypoint specific to the subsystem selected + # qtmain/QtEntryPointLib provides the correct entrypoint (wWinMain) for gui programs + # Additinaly LINK autodetects we use a GUI so we can omit /SUBSYSTEM + # This allows tests to still use have console without using seperate linker flags + # /MANIFEST:NO disables generating a manifest file, we instead provide our own + # /STACK sets the stack reserve size, ATL's pack list needs 3-4 MiB as of November 2022, provide 8 MiB + set(CMAKE_EXE_LINKER_FLAGS "/MANIFEST:NO /STACK:8388608 ${CMAKE_EXE_LINKER_FLAGS}") + + # See https://github.com/ccache/ccache/issues/1040 + # Note, CMake 3.25 replaces this with CMAKE_MSVC_DEBUG_INFORMATION_FORMAT + # See https://cmake.org/cmake/help/v3.25/variable/CMAKE_MSVC_DEBUG_INFORMATION_FORMAT.html + foreach(config DEBUG RELWITHDEBINFO) + foreach(lang C CXX) + set(flags_var "CMAKE_${lang}_FLAGS_${config}") + string(REGEX REPLACE "/Z[Ii]" "/Z7" ${flags_var} "${${flags_var}}") + endforeach() + endforeach() + + if(CMAKE_MSVC_RUNTIME_LIBRARY STREQUAL "MultiThreadedDLL") + set(CMAKE_MAP_IMPORTED_CONFIG_DEBUG Release "") + set(CMAKE_MAP_IMPORTED_CONFIG_RELWITHDEBINFO Release "") + endif() +else() + set(CMAKE_CXX_FLAGS "-Wall -pedantic -fstack-protector-strong --param=ssp-buffer-size=4 ${CMAKE_CXX_FLAGS}") + + # ATL's pack list needs more than the default 1 Mib stack on windows + if(WIN32) + set(CMAKE_EXE_LINKER_FLAGS "-Wl,--stack,8388608 ${CMAKE_EXE_LINKER_FLAGS}") + endif() +endif() # Fix build with Qt 5.13 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_NO_DEPRECATED_WARNINGS=Y") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_DISABLE_DEPRECATED_BEFORE=0x050C00") +# Fix aarch64 build for toml++ +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DTOML_ENABLE_FLOAT16=0") + # set CXXFLAGS for build targets set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS_RELEASE}") @@ -48,11 +82,18 @@ if(ENABLE_LTO) include(CheckIPOSupported) check_ipo_supported(RESULT ipo_supported OUTPUT ipo_error) - if(ipo_supported AND (CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")) - message(STATUS "IPO / LTO enabled") - set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) - elseif(ipo_supported) - message(STATUS "Not enabling IPO / LTO on debug builds") + if(ipo_supported) + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE) + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_MINSIZEREL TRUE) + if(CMAKE_BUILD_TYPE) + if(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "MinSizeRel") + message(STATUS "IPO / LTO enabled") + else() + message(STATUS "Not enabling IPO / LTO on debug builds") + endif() + else() + message(STATUS "IPO / LTO will only be enabled for release builds") + endif() else() message(STATUS "IPO / LTO not supported: <${ipo_error}>") endif() @@ -60,8 +101,20 @@ endif() option(BUILD_TESTING "Build the testing tree." ON) -find_package(ECM REQUIRED NO_MODULE) -set(CMAKE_MODULE_PATH "${ECM_MODULE_PATH};${CMAKE_MODULE_PATH}") +find_package(ECM QUIET NO_MODULE) +if(NOT ECM_FOUND) + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/libraries/extra-cmake-modules/CMakeLists.txt") + message(STATUS "Using bundled ECM") + set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/libraries/extra-cmake-modules/modules;${CMAKE_MODULE_PATH}") + else() + message(FATAL_ERROR + " Could not find ECM\n \n" + " Either install ECM using the system package manager or clone submodules\n" + " Submodules can be cloned with 'git submodule update --init --recursive'") + endif() +else() + set(CMAKE_MODULE_PATH "${ECM_MODULE_PATH};${CMAKE_MODULE_PATH}") +endif() include(CTest) include(ECMAddTests) if(BUILD_TESTING) @@ -76,7 +129,7 @@ set(Launcher_NEWS_OPEN_URL "https://prismlauncher.org/news" CACHE STRING "URL th set(Launcher_HELP_URL "https://prismlauncher.org/wiki/help-pages/%1" CACHE STRING "URL (with arg %1 to be substituted with page-id) that gets opened when the user requests help") ######## Set version numbers ######## -set(Launcher_VERSION_MAJOR 5) +set(Launcher_VERSION_MAJOR 6) set(Launcher_VERSION_MINOR 0) set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}") @@ -146,6 +199,10 @@ set(Launcher_BUILD_TIMESTAMP "${TODAY}") ################################ 3rd Party Libs ################################ +if(NOT Launcher_FORCE_BUNDLED_LIBS) + find_package(ZLIB QUIET) +endif() + # Find the required Qt parts include(QtVersionlessBackport) if(Launcher_QT_VERSION_MAJOR EQUAL 5) @@ -222,14 +279,14 @@ if(UNIX AND APPLE) set(APPS "\${CMAKE_INSTALL_PREFIX}/${Launcher_Name}.app") # Mac bundle settings - set(MACOSX_BUNDLE_BUNDLE_NAME "${Launcher_Name}") - set(MACOSX_BUNDLE_INFO_STRING "${Launcher_Name}: A custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once.") + set(MACOSX_BUNDLE_BUNDLE_NAME "${Launcher_DisplayName}") + set(MACOSX_BUNDLE_INFO_STRING "${Launcher_DisplayName}: A custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once.") set(MACOSX_BUNDLE_GUI_IDENTIFIER "org.prismlauncher.${Launcher_Name}") set(MACOSX_BUNDLE_BUNDLE_VERSION "${Launcher_VERSION_NAME}") set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${Launcher_VERSION_NAME}") set(MACOSX_BUNDLE_LONG_VERSION_STRING "${Launcher_VERSION_NAME}") set(MACOSX_BUNDLE_ICON_FILE ${Launcher_Name}.icns) - set(MACOSX_BUNDLE_COPYRIGHT "Copyright 2021-2022 ${Launcher_Copyright}") + set(MACOSX_BUNDLE_COPYRIGHT "© 2022 ${Launcher_Copyright_Mac}") set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "v55ZWWD6QlPoXGV6VLzOTZxZUggWeE51X8cRQyQh6vA=") set(MACOSX_SPARKLE_UPDATE_FEED_URL "https://prismlauncher.org/feed/appcast.xml") @@ -247,13 +304,11 @@ if(UNIX AND APPLE) install(FILES ${Launcher_Branding_ICNS} DESTINATION ${RESOURCES_DEST_DIR} RENAME ${Launcher_Name}.icns) elseif(UNIX) + include(KDEInstallDirs) + set(BINARY_DEST_DIR "bin") set(LIBRARY_DEST_DIR "lib${LIB_SUFFIX}") set(JARS_DEST_DIR "share/${Launcher_APP_BINARY_NAME}") - set(LAUNCHER_DESKTOP_DEST_DIR "share/applications" CACHE STRING "Path to the desktop file directory") - set(LAUNCHER_METAINFO_DEST_DIR "share/metainfo" CACHE STRING "Path to the metainfo directory") - set(LAUNCHER_ICON_DEST_DIR "share/icons/hicolor/scalable/apps" CACHE STRING "Path to the scalable icon directory") - set(LAUNCHER_MAN_DEST_DIR "share/man/man6" CACHE STRING "Path to the man page directory") # install as bundle with no dependencies included set(INSTALL_BUNDLE "nodeps") @@ -261,12 +316,13 @@ elseif(UNIX) # Set RPATH SET(Launcher_BINARY_RPATH "$ORIGIN/") - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_Desktop} DESTINATION ${LAUNCHER_DESKTOP_DEST_DIR}) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_MetaInfo} DESTINATION ${LAUNCHER_METAINFO_DEST_DIR}) - install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_SVG} DESTINATION ${LAUNCHER_ICON_DEST_DIR}) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_Desktop} DESTINATION ${KDE_INSTALL_APPDIR}) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_MetaInfo} DESTINATION ${KDE_INSTALL_METAINFODIR}) + install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_SVG} DESTINATION "${KDE_INSTALL_ICONDIR}/hicolor/scalable/apps") + install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_mrpack_MIMEInfo} DESTINATION ${KDE_INSTALL_MIMEDIR}) if(Launcher_ManPage) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_ManPage} DESTINATION ${LAUNCHER_MAN_DEST_DIR}) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_ManPage} DESTINATION "${KDE_INSTALL_MANDIR}/man6") endif() # Install basic runner script if component "portable" is selected @@ -306,6 +362,21 @@ add_subdirectory(libraries/systeminfo) # system information library add_subdirectory(libraries/hoedown) # markdown parser add_subdirectory(libraries/launcher) # java based launcher part for Minecraft add_subdirectory(libraries/javacheck) # java compatibility checker +if(NOT ZLIB_FOUND) + message(STATUS "Using bundled zlib") + set(CMAKE_POLICY_DEFAULT_CMP0069 NEW) # Suppress cmake warnings and allow INTERPROCEDURAL_OPTIMIZATION for zlib + set(SKIP_INSTALL_ALL ON) + add_subdirectory(libraries/zlib EXCLUDE_FROM_ALL) + + set(ZLIB_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib" "${CMAKE_CURRENT_BINARY_DIR}/libraries/zlib") + set_target_properties(zlibstatic PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${ZLIB_INCLUDE_DIR}") + add_library(ZLIB::ZLIB ALIAS zlibstatic) + set(ZLIB_LIBRARY ZLIB::ZLIB) + set(ZLIB_FOUND true) + find_package(ZLIB REQUIRED) +else() + message(STATUS "Using system zlib") +endif() if (FORCE_BUNDLED_QUAZIP) message(STATUS "Using bundled QuaZip") set(BUILD_SHARED_LIBS 0) # link statically to avoid conflicts. @@ -1,44 +1,52 @@ -<p align="center"> -<img src="./program_info/org.prismlauncher.PrismLauncher.logo.svg#gh-light-mode-only" alt="Prism Launcher logo" width="50%"/> -<img src="./program_info/org.prismlauncher.PrismLauncher.logo-darkmode.svg#gh-dark-mode-only" alt="Prism Launcher logo" width="50%"/> +<p align="left"> +<picture> + <source media="(prefers-color-scheme: dark)" srcset="/program_info/org.prismlauncher.PrismLauncher.logo-darkmode.svg"> + <source media="(prefers-color-scheme: light)" srcset="/program_info/org.prismlauncher.PrismLauncher.logo.svg"> + <img alt="Prism Launcher" src="/program_info/org.prismlauncher.PrismLauncher.logo.svg" width="50%"> +</picture> </p> + Prism Launcher is a custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once. -This is a **fork** of the MultiMC Launcher and not endorsed by MultiMC. +This is a **fork** of the MultiMC Launcher and is not endorsed by MultiMC. + ## Installation -- All downloads and instructions for Prism Launcher can be found [on our website](https://prismlauncher.org/download/). -- Last build status can be found [here](https://github.com/PrismLauncher/PrismLauncher/actions). +<a href="https://repology.org/project/prismlauncher/versions"> + <img src="https://repology.org/badge/vertical-allrepos/prismlauncher.svg" alt="Packaging status" align="right"> +</a> + +- All downloads and instructions for Prism Launcher can be found on our [Website](https://prismlauncher.org/download/). +- Last build status can be found in the [GitHub Actions](https://github.com/PrismLauncher/PrismLauncher/actions). ### Development Builds There are development builds available [here](https://github.com/PrismLauncher/PrismLauncher/actions). These have debug information in the binaries, so their file sizes are relatively larger. -Portable builds are provided for on Linux, Windows, and macOS. +Prebuilt Development builds are provided for **Linux**, **Windows** and **macOS**. -For Debian and Arch, you can use these packages for the latest development versions: -[](https://aur.archlinux.org/packages/prismlauncher-git/) -[](https://mpr.makedeb.org/packages/prismlauncher-git) -## Help & Support +For **Arch**, **Debian**, **Fedora**, **OpenSUSE (Tumbleweed)** and **Gentoo**, respectively, you can use these packages for the latest development versions: -Feel free to create an issue if you need help. However, you might find it easier to ask in the Discord server. +[](https://aur.archlinux.org/packages/prismlauncher-git/) [](https://aur.archlinux.org/packages/prismlauncher-qt5-git/) [](https://mpr.makedeb.org/packages/prismlauncher-git) [](https://copr.fedorainfracloud.org/coprs/g3tchoo/prismlauncher/) [](https://build.opensuse.org/project/show/home:getchoo) [](https://packages.gentoo.org/packages/games-action/prismlauncher) -[](https://discord.gg/prismlauncher) +## Community & Support -We will also soon be opening up our Matrix channels. -You can already join our Matrix space: +Feel free to create a GitHub issue if you find a bug or want to suggest a new feature. We have multiple community spaces where other community members can help you. -[](https://matrix.to/#/#prismlauncher:matrix.org) +#### Join our Discord server: +[](https://discord.gg/prismlauncher) -We also have a subreddit you can post your issues and suggestions on: +#### Join our Matrix space: +[](https://matrix.to/#/#prismlauncher:matrix.org) -[r/PrismLauncher](https://www.reddit.com/r/PrismLauncher/) +#### Join our Subreddit: +[](https://www.reddit.com/r/PrismLauncher/) ## Building -If you want to build Prism Launcher yourself, check [Build Instructions](https://prismlauncher.org/wiki/development/build-instructions/) for build instructions. +If you want to build Prism Launcher yourself, check the [Build Instructions](https://prismlauncher.org/wiki/development/build-instructions/). ## Translations @@ -60,13 +68,11 @@ Be aware that if you build this software without removing the provided API keys If you do not agree with these terms and conditions, then remove the associated API keys from the [CMakeLists.txt](CMakeLists.txt) file by setting them to an empty string (`""`). -## License +## Sponsors & Partners -All launcher code is available under the GPL-3.0-only license. - -The logo and related assets are under the CC BY-SA 4.0 license. +We thank all the wonderful backers over at Open Collective! Support Prism Launcher by [becoming a backer](https://opencollective.com/prismlauncher). -## Sponsors +[](https://opencollective.com/prismlauncher#backers) 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/). @@ -85,3 +91,12 @@ Thanks to Netlify for providing us their excellent web services, as part of thei Thanks to the awesome people over at [MacStadium](https://www.macstadium.com/), for providing M1-Macs for development purposes! <a href="https://www.macstadium.com"><img src="https://uploads-ssl.webflow.com/5ac3c046c82724970fc60918/5c019d917bba312af7553b49_MacStadium-developerlogo.png" alt="Powered by MacStadium" width="300"></a> + + +## License + +All launcher code is available under the GPL-3.0-only license. + + + +The logo and related assets are under the CC BY-SA 4.0 license. diff --git a/buildconfig/BuildConfig.cpp.in b/buildconfig/BuildConfig.cpp.in index b8fa5133..1262ce8e 100644 --- a/buildconfig/BuildConfig.cpp.in +++ b/buildconfig/BuildConfig.cpp.in @@ -49,6 +49,7 @@ Config::Config() LAUNCHER_CONFIGFILE = "@Launcher_ConfigFile@"; LAUNCHER_GIT = "@Launcher_Git@"; LAUNCHER_DESKTOPFILENAME = "@Launcher_DesktopFileName@"; + LAUNCHER_SVGFILENAME = "@Launcher_SVGFileName@"; USER_AGENT = "@Launcher_UserAgent@"; USER_AGENT_UNCACHED = USER_AGENT + " (Uncached)"; diff --git a/buildconfig/BuildConfig.h b/buildconfig/BuildConfig.h index 13ccdaa1..4a309073 100644 --- a/buildconfig/BuildConfig.h +++ b/buildconfig/BuildConfig.h @@ -51,6 +51,7 @@ class Config { QString LAUNCHER_CONFIGFILE; QString LAUNCHER_GIT; QString LAUNCHER_DESKTOPFILENAME; + QString LAUNCHER_SVGFILENAME; /// The major version number. int VERSION_MAJOR; diff --git a/cmake/MacOSXBundleInfo.plist.in b/cmake/MacOSXBundleInfo.plist.in index 1b22e21f..400e482f 100644 --- a/cmake/MacOSXBundleInfo.plist.in +++ b/cmake/MacOSXBundleInfo.plist.in @@ -44,5 +44,28 @@ <string>${MACOSX_SPARKLE_UPDATE_PUBLIC_KEY}</string> <key>SUFeedURL</key> <string>${MACOSX_SPARKLE_UPDATE_FEED_URL}</string> + <key>CFBundleDocumentTypes</key> + <array> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>zip</string> + <string>mrpack</string> + </array> + <key>CFBundleTypeName</key> + <string>Prism Launcher instance</string> + <key>CFBundleTypeOSTypes</key> + <array> + <string>TEXT</string> + <string>utxt</string> + <string>TUTX</string> + <string>****</string> + </array> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + <key>LSHandlerRank</key> + <string>Alternate</string> + </dict> + </array> </dict> </plist> diff --git a/flatpak/org.prismlauncher.PrismLauncher.yml b/flatpak/org.prismlauncher.PrismLauncher.yml new file mode 100644 index 00000000..fca306d7 --- /dev/null +++ b/flatpak/org.prismlauncher.PrismLauncher.yml @@ -0,0 +1,83 @@ +id: org.prismlauncher.PrismLauncher +runtime: org.kde.Platform +runtime-version: "5.15-22.08" +sdk: org.kde.Sdk +sdk-extensions: + - org.freedesktop.Sdk.Extension.openjdk17 + - org.freedesktop.Sdk.Extension.openjdk8 +add-extensions: + com.valvesoftware.Steam.Utility.gamescope: + version: stable + add-ld-path: lib + no-autodownload: true + autodelete: false + directory: utils/gamescope + +command: prismlauncher +finish-args: + - --share=ipc + - --socket=x11 + - --socket=wayland + - --device=all + - --share=network + - --socket=pulseaudio + # for Discord RPC mods + - --filesystem=xdg-run/app/com.discordapp.Discord:create + # Mod drag&drop + - --filesystem=xdg-download:ro + +modules: + - name: prismlauncher + buildsystem: cmake-ninja + config-opts: + - -DLauncher_BUILD_PLATFORM=flatpak + - -DCMAKE_BUILD_TYPE=Debug + build-options: + env: + JAVA_HOME: /usr/lib/sdk/openjdk17/jvm/openjdk-17 + JAVA_COMPILER: /usr/lib/sdk/openjdk17/jvm/openjdk-17/bin/javac + sources: + - type: dir + path: ../ + - name: openjdk + buildsystem: simple + build-commands: + - mkdir -p /app/jdk/ + - /usr/lib/sdk/openjdk17/install.sh + - mv /app/jre /app/jdk/17 + - /usr/lib/sdk/openjdk8/install.sh + - mv /app/jre /app/jdk/8 + cleanup: [/jre] + - name: xrandr + buildsystem: autotools + sources: + - type: archive + url: https://xorg.freedesktop.org/archive/individual/app/xrandr-1.5.1.tar.xz + sha256: 7bc76daf9d72f8aff885efad04ce06b90488a1a169d118dea8a2b661832e8762 + cleanup: [/share/man, /bin/xkeystone] + - name: gamemode + buildsystem: meson + config-opts: + - -Dwith-sd-bus-provider=no-daemon + - -Dwith-examples=false + post-install: + # gamemoderun is installed for users who want to use wrapper commands + # post-install is running inside the build dir, we need it from the source though + - install -Dm755 ../data/gamemoderun -t /app/bin + sources: + - type: git + url: https://github.com/FeralInteractive/gamemode + tag: "1.7" + commit: 4dc99dff76218718763a6b07fc1900fa6d1dafd9 + - name: enhance + buildsystem: simple + build-commands: + - mkdir -p /app/utils/gamescope + - install -Dm755 prime-run /app/bin/prime-run + - mv /app/bin/prismlauncher /app/bin/prismrun + - install -Dm755 prismlauncher /app/bin/prismlauncher + sources: + - type: file + path: ../flatpak/prime-run + - type: file + path: ../flatpak/prismlauncher diff --git a/flatpak/prime-run b/flatpak/prime-run new file mode 100644 index 00000000..946c28dd --- /dev/null +++ b/flatpak/prime-run @@ -0,0 +1,4 @@ +#!/bin/sh + +export __NV_PRIME_RENDER_OFFLOAD=1 __VK_LAYER_NV_optimus=NVIDIA_only __GLX_VENDOR_LIBRARY_NAME=nvidia +exec "$@" diff --git a/flatpak/prismlauncher b/flatpak/prismlauncher new file mode 100644 index 00000000..bb876711 --- /dev/null +++ b/flatpak/prismlauncher @@ -0,0 +1,11 @@ +#!/bin/bash + +# discord RPC +for i in {0..9}; do + test -S "$XDG_RUNTIME_DIR"/discord-ipc-"$i" || ln -sf {app/com.discordapp.Discord,"$XDG_RUNTIME_DIR"}/discord-ipc-"$i"; +done + +export PATH="${PATH}${PATH:+:}/app/utils/gamescope/bin:/usr/lib/extensions/vulkan/MangoHud/bin" +export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}${LD_LIBRARY_PATH:+:}/usr/lib/extensions/vulkan/MangoHud/\$LIB/" + +exec /app/bin/prismrun "$@" diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 97f757f7..883f8968 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -1,8 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-only /* - * PolyMC - Minecraft Launcher + * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Lenny McLennington <lenny@sneed.church> + * Copyright (C) 2022 Tayou <tayou@gmx.net> * * 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 @@ -54,14 +55,9 @@ #include "ui/pages/global/APIPage.h" #include "ui/pages/global/CustomCommandsPage.h" -#include "ui/themes/ITheme.h" -#include "ui/themes/SystemTheme.h" -#include "ui/themes/DarkTheme.h" -#include "ui/themes/BrightTheme.h" -#include "ui/themes/CustomTheme.h" - #ifdef Q_OS_WIN #include "ui/WinDarkmode.h" +#include <versionhelpers.h> #endif #include "ui/setupwizard/SetupWizard.h" @@ -73,6 +69,8 @@ #include "ui/pagedialog/PageDialog.h" +#include "ui/themes/ThemeManager.h" + #include "ApplicationMessage.h" #include <iostream> @@ -228,7 +226,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) setOrganizationDomain(BuildConfig.LAUNCHER_DOMAIN); setApplicationName(BuildConfig.LAUNCHER_NAME); setApplicationDisplayName(QString("%1 %2").arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.printableVersionString())); - setApplicationVersion(BuildConfig.printableVersionString()); + setApplicationVersion(BuildConfig.printableVersionString() + "\n" + BuildConfig.GIT_COMMIT); setDesktopFileName(BuildConfig.LAUNCHER_DESKTOPFILENAME); startTime = QDateTime::currentDateTime(); @@ -245,7 +243,8 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) {{"s", "server"}, "Join the specified server on launch (only valid in combination with --launch)", "address"}, {{"a", "profile"}, "Use the account specified by its profile name (only valid in combination with --launch)", "profile"}, {"alive", "Write a small '" + liveCheckFile + "' file after the launcher starts"}, - {{"I", "import"}, "Import instance from specified zip (local path or URL)", "file"} + {{"I", "import"}, "Import instance from specified zip (local path or URL)", "file"}, + {"show", "Opens the window for the specified instance (by instance ID)", "show"} }); parser.addHelpOption(); parser.addVersionOption(); @@ -257,6 +256,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_profileToUse = parser.value("profile"); m_liveCheck = parser.isSet("alive"); m_zipToImport = parser.value("import"); + m_instanceIdToShowWindowOf = parser.value("show"); // error if --launch is missing with --server or --profile if((!m_serverToJoin.isEmpty() || !m_profileToUse.isEmpty()) && m_instanceIdToLaunch.isEmpty()) @@ -498,6 +498,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) // Theming m_settings->registerSetting("IconTheme", QString("pe_colored")); m_settings->registerSetting("ApplicationTheme", QString("system")); + m_settings->registerSetting("BackgroundCat", QString("kitteh")); // Remembered state m_settings->registerSetting("LastUsedGroupForNewInstance", QString()); @@ -562,7 +563,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) // Memory m_settings->registerSetting({"MinMemAlloc", "MinMemoryAlloc"}, 512); - m_settings->registerSetting({"MaxMemAlloc", "MaxMemoryAlloc"}, 4096); + m_settings->registerSetting({"MaxMemAlloc", "MaxMemoryAlloc"}, suitableMaxMem()); m_settings->registerSetting("PermGen", 128); // Java Settings @@ -610,6 +611,8 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) // The cat m_settings->registerSetting("TheCat", false); + m_settings->registerSetting("ToolbarsLocked", false); + m_settings->registerSetting("InstSortMode", "Name"); m_settings->registerSetting("SelectedInstance", QString()); @@ -746,29 +749,8 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) qDebug() << "<> Instance icons intialized."; } - // Icon themes - { - // TODO: icon themes and instance icons do not mesh well together. Rearrange and fix discrepancies! - // set icon theme search path! - auto searchPaths = QIcon::themeSearchPaths(); - searchPaths.append("iconthemes"); - QIcon::setThemeSearchPaths(searchPaths); - qDebug() << "<> Icon themes initialized."; - } - - // Initialize widget themes - { - auto insertTheme = [this](ITheme * theme) - { - m_themes.insert(std::make_pair(theme->id(), std::unique_ptr<ITheme>(theme))); - }; - auto darkTheme = new DarkTheme(); - insertTheme(new SystemTheme()); - insertTheme(darkTheme); - insertTheme(new BrightTheme()); - insertTheme(new CustomTheme(darkTheme, "custom")); - qDebug() << "<> Widget themes initialized."; - } + // Themes + m_themeManager = std::make_unique<ThemeManager>(m_mainWindow); // initialize and load all instances { @@ -945,6 +927,12 @@ bool Application::event(QEvent* event) { m_prevAppState = ev->applicationState(); } #endif + + if (event->type() == QEvent::FileOpen) { + auto ev = static_cast<QFileOpenEvent*>(event); + m_mainWindow->droppedURLs({ ev->url() }); + } + return QApplication::event(event); } @@ -986,6 +974,16 @@ void Application::performMainStartupAction() return; } } + if(!m_instanceIdToShowWindowOf.isEmpty()) + { + auto inst = instances()->getInstanceById(m_instanceIdToShowWindowOf); + if(inst) + { + qDebug() << "<> Showing window of instance " << m_instanceIdToShowWindowOf; + showInstanceWindow(inst); + return; + } + } if(!m_mainWindow) { // normal main window @@ -1112,60 +1110,25 @@ std::shared_ptr<JavaInstallList> Application::javalist() return m_javalist; } -std::vector<ITheme *> Application::getValidApplicationThemes() +QList<ITheme*> Application::getValidApplicationThemes() { - std::vector<ITheme *> ret; - auto iter = m_themes.cbegin(); - while (iter != m_themes.cend()) - { - ret.push_back((*iter).second.get()); - iter++; - } - return ret; -} - -bool Application::isFlatpak() -{ - #ifdef Q_OS_LINUX - return QFile::exists("/.flatpak-info"); - #else - return false; - #endif + return m_themeManager->getValidApplicationThemes(); } void Application::setApplicationTheme(const QString& name, bool initial) { - auto systemPalette = qApp->palette(); - auto themeIter = m_themes.find(name); - if(themeIter != m_themes.end()) - { - auto & theme = (*themeIter).second; - theme->apply(initial); -#ifdef Q_OS_WIN - if (m_mainWindow) { - if (QString::compare(theme->id(), "dark") == 0) { - WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), true); - } else { - WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), false); - } - } -#endif - } - else - { - qWarning() << "Tried to set invalid theme:" << name; - } + m_themeManager->setApplicationTheme(name, initial); } void Application::setIconTheme(const QString& name) { - QIcon::setThemeName(name); + m_themeManager->setIconTheme(name); } QIcon Application::getThemedIcon(const QString& name) { if(name == "logo") { - return QIcon(":/org.prismlauncher.PrismLauncher.svg"); // FIXME: Make this a BuildConfig variable + return QIcon(":/" + BuildConfig.LAUNCHER_SVGFILENAME); } return QIcon::fromTheme(name); } @@ -1383,10 +1346,13 @@ MainWindow* Application::showMainWindow(bool minimized) m_mainWindow->restoreState(QByteArray::fromBase64(APPLICATION->settings()->get("MainWindowState").toByteArray())); m_mainWindow->restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("MainWindowGeometry").toByteArray())); #ifdef Q_OS_WIN - if (QString::compare(settings()->get("ApplicationTheme").toString(), "dark") == 0) { - WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), true); - } else { - WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), false); + if (IsWindows10OrGreater()) + { + if (QString::compare(settings()->get("ApplicationTheme").toString(), "dark") == 0) { + WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), true); + } else { + WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), false); + } } #endif if(minimized) @@ -1625,3 +1591,17 @@ QString Application::getUserAgentUncached() return BuildConfig.USER_AGENT_UNCACHED; } + +int Application::suitableMaxMem() +{ + float totalRAM = (float)Sys::getSystemRam() / (float)Sys::mebibyte; + int maxMemoryAlloc; + + // If totalRAM < 6GB, use (totalRAM / 1.5), else 4GB + if (totalRAM < (4096 * 1.5)) + maxMemoryAlloc = (int) (totalRAM / 1.5); + else + maxMemoryAlloc = 4096; + + return maxMemoryAlloc; +} diff --git a/launcher/Application.h b/launcher/Application.h index 34ad8c15..4c2f62d4 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -1,7 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-only /* - * PolyMC - Minecraft Launcher + * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> + * Copyright (C) 2022 Tayou <tayou@gmx.net> * * 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 @@ -68,6 +69,7 @@ class BaseDetachedToolFactory; class TranslationsModel; class ITheme; class MCEditTool; +class ThemeManager; namespace Meta { class Index; @@ -116,11 +118,9 @@ public: QIcon getThemedIcon(const QString& name); - bool isFlatpak(); - void setIconTheme(const QString& name); - std::vector<ITheme *> getValidApplicationThemes(); + QList<ITheme*> getValidApplicationThemes(); void setApplicationTheme(const QString& name, bool initial); @@ -200,6 +200,8 @@ public: void ShowGlobalSettings(class QWidget * parent, QString open_page = QString()); + int suitableMaxMem(); + signals: void updateAllowedChanged(bool status); void globalSettingsAboutToOpen(); @@ -257,9 +259,9 @@ private: std::shared_ptr<JavaInstallList> m_javalist; std::shared_ptr<TranslationsModel> m_translations; std::shared_ptr<GenericPageProvider> m_globalSettingsProvider; - std::map<QString, std::unique_ptr<ITheme>> m_themes; std::unique_ptr<MCEditTool> m_mcedit; QSet<QString> m_features; + std::unique_ptr<ThemeManager> m_themeManager; QMap<QString, std::shared_ptr<BaseProfilerFactory>> m_profilers; @@ -301,6 +303,7 @@ public: QString m_profileToUse; bool m_liveCheck = false; QUrl m_zipToImport; + QString m_instanceIdToShowWindowOf; std::unique_ptr<QFile> logFile; }; diff --git a/launcher/BaseInstaller.h b/launcher/BaseInstaller.h index b2e6a14d..a1b80e93 100644 --- a/launcher/BaseInstaller.h +++ b/launcher/BaseInstaller.h @@ -17,13 +17,14 @@ #include <memory> +#include "BaseVersion.h" + class MinecraftInstance; class QDir; class QString; class QObject; class Task; class BaseVersion; -typedef std::shared_ptr<BaseVersion> BaseVersionPtr; class BaseInstaller { @@ -35,7 +36,7 @@ public: virtual bool add(MinecraftInstance *to); virtual bool remove(MinecraftInstance *from); - virtual Task *createInstallTask(MinecraftInstance *instance, BaseVersionPtr version, QObject *parent) = 0; + virtual Task *createInstallTask(MinecraftInstance *instance, BaseVersion::Ptr version, QObject *parent) = 0; protected: virtual QString id() const = 0; diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index 307240e0..a2a4f824 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -151,7 +151,7 @@ public: void copyManagedPack(BaseInstance& other); /// guess log level from a line of game log - virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level) + virtual MessageLevel::Enum guessLevel([[maybe_unused]] const QString &line, MessageLevel::Enum level) { return level; }; diff --git a/launcher/BaseVersion.h b/launcher/BaseVersion.h index b88105fb..ca0e4502 100644 --- a/launcher/BaseVersion.h +++ b/launcher/BaseVersion.h @@ -25,6 +25,7 @@ class BaseVersion { public: + using Ptr = std::shared_ptr<BaseVersion>; virtual ~BaseVersion() {} /*! * A string used to identify this version in config files. @@ -54,6 +55,4 @@ public: }; }; -typedef std::shared_ptr<BaseVersion> BaseVersionPtr; - -Q_DECLARE_METATYPE(BaseVersionPtr) +Q_DECLARE_METATYPE(BaseVersion::Ptr) diff --git a/launcher/BaseVersionList.cpp b/launcher/BaseVersionList.cpp index b4a7d6dd..dc95e7ea 100644 --- a/launcher/BaseVersionList.cpp +++ b/launcher/BaseVersionList.cpp @@ -40,20 +40,20 @@ BaseVersionList::BaseVersionList(QObject *parent) : QAbstractListModel(parent) { } -BaseVersionPtr BaseVersionList::findVersion(const QString &descriptor) +BaseVersion::Ptr BaseVersionList::findVersion(const QString &descriptor) { for (int i = 0; i < count(); i++) { if (at(i)->descriptor() == descriptor) return at(i); } - return BaseVersionPtr(); + return nullptr; } -BaseVersionPtr BaseVersionList::getRecommended() const +BaseVersion::Ptr BaseVersionList::getRecommended() const { if (count() <= 0) - return BaseVersionPtr(); + return nullptr; else return at(0); } @@ -66,7 +66,7 @@ QVariant BaseVersionList::data(const QModelIndex &index, int role) const if (index.row() > count()) return QVariant(); - BaseVersionPtr version = at(index.row()); + BaseVersion::Ptr version = at(index.row()); switch (role) { @@ -95,12 +95,12 @@ BaseVersionList::RoleList BaseVersionList::providesRoles() const int BaseVersionList::rowCount(const QModelIndex &parent) const { // Return count - return count(); + return parent.isValid() ? 0 : count(); } int BaseVersionList::columnCount(const QModelIndex &parent) const { - return 1; + return parent.isValid() ? 0 : 1; } QHash<int, QByteArray> BaseVersionList::roleNames() const diff --git a/launcher/BaseVersionList.h b/launcher/BaseVersionList.h index 80a91e8f..31f29022 100644 --- a/launcher/BaseVersionList.h +++ b/launcher/BaseVersionList.h @@ -70,7 +70,7 @@ public: virtual bool isLoaded() = 0; //! Gets the version at the given index. - virtual const BaseVersionPtr at(int i) const = 0; + virtual const BaseVersion::Ptr at(int i) const = 0; //! Returns the number of versions in the list. virtual int count() const = 0; @@ -90,13 +90,13 @@ public: * \return A const pointer to the version with the given descriptor. NULL if * one doesn't exist. */ - virtual BaseVersionPtr findVersion(const QString &descriptor); + virtual BaseVersion::Ptr findVersion(const QString &descriptor); /*! * \brief Gets the recommended version from this list * If the list doesn't support recommended versions, this works exactly as getLatestStable */ - virtual BaseVersionPtr getRecommended() const; + virtual BaseVersion::Ptr getRecommended() const; /*! * Sorts the version list. @@ -117,5 +117,5 @@ slots: * then copies the versions and sets their parents correctly. * \param versions List of versions whose parents should be set. */ - virtual void updateListData(QList<BaseVersionPtr> versions) = 0; + virtual void updateListData(QList<BaseVersion::Ptr> versions) = 0; }; diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 79ac49c7..8db93429 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -24,13 +24,15 @@ set(CORE_SOURCES NullInstance.h MMCZip.h MMCZip.cpp - MMCStrings.h - MMCStrings.cpp + StringUtils.h + StringUtils.cpp RuntimeContext.h # Basic instance manipulation tasks (derived from InstanceTask) InstanceCreationTask.h InstanceCreationTask.cpp + InstanceCopyPrefs.h + InstanceCopyPrefs.cpp InstanceCopyTask.h InstanceCopyTask.cpp InstanceImportTask.h @@ -539,9 +541,6 @@ set(ATLAUNCHER_SOURCES ################################ COMPILE ################################ -# we need zlib -find_package(ZLIB REQUIRED) - set(LOGIC_SOURCES ${CORE_SOURCES} ${PATHMATCHER_SOURCES} @@ -602,6 +601,7 @@ SET(LAUNCHER_SOURCES resources/OSX/OSX.qrc resources/iOS/iOS.qrc resources/flat/flat.qrc + resources/flat_white/flat_white.qrc resources/documents/documents.qrc ../${Launcher_Branding_LogoQRC} @@ -649,6 +649,8 @@ SET(LAUNCHER_SOURCES ui/themes/ITheme.h ui/themes/SystemTheme.cpp ui/themes/SystemTheme.h + ui/themes/ThemeManager.cpp + ui/themes/ThemeManager.h # Processes LaunchController.h @@ -1061,96 +1063,95 @@ if(INSTALL_BUNDLE STREQUAL "full") COMPONENT Runtime ) # Bundle plugins - if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") - # Image formats - install( - DIRECTORY "${QT_PLUGINS_DIR}/imageformats" - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - REGEX "tga|tiff|mng" EXCLUDE - ) - # Icon engines - install( - DIRECTORY "${QT_PLUGINS_DIR}/iconengines" - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - REGEX "fontawesome" EXCLUDE - ) - # Platform plugins + # Image formats + install( + DIRECTORY "${QT_PLUGINS_DIR}/imageformats" + CONFIGURATIONS Debug RelWithDebInfo "" + DESTINATION ${PLUGIN_DEST_DIR} + COMPONENT Runtime + REGEX "tga|tiff|mng" EXCLUDE + ) + install( + DIRECTORY "${QT_PLUGINS_DIR}/imageformats" + CONFIGURATIONS Release MinSizeRel + DESTINATION ${PLUGIN_DEST_DIR} + COMPONENT Runtime + REGEX "tga|tiff|mng" EXCLUDE + REGEX "d\\." EXCLUDE + REGEX "_debug\\." EXCLUDE + REGEX "\\.dSYM" EXCLUDE + ) + # Icon engines + install( + DIRECTORY "${QT_PLUGINS_DIR}/iconengines" + CONFIGURATIONS Debug RelWithDebInfo "" + DESTINATION ${PLUGIN_DEST_DIR} + COMPONENT Runtime + REGEX "fontawesome" EXCLUDE + ) + install( + DIRECTORY "${QT_PLUGINS_DIR}/iconengines" + CONFIGURATIONS Release MinSizeRel + DESTINATION ${PLUGIN_DEST_DIR} + COMPONENT Runtime + REGEX "fontawesome" EXCLUDE + REGEX "d\\." EXCLUDE + REGEX "_debug\\." EXCLUDE + REGEX "\\.dSYM" EXCLUDE + ) + # Platform plugins + install( + DIRECTORY "${QT_PLUGINS_DIR}/platforms" + CONFIGURATIONS Debug RelWithDebInfo "" + DESTINATION ${PLUGIN_DEST_DIR} + COMPONENT Runtime + REGEX "minimal|linuxfb|offscreen" EXCLUDE + ) + install( + DIRECTORY "${QT_PLUGINS_DIR}/platforms" + CONFIGURATIONS Release MinSizeRel + DESTINATION ${PLUGIN_DEST_DIR} + COMPONENT Runtime + REGEX "minimal|linuxfb|offscreen" EXCLUDE + REGEX "[^2]d\\." EXCLUDE + REGEX "_debug\\." EXCLUDE + REGEX "\\.dSYM" EXCLUDE + ) + # Style plugins + if(EXISTS "${QT_PLUGINS_DIR}/styles") install( - DIRECTORY "${QT_PLUGINS_DIR}/platforms" + DIRECTORY "${QT_PLUGINS_DIR}/styles" + CONFIGURATIONS Debug RelWithDebInfo "" DESTINATION ${PLUGIN_DEST_DIR} COMPONENT Runtime - REGEX "minimal|linuxfb|offscreen" EXCLUDE ) - # Style plugins - if(EXISTS "${QT_PLUGINS_DIR}/styles") - install( - DIRECTORY "${QT_PLUGINS_DIR}/styles" - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - ) - endif() - # TLS plugins (Qt 6 only) - if(EXISTS "${QT_PLUGINS_DIR}/tls") - install( - DIRECTORY "${QT_PLUGINS_DIR}/tls" - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - ) - endif() - else() - # Image formats install( - DIRECTORY "${QT_PLUGINS_DIR}/imageformats" + DIRECTORY "${QT_PLUGINS_DIR}/styles" + CONFIGURATIONS Release MinSizeRel DESTINATION ${PLUGIN_DEST_DIR} COMPONENT Runtime - REGEX "tga|tiff|mng" EXCLUDE REGEX "d\\." EXCLUDE REGEX "_debug\\." EXCLUDE REGEX "\\.dSYM" EXCLUDE ) - # Icon engines + endif() + # TLS plugins (Qt 6 only) + if(EXISTS "${QT_PLUGINS_DIR}/tls") install( - DIRECTORY "${QT_PLUGINS_DIR}/iconengines" + DIRECTORY "${QT_PLUGINS_DIR}/tls" + CONFIGURATIONS Debug RelWithDebInfo "" DESTINATION ${PLUGIN_DEST_DIR} COMPONENT Runtime - REGEX "fontawesome" EXCLUDE - REGEX "d\\." EXCLUDE - REGEX "_debug\\." EXCLUDE - REGEX "\\.dSYM" EXCLUDE ) - # Platform plugins install( - DIRECTORY "${QT_PLUGINS_DIR}/platforms" + DIRECTORY "${QT_PLUGINS_DIR}/tls" + CONFIGURATIONS Release MinSizeRel DESTINATION ${PLUGIN_DEST_DIR} COMPONENT Runtime - REGEX "minimal|linuxfb|offscreen" EXCLUDE - REGEX "d\\." EXCLUDE + REGEX "dd\\." EXCLUDE REGEX "_debug\\." EXCLUDE REGEX "\\.dSYM" EXCLUDE ) - # Style plugins - if(EXISTS "${QT_PLUGINS_DIR}/styles") - install( - DIRECTORY "${QT_PLUGINS_DIR}/styles" - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - REGEX "d\\." EXCLUDE - REGEX "_debug\\." EXCLUDE - REGEX "\\.dSYM" EXCLUDE - ) - endif() - # TLS plugins (Qt 6 only) - if(EXISTS "${QT_PLUGINS_DIR}/tls") - install( - DIRECTORY "${QT_PLUGINS_DIR}/tls" - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - REGEX "_debug\\." EXCLUDE - REGEX "\\.dSYM" EXCLUDE - ) - endif() endif() configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/install_prereqs.cmake.in" diff --git a/launcher/DesktopServices.cpp b/launcher/DesktopServices.cpp index c29cbe7d..302eaf96 100644 --- a/launcher/DesktopServices.cpp +++ b/launcher/DesktopServices.cpp @@ -119,7 +119,7 @@ bool openDirectory(const QString &path, bool ensureExists) return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath())); }; #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) - if(!APPLICATION->isFlatpak()) + if(!isFlatpak()) { return IndirectOpen(f); } @@ -140,7 +140,7 @@ bool openFile(const QString &path) return QDesktopServices::openUrl(QUrl::fromLocalFile(path)); }; #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) - if(!APPLICATION->isFlatpak()) + if(!isFlatpak()) { return IndirectOpen(f); } @@ -158,7 +158,7 @@ bool openFile(const QString &application, const QString &path, const QString &wo qDebug() << "Opening file" << path << "using" << application; #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) // FIXME: the pid here is fake. So if something depends on it, it will likely misbehave - if(!APPLICATION->isFlatpak()) + if(!isFlatpak()) { return IndirectOpen([&]() { @@ -178,7 +178,7 @@ bool run(const QString &application, const QStringList &args, const QString &wor { qDebug() << "Running" << application << "with args" << args.join(' '); #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) - if(!APPLICATION->isFlatpak()) + if(!isFlatpak()) { // FIXME: the pid here is fake. So if something depends on it, it will likely misbehave return IndirectOpen([&]() @@ -203,7 +203,7 @@ bool openUrl(const QUrl &url) return QDesktopServices::openUrl(url); }; #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) - if(!APPLICATION->isFlatpak()) + if(!isFlatpak()) { return IndirectOpen(f); } @@ -216,4 +216,13 @@ bool openUrl(const QUrl &url) #endif } +bool isFlatpak() +{ +#ifdef Q_OS_LINUX + return QFile::exists("/.flatpak-info"); +#else + return false; +#endif +} + } diff --git a/launcher/DesktopServices.h b/launcher/DesktopServices.h index 1c081da4..21c9cae0 100644 --- a/launcher/DesktopServices.h +++ b/launcher/DesktopServices.h @@ -33,4 +33,6 @@ namespace DesktopServices * Open the URL, most likely in a browser. Maybe. */ bool openUrl(const QUrl &url); + + bool isFlatpak(); } diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 39e68c20..4a8f4bd3 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -45,6 +45,9 @@ #include <QTextStream> #include <QUrl> +#include "DesktopServices.h" +#include "StringUtils.h" + #if defined Q_OS_WIN32 #include <objbase.h> #include <objidl.h> @@ -78,22 +81,6 @@ namespace fs = std::filesystem; namespace fs = ghc::filesystem; #endif -#if defined Q_OS_WIN32 - -std::wstring toStdString(QString s) -{ - return s.toStdWString(); -} - -#else - -std::string toStdString(QString s) -{ - return s.toStdString(); -} - -#endif - namespace FS { void ensureExists(const QDir& dir) @@ -162,6 +149,9 @@ bool ensureFolderPathExists(QString foldernamepath) return success; } +/// @brief Copies a directory and it's contents from src to dest +/// @param offset subdirectory form src to copy to dest +/// @return if there was an error during the filecopy bool copy::operator()(const QString& offset) { using copy_opts = fs::copy_options; @@ -182,6 +172,21 @@ bool copy::operator()(const QString& offset) if (!m_followSymlinks) opt |= copy_opts::copy_symlinks; + // Function that'll do the actual copying + auto copy_file = [&](QString src_path, QString relative_dst_path) { + if (m_blacklist && m_blacklist->matches(relative_dst_path)) + return; + + auto dst_path = PathCombine(dst, relative_dst_path); + ensureFilePathExists(dst_path); + + fs::copy(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), opt, err); + if (err) { + qWarning() << "Failed to copy files:" << QString::fromStdString(err.message()); + qDebug() << "Source file:" << src_path; + qDebug() << "Destination file:" << dst_path; + } + }; // We can't use copy_opts::recursive because we need to take into account the // blacklisted paths, so we iterate over the source directory, and if there's no blacklist @@ -193,20 +198,13 @@ bool copy::operator()(const QString& offset) auto src_path = source_it.next(); auto relative_path = src_dir.relativeFilePath(src_path); - if (m_blacklist && m_blacklist->matches(relative_path)) - continue; - - auto dst_path = PathCombine(dst, relative_path); - ensureFilePathExists(dst_path); - - fs::copy(toStdString(src_path), toStdString(dst_path), opt, err); - if (err) { - qWarning() << "Failed to copy files:" << QString::fromStdString(err.message()); - qDebug() << "Source file:" << src_path; - qDebug() << "Destination file:" << dst_path; - } + copy_file(src_path, relative_path); } + // If the root src is not a directory, the previous iterator won't run. + if (!fs::is_directory(StringUtils::toStdString(src))) + copy_file(src, ""); + return err.value() == 0; } @@ -214,7 +212,7 @@ bool deletePath(QString path) { std::error_code err; - fs::remove_all(toStdString(path), err); + fs::remove_all(StringUtils::toStdString(path), err); if (err) { qWarning() << "Failed to remove files:" << QString::fromStdString(err.message()); @@ -228,6 +226,9 @@ bool trash(QString path, QString *pathInTrash = nullptr) #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) return false; #else + // FIXME: Figure out trash in Flatpak. Qt seemingly doesn't use the Trash portal + if (DesktopServices::isFlatpak()) + return false; return QFile::moveToTrash(path, pathInTrash); #endif } @@ -401,7 +402,8 @@ bool overrideFolder(QString overwritten_path, QString override_path) std::error_code err; fs::copy_options opt = copy_opts::recursive | copy_opts::overwrite_existing; - fs::copy(toStdString(override_path), toStdString(overwritten_path), opt, err); + // FIXME: hello traveller! Apparently std::copy does NOT overwrite existing files on GNU libstdc++ on Windows? + fs::copy(StringUtils::toStdString(override_path), StringUtils::toStdString(overwritten_path), opt, err); if (err) { qCritical() << QString("Failed to apply override from %1 to %2").arg(override_path, overwritten_path); diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index b46f3281..b7e175fd 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -75,6 +75,7 @@ bool ensureFilePathExists(QString filenamepath); */ bool ensureFolderPathExists(QString filenamepath); +/// @brief Copies a directory and it's contents from src to dest class copy { public: copy(const QString& src, const QString& dst) diff --git a/launcher/InstanceCopyPrefs.cpp b/launcher/InstanceCopyPrefs.cpp new file mode 100644 index 00000000..7b93a516 --- /dev/null +++ b/launcher/InstanceCopyPrefs.cpp @@ -0,0 +1,135 @@ +// +// Created by marcelohdez on 10/22/22. +// + +#include "InstanceCopyPrefs.h" + +bool InstanceCopyPrefs::allTrue() const +{ + return copySaves && + keepPlaytime && + copyGameOptions && + copyResourcePacks && + copyShaderPacks && + copyServers && + copyMods && + copyScreenshots; +} + +// Returns a single RegEx string of the selected folders/files to filter out (ex: ".minecraft/saves|.minecraft/server.dat") +QString InstanceCopyPrefs::getSelectedFiltersAsRegex() const +{ + QStringList filters; + + if(!copySaves) + filters << "saves"; + + if(!copyGameOptions) + filters << "options.txt"; + + if(!copyResourcePacks) + filters << "resourcepacks" << "texturepacks"; + + if(!copyShaderPacks) + filters << "shaderpacks"; + + if(!copyServers) + filters << "servers.dat" << "servers.dat_old" << "server-resource-packs"; + + if(!copyMods) + filters << "coremods" << "mods" << "config"; + + if(!copyScreenshots) + filters << "screenshots"; + + // If we have any filters to add, join them as a single regex string to return: + if (!filters.isEmpty()) { + const QString MC_ROOT = "[.]?minecraft/"; + // Ensure first filter starts with root, then join other filters with OR regex before root (ex: ".minecraft/saves|.minecraft/mods"): + return MC_ROOT + filters.join("|" + MC_ROOT); + } + + return {}; +} + +// ======= Getters ======= +bool InstanceCopyPrefs::isCopySavesEnabled() const +{ + return copySaves; +} + +bool InstanceCopyPrefs::isKeepPlaytimeEnabled() const +{ + return keepPlaytime; +} + +bool InstanceCopyPrefs::isCopyGameOptionsEnabled() const +{ + return copyGameOptions; +} + +bool InstanceCopyPrefs::isCopyResourcePacksEnabled() const +{ + return copyResourcePacks; +} + +bool InstanceCopyPrefs::isCopyShaderPacksEnabled() const +{ + return copyShaderPacks; +} + +bool InstanceCopyPrefs::isCopyServersEnabled() const +{ + return copyServers; +} + +bool InstanceCopyPrefs::isCopyModsEnabled() const +{ + return copyMods; +} + +bool InstanceCopyPrefs::isCopyScreenshotsEnabled() const +{ + return copyScreenshots; +} + +// ======= Setters ======= +void InstanceCopyPrefs::enableCopySaves(bool b) +{ + copySaves = b; +} + +void InstanceCopyPrefs::enableKeepPlaytime(bool b) +{ + keepPlaytime = b; +} + +void InstanceCopyPrefs::enableCopyGameOptions(bool b) +{ + copyGameOptions = b; +} + +void InstanceCopyPrefs::enableCopyResourcePacks(bool b) +{ + copyResourcePacks = b; +} + +void InstanceCopyPrefs::enableCopyShaderPacks(bool b) +{ + copyShaderPacks = b; +} + +void InstanceCopyPrefs::enableCopyServers(bool b) +{ + copyServers = b; +} + +void InstanceCopyPrefs::enableCopyMods(bool b) +{ + copyMods = b; +} + +void InstanceCopyPrefs::enableCopyScreenshots(bool b) +{ + copyScreenshots = b; +} diff --git a/launcher/InstanceCopyPrefs.h b/launcher/InstanceCopyPrefs.h new file mode 100644 index 00000000..6988b2df --- /dev/null +++ b/launcher/InstanceCopyPrefs.h @@ -0,0 +1,41 @@ +// +// Created by marcelohdez on 10/22/22. +// + +#pragma once + +#include <QStringList> + +struct InstanceCopyPrefs { + public: + [[nodiscard]] bool allTrue() const; + [[nodiscard]] QString getSelectedFiltersAsRegex() const; + // Getters + [[nodiscard]] bool isCopySavesEnabled() const; + [[nodiscard]] bool isKeepPlaytimeEnabled() const; + [[nodiscard]] bool isCopyGameOptionsEnabled() const; + [[nodiscard]] bool isCopyResourcePacksEnabled() const; + [[nodiscard]] bool isCopyShaderPacksEnabled() const; + [[nodiscard]] bool isCopyServersEnabled() const; + [[nodiscard]] bool isCopyModsEnabled() const; + [[nodiscard]] bool isCopyScreenshotsEnabled() const; + // Setters + void enableCopySaves(bool b); + void enableKeepPlaytime(bool b); + void enableCopyGameOptions(bool b); + void enableCopyResourcePacks(bool b); + void enableCopyShaderPacks(bool b); + void enableCopyServers(bool b); + void enableCopyMods(bool b); + void enableCopyScreenshots(bool b); + + protected: // data + bool copySaves = true; + bool keepPlaytime = true; + bool copyGameOptions = true; + bool copyResourcePacks = true; + bool copyShaderPacks = true; + bool copyServers = true; + bool copyMods = true; + bool copyScreenshots = true; +}; diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index b1e33884..a4ea947d 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -5,15 +5,17 @@ #include "pathmatcher/RegexpMatcher.h" #include <QtConcurrentRun> -InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, bool copySaves, bool keepPlaytime) +InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyPrefs& prefs) { m_origInstance = origInstance; - m_keepPlaytime = keepPlaytime; + m_keepPlaytime = prefs.isKeepPlaytimeEnabled(); - if(!copySaves) + QString filters = prefs.getSelectedFiltersAsRegex(); + if (!filters.isEmpty()) { + // Set regex filter: // FIXME: get this from the original instance type... - auto matcherReal = new RegexpMatcher("[.]?minecraft/saves"); + auto matcherReal = new RegexpMatcher(filters); matcherReal->caseSensitive(false); m_matcher.reset(matcherReal); } diff --git a/launcher/InstanceCopyTask.h b/launcher/InstanceCopyTask.h index 82901732..1f29b854 100644 --- a/launcher/InstanceCopyTask.h +++ b/launcher/InstanceCopyTask.h @@ -1,20 +1,21 @@ #pragma once -#include "tasks/Task.h" -#include "net/NetJob.h" -#include <QUrl> #include <QFuture> #include <QFutureWatcher> -#include "settings/SettingsObject.h" -#include "BaseVersion.h" +#include <QUrl> #include "BaseInstance.h" +#include "BaseVersion.h" +#include "InstanceCopyPrefs.h" #include "InstanceTask.h" +#include "net/NetJob.h" +#include "settings/SettingsObject.h" +#include "tasks/Task.h" class InstanceCopyTask : public InstanceTask { Q_OBJECT public: - explicit InstanceCopyTask(InstancePtr origInstance, bool copySaves, bool keepPlaytime); + explicit InstanceCopyTask(InstancePtr origInstance, const InstanceCopyPrefs& prefs); protected: //! Entry point for tasks. @@ -22,7 +23,8 @@ protected: void copyFinished(); void copyAborted(); -private: /* data */ +private: + /* data */ InstancePtr m_origInstance; QFuture<bool> m_copyFuture; QFutureWatcher<bool> m_copyFutureWatcher; diff --git a/launcher/JavaCommon.cpp b/launcher/JavaCommon.cpp index aa4d1123..52cc868a 100644 --- a/launcher/JavaCommon.cpp +++ b/launcher/JavaCommon.cpp @@ -36,7 +36,7 @@ #include "JavaCommon.h" #include "java/JavaUtils.h" #include "ui/dialogs/CustomMessageBox.h" -#include <MMCStrings.h> + #include <QRegularExpression> bool JavaCommon::checkJVMArgs(QString jvmargs, QWidget *parent) diff --git a/launcher/MMCStrings.h b/launcher/MMCStrings.h deleted file mode 100644 index 48052a00..00000000 --- a/launcher/MMCStrings.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -#include <QString> - -namespace Strings -{ - int naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs); -} diff --git a/launcher/MMCStrings.cpp b/launcher/StringUtils.cpp index dc91c8d6..0f3c3669 100644 --- a/launcher/MMCStrings.cpp +++ b/launcher/StringUtils.cpp @@ -1,26 +1,28 @@ -#include "MMCStrings.h" +#include "StringUtils.h" + +/// If you're wondering where these came from exactly, then know you're not the only one =D /// TAKEN FROM Qt, because it doesn't expose it intelligently -static inline QChar getNextChar(const QString &s, int location) +static inline QChar getNextChar(const QString& s, int location) { return (location < s.length()) ? s.at(location) : QChar(); } /// TAKEN FROM Qt, because it doesn't expose it intelligently -int Strings::naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs) +int StringUtils::naturalCompare(const QString& s1, const QString& s2, Qt::CaseSensitivity cs) { - for (int l1 = 0, l2 = 0; l1 <= s1.count() && l2 <= s2.count(); ++l1, ++l2) - { + int l1 = 0, l2 = 0; + while (l1 <= s1.count() && l2 <= s2.count()) { // skip spaces, tabs and 0's QChar c1 = getNextChar(s1, l1); while (c1.isSpace()) c1 = getNextChar(s1, ++l1); + QChar c2 = getNextChar(s2, l2); while (c2.isSpace()) c2 = getNextChar(s2, ++l2); - if (c1.isDigit() && c2.isDigit()) - { + if (c1.isDigit() && c2.isDigit()) { while (c1.digitValue() == 0) c1 = getNextChar(s1, ++l1); while (c2.digitValue() == 0) @@ -30,11 +32,8 @@ int Strings::naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensit int lookAheadLocation2 = l2; int currentReturnValue = 0; // find the last digit, setting currentReturnValue as we go if it isn't equal - for (QChar lookAhead1 = c1, lookAhead2 = c2; - (lookAheadLocation1 <= s1.length() && lookAheadLocation2 <= s2.length()); - lookAhead1 = getNextChar(s1, ++lookAheadLocation1), - lookAhead2 = getNextChar(s2, ++lookAheadLocation2)) - { + for (QChar lookAhead1 = c1, lookAhead2 = c2; (lookAheadLocation1 <= s1.length() && lookAheadLocation2 <= s2.length()); + lookAhead1 = getNextChar(s1, ++lookAheadLocation1), lookAhead2 = getNextChar(s2, ++lookAheadLocation2)) { bool is1ADigit = !lookAhead1.isNull() && lookAhead1.isDigit(); bool is2ADigit = !lookAhead2.isNull() && lookAhead2.isDigit(); if (!is1ADigit && !is2ADigit) @@ -43,14 +42,10 @@ int Strings::naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensit return -1; if (!is2ADigit) return 1; - if (currentReturnValue == 0) - { - if (lookAhead1 < lookAhead2) - { + if (currentReturnValue == 0) { + if (lookAhead1 < lookAhead2) { currentReturnValue = -1; - } - else if (lookAhead1 > lookAhead2) - { + } else if (lookAhead1 > lookAhead2) { currentReturnValue = 1; } } @@ -58,19 +53,24 @@ int Strings::naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensit if (currentReturnValue != 0) return currentReturnValue; } - if (cs == Qt::CaseInsensitive) - { + + if (cs == Qt::CaseInsensitive) { if (!c1.isLower()) c1 = c1.toLower(); if (!c2.isLower()) c2 = c2.toLower(); } + int r = QString::localeAwareCompare(c1, c2); if (r < 0) return -1; if (r > 0) return 1; + + l1 += 1; + l2 += 1; } + // The two strings are the same (02 == 2) so fall back to the normal sort return QString::compare(s1, s2, cs); } diff --git a/launcher/StringUtils.h b/launcher/StringUtils.h new file mode 100644 index 00000000..1799605b --- /dev/null +++ b/launcher/StringUtils.h @@ -0,0 +1,32 @@ +#pragma once + +#include <QString> + +namespace StringUtils { + +#if defined Q_OS_WIN32 +using string = std::wstring; + +inline string toStdString(QString s) +{ + return s.toStdWString(); +} +inline QString fromStdString(string s) +{ + return QString::fromStdWString(s); +} +#else +using string = std::string; + +inline string toStdString(QString s) +{ + return s.toStdString(); +} +inline QString fromStdString(string s) +{ + return QString::fromStdString(s); +} +#endif + +int naturalCompare(const QString& s1, const QString& s2, Qt::CaseSensitivity cs); +} // namespace StringUtils diff --git a/launcher/VersionProxyModel.cpp b/launcher/VersionProxyModel.cpp index 032f21f9..6aba268d 100644 --- a/launcher/VersionProxyModel.cpp +++ b/launcher/VersionProxyModel.cpp @@ -311,14 +311,14 @@ QModelIndex VersionProxyModel::index(int row, int column, const QModelIndex &par int VersionProxyModel::columnCount(const QModelIndex &parent) const { - return m_columns.size(); + return parent.isValid() ? 0 : m_columns.size(); } int VersionProxyModel::rowCount(const QModelIndex &parent) const { if(sourceModel()) { - return sourceModel()->rowCount(); + return sourceModel()->rowCount(parent); } return 0; } diff --git a/launcher/icons/IconList.cpp b/launcher/icons/IconList.cpp index 3a223d1b..01043ad2 100644 --- a/launcher/icons/IconList.cpp +++ b/launcher/icons/IconList.cpp @@ -242,7 +242,7 @@ Qt::DropActions IconList::supportedDropActions() const return Qt::CopyAction; } -bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) +bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, [[maybe_unused]] int row, [[maybe_unused]] int column, [[maybe_unused]] const QModelIndex &parent) { if (action == Qt::IgnoreAction) return true; @@ -302,7 +302,7 @@ QVariant IconList::data(const QModelIndex &index, int role) const int IconList::rowCount(const QModelIndex &parent) const { - return icons.size(); + return parent.isValid() ? 0 : icons.size(); } void IconList::installIcons(const QStringList &iconFiles) diff --git a/launcher/java/JavaInstall.cpp b/launcher/java/JavaInstall.cpp index 5bcf7bcb..d5932bcb 100644 --- a/launcher/java/JavaInstall.cpp +++ b/launcher/java/JavaInstall.cpp @@ -1,9 +1,10 @@ #include "JavaInstall.h" -#include <MMCStrings.h> + +#include "StringUtils.h" bool JavaInstall::operator<(const JavaInstall &rhs) { - auto archCompare = Strings::naturalCompare(arch, rhs.arch, Qt::CaseInsensitive); + auto archCompare = StringUtils::naturalCompare(arch, rhs.arch, Qt::CaseInsensitive); if(archCompare != 0) return archCompare < 0; if(id < rhs.id) @@ -14,7 +15,7 @@ bool JavaInstall::operator<(const JavaInstall &rhs) { return false; } - return Strings::naturalCompare(path, rhs.path, Qt::CaseInsensitive) < 0; + return StringUtils::naturalCompare(path, rhs.path, Qt::CaseInsensitive) < 0; } bool JavaInstall::operator==(const JavaInstall &rhs) diff --git a/launcher/java/JavaInstallList.cpp b/launcher/java/JavaInstallList.cpp index 0249bd22..e2f0aa00 100644 --- a/launcher/java/JavaInstallList.cpp +++ b/launcher/java/JavaInstallList.cpp @@ -41,7 +41,6 @@ #include "java/JavaInstallList.h" #include "java/JavaCheckerJob.h" #include "java/JavaUtils.h" -#include "MMCStrings.h" #include "minecraft/VersionFilterData.h" JavaInstallList::JavaInstallList(QObject *parent) : BaseVersionList(parent) @@ -73,7 +72,7 @@ void JavaInstallList::load() } } -const BaseVersionPtr JavaInstallList::at(int i) const +const BaseVersion::Ptr JavaInstallList::at(int i) const { return m_vlist.at(i); } @@ -122,7 +121,7 @@ BaseVersionList::RoleList JavaInstallList::providesRoles() const } -void JavaInstallList::updateListData(QList<BaseVersionPtr> versions) +void JavaInstallList::updateListData(QList<BaseVersion::Ptr> versions) { beginResetModel(); m_vlist = versions; @@ -137,7 +136,7 @@ void JavaInstallList::updateListData(QList<BaseVersionPtr> versions) m_loadTask.reset(); } -bool sortJavas(BaseVersionPtr left, BaseVersionPtr right) +bool sortJavas(BaseVersion::Ptr left, BaseVersion::Ptr right) { auto rleft = std::dynamic_pointer_cast<JavaInstall>(right); auto rright = std::dynamic_pointer_cast<JavaInstall>(left); @@ -210,11 +209,11 @@ void JavaListLoadTask::javaCheckerFinished() } } - QList<BaseVersionPtr> javas_bvp; + QList<BaseVersion::Ptr> javas_bvp; for (auto java : candidates) { //qDebug() << java->id << java->arch << " at " << java->path; - BaseVersionPtr bp_java = std::dynamic_pointer_cast<BaseVersion>(java); + BaseVersion::Ptr bp_java = std::dynamic_pointer_cast<BaseVersion>(java); if (bp_java) { diff --git a/launcher/java/JavaInstallList.h b/launcher/java/JavaInstallList.h index 3c237edf..733dc7e1 100644 --- a/launcher/java/JavaInstallList.h +++ b/launcher/java/JavaInstallList.h @@ -42,7 +42,7 @@ public: Task::Ptr getLoadTask() override; bool isLoaded() override; - const BaseVersionPtr at(int i) const override; + const BaseVersion::Ptr at(int i) const override; int count() const override; void sortVersions() override; @@ -50,7 +50,7 @@ public: RoleList providesRoles() const override; public slots: - void updateListData(QList<BaseVersionPtr> versions) override; + void updateListData(QList<BaseVersion::Ptr> versions) override; protected: void load(); @@ -59,7 +59,7 @@ protected: protected: Status m_status = Status::NotDone; shared_qobject_ptr<JavaListLoadTask> m_loadTask; - QList<BaseVersionPtr> m_vlist; + QList<BaseVersion::Ptr> m_vlist; }; class JavaListLoadTask : public Task diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp index 6c0c60cd..5efbc7a8 100644 --- a/launcher/java/JavaUtils.cpp +++ b/launcher/java/JavaUtils.cpp @@ -439,19 +439,28 @@ QList<QString> JavaUtils::FindJavaPaths() javas.append(FS::PathCombine(prefix, "bin/java")); } }; + // java installed in a snap is installed in the standard directory, but underneath $SNAP + auto snap = qEnvironmentVariable("SNAP"); + auto scanJavaDirs = [&](const QString & dirPath) + { + scanJavaDir(dirPath); + if (!snap.isNull()) { + scanJavaDir(snap + dirPath); + } + }; // oracle RPMs - scanJavaDir("/usr/java"); + scanJavaDirs("/usr/java"); // general locations used by distro packaging - scanJavaDir("/usr/lib/jvm"); - scanJavaDir("/usr/lib64/jvm"); - scanJavaDir("/usr/lib32/jvm"); + scanJavaDirs("/usr/lib/jvm"); + scanJavaDirs("/usr/lib64/jvm"); + scanJavaDirs("/usr/lib32/jvm"); // javas stored in Prism Launcher's folder - scanJavaDir("java"); + scanJavaDirs("java"); // manually installed JDKs in /opt - scanJavaDir("/opt/jdk"); - scanJavaDir("/opt/jdks"); + scanJavaDirs("/opt/jdk"); + scanJavaDirs("/opt/jdks"); // flatpak - scanJavaDir("/app/jdk"); + scanJavaDirs("/app/jdk"); javas = addJavasFromEnv(javas); javas.removeDuplicates(); return javas; diff --git a/launcher/java/JavaVersion.cpp b/launcher/java/JavaVersion.cpp index 179ccd8d..0e4fc1d3 100644 --- a/launcher/java/JavaVersion.cpp +++ b/launcher/java/JavaVersion.cpp @@ -1,5 +1,6 @@ #include "JavaVersion.h" -#include <MMCStrings.h> + +#include "StringUtils.h" #include <QRegularExpression> #include <QString> @@ -98,12 +99,12 @@ bool JavaVersion::operator<(const JavaVersion &rhs) else if(thisPre && rhsPre) { // both are prereleases - use natural compare... - return Strings::naturalCompare(m_prerelease, rhs.m_prerelease, Qt::CaseSensitive) < 0; + return StringUtils::naturalCompare(m_prerelease, rhs.m_prerelease, Qt::CaseSensitive) < 0; } // neither is prerelease, so they are the same -> this cannot be less than rhs return false; } - else return Strings::naturalCompare(m_string, rhs.m_string, Qt::CaseSensitive) < 0; + else return StringUtils::naturalCompare(m_string, rhs.m_string, Qt::CaseSensitive) < 0; } bool JavaVersion::operator==(const JavaVersion &rhs) diff --git a/launcher/launch/LaunchTask.cpp b/launcher/launch/LaunchTask.cpp index 28fcc4f4..9e1794b3 100644 --- a/launcher/launch/LaunchTask.cpp +++ b/launcher/launch/LaunchTask.cpp @@ -37,7 +37,6 @@ #include "launch/LaunchTask.h" #include "MessageLevel.h" -#include "MMCStrings.h" #include "java/JavaChecker.h" #include "tasks/Task.h" #include <QDebug> diff --git a/launcher/main.cpp b/launcher/main.cpp index c6a7614c..df596449 100644 --- a/launcher/main.cpp +++ b/launcher/main.cpp @@ -84,11 +84,14 @@ int main(int argc, char *argv[]) Q_INIT_RESOURCE(OSX); Q_INIT_RESOURCE(iOS); Q_INIT_RESOURCE(flat); + Q_INIT_RESOURCE(flat_white); return app.exec(); } case Application::Failed: return 1; case Application::Succeeded: return 0; + default: + return -1; } } diff --git a/launcher/meta/Index.cpp b/launcher/meta/Index.cpp index 6802470d..242aad9f 100644 --- a/launcher/meta/Index.cpp +++ b/launcher/meta/Index.cpp @@ -24,7 +24,7 @@ Index::Index(QObject *parent) : QAbstractListModel(parent) { } -Index::Index(const QVector<VersionListPtr> &lists, QObject *parent) +Index::Index(const QVector<VersionList::Ptr> &lists, QObject *parent) : QAbstractListModel(parent), m_lists(lists) { for (int i = 0; i < m_lists.size(); ++i) @@ -41,7 +41,7 @@ QVariant Index::data(const QModelIndex &index, int role) const return QVariant(); } - VersionListPtr list = m_lists.at(index.row()); + VersionList::Ptr list = m_lists.at(index.row()); switch (role) { case Qt::DisplayRole: @@ -58,11 +58,11 @@ QVariant Index::data(const QModelIndex &index, int role) const } int Index::rowCount(const QModelIndex &parent) const { - return m_lists.size(); + return parent.isValid() ? 0 : m_lists.size(); } int Index::columnCount(const QModelIndex &parent) const { - return 1; + return parent.isValid() ? 0 : 1; } QVariant Index::headerData(int section, Qt::Orientation orientation, int role) const { @@ -81,9 +81,9 @@ bool Index::hasUid(const QString &uid) const return m_uids.contains(uid); } -VersionListPtr Index::get(const QString &uid) +VersionList::Ptr Index::get(const QString &uid) { - VersionListPtr out = m_uids.value(uid, nullptr); + VersionList::Ptr out = m_uids.value(uid, nullptr); if(!out) { out = std::make_shared<VersionList>(uid); @@ -92,7 +92,7 @@ VersionListPtr Index::get(const QString &uid) return out; } -VersionPtr Index::get(const QString &uid, const QString &version) +Version::Ptr Index::get(const QString &uid, const QString &version) { auto list = get(uid); return list->getVersion(version); @@ -105,7 +105,7 @@ void Index::parse(const QJsonObject& obj) void Index::merge(const std::shared_ptr<Index> &other) { - const QVector<VersionListPtr> lists = std::dynamic_pointer_cast<Index>(other)->m_lists; + const QVector<VersionList::Ptr> lists = std::dynamic_pointer_cast<Index>(other)->m_lists; // initial load, no need to merge if (m_lists.isEmpty()) { @@ -120,7 +120,7 @@ void Index::merge(const std::shared_ptr<Index> &other) } else { - for (const VersionListPtr &list : lists) + for (const VersionList::Ptr &list : lists) { if (m_uids.contains(list->uid())) { @@ -138,7 +138,7 @@ void Index::merge(const std::shared_ptr<Index> &other) } } -void Index::connectVersionList(const int row, const VersionListPtr &list) +void Index::connectVersionList(const int row, const VersionList::Ptr &list) { connect(list.get(), &VersionList::nameChanged, this, [this, row]() { diff --git a/launcher/meta/Index.h b/launcher/meta/Index.h index d33ab0c8..06ea09dc 100644 --- a/launcher/meta/Index.h +++ b/launcher/meta/Index.h @@ -19,20 +19,19 @@ #include <memory> #include "BaseEntity.h" +#include "meta/VersionList.h" class Task; namespace Meta { -using VersionListPtr = std::shared_ptr<class VersionList>; -using VersionPtr = std::shared_ptr<class Version>; class Index : public QAbstractListModel, public BaseEntity { Q_OBJECT public: explicit Index(QObject *parent = nullptr); - explicit Index(const QVector<VersionListPtr> &lists, QObject *parent = nullptr); + explicit Index(const QVector<VersionList::Ptr> &lists, QObject *parent = nullptr); enum { @@ -49,21 +48,21 @@ public: QString localFilename() const override { return "index.json"; } // queries - VersionListPtr get(const QString &uid); - VersionPtr get(const QString &uid, const QString &version); + VersionList::Ptr get(const QString &uid); + Version::Ptr get(const QString &uid, const QString &version); bool hasUid(const QString &uid) const; - QVector<VersionListPtr> lists() const { return m_lists; } + QVector<VersionList::Ptr> lists() const { return m_lists; } public: // for usage by parsers only void merge(const std::shared_ptr<Index> &other); void parse(const QJsonObject &obj) override; private: - QVector<VersionListPtr> m_lists; - QHash<QString, VersionListPtr> m_uids; + QVector<VersionList::Ptr> m_lists; + QHash<QString, VersionList::Ptr> m_uids; - void connectVersionList(const int row, const VersionListPtr &list); + void connectVersionList(const int row, const VersionList::Ptr &list); }; } diff --git a/launcher/meta/JsonFormat.cpp b/launcher/meta/JsonFormat.cpp index 796da4bb..473f37d6 100644 --- a/launcher/meta/JsonFormat.cpp +++ b/launcher/meta/JsonFormat.cpp @@ -37,11 +37,11 @@ MetadataVersion currentFormatVersion() static std::shared_ptr<Index> parseIndexInternal(const QJsonObject &obj) { const QVector<QJsonObject> objects = requireIsArrayOf<QJsonObject>(obj, "packages"); - QVector<VersionListPtr> lists; + QVector<VersionList::Ptr> lists; lists.reserve(objects.size()); std::transform(objects.begin(), objects.end(), std::back_inserter(lists), [](const QJsonObject &obj) { - VersionListPtr list = std::make_shared<VersionList>(requireString(obj, "uid")); + VersionList::Ptr list = std::make_shared<VersionList>(requireString(obj, "uid")); list->setName(ensureString(obj, "name", QString())); return list; }); @@ -49,9 +49,9 @@ static std::shared_ptr<Index> parseIndexInternal(const QJsonObject &obj) } // Version -static VersionPtr parseCommonVersion(const QString &uid, const QJsonObject &obj) +static Version::Ptr parseCommonVersion(const QString &uid, const QJsonObject &obj) { - VersionPtr version = std::make_shared<Version>(uid, requireString(obj, "version")); + Version::Ptr version = std::make_shared<Version>(uid, requireString(obj, "version")); version->setTime(QDateTime::fromString(requireString(obj, "releaseTime"), Qt::ISODate).toMSecsSinceEpoch() / 1000); version->setType(ensureString(obj, "type", QString())); version->setRecommended(ensureBoolean(obj, QString("recommended"), false)); @@ -63,9 +63,9 @@ static VersionPtr parseCommonVersion(const QString &uid, const QJsonObject &obj) return version; } -static std::shared_ptr<Version> parseVersionInternal(const QJsonObject &obj) +static Version::Ptr parseVersionInternal(const QJsonObject &obj) { - VersionPtr version = parseCommonVersion(requireString(obj, "uid"), obj); + Version::Ptr version = parseCommonVersion(requireString(obj, "uid"), obj); version->setData(OneSixVersionFormat::versionFileFromJson(QJsonDocument(obj), QString("%1/%2.json").arg(version->uid(), version->version()), @@ -74,12 +74,12 @@ static std::shared_ptr<Version> parseVersionInternal(const QJsonObject &obj) } // Version list / package -static std::shared_ptr<VersionList> parseVersionListInternal(const QJsonObject &obj) +static VersionList::Ptr parseVersionListInternal(const QJsonObject &obj) { const QString uid = requireString(obj, "uid"); const QVector<QJsonObject> versionsRaw = requireIsArrayOf<QJsonObject>(obj, "versions"); - QVector<VersionPtr> versions; + QVector<Version::Ptr> versions; versions.reserve(versionsRaw.size()); std::transform(versionsRaw.begin(), versionsRaw.end(), std::back_inserter(versions), [uid](const QJsonObject &vObj) { @@ -88,7 +88,7 @@ static std::shared_ptr<VersionList> parseVersionListInternal(const QJsonObject & return version; }); - VersionListPtr list = std::make_shared<VersionList>(uid); + VersionList::Ptr list = std::make_shared<VersionList>(uid); list->setName(ensureString(obj, "name", QString())); list->setVersions(versions); return list; diff --git a/launcher/meta/JsonFormat.h b/launcher/meta/JsonFormat.h index 93217b7e..63128a4e 100644 --- a/launcher/meta/JsonFormat.h +++ b/launcher/meta/JsonFormat.h @@ -60,11 +60,6 @@ struct Require QString suggests; }; -inline Q_DECL_PURE_FUNCTION uint qHash(const Require &key, uint seed = 0) Q_DECL_NOTHROW -{ - return qHash(key.uid, seed); -} - using RequireSet = std::set<Require>; void parseIndex(const QJsonObject &obj, Index *ptr); diff --git a/launcher/meta/Version.cpp b/launcher/meta/Version.cpp index a8dc3169..68cfa55c 100644 --- a/launcher/meta/Version.cpp +++ b/launcher/meta/Version.cpp @@ -54,7 +54,7 @@ void Meta::Version::parse(const QJsonObject& obj) parseVersion(obj, this); } -void Meta::Version::mergeFromList(const Meta::VersionPtr& other) +void Meta::Version::mergeFromList(const Meta::Version::Ptr& other) { if(other->m_providesRecommendations) { @@ -85,7 +85,7 @@ void Meta::Version::mergeFromList(const Meta::VersionPtr& other) } } -void Meta::Version::merge(const VersionPtr &other) +void Meta::Version::merge(const Version::Ptr &other) { mergeFromList(other); if(other->m_data) diff --git a/launcher/meta/Version.h b/launcher/meta/Version.h index dea8dc8a..7228fa36 100644 --- a/launcher/meta/Version.h +++ b/launcher/meta/Version.h @@ -30,13 +30,14 @@ namespace Meta { -using VersionPtr = std::shared_ptr<class Version>; class Version : public QObject, public BaseVersion, public BaseEntity { Q_OBJECT -public: /* con/des */ +public: + using Ptr = std::shared_ptr<Version>; + explicit Version(const QString &uid, const QString &version); virtual ~Version(); @@ -78,8 +79,8 @@ public: /* con/des */ return m_data != nullptr; } - void merge(const VersionPtr &other); - void mergeFromList(const VersionPtr &other); + void merge(const Version::Ptr &other); + void mergeFromList(const Version::Ptr &other); void parse(const QJsonObject &obj) override; QString localFilename() const override; @@ -113,4 +114,4 @@ private: }; } -Q_DECLARE_METATYPE(Meta::VersionPtr) +Q_DECLARE_METATYPE(Meta::Version::Ptr) diff --git a/launcher/meta/VersionList.cpp b/launcher/meta/VersionList.cpp index f609e94c..7f001dfc 100644 --- a/launcher/meta/VersionList.cpp +++ b/launcher/meta/VersionList.cpp @@ -40,7 +40,7 @@ bool VersionList::isLoaded() return BaseEntity::isLoaded(); } -const BaseVersionPtr VersionList::at(int i) const +const BaseVersion::Ptr VersionList::at(int i) const { return m_versions.at(i); } @@ -52,7 +52,7 @@ int VersionList::count() const void VersionList::sortVersions() { beginResetModel(); - std::sort(m_versions.begin(), m_versions.end(), [](const VersionPtr &a, const VersionPtr &b) + std::sort(m_versions.begin(), m_versions.end(), [](const Version::Ptr &a, const Version::Ptr &b) { return *a.get() < *b.get(); }); @@ -66,7 +66,7 @@ QVariant VersionList::data(const QModelIndex &index, int role) const return QVariant(); } - VersionPtr version = m_versions.at(index.row()); + Version::Ptr version = m_versions.at(index.row()); switch (role) { @@ -129,9 +129,9 @@ QString VersionList::humanReadable() const return m_name.isEmpty() ? m_uid : m_name; } -VersionPtr VersionList::getVersion(const QString &version) +Version::Ptr VersionList::getVersion(const QString &version) { - VersionPtr out = m_lookup.value(version, nullptr); + Version::Ptr out = m_lookup.value(version, nullptr); if(!out) { out = std::make_shared<Version>(m_uid, version); @@ -143,7 +143,7 @@ VersionPtr VersionList::getVersion(const QString &version) bool VersionList::hasVersion(QString version) const { auto ver = std::find_if(m_versions.constBegin(), m_versions.constEnd(), - [&](Meta::VersionPtr const& a){ return a->version() == version; }); + [&](Meta::Version::Ptr const& a){ return a->version() == version; }); return (ver != m_versions.constEnd()); } @@ -153,11 +153,11 @@ void VersionList::setName(const QString &name) emit nameChanged(name); } -void VersionList::setVersions(const QVector<VersionPtr> &versions) +void VersionList::setVersions(const QVector<Version::Ptr> &versions) { beginResetModel(); m_versions = versions; - std::sort(m_versions.begin(), m_versions.end(), [](const VersionPtr &a, const VersionPtr &b) + std::sort(m_versions.begin(), m_versions.end(), [](const Version::Ptr &a, const Version::Ptr &b) { return a->rawTime() > b->rawTime(); }); @@ -168,7 +168,7 @@ void VersionList::setVersions(const QVector<VersionPtr> &versions) } // FIXME: this is dumb, we have 'recommended' as part of the metadata already... - auto recommendedIt = std::find_if(m_versions.constBegin(), m_versions.constEnd(), [](const VersionPtr &ptr) { return ptr->type() == "release"; }); + auto recommendedIt = std::find_if(m_versions.constBegin(), m_versions.constEnd(), [](const Version::Ptr &ptr) { return ptr->type() == "release"; }); m_recommended = recommendedIt == m_versions.constEnd() ? nullptr : *recommendedIt; endResetModel(); } @@ -179,7 +179,7 @@ void VersionList::parse(const QJsonObject& obj) } // FIXME: this is dumb, we have 'recommended' as part of the metadata already... -static const Meta::VersionPtr &getBetterVersion(const Meta::VersionPtr &a, const Meta::VersionPtr &b) +static const Meta::Version::Ptr &getBetterVersion(const Meta::Version::Ptr &a, const Meta::Version::Ptr &b) { if(!a) return b; @@ -194,7 +194,7 @@ static const Meta::VersionPtr &getBetterVersion(const Meta::VersionPtr &a, const return (a->type() == "release" ? a : b); } -void VersionList::mergeFromIndex(const VersionListPtr &other) +void VersionList::mergeFromIndex(const VersionList::Ptr &other) { if (m_name != other->m_name) { @@ -202,7 +202,7 @@ void VersionList::mergeFromIndex(const VersionListPtr &other) } } -void VersionList::merge(const VersionListPtr &other) +void VersionList::merge(const VersionList::Ptr &other) { if (m_name != other->m_name) { @@ -216,7 +216,7 @@ void VersionList::merge(const VersionListPtr &other) { qWarning() << "Empty list loaded ..."; } - for (const VersionPtr &version : other->m_versions) + for (const Version::Ptr &version : other->m_versions) { // we already have the version. merge the contents if (m_lookup.contains(version->version())) @@ -235,7 +235,7 @@ void VersionList::merge(const VersionListPtr &other) endResetModel(); } -void VersionList::setupAddedVersion(const int row, const VersionPtr &version) +void VersionList::setupAddedVersion(const int row, const Version::Ptr &version) { // FIXME: do not disconnect from everythin, disconnect only the lambdas here version->disconnect(); @@ -244,7 +244,7 @@ void VersionList::setupAddedVersion(const int row, const VersionPtr &version) connect(version.get(), &Version::typeChanged, this, [this, row]() { emit dataChanged(index(row), index(row), QVector<int>() << TypeRole); }); } -BaseVersionPtr VersionList::getRecommended() const +BaseVersion::Ptr VersionList::getRecommended() const { return m_recommended; } diff --git a/launcher/meta/VersionList.h b/launcher/meta/VersionList.h index a6db2fd7..a4d5603d 100644 --- a/launcher/meta/VersionList.h +++ b/launcher/meta/VersionList.h @@ -20,10 +20,10 @@ #include <QJsonObject> #include <memory> +#include "meta/Version.h" + namespace Meta { -using VersionPtr = std::shared_ptr<class Version>; -using VersionListPtr = std::shared_ptr<class VersionList>; class VersionList : public BaseVersionList, public BaseEntity { @@ -33,6 +33,8 @@ class VersionList : public BaseVersionList, public BaseEntity public: explicit VersionList(const QString &uid, QObject *parent = nullptr); + using Ptr = std::shared_ptr<VersionList>; + enum Roles { UidRole = Qt::UserRole + 100, @@ -43,11 +45,11 @@ public: Task::Ptr getLoadTask() override; bool isLoaded() override; - const BaseVersionPtr at(int i) const override; + const BaseVersion::Ptr at(int i) const override; int count() const override; void sortVersions() override; - BaseVersionPtr getRecommended() const override; + BaseVersion::Ptr getRecommended() const override; QVariant data(const QModelIndex &index, int role) const override; RoleList providesRoles() const override; @@ -65,38 +67,38 @@ public: } QString humanReadable() const; - VersionPtr getVersion(const QString &version); + Version::Ptr getVersion(const QString &version); bool hasVersion(QString version) const; - QVector<VersionPtr> versions() const + QVector<Version::Ptr> versions() const { return m_versions; } public: // for usage only by parsers void setName(const QString &name); - void setVersions(const QVector<VersionPtr> &versions); - void merge(const VersionListPtr &other); - void mergeFromIndex(const VersionListPtr &other); + void setVersions(const QVector<Version::Ptr> &versions); + void merge(const VersionList::Ptr &other); + void mergeFromIndex(const VersionList::Ptr &other); void parse(const QJsonObject &obj) override; signals: void nameChanged(const QString &name); protected slots: - void updateListData(QList<BaseVersionPtr>) override + void updateListData(QList<BaseVersion::Ptr>) override { } private: - QVector<VersionPtr> m_versions; - QHash<QString, VersionPtr> m_lookup; + QVector<Version::Ptr> m_versions; + QHash<QString, Version::Ptr> m_lookup; QString m_uid; QString m_name; - VersionPtr m_recommended; + Version::Ptr m_recommended; - void setupAddedVersion(const int row, const VersionPtr &version); + void setupAddedVersion(const int row, const Version::Ptr &version); }; } -Q_DECLARE_METATYPE(Meta::VersionListPtr) +Q_DECLARE_METATYPE(Meta::VersionList::Ptr) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index a5b19f09..70d0b949 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -44,7 +44,6 @@ #include "settings/SettingsObject.h" #include "Application.h" -#include "MMCStrings.h" #include "pathmatcher/RegexpMatcher.h" #include "pathmatcher/MultiMatcher.h" #include "FileSystem.h" diff --git a/launcher/minecraft/MojangVersionFormat.cpp b/launcher/minecraft/MojangVersionFormat.cpp index 9bbb4ada..623dcdfa 100644 --- a/launcher/minecraft/MojangVersionFormat.cpp +++ b/launcher/minecraft/MojangVersionFormat.cpp @@ -135,7 +135,7 @@ QJsonObject libDownloadInfoToJson(MojangLibraryDownloadInfo::Ptr libinfo) { out.insert("artifact", downloadInfoToJson(libinfo->artifact)); } - if(libinfo->classifiers.size()) + if(!libinfo->classifiers.isEmpty()) { QJsonObject classifiersOut; for(auto iter = libinfo->classifiers.begin(); iter != libinfo->classifiers.end(); iter++) @@ -297,7 +297,7 @@ void MojangVersionFormat::writeVersionProperties(const VersionFile* in, QJsonObj { out.insert("assetIndex", assetIndexToJson(in->mojangAssetIndex)); } - if(in->mojangDownloads.size()) + if(!in->mojangDownloads.isEmpty()) { QJsonObject downloadsOut; for(auto iter = in->mojangDownloads.begin(); iter != in->mojangDownloads.end(); iter++) @@ -306,6 +306,15 @@ void MojangVersionFormat::writeVersionProperties(const VersionFile* in, QJsonObj } out.insert("downloads", downloadsOut); } + if(!in->compatibleJavaMajors.isEmpty()) + { + QJsonArray compatibleJavaMajorsOut; + for(auto compatibleJavaMajor : in->compatibleJavaMajors) + { + compatibleJavaMajorsOut.append(compatibleJavaMajor); + } + out.insert("compatibleJavaMajors", compatibleJavaMajorsOut); + } } QJsonDocument MojangVersionFormat::versionFileToJson(const VersionFilePtr &patch) @@ -396,7 +405,7 @@ QJsonObject MojangVersionFormat::libraryToJson(Library *library) iter++; } libRoot.insert("natives", nativeList); - if (library->m_extractExcludes.size()) + if (!library->m_extractExcludes.isEmpty()) { QJsonArray excludes; QJsonObject extract; @@ -408,7 +417,7 @@ QJsonObject MojangVersionFormat::libraryToJson(Library *library) libRoot.insert("extract", extract); } } - if (library->m_rules.size()) + if (!library->m_rules.isEmpty()) { QJsonArray allRules; for (auto &rule : library->m_rules) diff --git a/launcher/minecraft/OneSixVersionFormat.cpp b/launcher/minecraft/OneSixVersionFormat.cpp index cec4a55b..280f6b26 100644 --- a/launcher/minecraft/OneSixVersionFormat.cpp +++ b/launcher/minecraft/OneSixVersionFormat.cpp @@ -63,13 +63,13 @@ LibraryPtr OneSixVersionFormat::libraryFromJson(ProblemContainer & problems, con QJsonObject OneSixVersionFormat::libraryToJson(Library *library) { QJsonObject libRoot = MojangVersionFormat::libraryToJson(library); - if (library->m_absoluteURL.size()) + if (!library->m_absoluteURL.isEmpty()) libRoot.insert("MMC-absoluteUrl", library->m_absoluteURL); - if (library->m_hint.size()) + if (!library->m_hint.isEmpty()) libRoot.insert("MMC-hint", library->m_hint); - if (library->m_filename.size()) + if (!library->m_filename.isEmpty()) libRoot.insert("MMC-filename", library->m_filename); - if (library->m_displayname.size()) + if (!library->m_displayname.isEmpty()) libRoot.insert("MMC-displayname", library->m_displayname); return libRoot; } @@ -225,11 +225,10 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc { QJsonObject agentObj = requireObject(agentVal); auto lib = libraryFromJson(*out, agentObj, filename); + QString arg = ""; - if (agentObj.contains("argument")) - { - readString(agentObj, "argument", arg); - } + readString(agentObj, "argument", arg); + AgentPtr agent(new Agent(lib, arg)); out->agents.append(agent); } @@ -332,6 +331,20 @@ QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch writeString(root, "appletClass", patch->appletClass); writeStringList(root, "+tweakers", patch->addTweakers); writeStringList(root, "+traits", patch->traits.values()); + writeStringList(root, "+jvmArgs", patch->addnJvmArguments); + if (!patch->agents.isEmpty()) + { + QJsonArray array; + for (auto value: patch->agents) + { + QJsonObject agentOut = OneSixVersionFormat::libraryToJson(value->library().get()); + if (!value->argument().isEmpty()) + agentOut.insert("argument", value->argument()); + + array.append(agentOut); + } + root.insert("+agents", array); + } if (!patch->libraries.isEmpty()) { QJsonArray array; diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp index 1618458f..6ce525eb 100644 --- a/launcher/minecraft/PackProfile.cpp +++ b/launcher/minecraft/PackProfile.cpp @@ -613,7 +613,7 @@ QVariant PackProfile::data(const QModelIndex &index, int role) const bool PackProfile::setData(const QModelIndex& index, const QVariant& value, int role) { - if (!index.isValid() || index.row() < 0 || index.row() >= rowCount(index)) + if (!index.isValid() || index.row() < 0 || index.row() >= rowCount(index.parent())) { return false; } @@ -675,12 +675,12 @@ Qt::ItemFlags PackProfile::flags(const QModelIndex &index) const int PackProfile::rowCount(const QModelIndex &parent) const { - return d->components.size(); + return parent.isValid() ? 0 : d->components.size(); } int PackProfile::columnCount(const QModelIndex &parent) const { - return NUM_COLUMNS; + return parent.isValid() ? 0 : NUM_COLUMNS; } void PackProfile::move(const int index, const MoveDirection direction) diff --git a/launcher/minecraft/Rule.h b/launcher/minecraft/Rule.h index 236f9a87..846e8e42 100644 --- a/launcher/minecraft/Rule.h +++ b/launcher/minecraft/Rule.h @@ -104,7 +104,7 @@ public: class ImplicitRule : public Rule { protected: - virtual bool applies(const Library *, const RuntimeContext & runtimeContext) + virtual bool applies(const Library *, [[maybe_unused]] const RuntimeContext & runtimeContext) { return true; } diff --git a/launcher/minecraft/VanillaInstanceCreationTask.cpp b/launcher/minecraft/VanillaInstanceCreationTask.cpp index c45daa9a..0bb92e87 100644 --- a/launcher/minecraft/VanillaInstanceCreationTask.cpp +++ b/launcher/minecraft/VanillaInstanceCreationTask.cpp @@ -7,7 +7,7 @@ #include "minecraft/PackProfile.h" #include "settings/INISettingsObject.h" -VanillaCreationTask::VanillaCreationTask(BaseVersionPtr version, QString loader, BaseVersionPtr loader_version) +VanillaCreationTask::VanillaCreationTask(BaseVersion::Ptr version, QString loader, BaseVersion::Ptr loader_version) : InstanceCreationTask(), m_version(std::move(version)), m_using_loader(true), m_loader(std::move(loader)), m_loader_version(std::move(loader_version)) {} diff --git a/launcher/minecraft/VanillaInstanceCreationTask.h b/launcher/minecraft/VanillaInstanceCreationTask.h index 7a37bbd6..d1b81682 100644 --- a/launcher/minecraft/VanillaInstanceCreationTask.h +++ b/launcher/minecraft/VanillaInstanceCreationTask.h @@ -7,16 +7,16 @@ class VanillaCreationTask final : public InstanceCreationTask { Q_OBJECT public: - VanillaCreationTask(BaseVersionPtr version) : InstanceCreationTask(), m_version(std::move(version)) {} - VanillaCreationTask(BaseVersionPtr version, QString loader, BaseVersionPtr loader_version); + VanillaCreationTask(BaseVersion::Ptr version) : InstanceCreationTask(), m_version(std::move(version)) {} + VanillaCreationTask(BaseVersion::Ptr version, QString loader, BaseVersion::Ptr loader_version); bool createInstance() override; private: // Version to update to / create of the instance. - BaseVersionPtr m_version; + BaseVersion::Ptr m_version; bool m_using_loader = false; QString m_loader; - BaseVersionPtr m_loader_version; + BaseVersion::Ptr m_loader_version; }; diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp index aee7be35..ae29a972 100644 --- a/launcher/minecraft/WorldList.cpp +++ b/launcher/minecraft/WorldList.cpp @@ -173,7 +173,7 @@ bool WorldList::resetIcon(int row) int WorldList::columnCount(const QModelIndex &parent) const { - return 4; + return parent.isValid()? 0 : 4; } QVariant WorldList::data(const QModelIndex &index, int role) const @@ -398,8 +398,8 @@ void WorldList::installWorld(QFileInfo filename) w.install(m_dir.absolutePath()); } -bool WorldList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, - const QModelIndex &parent) +bool WorldList::dropMimeData(const QMimeData *data, Qt::DropAction action, [[maybe_unused]] int row, [[maybe_unused]] int column, + [[maybe_unused]] const QModelIndex &parent) { if (action == Qt::IgnoreAction) return true; diff --git a/launcher/minecraft/WorldList.h b/launcher/minecraft/WorldList.h index 5138e583..08294755 100644 --- a/launcher/minecraft/WorldList.h +++ b/launcher/minecraft/WorldList.h @@ -54,7 +54,7 @@ public: virtual int rowCount(const QModelIndex &parent = QModelIndex()) const { - return size(); + return parent.isValid() ? 0 : static_cast<int>(size()); }; virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; diff --git a/launcher/minecraft/auth/AccountList.cpp b/launcher/minecraft/auth/AccountList.cpp index b3b57c74..9e2fd111 100644 --- a/launcher/minecraft/auth/AccountList.cpp +++ b/launcher/minecraft/auth/AccountList.cpp @@ -408,20 +408,20 @@ QVariant AccountList::headerData(int section, Qt::Orientation orientation, int r } } -int AccountList::rowCount(const QModelIndex &) const +int AccountList::rowCount(const QModelIndex &parent) const { // Return count - return count(); + return parent.isValid() ? 0 : count(); } -int AccountList::columnCount(const QModelIndex &) const +int AccountList::columnCount(const QModelIndex &parent) const { - return NUM_COLUMNS; + return parent.isValid() ? 0 : NUM_COLUMNS; } Qt::ItemFlags AccountList::flags(const QModelIndex &index) const { - if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid()) + if (index.row() < 0 || index.row() >= rowCount(index.parent()) || !index.isValid()) { return Qt::NoItemFlags; } diff --git a/launcher/minecraft/launch/VerifyJavaInstall.cpp b/launcher/minecraft/launch/VerifyJavaInstall.cpp index 99809f82..6ae666b4 100644 --- a/launcher/minecraft/launch/VerifyJavaInstall.cpp +++ b/launcher/minecraft/launch/VerifyJavaInstall.cpp @@ -71,5 +71,7 @@ void VerifyJavaInstall::executeTask() { { emit logLine(tr("Java version %1").arg(major), MessageLevel::Error); } + emit logLine(tr("Go to instance Java settings to change your Java version or disable the Java compatibility check if you know what you're doing."), MessageLevel::Error); + emitFailed(QString("Incompatible Java major version")); } diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 66e80f4a..4ccc5d4d 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -144,7 +144,7 @@ QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, in int ModFolderModel::columnCount(const QModelIndex &parent) const { - return NUM_COLUMNS; + return parent.isValid() ? 0 : NUM_COLUMNS; } Task* ModFolderModel::createUpdateTask() diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index b2356309..0310c8f6 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -426,7 +426,7 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const bool ResourceFolderModel::setData(const QModelIndex& index, const QVariant& value, int role) { int row = index.row(); - if (row < 0 || row >= rowCount(index) || !index.isValid()) + if (row < 0 || row >= rowCount(index.parent()) || !index.isValid()) return false; if (role == Qt::CheckStateRole) diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index 25095a45..fe283b04 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -90,8 +90,8 @@ class ResourceFolderModel : public QAbstractListModel { /* Basic columns */ enum Columns { ACTIVE_COLUMN = 0, NAME_COLUMN, DATE_COLUMN, NUM_COLUMNS }; - [[nodiscard]] int rowCount(const QModelIndex& = {}) const override { return size(); } - [[nodiscard]] int columnCount(const QModelIndex& = {}) const override { return NUM_COLUMNS; }; + [[nodiscard]] int rowCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : static_cast<int>(size()); } + [[nodiscard]] int columnCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : NUM_COLUMNS; }; [[nodiscard]] Qt::DropActions supportedDropActions() const override; @@ -176,7 +176,7 @@ class ResourceFolderModel : public QAbstractListModel { * if the resource is complex and has more stuff to parse. */ virtual void onParseSucceeded(int ticket, QString resource_id); - virtual void onParseFailed(int ticket, QString resource_id) {} + virtual void onParseFailed(int ticket, QString resource_id) { Q_UNUSED(ticket); Q_UNUSED(resource_id); } protected: // Represents the relationship between a column's index (represented by the list index), and it's sorting key. diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index f8a6c1cf..ebac707d 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -137,7 +137,7 @@ QVariant ResourcePackFolderModel::headerData(int section, Qt::Orientation orient int ResourcePackFolderModel::columnCount(const QModelIndex& parent) const { - return NUM_COLUMNS; + return parent.isValid() ? 0 : NUM_COLUMNS; } Task* ResourcePackFolderModel::createUpdateTask() diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp index a694e7b2..774f6114 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp @@ -121,7 +121,7 @@ ModDetails ReadMCModTOML(QByteArray contents) return {}; } auto modsTable = tomlModsTable0->as_table(); - if (!tomlModsTable0) { + if (!modsTable) { qWarning() << "Corrupted mods.toml? [[mods]] was not a table!"; return {}; } diff --git a/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp b/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp index 4b878918..cc4e252c 100644 --- a/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp @@ -36,7 +36,7 @@ LocalModUpdateTask::LocalModUpdateTask(QDir index_dir, ModPlatform::IndexedPack& } #ifdef Q_OS_WIN32 - SetFileAttributesA(index_dir.path().toStdString().c_str(), FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED); + SetFileAttributesW(index_dir.path().toStdWString().c_str(), FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED); #endif } diff --git a/launcher/modplatform/ModAPI.h b/launcher/modplatform/ModAPI.h index c7408835..703de143 100644 --- a/launcher/modplatform/ModAPI.h +++ b/launcher/modplatform/ModAPI.h @@ -39,7 +39,7 @@ #include <QList> #include <list> -#include "Version.h" +#include "../Version.h" #include "net/NetJob.h" namespace ModPlatform { diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index 68d75943..291ad916 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -58,7 +58,7 @@ namespace ATLauncher { -static Meta::VersionPtr getComponentVersion(const QString& uid, const QString& version); +static Meta::Version::Ptr getComponentVersion(const QString& uid, const QString& version); PackInstallTask::PackInstallTask(UserInteractionSupport *support, QString packName, QString version, InstallMode installMode) { @@ -1037,7 +1037,7 @@ void PackInstallTask::install() emitSucceeded(); } -static Meta::VersionPtr getComponentVersion(const QString& uid, const QString& version) +static Meta::Version::Ptr getComponentVersion(const QString& uid, const QString& version) { auto vlist = APPLICATION->metadataIndex()->get(uid); if (!vlist) diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.h b/launcher/modplatform/atlauncher/ATLPackInstallTask.h index 78cd87fb..90e25ae2 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.h +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.h @@ -68,7 +68,7 @@ public: * Requests a user interaction to select a component version from a given version list * and constrained to a given Minecraft version. */ - virtual QString chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) = 0; + virtual QString chooseVersion(Meta::VersionList::Ptr vlist, QString minecraftVersion) = 0; /** * Requests a user interaction to display a message. @@ -137,8 +137,8 @@ private: QString archivePath; QStringList jarmods; - Meta::VersionPtr minecraftVersion; - QMap<QString, Meta::VersionPtr> componentsToInstall; + Meta::Version::Ptr minecraftVersion; + QMap<QString, Meta::Version::Ptr> componentsToInstall; QFuture<std::optional<QStringList>> m_extractFuture; QFutureWatcher<std::optional<QStringList>> m_extractFutureWatcher; diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp index c50abb8f..25b56fbd 100644 --- a/launcher/modplatform/flame/FileResolvingTask.cpp +++ b/launcher/modplatform/flame/FileResolvingTask.cpp @@ -3,6 +3,8 @@ #include "Json.h" #include "net/Upload.h" +#include "modplatform/modrinth/ModrinthPackIndex.h" + Flame::FileResolvingTask::FileResolvingTask(const shared_qobject_ptr<QNetworkAccessManager>& network, Flame::Manifest& toProcess) : m_network(network), m_toProcess(toProcess) {} @@ -84,18 +86,21 @@ void Flame::FileResolvingTask::modrinthCheckFinished() { delete bytes; continue; } + QJsonDocument doc = QJsonDocument::fromJson(*bytes); auto obj = doc.object(); - auto array = Json::requireArray(obj,"files"); - for (auto file: array) { - auto fileObj = Json::requireObject(file); - auto primary = Json::requireBoolean(fileObj,"primary"); - if (primary) { - out->url = Json::requireUrl(fileObj,"url"); - qDebug() << "Found alternative on modrinth " << out->fileName; - break; - } + auto file = Modrinth::loadIndexedPackVersion(obj); + + // If there's more than one mod loader for this version, we can't know for sure + // which file is relative to each loader, so it's best to not use any one and + // let the user download it manually. + if (file.loaders.size() <= 1) { + out->url = file.downloadUrl; + qDebug() << "Found alternative on modrinth " << out->fileName; + } else { + out->resolved = false; } + delete bytes; } //copy to an output list and filter out projects found on modrinth diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 48ac02e0..91554b58 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -372,13 +372,20 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) auto results = m_mod_id_resolver->getResults(); // first check for blocked mods - QString text; - QList<QUrl> urls; + QList<BlockedMod> blocked_mods; auto anyBlocked = false; for (const auto& result : results.files.values()) { if (!result.resolved || result.url.isEmpty()) { - text += QString("%1: <a href='%2'>%2</a><br/>").arg(result.fileName, result.websiteUrl); - urls.append(QUrl(result.websiteUrl)); + + BlockedMod blocked_mod; + blocked_mod.name = result.fileName; + blocked_mod.websiteUrl = result.websiteUrl; + blocked_mod.hash = result.hash; + blocked_mod.matched = false; + blocked_mod.localPath = ""; + + blocked_mods.append(blocked_mod); + anyBlocked = true; } } @@ -386,13 +393,15 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) qWarning() << "Blocked mods found, displaying mod list"; auto message_dialog = new BlockedModsDialog(m_parent, tr("Blocked mods found"), - tr("The following mods were blocked on third party launchers.<br/>" - "You will need to manually download them and add them to the modpack"), - text, - urls); + tr("The following files are not available for download in third party launchers.<br/>" + "You will need to manually download them and add them to the instance."), + blocked_mods); + message_dialog->setModal(true); if (message_dialog->exec()) { + qDebug() << "Post dialog blocked mods list: " << blocked_mods; + copyBlockedMods(blocked_mods); setupDownloadJob(loop); } else { m_mod_id_resolver.reset(); @@ -404,6 +413,38 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) } } +/// @brief copy the matched blocked mods to the instance staging area +/// @param blocked_mods list of the blocked mods and their matched paths +void FlameCreationTask::copyBlockedMods(QList<BlockedMod> const& blocked_mods) +{ + setStatus(tr("Copying Blocked Mods...")); + setAbortable(false); + int i = 0; + int total = blocked_mods.length(); + setProgress(i, total); + for (auto const& mod : blocked_mods) { + if (!mod.matched) { + qDebug() << mod.name << "was not matched to a local file, skipping copy"; + continue; + } + + auto dest_path = FS::PathCombine(m_stagingPath, "minecraft", "mods", mod.name); + + setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total))); + + qDebug() << "Will try to copy" << mod.localPath << "to" << dest_path; + + if (!FS::copy(mod.localPath, dest_path)()) { + qDebug() << "Copy of" << mod.localPath << "to" << dest_path << "Failed"; + } + + i++; + setProgress(i, total); + } + + setAbortable(true); +} + void FlameCreationTask::setupDownloadJob(QEventLoop& loop) { m_files_job = new NetJob(tr("Mod download"), APPLICATION->network()); @@ -449,7 +490,7 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop) m_files_job.reset(); setError(reason); }); - connect(m_files_job.get(), &NetJob::progress, [&](qint64 current, qint64 total) { setProgress(current, total); }); + connect(m_files_job.get(), &NetJob::progress, this, &FlameCreationTask::setProgress); connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit); setStatus(tr("Downloading mods...")); diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.h b/launcher/modplatform/flame/FlameInstanceCreationTask.h index ded0e2ce..fbc7d5bf 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.h +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.h @@ -10,6 +10,8 @@ #include "net/NetJob.h" +#include "ui/dialogs/BlockedModsDialog.h" + class FlameCreationTask final : public InstanceCreationTask { Q_OBJECT @@ -29,6 +31,7 @@ class FlameCreationTask final : public InstanceCreationTask { private slots: void idResolverSucceeded(QEventLoop&); void setupDownloadJob(QEventLoop&); + void copyBlockedMods(QList<BlockedMod> const& blocked_mods); private: QWidget* m_parent = nullptr; diff --git a/launcher/modplatform/helpers/HashUtils.cpp b/launcher/modplatform/helpers/HashUtils.cpp index a7bbaba5..f1e4759e 100644 --- a/launcher/modplatform/helpers/HashUtils.cpp +++ b/launcher/modplatform/helpers/HashUtils.cpp @@ -4,6 +4,7 @@ #include <QFile> #include "FileSystem.h" +#include "StringUtils.h" #include <MurmurHash2.h> @@ -35,6 +36,18 @@ Hasher::Ptr createFlameHasher(QString file_path) return new FlameHasher(file_path); } +Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider) +{ + return new BlockedModHasher(file_path, provider); +} + +Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider, QString type) +{ + auto hasher = new BlockedModHasher(file_path, provider); + hasher->useHashType(type); + return hasher; +} + void ModrinthHasher::executeTask() { QFile file(m_path); @@ -66,7 +79,7 @@ void FlameHasher::executeTask() // CF-specific auto should_filter_out = [](char c) { return (c == 9 || c == 10 || c == 13 || c == 32); }; - std::ifstream file_stream(m_path.toStdString(), std::ifstream::binary); + std::ifstream file_stream(StringUtils::toStdString(m_path), std::ifstream::binary); // TODO: This is very heavy work, but apparently QtConcurrent can't use move semantics, so we can't boop this to another thread. // How do we make this non-blocking then? m_hash = QString::number(MurmurHash2(std::move(file_stream), 4 * MiB, should_filter_out)); @@ -78,4 +91,50 @@ void FlameHasher::executeTask() } } + +BlockedModHasher::BlockedModHasher(QString file_path, ModPlatform::Provider provider) + : Hasher(file_path), provider(provider) { + setObjectName(QString("BlockedModHasher: %1").arg(file_path)); + hash_type = ProviderCaps.hashType(provider).first(); +} + +void BlockedModHasher::executeTask() +{ + QFile file(m_path); + + try { + file.open(QFile::ReadOnly); + } catch (FS::FileSystemException& e) { + qCritical() << QString("Failed to open JAR file in %1").arg(m_path); + qCritical() << QString("Reason: ") << e.cause(); + + emitFailed("Failed to open file for hashing."); + return; + } + + m_hash = ProviderCaps.hash(provider, &file, hash_type); + + file.close(); + + if (m_hash.isEmpty()) { + emitFailed("Empty hash!"); + } else { + emitSucceeded(); + } +} + +QStringList BlockedModHasher::getHashTypes() { + return ProviderCaps.hashType(provider); +} + +bool BlockedModHasher::useHashType(QString type) { + auto types = ProviderCaps.hashType(provider); + if (types.contains(type)) { + hash_type = type; + return true; + } + qDebug() << "Bad hash type " << type << " for provider"; + return false; +} + } // namespace Hashing diff --git a/launcher/modplatform/helpers/HashUtils.h b/launcher/modplatform/helpers/HashUtils.h index 38fddf03..fa3244f6 100644 --- a/launcher/modplatform/helpers/HashUtils.h +++ b/launcher/modplatform/helpers/HashUtils.h @@ -40,8 +40,23 @@ class ModrinthHasher : public Hasher { void executeTask() override; }; +class BlockedModHasher : public Hasher { + public: + BlockedModHasher(QString file_path, ModPlatform::Provider provider); + + void executeTask() override; + + QStringList getHashTypes(); + bool useHashType(QString type); + private: + ModPlatform::Provider provider; + QString hash_type; +}; + Hasher::Ptr createHasher(QString file_path, ModPlatform::Provider provider); Hasher::Ptr createFlameHasher(QString file_path); Hasher::Ptr createModrinthHasher(QString file_path); +Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider); +Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider, QString type); } // namespace Hashing diff --git a/launcher/modplatform/helpers/NetworkModAPI.cpp b/launcher/modplatform/helpers/NetworkModAPI.cpp index 866e7540..7633030e 100644 --- a/launcher/modplatform/helpers/NetworkModAPI.cpp +++ b/launcher/modplatform/helpers/NetworkModAPI.cpp @@ -15,6 +15,7 @@ void NetworkModAPI::searchMods(CallerType* caller, SearchArgs&& args) const QObject::connect(netJob, &NetJob::started, caller, [caller, netJob] { caller->setActiveJob(netJob); }); QObject::connect(netJob, &NetJob::failed, caller, &CallerType::searchRequestFailed); + QObject::connect(netJob, &NetJob::aborted, caller, &CallerType::searchRequestAborted); QObject::connect(netJob, &NetJob::succeeded, caller, [caller, response] { QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp index 7b112d8f..4c7b7a4f 100644 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp +++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp @@ -176,8 +176,6 @@ void PackInstallTask::resolveMods() void PackInstallTask::onResolveModsSucceeded() { - QString text; - QList<QUrl> urls; auto anyBlocked = false; Flame::Manifest results = m_mod_id_resolver_task->getResults(); @@ -191,11 +189,16 @@ void PackInstallTask::onResolveModsSucceeded() // First check for blocked mods if (!results_file.resolved || results_file.url.isEmpty()) { - QString type(local_file.type); - type[0] = type[0].toUpper(); - text += QString("%1: %2 - <a href='%3'>%3</a><br/>").arg(type, local_file.name, results_file.websiteUrl); - urls.append(QUrl(results_file.websiteUrl)); + BlockedMod blocked_mod; + blocked_mod.name = local_file.name; + blocked_mod.websiteUrl = results_file.websiteUrl; + blocked_mod.hash = results_file.hash; + blocked_mod.matched = false; + blocked_mod.localPath = ""; + + m_blocked_mods.append(blocked_mod); + anyBlocked = true; } else { local_file.url = results_file.url.toString(); @@ -208,15 +211,17 @@ void PackInstallTask::onResolveModsSucceeded() qDebug() << "Blocked files found, displaying file list"; auto message_dialog = new BlockedModsDialog(m_parent, tr("Blocked files found"), - tr("The following files are not available for download in third party launchers.<br/>" - "You will need to manually download them and add them to the instance."), - text, - urls); + tr("The following files are not available for download in third party launchers.<br/>" + "You will need to manually download them and add them to the instance."), + m_blocked_mods); - if (message_dialog->exec() == QDialog::Accepted) + if (message_dialog->exec() == QDialog::Accepted) { + qDebug() << "Post dialog blocked mods list: " << m_blocked_mods; createInstance(); - else + } else { abort(); + } + } else { createInstance(); } @@ -320,6 +325,9 @@ void PackInstallTask::downloadPack() void PackInstallTask::onModDownloadSucceeded() { m_net_job.reset(); + if (!m_blocked_mods.isEmpty()) { + copyBlockedMods(); + } emitSucceeded(); } @@ -343,4 +351,35 @@ void PackInstallTask::onModDownloadFailed(QString reason) emitFailed(reason); } +/// @brief copy the matched blocked mods to the instance staging area +void PackInstallTask::copyBlockedMods() +{ + setStatus(tr("Copying Blocked Mods...")); + setAbortable(false); + int i = 0; + int total = m_blocked_mods.length(); + setProgress(i, total); + for (auto const& mod : m_blocked_mods) { + if (!mod.matched) { + qDebug() << mod.name << "was not matched to a local file, skipping copy"; + continue; + } + + auto dest_path = FS::PathCombine(m_stagingPath, ".minecraft", "mods", mod.name); + + setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total))); + + qDebug() << "Will try to copy" << mod.localPath << "to" << dest_path; + + if (!FS::copy(mod.localPath, dest_path)()) { + qDebug() << "Copy of" << mod.localPath << "to" << dest_path << "Failed"; + } + + i++; + setProgress(i, total); + } + + setAbortable(true); +} + } // namespace ModpacksCH diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.h b/launcher/modplatform/modpacksch/FTBPackInstallTask.h index 7c6fbeb9..2cd4d729 100644 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.h +++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.h @@ -43,6 +43,7 @@ #include "QObjectPtr.h" #include "modplatform/flame/FileResolvingTask.h" #include "net/NetJob.h" +#include "ui/dialogs/BlockedModsDialog.h" #include <QWidget> @@ -76,6 +77,7 @@ private: void resolveMods(); void createInstance(); void downloadPack(); + void copyBlockedMods(); private: NetJob::Ptr m_net_job = nullptr; @@ -90,6 +92,7 @@ private: Version m_version; QMap<QString, QString> m_files_to_copy; + QList<BlockedMod> m_blocked_mods; //FIXME: nuke QWidget* m_parent; diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index 3e53becb..ae45e096 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -1,20 +1,20 @@ // SPDX-License-Identifier: GPL-3.0-only /* -* PolyMC - Minecraft Launcher -* Copyright (c) 2022 flowln <flowlnlnln@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/>. -*/ + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln <flowlnlnln@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/>. + */ #include "ModrinthPackIndex.h" #include "ModrinthAPI.h" @@ -35,7 +35,7 @@ void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) pack.provider = ModPlatform::Provider::MODRINTH; pack.name = Json::requireString(obj, "title"); - + pack.slug = Json::ensureString(obj, "slug", ""); if (!pack.slug.isEmpty()) pack.websiteUrl = "https://modrinth.com/mod/" + pack.slug; @@ -59,23 +59,23 @@ void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& obj) { pack.extraData.issuesUrl = Json::ensureString(obj, "issues_url"); - if(pack.extraData.issuesUrl.endsWith('/')) + if (pack.extraData.issuesUrl.endsWith('/')) pack.extraData.issuesUrl.chop(1); pack.extraData.sourceUrl = Json::ensureString(obj, "source_url"); - if(pack.extraData.sourceUrl.endsWith('/')) + if (pack.extraData.sourceUrl.endsWith('/')) pack.extraData.sourceUrl.chop(1); pack.extraData.wikiUrl = Json::ensureString(obj, "wiki_url"); - if(pack.extraData.wikiUrl.endsWith('/')) + if (pack.extraData.wikiUrl.endsWith('/')) pack.extraData.wikiUrl.chop(1); pack.extraData.discordUrl = Json::ensureString(obj, "discord_url"); - if(pack.extraData.discordUrl.endsWith('/')) + if (pack.extraData.discordUrl.endsWith('/')) pack.extraData.discordUrl.chop(1); auto donate_arr = Json::ensureArray(obj, "donation_urls"); - for(auto d : donate_arr){ + for (auto d : donate_arr) { auto d_obj = Json::requireObject(d); ModPlatform::DonationData donate; @@ -104,7 +104,7 @@ void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, auto obj = versionIter.toObject(); auto file = loadIndexedPackVersion(obj); - if(file.fileId.isValid()) // Heuristic to check if the returned value is valid + if (file.fileId.isValid()) // Heuristic to check if the returned value is valid unsortedVersions.append(file); } auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool { @@ -116,7 +116,8 @@ void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, pack.versionsLoaded = true; } -auto Modrinth::loadIndexedPackVersion(QJsonObject &obj, QString preferred_hash_type, QString preferred_file_name) -> ModPlatform::IndexedVersion +auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_type, QString preferred_file_name) + -> ModPlatform::IndexedVersion { ModPlatform::IndexedVersion file; @@ -141,6 +142,12 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject &obj, QString preferred_hash_t auto files = Json::requireArray(obj, "files"); int i = 0; + if (files.empty()) { + // This should not happen normally, but check just in case + qWarning() << "Modrinth returned an unexpected empty list of files:" << obj; + return {}; + } + // Find correct file (needed in cases where one version may have multiple files) // Will default to the last one if there's no primary (though I think Modrinth requires that // at least one file is primary, idk) @@ -167,7 +174,7 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject &obj, QString preferred_hash_t file.fileName = Json::requireString(parent, "filename"); file.is_preferred = Json::requireBoolean(parent, "primary") || (files.count() == 1); auto hash_list = Json::requireObject(parent, "hashes"); - + if (hash_list.contains(preferred_hash_type)) { file.hash = Json::requireString(hash_list, preferred_hash_type); file.hash_type = preferred_hash_type; diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp index a4620df9..96f54067 100644 --- a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp @@ -140,7 +140,7 @@ auto loadIndexedVersion(QJsonObject &obj) -> ModpackVersion for (auto file_iter : files) { File indexed_file; auto parent = Json::requireObject(file_iter); - auto is_primary = Json::ensureBoolean(parent, "primary", false); + auto is_primary = Json::ensureBoolean(parent, (const QString)QStringLiteral("primary"), false); if (!is_primary) { auto filename = Json::ensureString(parent, "filename"); // Checking suffix here is fine because it's the response from Modrinth, diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index b1fe963e..0ed29311 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -22,10 +22,14 @@ #include <QDir> #include <QObject> -#include <toml++/toml.h> +#include "FileSystem.h" +#include "StringUtils.h" + #include "minecraft/mod/Mod.h" #include "modplatform/ModIndex.h" +#include <toml++/toml.h> + namespace Packwiz { auto getRealIndexName(QDir& index_dir, QString normalized_fname, bool should_find_match) -> QString @@ -63,22 +67,22 @@ static inline auto indexFileName(QString const& mod_slug) -> QString static ModPlatform::ProviderCapabilities ProviderCaps; // Helper functions for extracting data from the TOML file -auto stringEntry(toml::table table, const std::string entry_name) -> QString +auto stringEntry(toml::table table, QString entry_name) -> QString { - auto node = table[entry_name]; + auto node = table[StringUtils::toStdString(entry_name)]; if (!node) { - qCritical() << QString::fromStdString("Failed to read str property '" + entry_name + "' in mod metadata."); + qCritical() << "Failed to read str property '" + entry_name + "' in mod metadata."; return {}; } - return QString::fromStdString(node.value_or("")); + return node.value_or(""); } -auto intEntry(toml::table table, const std::string entry_name) -> int +auto intEntry(toml::table table, QString entry_name) -> int { - auto node = table[entry_name]; + auto node = table[StringUtils::toStdString(entry_name)]; if (!node) { - qCritical() << QString::fromStdString("Failed to read int property '" + entry_name + "' in mod metadata."); + qCritical() << "Failed to read int property '" + entry_name + "' in mod metadata."; return {}; } @@ -145,6 +149,8 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod) // they want to do! if (index_file.exists()) { index_file.remove(); + } else { + FS::ensureFilePathExists(index_file.fileName()); } if (!index_file.open(QIODevice::ReadWrite)) { @@ -228,14 +234,14 @@ auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod toml::table table; #if TOML_EXCEPTIONS try { - table = toml::parse_file(index_dir.absoluteFilePath(real_fname).toStdString()); + table = toml::parse_file(StringUtils::toStdString(index_dir.absoluteFilePath(real_fname))); } catch (const toml::parse_error& err) { qWarning() << QString("Could not open file %1!").arg(normalized_fname); qWarning() << "Reason: " << QString(err.what()); return {}; } #else - table = toml::parse_file(index_dir.absoluteFilePath(real_fname).toStdString()); + table = toml::parse_file(StringUtils::toStdString(index_dir.absoluteFilePath(real_fname))); if (!table) { qWarning() << QString("Could not open file %1!").arg(normalized_fname); qWarning() << "Reason: " << QString(table.error().what()); diff --git a/launcher/modplatform/packwiz/Packwiz.h b/launcher/modplatform/packwiz/Packwiz.h index 3ec80377..9754e5c4 100644 --- a/launcher/modplatform/packwiz/Packwiz.h +++ b/launcher/modplatform/packwiz/Packwiz.h @@ -24,7 +24,6 @@ #include <QUrl> #include <QVariant> -struct toml_table_t; class QDir; // Mod from launcher/minecraft/mod/Mod.h @@ -34,9 +33,6 @@ namespace Packwiz { auto getRealIndexName(QDir& index_dir, QString normalized_index_name, bool should_match = false) -> QString; -auto stringEntry(toml_table_t* parent, const char* entry_name) -> QString; -auto intEntry(toml_table_t* parent, const char* entry_name) -> int; - class V1 { public: struct Mod { diff --git a/launcher/net/HttpMetaCache.cpp b/launcher/net/HttpMetaCache.cpp index 42198b71..0d7ca769 100644 --- a/launcher/net/HttpMetaCache.cpp +++ b/launcher/net/HttpMetaCache.cpp @@ -47,7 +47,7 @@ auto MetaEntry::getFullPath() -> QString { // FIXME: make local? - return FS::PathCombine(basePath, relativePath); + return FS::PathCombine(m_basePath, m_relativePath); } HttpMetaCache::HttpMetaCache(QString path) : QObject(), m_index_file(path) @@ -99,7 +99,7 @@ auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString ex return staleEntry(base, resource_path); } - if (!expected_etag.isEmpty() && expected_etag != entry->etag) { + if (!expected_etag.isEmpty() && expected_etag != entry->m_etag) { // if the etag doesn't match expected, we disown the entry selected_base.entry_list.remove(resource_path); return staleEntry(base, resource_path); @@ -107,17 +107,17 @@ auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString ex // if the file changed, check md5sum qint64 file_last_changed = finfo.lastModified().toUTC().toMSecsSinceEpoch(); - if (file_last_changed != entry->local_changed_timestamp) { + if (file_last_changed != entry->m_local_changed_timestamp) { QFile input(real_path); input.open(QIODevice::ReadOnly); QString md5sum = QCryptographicHash::hash(input.readAll(), QCryptographicHash::Md5).toHex().constData(); - if (entry->md5sum != md5sum) { + if (entry->m_md5sum != md5sum) { selected_base.entry_list.remove(resource_path); return staleEntry(base, resource_path); } // md5sums matched... keep entry and save the new state to file - entry->local_changed_timestamp = file_last_changed; + entry->m_local_changed_timestamp = file_last_changed; SaveEventually(); } @@ -130,23 +130,23 @@ auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString ex } // entry passed all the checks we cared about. - entry->basePath = getBasePath(base); + entry->m_basePath = getBasePath(base); return entry; } auto HttpMetaCache::updateEntry(MetaEntryPtr stale_entry) -> bool { - if (!m_entries.contains(stale_entry->baseId)) { - qCritical() << "Cannot add entry with unknown base: " << stale_entry->baseId.toLocal8Bit(); + if (!m_entries.contains(stale_entry->m_baseId)) { + qCritical() << "Cannot add entry with unknown base: " << stale_entry->m_baseId.toLocal8Bit(); return false; } - if (stale_entry->stale) { + if (stale_entry->m_stale) { qCritical() << "Cannot add stale entry: " << stale_entry->getFullPath().toLocal8Bit(); return false; } - m_entries[stale_entry->baseId].entry_list[stale_entry->relativePath] = stale_entry; + m_entries[stale_entry->m_baseId].entry_list[stale_entry->m_relativePath] = stale_entry; SaveEventually(); return true; @@ -157,7 +157,7 @@ auto HttpMetaCache::evictEntry(MetaEntryPtr entry) -> bool if (!entry) return false; - entry->stale = true; + entry->m_stale = true; SaveEventually(); return true; } @@ -169,7 +169,7 @@ void HttpMetaCache::evictAll() qDebug() << "Evicting base" << base; for (MetaEntryPtr entry : map.entry_list) { if (!evictEntry(entry)) - qWarning() << "Unexpected missing cache entry" << entry->basePath; + qWarning() << "Unexpected missing cache entry" << entry->m_basePath; } } } @@ -177,10 +177,10 @@ void HttpMetaCache::evictAll() auto HttpMetaCache::staleEntry(QString base, QString resource_path) -> MetaEntryPtr { auto foo = new MetaEntry(); - foo->baseId = base; - foo->basePath = getBasePath(base); - foo->relativePath = resource_path; - foo->stale = true; + foo->m_baseId = base; + foo->m_basePath = getBasePath(base); + foo->m_relativePath = resource_path; + foo->m_stale = true; return MetaEntryPtr(foo); } @@ -235,23 +235,23 @@ void HttpMetaCache::Load() auto& entrymap = m_entries[base]; auto foo = new MetaEntry(); - foo->baseId = base; - foo->relativePath = Json::ensureString(element_obj, "path"); - foo->md5sum = Json::ensureString(element_obj, "md5sum"); - foo->etag = Json::ensureString(element_obj, "etag"); - foo->local_changed_timestamp = Json::ensureDouble(element_obj, "last_changed_timestamp"); - foo->remote_changed_timestamp = Json::ensureString(element_obj, "remote_changed_timestamp"); - - foo->makeEternal(Json::ensureBoolean(element_obj, "eternal", false)); + foo->m_baseId = base; + foo->m_relativePath = Json::ensureString(element_obj, "path"); + foo->m_md5sum = Json::ensureString(element_obj, "md5sum"); + foo->m_etag = Json::ensureString(element_obj, "etag"); + foo->m_local_changed_timestamp = Json::ensureDouble(element_obj, "last_changed_timestamp"); + foo->m_remote_changed_timestamp = Json::ensureString(element_obj, "remote_changed_timestamp"); + + foo->makeEternal(Json::ensureBoolean(element_obj, (const QString)QStringLiteral("eternal"), false)); if (!foo->isEternal()) { - foo->current_age = Json::ensureDouble(element_obj, "current_age"); - foo->max_age = Json::ensureDouble(element_obj, "max_age"); + foo->m_current_age = Json::ensureDouble(element_obj, "current_age"); + foo->m_max_age = Json::ensureDouble(element_obj, "max_age"); } // presumed innocent until closer examination - foo->stale = false; + foo->m_stale = false; - entrymap.entry_list[foo->relativePath] = MetaEntryPtr(foo); + entrymap.entry_list[foo->m_relativePath] = MetaEntryPtr(foo); } } @@ -276,23 +276,23 @@ void HttpMetaCache::SaveNow() for (auto group : m_entries) { for (auto entry : group.entry_list) { // do not save stale entries. they are dead. - if (entry->stale) { + if (entry->m_stale) { continue; } QJsonObject entryObj; - Json::writeString(entryObj, "base", entry->baseId); - Json::writeString(entryObj, "path", entry->relativePath); - Json::writeString(entryObj, "md5sum", entry->md5sum); - Json::writeString(entryObj, "etag", entry->etag); - entryObj.insert("last_changed_timestamp", QJsonValue(double(entry->local_changed_timestamp))); - if (!entry->remote_changed_timestamp.isEmpty()) - entryObj.insert("remote_changed_timestamp", QJsonValue(entry->remote_changed_timestamp)); + Json::writeString(entryObj, "base", entry->m_baseId); + Json::writeString(entryObj, "path", entry->m_relativePath); + Json::writeString(entryObj, "md5sum", entry->m_md5sum); + Json::writeString(entryObj, "etag", entry->m_etag); + entryObj.insert("last_changed_timestamp", QJsonValue(double(entry->m_local_changed_timestamp))); + if (!entry->m_remote_changed_timestamp.isEmpty()) + entryObj.insert("remote_changed_timestamp", QJsonValue(entry->m_remote_changed_timestamp)); if (entry->isEternal()) { entryObj.insert("eternal", true); } else { - entryObj.insert("current_age", QJsonValue(double(entry->current_age))); - entryObj.insert("max_age", QJsonValue(double(entry->max_age))); + entryObj.insert("current_age", QJsonValue(double(entry->m_current_age))); + entryObj.insert("max_age", QJsonValue(double(entry->m_max_age))); } entriesArr.append(entryObj); } diff --git a/launcher/net/HttpMetaCache.h b/launcher/net/HttpMetaCache.h index 2a07d65a..37f4b49a 100644 --- a/launcher/net/HttpMetaCache.h +++ b/launcher/net/HttpMetaCache.h @@ -49,47 +49,47 @@ class MetaEntry { MetaEntry() = default; public: - auto isStale() -> bool { return stale; } - void setStale(bool stale) { this->stale = stale; } + auto isStale() -> bool { return m_stale; } + void setStale(bool stale) { m_stale = stale; } auto getFullPath() -> QString; - auto getRemoteChangedTimestamp() -> QString { return remote_changed_timestamp; } - void setRemoteChangedTimestamp(QString remote_changed_timestamp) { this->remote_changed_timestamp = remote_changed_timestamp; } - void setLocalChangedTimestamp(qint64 timestamp) { local_changed_timestamp = timestamp; } + auto getRemoteChangedTimestamp() -> QString { return m_remote_changed_timestamp; } + void setRemoteChangedTimestamp(QString remote_changed_timestamp) { m_remote_changed_timestamp = remote_changed_timestamp; } + void setLocalChangedTimestamp(qint64 timestamp) { m_local_changed_timestamp = timestamp; } - auto getETag() -> QString { return etag; } - void setETag(QString etag) { this->etag = etag; } + auto getETag() -> QString { return m_etag; } + void setETag(QString etag) { m_etag = etag; } - auto getMD5Sum() -> QString { return md5sum; } - void setMD5Sum(QString md5sum) { this->md5sum = md5sum; } + auto getMD5Sum() -> QString { return m_md5sum; } + void setMD5Sum(QString md5sum) { m_md5sum = md5sum; } /* Whether the entry expires after some time (false) or not (true). */ - void makeEternal(bool eternal) { is_eternal = eternal; } - [[nodiscard]] bool isEternal() const { return is_eternal; } + void makeEternal(bool eternal) { m_is_eternal = eternal; } + [[nodiscard]] bool isEternal() const { return m_is_eternal; } - auto getCurrentAge() -> qint64 { return current_age; } - void setCurrentAge(qint64 age) { current_age = age; } + auto getCurrentAge() -> qint64 { return m_current_age; } + void setCurrentAge(qint64 age) { m_current_age = age; } - auto getMaximumAge() -> qint64 { return max_age; } - void setMaximumAge(qint64 age) { max_age = age; } + auto getMaximumAge() -> qint64 { return m_max_age; } + void setMaximumAge(qint64 age) { m_max_age = age; } - bool isExpired(qint64 offset) { return !is_eternal && (current_age >= max_age - offset); }; + bool isExpired(qint64 offset) { return !m_is_eternal && (m_current_age >= m_max_age - offset); }; protected: - QString baseId; - QString basePath; - QString relativePath; - QString md5sum; - QString etag; - - qint64 local_changed_timestamp = 0; - QString remote_changed_timestamp; // QString for now, RFC 2822 encoded time - qint64 current_age = 0; - qint64 max_age = 0; - bool is_eternal = false; - - bool stale = true; + QString m_baseId; + QString m_basePath; + QString m_relativePath; + QString m_md5sum; + QString m_etag; + + qint64 m_local_changed_timestamp = 0; + QString m_remote_changed_timestamp; // QString for now, RFC 2822 encoded time + qint64 m_current_age = 0; + qint64 m_max_age = 0; + bool m_is_eternal = false; + + bool m_stale = true; }; using MetaEntryPtr = std::shared_ptr<MetaEntry>; diff --git a/launcher/resources/OSX/OSX.qrc b/launcher/resources/OSX/OSX.qrc index 55be28b5..19fe312b 100644 --- a/launcher/resources/OSX/OSX.qrc +++ b/launcher/resources/OSX/OSX.qrc @@ -38,5 +38,6 @@ <file>scalable/tag.svg</file> <file>scalable/export.svg</file> <file>scalable/rename.svg</file> + <file>scalable/launch.svg</file> </qresource> </RCC> diff --git a/launcher/resources/OSX/scalable/launch.svg b/launcher/resources/OSX/scalable/launch.svg new file mode 100644 index 00000000..fb189162 --- /dev/null +++ b/launcher/resources/OSX/scalable/launch.svg @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + version="1.1" + id="Calque_1" + x="0px" + y="0px" + viewBox="0 0 32.099998 32" + enable-background="new 0 0 32 32" + xml:space="preserve" + width="32.099998" + height="32" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"><defs + id="defs45" /> + +<rect + fill-rule="evenodd" + clip-rule="evenodd" + fill="none" + width="32" + height="32" + id="rect1776" + x="0.1" + y="0" /><path + fill="#b6b5b6" + d="M 30,28.4 H 2 c -1.1,0 -2,-0.9 -2,-2 v -18 c 0,-1.1 0.9,-2 2,-2 h 28 c 1.1,0 2,0.9 2,2 v 18 c 0,1.1 -0.9,2 -2,2 z" + id="path1778" /><path + fill="#fbfbfb" + d="M 30,27.4 H 2 c -1.1,0 -2,-0.9 -2,-2 v -17 c 0,-1.1 0.9,-2 2,-2 h 28 c 1.1,0 2,0.9 2,2 v 17 c 0,1.1 -0.9,2 -2,2 z" + id="path1780" /><path + id="path11889" + style="color:#000000;fill:#585858;fill-opacity:1;stroke-width:0.999994;stroke-linecap:round;stroke-linejoin:round;paint-order:markers stroke fill" + d="m 11.520289,8.4637512 c -0.06474,-0.00183 -0.129661,-0.00125 -0.194234,0.00117 C 9.7763279,8.5230552 8.3049,9.7900155 8.3049,11.461507 v 10.877094 c -6e-7,2.228655 2.616628,3.738089 4.545774,2.622156 l 9.398109,-5.436205 c 1.928423,-1.115516 1.928423,-4.133479 0,-5.248993 L 12.850674,8.839348 C 12.428673,8.595238 11.973493,8.4765655 11.520289,8.4637512 Z m -0.184873,1.2309276 c 0.307885,1.456e-4 0.612521,0.080796 0.882242,0.2363567 a 0.89871379,0.89871379 0 0 0 0.0012,0 l 9.398109,5.4362075 c 1.143234,0.659415 1.143234,2.406201 0,3.065618 l -9.398109,5.436206 a 0.89871379,0.89871379 0 0 0 -0.0012,0 C 11.076744,24.528497 9.5659235,23.656375 9.56625,22.338599 V 11.461504 c 3.043e-4,-0.851902 0.622381,-1.5925143 1.461434,-1.7399133 0.102209,-0.018038 0.205103,-0.026958 0.307732,-0.026912 z" /></svg> diff --git a/launcher/resources/OSX/scalable/launcher.svg b/launcher/resources/OSX/scalable/launcher.svg index 69dd84b1..aeee8433 100644 --- a/launcher/resources/OSX/scalable/launcher.svg +++ b/launcher/resources/OSX/scalable/launcher.svg @@ -37,17 +37,21 @@ </cc:Agent> </dc:contributor> <dc:source>https://github.com/PrismLauncher/PrismLauncher</dc:source> - <dc:rights> - <cc:Agent> - <dc:title>CC BY-SA 4.0</dc:title> - </cc:Agent> - </dc:rights> <dc:publisher> <cc:Agent> <dc:title>Prism Launcher</dc:title> </cc:Agent> </dc:publisher> + <cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/> </cc:Work> + <cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/"> + <cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/> + <cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/> + <cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/> + <cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/> + <cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/> + <cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/> + </cc:License> </rdf:RDF> </metadata> </svg> diff --git a/launcher/resources/backgrounds/backgrounds.qrc b/launcher/resources/backgrounds/backgrounds.qrc index 52921512..e55faf15 100644 --- a/launcher/resources/backgrounds/backgrounds.qrc +++ b/launcher/resources/backgrounds/backgrounds.qrc @@ -1,8 +1,17 @@ <!DOCTYPE RCC> <RCC version="1.0"> <qresource prefix="/backgrounds"> - <file alias="kitteh">catbgrnd2.png</file> - <file alias="catmas">catmas.png</file> - <file alias="cattiversary">cattiversary.png</file> + <file alias="kitteh">kitteh.png</file> + <file alias="kitteh-xmas">kitteh-xmas.png</file> + <file alias="kitteh-bday">kitteh-bday.png</file> + <file alias="kitteh-spooky">kitteh-spooky.png</file> + <file alias="rory">rory.png</file> + <file alias="rory-xmas">rory-xmas.png</file> + <file alias="rory-bday">rory-bday.png</file> + <file alias="rory-spooky">rory-spooky.png</file> + <file alias="rory-flat">rory-flat.png</file> + <file alias="rory-flat-xmas">rory-flat-xmas.png</file> + <file alias="rory-flat-bday">rory-flat-bday.png</file> + <file alias="rory-flat-spooky">rory-flat-spooky.png</file> </qresource> </RCC> diff --git a/launcher/resources/backgrounds/cattiversary.png b/launcher/resources/backgrounds/kitteh-bday.png Binary files differindex 09a36566..09a36566 100644 --- a/launcher/resources/backgrounds/cattiversary.png +++ b/launcher/resources/backgrounds/kitteh-bday.png diff --git a/launcher/resources/backgrounds/kitteh-spooky.png b/launcher/resources/backgrounds/kitteh-spooky.png Binary files differnew file mode 100644 index 00000000..deb0bebb --- /dev/null +++ b/launcher/resources/backgrounds/kitteh-spooky.png diff --git a/launcher/resources/backgrounds/catmas.png b/launcher/resources/backgrounds/kitteh-xmas.png Binary files differindex 8bdb1d5c..8bdb1d5c 100644 --- a/launcher/resources/backgrounds/catmas.png +++ b/launcher/resources/backgrounds/kitteh-xmas.png diff --git a/launcher/resources/backgrounds/catbgrnd2.png b/launcher/resources/backgrounds/kitteh.png Binary files differindex e9de7f27..e9de7f27 100644 --- a/launcher/resources/backgrounds/catbgrnd2.png +++ b/launcher/resources/backgrounds/kitteh.png diff --git a/launcher/resources/backgrounds/rory-bday.png b/launcher/resources/backgrounds/rory-bday.png Binary files differnew file mode 100644 index 00000000..66b88094 --- /dev/null +++ b/launcher/resources/backgrounds/rory-bday.png diff --git a/launcher/resources/backgrounds/rory-flat-bday.png b/launcher/resources/backgrounds/rory-flat-bday.png Binary files differnew file mode 100644 index 00000000..8a6e366d --- /dev/null +++ b/launcher/resources/backgrounds/rory-flat-bday.png diff --git a/launcher/resources/backgrounds/rory-flat-spooky.png b/launcher/resources/backgrounds/rory-flat-spooky.png Binary files differnew file mode 100644 index 00000000..6360c612 --- /dev/null +++ b/launcher/resources/backgrounds/rory-flat-spooky.png diff --git a/launcher/resources/backgrounds/rory-flat-xmas.png b/launcher/resources/backgrounds/rory-flat-xmas.png Binary files differnew file mode 100644 index 00000000..96c3ae38 --- /dev/null +++ b/launcher/resources/backgrounds/rory-flat-xmas.png diff --git a/launcher/resources/backgrounds/rory-flat.png b/launcher/resources/backgrounds/rory-flat.png Binary files differnew file mode 100644 index 00000000..ccec0662 --- /dev/null +++ b/launcher/resources/backgrounds/rory-flat.png diff --git a/launcher/resources/backgrounds/rory-spooky.png b/launcher/resources/backgrounds/rory-spooky.png Binary files differnew file mode 100644 index 00000000..a727619b --- /dev/null +++ b/launcher/resources/backgrounds/rory-spooky.png diff --git a/launcher/resources/backgrounds/rory-xmas.png b/launcher/resources/backgrounds/rory-xmas.png Binary files differnew file mode 100644 index 00000000..107feb78 --- /dev/null +++ b/launcher/resources/backgrounds/rory-xmas.png diff --git a/launcher/resources/backgrounds/rory.png b/launcher/resources/backgrounds/rory.png Binary files differnew file mode 100644 index 00000000..577f4dce --- /dev/null +++ b/launcher/resources/backgrounds/rory.png diff --git a/launcher/resources/flat/flat.qrc b/launcher/resources/flat/flat.qrc index 7f59da7b..508e0a9f 100644 --- a/launcher/resources/flat/flat.qrc +++ b/launcher/resources/flat/flat.qrc @@ -46,5 +46,6 @@ <file>scalable/tag.svg</file> <file>scalable/export.svg</file> <file>scalable/rename.svg</file> + <file>scalable/launch.svg</file> </qresource> </RCC> diff --git a/launcher/resources/flat/scalable/launch.svg b/launcher/resources/flat/scalable/launch.svg new file mode 100644 index 00000000..b462f2e4 --- /dev/null +++ b/launcher/resources/flat/scalable/launch.svg @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + fill="#757575" + height="48" + width="48" + version="1.1" + id="svg24108" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs24112" /> + <path + d="M 11.674384,43.35533 V 4.3446706 L 42.325617,23.850001 Z" + id="path24106" + style="stroke-width:1" /> +</svg> diff --git a/launcher/resources/flat/scalable/launcher.svg b/launcher/resources/flat/scalable/launcher.svg index 69dd84b1..aeee8433 100644 --- a/launcher/resources/flat/scalable/launcher.svg +++ b/launcher/resources/flat/scalable/launcher.svg @@ -37,17 +37,21 @@ </cc:Agent> </dc:contributor> <dc:source>https://github.com/PrismLauncher/PrismLauncher</dc:source> - <dc:rights> - <cc:Agent> - <dc:title>CC BY-SA 4.0</dc:title> - </cc:Agent> - </dc:rights> <dc:publisher> <cc:Agent> <dc:title>Prism Launcher</dc:title> </cc:Agent> </dc:publisher> + <cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/> </cc:Work> + <cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/"> + <cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/> + <cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/> + <cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/> + <cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/> + <cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/> + <cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/> + </cc:License> </rdf:RDF> </metadata> </svg> diff --git a/launcher/resources/flat_white/flat_white.qrc b/launcher/resources/flat_white/flat_white.qrc new file mode 100644 index 00000000..e11d6316 --- /dev/null +++ b/launcher/resources/flat_white/flat_white.qrc @@ -0,0 +1,51 @@ +<!DOCTYPE RCC> +<RCC version="1.0"> + <qresource prefix="/icons/flat_white"> + <file>index.theme</file> + <file>scalable/about.svg</file> + <file>scalable/accounts.svg</file> + <file>scalable/bug.svg</file> + <file>scalable/cat.svg</file> + <file>scalable/centralmods.svg</file> + <file>scalable/checkupdate.svg</file> + <file>scalable/copy.svg</file> + <file>scalable/coremods.svg</file> + <file>scalable/custom-commands.svg</file> + <file>scalable/discord.svg</file> + <file>scalable/externaltools.svg</file> + <file>scalable/help.svg</file> + <file>scalable/instance-settings.svg</file> + <file>scalable/jarmods.svg</file> + <file>scalable/java.svg</file> + <file>scalable/language.svg</file> + <file>scalable/launcher.svg</file> + <file>scalable/loadermods.svg</file> + <file>scalable/log.svg</file> + <file>scalable/minecraft.svg</file> + <file>scalable/new.svg</file> + <file>scalable/news.svg</file> + <file>scalable/notes.svg</file> + <file>scalable/packages.svg</file> + <file>scalable/proxy.svg</file> + <file>scalable/quickmods.svg</file> + <file>scalable/reddit-alien.svg</file> + <file>scalable/refresh.svg</file> + <file>scalable/resourcepacks.svg</file> + <file>scalable/shaderpacks.svg</file> + <file>scalable/screenshot-placeholder.svg</file> + <file>scalable/screenshots.svg</file> + <file>scalable/settings.svg</file> + <file>scalable/star.svg</file> + <file>scalable/status-bad.svg</file> + <file>scalable/status-good.svg</file> + <file>scalable/status-running.svg</file> + <file>scalable/status-yellow.svg</file> + <file>scalable/viewfolder.svg</file> + <file>scalable/worlds.svg</file> + <file>scalable/delete.svg</file> + <file>scalable/export.svg</file> + <file>scalable/rename.svg</file> + <file>scalable/tag.svg</file> + <file>scalable/launch.svg</file> + </qresource> +</RCC> diff --git a/launcher/resources/flat_white/index.theme b/launcher/resources/flat_white/index.theme new file mode 100644 index 00000000..54dd0e10 --- /dev/null +++ b/launcher/resources/flat_white/index.theme @@ -0,0 +1,11 @@ +[Icon Theme] +Name=Flat (White) +Comment=White version of the flat icons (dark mode) +Inherits=multimc +Directories=scalable + +[scalable] +Size=48 +Type=Scalable +MinSize=16 +MaxSize=256 diff --git a/launcher/resources/flat_white/scalable/about.svg b/launcher/resources/flat_white/scalable/about.svg new file mode 100644 index 00000000..e42ca948 --- /dev/null +++ b/launcher/resources/flat_white/scalable/about.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M11,9H13V7H11M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M11,17H13V11H11V17Z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/accounts.svg b/launcher/resources/flat_white/scalable/accounts.svg new file mode 100644 index 00000000..e714bde1 --- /dev/null +++ b/launcher/resources/flat_white/scalable/accounts.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M16,13C15.71,13 15.38,13 15.03,13.05C16.19,13.89 17,15 17,16.5V19H23V16.5C23,14.17 18.33,13 16,13M8,13C5.67,13 1,14.17 1,16.5V19H15V16.5C15,14.17 10.33,13 8,13M8,11A3,3 0 0,0 11,8A3,3 0 0,0 8,5A3,3 0 0,0 5,8A3,3 0 0,0 8,11M16,11A3,3 0 0,0 19,8A3,3 0 0,0 16,5A3,3 0 0,0 13,8A3,3 0 0,0 16,11Z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/bug.svg b/launcher/resources/flat_white/scalable/bug.svg new file mode 100644 index 00000000..3122702e --- /dev/null +++ b/launcher/resources/flat_white/scalable/bug.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M20 8h-2.81c-.45-.78-1.07-1.45-1.82-1.96L17 4.41 15.59 3l-2.17 2.17C12.96 5.06 12.49 5 12 5c-.49 0-.96.06-1.41.17L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8zm-6 8h-4v-2h4v2zm0-4h-4v-2h4v2z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/cat.svg b/launcher/resources/flat_white/scalable/cat.svg new file mode 100644 index 00000000..18da097a --- /dev/null +++ b/launcher/resources/flat_white/scalable/cat.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M12,8L10.67,8.09C9.81,7.07 7.4,4.5 5,4.5C5,4.5 3.03,7.46 4.96,11.41C4.41,12.24 4.07,12.67 4,13.66L2.07,13.95L2.28,14.93L4.04,14.67L4.18,15.38L2.61,16.32L3.08,17.21L4.53,16.32C5.68,18.76 8.59,20 12,20C15.41,20 18.32,18.76 19.47,16.32L20.92,17.21L21.39,16.32L19.82,15.38L19.96,14.67L21.72,14.93L21.93,13.95L20,13.66C19.93,12.67 19.59,12.24 19.04,11.41C20.97,7.46 19,4.5 19,4.5C16.6,4.5 14.19,7.07 13.33,8.09L12,8M9,11A1,1 0 0,1 10,12A1,1 0 0,1 9,13A1,1 0 0,1 8,12A1,1 0 0,1 9,11M15,11A1,1 0 0,1 16,12A1,1 0 0,1 15,13A1,1 0 0,1 14,12A1,1 0 0,1 15,11M11,14H13L12.3,15.39C12.5,16.03 13.06,16.5 13.75,16.5A1.5,1.5 0 0,0 15.25,15H15.75A2,2 0 0,1 13.75,17C13,17 12.35,16.59 12,16V16H12C11.65,16.59 11,17 10.25,17A2,2 0 0,1 8.25,15H8.75A1.5,1.5 0 0,0 10.25,16.5C10.94,16.5 11.5,16.03 11.7,15.39L11,14Z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/centralmods.svg b/launcher/resources/flat_white/scalable/centralmods.svg new file mode 100644 index 00000000..d8d10f2f --- /dev/null +++ b/launcher/resources/flat_white/scalable/centralmods.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M20 6h-8l-2-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm-2.06 11L15 15.28 12.06 17l.78-3.33-2.59-2.24 3.41-.29L15 8l1.34 3.14 3.41.29-2.59 2.24.78 3.33z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/checkupdate.svg b/launcher/resources/flat_white/scalable/checkupdate.svg new file mode 100644 index 00000000..0fa66fc2 --- /dev/null +++ b/launcher/resources/flat_white/scalable/checkupdate.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M21 10.12h-6.78l2.74-2.82c-2.73-2.7-7.15-2.8-9.88-.1-2.73 2.71-2.73 7.08 0 9.79 2.73 2.71 7.15 2.71 9.88 0C18.32 15.65 19 14.08 19 12.1h2c0 1.98-.88 4.55-2.64 6.29-3.51 3.48-9.21 3.48-12.72 0-3.5-3.47-3.53-9.11-.02-12.58 3.51-3.47 9.14-3.47 12.65 0L21 3v7.12zM12.5 8v4.25l3.5 2.08-.72 1.21L11 13V8h1.5z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/copy.svg b/launcher/resources/flat_white/scalable/copy.svg new file mode 100644 index 00000000..1aaed97b --- /dev/null +++ b/launcher/resources/flat_white/scalable/copy.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm16-4H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-1 9h-4v4h-2v-4H9V9h4V5h2v4h4v2z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/coremods.svg b/launcher/resources/flat_white/scalable/coremods.svg new file mode 100644 index 00000000..32c34383 --- /dev/null +++ b/launcher/resources/flat_white/scalable/coremods.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M22,12A10,10,0,1,1,12,2,10,10,0,0,1,22,12ZM12,4a8,8,0,1,0,8,8A8,8,0,0,0,12,4Zm4.23,14-1.12-4.82,3.73-3.23-4.92-.42L12,5,10.08,9.54,5.16,10l3.73,3.23L7.77,18,12,15.45,16.23,18"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/custom-commands.svg b/launcher/resources/flat_white/scalable/custom-commands.svg new file mode 100644 index 00000000..3d67d8f1 --- /dev/null +++ b/launcher/resources/flat_white/scalable/custom-commands.svg @@ -0,0 +1,86 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + fill="#D8DEE9" + height="24" + viewBox="0 0 24 24" + width="24" + version="1.1" + id="svg4" + sodipodi:docname="custom-commands.svg" + inkscape:version="1.1 (c4e8f9ed74, 2021-05-24)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + <metadata + id="metadata10"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs8" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="3440" + inkscape:window-height="1382" + id="namedview6" + showgrid="true" + showguides="true" + inkscape:guide-bbox="true" + inkscape:zoom="39.333333" + inkscape:cx="11.38983" + inkscape:cy="13.283898" + inkscape:window-x="0" + inkscape:window-y="32" + inkscape:window-maximized="1" + inkscape:current-layer="svg4" + inkscape:pagecheckerboard="0"> + <inkscape:grid + type="xygrid" + id="grid981" /> + <sodipodi:guide + position="-8,11.440678" + orientation="1,0" + id="guide1060" + inkscape:locked="false" /> + <sodipodi:guide + position="-28.34375,24" + orientation="0,1" + id="guide1062" + inkscape:locked="false" /> + </sodipodi:namedview> + <g + style="fill:#000000" + id="g821" + transform="matrix(0.0322459,0,0,0.0322459,-17.878956,30.647558)"> + <g + style="fill:#000000" + id="g819" /> + </g> + <g + id="g503"> + <path + d="M 0,0 H 24 V 24 H 0 Z" + fill="none" + id="path491" /> + <path + d="M 8.7,15.9 4.8,12 8.7,8.1 C 9.09,7.71 9.09,7.09 8.7,6.7 8.31,6.31 7.69,6.31 7.3,6.7 l -4.59,4.59 c -0.39,0.39 -0.39,1.02 0,1.41 l 4.59,4.6 c 0.39,0.39 1.01,0.39 1.4,0 0.39,-0.39 0.39,-1.01 0,-1.4 z m 6.6,0 3.9,-3.9 -3.9,-3.9 c -0.39,-0.39 -0.39,-1.01 0,-1.4 0.39,-0.39 1.01,-0.39 1.4,0 l 4.59,4.59 c 0.39,0.39 0.39,1.02 0,1.41 l -4.59,4.6 c -0.39,0.39 -1.01,0.39 -1.4,0 -0.39,-0.39 -0.39,-1.01 0,-1.4 z" + id="path493" /> + </g> +</svg> diff --git a/launcher/resources/flat_white/scalable/delete.svg b/launcher/resources/flat_white/scalable/delete.svg new file mode 100644 index 00000000..3365a96f --- /dev/null +++ b/launcher/resources/flat_white/scalable/delete.svg @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" version="1.1" id="svg4" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> + <defs id="defs8"/> + <path id="path5145" style="fill:#D8DEE9;fill-opacity:1;stroke:none;stroke-width:1.4;stroke-linecap:square;stroke-linejoin:miter;stroke-dasharray:none" d="M 7.1074219 0.96484375 L 7.1074219 2.7304688 L 2.6132812 2.7304688 L 2.6132812 4.4960938 L 21.386719 4.4960938 L 21.386719 2.7304688 L 16.892578 2.7304688 L 16.892578 0.96484375 L 7.1074219 0.96484375 z M 5.0058594 6.0839844 L 5.0058594 22.859375 L 18.994141 22.859375 L 18.994141 6.0839844 L 5.0058594 6.0839844 z M 7.9160156 9.0605469 L 10.193359 9.0605469 L 10.193359 19.882812 L 7.9160156 19.882812 L 7.9160156 9.0605469 z M 13.804688 9.0605469 L 16.085938 9.0605469 L 16.085938 19.882812 L 13.804688 19.882812 L 13.804688 9.0605469 z "/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/discord.svg b/launcher/resources/flat_white/scalable/discord.svg new file mode 100644 index 00000000..ee07ed25 --- /dev/null +++ b/launcher/resources/flat_white/scalable/discord.svg @@ -0,0 +1,4 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M10.14,11.63a1.15,1.15,0,1,0,1,1.14A1.1,1.1,0,0,0,10.14,11.63Zm3.75,0a1.15,1.15,0,1,0,1,1.14A1.1,1.1,0,0,0,13.89,11.63Z"/> + <path d="M18.89,3H5.11A2.11,2.11,0,0,0,3,5.12V19a2.11,2.11,0,0,0,2.11,2.12H16.77l-.55-1.9,1.32,1.22,1.24,1.15,2.21,2V5.12A2.11,2.11,0,0,0,18.89,3Zm-4,13.43s-.37-.44-.68-.83a3.25,3.25,0,0,0,1.86-1.22,5.89,5.89,0,0,1-1.18.61,6.77,6.77,0,0,1-1.49.44,7.21,7.21,0,0,1-2.66,0A8.63,8.63,0,0,1,9.25,15a6,6,0,0,1-.75-.35l-.09-.05,0,0-.29-.17a3.2,3.2,0,0,0,1.8,1.21l-.69.85a3.73,3.73,0,0,1-3.14-1.56,13.77,13.77,0,0,1,1.48-6,5.09,5.09,0,0,1,2.89-1.08l.1.12A6.94,6.94,0,0,0,7.82,9.26s.23-.12.61-.3a7.72,7.72,0,0,1,2.33-.65l.17,0a8.7,8.7,0,0,1,2.08,0,8.38,8.38,0,0,1,3.1,1A6.85,6.85,0,0,0,13.55,8l.14-.16a5.09,5.09,0,0,1,2.89,1.08,13.77,13.77,0,0,1,1.48,6A3.76,3.76,0,0,1,14.92,16.43Z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/export.svg b/launcher/resources/flat_white/scalable/export.svg new file mode 100644 index 00000000..a28bb254 --- /dev/null +++ b/launcher/resources/flat_white/scalable/export.svg @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" version="1.1" id="svg4" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> + <defs id="defs8"/> + <path id="path2" d="M 4 4 C 2.9000011 4 2.0097656 4.9000011 2.0097656 6 L 2 18 C 2 19.099999 2.9000011 20 4 20 L 20 20 C 21.099999 20 22 19.099999 22 18 L 22 8 C 22 6.9000011 21.099999 6 20 6 L 12 6 L 10 4 L 4 4 z M 12.537109 9.9082031 L 13.185547 10.298828 L 17.554688 12.929688 L 13.185547 15.5625 L 12.537109 15.953125 L 11.755859 14.65625 L 12.404297 14.265625 L 13.349609 13.695312 L 6.4453125 13.695312 L 6.4453125 12.166016 L 13.351562 12.166016 L 12.404297 11.595703 L 11.755859 11.205078 L 12.537109 9.9082031 z "/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/externaltools.svg b/launcher/resources/flat_white/scalable/externaltools.svg new file mode 100644 index 00000000..e7c0930c --- /dev/null +++ b/launcher/resources/flat_white/scalable/externaltools.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M22.7,19L13.6,9.9C14.5,7.6 14,4.9 12.1,3C10.1,1 7.1,0.6 4.7,1.7L9,6L6,9L1.6,4.7C0.4,7.1 0.9,10.1 2.9,12.1C4.8,14 7.5,14.5 9.8,13.6L18.9,22.7C19.3,23.1 19.9,23.1 20.3,22.7L22.6,20.4C23.1,20 23.1,19.3 22.7,19Z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/help.svg b/launcher/resources/flat_white/scalable/help.svg new file mode 100644 index 00000000..82b413fe --- /dev/null +++ b/launcher/resources/flat_white/scalable/help.svg @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + id="svg4" + version="1.1" + width="24" + viewBox="0 0 24 24" + height="24" + fill="#D8DEE9"> + <path + d="m 15.07,11.25 -0.9,0.92 C 13.45,12.89 13,13.5 13,15 h -2 v -0.5 c 0,-1.11 0.45,-2.11 1.17,-2.83 l 1.24,-1.26 C 13.78,10.049999 14,9.549999 14,9 14,7.89 13.100001,7 12,7 A 2,2 0 0 0 10,9 H 7.9999995 A 4,4 0 0 1 12,5 4,4 0 0 1 16,9 c 0,0.879999 -0.36,1.67 -0.93,2.25 M 13,19 h -2 v -2 h 2 M 12,2 A 10,10 0 0 0 1.9999995,12 10,10 0 0 0 12,22 10,10 0 0 0 22,12 C 22,6.47 17.5,2 12,2 Z" + id="path817" /> +</svg> diff --git a/launcher/resources/flat_white/scalable/instance-settings.svg b/launcher/resources/flat_white/scalable/instance-settings.svg new file mode 100644 index 00000000..7dac7b14 --- /dev/null +++ b/launcher/resources/flat_white/scalable/instance-settings.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/jarmods.svg b/launcher/resources/flat_white/scalable/jarmods.svg new file mode 100644 index 00000000..f0f298f3 --- /dev/null +++ b/launcher/resources/flat_white/scalable/jarmods.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M2,21H20V19H2M20,8H18V5h2m0-2H4V13a4,4,0,0,0,4,4h6a4,4,0,0,0,4-4V10h2a2,2,0,0,0,2-2V5A2,2,0,0,0,20,3ZM11,4.43l1.62,3.29,3.63.53-2.63,2.56.62,3.62L11,12.72,7.75,14.43l.62-3.62L5.74,8.25l3.63-.53Z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/java.svg b/launcher/resources/flat_white/scalable/java.svg new file mode 100644 index 00000000..56bb481f --- /dev/null +++ b/launcher/resources/flat_white/scalable/java.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M2,21H20V19H2M20,8H18V5H20M20,3H4V13A4,4 0 0,0 8,17H14A4,4 0 0,0 18,13V10H20A2,2 0 0,0 22,8V5C22,3.89 21.1,3 20,3Z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/language.svg b/launcher/resources/flat_white/scalable/language.svg new file mode 100644 index 00000000..18c22efb --- /dev/null +++ b/launcher/resources/flat_white/scalable/language.svg @@ -0,0 +1,103 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + fill="#D8DEE9" + height="24" + viewBox="0 0 24 24" + width="24" + version="1.1" + id="svg4" + sodipodi:docname="language.svg" + inkscape:version="0.92.2 2405546, 2018-03-11"> + <metadata + id="metadata10"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs8"> + + + + + + + + + + + + + + + + + + + + + + + </defs> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="3840" + inkscape:window-height="2123" + id="namedview6" + showgrid="true" + showguides="true" + inkscape:guide-bbox="true" + inkscape:zoom="6.9532167" + inkscape:cx="-18.49351" + inkscape:cy="-12.477971" + inkscape:window-x="1200" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="svg4"> + <inkscape:grid + type="xygrid" + id="grid981" /> + <sodipodi:guide + position="-8,11.440678" + orientation="1,0" + id="guide1060" + inkscape:locked="false" /> + <sodipodi:guide + position="-28.34375,24" + orientation="0,1" + id="guide1062" + inkscape:locked="false" /> + </sodipodi:namedview> + <path + d="M 5,3 C 3.89,3 3,3.89 3,5 v 14 c 0,1.104569 0.895431,2 2,2 h 14 c 1.104569,0 2,-0.895431 2,-2 V 5 C 21,3.89 20.1,3 19,3 Z m 10.359375,4.505859 c 0.400344,0 0.726563,0.326845 0.726563,0.728516 v 0.724609 h 2.21875 c 0.400344,0 0.726562,0.326845 0.726562,0.728516 0,0.401669 -0.326217,0.726562 -0.726562,0.726562 h -0.191407 c -0.412128,1.326612 -1.066893,2.363281 -1.746093,3.181641 0.53207,0.488052 1.100334,0.887517 1.666015,1.335938 0.311924,0.250518 0.365651,0.709744 0.115235,1.023437 -0.249259,0.313205 -0.708226,0.362473 -1.019532,0.111328 -0.614642,-0.486742 -1.192601,-0.892661 -1.769531,-1.423828 -0.576929,0.531167 -1.104107,0.937086 -1.71875,1.423828 -0.311303,0.251148 -0.768322,0.201879 -1.017578,-0.111328 -0.250416,-0.313693 -0.200604,-0.772919 0.111328,-1.023437 0.565677,-0.448421 1.087072,-0.847886 1.619141,-1.335938 -0.679199,-0.818312 -1.283234,-1.854981 -1.695313,-3.181641 h -0.197265 c -0.400344,0 -0.720704,-0.324893 -0.720704,-0.726562 0,-0.401671 0.320361,-0.728516 0.720704,-0.728516 h 2.173828 V 8.234375 c 0,-0.401671 0.324264,-0.728516 0.724609,-0.728516 z M 7.142578,7.800781 h 1.496094 c 0.345155,0 0.643047,0.243927 0.710937,0.582031 l 1.447266,7.244141 c 0.07851,0.39257 -0.174512,0.773053 -0.566406,0.851563 -0.384074,0.07905 -0.774336,-0.168718 -0.853516,-0.566407 L 8.914062,13.595703 H 6.867188 L 6.402344,15.912109 C 6.324551,16.303955 5.947553,16.559826 5.550781,16.478516 5.158934,16.400005 4.905865,16.019523 4.984375,15.626953 L 6.431641,8.382812 C 6.499536,8.044708 6.797425,7.800781 7.142578,7.800781 Z m 0.4375,1.632813 -0.578125,2.898437 H 8.46875 L 7.890625,9.433594 Z m 6.589844,0.980468 c 0.312752,0.842923 0.729088,1.524886 1.189453,2.105469 0.460366,-0.580583 0.925527,-1.262594 1.238281,-2.105469 z" + id="path1072" + inkscape:connector-curvature="0" /> + <g + style="fill:#000000" + id="g821" + transform="matrix(0.0322459,0,0,0.0322459,-17.878956,30.647558)"> + <g + style="fill:#000000" + id="g819" /> + </g> +</svg> diff --git a/launcher/resources/flat_white/scalable/launch.svg b/launcher/resources/flat_white/scalable/launch.svg new file mode 100644 index 00000000..9e759431 --- /dev/null +++ b/launcher/resources/flat_white/scalable/launch.svg @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + fill="#D8DEE9" + height="48" + width="48" + version="1.1" + id="svg24108" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs24112" /> + <path + d="M 11.674384,43.35533 V 4.3446706 L 42.325617,23.850001 Z" + id="path24106" + style="stroke-width:1" /> +</svg> diff --git a/launcher/resources/flat_white/scalable/launcher.svg b/launcher/resources/flat_white/scalable/launcher.svg new file mode 100644 index 00000000..d7ad0dd3 --- /dev/null +++ b/launcher/resources/flat_white/scalable/launcher.svg @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="24" height="24" fill="#D8DEE9" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m20 4h-16v16h16zm0 18h-16c-1.1046 0-2-0.89543-2-2v-16c0-1.1046 0.89543-2 2-2h16c1.1046 0 2 0.89543 2 2v16c0 1.1046-0.89543 2-2 2z"/><path d="m7.2 18c-0.225 0-0.45-0.075-0.6-0.15-0.375-0.225-0.6-0.6-0.6-1.05v-9.6c0-0.45 0.225-0.825 0.6-1.05 0.225-0.15 0.375-0.15 0.6-0.15 0.15 0 0.375 0.075 0.525 0.15l9.6 4.8c0.375 0.225 0.675 0.6 0.675 1.05 0 0.45-0.225 0.9-0.675 1.05l-9.6 4.8c-0.15 0.075-0.375 0.15-0.525 0.15z" clip-rule="evenodd" fill="#D8DEE9" fill-rule="evenodd" stroke-width=".99999"/></svg> diff --git a/launcher/resources/flat_white/scalable/loadermods.svg b/launcher/resources/flat_white/scalable/loadermods.svg new file mode 100644 index 00000000..100f7a93 --- /dev/null +++ b/launcher/resources/flat_white/scalable/loadermods.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M16.23,18l-1.12-4.82,3.73-3.23-4.92-.42L12,5,10.08,9.54,5.16,10l3.73,3.23L7.77,18,12,15.45,16.23,18M20,4H4V20H20Zm0,18H4a2,2,0,0,1-2-2V4A2,2,0,0,1,4,2H20a2,2,0,0,1,2,2V20A2,2,0,0,1,20,22Z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/log.svg b/launcher/resources/flat_white/scalable/log.svg new file mode 100644 index 00000000..69b7c1dc --- /dev/null +++ b/launcher/resources/flat_white/scalable/log.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/minecraft.svg b/launcher/resources/flat_white/scalable/minecraft.svg new file mode 100644 index 00000000..a0348e79 --- /dev/null +++ b/launcher/resources/flat_white/scalable/minecraft.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M21,16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V7.5C3,7.12 3.21,6.79 3.53,6.62L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.79,6.79 21,7.12 21,7.5V16.5M12,4.15L6.04,7.5L12,10.85L17.96,7.5L12,4.15M5,15.91L11,19.29V12.58L5,9.21V15.91M19,15.91V9.21L13,12.58V19.29L19,15.91Z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/multimc.svg b/launcher/resources/flat_white/scalable/multimc.svg new file mode 100644 index 00000000..3dce2699 --- /dev/null +++ b/launcher/resources/flat_white/scalable/multimc.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M0,2A2,2,0,0,1,2,0H22a2,2,0,0,1,2,2V8L22,8V6H20V8H18v2H16V8H14V6H12V8H10V6H8v4H6V8H4V6H2V8H0ZM0,22a2,2,0,0,0,2,2H22a2,2,0,0,0,2-2V10H22V22H2V10H0Zm18.71-3.29a3.83,3.83,0,0,0-5.41-5.41L12,14.59l-1.29-1.29a3.83,3.83,0,1,0,0,5.41L12,17.41l1.29,1.29a3.83,3.83,0,0,0,5.41,0Zm-4-4a1.83,1.83,0,1,1,0,2.59L13.41,16Zm-5.41,0L10.59,16,9.29,17.29a1.83,1.83,0,1,1,0-2.59Z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/new.svg b/launcher/resources/flat_white/scalable/new.svg new file mode 100644 index 00000000..46dc3361 --- /dev/null +++ b/launcher/resources/flat_white/scalable/new.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-2 10h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/news.svg b/launcher/resources/flat_white/scalable/news.svg new file mode 100644 index 00000000..414e5454 --- /dev/null +++ b/launcher/resources/flat_white/scalable/news.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M20,11H4V8H20M20,15H13V13H20M20,19H13V17H20M11,19H4V13H11M20.33,4.67L18.67,3L17,4.67L15.33,3L13.67,4.67L12,3L10.33,4.67L8.67,3L7,4.67L5.33,3L3.67,4.67L2,3V19A2,2 0 0,0 4,21H20A2,2 0 0,0 22,19V3L20.33,4.67Z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/notes.svg b/launcher/resources/flat_white/scalable/notes.svg new file mode 100644 index 00000000..4ce5f6f1 --- /dev/null +++ b/launcher/resources/flat_white/scalable/notes.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M14,17H7V15H14M17,13H7V11H17M17,9H7V7H17M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3Z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/packages.svg b/launcher/resources/flat_white/scalable/packages.svg new file mode 100644 index 00000000..909ad0b2 --- /dev/null +++ b/launcher/resources/flat_white/scalable/packages.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M5.12,5l.81-1h12l.94,1m1.67.23L19.15,3.55A1.45,1.45,0,0,0,18,3H6a1.49,1.49,0,0,0-1.16.55L3.46,5.23A1.92,1.92,0,0,0,3,6.5V19a2,2,0,0,0,2,2H19a2,2,0,0,0,2-2V6.5A1.92,1.92,0,0,0,20.54,5.23Z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/patreon.svg b/launcher/resources/flat_white/scalable/patreon.svg new file mode 100644 index 00000000..b745765b --- /dev/null +++ b/launcher/resources/flat_white/scalable/patreon.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M12,3h0a9,9,0,0,0-9,9v9H5.09V12a6.91,6.91,0,1,1,7.23,6.9,5.9,5.9,0,0,1-2.59-.47v-3A4.13,4.13,0,1,0,7.85,12v9H12A9,9,0,1,0,12,3Zm0,15.91h0Z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/proxy.svg b/launcher/resources/flat_white/scalable/proxy.svg new file mode 100644 index 00000000..a86703f4 --- /dev/null +++ b/launcher/resources/flat_white/scalable/proxy.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M22 4v-.5C22 2.12 20.88 1 19.5 1S17 2.12 17 3.5V4c-.55 0-1 .45-1 1v4c0 .55.45 1 1 1h5c.55 0 1-.45 1-1V5c0-.55-.45-1-1-1zm-.8 0h-3.4v-.5c0-.94.76-1.7 1.7-1.7s1.7.76 1.7 1.7V4zm-2.28 8c.04.33.08.66.08 1 0 2.08-.8 3.97-2.1 5.39-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H7v-2h2c.55 0 1-.45 1-1V8h2c1.1 0 2-.9 2-2V3.46c-.95-.3-1.95-.46-3-.46C5.48 3 1 7.48 1 13s4.48 10 10 10 10-4.48 10-10c0-.34-.02-.67-.05-1h-2.03zM10 20.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L8 16v1c0 1.1.9 2 2 2v1.93z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/quickmods.svg b/launcher/resources/flat_white/scalable/quickmods.svg new file mode 100644 index 00000000..9e0045b2 --- /dev/null +++ b/launcher/resources/flat_white/scalable/quickmods.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M8.75,5.5v7.15H10.7V18.5l4.55-7.8h-2.6l2.6-5.2ZM20,4H4V20H20Zm0,18H4a2,2,0,0,1-2-2V4A2,2,0,0,1,4,2H20a2,2,0,0,1,2,2V20A2,2,0,0,1,20,22Z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/reddit-alien.svg b/launcher/resources/flat_white/scalable/reddit-alien.svg new file mode 100644 index 00000000..be22148c --- /dev/null +++ b/launcher/resources/flat_white/scalable/reddit-alien.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M20,11.86a1.76,1.76,0,0,0-1.75-1.75,1.73,1.73,0,0,0-1.22.51,9,9,0,0,0-4.67-1.38l1-3.16L16,6.71s0,0,0,0a1.46,1.46,0,1,0,.1-.53l-2.89-.68a.25.25,0,0,0-.29.17L11.83,9.23a9.16,9.16,0,0,0-4.88,1.36,1.75,1.75,0,1,0-2.07,2.79,3,3,0,0,0-.06.58C4.82,16.58,8,18.7,12,18.7s7.14-2.13,7.14-4.74a2.94,2.94,0,0,0-.05-.55A1.74,1.74,0,0,0,20,11.86ZM8.51,13.08a1.06,1.06,0,1,1,1.06,1.06A1.06,1.06,0,0,1,8.51,13.08Zm6.06,3.14A3.48,3.48,0,0,1,12,17h0a3.48,3.48,0,0,1-2.56-.79.25.25,0,0,1,.35-.35,3,3,0,0,0,2.2.65h0a3,3,0,0,0,2.2-.65.25.25,0,1,1,.35.35Zm-.13-2.08a1.06,1.06,0,1,1,1.06-1.06A1.06,1.06,0,0,1,14.44,14.14Z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/refresh.svg b/launcher/resources/flat_white/scalable/refresh.svg new file mode 100644 index 00000000..08c63bdf --- /dev/null +++ b/launcher/resources/flat_white/scalable/refresh.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M17.65,6.35C16.2,4.9 14.21,4 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20C15.73,20 18.84,17.45 19.73,14H17.65C16.83,16.33 14.61,18 12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6C13.66,6 15.14,6.69 16.22,7.78L13,11H20V4L17.65,6.35Z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/rename.svg b/launcher/resources/flat_white/scalable/rename.svg new file mode 100644 index 00000000..f2067c16 --- /dev/null +++ b/launcher/resources/flat_white/scalable/rename.svg @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" version="1.1" id="svg4" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"><defs id="defs6"/> + +<path id="rect345" style="stroke-width:1.40001;stroke-linecap:square" d="m 16.944458,1.2305698 -2.249239,2.2492384 5.824973,5.8249729 2.249239,-2.2492385 a 0.74206194,0.74206194 90.000002 0 0 0,-1.0494341 L 17.993892,1.2305699 a 0.74206211,0.74206211 2.5523302e-6 0 0 -1.049434,-1e-7 z M 13.645389,4.5296385 3.5726925,14.602335 9.3976653,20.427306 19.470362,10.35461 Z M 2.5228612,15.652165 1.0244287,22.411695 a 0.47276304,0.47276304 44.999993 0 0 0.5638754,0.563875 l 6.75953,-1.498434 z"/></svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/resourcepacks.svg b/launcher/resources/flat_white/scalable/resourcepacks.svg new file mode 100644 index 00000000..9dd73c3a --- /dev/null +++ b/launcher/resources/flat_white/scalable/resourcepacks.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M19.51 3.08L3.08 19.51c.09.34.27.65.51.9.25.24.56.42.9.51L20.93 4.49c-.19-.69-.73-1.23-1.42-1.41zM11.88 3L3 11.88v2.83L14.71 3h-2.83zM5 3c-1.1 0-2 .9-2 2v2l4-4H5zm14 18c.55 0 1.05-.22 1.41-.59.37-.36.59-.86.59-1.41v-2l-4 4h2zm-9.71 0h2.83L21 12.12V9.29L9.29 21z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/screenshot-placeholder.svg b/launcher/resources/flat_white/scalable/screenshot-placeholder.svg new file mode 100644 index 00000000..41eb6fcf --- /dev/null +++ b/launcher/resources/flat_white/scalable/screenshot-placeholder.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M21 15h2v2h-2v-2zm0-4h2v2h-2v-2zm2 8h-2v2c1 0 2-1 2-2zM13 3h2v2h-2V3zm8 4h2v2h-2V7zm0-4v2h2c0-1-1-2-2-2zM1 7h2v2H1V7zm16-4h2v2h-2V3zm0 16h2v2h-2v-2zM3 3C2 3 1 4 1 5h2V3zm6 0h2v2H9V3zM5 3h2v2H5V3zm-4 8v8c0 1.1.9 2 2 2h12V11H1zm2 8l2.5-3.21 1.79 2.15 2.5-3.22L13 19H3z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/screenshots.svg b/launcher/resources/flat_white/scalable/screenshots.svg new file mode 100644 index 00000000..68cf8969 --- /dev/null +++ b/launcher/resources/flat_white/scalable/screenshots.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M21 3H3C2 3 1 4 1 5v14c0 1.1.9 2 2 2h18c1 0 2-1 2-2V5c0-1-1-2-2-2zM5 17l3.5-4.5 2.5 3.01L14.5 11l4.5 6H5z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/settings.svg b/launcher/resources/flat_white/scalable/settings.svg new file mode 100644 index 00000000..7dac7b14 --- /dev/null +++ b/launcher/resources/flat_white/scalable/settings.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/shaderpacks.svg b/launcher/resources/flat_white/scalable/shaderpacks.svg new file mode 100644 index 00000000..ccae221c --- /dev/null +++ b/launcher/resources/flat_white/scalable/shaderpacks.svg @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + fill="#D8DEE9" + height="24" + viewBox="0 0 24 24" + width="24" + version="1.1" + id="svg4" + sodipodi:docname="shaderpacks.svg" + inkscape:version="1.1 (c4e8f9ed74, 2021-05-24)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs8" /> + <sodipodi:namedview + id="namedview6" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + showgrid="false" + inkscape:zoom="9.7227182" + inkscape:cx="16.302025" + inkscape:cy="3.1884088" + inkscape:window-width="3840" + inkscape:window-height="2129" + inkscape:window-x="1200" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="svg4"> + <inkscape:grid + type="xygrid" + id="grid974" /> + </sodipodi:namedview> + <path + id="path2" + d="M 5 3 C 3.9 3 3 3.9 3 5 L 3 7 L 5 5 L 7 3 L 5 3 z M 11.880859 3 L 9.8808594 5 L 12.710938 5 L 14.710938 3 L 11.880859 3 z M 19.509766 3.0800781 L 17.589844 5 L 19 5 L 19 6.4179688 L 20.929688 4.4902344 C 20.739687 3.8002344 20.199766 3.2600781 19.509766 3.0800781 z M 21 9.2890625 L 19 11.289062 L 19 14.119141 L 21 12.119141 L 21 9.2890625 z M 5 9.8808594 L 3 11.880859 L 3 14.710938 L 5 12.710938 L 5 9.8808594 z M 21 17 L 19 19 L 17 21 L 19 21 C 19.55 21 20.050156 20.780156 20.410156 20.410156 C 20.780156 20.050156 21 19.55 21 19 L 21 17 z M 5 17.589844 L 3.0800781 19.509766 C 3.1700781 19.849766 3.3498438 20.160156 3.5898438 20.410156 C 3.8398438 20.650156 4.1502344 20.829922 4.4902344 20.919922 L 6.4121094 19 L 5 19 L 5 17.589844 z M 11.289062 19 L 9.2890625 21 L 12.119141 21 L 14.119141 19 L 11.289062 19 z " /> + <path + style="fill:none;fill-rule:evenodd;stroke:#D8DEE9;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" + d="m 7,9 v 8 h 8 V 9 Z" + id="path6727" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#D8DEE9;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" + d="M 7,9 9,7 h 8 l -2,2 z" + id="path7008" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#D8DEE9;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" + d="m 17,7 v 8 l -2,2 V 9 Z" + id="path7010" + sodipodi:nodetypes="ccccc" /> +</svg> diff --git a/launcher/resources/flat_white/scalable/star.svg b/launcher/resources/flat_white/scalable/star.svg new file mode 100644 index 00000000..116f952e --- /dev/null +++ b/launcher/resources/flat_white/scalable/star.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M12,17.27L18.18,21L16.54,13.97L22,9.24L14.81,8.62L12,2L9.19,8.62L2,9.24L7.45,13.97L5.82,21L12,17.27Z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/status-bad.svg b/launcher/resources/flat_white/scalable/status-bad.svg new file mode 100644 index 00000000..5a121c09 --- /dev/null +++ b/launcher/resources/flat_white/scalable/status-bad.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M12,2A10,10,0,1,0,22,12,10,10,0,0,0,12,2Zm4.24,7.17L13.41,12l2.83,2.83-1.41,1.41L12,13.41,9.17,16.24,7.76,14.83,10.59,12,7.76,9.17,9.17,7.76,12,10.59l2.83-2.83Z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/status-good.svg b/launcher/resources/flat_white/scalable/status-good.svg new file mode 100644 index 00000000..ccb732ab --- /dev/null +++ b/launcher/resources/flat_white/scalable/status-good.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/status-running.svg b/launcher/resources/flat_white/scalable/status-running.svg new file mode 100644 index 00000000..aa2d5fbf --- /dev/null +++ b/launcher/resources/flat_white/scalable/status-running.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 14.5v-9l6 4.5-6 4.5z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/status-yellow.svg b/launcher/resources/flat_white/scalable/status-yellow.svg new file mode 100644 index 00000000..772699d3 --- /dev/null +++ b/launcher/resources/flat_white/scalable/status-yellow.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/tag.svg b/launcher/resources/flat_white/scalable/tag.svg new file mode 100644 index 00000000..f91fb0b4 --- /dev/null +++ b/launcher/resources/flat_white/scalable/tag.svg @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" version="1.1" id="svg4" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> + <path style="fill:#D8DEE9;fill-opacity:1;stroke:none;stroke-width:0.999999" d="M 12,2.4960934 2.5451968,2.5451969 2.4960933,12 12.791992,21.503907 21.503907,12.791992 Z M 8.0400388,5.6640622 c 1.3115393,0 2.3759772,1.0644375 2.3759772,2.3759766 0,1.3115391 -1.0644379,2.3759772 -2.3759772,2.3759772 -1.3115392,0 -2.3759767,-1.0644381 -2.3759767,-2.3759772 0,-1.3115391 1.0644375,-2.3759766 2.3759767,-2.3759766" class="ColorScheme-Text" id="path4838"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/viewfolder.svg b/launcher/resources/flat_white/scalable/viewfolder.svg new file mode 100644 index 00000000..145f8624 --- /dev/null +++ b/launcher/resources/flat_white/scalable/viewfolder.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/worlds.svg b/launcher/resources/flat_white/scalable/worlds.svg new file mode 100644 index 00000000..cea30cf8 --- /dev/null +++ b/launcher/resources/flat_white/scalable/worlds.svg @@ -0,0 +1,3 @@ +<svg fill="#D8DEE9" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> + <path d="M18.2,13a3.18,3.18,0,0,1-.84,2.16.8.8,0,0,0-.76-.56h-.4V13.4a.4.4,0,0,0-.4-.4H13.4v-.8h.8a.4.4,0,0,0,.4-.4V11h.8a.8.8,0,0,0,.8-.8V10A3.19,3.19,0,0,1,18.2,13Zm-4.4,1.6v-.4l-1.92-1.92a3.18,3.18,0,0,0,2.72,3.89V15.4A.8.8,0,0,1,13.8,14.6ZM22,8V18a2,2,0,0,1-2,2H4a2,2,0,0,1-2-2V6A2,2,0,0,1,4,4h6l2,2h8A2,2,0,0,1,22,8Zm-3,5a4,4,0,1,0-4,4A4,4,0,0,0,19,13Z"/> +</svg>
\ No newline at end of file diff --git a/launcher/resources/iOS/iOS.qrc b/launcher/resources/iOS/iOS.qrc index 1d752042..aa08d811 100644 --- a/launcher/resources/iOS/iOS.qrc +++ b/launcher/resources/iOS/iOS.qrc @@ -38,5 +38,6 @@ <file>scalable/tag.svg</file> <file>scalable/export.svg</file> <file>scalable/rename.svg</file> + <file>scalable/launch.svg</file> </qresource> </RCC> diff --git a/launcher/resources/iOS/scalable/launch.svg b/launcher/resources/iOS/scalable/launch.svg new file mode 100644 index 00000000..c16d5c37 --- /dev/null +++ b/launcher/resources/iOS/scalable/launch.svg @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + version="1.1" + id="Calque_1" + x="0px" + y="0px" + viewBox="0 0 32 32" + enable-background="new 0 0 32 32" + xml:space="preserve" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"><defs + id="defs45" /> + +<path + id="path11889" + style="color:#000000;fill:#3366cc;fill-opacity:1;stroke-width:0.999996;stroke-linecap:round;stroke-linejoin:round;paint-order:markers stroke fill" + d="M 9.3515625 1.5410156 C 9.2434917 1.5379599 9.1351284 1.5389257 9.0273438 1.5429688 C 6.4405102 1.6400023 3.9843749 3.7548369 3.984375 6.5449219 L 3.984375 24.701172 C 3.9843742 28.421285 8.3520988 30.94086 11.572266 29.078125 L 27.259766 20.003906 C 30.478726 18.141866 30.478727 13.104225 27.259766 11.242188 L 11.572266 2.1679688 C 10.867854 1.7604958 10.108058 1.5624055 9.3515625 1.5410156 z M 9.0429688 3.5957031 C 9.5568946 3.5959458 10.065401 3.7305695 10.515625 3.9902344 A 1.50015 1.50015 0 0 0 10.517578 3.9902344 L 26.205078 13.064453 C 28.113387 14.165162 28.113387 17.080931 26.205078 18.181641 L 10.517578 27.255859 A 1.50015 1.50015 0 0 0 10.515625 27.255859 C 8.6111878 28.356592 6.0892988 26.90083 6.0898438 24.701172 L 6.0898438 6.5449219 C 6.090352 5.1229119 7.128734 3.8866658 8.5292969 3.640625 C 8.6999053 3.6105103 8.87166 3.5956222 9.0429688 3.5957031 z " /></svg> diff --git a/launcher/resources/iOS/scalable/launcher.svg b/launcher/resources/iOS/scalable/launcher.svg index 69dd84b1..aeee8433 100644 --- a/launcher/resources/iOS/scalable/launcher.svg +++ b/launcher/resources/iOS/scalable/launcher.svg @@ -37,17 +37,21 @@ </cc:Agent> </dc:contributor> <dc:source>https://github.com/PrismLauncher/PrismLauncher</dc:source> - <dc:rights> - <cc:Agent> - <dc:title>CC BY-SA 4.0</dc:title> - </cc:Agent> - </dc:rights> <dc:publisher> <cc:Agent> <dc:title>Prism Launcher</dc:title> </cc:Agent> </dc:publisher> + <cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/> </cc:Work> + <cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/"> + <cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/> + <cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/> + <cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/> + <cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/> + <cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/> + <cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/> + </cc:License> </rdf:RDF> </metadata> </svg> diff --git a/launcher/resources/multimc/scalable/launcher.svg b/launcher/resources/multimc/scalable/launcher.svg index 69dd84b1..aeee8433 100644 --- a/launcher/resources/multimc/scalable/launcher.svg +++ b/launcher/resources/multimc/scalable/launcher.svg @@ -37,17 +37,21 @@ </cc:Agent> </dc:contributor> <dc:source>https://github.com/PrismLauncher/PrismLauncher</dc:source> - <dc:rights> - <cc:Agent> - <dc:title>CC BY-SA 4.0</dc:title> - </cc:Agent> - </dc:rights> <dc:publisher> <cc:Agent> <dc:title>Prism Launcher</dc:title> </cc:Agent> </dc:publisher> + <cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/> </cc:Work> + <cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/"> + <cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/> + <cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/> + <cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/> + <cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/> + <cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/> + <cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/> + </cc:License> </rdf:RDF> </metadata> </svg> diff --git a/launcher/resources/pe_blue/pe_blue.qrc b/launcher/resources/pe_blue/pe_blue.qrc index 3d385713..3121ffe6 100644 --- a/launcher/resources/pe_blue/pe_blue.qrc +++ b/launcher/resources/pe_blue/pe_blue.qrc @@ -38,5 +38,6 @@ <file>scalable/tag.svg</file> <file>scalable/export.svg</file> <file>scalable/rename.svg</file> + <file>scalable/launch.svg</file> </qresource> </RCC> diff --git a/launcher/resources/pe_blue/scalable/launch.svg b/launcher/resources/pe_blue/scalable/launch.svg new file mode 100644 index 00000000..b3bd124f --- /dev/null +++ b/launcher/resources/pe_blue/scalable/launch.svg @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + version="1.1" + id="Calque_1" + x="0px" + y="0px" + viewBox="0 0 32 32" + enable-background="new 0 0 32 32" + xml:space="preserve" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"><defs + id="defs45" /> + +<path + style="color:#000000;fill:#3366cc;fill-opacity:1;stroke-width:8;stroke-linecap:round;stroke-linejoin:round;paint-order:markers stroke fill;stroke:#3366cc;stroke-opacity:1" + d="M 25.454197,16.881861 9.7664963,25.956555 A 1.4501236,1.4501236 0 0 1 7.5902672,24.701314 V 6.544692 A 1.4501239,1.4501239 0 0 1 9.7664965,5.2894509 L 25.454196,14.364142 a 1.4543042,1.4543042 0 0 1 1e-6,2.517719 z" + id="path11891" /><path + style="color:#000000;fill:#daeeff;fill-opacity:1;stroke-width:8;stroke-linecap:round;stroke-linejoin:round;paint-order:markers stroke fill" + d="M 25.454197,16.881861 9.7664962,25.956555 A 1.4501236,1.4501236 0 0 1 7.5902671,24.701314 V 6.544692 A 1.4501239,1.4501239 0 0 1 9.7664964,5.289451 l 15.6876996,9.074691 a 1.4543042,1.4543042 0 0 1 1e-6,2.517719 z" + id="path11897" /></svg> diff --git a/launcher/resources/pe_blue/scalable/launcher.svg b/launcher/resources/pe_blue/scalable/launcher.svg index 69dd84b1..aeee8433 100644 --- a/launcher/resources/pe_blue/scalable/launcher.svg +++ b/launcher/resources/pe_blue/scalable/launcher.svg @@ -37,17 +37,21 @@ </cc:Agent> </dc:contributor> <dc:source>https://github.com/PrismLauncher/PrismLauncher</dc:source> - <dc:rights> - <cc:Agent> - <dc:title>CC BY-SA 4.0</dc:title> - </cc:Agent> - </dc:rights> <dc:publisher> <cc:Agent> <dc:title>Prism Launcher</dc:title> </cc:Agent> </dc:publisher> + <cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/> </cc:Work> + <cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/"> + <cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/> + <cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/> + <cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/> + <cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/> + <cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/> + <cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/> + </cc:License> </rdf:RDF> </metadata> </svg> diff --git a/launcher/resources/pe_colored/pe_colored.qrc b/launcher/resources/pe_colored/pe_colored.qrc index fa6cd9cd..ce5ad8e2 100644 --- a/launcher/resources/pe_colored/pe_colored.qrc +++ b/launcher/resources/pe_colored/pe_colored.qrc @@ -38,5 +38,6 @@ <file>scalable/tag.svg</file> <file>scalable/export.svg</file> <file>scalable/rename.svg</file> + <file>scalable/launch.svg</file> </qresource> </RCC> diff --git a/launcher/resources/pe_colored/scalable/launch.svg b/launcher/resources/pe_colored/scalable/launch.svg new file mode 100644 index 00000000..76713387 --- /dev/null +++ b/launcher/resources/pe_colored/scalable/launch.svg @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + version="1.1" + id="Calque_1" + x="0px" + y="0px" + viewBox="0 0 32 32" + enable-background="new 0 0 32 32" + xml:space="preserve" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"><defs + id="defs45" /> + +<path + style="color:#000000;fill:#8c6239;fill-opacity:1;stroke-width:0.999996;stroke-linecap:round;stroke-linejoin:round;paint-order:markers stroke fill" + d="M 9.0277705,1.5430089 C 6.4409319,1.6400426 3.9852419,3.7549517 3.985242,6.5450423 V 24.700962 c -8e-7,3.720121 4.3665092,6.239738 7.586682,4.376999 l 15.687475,-9.074438 c 3.218968,-1.862044 3.218969,-6.899 0,-8.761041 L 11.571924,2.1680429 C 10.766881,1.7023586 9.8900501,1.5106643 9.0277705,1.5430089 Z m 2.1691325,8.7399131 9.231137,5.34008 -9.231137,5.34008 z" + id="path11889" /><path + id="path11895" + style="color:#000000;fill:#39b54a;fill-opacity:1;stroke-width:0.999996;stroke-linecap:round;stroke-linejoin:round;paint-order:markers stroke fill" + d="M 9.3517318,1.541286 C 9.2436609,1.5382303 9.1355552,1.5390036 9.0277705,1.5430466 6.4409344,1.6400802 3.9852418,3.7549923 3.9852419,6.54508 V 9.6526439 H 24.511009 L 11.571923,2.1680807 C 10.867511,1.7606073 10.108228,1.5626759 9.3517318,1.541286 Z" /><path + style="color:#000000;fill:#f2f2f2;stroke-width:8;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none;paint-order:markers stroke fill" + d="M 25.454197,16.881861 9.7664962,25.956555 A 1.4501236,1.4501236 0 0 1 7.5902671,24.701314 V 6.544692 A 1.4501239,1.4501239 0 0 1 9.7664964,5.289451 l 15.6876996,9.074691 a 1.4543042,1.4543042 0 0 1 1e-6,2.517719 z" + id="path11897" /></svg> diff --git a/launcher/resources/pe_colored/scalable/launcher.svg b/launcher/resources/pe_colored/scalable/launcher.svg index 69dd84b1..aeee8433 100644 --- a/launcher/resources/pe_colored/scalable/launcher.svg +++ b/launcher/resources/pe_colored/scalable/launcher.svg @@ -37,17 +37,21 @@ </cc:Agent> </dc:contributor> <dc:source>https://github.com/PrismLauncher/PrismLauncher</dc:source> - <dc:rights> - <cc:Agent> - <dc:title>CC BY-SA 4.0</dc:title> - </cc:Agent> - </dc:rights> <dc:publisher> <cc:Agent> <dc:title>Prism Launcher</dc:title> </cc:Agent> </dc:publisher> + <cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/> </cc:Work> + <cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/"> + <cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/> + <cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/> + <cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/> + <cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/> + <cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/> + <cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/> + </cc:License> </rdf:RDF> </metadata> </svg> diff --git a/launcher/resources/pe_dark/pe_dark.qrc b/launcher/resources/pe_dark/pe_dark.qrc index 6b9c7cb6..156d8f8b 100644 --- a/launcher/resources/pe_dark/pe_dark.qrc +++ b/launcher/resources/pe_dark/pe_dark.qrc @@ -38,5 +38,6 @@ <file>scalable/tag.svg</file> <file>scalable/export.svg</file> <file>scalable/rename.svg</file> + <file>scalable/launch.svg</file> </qresource> </RCC> diff --git a/launcher/resources/pe_dark/scalable/launch.svg b/launcher/resources/pe_dark/scalable/launch.svg new file mode 100644 index 00000000..3746e2dd --- /dev/null +++ b/launcher/resources/pe_dark/scalable/launch.svg @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + version="1.1" + id="Calque_1" + x="0px" + y="0px" + viewBox="0 0 32 32" + enable-background="new 0 0 32 32" + xml:space="preserve" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"><defs + id="defs45" /> + +<path + id="path11889" + style="color:#000000;fill:#000000;fill-opacity:1;stroke-width:0.999996;stroke-linecap:round;stroke-linejoin:round;paint-order:markers stroke fill" + d="M 9.3515625,1.5410156 C 9.2434916,1.5379599 9.1351286,1.5389257 9.0273438,1.5429688 6.4405077,1.6400024 3.9843749,3.7548341 3.984375,6.5449219 V 24.701172 c -8e-7,3.720117 4.367721,6.23969 7.587891,4.376953 l 15.6875,-9.074219 c 3.218964,-1.862042 3.218965,-6.899679 0,-8.761718 L 11.572266,2.1679688 C 10.867854,1.7604954 10.108059,1.5624055 9.3515625,1.5410156 Z m -0.5625,3.5761719 a 1.4501239,1.4501239 0 0 1 0.9765625,0.171875 l 15.689453,9.0742185 a 1.4543042,1.4543042 0 0 1 0,2.519531 L 9.765625,25.957031 A 1.4501236,1.4501236 0 0 1 7.5898438,24.701172 V 6.5449219 A 1.4501239,1.4501239 0 0 1 8.7890625,5.1171875 Z" /></svg> diff --git a/launcher/resources/pe_dark/scalable/launcher.svg b/launcher/resources/pe_dark/scalable/launcher.svg index 69dd84b1..aeee8433 100644 --- a/launcher/resources/pe_dark/scalable/launcher.svg +++ b/launcher/resources/pe_dark/scalable/launcher.svg @@ -37,17 +37,21 @@ </cc:Agent> </dc:contributor> <dc:source>https://github.com/PrismLauncher/PrismLauncher</dc:source> - <dc:rights> - <cc:Agent> - <dc:title>CC BY-SA 4.0</dc:title> - </cc:Agent> - </dc:rights> <dc:publisher> <cc:Agent> <dc:title>Prism Launcher</dc:title> </cc:Agent> </dc:publisher> + <cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/> </cc:Work> + <cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/"> + <cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/> + <cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/> + <cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/> + <cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/> + <cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/> + <cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/> + </cc:License> </rdf:RDF> </metadata> </svg> diff --git a/launcher/resources/pe_light/pe_light.qrc b/launcher/resources/pe_light/pe_light.qrc index 963bfcde..d8e4a1bd 100644 --- a/launcher/resources/pe_light/pe_light.qrc +++ b/launcher/resources/pe_light/pe_light.qrc @@ -38,5 +38,6 @@ <file>scalable/tag.svg</file> <file>scalable/export.svg</file> <file>scalable/rename.svg</file> + <file>scalable/launch.svg</file> </qresource> </RCC> diff --git a/launcher/resources/pe_light/scalable/launch.svg b/launcher/resources/pe_light/scalable/launch.svg new file mode 100644 index 00000000..6c27b7e0 --- /dev/null +++ b/launcher/resources/pe_light/scalable/launch.svg @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + version="1.1" + id="Calque_1" + x="0px" + y="0px" + viewBox="0 0 32 32" + enable-background="new 0 0 32 32" + xml:space="preserve" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"><defs + id="defs45" /> + +<path + id="path11889" + style="color:#000000;fill:#f2f2f2;fill-opacity:1;stroke-width:0.999996;stroke-linecap:round;stroke-linejoin:round;paint-order:markers stroke fill" + d="M 9.3515625,1.5410156 C 9.2434916,1.5379599 9.1351286,1.5389257 9.0273438,1.5429688 6.4405077,1.6400024 3.9843749,3.7548341 3.984375,6.5449219 V 24.701172 c -8e-7,3.720117 4.367721,6.23969 7.587891,4.376953 l 15.6875,-9.074219 c 3.218964,-1.862042 3.218965,-6.899679 0,-8.761718 L 11.572266,2.1679688 C 10.867854,1.7604954 10.108059,1.5624055 9.3515625,1.5410156 Z m -0.5625,3.5761719 a 1.4501239,1.4501239 0 0 1 0.9765625,0.171875 l 15.689453,9.0742185 a 1.4543042,1.4543042 0 0 1 0,2.519531 L 9.765625,25.957031 A 1.4501236,1.4501236 0 0 1 7.5898438,24.701172 V 6.5449219 A 1.4501239,1.4501239 0 0 1 8.7890625,5.1171875 Z" /></svg> diff --git a/launcher/resources/pe_light/scalable/launcher.svg b/launcher/resources/pe_light/scalable/launcher.svg index 69dd84b1..aeee8433 100644 --- a/launcher/resources/pe_light/scalable/launcher.svg +++ b/launcher/resources/pe_light/scalable/launcher.svg @@ -37,17 +37,21 @@ </cc:Agent> </dc:contributor> <dc:source>https://github.com/PrismLauncher/PrismLauncher</dc:source> - <dc:rights> - <cc:Agent> - <dc:title>CC BY-SA 4.0</dc:title> - </cc:Agent> - </dc:rights> <dc:publisher> <cc:Agent> <dc:title>Prism Launcher</dc:title> </cc:Agent> </dc:publisher> + <cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/> </cc:Work> + <cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/"> + <cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/> + <cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/> + <cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/> + <cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/> + <cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/> + <cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/> + </cc:License> </rdf:RDF> </metadata> </svg> diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index 2f57de3a..38f48296 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -83,6 +83,21 @@ struct Language else if(key == "es_UY") { result = u8"español de Latinoamérica"; } + else if(key == "en_NZ") { + result = u8"New Zealand English"; // No idea why qt translates this to just english and not to New Zealand English + } + else if(key == "en@pirate") { + result = u8"Tongue of the High Seas"; + } + else if(key == "en@uwu") { + result = u8"Cute Engwish"; + } + else if(key == "tok") { + result = u8"toki pona"; + } + else if(key == "nan") { + result = u8"閩南語"; // Using traditional Chinese script. Not sure if we should use simplified instead? + } else { result = locale.nativeLanguageName(); } diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 97152a48..929f2a85 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -49,7 +49,7 @@ #include <QKeyEvent> #include <QAction> - +#include <QActionGroup> #include <QApplication> #include <QButtonGroup> #include <QHBoxLayout> @@ -61,6 +61,7 @@ #include <QMenu> #include <QMenuBar> #include <QMessageBox> +#include <QFileDialog> #include <QInputDialog> #include <QLabel> #include <QToolButton> @@ -105,6 +106,7 @@ #include "ui/dialogs/UpdateDialog.h" #include "ui/dialogs/EditAccountDialog.h" #include "ui/dialogs/ExportInstanceDialog.h" +#include "ui/themes/ITheme.h" #include "UpdateController.h" #include "KonamiCode.h" @@ -253,6 +255,9 @@ public: QMenu * helpMenu = nullptr; TranslatedToolButton helpMenuButton; TranslatedAction actionClearMetadata; + #ifdef Q_OS_MAC + TranslatedAction actionAddToPATH; + #endif TranslatedAction actionReportBug; TranslatedAction actionDISCORD; TranslatedAction actionMATRIX; @@ -262,6 +267,10 @@ public: TranslatedAction actionNoAccountsAdded; TranslatedAction actionNoDefaultAccount; + TranslatedAction actionLockToolbars; + + TranslatedAction actionChangeTheme; + QVector<TranslatedToolButton *> all_toolbuttons; QWidget *centralWidget = nullptr; @@ -288,7 +297,6 @@ public: actionAddInstance = TranslatedAction(MainWindow); actionAddInstance->setObjectName(QStringLiteral("actionAddInstance")); actionAddInstance->setIcon(APPLICATION->getThemedIcon("new")); - actionAddInstance->setIconVisibleInMenu(false); actionAddInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Add Instanc&e...")); actionAddInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Add a new instance.")); actionAddInstance->setShortcut(QKeySequence::New); @@ -335,11 +343,10 @@ public: all_actions.append(&actionSettings); actionUndoTrashInstance = TranslatedAction(MainWindow); - connect(actionUndoTrashInstance, SIGNAL(triggered(bool)), MainWindow, SLOT(undoTrashInstance())); actionUndoTrashInstance->setObjectName(QStringLiteral("actionUndoTrashInstance")); actionUndoTrashInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Undo Last Instance Deletion")); actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething()); - actionUndoTrashInstance->setShortcut(QKeySequence("Ctrl+Z")); + actionUndoTrashInstance->setShortcut(QKeySequence::Undo); all_actions.append(&actionUndoTrashInstance); actionClearMetadata = TranslatedAction(MainWindow); @@ -349,6 +356,14 @@ public: actionClearMetadata.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Clear cached metadata")); all_actions.append(&actionClearMetadata); + #ifdef Q_OS_MAC + actionAddToPATH = TranslatedAction(MainWindow); + actionAddToPATH->setObjectName(QStringLiteral("actionAddToPATH")); + actionAddToPATH.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Install to &PATH")); + actionAddToPATH.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Install a prismlauncher symlink to /usr/local/bin")); + all_actions.append(&actionAddToPATH); + #endif + if (!BuildConfig.BUG_TRACKER_URL.isEmpty()) { actionReportBug = TranslatedAction(MainWindow); actionReportBug->setObjectName(QStringLiteral("actionReportBug")); @@ -421,6 +436,17 @@ public: actionManageAccounts->setCheckable(false); actionManageAccounts->setIcon(APPLICATION->getThemedIcon("accounts")); all_actions.append(&actionManageAccounts); + + actionLockToolbars = TranslatedAction(MainWindow); + actionLockToolbars->setObjectName(QStringLiteral("actionLockToolbars")); + actionLockToolbars.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Lock Toolbars")); + actionLockToolbars->setCheckable(true); + all_actions.append(&actionLockToolbars); + + actionChangeTheme = TranslatedAction(MainWindow); + actionChangeTheme->setObjectName(QStringLiteral("actionChangeTheme")); + actionChangeTheme.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Themes")); + all_actions.append(&actionChangeTheme); } void createMainToolbar(QMainWindow *MainWindow) @@ -428,7 +454,6 @@ public: mainToolBar = TranslatedToolbar(MainWindow); mainToolBar->setVisible(menuBar->isNativeMenuBar() || !APPLICATION->settings()->get("MenuBarInsteadOfToolBar").toBool()); mainToolBar->setObjectName(QStringLiteral("mainToolBar")); - mainToolBar->setMovable(true); mainToolBar->setAllowedAreas(Qt::TopToolBarArea | Qt::BottomToolBarArea); mainToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); mainToolBar->setFloatable(false); @@ -449,6 +474,10 @@ public: helpMenu->addAction(actionClearMetadata); + #ifdef Q_OS_MAC + helpMenu->addAction(actionAddToPATH); + #endif + if (!BuildConfig.BUG_TRACKER_URL.isEmpty()) { helpMenu->addAction(actionReportBug); } @@ -503,8 +532,6 @@ public: fileMenu->setSeparatorsCollapsible(false); fileMenu->addAction(actionAddInstance); fileMenu->addAction(actionLaunchInstance); - fileMenu->addAction(actionLaunchInstanceOffline); - fileMenu->addAction(actionLaunchInstanceDemo); fileMenu->addAction(actionKillInstance); fileMenu->addAction(actionCloseWindow); fileMenu->addSeparator(); @@ -522,18 +549,25 @@ public: viewMenu = menuBar->addMenu(tr("&View")); viewMenu->setSeparatorsCollapsible(false); + viewMenu->addAction(actionChangeTheme); + viewMenu->addSeparator(); viewMenu->addAction(actionCAT); viewMenu->addSeparator(); + viewMenu->addAction(actionLockToolbars); + menuBar->addMenu(foldersMenu); - profileMenu = menuBar->addMenu(tr("&Profiles")); + profileMenu = menuBar->addMenu(tr("&Accounts")); profileMenu->setSeparatorsCollapsible(false); profileMenu->addAction(actionManageAccounts); helpMenu = menuBar->addMenu(tr("&Help")); helpMenu->setSeparatorsCollapsible(false); helpMenu->addAction(actionClearMetadata); + #ifdef Q_OS_MAC + helpMenu->addAction(actionAddToPATH); + #endif helpMenu->addSeparator(); helpMenu->addAction(actionAbout); helpMenu->addAction(actionOpenWiki); @@ -547,10 +581,11 @@ public: helpMenu->addAction(actionDISCORD); if (!BuildConfig.SUBREDDIT_URL.isEmpty()) helpMenu->addAction(actionREDDIT); - helpMenu->addSeparator(); if(BuildConfig.UPDATER_ENABLED) + { + helpMenu->addSeparator(); helpMenu->addAction(actionCheckUpdate); - + } MainWindow->setMenuBar(menuBar); } @@ -568,6 +603,7 @@ public: actionOpenWiki->setObjectName(QStringLiteral("actionOpenWiki")); actionOpenWiki.setTextId(QT_TRANSLATE_NOOP("MainWindow", "%1 &Help")); actionOpenWiki.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the %1 wiki")); + actionOpenWiki->setIcon(APPLICATION->getThemedIcon("help")); connect(actionOpenWiki, &QAction::triggered, MainWindow, &MainWindow::on_actionOpenWiki_triggered); all_actions.append(&actionOpenWiki); @@ -575,6 +611,7 @@ public: actionNewsMenuBar->setObjectName(QStringLiteral("actionNewsMenuBar")); actionNewsMenuBar.setTextId(QT_TRANSLATE_NOOP("MainWindow", "%1 &News")); actionNewsMenuBar.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the %1 wiki")); + actionNewsMenuBar->setIcon(APPLICATION->getThemedIcon("news")); connect(actionNewsMenuBar, &QAction::triggered, MainWindow, &MainWindow::on_actionMoreNews_triggered); all_actions.append(&actionNewsMenuBar); } @@ -602,7 +639,6 @@ public: { newsToolBar = TranslatedToolbar(MainWindow); newsToolBar->setObjectName(QStringLiteral("newsToolBar")); - newsToolBar->setMovable(true); newsToolBar->setAllowedAreas(Qt::TopToolBarArea | Qt::BottomToolBarArea); newsToolBar->setIconSize(QSize(16, 16)); newsToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); @@ -656,6 +692,7 @@ public: actionLaunchInstance->setObjectName(QStringLiteral("actionLaunchInstance")); actionLaunchInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Launch")); actionLaunchInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Launch the selected instance.")); + actionLaunchInstance->setIcon(APPLICATION->getThemedIcon("launch")); all_actions.append(&actionLaunchInstance); actionLaunchInstanceOffline = TranslatedAction(MainWindow); @@ -736,12 +773,13 @@ public: instanceToolBar->setObjectName(QStringLiteral("instanceToolBar")); // disabled until we have an instance selected instanceToolBar->setEnabled(false); - instanceToolBar->setMovable(true); // Qt doesn't like vertical moving toolbars, so we have to force them... // See https://github.com/PolyMC/PolyMC/issues/493 connect(instanceToolBar, &QToolBar::orientationChanged, [=](Qt::Orientation){ instanceToolBar->setOrientation(Qt::Vertical); }); instanceToolBar->setAllowedAreas(Qt::LeftToolBarArea | Qt::RightToolBarArea); - instanceToolBar->setToolButtonStyle(Qt::ToolButtonTextOnly); + instanceToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + instanceToolBar->setIconSize(QSize(16, 16)); + instanceToolBar->setFloatable(false); instanceToolBar->setWindowTitle(QT_TRANSLATE_NOOP("MainWindow", "Instance Toolbar")); @@ -761,8 +799,18 @@ public: instanceToolBar->addAction(actionViewSelectedInstFolder); instanceToolBar->addAction(actionExportInstance); - instanceToolBar->addAction(actionDeleteInstance); instanceToolBar->addAction(actionCopyInstance); + instanceToolBar->addAction(actionDeleteInstance); + + QLayout * lay = instanceToolBar->layout(); + for(int i = 0; i < lay->count(); i++) + { + QLayoutItem * item = lay->itemAt(i); + if (item->widget()->metaObject()->className() == QString("QToolButton")) + { + item->setAlignment(Qt::AlignLeft); + } + } all_toolbars.append(&instanceToolBar); MainWindow->addToolBar(Qt::RightToolBarArea, instanceToolBar); @@ -803,6 +851,7 @@ public: createInstanceToolbar(MainWindow); MainWindow->updateToolsMenu(); + MainWindow->updateThemeMenu(); retranslateUi(MainWindow); @@ -906,6 +955,14 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow connect(ui->actionCAT.operator->(), SIGNAL(toggled(bool)), SLOT(onCatToggled(bool))); setCatBackground(cat_enable); } + + // Lock toolbars + { + bool toolbarsLocked = APPLICATION->settings()->get("ToolbarsLocked").toBool(); + ui->actionLockToolbars->setChecked(toolbarsLocked); + connect(ui->actionLockToolbars, &QAction::toggled, this, &MainWindow::lockToolbars); + lockToolbars(toolbarsLocked); + } // start instance when double-clicked connect(view, &InstanceView::activated, this, &MainWindow::instanceActivated); @@ -1010,6 +1067,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow } } + connect(ui->actionUndoTrashInstance.operator->(), &QAction::triggered, this, &MainWindow::undoTrashInstance); + setSelectedInstanceById(APPLICATION->settings()->get("SelectedInstance").toString()); // removing this looks stupid @@ -1039,7 +1098,7 @@ void MainWindow::retranslateUi() accountMenuButton->setText(profileLabel); } else { - accountMenuButton->setText(tr("Profiles")); + accountMenuButton->setText(tr("Accounts")); } if (m_selectedInstance) { @@ -1059,8 +1118,19 @@ QMenu * MainWindow::createPopupMenu() { QMenu* filteredMenu = QMainWindow::createPopupMenu(); filteredMenu->removeAction( ui->mainToolBar->toggleViewAction() ); + + filteredMenu->addAction(ui->actionLockToolbars); + return filteredMenu; } +void MainWindow::lockToolbars(bool state) +{ + ui->mainToolBar->setMovable(!state); + ui->instanceToolBar->setMovable(!state); + ui->newsToolBar->setMovable(!state); + APPLICATION->settings()->set("ToolbarsLocked", state); +} + void MainWindow::konamiTriggered() { @@ -1121,11 +1191,6 @@ void MainWindow::showInstanceContextMenu(const QPoint &pos) connect(actionDeleteGroup, SIGNAL(triggered(bool)), SLOT(deleteGroup())); actions.append(actionDeleteGroup); } - - QAction *actionUndoTrashInstance = new QAction("Undo last trash instance", this); - connect(actionUndoTrashInstance, SIGNAL(triggered(bool)), SLOT(undoTrashInstance())); - actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething()); - actions.append(actionUndoTrashInstance); } QMenu myMenu; myMenu.addActions(actions); @@ -1236,6 +1301,38 @@ void MainWindow::updateToolsMenu() ui->actionLaunchInstance->setMenu(launchMenu); } +void MainWindow::updateThemeMenu() +{ + QMenu *themeMenu = ui->actionChangeTheme->menu(); + + if (themeMenu) { + themeMenu->clear(); + } else { + themeMenu = new QMenu(this); + } + + auto themes = APPLICATION->getValidApplicationThemes(); + + QActionGroup* themesGroup = new QActionGroup( this ); + + for (auto* theme : themes) { + QAction * themeAction = themeMenu->addAction(theme->name()); + + themeAction->setCheckable(true); + if (APPLICATION->settings()->get("ApplicationTheme").toString() == theme->id()) { + themeAction->setChecked(true); + } + themeAction->setActionGroup(themesGroup); + + connect(themeAction, &QAction::triggered, [theme]() { + APPLICATION->setApplicationTheme(theme->id(),false); + APPLICATION->settings()->set("ApplicationTheme", theme->id()); + }); + } + + ui->actionChangeTheme->setMenu(themeMenu); +} + void MainWindow::repopulateAccountsMenu() { accountMenu->clear(); @@ -1382,7 +1479,7 @@ void MainWindow::defaultAccountChanged() // Set the icon to the "no account" icon. accountMenuButton->setIcon(APPLICATION->getThemedIcon("noaccount")); - accountMenuButton->setText(tr("Profiles")); + accountMenuButton->setText(tr("Accounts")); } bool MainWindow::eventFilter(QObject *obj, QEvent *ev) @@ -1551,15 +1648,14 @@ void MainWindow::setCatBackground(bool enabled) QDateTime now = QDateTime::currentDateTime(); QDateTime birthday(QDate(now.date().year(), 11, 30), QTime(0, 0)); QDateTime xmas(QDate(now.date().year(), 12, 25), QTime(0, 0)); - QString cat; - if(non_stupid_abs(now.daysTo(xmas)) <= 4) { - cat = "catmas"; - } - else if (non_stupid_abs(now.daysTo(birthday)) <= 12) { - cat = "cattiversary"; - } - else { - cat = "kitteh"; + QDateTime halloween(QDate(now.date().year(), 10, 31), QTime(0, 0)); + QString cat = APPLICATION->settings()->get("BackgroundCat").toString(); + if (non_stupid_abs(now.daysTo(xmas)) <= 4) { + cat += "-xmas"; + } else if (non_stupid_abs(now.daysTo(halloween)) <= 4) { + cat += "-spooky"; + } else if (non_stupid_abs(now.daysTo(birthday)) <= 12) { + cat += "-bday"; } view->setStyleSheet(QString(R"( InstanceView @@ -1567,10 +1663,11 @@ InstanceView background-image: url(:/backgrounds/%1); background-attachment: fixed; background-clip: padding; - background-position: top right; + background-position: bottom left; background-repeat: none; background-color:palette(base); -})").arg(cat)); +})") + .arg(cat)); } else { @@ -1616,7 +1713,7 @@ void MainWindow::on_actionCopyInstance_triggered() if (!copyInstDlg.exec()) return; - auto copyTask = new InstanceCopyTask(m_selectedInstance, copyInstDlg.shouldCopySaves(), copyInstDlg.shouldKeepPlaytime()); + auto copyTask = new InstanceCopyTask(m_selectedInstance, copyInstDlg.getChosenOptions()); copyTask->setName(copyInstDlg.instName()); copyTask->setGroup(copyInstDlg.instGroup()); copyTask->setIcon(copyInstDlg.iconKey()); @@ -1821,6 +1918,7 @@ void MainWindow::deleteGroup() void MainWindow::undoTrashInstance() { APPLICATION->instances()->undoTrashInstance(); + ui->actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething()); } void MainWindow::on_actionViewInstanceFolder_triggered() @@ -1865,6 +1963,7 @@ void MainWindow::globalSettingsClosed() proxymodel->sort(0); updateMainToolBar(); updateToolsMenu(); + updateThemeMenu(); updateStatusCenter(); // This needs to be done to prevent UI elements disappearing in the event the config is changed // but Prism Launcher exits abnormally, causing the window state to never be saved: @@ -1890,8 +1989,32 @@ void MainWindow::on_actionReportBug_triggered() void MainWindow::on_actionClearMetadata_triggered() { APPLICATION->metacache()->evictAll(); + APPLICATION->metacache()->SaveNow(); } +#ifdef Q_OS_MAC +void MainWindow::on_actionAddToPATH_triggered() +{ + auto binaryPath = APPLICATION->applicationFilePath(); + auto targetPath = QString("/usr/local/bin/%1").arg(BuildConfig.LAUNCHER_APP_BINARY_NAME); + qDebug() << "Symlinking" << binaryPath << "to" << targetPath; + + QStringList args; + args << "-e"; + args << QString("do shell script \"mkdir -p /usr/local/bin && ln -sf '%1' '%2'\" with administrator privileges") + .arg(binaryPath, targetPath); + auto outcome = QProcess::execute("/usr/bin/osascript", args); + if (!outcome) { + QMessageBox::information(this, tr("Successfully added %1 to PATH").arg(BuildConfig.LAUNCHER_DISPLAYNAME), + tr("%1 was successfully added to your PATH. You can now start it by running `%2`.") + .arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.LAUNCHER_APP_BINARY_NAME)); + } else { + QMessageBox::critical(this, tr("Failed to add %1 to PATH").arg(BuildConfig.LAUNCHER_DISPLAYNAME), + tr("An error occurred while trying to add %1 to PATH").arg(BuildConfig.LAUNCHER_DISPLAYNAME)); + } +} +#endif + void MainWindow::on_actionOpenWiki_triggered() { DesktopServices::openUrl(QUrl(BuildConfig.HELP_URL.arg(""))); @@ -1927,6 +2050,7 @@ void MainWindow::on_actionDeleteInstance_triggered() auto id = m_selectedInstance->id(); if (APPLICATION->instances()->trashInstance(id)) { + ui->actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething()); return; } diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index cb8cb4aa..0aa01ee2 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -128,6 +128,10 @@ private slots: void on_actionClearMetadata_triggered(); + #ifdef Q_OS_MAC + void on_actionAddToPATH_triggered(); + #endif + void on_actionOpenWiki_triggered(); void on_actionMoreNews_triggered(); @@ -170,6 +174,8 @@ private slots: void updateToolsMenu(); + void updateThemeMenu(); + void instanceActivated(QModelIndex); void instanceChanged(const QModelIndex ¤t, const QModelIndex &previous); @@ -203,6 +209,8 @@ private slots: void globalSettingsClosed(); + void lockToolbars(bool); + #ifndef Q_OS_MAC void keyReleaseEvent(QKeyEvent *event) override; #endif diff --git a/launcher/ui/dialogs/AboutDialog.cpp b/launcher/ui/dialogs/AboutDialog.cpp index cecda1df..a36e4a3d 100644 --- a/launcher/ui/dialogs/AboutDialog.cpp +++ b/launcher/ui/dialogs/AboutDialog.cpp @@ -73,17 +73,12 @@ QString getCreditsHtml() stream << "<h3>" << QObject::tr("%1 Developers", "About Credits").arg(BuildConfig.LAUNCHER_DISPLAYNAME) << "</h3>\n"; stream << QString("<p>Sefa Eyeoglu (Scrumplex) %1</p>\n") .arg(getWebsite("https://scrumplex.net")); stream << QString("<p>dada513 %1</p>\n") .arg(getGitHub("dada513")); - stream << QString("<p>txtsd %1</p>\n") .arg(getGitHub("txtsd")); + stream << QString("<p>txtsd %1</p>\n") .arg(getWebsite("https://ihavea.quest")); stream << QString("<p>timoreo %1</p>\n") .arg(getGitHub("timoreo22")); stream << QString("<p>Ezekiel Smith (ZekeSmith) %1</p>\n") .arg(getGitHub("ZekeSmith")); stream << QString("<p>cozyGalvinism %1</p>\n") .arg(getGitHub("cozyGalvinism")); - stream << "<br />\n"; - - //: %1 is the name of the launcher, determined at build time, e.g. "Prism Launcher Contributors" - stream << "<h3>" << QObject::tr("%1 Contributors", "About Credits").arg(BuildConfig.LAUNCHER_DISPLAYNAME) << "</h3>\n"; stream << QString("<p>DioEgizio %1</p>\n") .arg(getGitHub("DioEgizio")); stream << QString("<p>flowln %1</p>\n") .arg(getGitHub("flowln")); - stream << QString("<p>swirl %1</p>\n") .arg(getWebsite("https://swurl.xyz/")); stream << "<br />\n"; // TODO: possibly retrieve from git history at build time? @@ -97,7 +92,7 @@ QString getCreditsHtml() stream << "<br />\n"; stream << "<h3>" << QObject::tr("With thanks to", "About Credits") << "</h3>\n"; - stream << QString("<p>Boba %1</p>\n") .arg(getWebsite("https://cmdplusv.neocities.org/")); + stream << QString("<p>Boba %1</p>\n") .arg(getWebsite("https://bobaonline.neocities.org/")); stream << QString("<p>Davi Rafael %1</p>\n") .arg(getWebsite("https://auti.one/")); stream << QString("<p>Fulmine %1</p>\n") .arg(getWebsite("https://www.fulmine.xyz/")); stream << QString("<p>ely %1</p>\n") .arg(getGitHub("elyrodso")); diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp index fe87b517..edb4ff7d 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.cpp +++ b/launcher/ui/dialogs/BlockedModsDialog.cpp @@ -1,28 +1,321 @@ #include "BlockedModsDialog.h" -#include "ui_BlockedModsDialog.h" -#include <QPushButton> -#include <QDialogButtonBox> #include <QDesktopServices> +#include <QDialogButtonBox> +#include <QPushButton> +#include "Application.h" +#include "ui_BlockedModsDialog.h" +#include <QDebug> +#include <QDragEnterEvent> +#include <QFileDialog> +#include <QFileInfo> +#include <QStandardPaths> + +BlockedModsDialog::BlockedModsDialog(QWidget* parent, const QString& title, const QString& text, QList<BlockedMod>& mods) + : QDialog(parent), ui(new Ui::BlockedModsDialog), m_mods(mods) +{ + m_hashing_task = shared_qobject_ptr<ConcurrentTask>(new ConcurrentTask(this, "MakeHashesTask", 10)); + connect(m_hashing_task.get(), &Task::finished, this, &BlockedModsDialog::hashTaskFinished); -BlockedModsDialog::BlockedModsDialog(QWidget *parent, const QString &title, const QString &text, const QString &body, const QList<QUrl> &urls) : - QDialog(parent), ui(new Ui::BlockedModsDialog), urls(urls) { ui->setupUi(this); auto openAllButton = ui->buttonBox->addButton(tr("Open All"), QDialogButtonBox::ActionRole); connect(openAllButton, &QPushButton::clicked, this, &BlockedModsDialog::openAll); + auto downloadFolderButton = ui->buttonBox->addButton(tr("Add Download Folder"), QDialogButtonBox::ActionRole); + connect(downloadFolderButton, &QPushButton::clicked, this, &BlockedModsDialog::addDownloadFolder); + + connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &BlockedModsDialog::directoryChanged); + + qDebug() << "[Blocked Mods Dialog] Mods List: " << mods; + + setupWatch(); + scanPaths(); + this->setWindowTitle(title); - ui->label->setText(text); - ui->textBrowser->setText(body); + ui->labelDescription->setText(text); + ui->labelExplain->setText( + QString(tr("Your configured global mods folder and default downloads folder " + "are automatically checked for the downloaded mods and they will be copied to the instance if found.<br/>" + "Optionally, you may drag and drop the downloaded mods onto this dialog or add a folder to watch " + "if you did not download the mods to a default location.")) + .arg(APPLICATION->settings()->get("CentralModsDir").toString(), + QStandardPaths::writableLocation(QStandardPaths::DownloadLocation))); + + // force all URL handeling as external + connect(ui->textBrowserWatched, &QTextBrowser::anchorClicked, this, [](const QUrl url) { QDesktopServices::openUrl(url); }); + + setAcceptDrops(true); + + update(); } -BlockedModsDialog::~BlockedModsDialog() { +BlockedModsDialog::~BlockedModsDialog() +{ delete ui; } -void BlockedModsDialog::openAll() { - for(auto &url : urls) { - QDesktopServices::openUrl(url); +void BlockedModsDialog::dragEnterEvent(QDragEnterEvent* e) +{ + if (e->mimeData()->hasUrls()) { + e->acceptProposedAction(); + } +} + +void BlockedModsDialog::dropEvent(QDropEvent* e) +{ + for (const QUrl& url : e->mimeData()->urls()) { + QString filePath = url.toLocalFile(); + qDebug() << "[Blocked Mods Dialog] Dropped file:" << filePath; + addHashTask(filePath); + + // watch for changes + QFileInfo file = QFileInfo(filePath); + QString path = file.dir().absolutePath(); + qDebug() << "[Blocked Mods Dialog] Adding watch path:" << path; + m_watcher.addPath(path); + } + scanPaths(); + update(); +} + +void BlockedModsDialog::openAll() +{ + for (auto& mod : m_mods) { + QDesktopServices::openUrl(mod.websiteUrl); + } +} + +void BlockedModsDialog::addDownloadFolder() +{ + QString dir = + QFileDialog::getExistingDirectory(this, tr("Select directory where you downloaded the mods"), + QStandardPaths::writableLocation(QStandardPaths::DownloadLocation), QFileDialog::ShowDirsOnly); + qDebug() << "[Blocked Mods Dialog] Adding watch path:" << dir; + m_watcher.addPath(dir); + scanPath(dir, true); + update(); +} + +/// @brief update UI with current status of the blocked mod detection +void BlockedModsDialog::update() +{ + QString text; + QString span; + + for (auto& mod : m_mods) { + if (mod.matched) { + // ✔ -> html for HEAVY CHECK MARK : ✔ + span = QString(tr("<span style=\"color:green\"> ✔ Found at %1 </span>")).arg(mod.localPath); + } else { + // ✘ -> html for HEAVY BALLOT X : ✘ + span = QString(tr("<span style=\"color:red\"> ✘ Not Found </span>")); + } + text += QString(tr("%1: <a href='%2'>%2</a> <p>Hash: %3 %4</p> <br/>")).arg(mod.name, mod.websiteUrl, mod.hash, span); + } + + ui->textBrowserModsListing->setText(text); + + QString watching; + for (auto& dir : m_watcher.directories()) { + watching += QString("<a href=\"%1\">%1</a><br/>").arg(dir); + } + + ui->textBrowserWatched->setText(watching); + + if (allModsMatched()) { + ui->labelModsFound->setText("<span style=\"color:green\">✔</span>" + tr("All mods found")); + } else { + ui->labelModsFound->setText(tr("Please download the missing mods.")); + } +} + +/// @brief Signal fired when a watched direcotry has changed +/// @param path the path to the changed directory +void BlockedModsDialog::directoryChanged(QString path) +{ + qDebug() << "[Blocked Mods Dialog] Directory changed: " << path; + validateMatchedMods(); + scanPath(path, true); +} + +/// @brief add the user downloads folder and the global mods folder to the filesystem watcher +void BlockedModsDialog::setupWatch() +{ + const QString downloadsFolder = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); + const QString modsFolder = APPLICATION->settings()->get("CentralModsDir").toString(); + m_watcher.addPath(downloadsFolder); + m_watcher.addPath(modsFolder); +} + +/// @brief scan all watched folder +void BlockedModsDialog::scanPaths() +{ + for (auto& dir : m_watcher.directories()) { + scanPath(dir, false); } + runHashTask(); +} + +/// @brief Scan the directory at path, skip paths that do not contain a file name +/// of a blocked mod we are looking for +/// @param path the directory to scan +void BlockedModsDialog::scanPath(QString path, bool start_task) +{ + QDir scan_dir(path); + QDirIterator scan_it(path, QDir::Filter::Files | QDir::Filter::Hidden, QDirIterator::NoIteratorFlags); + while (scan_it.hasNext()) { + QString file = scan_it.next(); + + if (!checkValidPath(file)) { + continue; + } + + addHashTask(file); + } + + if (start_task) { + runHashTask(); + } +} + +/// @brief add a hashing task for the file located at path, add the path to the pending set if the hasing task is already running +/// @param path the path to the local file being hashed +void BlockedModsDialog::addHashTask(QString path) +{ + qDebug() << "[Blocked Mods Dialog] adding a Hash task for" << path << "to the pending set."; + m_pending_hash_paths.insert(path); +} + +/// @brief add a hashing task for the file located at path and connect it to check that hash against +/// our blocked mods list +/// @param path the path to the local file being hashed +void BlockedModsDialog::buildHashTask(QString path) +{ + auto hash_task = Hashing::createBlockedModHasher(path, ModPlatform::Provider::FLAME, "sha1"); + + qDebug() << "[Blocked Mods Dialog] Creating Hash task for path: " << path; + + connect(hash_task.get(), &Task::succeeded, this, [this, hash_task, path] { checkMatchHash(hash_task->getResult(), path); }); + connect(hash_task.get(), &Task::failed, this, [path] { qDebug() << "Failed to hash path: " << path; }); + + m_hashing_task->addTask(hash_task); +} + +/// @brief check if the computed hash for the provided path matches a blocked +/// mod we are looking for +/// @param hash the computed hash for the provided path +/// @param path the path to the local file being compared +void BlockedModsDialog::checkMatchHash(QString hash, QString path) +{ + bool match = false; + + qDebug() << "[Blocked Mods Dialog] Checking for match on hash: " << hash << "| From path:" << path; + + for (auto& mod : m_mods) { + if (mod.matched) { + continue; + } + if (mod.hash.compare(hash, Qt::CaseInsensitive) == 0) { + mod.matched = true; + mod.localPath = path; + match = true; + + qDebug() << "[Blocked Mods Dialog] Hash match found:" << mod.name << hash << "| From path:" << path; + + break; + } + } + + if (match) { + update(); + } +} + +/// @brief Check if the name of the file at path matches the name of a blocked mod we are searching for +/// @param path the path to check +/// @return boolean: did the path match the name of a blocked mod? +bool BlockedModsDialog::checkValidPath(QString path) +{ + QFileInfo file = QFileInfo(path); + QString filename = file.fileName(); + + for (auto& mod : m_mods) { + if (mod.name.compare(filename, Qt::CaseInsensitive) == 0) { + qDebug() << "[Blocked Mods Dialog] Name match found:" << mod.name << "| From path:" << path; + return true; + } + } + + return false; +} + +bool BlockedModsDialog::allModsMatched() +{ + return std::all_of(m_mods.begin(), m_mods.end(), [](auto const& mod) { return mod.matched; }); +} + +/// @brief ensure matched file paths still exist +void BlockedModsDialog::validateMatchedMods() +{ + bool changed = false; + for (auto& mod : m_mods) { + if (mod.matched) { + QFileInfo file = QFileInfo(mod.localPath); + if (!file.exists() || !file.isFile()) { + qDebug() << "[Blocked Mods Dialog] File" << mod.localPath << "for mod" << mod.name + << "has vanshed! marking as not matched."; + mod.localPath = ""; + mod.matched = false; + changed = true; + } + } + } + if (changed) { + update(); + } +} + +/// @brief run hash task or mark a pending run if it is already runing +void BlockedModsDialog::runHashTask() +{ + if (!m_hashing_task->isRunning()) { + m_rehash_pending = false; + + if (!m_pending_hash_paths.isEmpty()) { + qDebug() << "[Blocked Mods Dialog] there are pending hash tasks, building and running tasks"; + + auto path = m_pending_hash_paths.begin(); + while (path != m_pending_hash_paths.end()) { + buildHashTask(*path); + path = m_pending_hash_paths.erase(path); + } + + m_hashing_task->start(); + } + } else { + qDebug() << "[Blocked Mods Dialog] queueing another run of the hashing task"; + qDebug() << "[Blocked Mods Dialog] pending hash tasks:" << m_pending_hash_paths; + m_rehash_pending = true; + } +} + +void BlockedModsDialog::hashTaskFinished() +{ + qDebug() << "[Blocked Mods Dialog] All hash tasks finished"; + if (m_rehash_pending) { + qDebug() << "[Blocked Mods Dialog] task finished with a rehash pending, rerunning"; + runHashTask(); + } +} + +/// qDebug print support for the BlockedMod struct +QDebug operator<<(QDebug debug, const BlockedMod& m) +{ + QDebugStateSaver saver(debug); + + debug.nospace() << "{ name: " << m.name << ", websiteUrl: " << m.websiteUrl << ", hash: " << m.hash << ", matched: " << m.matched + << ", localPath: " << m.localPath << "}"; + + return debug; } diff --git a/launcher/ui/dialogs/BlockedModsDialog.h b/launcher/ui/dialogs/BlockedModsDialog.h index 5f5bd61b..dac43cba 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.h +++ b/launcher/ui/dialogs/BlockedModsDialog.h @@ -1,7 +1,23 @@ #pragma once #include <QDialog> +#include <QString> +#include <QList> +#include <QFileSystemWatcher> + +#include "modplatform/helpers/HashUtils.h" + +#include "tasks/ConcurrentTask.h" + +struct BlockedMod { + QString name; + QString websiteUrl; + QString hash; + bool matched; + QString localPath; + +}; QT_BEGIN_NAMESPACE namespace Ui { class BlockedModsDialog; } @@ -11,12 +27,38 @@ class BlockedModsDialog : public QDialog { Q_OBJECT public: - BlockedModsDialog(QWidget *parent, const QString &title, const QString &text, const QString &body, const QList<QUrl> &urls); + BlockedModsDialog(QWidget *parent, const QString &title, const QString &text, QList<BlockedMod> &mods); ~BlockedModsDialog() override; +protected: + void dragEnterEvent(QDragEnterEvent *event) override; + void dropEvent(QDropEvent *event) override; + private: Ui::BlockedModsDialog *ui; - const QList<QUrl> &urls; + QList<BlockedMod> &m_mods; + QFileSystemWatcher m_watcher; + shared_qobject_ptr<ConcurrentTask> m_hashing_task; + QSet<QString> m_pending_hash_paths; + bool m_rehash_pending; + void openAll(); + void addDownloadFolder(); + void update(); + void directoryChanged(QString path); + void setupWatch(); + void scanPaths(); + void scanPath(QString path, bool start_task); + void addHashTask(QString path); + void buildHashTask(QString path); + void checkMatchHash(QString hash, QString path); + void validateMatchedMods(); + void runHashTask(); + void hashTaskFinished(); + + bool checkValidPath(QString path); + bool allModsMatched(); }; + +QDebug operator<<(QDebug debug, const BlockedMod &m); diff --git a/launcher/ui/dialogs/BlockedModsDialog.ui b/launcher/ui/dialogs/BlockedModsDialog.ui index f4ae95b6..88105178 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.ui +++ b/launcher/ui/dialogs/BlockedModsDialog.ui @@ -13,29 +13,41 @@ <property name="windowTitle"> <string notr="true">BlockedModsDialog</string> </property> - <layout class="QGridLayout" name="gridLayout"> - <item row="0" column="0"> - <widget class="QLabel" name="label"> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="labelDescription"> <property name="text"> <string notr="true"/> </property> <property name="textFormat"> <enum>Qt::RichText</enum> </property> + <property name="wordWrap"> + <bool>true</bool> + </property> </widget> </item> - <item row="2" column="0"> - <widget class="QDialogButtonBox" name="buttonBox"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> + <item> + <widget class="QLabel" name="labelExplain"> + <property name="text"> + <string/> </property> - <property name="standardButtons"> - <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + <property name="wordWrap"> + <bool>true</bool> + </property> + <property name="openExternalLinks"> + <bool>true</bool> </property> </widget> </item> - <item row="1" column="0"> - <widget class="QTextBrowser" name="textBrowser"> + <item> + <widget class="QTextBrowser" name="textBrowserModsListing"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>165</height> + </size> + </property> <property name="acceptRichText"> <bool>true</bool> </property> @@ -44,6 +56,68 @@ </property> </widget> </item> + <item> + <widget class="QLabel" name="labelWatched"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>1</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Watched Folders:</string> + </property> + </widget> + </item> + <item> + <widget class="QTextBrowser" name="textBrowserWatched"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>16</height> + </size> + </property> + <property name="baseSize"> + <size> + <width>0</width> + <height>12</height> + </size> + </property> + <property name="openExternalLinks"> + <bool>true</bool> + </property> + <property name="openLinks"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="bottomBoxH"> + <item> + <widget class="QLabel" name="labelModsFound"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </item> </layout> </widget> <resources/> diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp index 9ec341bc..3f5122f6 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.cpp +++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp @@ -44,7 +44,6 @@ #include "BaseVersion.h" #include "icons/IconList.h" -#include "tasks/Task.h" #include "BaseInstance.h" #include "InstanceList.h" @@ -78,8 +77,14 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent) } ui->groupBox->setCurrentIndex(index); ui->groupBox->lineEdit()->setPlaceholderText(tr("No group")); - ui->copySavesCheckbox->setChecked(m_copySaves); - ui->keepPlaytimeCheckbox->setChecked(m_keepPlaytime); + ui->copySavesCheckbox->setChecked(m_selectedOptions.isCopySavesEnabled()); + ui->keepPlaytimeCheckbox->setChecked(m_selectedOptions.isKeepPlaytimeEnabled()); + ui->copyGameOptionsCheckbox->setChecked(m_selectedOptions.isCopyGameOptionsEnabled()); + ui->copyResPacksCheckbox->setChecked(m_selectedOptions.isCopyResourcePacksEnabled()); + ui->copyShaderPacksCheckbox->setChecked(m_selectedOptions.isCopyShaderPacksEnabled()); + ui->copyServersCheckbox->setChecked(m_selectedOptions.isCopyServersEnabled()); + ui->copyModsCheckbox->setChecked(m_selectedOptions.isCopyModsEnabled()); + ui->copyScreenshotsCheckbox->setChecked(m_selectedOptions.isCopyScreenshotsEnabled()); } CopyInstanceDialog::~CopyInstanceDialog() @@ -117,6 +122,31 @@ QString CopyInstanceDialog::instGroup() const return ui->groupBox->currentText(); } +const InstanceCopyPrefs& CopyInstanceDialog::getChosenOptions() const +{ + return m_selectedOptions; +} + +void CopyInstanceDialog::checkAllCheckboxes(const bool& b) +{ + ui->keepPlaytimeCheckbox->setChecked(b); + ui->copySavesCheckbox->setChecked(b); + ui->copyGameOptionsCheckbox->setChecked(b); + ui->copyResPacksCheckbox->setChecked(b); + ui->copyShaderPacksCheckbox->setChecked(b); + ui->copyServersCheckbox->setChecked(b); + ui->copyModsCheckbox->setChecked(b); + ui->copyScreenshotsCheckbox->setChecked(b); +} + +// Check the "Select all" checkbox if all options are already selected: +void CopyInstanceDialog::updateSelectAllCheckbox() +{ + ui->selectAllCheckbox->blockSignals(true); + ui->selectAllCheckbox->setChecked(m_selectedOptions.allTrue()); + ui->selectAllCheckbox->blockSignals(false); +} + void CopyInstanceDialog::on_iconButton_clicked() { IconPickerDialog dlg(this); @@ -129,42 +159,64 @@ void CopyInstanceDialog::on_iconButton_clicked() } } + void CopyInstanceDialog::on_instNameTextBox_textChanged(const QString &arg1) { updateDialogState(); } -bool CopyInstanceDialog::shouldCopySaves() const +void CopyInstanceDialog::on_selectAllCheckbox_stateChanged(int state) { - return m_copySaves; + bool checked; + checked = (state == Qt::Checked); + checkAllCheckboxes(checked); } void CopyInstanceDialog::on_copySavesCheckbox_stateChanged(int state) { - if(state == Qt::Unchecked) - { - m_copySaves = false; - } - else if(state == Qt::Checked) - { - m_copySaves = true; - } + m_selectedOptions.enableCopySaves(state == Qt::Checked); + updateSelectAllCheckbox(); } -bool CopyInstanceDialog::shouldKeepPlaytime() const + +void CopyInstanceDialog::on_keepPlaytimeCheckbox_stateChanged(int state) { - return m_keepPlaytime; + m_selectedOptions.enableKeepPlaytime(state == Qt::Checked); + updateSelectAllCheckbox(); } +void CopyInstanceDialog::on_copyGameOptionsCheckbox_stateChanged(int state) +{ + m_selectedOptions.enableCopyGameOptions(state == Qt::Checked); + updateSelectAllCheckbox(); +} -void CopyInstanceDialog::on_keepPlaytimeCheckbox_stateChanged(int state) +void CopyInstanceDialog::on_copyResPacksCheckbox_stateChanged(int state) { - if(state == Qt::Unchecked) - { - m_keepPlaytime = false; - } - else if(state == Qt::Checked) - { - m_keepPlaytime = true; - } + m_selectedOptions.enableCopyResourcePacks(state == Qt::Checked); + updateSelectAllCheckbox(); +} + +void CopyInstanceDialog::on_copyShaderPacksCheckbox_stateChanged(int state) +{ + m_selectedOptions.enableCopyShaderPacks(state == Qt::Checked); + updateSelectAllCheckbox(); +} + +void CopyInstanceDialog::on_copyServersCheckbox_stateChanged(int state) +{ + m_selectedOptions.enableCopyServers(state == Qt::Checked); + updateSelectAllCheckbox(); +} + +void CopyInstanceDialog::on_copyModsCheckbox_stateChanged(int state) +{ + m_selectedOptions.enableCopyMods(state == Qt::Checked); + updateSelectAllCheckbox(); +} + +void CopyInstanceDialog::on_copyScreenshotsCheckbox_stateChanged(int state) +{ + m_selectedOptions.enableCopyScreenshots(state == Qt::Checked); + updateSelectAllCheckbox(); } diff --git a/launcher/ui/dialogs/CopyInstanceDialog.h b/launcher/ui/dialogs/CopyInstanceDialog.h index bf3cd920..884501d1 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.h +++ b/launcher/ui/dialogs/CopyInstanceDialog.h @@ -17,7 +17,7 @@ #include <QDialog> #include "BaseVersion.h" -#include <BaseInstance.h> +#include "InstanceCopyPrefs.h" class BaseInstance; @@ -39,20 +39,29 @@ public: QString instName() const; QString instGroup() const; QString iconKey() const; - bool shouldCopySaves() const; - bool shouldKeepPlaytime() const; + const InstanceCopyPrefs& getChosenOptions() const; private slots: void on_iconButton_clicked(); void on_instNameTextBox_textChanged(const QString &arg1); + // Checkboxes + void on_selectAllCheckbox_stateChanged(int state); void on_copySavesCheckbox_stateChanged(int state); void on_keepPlaytimeCheckbox_stateChanged(int state); + void on_copyGameOptionsCheckbox_stateChanged(int state); + void on_copyResPacksCheckbox_stateChanged(int state); + void on_copyShaderPacksCheckbox_stateChanged(int state); + void on_copyServersCheckbox_stateChanged(int state); + void on_copyModsCheckbox_stateChanged(int state); + void on_copyScreenshotsCheckbox_stateChanged(int state); private: + void checkAllCheckboxes(const bool& b); + void updateSelectAllCheckbox(); + /* data */ Ui::CopyInstanceDialog *ui; QString InstIconKey; InstancePtr m_original; - bool m_copySaves = true; - bool m_keepPlaytime = true; + InstanceCopyPrefs m_selectedOptions; }; diff --git a/launcher/ui/dialogs/CopyInstanceDialog.ui b/launcher/ui/dialogs/CopyInstanceDialog.ui index f4b191e2..b7828fe3 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.ui +++ b/launcher/ui/dialogs/CopyInstanceDialog.ui @@ -9,8 +9,8 @@ <rect> <x>0</x> <y>0</y> - <width>345</width> - <height>323</height> + <width>341</width> + <height>399</height> </rect> </property> <property name="windowTitle"> @@ -33,7 +33,7 @@ </property> <property name="sizeHint" stdset="0"> <size> - <width>40</width> + <width>60</width> <height>20</height> </size> </property> @@ -60,7 +60,7 @@ </property> <property name="sizeHint" stdset="0"> <size> - <width>40</width> + <width>60</width> <height>20</height> </size> </property> @@ -83,7 +83,10 @@ </widget> </item> <item> - <layout class="QGridLayout" name="gridLayout"> + <layout class="QGridLayout" name="groupDropdownLayout"> + <property name="verticalSpacing"> + <number>6</number> + </property> <item row="0" column="0"> <widget class="QLabel" name="labelVersion_3"> <property name="text"> @@ -110,18 +113,96 @@ </layout> </item> <item> - <widget class="QCheckBox" name="copySavesCheckbox"> - <property name="text"> - <string>Copy saves</string> - </property> - </widget> + <layout class="QHBoxLayout" name="selectAllButtonLayout"> + <item> + <widget class="QCheckBox" name="selectAllCheckbox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="layoutDirection"> + <enum>Qt::LeftToRight</enum> + </property> + <property name="text"> + <string>Select all</string> + </property> + <property name="checked"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> </item> <item> - <widget class="QCheckBox" name="keepPlaytimeCheckbox"> - <property name="text"> - <string>Keep play time</string> - </property> - </widget> + <layout class="QGridLayout" name="copyOptionsLayout"> + <item row="6" column="1"> + <widget class="QCheckBox" name="copyModsCheckbox"> + <property name="toolTip"> + <string>Disabling this will still keep the mod loader (ex: Fabric, Quilt, etc.) but erase the mods folder and their configs.</string> + </property> + <property name="text"> + <string>Copy mods</string> + </property> + </widget> + </item> + <item row="5" column="0"> + <widget class="QCheckBox" name="copyGameOptionsCheckbox"> + <property name="toolTip"> + <string>Copy the in-game options like FOV, max framerate, etc.</string> + </property> + <property name="text"> + <string>Copy game options</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QCheckBox" name="copySavesCheckbox"> + <property name="text"> + <string>Copy saves</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QCheckBox" name="copyShaderPacksCheckbox"> + <property name="text"> + <string>Copy shader packs</string> + </property> + </widget> + </item> + <item row="5" column="1"> + <widget class="QCheckBox" name="copyServersCheckbox"> + <property name="text"> + <string>Copy servers</string> + </property> + </widget> + </item> + <item row="6" column="0"> + <widget class="QCheckBox" name="copyResPacksCheckbox"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="text"> + <string>Copy resource packs</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QCheckBox" name="keepPlaytimeCheckbox"> + <property name="text"> + <string>Keep play time</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QCheckBox" name="copyScreenshotsCheckbox"> + <property name="text"> + <string>Copy screenshots</string> + </property> + </widget> + </item> + </layout> </item> <item> <widget class="QDialogButtonBox" name="buttonBox"> @@ -139,8 +220,6 @@ <tabstop>iconButton</tabstop> <tabstop>instNameTextBox</tabstop> <tabstop>groupBox</tabstop> - <tabstop>copySavesCheckbox</tabstop> - <tabstop>keepPlaytimeCheckbox</tabstop> </tabstops> <resources> <include location="../../graphics.qrc"/> @@ -153,8 +232,8 @@ <slot>accept()</slot> <hints> <hint type="sourcelabel"> - <x>248</x> - <y>254</y> + <x>254</x> + <y>316</y> </hint> <hint type="destinationlabel"> <x>157</x> @@ -169,8 +248,8 @@ <slot>reject()</slot> <hints> <hint type="sourcelabel"> - <x>316</x> - <y>260</y> + <x>322</x> + <y>316</y> </hint> <hint type="destinationlabel"> <x>286</x> diff --git a/launcher/ui/dialogs/ExportInstanceDialog.cpp b/launcher/ui/dialogs/ExportInstanceDialog.cpp index 9f32dd8e..88552b23 100644 --- a/launcher/ui/dialogs/ExportInstanceDialog.cpp +++ b/launcher/ui/dialogs/ExportInstanceDialog.cpp @@ -39,13 +39,12 @@ #include <MMCZip.h> #include <QFileDialog> #include <QMessageBox> -#include <qfilesystemmodel.h> +#include <QFileSystemModel> #include <QSortFilterProxyModel> #include <QDebug> -#include <qstack.h> #include <QSaveFile> -#include "MMCStrings.h" +#include "StringUtils.h" #include "SeparatorPrefixTree.h" #include "Application.h" #include <icons/IconList.h> @@ -85,7 +84,7 @@ public: // sort and proxy model breaks the original model... if (sortColumn() == 0) { - return Strings::naturalCompare(leftFileInfo.fileName(), rightFileInfo.fileName(), + return StringUtils::naturalCompare(leftFileInfo.fileName(), rightFileInfo.fileName(), Qt::CaseInsensitive) < 0; } if (sortColumn() == 1) @@ -94,7 +93,7 @@ public: auto rightSize = rightFileInfo.size(); if ((leftSize == rightSize) || (leftFileInfo.isDir() && rightFileInfo.isDir())) { - return Strings::naturalCompare(leftFileInfo.fileName(), + return StringUtils::naturalCompare(leftFileInfo.fileName(), rightFileInfo.fileName(), Qt::CaseInsensitive) < 0 ? asc diff --git a/launcher/ui/dialogs/ModDownloadDialog.cpp b/launcher/ui/dialogs/ModDownloadDialog.cpp index d740c8cb..24d23ba9 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.cpp +++ b/launcher/ui/dialogs/ModDownloadDialog.cpp @@ -1,7 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-only /* - * PolyMC - Minecraft Launcher + * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> + * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me> * * 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 @@ -131,6 +132,8 @@ QList<BasePage*> ModDownloadDialog::getPages() if (APPLICATION->capabilities() & Application::SupportsFlame) pages.append(FlameModPage::create(this, m_instance)); + m_selectedPage = dynamic_cast<ModPage*>(pages[0]); + return pages; } @@ -178,12 +181,22 @@ void ModDownloadDialog::selectedPageChanged(BasePage* previous, BasePage* select return; } - auto* selected_page = dynamic_cast<ModPage*>(selected); - if (!selected_page) { + m_selectedPage = dynamic_cast<ModPage*>(selected); + if (!m_selectedPage) { qCritical() << "Page '" << selected->displayName() << "' in ModDownloadDialog is not a ModPage!"; return; } // Same effect as having a global search bar - selected_page->setSearchTerm(prev_page->getSearchTerm()); + m_selectedPage->setSearchTerm(prev_page->getSearchTerm()); +} + +bool ModDownloadDialog::selectPage(QString pageId) +{ + return m_container->selectPage(pageId); +} + +ModPage* ModDownloadDialog::getSelectedPage() +{ + return m_selectedPage; } diff --git a/launcher/ui/dialogs/ModDownloadDialog.h b/launcher/ui/dialogs/ModDownloadDialog.h index 18a5f0f3..fcf6f4fc 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.h +++ b/launcher/ui/dialogs/ModDownloadDialog.h @@ -1,7 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-only /* - * PolyMC - Minecraft Launcher + * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> + * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me> * * 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 @@ -32,13 +33,14 @@ class ModDownloadDialog; class PageContainer; class QDialogButtonBox; +class ModPage; class ModrinthModPage; class ModDownloadDialog final : public QDialog, public BasePageProvider { Q_OBJECT -public: + public: explicit ModDownloadDialog(const std::shared_ptr<ModFolderModel>& mods, QWidget* parent, BaseInstance* instance); ~ModDownloadDialog() override = default; @@ -51,22 +53,26 @@ public: bool isModSelected(QString name) const; const QList<ModDownloadTask*> getTasks(); - const std::shared_ptr<ModFolderModel> &mods; + const std::shared_ptr<ModFolderModel>& mods; -public slots: + bool selectPage(QString pageId); + ModPage* getSelectedPage(); + + public slots: void confirm(); void accept() override; void reject() override; -private slots: + private slots: void selectedPageChanged(BasePage* previous, BasePage* selected); -private: - Ui::ModDownloadDialog *ui = nullptr; - PageContainer * m_container = nullptr; - QDialogButtonBox * m_buttons = nullptr; - QVBoxLayout *m_verticalLayout = nullptr; + private: + Ui::ModDownloadDialog* ui = nullptr; + PageContainer* m_container = nullptr; + QDialogButtonBox* m_buttons = nullptr; + QVBoxLayout* m_verticalLayout = nullptr; + ModPage* m_selectedPage = nullptr; QHash<QString, ModDownloadTask*> modTask; - BaseInstance *m_instance; + BaseInstance* m_instance; }; diff --git a/launcher/ui/dialogs/ModUpdateDialog.cpp b/launcher/ui/dialogs/ModUpdateDialog.cpp index 4171586e..cedd4a96 100644 --- a/launcher/ui/dialogs/ModUpdateDialog.cpp +++ b/launcher/ui/dialogs/ModUpdateDialog.cpp @@ -366,33 +366,28 @@ void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info) auto changelog = new QTreeWidgetItem(changelog_item); auto changelog_area = new QTextBrowser(); + QString text = info.changelog; switch (info.provider) { case ModPlatform::Provider::MODRINTH: { HoeDown h; // HoeDown bug?: \n aren't converted to <br> - auto text = h.process(info.changelog.toUtf8()); + text = h.process(info.changelog.toUtf8()); // Don't convert if there's an HTML tag right after (Qt rendering weirdness) text.remove(QRegularExpression("(\n+)(?=<)")); text.replace('\n', "<br>"); - changelog_area->setHtml(text); break; } - case ModPlatform::Provider::FLAME: { - changelog_area->setHtml(info.changelog); + default: break; - } } + changelog_area->setHtml(text); changelog_area->setOpenExternalLinks(true); - changelog_area->setLineWrapMode(QTextBrowser::LineWrapMode::NoWrap); + changelog_area->setLineWrapMode(QTextBrowser::LineWrapMode::WidgetWidth); changelog_area->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); - // HACK: Is there a better way of achieving this? - auto font_height = QFontMetrics(changelog_area->font()).height(); - changelog_area->setMaximumHeight((changelog_area->toPlainText().count(QRegularExpression("\n|<br>")) + 2) * font_height); - ui->modTreeWidget->setItemWidget(changelog, 0, changelog_area); ui->modTreeWidget->addTopLevelItem(item_top); diff --git a/launcher/ui/dialogs/VersionSelectDialog.cpp b/launcher/ui/dialogs/VersionSelectDialog.cpp index 70ef72d6..d7880334 100644 --- a/launcher/ui/dialogs/VersionSelectDialog.cpp +++ b/launcher/ui/dialogs/VersionSelectDialog.cpp @@ -120,7 +120,7 @@ void VersionSelectDialog::selectRecommended() m_versionWidget->selectRecommended(); } -BaseVersionPtr VersionSelectDialog::selectedVersion() const +BaseVersion::Ptr VersionSelectDialog::selectedVersion() const { return m_versionWidget->selectedVersion(); } diff --git a/launcher/ui/dialogs/VersionSelectDialog.h b/launcher/ui/dialogs/VersionSelectDialog.h index ed30d3f3..18a50cdb 100644 --- a/launcher/ui/dialogs/VersionSelectDialog.h +++ b/launcher/ui/dialogs/VersionSelectDialog.h @@ -44,7 +44,7 @@ public: int exec() override; - BaseVersionPtr selectedVersion() const; + BaseVersion::Ptr selectedVersion() const; void setCurrentVersion(const QString & version); void setFuzzyFilter(BaseVersionList::ModelRoles role, QString filter); diff --git a/launcher/ui/pages/global/APIPage.ui b/launcher/ui/pages/global/APIPage.ui index 1ae788c7..d56a9ef6 100644 --- a/launcher/ui/pages/global/APIPage.ui +++ b/launcher/ui/pages/global/APIPage.ui @@ -179,7 +179,7 @@ <item> <widget class="QLabel" name="label_4"> <property name="text"> - <string>Enter a custom client ID for Microsoft Authentication here. </string> + <string>Enter a custom client ID for Microsoft Authentication here.</string> </property> <property name="textFormat"> <enum>Qt::RichText</enum> diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index b8431e8c..6661bf0f 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -1,8 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-only /* - * PolyMC - Minecraft Launcher + * Prism Launcher - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> * Copyright (c) 2022 dada513 <dada513@protonmail.com> + * Copyright (C) 2022 Tayou <tayou@gmx.net> * * 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 @@ -49,6 +50,7 @@ #include <FileSystem.h> #include "Application.h" #include "BuildConfig.h" +#include "DesktopServices.h" #include "ui/themes/ITheme.h" #include <QApplication> @@ -143,7 +145,7 @@ void LauncherPage::on_instDirBrowseBtn_clicked() ui->instDirTextBox->setText(cooked_dir); } } - else if(APPLICATION->isFlatpak() && raw_dir.startsWith("/run/user")) + else if(DesktopServices::isFlatpak() && raw_dir.startsWith("/run/user")) { QMessageBox warning; warning.setText(tr("You're trying to specify an instance folder " @@ -310,9 +312,12 @@ void LauncherPage::applySettings() s->set("IconTheme", "flat"); break; case 7: - s->set("IconTheme", "multimc"); + s->set("IconTheme", "flat_white"); break; case 8: + s->set("IconTheme", "multimc"); + break; + case 9: s->set("IconTheme", "custom"); break; } @@ -330,6 +335,18 @@ void LauncherPage::applySettings() APPLICATION->setApplicationTheme(newAppTheme, false); } + switch (ui->themeBackgroundCat->currentIndex()) { + case 0: // original cat + s->set("BackgroundCat", "kitteh"); + break; + case 1: // rory the cat + s->set("BackgroundCat", "rory"); + break; + case 2: // rory the cat flat edition + s->set("BackgroundCat", "rory-flat"); + break; + } + s->set("MenuBarInsteadOfToolBar", ui->preferMenuBarCheckBox->isChecked()); // Console settings @@ -380,41 +397,16 @@ void LauncherPage::loadSettings() m_currentUpdateChannel = s->get("UpdateChannel").toString(); //FIXME: make generic auto theme = s->get("IconTheme").toString(); - if (theme == "pe_colored") - { - ui->themeComboBox->setCurrentIndex(0); - } - else if (theme == "pe_light") - { - ui->themeComboBox->setCurrentIndex(1); - } - else if (theme == "pe_dark") - { - ui->themeComboBox->setCurrentIndex(2); - } - else if (theme == "pe_blue") - { - ui->themeComboBox->setCurrentIndex(3); - } - else if (theme == "OSX") - { - ui->themeComboBox->setCurrentIndex(4); - } - else if (theme == "iOS") - { - ui->themeComboBox->setCurrentIndex(5); - } - else if (theme == "flat") - { - ui->themeComboBox->setCurrentIndex(6); - } - else if (theme == "multimc") - { - ui->themeComboBox->setCurrentIndex(7); - } - else if (theme == "custom") - { - ui->themeComboBox->setCurrentIndex(8); + QStringList iconThemeOptions{"pe_colored", "pe_light", "pe_dark", "pe_blue", "OSX", "iOS", "flat", "flat_white", "multimc", "custom"}; + ui->themeComboBox->setCurrentIndex(iconThemeOptions.indexOf(theme)); + + auto cat = s->get("BackgroundCat").toString(); + if (cat == "kitteh") { + ui->themeBackgroundCat->setCurrentIndex(0); + } else if (cat == "rory") { + ui->themeBackgroundCat->setCurrentIndex(1); + } else if (cat == "rory-flat") { + ui->themeBackgroundCat->setCurrentIndex(2); } { diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index 0d14f147..6de644ee 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -302,6 +302,11 @@ </item> <item> <property name="text"> + <string>Flat (White)</string> + </property> + </item> + <item> + <property name="text"> <string>Legacy</string> </property> </item> @@ -335,6 +340,44 @@ </property> </widget> </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>C&at</string> + </property> + <property name="buddy"> + <cstring>themeBackgroundCat</cstring> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QComboBox" name="themeBackgroundCat"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="focusPolicy"> + <enum>Qt::StrongFocus</enum> + </property> + <item> + <property name="text"> + <string>Background Cat (from MultiMC)</string> + </property> + </item> + <item> + <property name="text"> + <string>Rory ID 11 (drawn by Ashtaka)</string> + </property> + </item> + <item> + <property name="text"> + <string>Rory ID 11 (flat edition, drawn by Ashtaka)</string> + </property> + </item> + </widget> + </item> </layout> </widget> </item> diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.ui b/launcher/ui/pages/instance/ExternalResourcesPage.ui index 76f8ec18..33a03336 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.ui +++ b/launcher/ui/pages/instance/ExternalResourcesPage.ui @@ -27,11 +27,7 @@ <item row="4" column="1" colspan="3"> <layout class="QGridLayout" name="gridLayout_2"> <item row="0" column="1"> - <widget class="QLineEdit" name="filterEdit"> - <property name="clearButtonEnabled"> - <bool>true</bool> - </property> - </widget> + <widget class="QLineEdit" name="filterEdit"/> </item> <item row="0" column="0"> <widget class="QLabel" name="filterLabel"> diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 5e8bd7cc..d64bcb76 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -400,11 +400,11 @@ public: virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override { - return m_servers.size(); + return parent.isValid() ? 0 : m_servers.size(); } int columnCount(const QModelIndex & parent) const override { - return COLUMN_COUNT; + return parent.isValid() ? 0 : COLUMN_COUNT; } Server * at(int index) diff --git a/launcher/ui/pages/instance/VersionPage.ui b/launcher/ui/pages/instance/VersionPage.ui index fcba5598..14b7cd9f 100644 --- a/launcher/ui/pages/instance/VersionPage.ui +++ b/launcher/ui/pages/instance/VersionPage.ui @@ -48,11 +48,7 @@ <item> <layout class="QGridLayout" name="gridLayout_2"> <item row="0" column="1"> - <widget class="QLineEdit" name="filterEdit"> - <property name="clearButtonEnabled"> - <bool>true</bool> - </property> - </widget> + <widget class="QLineEdit" name="filterEdit"/> </item> <item row="0" column="0"> <widget class="QLabel" name="filterLabel"> diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index 49766fa6..ed58eb32 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -267,18 +267,25 @@ void ListModel::searchRequestFailed(QString reason) .arg(m_parent->displayName()) .arg(tr("API version too old!\nPlease update %1!").arg(BuildConfig.LAUNCHER_DISPLAYNAME))); } + jobPtr.reset(); + searchState = Finished; +} - if (searchState == ResetRequested) { - beginResetModel(); - modpacks.clear(); - endResetModel(); +void ListModel::searchRequestAborted() +{ + if (searchState != ResetRequested) + qCritical() << "Search task in ModModel aborted by an unknown reason!"; - nextSearchOffset = 0; - performPaginatedSearch(); - } else { - searchState = Finished; - } + // Retry fetching + jobPtr.reset(); + + beginResetModel(); + modpacks.clear(); + endResetModel(); + + nextSearchOffset = 0; + performPaginatedSearch(); } void ListModel::infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index) diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h index a58c7c55..36840649 100644 --- a/launcher/ui/pages/modplatform/ModModel.h +++ b/launcher/ui/pages/modplatform/ModModel.h @@ -20,8 +20,8 @@ class ListModel : public QAbstractListModel { ListModel(ModPage* parent); ~ListModel() override; - inline auto rowCount(const QModelIndex& parent) const -> int override { return modpacks.size(); }; - inline auto columnCount(const QModelIndex& parent) const -> int override { return 1; }; + inline auto rowCount(const QModelIndex& parent) const -> int override { return parent.isValid() ? 0 : modpacks.size(); }; + inline auto columnCount(const QModelIndex& parent) const -> int override { return parent.isValid() ? 0 : 1; }; inline auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); }; auto debugName() const -> QString; @@ -41,16 +41,17 @@ class ListModel : public QAbstractListModel { void requestModVersions(const ModPlatform::IndexedPack& current, QModelIndex index); virtual void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0; - virtual void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) {}; + virtual void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0; virtual void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) = 0; void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback); - inline auto canFetchMore(const QModelIndex& parent) const -> bool override { return searchState == CanPossiblyFetchMore; }; + inline auto canFetchMore(const QModelIndex& parent) const -> bool override { return parent.isValid() ? false : searchState == CanPossiblyFetchMore; }; public slots: void searchRequestFinished(QJsonDocument& doc); void searchRequestFailed(QString reason); + void searchRequestAborted(); void infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index); diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index f2c1746f..677bc4d6 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -1,7 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-only /* - * PolyMC - Minecraft Launcher + * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> + * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me> * * 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 @@ -37,7 +38,9 @@ #include "Application.h" #include "ui_ModPage.h" +#include <QDesktopServices> #include <QKeyEvent> +#include <QRegularExpression> #include <memory> #include <HoeDown.h> @@ -80,6 +83,8 @@ ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api) ui->packView->setItemDelegate(new ProjectItemDelegate(this)); ui->packView->installEventFilter(this); + + connect(ui->packDescription, &QTextBrowser::anchorClicked, this, &ModPage::openUrl); } ModPage::~ModPage() @@ -158,8 +163,8 @@ void ModPage::triggerSearch() { auto changed = m_filter_widget->changed(); m_filter = m_filter_widget->getFilter(); - - if(changed){ + + if (changed) { ui->packView->clearSelection(); ui->packDescription->clear(); ui->versionSelectionBox->clear(); @@ -241,6 +246,79 @@ void ModPage::onModSelected() ui->packView->adjustSize(); } +static const QRegularExpression modrinth(QRegularExpression::anchoredPattern("(?:www\\.)?modrinth\\.com\\/mod\\/([^\\/]+)\\/?")); +static const QRegularExpression curseForge(QRegularExpression::anchoredPattern("(?:www\\.)?curseforge\\.com\\/minecraft\\/mc-mods\\/([^\\/]+)\\/?")); +static const QRegularExpression curseForgeOld(QRegularExpression::anchoredPattern("minecraft\\.curseforge\\.com\\/projects\\/([^\\/]+)\\/?")); + +void ModPage::openUrl(const QUrl& url) +{ + // do not allow other url schemes for security reasons + if (!(url.scheme() == "http" || url.scheme() == "https")) { + qWarning() << "Unsupported scheme" << url.scheme(); + return; + } + + // detect mod URLs and search instead + + const QString address = url.host() + url.path(); + QRegularExpressionMatch match; + QString page; + + match = modrinth.match(address); + if (match.hasMatch()) + page = "modrinth"; + else if (APPLICATION->capabilities() & Application::SupportsFlame) { + match = curseForge.match(address); + if (!match.hasMatch()) + match = curseForgeOld.match(address); + + if (match.hasMatch()) + page = "curseforge"; + } + + if (!page.isNull()) { + const QString slug = match.captured(1); + + // ensure the user isn't opening the same mod + if (slug != current.slug) { + dialog->selectPage(page); + + ModPage* newPage = dialog->getSelectedPage(); + + QLineEdit* searchEdit = newPage->ui->searchEdit; + ModPlatform::ListModel* model = newPage->listModel; + QListView* view = newPage->ui->packView; + + auto jump = [url, slug, model, view] { + for (int row = 0; row < model->rowCount({}); row++) { + const QModelIndex index = model->index(row); + const auto pack = model->data(index, Qt::UserRole).value<ModPlatform::IndexedPack>(); + + if (pack.slug == slug) { + view->setCurrentIndex(index); + return; + } + } + + // The final fallback. + QDesktopServices::openUrl(url); + }; + + searchEdit->setText(slug); + newPage->triggerSearch(); + + if (model->activeJob()) + connect(model->activeJob(), &Task::finished, jump); + else + jump(); + + return; + } + } + + // open in the user's web browser + QDesktopServices::openUrl(url); +} /******** Make changes to the UI ********/ @@ -270,8 +348,8 @@ void ModPage::updateModVersions(int prev_count) if ((valid || m_filter->versions.empty()) && !optedOut(version)) ui->versionSelectionBox->addItem(version.version, QVariant(i)); } - if (ui->versionSelectionBox->count() == 0 && prev_count != 0) { - ui->versionSelectionBox->addItem(tr("No valid version found!"), QVariant(-1)); + if (ui->versionSelectionBox->count() == 0 && prev_count != 0) { + ui->versionSelectionBox->addItem(tr("No valid version found!"), QVariant(-1)); ui->modSelectionButton->setText(tr("Cannot select invalid version :(")); } @@ -317,8 +395,7 @@ void ModPage::updateUi() text += "<br>" + tr(" by ") + authorStrs.join(", "); } - - if(current.extraDataLoaded) { + if (current.extraDataLoaded) { if (!current.extraData.donate.isEmpty()) { text += "<br><br>" + tr("Donate information: "); auto donateToStr = [](ModPlatform::DonationData& donate) -> QString { diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index ae3d7e77..c9ccbaf2 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -82,6 +82,7 @@ class ModPage : public QWidget, public BasePage { void onSelectionChanged(QModelIndex first, QModelIndex second); void onVersionSelectionChanged(QString data); void onModSelected(); + virtual void openUrl(const QUrl& url); protected: Ui::ModPage* ui = nullptr; diff --git a/launcher/ui/pages/modplatform/ModPage.ui b/launcher/ui/pages/modplatform/ModPage.ui index 943f02aa..94365aa5 100644 --- a/launcher/ui/pages/modplatform/ModPage.ui +++ b/launcher/ui/pages/modplatform/ModPage.ui @@ -16,10 +16,10 @@ <item row="1" column="2"> <widget class="ProjectDescriptionPage" name="packDescription"> <property name="openExternalLinks"> - <bool>true</bool> + <bool>false</bool> </property> <property name="openLinks"> - <bool>true</bool> + <bool>false</bool> </property> </widget> </item> diff --git a/launcher/ui/pages/modplatform/VanillaPage.cpp b/launcher/ui/pages/modplatform/VanillaPage.cpp index 99190f31..29fecb85 100644 --- a/launcher/ui/pages/modplatform/VanillaPage.cpp +++ b/launcher/ui/pages/modplatform/VanillaPage.cpp @@ -187,12 +187,12 @@ void VanillaPage::retranslate() ui->retranslateUi(this); } -BaseVersionPtr VanillaPage::selectedVersion() const +BaseVersion::Ptr VanillaPage::selectedVersion() const { return m_selectedVersion; } -BaseVersionPtr VanillaPage::selectedLoaderVersion() const +BaseVersion::Ptr VanillaPage::selectedLoaderVersion() const { return m_selectedLoaderVersion; } @@ -227,14 +227,14 @@ void VanillaPage::suggestCurrent() dialog->setSuggestedIcon("default"); } -void VanillaPage::setSelectedVersion(BaseVersionPtr version) +void VanillaPage::setSelectedVersion(BaseVersion::Ptr version) { m_selectedVersion = version; suggestCurrent(); loaderFilterChanged(); } -void VanillaPage::setSelectedLoaderVersion(BaseVersionPtr version) +void VanillaPage::setSelectedLoaderVersion(BaseVersion::Ptr version) { m_selectedLoaderVersion = version; suggestCurrent(); diff --git a/launcher/ui/pages/modplatform/VanillaPage.h b/launcher/ui/pages/modplatform/VanillaPage.h index 7193597d..39aba760 100644 --- a/launcher/ui/pages/modplatform/VanillaPage.h +++ b/launcher/ui/pages/modplatform/VanillaPage.h @@ -76,13 +76,13 @@ public: void openedImpl() override; - BaseVersionPtr selectedVersion() const; - BaseVersionPtr selectedLoaderVersion() const; + BaseVersion::Ptr selectedVersion() const; + BaseVersion::Ptr selectedLoaderVersion() const; QString selectedLoader() const; public slots: - void setSelectedVersion(BaseVersionPtr version); - void setSelectedLoaderVersion(BaseVersionPtr version); + void setSelectedVersion(BaseVersion::Ptr version); + void setSelectedLoaderVersion(BaseVersion::Ptr version); private slots: void filterChanged(); @@ -98,7 +98,7 @@ private: NewInstanceDialog *dialog = nullptr; Ui::VanillaPage *ui = nullptr; bool m_versionSetByUser = false; - BaseVersionPtr m_selectedVersion; - BaseVersionPtr m_selectedLoaderVersion; + BaseVersion::Ptr m_selectedVersion; + BaseVersion::Ptr m_selectedLoaderVersion; QString m_selectedLoader; }; diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp index c1ab166b..0887ebee 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp @@ -20,7 +20,8 @@ #include <modplatform/atlauncher/ATLPackIndex.h> #include <Version.h> -#include <MMCStrings.h> + +#include "StringUtils.h" namespace Atl { @@ -86,7 +87,7 @@ bool FilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) co return lv < rv; } else if (currentSorting == ByName) { - return Strings::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0; + return StringUtils::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0; } // Invalid sorting set, somehow... diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp index ef9a9268..2ce04068 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp @@ -32,12 +32,12 @@ ListModel::~ListModel() int ListModel::rowCount(const QModelIndex &parent) const { - return modpacks.size(); + return parent.isValid() ? 0 : modpacks.size(); } int ListModel::columnCount(const QModelIndex &parent) const { - return 1; + return parent.isValid() ? 0 : 1; } QVariant ListModel::data(const QModelIndex &index, int role) const diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp index 9138dcbb..cdb4532c 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp @@ -75,12 +75,12 @@ QVector<QString> AtlOptionalModListModel::getResult() { } int AtlOptionalModListModel::rowCount(const QModelIndex &parent) const { - return m_mods.size(); + return parent.isValid() ? 0 : m_mods.size(); } int AtlOptionalModListModel::columnCount(const QModelIndex &parent) const { // Enabled, Name, Description - return 3; + return parent.isValid() ? 0 : 3; } QVariant AtlOptionalModListModel::data(const QModelIndex &index, int role) const { diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp index c68e40ba..f5f50cae 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp @@ -53,7 +53,7 @@ std::optional<QVector<QString>> AtlUserInteractionSupportImpl::chooseOptionalMod return optionalModDialog.getResult(); } -QString AtlUserInteractionSupportImpl::chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) +QString AtlUserInteractionSupportImpl::chooseVersion(Meta::VersionList::Ptr vlist, QString minecraftVersion) { VersionSelectDialog vselect(vlist.get(), "Choose Version", m_parent, false); if (minecraftVersion != nullptr) { diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h index 3b37c9be..37010b3f 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h +++ b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h @@ -46,7 +46,7 @@ public: AtlUserInteractionSupportImpl(QWidget* parent); private: - QString chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) override; + QString chooseVersion(Meta::VersionList::Ptr vlist, QString minecraftVersion) override; std::optional<QVector<QString>> chooseOptionalMods(ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods) override; void displayMessage(QString message) override; diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp index fd6e32ff..bad78c97 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp @@ -1,7 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-only /* - * PolyMC - Minecraft Launcher + * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> + * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me> * * 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 @@ -39,7 +40,7 @@ #include "FlameModModel.h" #include "ui/dialogs/ModDownloadDialog.h" -FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance* instance) +FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance* instance) : ModPage(dialog, instance, new FlameAPI()) { listModel = new FlameMod::ListModel(this); @@ -53,7 +54,7 @@ FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance* instance) ui->sortByBox->addItem(tr("Sort by Author")); ui->sortByBox->addItem(tr("Sort by Downloads")); - // sometimes Qt just ignores virtual slots and doesn't work as intended it seems, + // sometimes Qt just ignores virtual slots and doesn't work as intended it seems, // so it's best not to connect them in the parent's contructor... connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameModPage::onSelectionChanged); @@ -78,3 +79,19 @@ bool FlameModPage::optedOut(ModPlatform::IndexedVersion& ver) const // other mod providers start loading before being selected, at least with // my Qt, so we need to implement this in every derived class... auto FlameModPage::shouldDisplay() const -> bool { return true; } + +void FlameModPage::openUrl(const QUrl& url) +{ + if (url.scheme().isEmpty()) { + QString query = url.query(QUrl::FullyDecoded); + + if (query.startsWith("remoteUrl=")) { + // attempt to resolve url from warning page + query.remove(0, 10); + ModPage::openUrl({QUrl::fromPercentEncoding(query.toUtf8())}); // double decoding is necessary + return; + } + } + + ModPage::openUrl(url); +} diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.h b/launcher/ui/pages/modplatform/flame/FlameModPage.h index 50dedd6f..58479ab9 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModPage.h +++ b/launcher/ui/pages/modplatform/flame/FlameModPage.h @@ -1,7 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-only /* - * PolyMC - Minecraft Launcher + * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> + * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me> * * 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 @@ -64,4 +65,6 @@ class FlameModPage : public ModPage { bool optedOut(ModPlatform::IndexedVersion& ver) const override; auto shouldDisplay() const -> bool override; + + void openUrl(const QUrl& url) override; }; diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModel.cpp index 9f8605eb..127c3de5 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModel.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModel.cpp @@ -3,7 +3,6 @@ #include "Application.h" #include "ui/widgets/ProjectItem.h" -#include <MMCStrings.h> #include <Version.h> #include <QtMath> @@ -16,12 +15,12 @@ ListModel::~ListModel() {} int ListModel::rowCount(const QModelIndex& parent) const { - return modpacks.size(); + return parent.isValid() ? 0 : modpacks.size(); } int ListModel::columnCount(const QModelIndex& parent) const { - return 1; + return parent.isValid() ? 0 : 1; } QVariant ListModel::data(const QModelIndex& index, int role) const diff --git a/launcher/ui/pages/modplatform/ftb/FtbFilterModel.cpp b/launcher/ui/pages/modplatform/ftb/FtbFilterModel.cpp index cbf347fc..e2b548f2 100644 --- a/launcher/ui/pages/modplatform/ftb/FtbFilterModel.cpp +++ b/launcher/ui/pages/modplatform/ftb/FtbFilterModel.cpp @@ -19,7 +19,8 @@ #include <QDebug> #include "modplatform/modpacksch/FTBPackManifest.h" -#include <MMCStrings.h> + +#include "StringUtils.h" namespace Ftb { @@ -81,7 +82,7 @@ bool FilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) co return leftPack.installs < rightPack.installs; } else if (currentSorting == ByName) { - return Strings::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0; + return StringUtils::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0; } // Invalid sorting set, somehow... diff --git a/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp b/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp index 3a149944..ce2b2b18 100644 --- a/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp +++ b/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp @@ -34,12 +34,12 @@ ListModel::~ListModel() int ListModel::rowCount(const QModelIndex &parent) const { - return modpacks.size(); + return parent.isValid() ? 0 : modpacks.size(); } int ListModel::columnCount(const QModelIndex &parent) const { - return 1; + return parent.isValid() ? 0 : 1; } QVariant ListModel::data(const QModelIndex &index, int role) const diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp index 2d135e59..6b1f6b89 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp +++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp @@ -36,7 +36,7 @@ #include "ListModel.h" #include "Application.h" -#include <MMCStrings.h> +#include "StringUtils.h" #include <Version.h> #include <QtMath> @@ -66,7 +66,7 @@ bool FilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) co return lv < rv; } else if(currentSorting == Sorting::ByName) { - return Strings::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0; + return StringUtils::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0; } //UHM, some inavlid value set?! @@ -125,12 +125,12 @@ QString ListModel::translatePackType(PackType type) const int ListModel::rowCount(const QModelIndex &parent) const { - return modpacks.size(); + return parent.isValid() ? 0 : modpacks.size(); } int ListModel::columnCount(const QModelIndex &parent) const { - return 1; + return parent.isValid() ? 0 : 1; } QVariant ListModel::data(const QModelIndex &index, int role) const diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp index 62e417c8..c531ea90 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp @@ -53,7 +53,7 @@ ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance* instan ui->sortByBox->addItem(tr("Sort by Last Updated")); ui->sortByBox->addItem(tr("Sort by Newest")); - // sometimes Qt just ignores virtual slots and doesn't work as intended it seems, + // sometimes Qt just ignores virtual slots and doesn't work as intended it seems, // so it's best not to connect them in the parent's constructor... connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthModPage::onSelectionChanged); diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h index 6f33e11e..3be377a1 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h @@ -55,8 +55,8 @@ class ModpackListModel : public QAbstractListModel { ModpackListModel(ModrinthPage* parent); ~ModpackListModel() override = default; - inline auto rowCount(const QModelIndex& parent) const -> int override { return modpacks.size(); }; - inline auto columnCount(const QModelIndex& parent) const -> int override { return 1; }; + inline auto rowCount(const QModelIndex& parent) const -> int override { return parent.isValid() ? 0 : modpacks.size(); }; + inline auto columnCount(const QModelIndex& parent) const -> int override { return parent.isValid() ? 0 : 1; }; inline auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); }; auto debugName() const -> QString; @@ -74,7 +74,7 @@ class ModpackListModel : public QAbstractListModel { void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback); - inline auto canFetchMore(const QModelIndex& parent) const -> bool override { return searchState == CanPossiblyFetchMore; }; + inline auto canFetchMore(const QModelIndex& parent) const -> bool override { return parent.isValid() ? false : searchState == CanPossiblyFetchMore; }; public slots: void searchRequestFinished(QJsonDocument& doc_all); diff --git a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp index 742f4f2a..b2af1ac0 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp +++ b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp @@ -80,14 +80,14 @@ QVariant Technic::ListModel::data(const QModelIndex& index, int role) const return QVariant(); } -int Technic::ListModel::columnCount(const QModelIndex&) const +int Technic::ListModel::columnCount(const QModelIndex& parent) const { - return 1; + return parent.isValid() ? 0 : 1; } -int Technic::ListModel::rowCount(const QModelIndex&) const +int Technic::ListModel::rowCount(const QModelIndex& parent) const { - return modpacks.size(); + return parent.isValid() ? 0 : modpacks.size(); } void Technic::ListModel::searchWithTerm(const QString& term) diff --git a/launcher/ui/themes/CustomTheme.cpp b/launcher/ui/themes/CustomTheme.cpp index 3e3e27de..3ad61668 100644 --- a/launcher/ui/themes/CustomTheme.cpp +++ b/launcher/ui/themes/CustomTheme.cpp @@ -1,48 +1,81 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Tayou <tayou@gmx.net> + * + * 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 "CustomTheme.h" -#include <QDir> -#include <Json.h> #include <FileSystem.h> +#include <Json.h> +#include "ThemeManager.h" -const char * themeFile = "theme.json"; -const char * styleFile = "themeStyle.css"; +const char* themeFile = "theme.json"; -static bool readThemeJson(const QString &path, QPalette &palette, double &fadeAmount, QColor &fadeColor, QString &name, QString &widgets) +static bool readThemeJson(const QString& path, + QPalette& palette, + double& fadeAmount, + QColor& fadeColor, + QString& name, + QString& widgets, + QString& qssFilePath, + bool& dataIncomplete) { QFileInfo pathInfo(path); - if(pathInfo.exists() && pathInfo.isFile()) - { - try - { + if (pathInfo.exists() && pathInfo.isFile()) { + try { auto doc = Json::requireDocument(path, "Theme JSON file"); const QJsonObject root = doc.object(); + dataIncomplete = !root.contains("qssFilePath"); name = Json::requireString(root, "name", "Theme name"); widgets = Json::requireString(root, "widgets", "Qt widget theme"); + qssFilePath = Json::ensureString(root, "qssFilePath", "themeStyle.css"); auto colorsRoot = Json::requireObject(root, "colors", "colors object"); - auto readColor = [&](QString colorName) -> QColor - { + auto readColor = [&](QString colorName) -> QColor { auto colorValue = Json::ensureString(colorsRoot, colorName, QString()); - if(!colorValue.isEmpty()) - { + if (!colorValue.isEmpty()) { QColor color(colorValue); - if(!color.isValid()) - { - qWarning() << "Color value" << colorValue << "for" << colorName << "was not recognized."; + if (!color.isValid()) { + themeWarningLog() << "Color value" << colorValue << "for" << colorName << "was not recognized."; return QColor(); } return color; } return QColor(); }; - auto readAndSetColor = [&](QPalette::ColorRole role, QString colorName) - { + auto readAndSetColor = [&](QPalette::ColorRole role, QString colorName) { auto color = readColor(colorName); - if(color.isValid()) - { + if (color.isValid()) { palette.setColor(role, color); - } - else - { - qDebug() << "Color value for" << colorName << "was not present."; + } else { + themeDebugLog() << "Color value for" << colorName << "was not present."; } }; @@ -61,36 +94,36 @@ static bool readThemeJson(const QString &path, QPalette &palette, double &fadeAm readAndSetColor(QPalette::Highlight, "Highlight"); readAndSetColor(QPalette::HighlightedText, "HighlightedText"); - //fade + // fade fadeColor = readColor("fadeColor"); fadeAmount = Json::ensureDouble(colorsRoot, "fadeAmount", 0.5, "fade amount"); - } - catch (const Exception &e) - { - qWarning() << "Couldn't load theme json: " << e.cause(); + } catch (const Exception& e) { + themeWarningLog() << "Couldn't load theme json: " << e.cause(); return false; } - } - else - { - qDebug() << "No theme json present."; + } else { + themeDebugLog() << "No theme json present."; return false; } return true; } -static bool writeThemeJson(const QString &path, const QPalette &palette, double fadeAmount, QColor fadeColor, QString name, QString widgets) +static bool writeThemeJson(const QString& path, + const QPalette& palette, + double fadeAmount, + QColor fadeColor, + QString name, + QString widgets, + QString qssFilePath) { QJsonObject rootObj; rootObj.insert("name", name); rootObj.insert("widgets", widgets); + rootObj.insert("qssFilePath", qssFilePath); QJsonObject colorsObj; - auto insertColor = [&](QPalette::ColorRole role, QString colorName) - { - colorsObj.insert(colorName, palette.color(role).name()); - }; + auto insertColor = [&](QPalette::ColorRole role, QString colorName) { colorsObj.insert(colorName, palette.color(role).name()); }; // palette insertColor(QPalette::Window, "Window"); @@ -112,82 +145,95 @@ static bool writeThemeJson(const QString &path, const QPalette &palette, double colorsObj.insert("fadeAmount", fadeAmount); rootObj.insert("colors", colorsObj); - try - { + try { Json::write(rootObj, path); return true; - } - catch (const Exception &e) - { - qWarning() << "Failed to write theme json to" << path; + } catch (const Exception& e) { + themeWarningLog() << "Failed to write theme json to" << path; return false; } } -CustomTheme::CustomTheme(ITheme* baseTheme, QString folder) +/// @param baseTheme Base Theme +/// @param fileInfo FileInfo object for file to load +/// @param isManifest whether to load a theme manifest or a qss file +CustomTheme::CustomTheme(ITheme* baseTheme, QFileInfo& fileInfo, bool isManifest) { - m_id = folder; - QString path = FS::PathCombine("themes", m_id); - QString pathResources = FS::PathCombine("themes", m_id, "resources"); + if (isManifest) { + m_id = fileInfo.dir().dirName(); - qDebug() << "Loading theme" << m_id; + QString path = FS::PathCombine("themes", m_id); + QString pathResources = FS::PathCombine("themes", m_id, "resources"); - if(!FS::ensureFolderPathExists(path) || !FS::ensureFolderPathExists(pathResources)) - { - qWarning() << "couldn't create folder for theme!"; - m_palette = baseTheme->colorScheme(); - m_styleSheet = baseTheme->appStyleSheet(); - return; - } + if (!FS::ensureFolderPathExists(path) || !FS::ensureFolderPathExists(pathResources)) { + themeWarningLog() << "couldn't create folder for theme!"; + m_palette = baseTheme->colorScheme(); + m_styleSheet = baseTheme->appStyleSheet(); + return; + } + + auto themeFilePath = FS::PathCombine(path, themeFile); - auto themeFilePath = FS::PathCombine(path, themeFile); + bool jsonDataIncomplete = false; - m_palette = baseTheme->colorScheme(); - if (!readThemeJson(themeFilePath, m_palette, m_fadeAmount, m_fadeColor, m_name, m_widgets)) - { - m_name = "Custom"; m_palette = baseTheme->colorScheme(); - m_fadeColor = baseTheme->fadeColor(); - m_fadeAmount = baseTheme->fadeAmount(); - m_widgets = baseTheme->qtTheme(); - - QFileInfo info(themeFilePath); - if(!info.exists()) - { - writeThemeJson(themeFilePath, m_palette, m_fadeAmount, m_fadeColor, "Custom", m_widgets); + if (!readThemeJson(themeFilePath, m_palette, m_fadeAmount, m_fadeColor, m_name, m_widgets, m_qssFilePath, jsonDataIncomplete)) { + themeDebugLog() << "Did not read theme json file correctly, writing new one to: " << themeFilePath; + m_name = "Custom"; + m_palette = baseTheme->colorScheme(); + m_fadeColor = baseTheme->fadeColor(); + m_fadeAmount = baseTheme->fadeAmount(); + m_widgets = baseTheme->qtTheme(); + m_qssFilePath = "themeStyle.css"; + } else { + m_palette = fadeInactive(m_palette, m_fadeAmount, m_fadeColor); } - } - else - { - m_palette = fadeInactive(m_palette, m_fadeAmount, m_fadeColor); - } - auto cssFilePath = FS::PathCombine(path, styleFile); - QFileInfo info (cssFilePath); - if(info.isFile()) - { - try - { - // TODO: validate css? - m_styleSheet = QString::fromUtf8(FS::read(cssFilePath)); + if (jsonDataIncomplete) { + writeThemeJson(fileInfo.absoluteFilePath(), m_palette, m_fadeAmount, m_fadeColor, m_name, m_widgets, m_qssFilePath); } - catch (const Exception &e) - { - qWarning() << "Couldn't load css:" << e.cause() << "from" << cssFilePath; + + auto qssFilePath = FS::PathCombine(path, m_qssFilePath); + QFileInfo info(qssFilePath); + if (info.isFile()) { + try { + // TODO: validate css? + m_styleSheet = QString::fromUtf8(FS::read(qssFilePath)); + } catch (const Exception& e) { + themeWarningLog() << "Couldn't load css:" << e.cause() << "from" << qssFilePath; + m_styleSheet = baseTheme->appStyleSheet(); + } + } else { + themeDebugLog() << "No theme css present."; m_styleSheet = baseTheme->appStyleSheet(); + try { + FS::write(qssFilePath, m_styleSheet.toUtf8()); + } catch (const Exception& e) { + themeWarningLog() << "Couldn't write css:" << e.cause() << "to" << qssFilePath; + } } - } - else - { - qDebug() << "No theme css present."; - m_styleSheet = baseTheme->appStyleSheet(); - try - { - FS::write(cssFilePath, m_styleSheet.toUtf8()); + } else { + m_id = fileInfo.fileName(); + m_name = fileInfo.baseName(); + QString path = fileInfo.filePath(); + // themeDebugLog << "Theme ID: " << m_id; + // themeDebugLog << "Theme Name: " << m_name; + // themeDebugLog << "Theme Path: " << path; + + if (!FS::ensureFilePathExists(path)) { + themeWarningLog() << m_name << " Theme file path doesn't exist!"; + m_palette = baseTheme->colorScheme(); + m_styleSheet = baseTheme->appStyleSheet(); + return; } - catch (const Exception &e) - { - qWarning() << "Couldn't write css:" << e.cause() << "to" << cssFilePath; + + m_palette = baseTheme->colorScheme(); + try { + // TODO: validate qss? + m_styleSheet = QString::fromUtf8(FS::read(path)); + } catch (const Exception& e) { + themeWarningLog() << "Couldn't load qss:" << e.cause() << "from" << path; + m_styleSheet = baseTheme->appStyleSheet(); } } } @@ -197,7 +243,6 @@ QStringList CustomTheme::searchPaths() return { FS::PathCombine("themes", m_id, "resources") }; } - QString CustomTheme::id() { return m_id; diff --git a/launcher/ui/themes/CustomTheme.h b/launcher/ui/themes/CustomTheme.h index d216895d..f2b1b06e 100644 --- a/launcher/ui/themes/CustomTheme.h +++ b/launcher/ui/themes/CustomTheme.h @@ -1,11 +1,45 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Tayou <tayou@gmx.net> + * + * 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 <QFileInfo> #include "ITheme.h" -class CustomTheme: public ITheme -{ -public: - CustomTheme(ITheme * baseTheme, QString folder); +class CustomTheme : public ITheme { + public: + CustomTheme(ITheme* baseTheme, QFileInfo& file, bool isManifest); virtual ~CustomTheme() {} QString id() override; @@ -19,7 +53,7 @@ public: QString qtTheme() override; QStringList searchPaths() override; -private: /* data */ + private: /* data */ QPalette m_palette; QColor m_fadeColor; double m_fadeAmount; @@ -27,5 +61,5 @@ private: /* data */ QString m_name; QString m_id; QString m_widgets; + QString m_qssFilePath; }; - diff --git a/launcher/ui/themes/SystemTheme.cpp b/launcher/ui/themes/SystemTheme.cpp index 49b1afaa..a63d1741 100644 --- a/launcher/ui/themes/SystemTheme.cpp +++ b/launcher/ui/themes/SystemTheme.cpp @@ -1,30 +1,65 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Tayou <tayou@gmx.net> + * + * 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 "SystemTheme.h" #include <QApplication> #include <QStyle> #include <QStyleFactory> #include <QDebug> +#include "ThemeManager.h" SystemTheme::SystemTheme() { - qDebug() << "Determining System Theme..."; + themeDebugLog() << "Determining System Theme..."; const auto & style = QApplication::style(); systemPalette = style->standardPalette(); QString lowerThemeName = style->objectName(); - qDebug() << "System theme seems to be:" << lowerThemeName; + themeDebugLog() << "System theme seems to be:" << lowerThemeName; QStringList styles = QStyleFactory::keys(); for(auto &st: styles) { - qDebug() << "Considering theme from theme factory:" << st.toLower(); + themeDebugLog() << "Considering theme from theme factory:" << st.toLower(); if(st.toLower() == lowerThemeName) { systemTheme = st; - qDebug() << "System theme has been determined to be:" << systemTheme; + themeDebugLog() << "System theme has been determined to be:" << systemTheme; return; } } // fall back to fusion if we can't find the current theme. systemTheme = "Fusion"; - qDebug() << "System theme not found, defaulted to Fusion"; + themeDebugLog() << "System theme not found, defaulted to Fusion"; } void SystemTheme::apply(bool initial) diff --git a/launcher/ui/themes/ThemeManager.cpp b/launcher/ui/themes/ThemeManager.cpp new file mode 100644 index 00000000..01a38a86 --- /dev/null +++ b/launcher/ui/themes/ThemeManager.cpp @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 Tayou <tayou@gmx.net>
+ *
+ * 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/>.
+ */
+#include "ThemeManager.h"
+
+#include <QApplication>
+#include <QDir>
+#include <QDirIterator>
+#include <QIcon>
+#include "ui/themes/BrightTheme.h"
+#include "ui/themes/CustomTheme.h"
+#include "ui/themes/DarkTheme.h"
+#include "ui/themes/SystemTheme.h"
+
+#include "Application.h"
+
+#ifdef Q_OS_WIN
+#include <windows.h>
+// this is needed for versionhelpers.h, it is also included in WinDarkmode, but we can't rely on that.
+// Ultimately this should be included in versionhelpers, but that is outside of the project.
+#include "ui/WinDarkmode.h"
+#include <versionhelpers.h>
+#endif
+
+ThemeManager::ThemeManager(MainWindow* mainWindow)
+{
+ m_mainWindow = mainWindow;
+ InitializeThemes();
+}
+
+/// @brief Adds the Theme to the list of themes
+/// @param theme The Theme to add
+/// @return Theme ID
+QString ThemeManager::AddTheme(std::unique_ptr<ITheme> theme)
+{
+ QString id = theme->id();
+ m_themes.emplace(id, std::move(theme));
+ return id;
+}
+
+/// @brief Gets the Theme from the List via ID
+/// @param themeId Theme ID of theme to fetch
+/// @return Theme at themeId
+ITheme* ThemeManager::GetTheme(QString themeId)
+{
+ return m_themes[themeId].get();
+}
+
+void ThemeManager::InitializeThemes()
+{
+ // Icon themes
+ {
+ // TODO: icon themes and instance icons do not mesh well together. Rearrange and fix discrepancies!
+ // set icon theme search path!
+ auto searchPaths = QIcon::themeSearchPaths();
+ searchPaths.append("iconthemes");
+ QIcon::setThemeSearchPaths(searchPaths);
+ themeDebugLog() << "<> Icon themes initialized.";
+ }
+
+ // Initialize widget themes
+ {
+ themeDebugLog() << "<> Initializing Widget Themes";
+ themeDebugLog() << "Loading Built-in Theme:" << AddTheme(std::make_unique<SystemTheme>());
+ auto darkThemeId = AddTheme(std::make_unique<DarkTheme>());
+ themeDebugLog() << "Loading Built-in Theme:" << darkThemeId;
+ themeDebugLog() << "Loading Built-in Theme:" << AddTheme(std::make_unique<BrightTheme>());
+
+ // TODO: need some way to differentiate same name themes in different subdirectories (maybe smaller grey text next to theme name in
+ // dropdown?)
+ QString themeFolder = QDir("./themes/").absoluteFilePath("");
+ themeDebugLog() << "Theme Folder Path: " << themeFolder;
+
+ QDirIterator directoryIterator(themeFolder, QDir::Dirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
+ while (directoryIterator.hasNext()) {
+ QDir dir(directoryIterator.next());
+ QFileInfo themeJson(dir.absoluteFilePath("theme.json"));
+ if (themeJson.exists()) {
+ // Load "theme.json" based themes
+ themeDebugLog() << "Loading JSON Theme from:" << themeJson.absoluteFilePath();
+ AddTheme(std::make_unique<CustomTheme>(GetTheme(darkThemeId), themeJson, true));
+ } else {
+ // Load pure QSS Themes
+ QDirIterator stylesheetFileIterator(dir.absoluteFilePath(""), { "*.qss", "*.css" }, QDir::Files);
+ while (stylesheetFileIterator.hasNext()) {
+ QFile customThemeFile(stylesheetFileIterator.next());
+ QFileInfo customThemeFileInfo(customThemeFile);
+ themeDebugLog() << "Loading QSS Theme from:" << customThemeFileInfo.absoluteFilePath();
+ AddTheme(std::make_unique<CustomTheme>(GetTheme(darkThemeId), customThemeFileInfo, false));
+ }
+ }
+ }
+
+ themeDebugLog() << "<> Widget themes initialized.";
+ }
+}
+
+QList<ITheme*> ThemeManager::getValidApplicationThemes()
+{
+ QList<ITheme*> ret;
+ ret.reserve(m_themes.size());
+ for (auto&& [id, theme] : m_themes) {
+ ret.append(theme.get());
+ }
+ return ret;
+}
+
+void ThemeManager::setIconTheme(const QString& name)
+{
+ QIcon::setThemeName(name);
+}
+
+void ThemeManager::applyCurrentlySelectedTheme()
+{
+ setIconTheme(APPLICATION->settings()->get("IconTheme").toString());
+ themeDebugLog() << "<> Icon theme set.";
+ setApplicationTheme(APPLICATION->settings()->get("ApplicationTheme").toString(), true);
+ themeDebugLog() << "<> Application theme set.";
+}
+
+void ThemeManager::setApplicationTheme(const QString& name, bool initial)
+{
+ auto systemPalette = qApp->palette();
+ auto themeIter = m_themes.find(name);
+ if (themeIter != m_themes.end()) {
+ auto& theme = themeIter->second;
+ themeDebugLog() << "applying theme" << theme->name();
+ theme->apply(initial);
+#ifdef Q_OS_WIN
+ if (m_mainWindow && IsWindows10OrGreater()) {
+ if (QString::compare(theme->id(), "dark") == 0) {
+ WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), true);
+ } else {
+ WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), false);
+ }
+ }
+#endif
+ } else {
+ themeWarningLog() << "Tried to set invalid theme:" << name;
+ }
+}
diff --git a/launcher/ui/themes/ThemeManager.h b/launcher/ui/themes/ThemeManager.h new file mode 100644 index 00000000..b85cb742 --- /dev/null +++ b/launcher/ui/themes/ThemeManager.h @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 Tayou <tayou@gmx.net>
+ *
+ * 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/>.
+ */
+#pragma once
+
+#include <QString>
+
+#include "ui/MainWindow.h"
+#include "ui/themes/ITheme.h"
+
+inline auto themeDebugLog()
+{
+ return qDebug() << "[Theme]";
+}
+inline auto themeWarningLog()
+{
+ return qWarning() << "[Theme]";
+}
+
+class ThemeManager {
+ public:
+ ThemeManager(MainWindow* mainWindow);
+
+ // maybe make private? Or put in ctor?
+ void InitializeThemes();
+
+ QList<ITheme*> getValidApplicationThemes();
+ void setIconTheme(const QString& name);
+ void applyCurrentlySelectedTheme();
+ void setApplicationTheme(const QString& name, bool initial);
+
+ private:
+ std::map<QString, std::unique_ptr<ITheme>> m_themes;
+ MainWindow* m_mainWindow;
+
+ QString AddTheme(std::unique_ptr<ITheme> theme);
+ ITheme* GetTheme(QString themeId);
+};
diff --git a/launcher/ui/widgets/JavaSettingsWidget.cpp b/launcher/ui/widgets/JavaSettingsWidget.cpp index 314a126e..c7c4dbbd 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.cpp +++ b/launcher/ui/widgets/JavaSettingsWidget.cpp @@ -245,7 +245,7 @@ void JavaSettingsWidget::memoryValueChanged(int) } } -void JavaSettingsWidget::javaVersionSelected(BaseVersionPtr version) +void JavaSettingsWidget::javaVersionSelected(BaseVersion::Ptr version) { auto java = std::dynamic_pointer_cast<JavaInstall>(version); if(!java) diff --git a/launcher/ui/widgets/JavaSettingsWidget.h b/launcher/ui/widgets/JavaSettingsWidget.h index 0d280daf..5344e2cd 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.h +++ b/launcher/ui/widgets/JavaSettingsWidget.h @@ -60,7 +60,7 @@ public: protected slots: void memoryValueChanged(int); void javaPathEdited(const QString &path); - void javaVersionSelected(BaseVersionPtr version); + void javaVersionSelected(BaseVersion::Ptr version); void on_javaBrowseBtn_clicked(); void on_javaStatusBtn_clicked(); void checkFinished(JavaCheckResult result); diff --git a/launcher/ui/widgets/ModFilterWidget.h b/launcher/ui/widgets/ModFilterWidget.h index 958a1e2b..706ffd21 100644 --- a/launcher/ui/widgets/ModFilterWidget.h +++ b/launcher/ui/widgets/ModFilterWidget.h @@ -49,7 +49,7 @@ public: auto getFilter() -> std::shared_ptr<Filter>; auto changed() const -> bool { return m_last_version_id != m_version_id; } - Meta::VersionListPtr versionList() { return m_version_list; } + Meta::VersionList::Ptr versionList() { return m_version_list; } private: ModFilterWidget(Version def, QWidget* parent = nullptr); @@ -73,7 +73,7 @@ private: /* Version stuff */ QButtonGroup m_mcVersion_buttons; - Meta::VersionListPtr m_version_list; + Meta::VersionList::Ptr m_version_list; /* Used to tell if the filter was changed since the last getFilter() call */ VersionButtonID m_last_version_id = VersionButtonID::Strict; diff --git a/launcher/ui/widgets/VersionSelectWidget.cpp b/launcher/ui/widgets/VersionSelectWidget.cpp index cc4fc6a2..404860d9 100644 --- a/launcher/ui/widgets/VersionSelectWidget.cpp +++ b/launcher/ui/widgets/VersionSelectWidget.cpp @@ -142,7 +142,7 @@ void VersionSelectWidget::changeProgress(qint64 current, qint64 total) void VersionSelectWidget::currentRowChanged(const QModelIndex& current, const QModelIndex&) { auto variant = m_proxyModel->data(current, BaseVersionList::VersionPointerRole); - emit selectedVersionChanged(variant.value<BaseVersionPtr>()); + emit selectedVersionChanged(variant.value<BaseVersion::Ptr>()); } void VersionSelectWidget::preselect() @@ -186,11 +186,11 @@ bool VersionSelectWidget::hasVersions() const return m_proxyModel->rowCount(QModelIndex()) != 0; } -BaseVersionPtr VersionSelectWidget::selectedVersion() const +BaseVersion::Ptr VersionSelectWidget::selectedVersion() const { auto currentIndex = listView->selectionModel()->currentIndex(); auto variant = m_proxyModel->data(currentIndex, BaseVersionList::VersionPointerRole); - return variant.value<BaseVersionPtr>(); + return variant.value<BaseVersion::Ptr>(); } void VersionSelectWidget::setExactFilter(BaseVersionList::ModelRoles role, QString filter) diff --git a/launcher/ui/widgets/VersionSelectWidget.h b/launcher/ui/widgets/VersionSelectWidget.h index f56daa8a..e75efc6f 100644 --- a/launcher/ui/widgets/VersionSelectWidget.h +++ b/launcher/ui/widgets/VersionSelectWidget.h @@ -40,7 +40,7 @@ public: void loadList(); bool hasVersions() const; - BaseVersionPtr selectedVersion() const; + BaseVersion::Ptr selectedVersion() const; void selectRecommended(); void selectCurrent(); @@ -54,7 +54,7 @@ public: void setResizeOn(int column); signals: - void selectedVersionChanged(BaseVersionPtr version); + void selectedVersionChanged(BaseVersion::Ptr version); protected: virtual void closeEvent ( QCloseEvent* ); diff --git a/libraries/extra-cmake-modules b/libraries/extra-cmake-modules new file mode 160000 +Subproject bbcbaff78283270c2beee69afd8d5b91da854af diff --git a/libraries/hoedown/src/autolink.c b/libraries/hoedown/src/autolink.c index 3063b1a0..3592b8e3 100644 --- a/libraries/hoedown/src/autolink.c +++ b/libraries/hoedown/src/autolink.c @@ -150,7 +150,7 @@ hoedown_autolink__www( uint8_t *data, size_t max_rewind, size_t size, - unsigned int flags) + hoedown_autolink_flags flags) { size_t link_end; @@ -186,7 +186,7 @@ hoedown_autolink__email( uint8_t *data, size_t max_rewind, size_t size, - unsigned int flags) + hoedown_autolink_flags flags) { size_t link_end, rewind; int nb = 0, np = 0; @@ -242,7 +242,7 @@ hoedown_autolink__url( uint8_t *data, size_t max_rewind, size_t size, - unsigned int flags) + hoedown_autolink_flags flags) { size_t link_end, rewind = 0, domain_len; diff --git a/libraries/tomlplusplus b/libraries/tomlplusplus -Subproject 4b166b69f28e70a416a1a04a98f365d2aeb90de +Subproject 0a90913abf9390b9e08ab6d3b40ac11634553f3 diff --git a/libraries/zlib b/libraries/zlib new file mode 160000 +Subproject 04f42ceca40f73e2978b50e93806c2a18c1281f diff --git a/program_info/CMakeLists.txt b/program_info/CMakeLists.txt index f6e2ea84..8c682711 100644 --- a/program_info/CMakeLists.txt +++ b/program_info/CMakeLists.txt @@ -15,14 +15,17 @@ set(Launcher_Name "${Launcher_CommonName}" PARENT_SCOPE) set(Launcher_DisplayName "${Launcher_DisplayName}" PARENT_SCOPE) set(Launcher_Copyright "Prism Launcher Contributors\\n© 2021-2022 PolyMC Contributors \\n© 2012-2021 MultiMC Contributors") +set(Launcher_Copyright_Mac "Prism Launcher Contributors, © 2021-2022 PolyMC Contributors and © 2012-2021 MultiMC Contributors" PARENT_SCOPE) set(Launcher_Copyright "${Launcher_Copyright}" PARENT_SCOPE) set(Launcher_Domain "prismlauncher.org" PARENT_SCOPE) set(Launcher_UserAgent "${Launcher_CommonName}/${Launcher_VERSION_NAME}" PARENT_SCOPE) set(Launcher_ConfigFile "prismlauncher.cfg" PARENT_SCOPE) set(Launcher_Git "https://github.com/PrismLauncher/PrismLauncher" PARENT_SCOPE) set(Launcher_DesktopFileName "org.prismlauncher.PrismLauncher.desktop" PARENT_SCOPE) +set(Launcher_SVGFileName "org.prismlauncher.PrismLauncher.svg" PARENT_SCOPE) set(Launcher_Desktop "program_info/org.prismlauncher.PrismLauncher.desktop" PARENT_SCOPE) +set(Launcher_mrpack_MIMEInfo "program_info/modrinth-mrpack-mime.xml" PARENT_SCOPE) set(Launcher_MetaInfo "program_info/org.prismlauncher.PrismLauncher.metainfo.xml" PARENT_SCOPE) set(Launcher_SVG "program_info/org.prismlauncher.PrismLauncher.svg" PARENT_SCOPE) set(Launcher_Branding_ICNS "program_info/prismlauncher.icns" PARENT_SCOPE) diff --git a/program_info/genicons.sh b/program_info/genicons.sh index bfe756d8..42592c4e 100755 --- a/program_info/genicons.sh +++ b/program_info/genicons.sh @@ -1,39 +1,73 @@ -#/bin/bash +#!/bin/bash -# ICO +svg2png() { + input_file="$1" + output_file="$2" + width="$3" + height="$4" -inkscape -w 16 -h 16 -o prismlauncher_16.png org.prismlauncher.PrismLauncher.svg -inkscape -w 24 -h 24 -o prismlauncher_24.png org.prismlauncher.PrismLauncher.svg -inkscape -w 32 -h 32 -o prismlauncher_32.png org.prismlauncher.PrismLauncher.svg -inkscape -w 48 -h 48 -o prismlauncher_48.png org.prismlauncher.PrismLauncher.svg -inkscape -w 64 -h 64 -o prismlauncher_64.png org.prismlauncher.PrismLauncher.svg -inkscape -w 128 -h 128 -o prismlauncher_128.png org.prismlauncher.PrismLauncher.svg + inkscape -w "$width" -h "$height" -o "$output_file" "$input_file" +} -convert prismlauncher_128.png prismlauncher_64.png prismlauncher_48.png prismlauncher_32.png prismlauncher_24.png prismlauncher_16.png prismlauncher.ico +sipsresize() { + input_file="$1" + output_file="$2" + width="$3" + height="$4" -rm -f prismlauncher_*.png + sips -z "$width" "$height" "$input_file" --out "$output_file" +} -inkscape -w 1024 -h 1024 -o prismlauncher_1024.png org.prismlauncher.PrismLauncher.bigsur.svg +if command -v "inkscape" && command -v "icotool"; then + # Windows ICO + d=$(mktemp -d) -mkdir prismlauncher.iconset + svg2png org.prismlauncher.PrismLauncher.svg "$d/prismlauncher_16.png" 16 16 + svg2png org.prismlauncher.PrismLauncher.svg "$d/prismlauncher_24.png" 24 24 + svg2png org.prismlauncher.PrismLauncher.svg "$d/prismlauncher_32.png" 32 32 + svg2png org.prismlauncher.PrismLauncher.svg "$d/prismlauncher_48.png" 48 48 + svg2png org.prismlauncher.PrismLauncher.svg "$d/prismlauncher_64.png" 64 64 + svg2png org.prismlauncher.PrismLauncher.svg "$d/prismlauncher_128.png" 128 128 + svg2png org.prismlauncher.PrismLauncher.svg "$d/prismlauncher_256.png" 256 256 -sips -z 16 16 prismlauncher_1024.png --out prismlauncher.iconset/icon_16x16.png -sips -z 32 32 prismlauncher_1024.png --out prismlauncher.iconset/icon_16x16@2x.png -sips -z 32 32 prismlauncher_1024.png --out prismlauncher.iconset/icon_32x32.png -sips -z 64 64 prismlauncher_1024.png --out prismlauncher.iconset/icon_32x32@2x.png -sips -z 128 128 prismlauncher_1024.png --out prismlauncher.iconset/icon_128x128.png -sips -z 256 256 prismlauncher_1024.png --out prismlauncher.iconset/icon_128x128@2x.png -sips -z 256 256 prismlauncher_1024.png --out prismlauncher.iconset/icon_256x256.png -sips -z 512 512 prismlauncher_1024.png --out prismlauncher.iconset/icon_256x256@2x.png -sips -z 512 512 prismlauncher_1024.png --out prismlauncher.iconset/icon_512x512.png -cp prismlauncher_1024.png prismlauncher.iconset/icon_512x512@2x.png + rm prismlauncher.ico && icotool -o prismlauncher.ico -c \ + "$d/prismlauncher_256.png" \ + "$d/prismlauncher_128.png" \ + "$d/prismlauncher_64.png" \ + "$d/prismlauncher_48.png" \ + "$d/prismlauncher_32.png" \ + "$d/prismlauncher_24.png" \ + "$d/prismlauncher_16.png" +else + echo "ERROR: Windows icons were NOT generated!" >&2 + echo "ERROR: requires inkscape and icotool in PATH" +fi -iconutil -c icns prismlauncher.iconset +if command -v "inkscape" && command -v "sips" && command -v "iconutil"; then + # macOS ICNS + d=$(mktemp -d) -rm -f prismlauncher_*.png -rm -rf prismlauncher.iconset + d="$d/prismlauncher.iconset" + mkdir -p "$d" + + svg2png org.prismlauncher.PrismLauncher.bigsur.svg "$d/icon_512x512@2x.png" 1024 1024 + sipsresize "$d/icon_512x512@2.png" "$d/icon_16x16.png" 16 16 + sipsresize "$d/icon_512x512@2.png" "$d/icon_16x16@2.png" 32 32 + sipsresize "$d/icon_512x512@2.png" "$d/icon_32x32.png" 32 32 + sipsresize "$d/icon_512x512@2.png" "$d/icon_32x32@2.png" 64 64 + sipsresize "$d/icon_512x512@2.png" "$d/icon_128x128.png" 128 128 + sipsresize "$d/icon_512x512@2.png" "$d/icon_128x128@2.png" 256 256 + sipsresize "$d/icon_512x512@2.png" "$d/icon_256x256.png" 256 256 + sipsresize "$d/icon_512x512@2.png" "$d/icon_256x256@2.png" 512 512 + iconutil -c icns "$d" +else + echo "ERROR: macOS icons were NOT generated!" >&2 + echo "ERROR: requires inkscape, sips and iconutil in PATH" +fi + +# replace icon in themes for dir in ../launcher/resources/*/scalable do - cp -v org.prismlauncher.PrismLauncher.svg $dir/launcher.svg + cp -v org.prismlauncher.PrismLauncher.svg "$dir/launcher.svg" done diff --git a/program_info/modrinth-mrpack-mime.xml b/program_info/modrinth-mrpack-mime.xml new file mode 100644 index 00000000..5001e5e7 --- /dev/null +++ b/program_info/modrinth-mrpack-mime.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?>
+<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
+ <mime-type type="application/x-modrinth-modpack+zip">
+ <comment>Modrinth Modpack File</comment>
+ <icon name="application-x-modrinth-modpack"/>
+ <glob-deleteall/>
+ <glob pattern="*.mrpack"/>
+ </mime-type>
+</mime-info>
diff --git a/program_info/org.prismlauncher.PrismLauncher.desktop.in b/program_info/org.prismlauncher.PrismLauncher.desktop.in index e608f588..f08f2ba4 100644 --- a/program_info/org.prismlauncher.PrismLauncher.desktop.in +++ b/program_info/org.prismlauncher.PrismLauncher.desktop.in @@ -10,3 +10,4 @@ Icon=org.prismlauncher.PrismLauncher Categories=Game;ActionGame;AdventureGame;Simulation; Keywords=game;minecraft;launcher;mc;multimc;polymc; StartupWMClass=PrismLauncher +MimeType=application/zip;application/x-modrinth-modpack+zip diff --git a/program_info/prismlauncher.6.scd b/program_info/prismlauncher.6.scd index e3c7de86..f979e457 100644 --- a/program_info/prismlauncher.6.scd +++ b/program_info/prismlauncher.6.scd @@ -26,6 +26,9 @@ Here are the current features of Prism Launcher. *-l, --launch*=INSTANCE_ID Launch the instance specified by INSTANCE_ID. +*--show*=INSTANCE_ID + Show the configuration window of the instance specified by INSTANCE_ID. + *--alive* Write a small 'live.check' file after Prism Launcher starts. diff --git a/program_info/prismlauncher.ico b/program_info/prismlauncher.ico Binary files differindex e4529f93..2f0fa67f 100644 --- a/program_info/prismlauncher.ico +++ b/program_info/prismlauncher.ico diff --git a/program_info/prismlauncher.manifest.in b/program_info/prismlauncher.manifest.in index 1d764445..6f4467c7 100644 --- a/program_info/prismlauncher.manifest.in +++ b/program_info/prismlauncher.manifest.in @@ -10,7 +10,7 @@ </trustInfo> <dependency> <dependentAssembly> - <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="x86" publicKeyToken="6595b64144ccf1df" language="*"/> + <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/> </dependentAssembly> </dependency> <description>Custom Minecraft launcher for managing multiple installs.</description> diff --git a/program_info/win_install.nsi.in b/program_info/win_install.nsi.in index 0cd7ea11..42a9e762 100644 --- a/program_info/win_install.nsi.in +++ b/program_info/win_install.nsi.in @@ -110,6 +110,142 @@ VIAddVersionKey /LANG=${LANG_ENGLISH} "LegalCopyright" "@Launcher_Copyright@" VIAddVersionKey /LANG=${LANG_ENGLISH} "FileVersion" "@Launcher_VERSION_NAME4@" VIAddVersionKey /LANG=${LANG_ENGLISH} "ProductVersion" "@Launcher_VERSION_NAME4@" + +;-------------------------------- +; Shell Associate Macros + +!macro APP_SETUP DESCRIPTION ICON APP_ID APP_NAME APP_EXE COMMANDTEXT COMMAND ; VERB APP_COMPANY + ; setup APP_ID + WriteRegStr ShCtx "Software\Classes\${APP_ID}" "" `${DESCRIPTION}` + WriteRegStr ShCtx "Software\Classes\${APP_ID}\DefaultIcon" "" `${ICON}` + ; default open verb + WriteRegStr ShCtx "Software\Classes\${APP_ID}\shell" "" "open" + WriteRegStr ShCtx "Software\Classes\${APP_ID}\shell\open" "" `${COMMANDTEXT}` + WriteRegStr ShCtx "Software\Classes\${APP_ID}\shell\open\command" "" `${COMMAND}` + + ; if you want the app to use it's own implementation of a verb + ;WriteRegStr ShCtx "Software\Classes\${APP_ID}\shell\${VERB}" "" "${DESCRIPTION}" + ;WriteRegStr ShCtx "Software\Classes\${APP_ID}\shell\${VERB}\command" "" `${COMMAND}` + + WriteRegStr ShCtx "Software\Classes\Applications\${APP_EXE}\shell\open\command" "" `${COMMAND}` + WriteRegStr ShCtx "Software\Classes\Applications\${APP_EXE}" "FriendlyAppName" `${APP_NAME}` ; [Optional] + ;WriteRegStr ShCtx "Software\Classes\Applications\${APP_EXE}" "ApplicationCompany" `${APP_COMPANY}` ; [Optional] + ;WriteRegNone ShCtx "Software\Classes\Applications\${APP_EXE}\SupportedTypes" ".${EXT}" ; [Optional] Only allow "Open With" with specific extension(s) on WinXP+ + + # Register "Default Programs" [Optional] + !ifdef REGISTER_DEFAULTPROGRAMS + WriteRegStr ShCtx "Software\Classes\Applications\${APP_EXE}\Capabilities" "ApplicationDescription" `${DESCRIPTION}` + WriteRegStr ShCtx "Software\RegisteredApplications" `${APP_NAME}` "Software\Classes\Applications\${APP_EXE}\Capabilities" + !endif + +!macroend + +!macro APP_ASSOCIATE EXT APP_ID APP_EXE OVERWIRTE + ; Backup the previously associated file class + ${If} ${OVERWIRTE} == true + ReadRegStr $R0 ShCtx "Software\Classes\${EXT}" "" + WriteRegStr ShCtx "Software\Classes\${EXT}" "${APP_ID}_backup" "$R0" + WriteRegStr ShCtx "Software\Classes\${EXT}" "" "${APP_ID}" + ${EndIf} + + WriteRegNone ShCtx "Software\Classes\${EXT}\OpenWithList" "${APP_EXE}" ; Win2000+ + WriteRegNone ShCtx "Software\Classes\${EXT}\OpenWithProgids" "${APP_ID}" ; WinXP+ + + # Register "Default Programs" [Optional] + !ifdef REGISTER_DEFAULTPROGRAMS + WriteRegStr ShCtx "Software\Classes\Applications\${APP_EXE}\Capabilities\FileAssociations" "${EXT}" "${APP_ID}" + !endif + +!macroend + +!macro APP_UNASSOCIATE EXT APP_ID + + # Unregister file type + ClearErrors + ; restore backup + ReadRegStr $R1 ShCtx "Software\Classes\${EXT}" "" + ${If} $R1 == "${APP_ID}" + ReadRegStr $R0 ShCtx "Software\Classes\${EXT}" `${APP_ID}_backup` + WriteRegStr ShCtx "Software\Classes\${EXT}" "" "$R0" + ${Else} + ReadRegStr $R0 ShCtx "Software\Classes\${EXT}" "" + ${EndIf} + + DeleteRegKey /IfEmpty ShCtx "Software\Classes\${APP_ID}" + ${IfNot} ${Errors} + ${AndIf} $R0 == "${APP_ID}" + DeleteRegValue ShCtx "Software\Classes\${EXT}" "" + DeleteRegKey /IfEmpty ShCtx "Software\Classes\${EXT}" + ${EndIf} + + DeleteRegValue ShCtx "Software\Classes\${EXT}\OpenWithList" "${APP_EXE}" + DeleteRegKey /IfEmpty ShCtx "Software\Classes\${EXT}\OpenWithList" + DeleteRegValue ShCtx "Software\Classes\${EXT}\OpenWithProgids" "${APP_ID}" + DeleteRegKey /IfEmpty ShCtx "Software\Classes\${EXT}\OpenWithProgids" + DeleteRegKey /IfEmpty ShCtx "Software\Classes\${EXT}" + + # Attempt to clean up junk left behind by the Windows shell + DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\ApplicationAssociationToasts" "${APP_ID}_${EXT}" + DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\ApplicationAssociationToasts" "Applications\${APP_EXE}_${EXT}" + DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\${EXT}\OpenWithProgids" "${APP_ID}" + DeleteRegKey /IfEmpty HKCU "Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\${EXT}\OpenWithProgids" + DeleteRegKey /IfEmpty HKCU "Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\${EXT}\OpenWithList" + DeleteRegKey /IfEmpty HKCU "Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\${EXT}" + ;DeleteRegKey HKCU "Software\Microsoft\Windows\Roaming\OpenWith\FileExts\.${EXT}" + ;DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Explorer\RecentDocs\.${EXT}" + +!macroend + +!macro APP_TEARDOWN APP_ID APP_NAME APP_EXE + + # Unregister file type + ClearErrors + DeleteRegKey /IfEmpty ShCtx "Software\Classes\${APP_ID}\shell" + ${IfNot} ${Errors} + DeleteRegKey ShCtx "Software\Classes\${APP_ID}\DefaultIcon" + ${EndIf} + + # Unregister "Open With" + DeleteRegKey ShCtx "Software\Classes\Applications\${APP_EXE}" + + # Unregister "Default Programs" + !ifdef REGISTER_DEFAULTPROGRAMS + DeleteRegValue ShCtx "Software\RegisteredApplications" `${APP_NAME}` + DeleteRegKey ShCtx "Software\Classes\Applications\${APP_EXE}\Capabilities" + DeleteRegKey /IfEmpty ShCtx "Software\Classes\Applications\${APP_EXE}" + !endif + + DeleteRegKey ShCtx `Software\Classes\${APP_ID}` + DeleteRegKey ShCtx "Software\Classes\Applications\${APP_EXE}" + + # Attempt to clean up junk left behind by the Windows shell + DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Search\JumplistData" "$INSTDIR\${APP_EXE}" + DeleteRegValue HKCU "Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\MuiCache" "$INSTDIR\${APP_EXE}.FriendlyAppName" + DeleteRegValue HKCU "Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\MuiCache" "$INSTDIR\${APP_EXE}.ApplicationCompany" + DeleteRegValue HKCU "Software\Microsoft\Windows\ShellNoRoam\MUICache" "$INSTDIR\${APP_EXE}" ; WinXP + DeleteRegValue HKCU "Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Compatibility Assistant\Store" "$INSTDIR\${APP_EXE}" + +!macroend + +; !defines for use with SHChangeNotify +!ifdef SHCNE_ASSOCCHANGED +!undef SHCNE_ASSOCCHANGED +!endif +!define SHCNE_ASSOCCHANGED 0x08000000 +!ifdef SHCNF_FLUSH +!undef SHCNF_FLUSH +!endif +!define SHCNF_FLUSH 0x1000 + + +# ensure this is called at the end of any section that changes shell keys +!macro NotifyShell_AssocChanged +; Using the system.dll plugin to call the SHChangeNotify Win32 API function so we +; can update the shell. + System::Call "shell32::SHChangeNotify(i,i,i,i) (${SHCNE_ASSOCCHANGED}, ${SHCNF_FLUSH}, 0, 0)" +!macroend + + ;-------------------------------- ; The stuff to install @@ -171,6 +307,27 @@ Section /o "Desktop Shortcut" DESKTOP_SHORTCUTS SectionEnd + +!define APP_ID "@Launcher_CommonName@.App" +!define APP_EXE "@Launcher_APP_BINARY_NAME@.exe" +!define APP_ICON "$INSTDIR\${APP_EXE},0" +!define APP_DESCRIPTION "@Launcher_DisplayName@" +!define APP_NAME "@Launcher_DisplayName@" +!define APP_CMD_TEXT "Minecraft Modpack" + +!define REGISTER_DEFAULTPROGRAMS ; value doesn't matter + +Section -ShellAssoc + + !insertmacro APP_SETUP `${APP_DESCRIPTION}` `${APP_ICON}` `${APP_ID}` `${APP_CMD_TEXT}` `${APP_EXE}` `${APP_CMD_TEXT}` '$INSTDIR\${APP_EXE} -I "%1"' + + !insertmacro APP_ASSOCIATE ".zip" `${APP_ID}` `${APP_EXE}` false + !insertmacro APP_ASSOCIATE ".mrpack" `${APP_ID}` `${APP_EXE}` true + + !insertmacro NotifyShell_AssocChanged +SectionEnd + + ;-------------------------------- ; Uninstaller @@ -202,6 +359,16 @@ Section "Uninstall" SectionEnd +Section -un.ShellAssoc + + !insertmacro APP_TEARDOWN `${APP_ID}` `${APP_NAME}` `${APP_EXE}` + + !insertmacro APP_UNASSOCIATE ".zip" `${APP_ID}` + !insertmacro APP_UNASSOCIATE ".mrpack" `${APP_ID}` + + !insertmacro NotifyShell_AssocChanged +SectionEnd + ;-------------------------------- ; Extra command line parameters diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml new file mode 100644 index 00000000..8e647eea --- /dev/null +++ b/snap/snapcraft.yaml @@ -0,0 +1,55 @@ +name: prismlauncher +license: GPL-3.0-only +base: core20 +website: https://prismlauncher.org/ +source-code: https://github.com/PrismLauncher/PrismLauncher +issues: https://github.com/PrismLauncher/PrismLauncher/issues +donation: https://opencollective.com/prismlauncher +contact: https://discord.gg/prismlauncher +summary: A custom Minecraft launcher with modpack support +adopt-info: prismlauncher + +grade: devel +confinement: strict + +architectures: + - build-on: amd64 + - build-on: arm64 + +parts: + prismlauncher: + parse-info: + - usr/share/metainfo/org.prismlauncher.PrismLauncher.metainfo.xml + plugin: cmake + build-packages: + - default-jdk-headless + stage-packages: + - openjdk-17-jre + - openjdk-8-jre + source: . + override-pull: | + snapcraftctl pull + # Fix the icon reference in the desktop file + sed -i.bak -e 's|Icon=org.prismlauncher.PrismLauncher|Icon=/usr/share/icons/hicolor/scalable/apps/org.prismlauncher.PrismLauncher.svg|g' program_info/org.prismlauncher.PrismLauncher.desktop.in + # Remove the build directory so that local development doesn't interfere with Snap compilation + rm -rf build + cmake-generator: Ninja + cmake-parameters: + - "-DCMAKE_INSTALL_PREFIX=/usr" + - "-DCMAKE_BUILD_TYPE=RelWithDebInfo" + - "-DENABLE_LTO=ON" + - "-DLauncher_BUILD_PLATFORM=snap" + +apps: + prismlauncher: + common-id: org.prismlauncher.PrismLauncher + desktop: usr/share/applications/org.prismlauncher.PrismLauncher.desktop + command: usr/bin/prismlauncher + extensions: + - kde-neon + plugs: + - home + - opengl + - network + - network-bind + - audio-playback diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp index 47a963b0..21270f6f 100644 --- a/tests/FileSystem_test.cpp +++ b/tests/FileSystem_test.cpp @@ -183,6 +183,32 @@ slots: f(); } + void test_copy_single_file() + { + QTemporaryDir tempDir; + tempDir.setAutoRemove(true); + + { + QString file = QFINDTESTDATA("testdata/FileSystem/test_folder/pack.mcmeta"); + + qDebug() << "From:" << file << "To:" << tempDir.path(); + + QDir target_dir(FS::PathCombine(tempDir.path(), "pack.mcmeta")); + qDebug() << tempDir.path(); + qDebug() << target_dir.path(); + FS::copy c(file, target_dir.filePath("pack.mcmeta")); + c(); + + auto filter = QDir::Filter::Files; + + for (auto entry: target_dir.entryList(filter)) { + qDebug() << entry; + } + + QVERIFY(target_dir.entryList(filter).contains("pack.mcmeta")); + } + } + void test_getDesktop() { QCOMPARE(FS::getDesktopDir(), QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)); diff --git a/tests/GZip_test.cpp b/tests/GZip_test.cpp index 1e762b2e..82503d81 100644 --- a/tests/GZip_test.cpp +++ b/tests/GZip_test.cpp @@ -24,7 +24,7 @@ slots: QByteArray compressed; QByteArray decompressed; std::default_random_engine eng((std::random_device())()); - std::uniform_int_distribution<uint8_t> idis(0, std::numeric_limits<uint8_t>::max()); + std::uniform_int_distribution<uint16_t> idis(0, std::numeric_limits<uint8_t>::max()); // initialize random buffer for(int i = 0; i < size; i++) |