forked from Github/PrismLauncher
Compare commits
384 commits
release-9.
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6799b772de | ||
|
|
3b87452699 | ||
|
|
0af021fef2 | ||
|
|
d24066c965 | ||
|
|
46385e671d | ||
|
|
73bb740bbb | ||
|
|
50ab1b2c40 | ||
|
|
d472b966dc | ||
|
|
beee93e199 | ||
|
|
8f1750df51 | ||
|
|
bdf87fa02f | ||
|
|
3295b0717a | ||
|
|
7b97863685 | ||
|
|
3e4fc2ff3b | ||
|
|
d22a20ffa6 | ||
|
|
de86e63611 | ||
|
|
e41a60e57e | ||
|
|
174cddcf42 | ||
|
|
9e954548dd | ||
|
|
14b653e08d | ||
|
|
79180db663 | ||
|
|
22b59e760c | ||
|
|
1d1a4f1b30 | ||
|
|
bf1084d7fa | ||
|
|
ef825a7bc5 | ||
|
|
8d143c7b95 | ||
|
|
9ea33e0651 | ||
|
|
f1209c1279 | ||
|
|
17e95c0d07 | ||
|
|
cb83f8e52d | ||
|
|
7d202e348d | ||
|
|
364b5be856 | ||
|
|
e1896d5b6b | ||
|
|
6e3dd04a1f | ||
|
|
f7a401ab22 | ||
|
|
9ed6801b15 | ||
|
|
06b50dfd33 | ||
|
|
5a76d9b7e1 | ||
|
|
34438824c1 | ||
|
|
ac598740b3 | ||
|
|
85c2710e33 | ||
|
|
afe6d4836d | ||
|
|
9c26ba5932 | ||
|
|
2a987b5a4a | ||
|
|
1a375dc243 | ||
|
|
ab3792b080 | ||
|
|
f9dcc31843 | ||
|
|
989e6c6cb6 | ||
|
|
138684a84f | ||
|
|
faf7e6b06e | ||
|
|
f1671e3ded | ||
|
|
6404d81ada | ||
|
|
d6eb96df5c | ||
|
|
c5efe081b4 | ||
|
|
a639bc019e | ||
|
|
588e0d6305 | ||
|
|
d7eddedf41 | ||
|
|
6ae9fa4df4 | ||
|
|
86cc6d3c5e | ||
|
|
61d7f08883 | ||
|
|
0deb5e20ae | ||
|
|
fcb3dd5e9d | ||
|
|
169f5728b1 | ||
|
|
8816be1668 | ||
|
|
bd752c025c | ||
|
|
49d734f314 | ||
|
|
417688089d | ||
|
|
de21b77ae1 | ||
|
|
68bf409a98 | ||
|
|
5d179671bf | ||
|
|
dc6f536843 | ||
|
|
cc504f4a6c | ||
|
|
a92c68adbd | ||
|
|
7aaa9fa93c | ||
|
|
81769b9047 | ||
|
|
147b315c14 | ||
|
|
9d0918a064 | ||
|
|
98ab686348 | ||
|
|
39ae2fbd4a | ||
|
|
36a35d7925 | ||
|
|
1b4b36df33 | ||
|
|
3d9e073721 | ||
|
|
fdb4159f2a | ||
|
|
98be91c9d5 | ||
|
|
705375c0df | ||
|
|
8d3684c484 | ||
|
|
0d4eeb9965 | ||
|
|
caccaefb39 | ||
|
|
532fec7748 | ||
|
|
4ba3fd559d | ||
|
|
2a403b3bfe | ||
|
|
f8a504e254 | ||
|
|
e66f447ce5 | ||
|
|
67b218b1f3 | ||
|
|
db766574a4 | ||
|
|
18da01c9a6 | ||
|
|
c79ad73311 | ||
|
|
fb3926a85d | ||
|
|
8d4f279539 | ||
|
|
1b5d3c2bf9 | ||
|
|
be3eca8c97 | ||
|
|
66f0397087 | ||
|
|
8e8538b506 | ||
|
|
dc00c47f2e | ||
|
|
7d3238d234 | ||
|
|
f17599c8b8 | ||
|
|
8e5a7c6e33 | ||
|
|
1d8bf1d5a7 | ||
|
|
c2f4389e5f | ||
|
|
b7d117cc77 | ||
|
|
1cce582d56 | ||
|
|
867aaf881e | ||
|
|
14b348ffe7 | ||
|
|
181610a8c1 | ||
|
|
b7f2175718 | ||
|
|
7a1dc0e9c9 | ||
|
|
9317a675ed | ||
|
|
7cb8deeb90 | ||
|
|
bca517b8d3 | ||
|
|
e4ad4051c8 | ||
|
|
3ee73916ca | ||
|
|
03834e1455 | ||
|
|
54da41c625 | ||
|
|
f4f1d5f0d7 | ||
|
|
fbd886e9db | ||
|
|
0fb99d837f | ||
|
|
cb8320ca41 | ||
|
|
33981b9870 | ||
|
|
07a0a15457 | ||
|
|
0f9ea5c949 | ||
|
|
d40c8b704c | ||
|
|
82a500f4e8 | ||
|
|
6ebfcb91cf | ||
|
|
e01df73deb | ||
|
|
7dde35fef2 | ||
|
|
0b6ce5525d | ||
|
|
f17faadf8a | ||
|
|
c7b6977692 | ||
|
|
423d72a6e8 | ||
|
|
2a2ed9f8bd | ||
|
|
0412ed3f78 | ||
|
|
f4d93a08ba | ||
|
|
8d53242952 | ||
|
|
706e263050 | ||
|
|
507faf9f00 | ||
|
|
c0803365ef | ||
|
|
02e527c15b | ||
|
|
5659dad23d | ||
|
|
647bb04a0d | ||
|
|
b6cd46ad27 | ||
|
|
c3712ba648 | ||
|
|
c3e44554ab | ||
|
|
a1c1c0b769 | ||
|
|
15e58589d5 | ||
|
|
eb8c375ec5 | ||
|
|
1dd0728a58 | ||
|
|
cf2dcbd431 | ||
|
|
dedb7a2343 | ||
|
|
614574f15c | ||
|
|
28a471777a | ||
|
|
93526637cb | ||
|
|
94c893bd86 | ||
|
|
c99876f3d7 | ||
|
|
a3b2f1a931 | ||
|
|
488dc7815c | ||
|
|
dbee4bee14 | ||
|
|
2cfd116947 | ||
|
|
3f67ef968b | ||
|
|
fd9c80db62 | ||
|
|
7e76638f2c | ||
|
|
884f15da1e | ||
|
|
616aa002e1 | ||
|
|
e0faee7f26 | ||
|
|
f6770a847a | ||
|
|
4a50e94967 | ||
|
|
514ec99a03 | ||
|
|
6c062c48b4 | ||
|
|
b40a1973bf | ||
|
|
a09af619ce | ||
|
|
835944387b | ||
|
|
d927c539b5 | ||
|
|
14454faac8 | ||
|
|
2351531e84 | ||
|
|
33ff3b4f36 | ||
|
|
11e7b218d4 | ||
|
|
fbbd64ddb7 | ||
|
|
41db87c8cf | ||
|
|
79dd912126 | ||
|
|
00fa65c18b | ||
|
|
59db80cb0e | ||
|
|
e5a4dfdf16 | ||
|
|
6ca18c6266 | ||
|
|
e74592fa09 | ||
|
|
abbebff400 | ||
|
|
3c88eccf17 | ||
|
|
48235f5b71 | ||
|
|
736ecf266d | ||
|
|
b2a37452be | ||
|
|
54f2e75b9d | ||
|
|
f7620a43aa | ||
|
|
bcbe5da87f | ||
|
|
e735ea7e63 | ||
|
|
ce61ed2f86 | ||
|
|
172e43cd3f | ||
|
|
9f0b2bafd8 | ||
|
|
03b34e39f8 | ||
|
|
885fc66c14 | ||
|
|
f41380d91c | ||
|
|
66883db645 | ||
|
|
df45e5518f | ||
|
|
4c8c6e543e | ||
|
|
1d9508c268 | ||
|
|
7d03cb114d | ||
|
|
2fcf03ead3 | ||
|
|
012bbca197 | ||
|
|
8132828ad7 | ||
|
|
9601fbf2d5 | ||
|
|
6e55239cec | ||
|
|
900235642c | ||
|
|
f667f56fb7 | ||
|
|
e06b3e7a7a | ||
|
|
223b6b3b2b | ||
|
|
71e55f8829 | ||
|
|
e6d4e5d78d | ||
|
|
8ecc21c093 | ||
|
|
0a80da28fa | ||
|
|
b67d400f60 | ||
|
|
1ab10bdd84 | ||
|
|
ba6743d134 | ||
|
|
d63a336411 | ||
|
|
4a92573235 | ||
|
|
db3d796275 | ||
|
|
6f2f3c2d3b | ||
|
|
f77f0207f7 | ||
|
|
99cfb62370 | ||
|
|
2b590f24ed | ||
|
|
670c932f32 | ||
|
|
ba07a136dc | ||
|
|
9ae16921bc | ||
|
|
887e4a964a | ||
|
|
8faff9ac6a | ||
|
|
0e80aae1b8 | ||
|
|
c142d38f08 | ||
|
|
82bffabb69 | ||
|
|
14c95b9d37 | ||
|
|
b7598c6a41 | ||
|
|
ced2f9d1a0 | ||
|
|
5661444414 | ||
|
|
afa1e0599a | ||
|
|
43e4481b70 | ||
|
|
65b0fdf08d | ||
|
|
f6511c601e | ||
|
|
fd109c4740 | ||
|
|
2368521a9a | ||
|
|
d49e6187cd | ||
|
|
c1c198b84b | ||
|
|
e7ff3146fe | ||
|
|
8e7d84d05b | ||
|
|
d157159106 | ||
|
|
bd8b30c606 | ||
|
|
f56a8feade | ||
|
|
f3cae427f1 | ||
|
|
51b7abec24 | ||
|
|
d90ed89a30 | ||
|
|
ad1d5ddcdf | ||
|
|
2dee936246 | ||
|
|
a0a74612e1 | ||
|
|
fbd3aa36c4 | ||
|
|
94f65f8727 | ||
|
|
2b22a1aebe | ||
|
|
354a0ed46e | ||
|
|
9cdda4377b | ||
|
|
49044e23de | ||
|
|
f57ca1e79e | ||
|
|
0f42f3cc5c | ||
|
|
81c5866fa9 | ||
|
|
1be29076ea | ||
|
|
3c17f606ed | ||
|
|
09a118e85e | ||
|
|
c12beb43a0 | ||
|
|
08f5148a9a | ||
|
|
14ad720c58 | ||
|
|
0ee5665c95 | ||
|
|
bd82737efb | ||
|
|
f75e049675 | ||
|
|
1f8ce9eb92 | ||
|
|
d4bf61da50 | ||
|
|
33754db9b9 | ||
|
|
1809858fc7 | ||
|
|
0cafac84ac | ||
|
|
9eb1ce3ad7 | ||
|
|
6a6fe60a5b | ||
|
|
6338e2b362 | ||
|
|
b1113f4293 | ||
|
|
c4cd7cf6c1 | ||
|
|
11d4d948aa | ||
|
|
61448a61ea | ||
|
|
f373a5cea0 | ||
|
|
935b2e8fb2 | ||
|
|
b1c8ab9350 | ||
|
|
b593ff09e9 | ||
|
|
44894a29b1 | ||
|
|
685d3c79ac | ||
|
|
b66d6b2812 | ||
|
|
5b8ea89092 | ||
|
|
13e13ea8fc | ||
|
|
63b10738b2 | ||
|
|
a5c554cf6e | ||
|
|
6a2ed4a631 | ||
|
|
b7152e7010 | ||
|
|
d766de5fbf | ||
|
|
8daa1219a5 | ||
|
|
3bc40120cd | ||
|
|
4ce025c0a2 | ||
|
|
d8702e1357 | ||
|
|
9fe81ff564 | ||
|
|
5eff9b0934 | ||
|
|
8e586c883c | ||
|
|
7bd04ae928 | ||
|
|
4a7d624e9b | ||
|
|
92a906e0d4 | ||
|
|
24cecf7b63 | ||
|
|
89a327d363 | ||
|
|
e7cd8fe078 | ||
|
|
3d82bc37af | ||
|
|
f13e875f0d | ||
|
|
79439a0217 | ||
|
|
210d0d8aa2 | ||
|
|
836aebc0d9 | ||
|
|
73d33f93b3 | ||
|
|
562c301326 | ||
|
|
a43ebefc97 | ||
|
|
69028969f1 | ||
|
|
671d5270eb | ||
|
|
9a5b773e69 | ||
|
|
f3f4c44620 | ||
|
|
b049e92697 | ||
|
|
ea703ba26e | ||
|
|
78e24962f9 | ||
|
|
fcadbbb739 | ||
|
|
a8c3a49cb9 | ||
|
|
0586d38e03 | ||
|
|
0beaa94311 | ||
|
|
b39098dbc5 | ||
|
|
5b6d551650 | ||
|
|
4b66320f63 | ||
|
|
a76f37760c | ||
|
|
538815e033 | ||
|
|
3fae659b30 | ||
|
|
f641f3acda | ||
|
|
b675406b1a | ||
|
|
a58e81d744 | ||
|
|
e35faa5522 | ||
|
|
7b49fd6508 | ||
|
|
f7f7c4b43e | ||
|
|
506ec64273 | ||
|
|
0a576a0f67 | ||
|
|
40c3866f63 | ||
|
|
6b0007291b | ||
|
|
6a12c43c78 | ||
|
|
43fc9ba932 | ||
|
|
762d24bd02 | ||
|
|
a910337e9d | ||
|
|
6d017b5f0b | ||
|
|
65f8526152 | ||
|
|
99bd4a8937 | ||
|
|
cf914526bf | ||
|
|
858f6aa9b8 | ||
|
|
e6f30c0ebe | ||
|
|
014fc14291 | ||
|
|
60bd8104c4 | ||
|
|
9ce10231ed | ||
|
|
803e26a401 | ||
|
|
27780cc7ae | ||
|
|
82d0f204e2 | ||
|
|
97ee0a19b5 | ||
|
|
2c18d0f1a5 | ||
|
|
35b5c8097c | ||
|
|
f4940e26c6 | ||
|
|
179abfa03e | ||
|
|
ad16d61208 | ||
|
|
6aecbfc38f | ||
|
|
ee48766996 | ||
|
|
93876e27f8 |
490 changed files with 6915 additions and 6724 deletions
20
.clang-tidy
20
.clang-tidy
|
|
@ -1,5 +1,23 @@
|
||||||
Checks:
|
Checks:
|
||||||
- modernize-use-using
|
- modernize-use-using
|
||||||
- readability-avoid-const-params-in-decls
|
- readability-avoid-const-params-in-decls
|
||||||
|
- misc-unused-parameters,
|
||||||
|
- readability-identifier-naming
|
||||||
|
|
||||||
SystemHeaders: false
|
# ^ Without unused-parameters the readability-identifier-naming check doesn't cause any warnings.
|
||||||
|
|
||||||
|
CheckOptions:
|
||||||
|
- { key: readability-identifier-naming.ClassCase, value: PascalCase }
|
||||||
|
- { key: readability-identifier-naming.EnumCase, value: PascalCase }
|
||||||
|
- { key: readability-identifier-naming.FunctionCase, value: camelCase }
|
||||||
|
- { key: readability-identifier-naming.GlobalVariableCase, value: camelCase }
|
||||||
|
- { key: readability-identifier-naming.GlobalFunctionCase, value: camelCase }
|
||||||
|
- { key: readability-identifier-naming.GlobalConstantCase, value: SCREAMING_SNAKE_CASE }
|
||||||
|
- { key: readability-identifier-naming.MacroDefinitionCase, value: SCREAMING_SNAKE_CASE }
|
||||||
|
- { key: readability-identifier-naming.ClassMemberCase, value: camelCase }
|
||||||
|
- { key: readability-identifier-naming.PrivateMemberPrefix, value: m_ }
|
||||||
|
- { key: readability-identifier-naming.ProtectedMemberPrefix, value: m_ }
|
||||||
|
- { key: readability-identifier-naming.PrivateStaticMemberPrefix, value: s_ }
|
||||||
|
- { key: readability-identifier-naming.ProtectedStaticMemberPrefix, value: s_ }
|
||||||
|
- { key: readability-identifier-naming.PublicStaticConstantCase, value: SCREAMING_SNAKE_CASE }
|
||||||
|
- { key: readability-identifier-naming.EnumConstantCase, value: SCREAMING_SNAKE_CASE }
|
||||||
124
.github/workflows/build.yml
vendored
124
.github/workflows/build.yml
vendored
|
|
@ -39,9 +39,6 @@ on:
|
||||||
APPLE_NOTARIZE_PASSWORD:
|
APPLE_NOTARIZE_PASSWORD:
|
||||||
description: Password used for notarizing macOS builds
|
description: Password used for notarizing macOS builds
|
||||||
required: false
|
required: false
|
||||||
CACHIX_AUTH_TOKEN:
|
|
||||||
description: Private token for authenticating against Cachix cache
|
|
||||||
required: false
|
|
||||||
GPG_PRIVATE_KEY:
|
GPG_PRIVATE_KEY:
|
||||||
description: Private key for AppImage signing
|
description: Private key for AppImage signing
|
||||||
required: false
|
required: false
|
||||||
|
|
@ -59,14 +56,14 @@ jobs:
|
||||||
qt_ver: 5
|
qt_ver: 5
|
||||||
qt_host: linux
|
qt_host: linux
|
||||||
qt_arch: ""
|
qt_arch: ""
|
||||||
qt_version: "5.12.8"
|
qt_version: "5.15.2"
|
||||||
qt_modules: "qtnetworkauth"
|
qt_modules: "qtnetworkauth"
|
||||||
|
|
||||||
- os: ubuntu-20.04
|
- os: ubuntu-22.04
|
||||||
qt_ver: 6
|
qt_ver: 6
|
||||||
qt_host: linux
|
qt_host: linux
|
||||||
qt_arch: ""
|
qt_arch: ""
|
||||||
qt_version: "6.2.4"
|
qt_version: "6.5.3"
|
||||||
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
||||||
|
|
||||||
- os: windows-2022
|
- os: windows-2022
|
||||||
|
|
@ -80,9 +77,9 @@ jobs:
|
||||||
architecture: "x64"
|
architecture: "x64"
|
||||||
vcvars_arch: "amd64"
|
vcvars_arch: "amd64"
|
||||||
qt_ver: 6
|
qt_ver: 6
|
||||||
qt_host: windows
|
qt_host: "windows"
|
||||||
qt_arch: ""
|
qt_arch: "win64_msvc2022_64"
|
||||||
qt_version: "6.7.3"
|
qt_version: "6.8.1"
|
||||||
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
||||||
nscurl_tag: "v24.9.26.122"
|
nscurl_tag: "v24.9.26.122"
|
||||||
nscurl_sha256: "AEE6C4BE3CB6455858E9C1EE4B3AFE0DB9960FA03FE99CCDEDC28390D57CCBB0"
|
nscurl_sha256: "AEE6C4BE3CB6455858E9C1EE4B3AFE0DB9960FA03FE99CCDEDC28390D57CCBB0"
|
||||||
|
|
@ -93,9 +90,9 @@ jobs:
|
||||||
architecture: "arm64"
|
architecture: "arm64"
|
||||||
vcvars_arch: "amd64_arm64"
|
vcvars_arch: "amd64_arm64"
|
||||||
qt_ver: 6
|
qt_ver: 6
|
||||||
qt_host: windows
|
qt_host: "windows"
|
||||||
qt_arch: "win64_msvc2019_arm64"
|
qt_arch: "win64_msvc2022_arm64_cross_compiled"
|
||||||
qt_version: "6.7.3"
|
qt_version: "6.8.1"
|
||||||
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
||||||
nscurl_tag: "v24.9.26.122"
|
nscurl_tag: "v24.9.26.122"
|
||||||
nscurl_sha256: "AEE6C4BE3CB6455858E9C1EE4B3AFE0DB9960FA03FE99CCDEDC28390D57CCBB0"
|
nscurl_sha256: "AEE6C4BE3CB6455858E9C1EE4B3AFE0DB9960FA03FE99CCDEDC28390D57CCBB0"
|
||||||
|
|
@ -106,7 +103,7 @@ jobs:
|
||||||
qt_ver: 6
|
qt_ver: 6
|
||||||
qt_host: mac
|
qt_host: mac
|
||||||
qt_arch: ""
|
qt_arch: ""
|
||||||
qt_version: "6.7.3"
|
qt_version: "6.8.1"
|
||||||
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
||||||
|
|
||||||
- os: macos-14
|
- os: macos-14
|
||||||
|
|
@ -167,13 +164,13 @@ jobs:
|
||||||
|
|
||||||
- name: Setup ccache
|
- name: Setup ccache
|
||||||
if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug'
|
if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug'
|
||||||
uses: hendrikmuhs/ccache-action@v1.2.14
|
uses: hendrikmuhs/ccache-action@v1.2.17
|
||||||
with:
|
with:
|
||||||
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }}
|
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }}
|
||||||
|
|
||||||
- name: Retrieve ccache cache (Windows MinGW-w64)
|
- name: Retrieve ccache cache (Windows MinGW-w64)
|
||||||
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
|
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
|
||||||
uses: actions/cache@v4.1.1
|
uses: actions/cache@v4.2.1
|
||||||
with:
|
with:
|
||||||
path: '${{ github.workspace }}\.ccache'
|
path: '${{ github.workspace }}\.ccache'
|
||||||
key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }}
|
key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }}
|
||||||
|
|
@ -206,7 +203,7 @@ jobs:
|
||||||
if: runner.os == 'Linux'
|
if: runner.os == 'Linux'
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get -y update
|
sudo apt-get -y update
|
||||||
sudo apt-get -y install ninja-build extra-cmake-modules scdoc appstream
|
sudo apt-get -y install ninja-build extra-cmake-modules scdoc appstream libxcb-cursor-dev
|
||||||
|
|
||||||
- name: Install Dependencies (macOS)
|
- name: Install Dependencies (macOS)
|
||||||
if: runner.os == 'macOS'
|
if: runner.os == 'macOS'
|
||||||
|
|
@ -216,14 +213,14 @@ jobs:
|
||||||
|
|
||||||
- name: Install host Qt (Windows MSVC arm64)
|
- name: Install host Qt (Windows MSVC arm64)
|
||||||
if: runner.os == 'Windows' && matrix.architecture == 'arm64'
|
if: runner.os == 'Windows' && matrix.architecture == 'arm64'
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v4
|
||||||
with:
|
with:
|
||||||
aqtversion: "==3.1.*"
|
aqtversion: "==3.1.*"
|
||||||
py7zrversion: ">=0.20.2"
|
py7zrversion: ">=0.20.2"
|
||||||
version: ${{ matrix.qt_version }}
|
version: ${{ matrix.qt_version }}
|
||||||
host: "windows"
|
host: "windows"
|
||||||
target: "desktop"
|
target: "desktop"
|
||||||
arch: ""
|
arch: ${{ matrix.qt_arch }}
|
||||||
modules: ${{ matrix.qt_modules }}
|
modules: ${{ matrix.qt_modules }}
|
||||||
cache: ${{ inputs.is_qt_cached }}
|
cache: ${{ inputs.is_qt_cached }}
|
||||||
cache-key-prefix: host-qt-arm64-windows
|
cache-key-prefix: host-qt-arm64-windows
|
||||||
|
|
@ -232,7 +229,7 @@ jobs:
|
||||||
|
|
||||||
- name: Install Qt (macOS, Linux & Windows MSVC)
|
- name: Install Qt (macOS, Linux & Windows MSVC)
|
||||||
if: matrix.msystem == ''
|
if: matrix.msystem == ''
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v4
|
||||||
with:
|
with:
|
||||||
aqtversion: "==3.1.*"
|
aqtversion: "==3.1.*"
|
||||||
py7zrversion: ">=0.20.2"
|
py7zrversion: ">=0.20.2"
|
||||||
|
|
@ -259,12 +256,12 @@ jobs:
|
||||||
|
|
||||||
wget "https://github.com/AppImageCommunity/AppImageUpdate/releases/download/continuous/AppImageUpdate-x86_64.AppImage"
|
wget "https://github.com/AppImageCommunity/AppImageUpdate/releases/download/continuous/AppImageUpdate-x86_64.AppImage"
|
||||||
|
|
||||||
sudo apt install libopengl0
|
sudo apt install libopengl0 libfuse2
|
||||||
|
|
||||||
- name: Add QT_HOST_PATH var (Windows MSVC arm64)
|
- name: Add QT_HOST_PATH var (Windows MSVC arm64)
|
||||||
if: runner.os == 'Windows' && matrix.architecture == 'arm64'
|
if: runner.os == 'Windows' && matrix.architecture == 'arm64'
|
||||||
run: |
|
run: |
|
||||||
echo "QT_HOST_PATH=${{ github.workspace }}\HostQt\Qt\${{ matrix.qt_version }}\msvc2019_64" >> $env:GITHUB_ENV
|
echo "QT_HOST_PATH=${{ github.workspace }}\HostQt\Qt\${{ matrix.qt_version }}\msvc2022_64" >> $env:GITHUB_ENV
|
||||||
|
|
||||||
- name: Setup java (macOS)
|
- name: Setup java (macOS)
|
||||||
if: runner.os == 'macOS'
|
if: runner.os == 'macOS'
|
||||||
|
|
@ -380,11 +377,13 @@ jobs:
|
||||||
|
|
||||||
if [ -n '${{ secrets.APPLE_CODESIGN_ID }}' ]; then
|
if [ -n '${{ secrets.APPLE_CODESIGN_ID }}' ]; then
|
||||||
APPLE_CODESIGN_ID='${{ secrets.APPLE_CODESIGN_ID }}'
|
APPLE_CODESIGN_ID='${{ secrets.APPLE_CODESIGN_ID }}'
|
||||||
|
ENTITLEMENTS_FILE='../program_info/App.entitlements'
|
||||||
else
|
else
|
||||||
APPLE_CODESIGN_ID='-'
|
APPLE_CODESIGN_ID='-'
|
||||||
|
ENTITLEMENTS_FILE='../program_info/AdhocSignedApp.entitlements'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
sudo codesign --sign "$APPLE_CODESIGN_ID" --deep --force --entitlements "../program_info/App.entitlements" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher"
|
sudo codesign --sign "$APPLE_CODESIGN_ID" --deep --force --entitlements "$ENTITLEMENTS_FILE" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher"
|
||||||
mv "PrismLauncher.app" "Prism Launcher.app"
|
mv "PrismLauncher.app" "Prism Launcher.app"
|
||||||
|
|
||||||
- name: Notarize (macOS)
|
- name: Notarize (macOS)
|
||||||
|
|
@ -410,9 +409,8 @@ jobs:
|
||||||
if: matrix.name == 'macOS'
|
if: matrix.name == 'macOS'
|
||||||
run: |
|
run: |
|
||||||
if [ '${{ secrets.SPARKLE_ED25519_KEY }}' != '' ]; then
|
if [ '${{ secrets.SPARKLE_ED25519_KEY }}' != '' ]; then
|
||||||
brew install openssl@3
|
|
||||||
echo '${{ secrets.SPARKLE_ED25519_KEY }}' > ed25519-priv.pem
|
echo '${{ secrets.SPARKLE_ED25519_KEY }}' > ed25519-priv.pem
|
||||||
signature=$(/usr/local/opt/openssl@3/bin/openssl pkeyutl -sign -rawin -in ${{ github.workspace }}/PrismLauncher.zip -inkey ed25519-priv.pem | openssl base64 | tr -d \\n)
|
signature=$(/opt/homebrew/opt/openssl@3/bin/openssl pkeyutl -sign -rawin -in ${{ github.workspace }}/PrismLauncher.zip -inkey ed25519-priv.pem | openssl base64 | tr -d \\n)
|
||||||
rm ed25519-priv.pem
|
rm ed25519-priv.pem
|
||||||
cat >> $GITHUB_STEP_SUMMARY << EOF
|
cat >> $GITHUB_STEP_SUMMARY << EOF
|
||||||
### Artifact Information :information_source:
|
### Artifact Information :information_source:
|
||||||
|
|
@ -520,8 +518,8 @@ jobs:
|
||||||
|
|
||||||
cp -r ${{ runner.workspace }}/Qt/${{ matrix.qt_version }}/gcc_64/plugins/iconengines/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines
|
cp -r ${{ runner.workspace }}/Qt/${{ matrix.qt_version }}/gcc_64/plugins/iconengines/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines
|
||||||
|
|
||||||
cp /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
|
cp /usr/lib/x86_64-linux-gnu/libcrypto.so.* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
|
||||||
cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
|
cp /usr/lib/x86_64-linux-gnu/libssl.so.* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
|
||||||
cp /usr/lib/x86_64-linux-gnu/libOpenGL.so.0* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
|
cp /usr/lib/x86_64-linux-gnu/libOpenGL.so.0* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
|
||||||
|
|
||||||
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib"
|
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib"
|
||||||
|
|
@ -556,9 +554,9 @@ jobs:
|
||||||
mkdir ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
mkdir ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||||
cp /lib/x86_64-linux-gnu/libbz2.so.1.0 ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
cp /lib/x86_64-linux-gnu/libbz2.so.1.0 ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||||
cp /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0 ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
cp /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0 ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||||
cp /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
cp /usr/lib/x86_64-linux-gnu/libcrypto.so.* ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||||
cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
cp /usr/lib/x86_64-linux-gnu/libssl.so.* ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||||
cp /usr/lib/x86_64-linux-gnu/libffi.so.7 ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
cp /usr/lib/x86_64-linux-gnu/libffi.so.*.* ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||||
mv ${{ env.INSTALL_PORTABLE_DIR }}/bin/*.so* ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
mv ${{ env.INSTALL_PORTABLE_DIR }}/bin/*.so* ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||||
|
|
||||||
for l in $(find ${{ env.INSTALL_PORTABLE_DIR }} -type f); do l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_PORTABLE_DIR }}/}; l=${l#./}; echo $l; done > ${{ env.INSTALL_PORTABLE_DIR }}/manifest.txt
|
for l in $(find ${{ env.INSTALL_PORTABLE_DIR }} -type f); do l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_PORTABLE_DIR }}/}; l=${l#./}; echo $l; done > ${{ env.INSTALL_PORTABLE_DIR }}/manifest.txt
|
||||||
|
|
@ -630,71 +628,3 @@ jobs:
|
||||||
shell: msys2 {0}
|
shell: msys2 {0}
|
||||||
run: |
|
run: |
|
||||||
ccache -s
|
ccache -s
|
||||||
|
|
||||||
flatpak:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container:
|
|
||||||
image: bilelmoussaoui/flatpak-github-actions:kde-6.7
|
|
||||||
options: --privileged
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
if: inputs.build_type == 'Debug'
|
|
||||||
with:
|
|
||||||
submodules: "true"
|
|
||||||
- name: Build Flatpak (Linux)
|
|
||||||
if: inputs.build_type == 'Debug'
|
|
||||||
uses: flatpak/flatpak-github-actions/flatpak-builder@v6
|
|
||||||
with:
|
|
||||||
bundle: "Prism Launcher.flatpak"
|
|
||||||
manifest-path: flatpak/org.prismlauncher.PrismLauncher.yml
|
|
||||||
|
|
||||||
nix:
|
|
||||||
name: Nix (${{ matrix.system }})
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- os: ubuntu-22.04
|
|
||||||
system: x86_64-linux
|
|
||||||
|
|
||||||
- os: macos-13
|
|
||||||
system: x86_64-darwin
|
|
||||||
|
|
||||||
- os: macos-14
|
|
||||||
system: aarch64-darwin
|
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install Nix
|
|
||||||
uses: cachix/install-nix-action@v30
|
|
||||||
|
|
||||||
# For PRs
|
|
||||||
- name: Setup Nix Magic Cache
|
|
||||||
uses: DeterminateSystems/magic-nix-cache-action@v8
|
|
||||||
|
|
||||||
# For in-tree builds
|
|
||||||
- name: Setup Cachix
|
|
||||||
uses: cachix/cachix-action@v15
|
|
||||||
with:
|
|
||||||
name: prismlauncher
|
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
|
||||||
|
|
||||||
- name: Run flake checks
|
|
||||||
run: |
|
|
||||||
nix flake check --print-build-logs --show-trace
|
|
||||||
|
|
||||||
- name: Build debug package
|
|
||||||
if: ${{ inputs.build_type == 'Debug' }}
|
|
||||||
run: |
|
|
||||||
nix build --print-build-logs .#prismlauncher-debug
|
|
||||||
|
|
||||||
- name: Build release package
|
|
||||||
if: ${{ inputs.build_type != 'Debug' }}
|
|
||||||
run: |
|
|
||||||
nix build --print-build-logs .#prismlauncher
|
|
||||||
|
|
|
||||||
62
.github/workflows/flatpak.yml
vendored
Normal file
62
.github/workflows/flatpak.yml
vendored
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
name: Flatpak
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths-ignore:
|
||||||
|
- "**.md"
|
||||||
|
- "**/LICENSE"
|
||||||
|
- ".github/ISSUE_TEMPLATE/**"
|
||||||
|
- ".markdownlint**"
|
||||||
|
- "nix/**"
|
||||||
|
# We don't do anything with these artifacts on releases. They go to Flathub
|
||||||
|
tags-ignore:
|
||||||
|
- "*"
|
||||||
|
pull_request:
|
||||||
|
paths-ignore:
|
||||||
|
- "**.md"
|
||||||
|
- "**/LICENSE"
|
||||||
|
- ".github/ISSUE_TEMPLATE/**"
|
||||||
|
- ".markdownlint**"
|
||||||
|
- "nix/**"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build (${{ matrix.arch }})
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: ubuntu-22.04
|
||||||
|
arch: x86_64
|
||||||
|
|
||||||
|
- os: ubuntu-22.04-arm
|
||||||
|
arch: aarch64
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
|
container:
|
||||||
|
image: ghcr.io/flathub-infra/flatpak-github-actions:kde-6.8
|
||||||
|
options: --privileged
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
|
||||||
|
- name: Set short version
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "VERSION=${GITHUB_SHA::7}" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Build Flatpak
|
||||||
|
uses: flatpak/flatpak-github-actions/flatpak-builder@v6
|
||||||
|
with:
|
||||||
|
bundle: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-Flatpak.flatpak
|
||||||
|
manifest-path: flatpak/org.prismlauncher.PrismLauncher.yml
|
||||||
|
arch: ${{ matrix.arch }}
|
||||||
90
.github/workflows/nix.yml
vendored
Normal file
90
.github/workflows/nix.yml
vendored
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
name: Nix
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths-ignore:
|
||||||
|
- "**.md"
|
||||||
|
- "**/LICENSE"
|
||||||
|
- ".github/ISSUE_TEMPLATE/**"
|
||||||
|
- ".markdownlint**"
|
||||||
|
- "flatpak/**"
|
||||||
|
pull_request_target:
|
||||||
|
paths-ignore:
|
||||||
|
- "**.md"
|
||||||
|
- "**/LICENSE"
|
||||||
|
- ".github/ISSUE_TEMPLATE/**"
|
||||||
|
- ".markdownlint**"
|
||||||
|
- "flatpak/**"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
env:
|
||||||
|
DEBUG: ${{ github.ref_type != 'tag' }}
|
||||||
|
USE_DETERMINATE: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build (${{ matrix.system }})
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: ubuntu-22.04
|
||||||
|
system: x86_64-linux
|
||||||
|
|
||||||
|
- os: ubuntu-22.04-arm
|
||||||
|
system: aarch64-linux
|
||||||
|
|
||||||
|
- os: macos-13
|
||||||
|
system: x86_64-darwin
|
||||||
|
|
||||||
|
- os: macos-14
|
||||||
|
system: aarch64-darwin
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Nix
|
||||||
|
uses: DeterminateSystems/nix-installer-action@v16
|
||||||
|
with:
|
||||||
|
determinate: ${{ env.USE_DETERMINATE }}
|
||||||
|
|
||||||
|
# For PRs
|
||||||
|
- name: Setup Nix Magic Cache
|
||||||
|
if: ${{ env.USE_DETERMINATE }}
|
||||||
|
uses: DeterminateSystems/flakehub-cache-action@v1
|
||||||
|
|
||||||
|
# For in-tree builds
|
||||||
|
- name: Setup Cachix
|
||||||
|
if: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }}
|
||||||
|
uses: cachix/cachix-action@v15
|
||||||
|
with:
|
||||||
|
name: prismlauncher
|
||||||
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
|
|
||||||
|
- name: Run Flake checks
|
||||||
|
run: |
|
||||||
|
nix flake check --print-build-logs --show-trace
|
||||||
|
|
||||||
|
- name: Build debug package
|
||||||
|
if: ${{ env.DEBUG }}
|
||||||
|
run: |
|
||||||
|
nix build \
|
||||||
|
--no-link --print-build-logs --print-out-paths \
|
||||||
|
.#prismlauncher-debug >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
|
||||||
|
- name: Build release package
|
||||||
|
if: ${{ !env.DEBUG }}
|
||||||
|
run: |
|
||||||
|
nix build \
|
||||||
|
--no-link --print-build-logs --print-out-paths \
|
||||||
|
.#prismlauncher >> "$GITHUB_STEP_SUMMARY"
|
||||||
45
.github/workflows/publish.yml
vendored
Normal file
45
.github/workflows/publish.yml
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
name: Publish
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [ released ]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
flakehub:
|
||||||
|
name: FlakeHub
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.ref }}
|
||||||
|
|
||||||
|
- name: Install Nix
|
||||||
|
uses: cachix/install-nix-action@v30
|
||||||
|
|
||||||
|
- name: Publish on FlakeHub
|
||||||
|
uses: determinatesystems/flakehub-push@v5
|
||||||
|
with:
|
||||||
|
visibility: "public"
|
||||||
|
|
||||||
|
winget:
|
||||||
|
name: Winget
|
||||||
|
|
||||||
|
runs-on: windows-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Publish on Winget
|
||||||
|
uses: vedantmgoyal2009/winget-releaser@v2
|
||||||
|
with:
|
||||||
|
identifier: PrismLauncher.PrismLauncher
|
||||||
|
version: ${{ github.event.release.tag_name }}
|
||||||
|
installers-regex: 'PrismLauncher-Windows-MSVC(:?-arm64|-Legacy)?-Setup-.+\.exe$'
|
||||||
|
token: ${{ secrets.WINGET_TOKEN }}
|
||||||
29
.github/workflows/stale.yml
vendored
Normal file
29
.github/workflows/stale.yml
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
name: Stale
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
# run weekly on sunday
|
||||||
|
- cron: "0 0 * * 0"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
label:
|
||||||
|
name: Label issues and PRs
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v9
|
||||||
|
with:
|
||||||
|
days-before-stale: 60
|
||||||
|
days-before-close: -1 # Don't close anything
|
||||||
|
exempt-issue-labels: rfc,nostale,help wanted
|
||||||
|
exempt-all-milestones: true
|
||||||
|
exempt-all-assignees: true
|
||||||
|
operations-per-run: 1000
|
||||||
|
stale-issue-label: inactive
|
||||||
|
stale-pr-label: inactive
|
||||||
1
.github/workflows/trigger_builds.yml
vendored
1
.github/workflows/trigger_builds.yml
vendored
|
|
@ -38,6 +38,5 @@ jobs:
|
||||||
APPLE_NOTARIZE_APPLE_ID: ${{ secrets.APPLE_NOTARIZE_APPLE_ID }}
|
APPLE_NOTARIZE_APPLE_ID: ${{ secrets.APPLE_NOTARIZE_APPLE_ID }}
|
||||||
APPLE_NOTARIZE_TEAM_ID: ${{ secrets.APPLE_NOTARIZE_TEAM_ID }}
|
APPLE_NOTARIZE_TEAM_ID: ${{ secrets.APPLE_NOTARIZE_TEAM_ID }}
|
||||||
APPLE_NOTARIZE_PASSWORD: ${{ secrets.APPLE_NOTARIZE_PASSWORD }}
|
APPLE_NOTARIZE_PASSWORD: ${{ secrets.APPLE_NOTARIZE_PASSWORD }}
|
||||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
|
||||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||||
GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}
|
GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}
|
||||||
|
|
|
||||||
1
.github/workflows/trigger_release.yml
vendored
1
.github/workflows/trigger_release.yml
vendored
|
|
@ -22,7 +22,6 @@ jobs:
|
||||||
APPLE_NOTARIZE_APPLE_ID: ${{ secrets.APPLE_NOTARIZE_APPLE_ID }}
|
APPLE_NOTARIZE_APPLE_ID: ${{ secrets.APPLE_NOTARIZE_APPLE_ID }}
|
||||||
APPLE_NOTARIZE_TEAM_ID: ${{ secrets.APPLE_NOTARIZE_TEAM_ID }}
|
APPLE_NOTARIZE_TEAM_ID: ${{ secrets.APPLE_NOTARIZE_TEAM_ID }}
|
||||||
APPLE_NOTARIZE_PASSWORD: ${{ secrets.APPLE_NOTARIZE_PASSWORD }}
|
APPLE_NOTARIZE_PASSWORD: ${{ secrets.APPLE_NOTARIZE_PASSWORD }}
|
||||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
|
||||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||||
GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}
|
GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}
|
||||||
|
|
||||||
|
|
|
||||||
15
.github/workflows/winget.yml
vendored
15
.github/workflows/winget.yml
vendored
|
|
@ -1,15 +0,0 @@
|
||||||
name: Publish to WinGet
|
|
||||||
on:
|
|
||||||
release:
|
|
||||||
types: [released]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
publish:
|
|
||||||
runs-on: windows-latest
|
|
||||||
steps:
|
|
||||||
- uses: vedantmgoyal2009/winget-releaser@v2
|
|
||||||
with:
|
|
||||||
identifier: PrismLauncher.PrismLauncher
|
|
||||||
version: ${{ github.event.release.tag_name }}
|
|
||||||
installers-regex: 'PrismLauncher-Windows-MSVC(:?-arm64|-Legacy)?-Setup-.+\.exe$'
|
|
||||||
token: ${{ secrets.WINGET_TOKEN }}
|
|
||||||
|
|
@ -78,6 +78,13 @@ else()
|
||||||
# ATL's pack list needs more than the default 1 Mib stack on windows
|
# ATL's pack list needs more than the default 1 Mib stack on windows
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
set(CMAKE_EXE_LINKER_FLAGS "-Wl,--stack,8388608 ${CMAKE_EXE_LINKER_FLAGS}")
|
set(CMAKE_EXE_LINKER_FLAGS "-Wl,--stack,8388608 ${CMAKE_EXE_LINKER_FLAGS}")
|
||||||
|
|
||||||
|
# -ffunction-sections and -fdata-sections help reduce binary size
|
||||||
|
# -mguard=cf enables Control Flow Guard
|
||||||
|
# TODO: Look into -gc-sections to further reduce binary size
|
||||||
|
foreach(lang C CXX)
|
||||||
|
set("CMAKE_${lang}_FLAGS_RELEASE" "-ffunction-sections -fdata-sections -mguard=cf")
|
||||||
|
endforeach()
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
@ -106,14 +113,14 @@ if ((CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebI
|
||||||
else()
|
else()
|
||||||
# AppleClang and Clang
|
# AppleClang and Clang
|
||||||
message(STATUS "Address Sanitizer available on Clang")
|
message(STATUS "Address Sanitizer available on Clang")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=null")
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=null")
|
||||||
endif()
|
endif()
|
||||||
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
|
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
|
||||||
# GCC
|
# GCC
|
||||||
message(STATUS "Address Sanitizer available on GCC")
|
message(STATUS "Address Sanitizer available on GCC")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover")
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover")
|
||||||
link_libraries("asan")
|
link_libraries("asan")
|
||||||
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
|
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
|
||||||
message(STATUS "Address Sanitizer available on MSVC")
|
message(STATUS "Address Sanitizer available on MSVC")
|
||||||
|
|
@ -180,7 +187,7 @@ set(Launcher_LOGIN_CALLBACK_URL "https://prismlauncher.org/successful-login" CAC
|
||||||
set(Launcher_FMLLIBS_BASE_URL "https://files.prismlauncher.org/fmllibs/" CACHE STRING "URL for FML Libraries.")
|
set(Launcher_FMLLIBS_BASE_URL "https://files.prismlauncher.org/fmllibs/" CACHE STRING "URL for FML Libraries.")
|
||||||
|
|
||||||
######## Set version numbers ########
|
######## Set version numbers ########
|
||||||
set(Launcher_VERSION_MAJOR 9)
|
set(Launcher_VERSION_MAJOR 10)
|
||||||
set(Launcher_VERSION_MINOR 0)
|
set(Launcher_VERSION_MINOR 0)
|
||||||
|
|
||||||
set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}")
|
set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}")
|
||||||
|
|
@ -299,6 +306,8 @@ include(QtVersionlessBackport)
|
||||||
if(Launcher_QT_VERSION_MAJOR EQUAL 5)
|
if(Launcher_QT_VERSION_MAJOR EQUAL 5)
|
||||||
set(QT_VERSION_MAJOR 5)
|
set(QT_VERSION_MAJOR 5)
|
||||||
find_package(Qt5 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml NetworkAuth)
|
find_package(Qt5 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml NetworkAuth)
|
||||||
|
find_package(Qt5 COMPONENTS DBus)
|
||||||
|
list(APPEND Launcher_QT_DBUS Qt5::DBus)
|
||||||
|
|
||||||
if(NOT Launcher_FORCE_BUNDLED_LIBS)
|
if(NOT Launcher_FORCE_BUNDLED_LIBS)
|
||||||
find_package(QuaZip-Qt5 1.3 QUIET)
|
find_package(QuaZip-Qt5 1.3 QUIET)
|
||||||
|
|
@ -313,6 +322,8 @@ if(Launcher_QT_VERSION_MAJOR EQUAL 5)
|
||||||
elseif(Launcher_QT_VERSION_MAJOR EQUAL 6)
|
elseif(Launcher_QT_VERSION_MAJOR EQUAL 6)
|
||||||
set(QT_VERSION_MAJOR 6)
|
set(QT_VERSION_MAJOR 6)
|
||||||
find_package(Qt6 REQUIRED COMPONENTS Core CoreTools Widgets Concurrent Network Test Xml Core5Compat NetworkAuth)
|
find_package(Qt6 REQUIRED COMPONENTS Core CoreTools Widgets Concurrent Network Test Xml Core5Compat NetworkAuth)
|
||||||
|
find_package(Qt6 COMPONENTS DBus)
|
||||||
|
list(APPEND Launcher_QT_DBUS Qt6::DBus)
|
||||||
list(APPEND Launcher_QT_LIBS Qt6::Core5Compat)
|
list(APPEND Launcher_QT_LIBS Qt6::Core5Compat)
|
||||||
|
|
||||||
if(NOT Launcher_FORCE_BUNDLED_LIBS)
|
if(NOT Launcher_FORCE_BUNDLED_LIBS)
|
||||||
|
|
@ -397,8 +408,8 @@ if(UNIX AND APPLE)
|
||||||
set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "v55ZWWD6QlPoXGV6VLzOTZxZUggWeE51X8cRQyQh6vA=" CACHE STRING "Public key for Sparkle update feed")
|
set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "v55ZWWD6QlPoXGV6VLzOTZxZUggWeE51X8cRQyQh6vA=" CACHE STRING "Public key for Sparkle update feed")
|
||||||
set(MACOSX_SPARKLE_UPDATE_FEED_URL "https://prismlauncher.org/feed/appcast.xml" CACHE STRING "URL for Sparkle update feed")
|
set(MACOSX_SPARKLE_UPDATE_FEED_URL "https://prismlauncher.org/feed/appcast.xml" CACHE STRING "URL for Sparkle update feed")
|
||||||
|
|
||||||
set(MACOSX_SPARKLE_DOWNLOAD_URL "https://github.com/sparkle-project/Sparkle/releases/download/2.5.2/Sparkle-2.5.2.tar.xz" CACHE STRING "URL to Sparkle release archive")
|
set(MACOSX_SPARKLE_DOWNLOAD_URL "https://github.com/sparkle-project/Sparkle/releases/download/2.6.4/Sparkle-2.6.4.tar.xz" CACHE STRING "URL to Sparkle release archive")
|
||||||
set(MACOSX_SPARKLE_SHA256 "572dd67ae398a466f19f343a449e1890bac1ef74885b4739f68f979a8a89884b" CACHE STRING "SHA256 checksum for Sparkle release archive")
|
set(MACOSX_SPARKLE_SHA256 "50612a06038abc931f16011d7903b8326a362c1074dabccb718404ce8e585f0b" CACHE STRING "SHA256 checksum for Sparkle release archive")
|
||||||
set(MACOSX_SPARKLE_DIR "${CMAKE_BINARY_DIR}/frameworks/Sparkle")
|
set(MACOSX_SPARKLE_DIR "${CMAKE_BINARY_DIR}/frameworks/Sparkle")
|
||||||
|
|
||||||
# directories to look for dependencies
|
# directories to look for dependencies
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,59 @@
|
||||||
|
|
||||||
## Code formatting
|
## Code formatting
|
||||||
|
|
||||||
Try to follow the existing formatting.
|
All files are formatted with `clang-format` using the configuration in `.clang-format`. Ensure it is run on changed files before committing!
|
||||||
If there is no existing formatting, you may use `clang-format` with our included `.clang-format` configuration.
|
|
||||||
|
|
||||||
In general, in order of importance:
|
Please also follow the project's conventions for C++:
|
||||||
|
|
||||||
- Make sure your IDE is not messing up line endings or whitespace and avoid using linters.
|
- Class and type names should be formatted as `PascalCase`: `MyClass`.
|
||||||
- Prefer readability over dogma.
|
- Private or protected class data members should be formatted as `camelCase` prefixed with `m_`: `m_myCounter`.
|
||||||
- Keep to the existing formatting.
|
- Private or protected `static` class data members should be formatted as `camelCase` prefixed with `s_`: `s_instance`.
|
||||||
- Indent with 4 space unless it's in a submodule.
|
- Public class data members should be formatted as `camelCase` without the prefix: `dateOfBirth`.
|
||||||
- Keep lists (of arguments, parameters, initializers...) as lists, not paragraphs. It should either read from top to bottom, or left to right. Not both.
|
- Public, private or protected `static const` class data members should be formatted as `SCREAMING_SNAKE_CASE`: `MAX_VALUE`.
|
||||||
|
- Class function members should be formatted as `camelCase` without a prefix: `incrementCounter`.
|
||||||
|
- Global functions and non-`const` global variables should be formatted as `camelCase` without a prefix: `globalData`.
|
||||||
|
- `const` global variables, macros, and enum constants should be formatted as `SCREAMING_SNAKE_CASE`: `LIGHT_GRAY`.
|
||||||
|
- Avoid inventing acronyms or abbreviations especially for a name of multiple words - like `tp` for `texturePack`.
|
||||||
|
|
||||||
|
Most of these rules are included in the `.clang-tidy` file, so you can run `clang-tidy` to check for any violations.
|
||||||
|
|
||||||
|
Here is what these conventions with the formatting configuration look like:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
#define AWESOMENESS 10
|
||||||
|
|
||||||
|
constexpr double PI = 3.14159;
|
||||||
|
|
||||||
|
enum class PizzaToppings { HAM_AND_PINEAPPLE, OREO_AND_KETCHUP };
|
||||||
|
|
||||||
|
struct Person {
|
||||||
|
QString name;
|
||||||
|
QDateTime dateOfBirth;
|
||||||
|
|
||||||
|
long daysOld() const { return dateOfBirth.daysTo(QDateTime::currentDateTime()); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class ImportantClass {
|
||||||
|
public:
|
||||||
|
void incrementCounter()
|
||||||
|
{
|
||||||
|
if (m_counter + 1 > MAX_COUNTER_VALUE)
|
||||||
|
throw std::runtime_error("Counter has reached limit!");
|
||||||
|
|
||||||
|
++m_counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
int counter() const { return m_counter; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr int MAX_COUNTER_VALUE = 100;
|
||||||
|
int m_counter;
|
||||||
|
};
|
||||||
|
|
||||||
|
ImportantClass importantClassInstance;
|
||||||
|
```
|
||||||
|
|
||||||
|
If you see any names which do not follow these conventions, it is preferred that you leave them be - renames increase the number of changes therefore make reviewing harder and make your PR more prone to conflicts. However, if you're refactoring a whole class anyway, it's fine.
|
||||||
|
|
||||||
## Signing your work
|
## Signing your work
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
<p align="center"><b><i>This is a fork of PrismLauncher, unlocked to be able to use offline accounts without owning the game.</b></i></p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<picture>
|
<picture>
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="/program_info/org.prismlauncher.PrismLauncher.logo-darkmode.svg">
|
<source media="(prefers-color-scheme: dark)" srcset="/program_info/org.prismlauncher.PrismLauncher.logo-darkmode.svg">
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@
|
||||||
<string>A Minecraft mod wants to access your microphone.</string>
|
<string>A Minecraft mod wants to access your microphone.</string>
|
||||||
<key>NSDownloadsFolderUsageDescription</key>
|
<key>NSDownloadsFolderUsageDescription</key>
|
||||||
<string>Prism uses access to your Downloads folder to help you more quickly add mods that can't be automatically downloaded to your instance. You can change where Prism scans for downloaded mods in Settings or the prompt that appears.</string>
|
<string>Prism uses access to your Downloads folder to help you more quickly add mods that can't be automatically downloaded to your instance. You can change where Prism scans for downloaded mods in Settings or the prompt that appears.</string>
|
||||||
|
<key>NSLocalNetworkUsageDescription</key>
|
||||||
|
<string>Minecraft uses the local network to find and connect to LAN servers.</string>
|
||||||
<key>NSPrincipalClass</key>
|
<key>NSPrincipalClass</key>
|
||||||
<string>NSApplication</string>
|
<string>NSApplication</string>
|
||||||
<key>NSHighResolutionCapable</key>
|
<key>NSHighResolutionCapable</key>
|
||||||
|
|
|
||||||
18
flake.lock
generated
18
flake.lock
generated
|
|
@ -3,11 +3,11 @@
|
||||||
"flake-compat": {
|
"flake-compat": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1696426674,
|
"lastModified": 1733328505,
|
||||||
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
|
||||||
"owner": "edolstra",
|
"owner": "edolstra",
|
||||||
"repo": "flake-compat",
|
"repo": "flake-compat",
|
||||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -34,11 +34,11 @@
|
||||||
},
|
},
|
||||||
"nix-filter": {
|
"nix-filter": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1710156097,
|
"lastModified": 1731533336,
|
||||||
"narHash": "sha256-1Wvk8UP7PXdf8bCCaEoMnOT1qe5/Duqgj+rL8sRQsSM=",
|
"narHash": "sha256-oRam5PS1vcrr5UPgALW0eo1m/5/pls27Z/pabHNy2Ms=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "nix-filter",
|
"repo": "nix-filter",
|
||||||
"rev": "3342559a24e85fc164b295c3444e8a139924675b",
|
"rev": "f7653272fd234696ae94229839a99b73c9ab7de0",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -49,11 +49,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1729256560,
|
"lastModified": 1739446958,
|
||||||
"narHash": "sha256-/uilDXvCIEs3C9l73JTACm4quuHUsIHcns1c+cHUJwA=",
|
"narHash": "sha256-+/bYK3DbPxMIvSL4zArkMX0LQvS7rzBKXnDXLfKyRVc=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "4c2fcb090b1f3e5b47eaa7bd33913b574a11e0a0",
|
"rev": "2ff53fe64443980e139eaa286017f53f88336dd0",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
|
||||||
26
flake.nix
26
flake.nix
|
|
@ -85,24 +85,18 @@
|
||||||
|
|
||||||
formatter = forAllSystems (system: nixpkgsFor.${system}.nixfmt-rfc-style);
|
formatter = forAllSystems (system: nixpkgsFor.${system}.nixfmt-rfc-style);
|
||||||
|
|
||||||
overlays.default =
|
overlays.default = final: prev: {
|
||||||
final: prev:
|
prismlauncher-unwrapped = prev.callPackage ./nix/unwrapped.nix {
|
||||||
let
|
inherit
|
||||||
version = builtins.substring 0 8 self.lastModifiedDate or "dirty";
|
libnbtplusplus
|
||||||
in
|
nix-filter
|
||||||
{
|
self
|
||||||
prismlauncher-unwrapped = prev.callPackage ./nix/unwrapped.nix {
|
;
|
||||||
inherit
|
|
||||||
libnbtplusplus
|
|
||||||
nix-filter
|
|
||||||
self
|
|
||||||
version
|
|
||||||
;
|
|
||||||
};
|
|
||||||
|
|
||||||
prismlauncher = final.callPackage ./nix/wrapper.nix { };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
prismlauncher = final.callPackage ./nix/wrapper.nix { };
|
||||||
|
};
|
||||||
|
|
||||||
packages = forAllSystems (
|
packages = forAllSystems (
|
||||||
system:
|
system:
|
||||||
let
|
let
|
||||||
|
|
|
||||||
20
flatpak/flite.json
Normal file
20
flatpak/flite.json
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"name": "flite",
|
||||||
|
"config-opts": [
|
||||||
|
"--enable-shared",
|
||||||
|
"--with-audio=pulseaudio"
|
||||||
|
],
|
||||||
|
"no-parallel-make": true,
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/festvox/flite.git",
|
||||||
|
"tag": "v2.2",
|
||||||
|
"commit": "e9e2e37c329dbe98bfeb27a1828ef9a71fa84f88",
|
||||||
|
"x-checker-data": {
|
||||||
|
"type": "git",
|
||||||
|
"tag-pattern": "^v([\\d.]+)$"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -1,22 +1,18 @@
|
||||||
{
|
{
|
||||||
"name": "libdecor",
|
"name": "libdecor",
|
||||||
"buildsystem": "meson",
|
"buildsystem": "meson",
|
||||||
"config-opts": [
|
"config-opts": [
|
||||||
"-Ddemo=false"
|
"-Ddemo=false"
|
||||||
],
|
],
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://gitlab.freedesktop.org/libdecor/libdecor.git",
|
"url": "https://gitlab.freedesktop.org/libdecor/libdecor.git",
|
||||||
"commit": "73260393a97291c887e1074ab7f318e031be0ac6"
|
"commit": "c2bd8ad6fa42c0cb17553ce77ad8a87d1f543b1f"
|
||||||
},
|
}
|
||||||
{
|
],
|
||||||
"type": "patch",
|
"cleanup": [
|
||||||
"path": "patches/weird_libdecor.patch"
|
"/include",
|
||||||
}
|
"/lib/pkgconfig"
|
||||||
],
|
]
|
||||||
"cleanup": [
|
|
||||||
"/include",
|
|
||||||
"/lib/pkgconfig"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
id: org.prismlauncher.PrismLauncher
|
id: org.prismlauncher.PrismLauncher
|
||||||
runtime: org.kde.Platform
|
runtime: org.kde.Platform
|
||||||
runtime-version: 6.7
|
runtime-version: '6.8'
|
||||||
sdk: org.kde.Sdk
|
sdk: org.kde.Sdk
|
||||||
sdk-extensions:
|
sdk-extensions:
|
||||||
- org.freedesktop.Sdk.Extension.openjdk17
|
- org.freedesktop.Sdk.Extension.openjdk17
|
||||||
|
|
@ -19,6 +19,12 @@ finish-args:
|
||||||
- --filesystem=xdg-download:ro
|
- --filesystem=xdg-download:ro
|
||||||
# FTBApp import
|
# FTBApp import
|
||||||
- --filesystem=~/.ftba:ro
|
- --filesystem=~/.ftba:ro
|
||||||
|
# Userspace visibility for manual hugepages configuration
|
||||||
|
# Required for -XX:+UseLargePages
|
||||||
|
- --filesystem=/sys/kernel/mm/hugepages:ro
|
||||||
|
# Userspace visibility for transparent hugepages configuration
|
||||||
|
# Required for -XX:+UseTransparentHugePages
|
||||||
|
- --filesystem=/sys/kernel/mm/transparent_hugepage:ro
|
||||||
|
|
||||||
modules:
|
modules:
|
||||||
# Might be needed by some Controller mods (see https://github.com/isXander/Controlify/issues/31)
|
# Might be needed by some Controller mods (see https://github.com/isXander/Controlify/issues/31)
|
||||||
|
|
@ -27,11 +33,16 @@ modules:
|
||||||
# Needed for proper Wayland support
|
# Needed for proper Wayland support
|
||||||
- libdecor.json
|
- libdecor.json
|
||||||
|
|
||||||
|
# Text to Speech in the game
|
||||||
|
- flite.json
|
||||||
|
|
||||||
- name: prismlauncher
|
- name: prismlauncher
|
||||||
buildsystem: cmake-ninja
|
buildsystem: cmake-ninja
|
||||||
builddir: true
|
builddir: true
|
||||||
config-opts:
|
config-opts:
|
||||||
- -DLauncher_BUILD_PLATFORM=flatpak
|
- -DLauncher_BUILD_PLATFORM=flatpak
|
||||||
|
# This allows us to manage and update Java independently of this Flatpak
|
||||||
|
- -DLauncher_ENABLE_JAVA_DOWNLOADER=ON
|
||||||
- -DCMAKE_BUILD_TYPE=RelWithDebInfo
|
- -DCMAKE_BUILD_TYPE=RelWithDebInfo
|
||||||
build-options:
|
build-options:
|
||||||
env:
|
env:
|
||||||
|
|
@ -47,18 +58,14 @@ modules:
|
||||||
config-opts:
|
config-opts:
|
||||||
- -DCMAKE_BUILD_TYPE=RelWithDebInfo
|
- -DCMAKE_BUILD_TYPE=RelWithDebInfo
|
||||||
- -DBUILD_SHARED_LIBS:BOOL=ON
|
- -DBUILD_SHARED_LIBS:BOOL=ON
|
||||||
- -DGLFW_USE_WAYLAND:BOOL=ON
|
- -DGLFW_BUILD_WAYLAND:BOOL=ON
|
||||||
- -DGLFW_BUILD_DOCS:BOOL=OFF
|
- -DGLFW_BUILD_DOCS:BOOL=OFF
|
||||||
sources:
|
sources:
|
||||||
- type: git
|
- type: git
|
||||||
url: https://github.com/glfw/glfw.git
|
url: https://github.com/glfw/glfw.git
|
||||||
commit: 3fa2360720eeba1964df3c0ecf4b5df8648a8e52
|
commit: 7b6aead9fb88b3623e3b3725ebb42670cbe4c579 # 3.4
|
||||||
- type: patch
|
- type: patch
|
||||||
path: patches/0003-Don-t-crash-on-calls-to-focus-or-icon.patch
|
path: patches/0009-Defer-setting-cursor-position-until-the-cursor-is-lo.patch
|
||||||
- type: patch
|
|
||||||
path: patches/0005-Add-warning-about-being-an-unofficial-patch.patch
|
|
||||||
- type: patch
|
|
||||||
path: patches/0007-Platform-Prefer-Wayland-over-X11.patch
|
|
||||||
cleanup:
|
cleanup:
|
||||||
- /include
|
- /include
|
||||||
- /lib/cmake
|
- /lib/cmake
|
||||||
|
|
@ -68,8 +75,8 @@ modules:
|
||||||
buildsystem: autotools
|
buildsystem: autotools
|
||||||
sources:
|
sources:
|
||||||
- type: archive
|
- type: archive
|
||||||
url: https://xorg.freedesktop.org/archive/individual/app/xrandr-1.5.2.tar.xz
|
url: https://xorg.freedesktop.org/archive/individual/app/xrandr-1.5.3.tar.xz
|
||||||
sha256: c8bee4790d9058bacc4b6246456c58021db58a87ddda1a9d0139bf5f18f1f240
|
sha256: f8dd7566adb74147fab9964680b6bbadee87cf406a7fcff51718a5e6949b841c
|
||||||
x-checker-data:
|
x-checker-data:
|
||||||
type: anitya
|
type: anitya
|
||||||
project-id: 14957
|
project-id: 14957
|
||||||
|
|
@ -91,8 +98,8 @@ modules:
|
||||||
sources:
|
sources:
|
||||||
- type: archive
|
- type: archive
|
||||||
dest-filename: gamemode.tar.gz
|
dest-filename: gamemode.tar.gz
|
||||||
url: https://api.github.com/repos/FeralInteractive/gamemode/tarball/1.8.1
|
url: https://api.github.com/repos/FeralInteractive/gamemode/tarball/1.8.2
|
||||||
sha256: 969cf85b5ca3944f3e315cd73a0ee9bea4f9c968cd7d485e9f4745bc1e679c4e
|
sha256: 2886d4ce543c78bd2a364316d5e7fd59ef06b71de63f896b37c6d3dc97658f60
|
||||||
x-checker-data:
|
x-checker-data:
|
||||||
type: json
|
type: json
|
||||||
url: https://api.github.com/repos/FeralInteractive/gamemode/releases/latest
|
url: https://api.github.com/repos/FeralInteractive/gamemode/releases/latest
|
||||||
|
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
diff --git a/src/wl_window.c b/src/wl_window.c
|
|
||||||
index 52d3b9eb..4ac4eb5d 100644
|
|
||||||
--- a/src/wl_window.c
|
|
||||||
+++ b/src/wl_window.c
|
|
||||||
@@ -2117,8 +2117,7 @@ void _glfwSetWindowTitleWayland(_GLFWwindow* window, const char* title)
|
|
||||||
void _glfwSetWindowIconWayland(_GLFWwindow* window,
|
|
||||||
int count, const GLFWimage* images)
|
|
||||||
{
|
|
||||||
- _glfwInputError(GLFW_FEATURE_UNAVAILABLE,
|
|
||||||
- "Wayland: The platform does not support setting the window icon");
|
|
||||||
+ fprintf(stderr, "!!! Ignoring Error: Wayland: The platform does not support setting the window icon\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
void _glfwGetWindowPosWayland(_GLFWwindow* window, int* xpos, int* ypos)
|
|
||||||
@@ -2361,8 +2360,7 @@ void _glfwRequestWindowAttentionWayland(_GLFWwindow* window)
|
|
||||||
|
|
||||||
void _glfwFocusWindowWayland(_GLFWwindow* window)
|
|
||||||
{
|
|
||||||
- _glfwInputError(GLFW_FEATURE_UNAVAILABLE,
|
|
||||||
- "Wayland: The platform does not support setting the input focus");
|
|
||||||
+ fprintf(stderr, "!!! Ignoring Error: Wayland: The platform does not support setting the input focus\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
void _glfwSetWindowMonitorWayland(_GLFWwindow* window,
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
diff --git a/src/init.c b/src/init.c
|
|
||||||
index 06dbb3f2..a7c6da86 100644
|
|
||||||
--- a/src/init.c
|
|
||||||
+++ b/src/init.c
|
|
||||||
@@ -449,6 +449,12 @@ GLFWAPI int glfwInit(void)
|
|
||||||
_glfw.initialized = GLFW_TRUE;
|
|
||||||
|
|
||||||
glfwDefaultWindowHints();
|
|
||||||
+
|
|
||||||
+ fprintf(stderr, "!!! Patched GLFW from https://github.com/Admicos/minecraft-wayland\n"
|
|
||||||
+ "!!! If any issues with the window, or some issues with rendering, occur, "
|
|
||||||
+ "first try with the built-in GLFW, and if that solves the issue, report there first.\n"
|
|
||||||
+ "!!! Use outside Minecraft is untested, and things might break.\n");
|
|
||||||
+
|
|
||||||
return GLFW_TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
diff --git a/src/platform.c b/src/platform.c
|
|
||||||
index c5966ae7..3e7442f9 100644
|
|
||||||
--- a/src/platform.c
|
|
||||||
+++ b/src/platform.c
|
|
||||||
@@ -49,12 +49,12 @@ static const struct
|
|
||||||
#if defined(_GLFW_COCOA)
|
|
||||||
{ GLFW_PLATFORM_COCOA, _glfwConnectCocoa },
|
|
||||||
#endif
|
|
||||||
-#if defined(_GLFW_X11)
|
|
||||||
- { GLFW_PLATFORM_X11, _glfwConnectX11 },
|
|
||||||
-#endif
|
|
||||||
#if defined(_GLFW_WAYLAND)
|
|
||||||
{ GLFW_PLATFORM_WAYLAND, _glfwConnectWayland },
|
|
||||||
#endif
|
|
||||||
+#if defined(_GLFW_X11)
|
|
||||||
+ { GLFW_PLATFORM_X11, _glfwConnectX11 },
|
|
||||||
+#endif
|
|
||||||
};
|
|
||||||
|
|
||||||
GLFWbool _glfwSelectPlatform(int desiredID, _GLFWplatform* platform)
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
From 9997ae55a47de469ea26f8437c30b51483abda5f Mon Sep 17 00:00:00 2001
|
||||||
|
From: Dan Klishch <danilklishch@gmail.com>
|
||||||
|
Date: Sat, 30 Sep 2023 23:38:05 -0400
|
||||||
|
Subject: Defer setting cursor position until the cursor is locked
|
||||||
|
|
||||||
|
---
|
||||||
|
src/wl_platform.h | 3 +++
|
||||||
|
src/wl_window.c | 14 ++++++++++++--
|
||||||
|
2 files changed, 15 insertions(+), 2 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/src/wl_platform.h b/src/wl_platform.h
|
||||||
|
index ca34f66e..cd1f227f 100644
|
||||||
|
--- a/src/wl_platform.h
|
||||||
|
+++ b/src/wl_platform.h
|
||||||
|
@@ -403,6 +403,9 @@ typedef struct _GLFWwindowWayland
|
||||||
|
int scaleSize;
|
||||||
|
int compositorPreferredScale;
|
||||||
|
|
||||||
|
+ double askedCursorPosX, askedCursorPosY;
|
||||||
|
+ GLFWbool didAskForSetCursorPos;
|
||||||
|
+
|
||||||
|
struct zwp_relative_pointer_v1* relativePointer;
|
||||||
|
struct zwp_locked_pointer_v1* lockedPointer;
|
||||||
|
struct zwp_confined_pointer_v1* confinedPointer;
|
||||||
|
diff --git a/src/wl_window.c b/src/wl_window.c
|
||||||
|
index 1de26558..0df16747 100644
|
||||||
|
--- a/src/wl_window.c
|
||||||
|
+++ b/src/wl_window.c
|
||||||
|
@@ -2586,8 +2586,9 @@ void _glfwGetCursorPosWayland(_GLFWwindow* window, double* xpos, double* ypos)
|
||||||
|
|
||||||
|
void _glfwSetCursorPosWayland(_GLFWwindow* window, double x, double y)
|
||||||
|
{
|
||||||
|
- _glfwInputError(GLFW_FEATURE_UNAVAILABLE,
|
||||||
|
- "Wayland: The platform does not support setting the cursor position");
|
||||||
|
+ window->wl.didAskForSetCursorPos = true;
|
||||||
|
+ window->wl.askedCursorPosX = x;
|
||||||
|
+ window->wl.askedCursorPosY = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _glfwSetCursorModeWayland(_GLFWwindow* window, int mode)
|
||||||
|
@@ -2819,6 +2820,15 @@ static const struct zwp_relative_pointer_v1_listener relativePointerListener =
|
||||||
|
static void lockedPointerHandleLocked(void* userData,
|
||||||
|
struct zwp_locked_pointer_v1* lockedPointer)
|
||||||
|
{
|
||||||
|
+ _GLFWwindow* window = userData;
|
||||||
|
+
|
||||||
|
+ if (window->wl.didAskForSetCursorPos)
|
||||||
|
+ {
|
||||||
|
+ window->wl.didAskForSetCursorPos = false;
|
||||||
|
+ zwp_locked_pointer_v1_set_cursor_position_hint(window->wl.lockedPointer,
|
||||||
|
+ wl_fixed_from_double(window->wl.askedCursorPosX),
|
||||||
|
+ wl_fixed_from_double(window->wl.askedCursorPosY));
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
|
||||||
|
static void lockedPointerHandleUnlocked(void* userData,
|
||||||
|
--
|
||||||
|
2.42.0
|
||||||
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
diff --git a/src/libdecor.c b/src/libdecor.c
|
|
||||||
index a9c1106..1aa38b3 100644
|
|
||||||
--- a/src/libdecor.c
|
|
||||||
+++ b/src/libdecor.c
|
|
||||||
@@ -1391,22 +1391,32 @@ calculate_priority(const struct libdecor_plugin_description *plugin_description)
|
|
||||||
static bool
|
|
||||||
check_symbol_conflicts(const struct libdecor_plugin_description *plugin_description)
|
|
||||||
{
|
|
||||||
+ bool ret = true;
|
|
||||||
char * const *symbol;
|
|
||||||
+ void* main_prog = dlopen(NULL, RTLD_LAZY);
|
|
||||||
+ if (!main_prog) {
|
|
||||||
+ fprintf(stderr, "Plugin \"%s\" couldn't check conflicting symbols: \"%s\".\n",
|
|
||||||
+ plugin_description->description, dlerror());
|
|
||||||
+ return false;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
|
|
||||||
symbol = plugin_description->conflicting_symbols;
|
|
||||||
while (*symbol) {
|
|
||||||
dlerror();
|
|
||||||
- dlsym (RTLD_DEFAULT, *symbol);
|
|
||||||
+ dlsym (main_prog, *symbol);
|
|
||||||
if (!dlerror()) {
|
|
||||||
fprintf(stderr, "Plugin \"%s\" uses conflicting symbol \"%s\".\n",
|
|
||||||
plugin_description->description, *symbol);
|
|
||||||
- return false;
|
|
||||||
+ ret = false;
|
|
||||||
+ break;
|
|
||||||
}
|
|
||||||
|
|
||||||
symbol++;
|
|
||||||
}
|
|
||||||
|
|
||||||
- return true;
|
|
||||||
+ dlclose(main_prog);
|
|
||||||
+ return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct plugin_loader *
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit f2b0c16a2a217a1822ce5a6538ba8f755ed1dd32
|
Subproject commit f5d368a31d6ef046eb2955c74ec6f54f32ed5c4e
|
||||||
|
|
@ -48,6 +48,7 @@
|
||||||
#include "net/PasteUpload.h"
|
#include "net/PasteUpload.h"
|
||||||
#include "pathmatcher/MultiMatcher.h"
|
#include "pathmatcher/MultiMatcher.h"
|
||||||
#include "pathmatcher/SimplePrefixMatcher.h"
|
#include "pathmatcher/SimplePrefixMatcher.h"
|
||||||
|
#include "tasks/Task.h"
|
||||||
#include "tools/GenericProfiler.h"
|
#include "tools/GenericProfiler.h"
|
||||||
#include "ui/InstanceWindow.h"
|
#include "ui/InstanceWindow.h"
|
||||||
#include "ui/MainWindow.h"
|
#include "ui/MainWindow.h"
|
||||||
|
|
@ -58,8 +59,6 @@
|
||||||
#include "ui/pages/BasePageProvider.h"
|
#include "ui/pages/BasePageProvider.h"
|
||||||
#include "ui/pages/global/APIPage.h"
|
#include "ui/pages/global/APIPage.h"
|
||||||
#include "ui/pages/global/AccountListPage.h"
|
#include "ui/pages/global/AccountListPage.h"
|
||||||
#include "ui/pages/global/CustomCommandsPage.h"
|
|
||||||
#include "ui/pages/global/EnvironmentVariablesPage.h"
|
|
||||||
#include "ui/pages/global/ExternalToolsPage.h"
|
#include "ui/pages/global/ExternalToolsPage.h"
|
||||||
#include "ui/pages/global/JavaPage.h"
|
#include "ui/pages/global/JavaPage.h"
|
||||||
#include "ui/pages/global/LanguagePage.h"
|
#include "ui/pages/global/LanguagePage.h"
|
||||||
|
|
@ -242,6 +241,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
||||||
{ { "s", "server" }, "Join the specified server on launch (only valid in combination with --launch)", "address" },
|
{ { "s", "server" }, "Join the specified server on launch (only valid in combination with --launch)", "address" },
|
||||||
{ { "w", "world" }, "Join the specified world on launch (only valid in combination with --launch)", "world" },
|
{ { "w", "world" }, "Join the specified world on launch (only valid in combination with --launch)", "world" },
|
||||||
{ { "a", "profile" }, "Use the account specified by its profile name (only valid in combination with --launch)", "profile" },
|
{ { "a", "profile" }, "Use the account specified by its profile name (only valid in combination with --launch)", "profile" },
|
||||||
|
{ { "o", "offline" }, "Launch offline, with given player name (only valid in combination with --launch)", "offline" },
|
||||||
{ "alive", "Write a small '" + liveCheckFile + "' file after the launcher starts" },
|
{ "alive", "Write a small '" + liveCheckFile + "' file after the launcher starts" },
|
||||||
{ { "I", "import" }, "Import instance or resource from specified local path or URL", "url" },
|
{ { "I", "import" }, "Import instance or resource from specified local path or URL", "url" },
|
||||||
{ "show", "Opens the window for the specified instance (by instance ID)", "show" } });
|
{ "show", "Opens the window for the specified instance (by instance ID)", "show" } });
|
||||||
|
|
@ -257,6 +257,10 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
||||||
m_serverToJoin = parser.value("server");
|
m_serverToJoin = parser.value("server");
|
||||||
m_worldToJoin = parser.value("world");
|
m_worldToJoin = parser.value("world");
|
||||||
m_profileToUse = parser.value("profile");
|
m_profileToUse = parser.value("profile");
|
||||||
|
if (parser.isSet("offline")) {
|
||||||
|
m_offline = true;
|
||||||
|
m_offlineName = parser.value("offline");
|
||||||
|
}
|
||||||
m_liveCheck = parser.isSet("alive");
|
m_liveCheck = parser.isSet("alive");
|
||||||
|
|
||||||
m_instanceIdToShowWindowOf = parser.value("show");
|
m_instanceIdToShowWindowOf = parser.value("show");
|
||||||
|
|
@ -271,8 +275,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
||||||
}
|
}
|
||||||
|
|
||||||
// error if --launch is missing with --server or --profile
|
// error if --launch is missing with --server or --profile
|
||||||
if (((!m_serverToJoin.isEmpty() || !m_worldToJoin.isEmpty()) || !m_profileToUse.isEmpty()) && m_instanceIdToLaunch.isEmpty()) {
|
if ((!m_serverToJoin.isEmpty() || !m_worldToJoin.isEmpty() || !m_profileToUse.isEmpty() || m_offline) &&
|
||||||
std::cerr << "--server and --profile can only be used in combination with --launch!" << std::endl;
|
m_instanceIdToLaunch.isEmpty()) {
|
||||||
|
std::cerr << "--server, --profile and --offline can only be used in combination with --launch!" << std::endl;
|
||||||
m_status = Application::Failed;
|
m_status = Application::Failed;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -397,6 +402,10 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
||||||
if (!m_profileToUse.isEmpty()) {
|
if (!m_profileToUse.isEmpty()) {
|
||||||
launch.args["profile"] = m_profileToUse;
|
launch.args["profile"] = m_profileToUse;
|
||||||
}
|
}
|
||||||
|
if (m_offline) {
|
||||||
|
launch.args["offline_enabled"] = "true";
|
||||||
|
launch.args["offline_name"] = m_offlineName;
|
||||||
|
}
|
||||||
m_peerInstance->sendMessage(launch.serialize(), timeout);
|
m_peerInstance->sendMessage(launch.serialize(), timeout);
|
||||||
}
|
}
|
||||||
m_status = Application::Succeeded;
|
m_status = Application::Succeeded;
|
||||||
|
|
@ -605,6 +614,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
||||||
m_settings->registerSetting("IconsDir", "icons");
|
m_settings->registerSetting("IconsDir", "icons");
|
||||||
m_settings->registerSetting("DownloadsDir", QStandardPaths::writableLocation(QStandardPaths::DownloadLocation));
|
m_settings->registerSetting("DownloadsDir", QStandardPaths::writableLocation(QStandardPaths::DownloadLocation));
|
||||||
m_settings->registerSetting("DownloadsDirWatchRecursive", false);
|
m_settings->registerSetting("DownloadsDirWatchRecursive", false);
|
||||||
|
m_settings->registerSetting("MoveModsFromDownloadsDir", false);
|
||||||
m_settings->registerSetting("SkinsDir", "skins");
|
m_settings->registerSetting("SkinsDir", "skins");
|
||||||
m_settings->registerSetting("JavaDir", "java");
|
m_settings->registerSetting("JavaDir", "java");
|
||||||
|
|
||||||
|
|
@ -790,8 +800,6 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
||||||
m_globalSettingsProvider->addPage<MinecraftPage>();
|
m_globalSettingsProvider->addPage<MinecraftPage>();
|
||||||
m_globalSettingsProvider->addPage<JavaPage>();
|
m_globalSettingsProvider->addPage<JavaPage>();
|
||||||
m_globalSettingsProvider->addPage<LanguagePage>();
|
m_globalSettingsProvider->addPage<LanguagePage>();
|
||||||
m_globalSettingsProvider->addPage<CustomCommandsPage>();
|
|
||||||
m_globalSettingsProvider->addPage<EnvironmentVariablesPage>();
|
|
||||||
m_globalSettingsProvider->addPage<ProxyPage>();
|
m_globalSettingsProvider->addPage<ProxyPage>();
|
||||||
m_globalSettingsProvider->addPage<ExternalToolsPage>();
|
m_globalSettingsProvider->addPage<ExternalToolsPage>();
|
||||||
m_globalSettingsProvider->addPage<AccountListPage>();
|
m_globalSettingsProvider->addPage<AccountListPage>();
|
||||||
|
|
@ -835,7 +843,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
||||||
":/icons/multimc/128x128/instances/", ":/icons/multimc/scalable/instances/" };
|
":/icons/multimc/128x128/instances/", ":/icons/multimc/scalable/instances/" };
|
||||||
m_icons.reset(new IconList(instFolders, setting->get().toString()));
|
m_icons.reset(new IconList(instFolders, setting->get().toString()));
|
||||||
connect(setting.get(), &Setting::SettingChanged,
|
connect(setting.get(), &Setting::SettingChanged,
|
||||||
[&](const Setting&, QVariant value) { m_icons->directoryChanged(value.toString()); });
|
[this](const Setting&, QVariant value) { m_icons->directoryChanged(value.toString()); });
|
||||||
qDebug() << "<> Instance icons initialized.";
|
qDebug() << "<> Instance icons initialized.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1070,8 +1078,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
||||||
|
|
||||||
bool Application::createSetupWizard()
|
bool Application::createSetupWizard()
|
||||||
{
|
{
|
||||||
bool javaRequired = [&]() {
|
bool javaRequired = [this]() {
|
||||||
bool ignoreJavaWizard = m_settings->get("IgnoreJavaWizard").toBool();
|
if (BuildConfig.JAVA_DOWNLOADER_ENABLED && settings()->get("AutomaticJavaDownload").toBool()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool ignoreJavaWizard = settings()->get("IgnoreJavaWizard").toBool();
|
||||||
if (ignoreJavaWizard) {
|
if (ignoreJavaWizard) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -1083,13 +1094,10 @@ bool Application::createSetupWizard()
|
||||||
}
|
}
|
||||||
QString currentJavaPath = settings()->get("JavaPath").toString();
|
QString currentJavaPath = settings()->get("JavaPath").toString();
|
||||||
QString actualPath = FS::ResolveExecutable(currentJavaPath);
|
QString actualPath = FS::ResolveExecutable(currentJavaPath);
|
||||||
if (actualPath.isNull()) {
|
return actualPath.isNull();
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}();
|
}();
|
||||||
bool askjava = BuildConfig.JAVA_DOWNLOADER_ENABLED && !javaRequired && !m_settings->get("AutomaticJavaDownload").toBool() &&
|
bool askjava = BuildConfig.JAVA_DOWNLOADER_ENABLED && !javaRequired && !settings()->get("AutomaticJavaDownload").toBool() &&
|
||||||
!m_settings->get("AutomaticJavaSwitch").toBool() && !m_settings->get("UserAskedAboutAutomaticJavaDownload").toBool();
|
!settings()->get("AutomaticJavaSwitch").toBool() && !settings()->get("UserAskedAboutAutomaticJavaDownload").toBool();
|
||||||
bool languageRequired = settings()->get("Language").toString().isEmpty();
|
bool languageRequired = settings()->get("Language").toString().isEmpty();
|
||||||
bool pasteInterventionRequired = settings()->get("PastebinURL") != "";
|
bool pasteInterventionRequired = settings()->get("PastebinURL") != "";
|
||||||
bool validWidgets = m_themeManager->isValidApplicationTheme(settings()->get("ApplicationTheme").toString());
|
bool validWidgets = m_themeManager->isValidApplicationTheme(settings()->get("ApplicationTheme").toString());
|
||||||
|
|
@ -1209,7 +1217,7 @@ void Application::performMainStartupAction()
|
||||||
qDebug() << " Launching with account" << m_profileToUse;
|
qDebug() << " Launching with account" << m_profileToUse;
|
||||||
}
|
}
|
||||||
|
|
||||||
launch(inst, true, false, targetToJoin, accountToUse);
|
launch(inst, !m_offline, false, targetToJoin, accountToUse, m_offlineName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1308,6 +1316,8 @@ void Application::messageReceived(const QByteArray& message)
|
||||||
QString server = received.args["server"];
|
QString server = received.args["server"];
|
||||||
QString world = received.args["world"];
|
QString world = received.args["world"];
|
||||||
QString profile = received.args["profile"];
|
QString profile = received.args["profile"];
|
||||||
|
bool offline = received.args["offline_enabled"] == "true";
|
||||||
|
QString offlineName = received.args["offline_name"];
|
||||||
|
|
||||||
InstancePtr instance;
|
InstancePtr instance;
|
||||||
if (!id.isEmpty()) {
|
if (!id.isEmpty()) {
|
||||||
|
|
@ -1337,7 +1347,7 @@ void Application::messageReceived(const QByteArray& message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
launch(instance, true, false, serverObject, accountObject);
|
launch(instance, !offline, false, serverObject, accountObject, offlineName);
|
||||||
} else {
|
} else {
|
||||||
qWarning() << "Received invalid message" << message;
|
qWarning() << "Received invalid message" << message;
|
||||||
}
|
}
|
||||||
|
|
@ -1375,11 +1385,17 @@ bool Application::openJsonEditor(const QString& filename)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Application::launch(InstancePtr instance, bool online, bool demo, MinecraftTarget::Ptr targetToJoin, MinecraftAccountPtr accountToUse)
|
bool Application::launch(InstancePtr instance,
|
||||||
|
bool online,
|
||||||
|
bool demo,
|
||||||
|
MinecraftTarget::Ptr targetToJoin,
|
||||||
|
MinecraftAccountPtr accountToUse,
|
||||||
|
const QString& offlineName)
|
||||||
{
|
{
|
||||||
if (m_updateRunning) {
|
if (m_updateRunning) {
|
||||||
qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed.";
|
qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed.";
|
||||||
} else if (instance->canLaunch()) {
|
} else if (instance->canLaunch()) {
|
||||||
|
QMutexLocker locker(&m_instanceExtrasMutex);
|
||||||
auto& extras = m_instanceExtras[instance->id()];
|
auto& extras = m_instanceExtras[instance->id()];
|
||||||
auto window = extras.window;
|
auto window = extras.window;
|
||||||
if (window) {
|
if (window) {
|
||||||
|
|
@ -1395,6 +1411,7 @@ bool Application::launch(InstancePtr instance, bool online, bool demo, Minecraft
|
||||||
controller->setProfiler(profilers().value(instance->settings()->get("Profiler").toString(), nullptr).get());
|
controller->setProfiler(profilers().value(instance->settings()->get("Profiler").toString(), nullptr).get());
|
||||||
controller->setTargetToJoin(targetToJoin);
|
controller->setTargetToJoin(targetToJoin);
|
||||||
controller->setAccountToUse(accountToUse);
|
controller->setAccountToUse(accountToUse);
|
||||||
|
controller->setOfflineName(offlineName);
|
||||||
if (window) {
|
if (window) {
|
||||||
controller->setParentWidget(window);
|
controller->setParentWidget(window);
|
||||||
} else if (m_mainWindow) {
|
} else if (m_mainWindow) {
|
||||||
|
|
@ -1404,7 +1421,7 @@ bool Application::launch(InstancePtr instance, bool online, bool demo, Minecraft
|
||||||
connect(controller.get(), &LaunchController::failed, this, &Application::controllerFailed);
|
connect(controller.get(), &LaunchController::failed, this, &Application::controllerFailed);
|
||||||
connect(controller.get(), &LaunchController::aborted, this, [this] { controllerFailed(tr("Aborted")); });
|
connect(controller.get(), &LaunchController::aborted, this, [this] { controllerFailed(tr("Aborted")); });
|
||||||
addRunningInstance();
|
addRunningInstance();
|
||||||
controller->start();
|
QMetaObject::invokeMethod(controller.get(), &Task::start, Qt::QueuedConnection);
|
||||||
return true;
|
return true;
|
||||||
} else if (instance->isRunning()) {
|
} else if (instance->isRunning()) {
|
||||||
showInstanceWindow(instance, "console");
|
showInstanceWindow(instance, "console");
|
||||||
|
|
@ -1422,9 +1439,11 @@ bool Application::kill(InstancePtr instance)
|
||||||
qWarning() << "Attempted to kill instance" << instance->id() << ", which isn't running.";
|
qWarning() << "Attempted to kill instance" << instance->id() << ", which isn't running.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
QMutexLocker locker(&m_instanceExtrasMutex);
|
||||||
auto& extras = m_instanceExtras[instance->id()];
|
auto& extras = m_instanceExtras[instance->id()];
|
||||||
// NOTE: copy of the shared pointer keeps it alive
|
// NOTE: copy of the shared pointer keeps it alive
|
||||||
auto controller = extras.controller;
|
auto controller = extras.controller;
|
||||||
|
locker.unlock();
|
||||||
if (controller) {
|
if (controller) {
|
||||||
return controller->abort();
|
return controller->abort();
|
||||||
}
|
}
|
||||||
|
|
@ -1478,12 +1497,14 @@ void Application::controllerSucceeded()
|
||||||
if (!controller)
|
if (!controller)
|
||||||
return;
|
return;
|
||||||
auto id = controller->id();
|
auto id = controller->id();
|
||||||
|
|
||||||
|
QMutexLocker locker(&m_instanceExtrasMutex);
|
||||||
auto& extras = m_instanceExtras[id];
|
auto& extras = m_instanceExtras[id];
|
||||||
|
|
||||||
// on success, do...
|
// on success, do...
|
||||||
if (controller->instance()->settings()->get("AutoCloseConsole").toBool()) {
|
if (controller->instance()->settings()->get("AutoCloseConsole").toBool()) {
|
||||||
if (extras.window) {
|
if (extras.window) {
|
||||||
extras.window->close();
|
QMetaObject::invokeMethod(extras.window, &QWidget::close, Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
extras.controller.reset();
|
extras.controller.reset();
|
||||||
|
|
@ -1503,6 +1524,7 @@ void Application::controllerFailed(const QString& error)
|
||||||
if (!controller)
|
if (!controller)
|
||||||
return;
|
return;
|
||||||
auto id = controller->id();
|
auto id = controller->id();
|
||||||
|
QMutexLocker locker(&m_instanceExtrasMutex);
|
||||||
auto& extras = m_instanceExtras[id];
|
auto& extras = m_instanceExtras[id];
|
||||||
|
|
||||||
// on failure, do... nothing
|
// on failure, do... nothing
|
||||||
|
|
@ -1560,6 +1582,7 @@ InstanceWindow* Application::showInstanceWindow(InstancePtr instance, QString pa
|
||||||
if (!instance)
|
if (!instance)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
auto id = instance->id();
|
auto id = instance->id();
|
||||||
|
QMutexLocker locker(&m_instanceExtrasMutex);
|
||||||
auto& extras = m_instanceExtras[id];
|
auto& extras = m_instanceExtras[id];
|
||||||
auto& window = extras.window;
|
auto& window = extras.window;
|
||||||
|
|
||||||
|
|
@ -1597,6 +1620,7 @@ void Application::on_windowClose()
|
||||||
m_openWindows--;
|
m_openWindows--;
|
||||||
auto instWindow = qobject_cast<InstanceWindow*>(QObject::sender());
|
auto instWindow = qobject_cast<InstanceWindow*>(QObject::sender());
|
||||||
if (instWindow) {
|
if (instWindow) {
|
||||||
|
QMutexLocker locker(&m_instanceExtrasMutex);
|
||||||
auto& extras = m_instanceExtras[instWindow->instanceId()];
|
auto& extras = m_instanceExtras[instWindow->instanceId()];
|
||||||
extras.window = nullptr;
|
extras.window = nullptr;
|
||||||
if (extras.controller) {
|
if (extras.controller) {
|
||||||
|
|
@ -1844,7 +1868,7 @@ bool Application::handleDataMigration(const QString& currentData,
|
||||||
matcher->add(std::make_shared<SimplePrefixMatcher>("themes/"));
|
matcher->add(std::make_shared<SimplePrefixMatcher>("themes/"));
|
||||||
|
|
||||||
ProgressDialog diag;
|
ProgressDialog diag;
|
||||||
DataMigrationTask task(nullptr, oldData, currentData, matcher);
|
DataMigrationTask task(oldData, currentData, matcher);
|
||||||
if (diag.execWithTask(&task)) {
|
if (diag.execWithTask(&task)) {
|
||||||
qDebug() << "<> Migration succeeded";
|
qDebug() << "<> Migration succeeded";
|
||||||
setDoNotMigrate();
|
setDoNotMigrate();
|
||||||
|
|
@ -1883,3 +1907,31 @@ const QString Application::javaPath()
|
||||||
{
|
{
|
||||||
return m_settings->get("JavaDir").toString();
|
return m_settings->get("JavaDir").toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::addQSavePath(QString path)
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&m_qsaveResourcesMutex);
|
||||||
|
m_qsaveResources[path] = m_qsaveResources.value(path, 0) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::removeQSavePath(QString path)
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&m_qsaveResourcesMutex);
|
||||||
|
auto count = m_qsaveResources.value(path, 0) - 1;
|
||||||
|
if (count <= 0) {
|
||||||
|
m_qsaveResources.remove(path);
|
||||||
|
} else {
|
||||||
|
m_qsaveResources[path] = count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Application::checkQSavePath(QString path)
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&m_qsaveResourcesMutex);
|
||||||
|
for (auto partialPath : m_qsaveResources.keys()) {
|
||||||
|
if (path.startsWith(partialPath) && m_qsaveResources.value(partialPath, 0) > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
@ -42,6 +42,7 @@
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QFlag>
|
#include <QFlag>
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
|
#include <QMutex>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
|
@ -210,7 +211,8 @@ class Application : public QApplication {
|
||||||
bool online = true,
|
bool online = true,
|
||||||
bool demo = false,
|
bool demo = false,
|
||||||
MinecraftTarget::Ptr targetToJoin = nullptr,
|
MinecraftTarget::Ptr targetToJoin = nullptr,
|
||||||
MinecraftAccountPtr accountToUse = nullptr);
|
MinecraftAccountPtr accountToUse = nullptr,
|
||||||
|
const QString& offlineName = QString());
|
||||||
bool kill(InstancePtr instance);
|
bool kill(InstancePtr instance);
|
||||||
void closeCurrentWindow();
|
void closeCurrentWindow();
|
||||||
|
|
||||||
|
|
@ -278,6 +280,7 @@ class Application : public QApplication {
|
||||||
shared_qobject_ptr<LaunchController> controller;
|
shared_qobject_ptr<LaunchController> controller;
|
||||||
};
|
};
|
||||||
std::map<QString, InstanceXtras> m_instanceExtras;
|
std::map<QString, InstanceXtras> m_instanceExtras;
|
||||||
|
mutable QMutex m_instanceExtrasMutex;
|
||||||
|
|
||||||
// main state variables
|
// main state variables
|
||||||
size_t m_openWindows = 0;
|
size_t m_openWindows = 0;
|
||||||
|
|
@ -299,8 +302,19 @@ class Application : public QApplication {
|
||||||
QString m_serverToJoin;
|
QString m_serverToJoin;
|
||||||
QString m_worldToJoin;
|
QString m_worldToJoin;
|
||||||
QString m_profileToUse;
|
QString m_profileToUse;
|
||||||
|
bool m_offline = false;
|
||||||
|
QString m_offlineName;
|
||||||
bool m_liveCheck = false;
|
bool m_liveCheck = false;
|
||||||
QList<QUrl> m_urlsToImport;
|
QList<QUrl> m_urlsToImport;
|
||||||
QString m_instanceIdToShowWindowOf;
|
QString m_instanceIdToShowWindowOf;
|
||||||
std::unique_ptr<QFile> logFile;
|
std::unique_ptr<QFile> logFile;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void addQSavePath(QString);
|
||||||
|
void removeQSavePath(QString);
|
||||||
|
bool checkQSavePath(QString);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QHash<QString, int> m_qsaveResources;
|
||||||
|
mutable QMutex m_qsaveResourcesMutex;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -411,3 +411,8 @@ void BaseInstance::updateRuntimeContext()
|
||||||
{
|
{
|
||||||
// NOOP
|
// NOOP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool BaseInstance::isLegacy()
|
||||||
|
{
|
||||||
|
return traits().contains("legacyLaunch") || traits().contains("alphaLaunch");
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -269,6 +269,8 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
|
||||||
bool removeLinkedInstanceId(const QString& id);
|
bool removeLinkedInstanceId(const QString& id);
|
||||||
bool isLinkedToInstanceId(const QString& id) const;
|
bool isLinkedToInstanceId(const QString& id) const;
|
||||||
|
|
||||||
|
bool isLegacy();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void changeStatus(Status newStatus);
|
void changeStatus(Status newStatus);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ set(CORE_SOURCES
|
||||||
StringUtils.cpp
|
StringUtils.cpp
|
||||||
QVariantUtils.h
|
QVariantUtils.h
|
||||||
RuntimeContext.h
|
RuntimeContext.h
|
||||||
|
PSaveFile.h
|
||||||
|
|
||||||
# Basic instance manipulation tasks (derived from InstanceTask)
|
# Basic instance manipulation tasks (derived from InstanceTask)
|
||||||
InstanceCreationTask.h
|
InstanceCreationTask.h
|
||||||
|
|
@ -344,13 +345,12 @@ set(MINECRAFT_SOURCES
|
||||||
minecraft/mod/TexturePackFolderModel.h
|
minecraft/mod/TexturePackFolderModel.h
|
||||||
minecraft/mod/TexturePackFolderModel.cpp
|
minecraft/mod/TexturePackFolderModel.cpp
|
||||||
minecraft/mod/ShaderPackFolderModel.h
|
minecraft/mod/ShaderPackFolderModel.h
|
||||||
minecraft/mod/tasks/BasicFolderLoadTask.h
|
minecraft/mod/tasks/ResourceFolderLoadTask.h
|
||||||
minecraft/mod/tasks/ModFolderLoadTask.h
|
minecraft/mod/tasks/ResourceFolderLoadTask.cpp
|
||||||
minecraft/mod/tasks/ModFolderLoadTask.cpp
|
|
||||||
minecraft/mod/tasks/LocalModParseTask.h
|
minecraft/mod/tasks/LocalModParseTask.h
|
||||||
minecraft/mod/tasks/LocalModParseTask.cpp
|
minecraft/mod/tasks/LocalModParseTask.cpp
|
||||||
minecraft/mod/tasks/LocalModUpdateTask.h
|
minecraft/mod/tasks/LocalResourceUpdateTask.h
|
||||||
minecraft/mod/tasks/LocalModUpdateTask.cpp
|
minecraft/mod/tasks/LocalResourceUpdateTask.cpp
|
||||||
minecraft/mod/tasks/LocalDataPackParseTask.h
|
minecraft/mod/tasks/LocalDataPackParseTask.h
|
||||||
minecraft/mod/tasks/LocalDataPackParseTask.cpp
|
minecraft/mod/tasks/LocalDataPackParseTask.cpp
|
||||||
minecraft/mod/tasks/LocalResourcePackParseTask.h
|
minecraft/mod/tasks/LocalResourcePackParseTask.h
|
||||||
|
|
@ -908,7 +908,6 @@ SET(LAUNCHER_SOURCES
|
||||||
ui/pages/instance/NotesPage.h
|
ui/pages/instance/NotesPage.h
|
||||||
ui/pages/instance/LogPage.cpp
|
ui/pages/instance/LogPage.cpp
|
||||||
ui/pages/instance/LogPage.h
|
ui/pages/instance/LogPage.h
|
||||||
ui/pages/instance/InstanceSettingsPage.cpp
|
|
||||||
ui/pages/instance/InstanceSettingsPage.h
|
ui/pages/instance/InstanceSettingsPage.h
|
||||||
ui/pages/instance/ScreenshotsPage.cpp
|
ui/pages/instance/ScreenshotsPage.cpp
|
||||||
ui/pages/instance/ScreenshotsPage.h
|
ui/pages/instance/ScreenshotsPage.h
|
||||||
|
|
@ -922,17 +921,12 @@ SET(LAUNCHER_SOURCES
|
||||||
# GUI - global settings pages
|
# GUI - global settings pages
|
||||||
ui/pages/global/AccountListPage.cpp
|
ui/pages/global/AccountListPage.cpp
|
||||||
ui/pages/global/AccountListPage.h
|
ui/pages/global/AccountListPage.h
|
||||||
ui/pages/global/CustomCommandsPage.cpp
|
|
||||||
ui/pages/global/CustomCommandsPage.h
|
|
||||||
ui/pages/global/EnvironmentVariablesPage.cpp
|
|
||||||
ui/pages/global/EnvironmentVariablesPage.h
|
|
||||||
ui/pages/global/ExternalToolsPage.cpp
|
ui/pages/global/ExternalToolsPage.cpp
|
||||||
ui/pages/global/ExternalToolsPage.h
|
ui/pages/global/ExternalToolsPage.h
|
||||||
ui/pages/global/JavaPage.cpp
|
ui/pages/global/JavaPage.cpp
|
||||||
ui/pages/global/JavaPage.h
|
ui/pages/global/JavaPage.h
|
||||||
ui/pages/global/LanguagePage.cpp
|
ui/pages/global/LanguagePage.cpp
|
||||||
ui/pages/global/LanguagePage.h
|
ui/pages/global/LanguagePage.h
|
||||||
ui/pages/global/MinecraftPage.cpp
|
|
||||||
ui/pages/global/MinecraftPage.h
|
ui/pages/global/MinecraftPage.h
|
||||||
ui/pages/global/LauncherPage.cpp
|
ui/pages/global/LauncherPage.cpp
|
||||||
ui/pages/global/LauncherPage.h
|
ui/pages/global/LauncherPage.h
|
||||||
|
|
@ -1030,8 +1024,6 @@ SET(LAUNCHER_SOURCES
|
||||||
ui/dialogs/CopyInstanceDialog.h
|
ui/dialogs/CopyInstanceDialog.h
|
||||||
ui/dialogs/CustomMessageBox.cpp
|
ui/dialogs/CustomMessageBox.cpp
|
||||||
ui/dialogs/CustomMessageBox.h
|
ui/dialogs/CustomMessageBox.h
|
||||||
ui/dialogs/EditAccountDialog.cpp
|
|
||||||
ui/dialogs/EditAccountDialog.h
|
|
||||||
ui/dialogs/ExportInstanceDialog.cpp
|
ui/dialogs/ExportInstanceDialog.cpp
|
||||||
ui/dialogs/ExportInstanceDialog.h
|
ui/dialogs/ExportInstanceDialog.h
|
||||||
ui/dialogs/ExportPackDialog.cpp
|
ui/dialogs/ExportPackDialog.cpp
|
||||||
|
|
@ -1068,8 +1060,8 @@ SET(LAUNCHER_SOURCES
|
||||||
ui/dialogs/BlockedModsDialog.h
|
ui/dialogs/BlockedModsDialog.h
|
||||||
ui/dialogs/ChooseProviderDialog.h
|
ui/dialogs/ChooseProviderDialog.h
|
||||||
ui/dialogs/ChooseProviderDialog.cpp
|
ui/dialogs/ChooseProviderDialog.cpp
|
||||||
ui/dialogs/ModUpdateDialog.cpp
|
ui/dialogs/ResourceUpdateDialog.cpp
|
||||||
ui/dialogs/ModUpdateDialog.h
|
ui/dialogs/ResourceUpdateDialog.h
|
||||||
ui/dialogs/InstallLoaderDialog.cpp
|
ui/dialogs/InstallLoaderDialog.cpp
|
||||||
ui/dialogs/InstallLoaderDialog.h
|
ui/dialogs/InstallLoaderDialog.h
|
||||||
|
|
||||||
|
|
@ -1085,14 +1077,12 @@ SET(LAUNCHER_SOURCES
|
||||||
ui/widgets/CustomCommands.h
|
ui/widgets/CustomCommands.h
|
||||||
ui/widgets/EnvironmentVariables.cpp
|
ui/widgets/EnvironmentVariables.cpp
|
||||||
ui/widgets/EnvironmentVariables.h
|
ui/widgets/EnvironmentVariables.h
|
||||||
ui/widgets/DropLabel.cpp
|
|
||||||
ui/widgets/DropLabel.h
|
|
||||||
ui/widgets/FocusLineEdit.cpp
|
ui/widgets/FocusLineEdit.cpp
|
||||||
ui/widgets/FocusLineEdit.h
|
ui/widgets/FocusLineEdit.h
|
||||||
ui/widgets/IconLabel.cpp
|
ui/widgets/IconLabel.cpp
|
||||||
ui/widgets/IconLabel.h
|
ui/widgets/IconLabel.h
|
||||||
ui/widgets/JavaSettingsWidget.cpp
|
ui/widgets/JavaWizardWidget.cpp
|
||||||
ui/widgets/JavaSettingsWidget.h
|
ui/widgets/JavaWizardWidget.h
|
||||||
ui/widgets/LabeledToolButton.cpp
|
ui/widgets/LabeledToolButton.cpp
|
||||||
ui/widgets/LabeledToolButton.h
|
ui/widgets/LabeledToolButton.h
|
||||||
ui/widgets/LanguageSelectionWidget.cpp
|
ui/widgets/LanguageSelectionWidget.cpp
|
||||||
|
|
@ -1128,6 +1118,10 @@ SET(LAUNCHER_SOURCES
|
||||||
ui/widgets/WideBar.cpp
|
ui/widgets/WideBar.cpp
|
||||||
ui/widgets/ThemeCustomizationWidget.h
|
ui/widgets/ThemeCustomizationWidget.h
|
||||||
ui/widgets/ThemeCustomizationWidget.cpp
|
ui/widgets/ThemeCustomizationWidget.cpp
|
||||||
|
ui/widgets/MinecraftSettingsWidget.h
|
||||||
|
ui/widgets/MinecraftSettingsWidget.cpp
|
||||||
|
ui/widgets/JavaSettingsWidget.h
|
||||||
|
ui/widgets/JavaSettingsWidget.cpp
|
||||||
|
|
||||||
# GUI - instance group view
|
# GUI - instance group view
|
||||||
ui/instanceview/InstanceProxyModel.cpp
|
ui/instanceview/InstanceProxyModel.cpp
|
||||||
|
|
@ -1171,7 +1165,6 @@ qt_wrap_ui(LAUNCHER_UI
|
||||||
ui/pages/global/LauncherPage.ui
|
ui/pages/global/LauncherPage.ui
|
||||||
ui/pages/global/APIPage.ui
|
ui/pages/global/APIPage.ui
|
||||||
ui/pages/global/ProxyPage.ui
|
ui/pages/global/ProxyPage.ui
|
||||||
ui/pages/global/MinecraftPage.ui
|
|
||||||
ui/pages/global/ExternalToolsPage.ui
|
ui/pages/global/ExternalToolsPage.ui
|
||||||
ui/pages/instance/ExternalResourcesPage.ui
|
ui/pages/instance/ExternalResourcesPage.ui
|
||||||
ui/pages/instance/NotesPage.ui
|
ui/pages/instance/NotesPage.ui
|
||||||
|
|
@ -1179,7 +1172,6 @@ qt_wrap_ui(LAUNCHER_UI
|
||||||
ui/pages/instance/ServersPage.ui
|
ui/pages/instance/ServersPage.ui
|
||||||
ui/pages/instance/GameOptionsPage.ui
|
ui/pages/instance/GameOptionsPage.ui
|
||||||
ui/pages/instance/OtherLogsPage.ui
|
ui/pages/instance/OtherLogsPage.ui
|
||||||
ui/pages/instance/InstanceSettingsPage.ui
|
|
||||||
ui/pages/instance/VersionPage.ui
|
ui/pages/instance/VersionPage.ui
|
||||||
ui/pages/instance/ManagedPackPage.ui
|
ui/pages/instance/ManagedPackPage.ui
|
||||||
ui/pages/instance/WorldListPage.ui
|
ui/pages/instance/WorldListPage.ui
|
||||||
|
|
@ -1202,6 +1194,8 @@ qt_wrap_ui(LAUNCHER_UI
|
||||||
ui/widgets/ModFilterWidget.ui
|
ui/widgets/ModFilterWidget.ui
|
||||||
ui/widgets/SubTaskProgressBar.ui
|
ui/widgets/SubTaskProgressBar.ui
|
||||||
ui/widgets/ThemeCustomizationWidget.ui
|
ui/widgets/ThemeCustomizationWidget.ui
|
||||||
|
ui/widgets/MinecraftSettingsWidget.ui
|
||||||
|
ui/widgets/JavaSettingsWidget.ui
|
||||||
ui/dialogs/CopyInstanceDialog.ui
|
ui/dialogs/CopyInstanceDialog.ui
|
||||||
ui/dialogs/ProfileSetupDialog.ui
|
ui/dialogs/ProfileSetupDialog.ui
|
||||||
ui/dialogs/ProgressDialog.ui
|
ui/dialogs/ProgressDialog.ui
|
||||||
|
|
@ -1217,12 +1211,10 @@ qt_wrap_ui(LAUNCHER_UI
|
||||||
ui/dialogs/MSALoginDialog.ui
|
ui/dialogs/MSALoginDialog.ui
|
||||||
ui/dialogs/OfflineLoginDialog.ui
|
ui/dialogs/OfflineLoginDialog.ui
|
||||||
ui/dialogs/AboutDialog.ui
|
ui/dialogs/AboutDialog.ui
|
||||||
ui/dialogs/EditAccountDialog.ui
|
|
||||||
ui/dialogs/ReviewMessageBox.ui
|
ui/dialogs/ReviewMessageBox.ui
|
||||||
ui/dialogs/ScrollMessageBox.ui
|
ui/dialogs/ScrollMessageBox.ui
|
||||||
ui/dialogs/BlockedModsDialog.ui
|
ui/dialogs/BlockedModsDialog.ui
|
||||||
ui/dialogs/ChooseProviderDialog.ui
|
ui/dialogs/ChooseProviderDialog.ui
|
||||||
|
|
||||||
ui/dialogs/skins/SkinManageDialog.ui
|
ui/dialogs/skins/SkinManageDialog.ui
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1297,6 +1289,7 @@ target_link_libraries(Launcher_logic
|
||||||
Qt${QT_VERSION_MAJOR}::Gui
|
Qt${QT_VERSION_MAJOR}::Gui
|
||||||
Qt${QT_VERSION_MAJOR}::Widgets
|
Qt${QT_VERSION_MAJOR}::Widgets
|
||||||
Qt${QT_VERSION_MAJOR}::NetworkAuth
|
Qt${QT_VERSION_MAJOR}::NetworkAuth
|
||||||
|
${Launcher_QT_DBUS}
|
||||||
${Launcher_QT_LIBS}
|
${Launcher_QT_LIBS}
|
||||||
)
|
)
|
||||||
target_link_libraries(Launcher_logic
|
target_link_libraries(Launcher_logic
|
||||||
|
|
@ -1305,6 +1298,10 @@ target_link_libraries(Launcher_logic
|
||||||
LocalPeer
|
LocalPeer
|
||||||
Launcher_rainbow
|
Launcher_rainbow
|
||||||
)
|
)
|
||||||
|
if (TARGET ${Launcher_QT_DBUS})
|
||||||
|
add_compile_definitions(WITH_QTDBUS)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
set(CMAKE_MACOSX_RPATH 1)
|
set(CMAKE_MACOSX_RPATH 1)
|
||||||
set(CMAKE_INSTALL_RPATH "@loader_path/../Frameworks/")
|
set(CMAKE_INSTALL_RPATH "@loader_path/../Frameworks/")
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,8 @@
|
||||||
|
|
||||||
#include <QtConcurrent>
|
#include <QtConcurrent>
|
||||||
|
|
||||||
DataMigrationTask::DataMigrationTask(QObject* parent,
|
DataMigrationTask::DataMigrationTask(const QString& sourcePath, const QString& targetPath, const IPathMatcher::Ptr pathMatcher)
|
||||||
const QString& sourcePath,
|
: Task(), m_sourcePath(sourcePath), m_targetPath(targetPath), m_pathMatcher(pathMatcher), m_copy(sourcePath, targetPath)
|
||||||
const QString& targetPath,
|
|
||||||
const IPathMatcher::Ptr pathMatcher)
|
|
||||||
: Task(parent), m_sourcePath(sourcePath), m_targetPath(targetPath), m_pathMatcher(pathMatcher), m_copy(sourcePath, targetPath)
|
|
||||||
{
|
{
|
||||||
m_copy.matcher(m_pathMatcher.get()).whitelist(true);
|
m_copy.matcher(m_pathMatcher.get()).whitelist(true);
|
||||||
}
|
}
|
||||||
|
|
@ -27,7 +24,7 @@ void DataMigrationTask::executeTask()
|
||||||
|
|
||||||
// 1. Scan
|
// 1. Scan
|
||||||
// Check how many files we gotta copy
|
// Check how many files we gotta copy
|
||||||
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [&] {
|
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] {
|
||||||
return m_copy(true); // dry run to collect amount of files
|
return m_copy(true); // dry run to collect amount of files
|
||||||
});
|
});
|
||||||
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::dryRunFinished);
|
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::dryRunFinished);
|
||||||
|
|
@ -60,7 +57,7 @@ void DataMigrationTask::dryRunFinished()
|
||||||
setProgress(m_copy.totalCopied(), m_toCopy);
|
setProgress(m_copy.totalCopied(), m_toCopy);
|
||||||
setStatus(tr("Copying %1…").arg(shortenedName));
|
setStatus(tr("Copying %1…").arg(shortenedName));
|
||||||
});
|
});
|
||||||
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [&] {
|
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] {
|
||||||
return m_copy(false); // actually copy now
|
return m_copy(false); // actually copy now
|
||||||
});
|
});
|
||||||
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::copyFinished);
|
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::copyFinished);
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
class DataMigrationTask : public Task {
|
class DataMigrationTask : public Task {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit DataMigrationTask(QObject* parent, const QString& sourcePath, const QString& targetPath, IPathMatcher::Ptr pathmatcher);
|
explicit DataMigrationTask(const QString& sourcePath, const QString& targetPath, IPathMatcher::Ptr pathmatcher);
|
||||||
~DataMigrationTask() override = default;
|
~DataMigrationTask() override = default;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,6 @@
|
||||||
#include <QDirIterator>
|
#include <QDirIterator>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QSaveFile>
|
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <QStorageInfo>
|
#include <QStorageInfo>
|
||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
|
|
@ -54,6 +53,7 @@
|
||||||
#include <system_error>
|
#include <system_error>
|
||||||
|
|
||||||
#include "DesktopServices.h"
|
#include "DesktopServices.h"
|
||||||
|
#include "PSaveFile.h"
|
||||||
#include "StringUtils.h"
|
#include "StringUtils.h"
|
||||||
|
|
||||||
#if defined Q_OS_WIN32
|
#if defined Q_OS_WIN32
|
||||||
|
|
@ -191,8 +191,8 @@ void ensureExists(const QDir& dir)
|
||||||
void write(const QString& filename, const QByteArray& data)
|
void write(const QString& filename, const QByteArray& data)
|
||||||
{
|
{
|
||||||
ensureExists(QFileInfo(filename).dir());
|
ensureExists(QFileInfo(filename).dir());
|
||||||
QSaveFile file(filename);
|
PSaveFile file(filename);
|
||||||
if (!file.open(QSaveFile::WriteOnly)) {
|
if (!file.open(PSaveFile::WriteOnly)) {
|
||||||
throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
|
throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
|
||||||
}
|
}
|
||||||
if (data.size() != file.write(data)) {
|
if (data.size() != file.write(data)) {
|
||||||
|
|
@ -213,8 +213,8 @@ void appendSafe(const QString& filename, const QByteArray& data)
|
||||||
buffer = QByteArray();
|
buffer = QByteArray();
|
||||||
}
|
}
|
||||||
buffer.append(data);
|
buffer.append(data);
|
||||||
QSaveFile file(filename);
|
PSaveFile file(filename);
|
||||||
if (!file.open(QSaveFile::WriteOnly)) {
|
if (!file.open(PSaveFile::WriteOnly)) {
|
||||||
throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
|
throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
|
||||||
}
|
}
|
||||||
if (buffer.size() != file.write(buffer)) {
|
if (buffer.size() != file.write(buffer)) {
|
||||||
|
|
@ -341,7 +341,7 @@ bool copy::operator()(const QString& offset, bool dryRun)
|
||||||
opt |= copy_opts::overwrite_existing;
|
opt |= copy_opts::overwrite_existing;
|
||||||
|
|
||||||
// Function that'll do the actual copying
|
// Function that'll do the actual copying
|
||||||
auto copy_file = [&](QString src_path, QString relative_dst_path) {
|
auto copy_file = [this, dryRun, src, dst, opt, &err](QString src_path, QString relative_dst_path) {
|
||||||
if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist))
|
if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -428,7 +428,7 @@ void create_link::make_link_list(const QString& offset)
|
||||||
m_recursive = true;
|
m_recursive = true;
|
||||||
|
|
||||||
// Function that'll do the actual linking
|
// Function that'll do the actual linking
|
||||||
auto link_file = [&](QString src_path, QString relative_dst_path) {
|
auto link_file = [this, dst](QString src_path, QString relative_dst_path) {
|
||||||
if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist)) {
|
if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist)) {
|
||||||
qDebug() << "path" << relative_dst_path << "in black list or not in whitelist";
|
qDebug() << "path" << relative_dst_path << "in black list or not in whitelist";
|
||||||
return;
|
return;
|
||||||
|
|
@ -523,7 +523,7 @@ void create_link::runPrivileged(const QString& offset)
|
||||||
|
|
||||||
QString serverName = BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink_server" + StringUtils::getRandomAlphaNumeric();
|
QString serverName = BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink_server" + StringUtils::getRandomAlphaNumeric();
|
||||||
|
|
||||||
connect(&m_linkServer, &QLocalServer::newConnection, this, [&]() {
|
connect(&m_linkServer, &QLocalServer::newConnection, this, [this, &gotResults]() {
|
||||||
qDebug() << "Client connected, sending out pairs";
|
qDebug() << "Client connected, sending out pairs";
|
||||||
// construct block of data to send
|
// construct block of data to send
|
||||||
QByteArray block;
|
QByteArray block;
|
||||||
|
|
@ -605,7 +605,7 @@ void create_link::runPrivileged(const QString& offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
ExternalLinkFileProcess* linkFileProcess = new ExternalLinkFileProcess(serverName, m_useHardLinks, this);
|
ExternalLinkFileProcess* linkFileProcess = new ExternalLinkFileProcess(serverName, m_useHardLinks, this);
|
||||||
connect(linkFileProcess, &ExternalLinkFileProcess::processExited, this, [&]() { emit finishedPrivileged(gotResults); });
|
connect(linkFileProcess, &ExternalLinkFileProcess::processExited, this, [this, gotResults]() { emit finishedPrivileged(gotResults); });
|
||||||
connect(linkFileProcess, &ExternalLinkFileProcess::finished, linkFileProcess, &QObject::deleteLater);
|
connect(linkFileProcess, &ExternalLinkFileProcess::finished, linkFileProcess, &QObject::deleteLater);
|
||||||
|
|
||||||
linkFileProcess->start();
|
linkFileProcess->start();
|
||||||
|
|
@ -971,8 +971,7 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
|
||||||
if (!args.empty())
|
if (!args.empty())
|
||||||
argstring = " \"" + args.join("\" \"") + "\"";
|
argstring = " \"" + args.join("\" \"") + "\"";
|
||||||
|
|
||||||
stream << "#!/bin/bash"
|
stream << "#!/bin/bash" << "\n";
|
||||||
<< "\n";
|
|
||||||
stream << "\"" << target << "\" " << argstring << "\n";
|
stream << "\"" << target << "\" " << argstring << "\n";
|
||||||
|
|
||||||
stream.flush();
|
stream.flush();
|
||||||
|
|
@ -1016,12 +1015,9 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
|
||||||
if (!args.empty())
|
if (!args.empty())
|
||||||
argstring = " '" + args.join("' '") + "'";
|
argstring = " '" + args.join("' '") + "'";
|
||||||
|
|
||||||
stream << "[Desktop Entry]"
|
stream << "[Desktop Entry]" << "\n";
|
||||||
<< "\n";
|
stream << "Type=Application" << "\n";
|
||||||
stream << "Type=Application"
|
stream << "Categories=Game;ActionGame;AdventureGame;Simulation" << "\n";
|
||||||
<< "\n";
|
|
||||||
stream << "Categories=Game;ActionGame;AdventureGame;Simulation"
|
|
||||||
<< "\n";
|
|
||||||
stream << "Exec=\"" << target.toLocal8Bit() << "\"" << argstring.toLocal8Bit() << "\n";
|
stream << "Exec=\"" << target.toLocal8Bit() << "\"" << argstring.toLocal8Bit() << "\n";
|
||||||
stream << "Name=" << name.toLocal8Bit() << "\n";
|
stream << "Name=" << name.toLocal8Bit() << "\n";
|
||||||
if (!icon.isEmpty()) {
|
if (!icon.isEmpty()) {
|
||||||
|
|
@ -1299,7 +1295,7 @@ bool clone::operator()(const QString& offset, bool dryRun)
|
||||||
std::error_code err;
|
std::error_code err;
|
||||||
|
|
||||||
// Function that'll do the actual cloneing
|
// Function that'll do the actual cloneing
|
||||||
auto cloneFile = [&](QString src_path, QString relative_dst_path) {
|
auto cloneFile = [this, dryRun, dst, &err](QString src_path, QString relative_dst_path) {
|
||||||
if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist))
|
if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ void InstanceCopyTask::executeTask()
|
||||||
QEventLoop loop;
|
QEventLoop loop;
|
||||||
bool got_priv_results = false;
|
bool got_priv_results = false;
|
||||||
|
|
||||||
connect(&folderLink, &FS::create_link::finishedPrivileged, this, [&](bool gotResults) {
|
connect(&folderLink, &FS::create_link::finishedPrivileged, this, [&got_priv_results, &loop](bool gotResults) {
|
||||||
if (!gotResults) {
|
if (!gotResults) {
|
||||||
qDebug() << "Privileged run exited without results!";
|
qDebug() << "Privileged run exited without results!";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,6 @@ void InstanceCreationTask::executeTask()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!m_abort)
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,9 +69,11 @@ bool InstanceImportTask::abort()
|
||||||
if (!canAbort())
|
if (!canAbort())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (task)
|
bool wasAborted = false;
|
||||||
task->abort();
|
if (m_task)
|
||||||
return Task::abort();
|
wasAborted = m_task->abort();
|
||||||
|
Task::abort();
|
||||||
|
return wasAborted;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstanceImportTask::executeTask()
|
void InstanceImportTask::executeTask()
|
||||||
|
|
@ -104,7 +106,7 @@ void InstanceImportTask::downloadFromUrl()
|
||||||
connect(filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propagateStepProgress);
|
connect(filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propagateStepProgress);
|
||||||
connect(filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::emitFailed);
|
connect(filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::emitFailed);
|
||||||
connect(filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::emitAborted);
|
connect(filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::emitAborted);
|
||||||
task.reset(filesNetJob);
|
m_task.reset(filesNetJob);
|
||||||
filesNetJob->start();
|
filesNetJob->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -193,7 +195,7 @@ void InstanceImportTask::processZipPack()
|
||||||
stepProgress(*progressStep);
|
stepProgress(*progressStep);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(zipTask.get(), &Task::succeeded, this, &InstanceImportTask::extractFinished);
|
connect(zipTask.get(), &Task::succeeded, this, &InstanceImportTask::extractFinished, Qt::QueuedConnection);
|
||||||
connect(zipTask.get(), &Task::aborted, this, &InstanceImportTask::emitAborted);
|
connect(zipTask.get(), &Task::aborted, this, &InstanceImportTask::emitAborted);
|
||||||
connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) {
|
connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) {
|
||||||
progressStep->state = TaskStepState::Failed;
|
progressStep->state = TaskStepState::Failed;
|
||||||
|
|
@ -210,12 +212,13 @@ void InstanceImportTask::processZipPack()
|
||||||
progressStep->status = status;
|
progressStep->status = status;
|
||||||
stepProgress(*progressStep);
|
stepProgress(*progressStep);
|
||||||
});
|
});
|
||||||
task.reset(zipTask);
|
m_task.reset(zipTask);
|
||||||
zipTask->start();
|
zipTask->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstanceImportTask::extractFinished()
|
void InstanceImportTask::extractFinished()
|
||||||
{
|
{
|
||||||
|
setAbortable(false);
|
||||||
QDir extractDir(m_stagingPath);
|
QDir extractDir(m_stagingPath);
|
||||||
|
|
||||||
qDebug() << "Fixing permissions for extracted pack files...";
|
qDebug() << "Fixing permissions for extracted pack files...";
|
||||||
|
|
@ -289,8 +292,11 @@ void InstanceImportTask::processFlame()
|
||||||
inst_creation_task->setGroup(m_instGroup);
|
inst_creation_task->setGroup(m_instGroup);
|
||||||
inst_creation_task->setConfirmUpdate(shouldConfirmUpdate());
|
inst_creation_task->setConfirmUpdate(shouldConfirmUpdate());
|
||||||
|
|
||||||
connect(inst_creation_task.get(), &Task::succeeded, this, [this, inst_creation_task] {
|
auto weak = inst_creation_task.toWeakRef();
|
||||||
setOverride(inst_creation_task->shouldOverride(), inst_creation_task->originalInstanceID());
|
connect(inst_creation_task.get(), &Task::succeeded, this, [this, weak] {
|
||||||
|
if (auto sp = weak.lock()) {
|
||||||
|
setOverride(sp->shouldOverride(), sp->originalInstanceID());
|
||||||
|
}
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
});
|
});
|
||||||
connect(inst_creation_task.get(), &Task::failed, this, &InstanceImportTask::emitFailed);
|
connect(inst_creation_task.get(), &Task::failed, this, &InstanceImportTask::emitFailed);
|
||||||
|
|
@ -299,11 +305,12 @@ void InstanceImportTask::processFlame()
|
||||||
connect(inst_creation_task.get(), &Task::status, this, &InstanceImportTask::setStatus);
|
connect(inst_creation_task.get(), &Task::status, this, &InstanceImportTask::setStatus);
|
||||||
connect(inst_creation_task.get(), &Task::details, this, &InstanceImportTask::setDetails);
|
connect(inst_creation_task.get(), &Task::details, this, &InstanceImportTask::setDetails);
|
||||||
|
|
||||||
connect(this, &Task::aborted, inst_creation_task.get(), &InstanceCreationTask::abort);
|
|
||||||
connect(inst_creation_task.get(), &Task::aborted, this, &Task::abort);
|
connect(inst_creation_task.get(), &Task::aborted, this, &Task::abort);
|
||||||
connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable);
|
connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable);
|
||||||
|
|
||||||
inst_creation_task->start();
|
m_task.reset(inst_creation_task);
|
||||||
|
setAbortable(true);
|
||||||
|
m_task->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstanceImportTask::processTechnic()
|
void InstanceImportTask::processTechnic()
|
||||||
|
|
@ -350,7 +357,7 @@ void InstanceImportTask::processMultiMC()
|
||||||
|
|
||||||
void InstanceImportTask::processModrinth()
|
void InstanceImportTask::processModrinth()
|
||||||
{
|
{
|
||||||
ModrinthCreationTask* inst_creation_task = nullptr;
|
shared_qobject_ptr<ModrinthCreationTask> inst_creation_task = nullptr;
|
||||||
if (!m_extra_info.isEmpty()) {
|
if (!m_extra_info.isEmpty()) {
|
||||||
auto pack_id_it = m_extra_info.constFind("pack_id");
|
auto pack_id_it = m_extra_info.constFind("pack_id");
|
||||||
Q_ASSERT(pack_id_it != m_extra_info.constEnd());
|
Q_ASSERT(pack_id_it != m_extra_info.constEnd());
|
||||||
|
|
@ -367,7 +374,7 @@ void InstanceImportTask::processModrinth()
|
||||||
original_instance_id = original_instance_id_it.value();
|
original_instance_id = original_instance_id_it.value();
|
||||||
|
|
||||||
inst_creation_task =
|
inst_creation_task =
|
||||||
new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id);
|
makeShared<ModrinthCreationTask>(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id);
|
||||||
} else {
|
} else {
|
||||||
QString pack_id;
|
QString pack_id;
|
||||||
if (!m_sourceUrl.isEmpty()) {
|
if (!m_sourceUrl.isEmpty()) {
|
||||||
|
|
@ -376,7 +383,7 @@ void InstanceImportTask::processModrinth()
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Find a way to get the ID in directly imported ZIPs
|
// FIXME: Find a way to get the ID in directly imported ZIPs
|
||||||
inst_creation_task = new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id);
|
inst_creation_task = makeShared<ModrinthCreationTask>(m_stagingPath, m_globalSettings, m_parent, pack_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
inst_creation_task->setName(*this);
|
inst_creation_task->setName(*this);
|
||||||
|
|
@ -384,20 +391,23 @@ void InstanceImportTask::processModrinth()
|
||||||
inst_creation_task->setGroup(m_instGroup);
|
inst_creation_task->setGroup(m_instGroup);
|
||||||
inst_creation_task->setConfirmUpdate(shouldConfirmUpdate());
|
inst_creation_task->setConfirmUpdate(shouldConfirmUpdate());
|
||||||
|
|
||||||
connect(inst_creation_task, &Task::succeeded, this, [this, inst_creation_task] {
|
auto weak = inst_creation_task.toWeakRef();
|
||||||
setOverride(inst_creation_task->shouldOverride(), inst_creation_task->originalInstanceID());
|
connect(inst_creation_task.get(), &Task::succeeded, this, [this, weak] {
|
||||||
|
if (auto sp = weak.lock()) {
|
||||||
|
setOverride(sp->shouldOverride(), sp->originalInstanceID());
|
||||||
|
}
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
});
|
});
|
||||||
connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed);
|
connect(inst_creation_task.get(), &Task::failed, this, &InstanceImportTask::emitFailed);
|
||||||
connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress);
|
connect(inst_creation_task.get(), &Task::progress, this, &InstanceImportTask::setProgress);
|
||||||
connect(inst_creation_task, &Task::stepProgress, this, &InstanceImportTask::propagateStepProgress);
|
connect(inst_creation_task.get(), &Task::stepProgress, this, &InstanceImportTask::propagateStepProgress);
|
||||||
connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus);
|
connect(inst_creation_task.get(), &Task::status, this, &InstanceImportTask::setStatus);
|
||||||
connect(inst_creation_task, &Task::details, this, &InstanceImportTask::setDetails);
|
connect(inst_creation_task.get(), &Task::details, this, &InstanceImportTask::setDetails);
|
||||||
connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater);
|
|
||||||
|
|
||||||
connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort);
|
connect(inst_creation_task.get(), &Task::aborted, this, &Task::abort);
|
||||||
connect(inst_creation_task, &Task::aborted, this, &Task::abort);
|
connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable);
|
||||||
connect(inst_creation_task, &Task::abortStatusChanged, this, &Task::setAbortable);
|
|
||||||
|
|
||||||
inst_creation_task->start();
|
m_task.reset(inst_creation_task);
|
||||||
|
setAbortable(true);
|
||||||
|
m_task->start();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,16 +40,13 @@
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include "InstanceTask.h"
|
#include "InstanceTask.h"
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <optional>
|
|
||||||
|
|
||||||
class QuaZip;
|
class QuaZip;
|
||||||
|
|
||||||
class InstanceImportTask : public InstanceTask {
|
class InstanceImportTask : public InstanceTask {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit InstanceImportTask(const QUrl& sourceUrl, QWidget* parent = nullptr, QMap<QString, QString>&& extra_info = {});
|
explicit InstanceImportTask(const QUrl& sourceUrl, QWidget* parent = nullptr, QMap<QString, QString>&& extra_info = {});
|
||||||
|
virtual ~InstanceImportTask() = default;
|
||||||
bool abort() override;
|
bool abort() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
@ -70,7 +67,7 @@ class InstanceImportTask : public InstanceTask {
|
||||||
private: /* data */
|
private: /* data */
|
||||||
QUrl m_sourceUrl;
|
QUrl m_sourceUrl;
|
||||||
QString m_archivePath;
|
QString m_archivePath;
|
||||||
Task::Ptr task;
|
Task::Ptr m_task;
|
||||||
enum class ModpackType {
|
enum class ModpackType {
|
||||||
Unknown,
|
Unknown,
|
||||||
MultiMC,
|
MultiMC,
|
||||||
|
|
|
||||||
|
|
@ -487,7 +487,7 @@ InstanceList::InstListError InstanceList::loadList()
|
||||||
int front_bookmark = -1;
|
int front_bookmark = -1;
|
||||||
int back_bookmark = -1;
|
int back_bookmark = -1;
|
||||||
int currentItem = -1;
|
int currentItem = -1;
|
||||||
auto removeNow = [&]() {
|
auto removeNow = [this, &front_bookmark, &back_bookmark, ¤tItem]() {
|
||||||
beginRemoveRows(QModelIndex(), front_bookmark, back_bookmark);
|
beginRemoveRows(QModelIndex(), front_bookmark, back_bookmark);
|
||||||
m_instances.erase(m_instances.begin() + front_bookmark, m_instances.begin() + back_bookmark + 1);
|
m_instances.erase(m_instances.begin() + front_bookmark, m_instances.begin() + back_bookmark + 1);
|
||||||
endRemoveRows();
|
endRemoveRows();
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ class InstancePageProvider : protected QObject, public BasePageProvider {
|
||||||
values.append(new ServersPage(onesix));
|
values.append(new ServersPage(onesix));
|
||||||
// values.append(new GameOptionsPage(onesix.get()));
|
// values.append(new GameOptionsPage(onesix.get()));
|
||||||
values.append(new ScreenshotsPage(FS::PathCombine(onesix->gameRoot(), "screenshots")));
|
values.append(new ScreenshotsPage(FS::PathCombine(onesix->gameRoot(), "screenshots")));
|
||||||
values.append(new InstanceSettingsPage(onesix.get()));
|
values.append(new InstanceSettingsPage(onesix));
|
||||||
auto logMatcher = inst->getLogFileMatcher();
|
auto logMatcher = inst->getLogFileMatcher();
|
||||||
if (logMatcher) {
|
if (logMatcher) {
|
||||||
values.append(new OtherLogsPage(inst->getLogFileRoot(), logMatcher));
|
values.append(new OtherLogsPage(inst->getLogFileRoot(), logMatcher));
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,7 @@ void JavaCommon::TestCheck::run()
|
||||||
emit finished();
|
emit finished();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
checker.reset(new JavaChecker(m_path, "", 0, 0, 0, 0, this));
|
checker.reset(new JavaChecker(m_path, "", 0, 0, 0, 0));
|
||||||
connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinished);
|
connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinished);
|
||||||
checker->start();
|
checker->start();
|
||||||
}
|
}
|
||||||
|
|
@ -128,7 +128,7 @@ void JavaCommon::TestCheck::checkFinished(const JavaChecker::Result& result)
|
||||||
emit finished();
|
emit finished();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
checker.reset(new JavaChecker(m_path, m_args, m_maxMem, m_maxMem, result.javaVersion.requiresPermGen() ? m_permGen : 0, 0, this));
|
checker.reset(new JavaChecker(m_path, m_args, m_maxMem, m_maxMem, result.javaVersion.requiresPermGen() ? m_permGen : 0, 0));
|
||||||
connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinishedWithArgs);
|
connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinishedWithArgs);
|
||||||
checker->start();
|
checker->start();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@
|
||||||
#include "ui/InstanceWindow.h"
|
#include "ui/InstanceWindow.h"
|
||||||
#include "ui/MainWindow.h"
|
#include "ui/MainWindow.h"
|
||||||
#include "ui/dialogs/CustomMessageBox.h"
|
#include "ui/dialogs/CustomMessageBox.h"
|
||||||
|
#include "ui/dialogs/MSALoginDialog.h"
|
||||||
#include "ui/dialogs/ProfileSelectDialog.h"
|
#include "ui/dialogs/ProfileSelectDialog.h"
|
||||||
#include "ui/dialogs/ProfileSetupDialog.h"
|
#include "ui/dialogs/ProfileSetupDialog.h"
|
||||||
#include "ui/dialogs/ProgressDialog.h"
|
#include "ui/dialogs/ProgressDialog.h"
|
||||||
|
|
@ -61,7 +62,7 @@
|
||||||
#include "launch/steps/TextPrint.h"
|
#include "launch/steps/TextPrint.h"
|
||||||
#include "tasks/Task.h"
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
LaunchController::LaunchController(QObject* parent) : Task(parent) {}
|
LaunchController::LaunchController() : Task() {}
|
||||||
|
|
||||||
void LaunchController::executeTask()
|
void LaunchController::executeTask()
|
||||||
{
|
{
|
||||||
|
|
@ -234,10 +235,15 @@ void LaunchController::login()
|
||||||
if (!m_session->wants_online) {
|
if (!m_session->wants_online) {
|
||||||
// we ask the user for a player name
|
// we ask the user for a player name
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
auto name = askOfflineName(m_session->player_name, m_session->demo, ok);
|
QString name;
|
||||||
if (!ok) {
|
if (m_offlineName.isEmpty()) {
|
||||||
tryagain = false;
|
name = askOfflineName(m_session->player_name, m_session->demo, ok);
|
||||||
break;
|
if (!ok) {
|
||||||
|
tryagain = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
name = m_offlineName;
|
||||||
}
|
}
|
||||||
m_session->MakeOffline(name);
|
m_session->MakeOffline(name);
|
||||||
// offline flavored game from here :3
|
// offline flavored game from here :3
|
||||||
|
|
@ -287,10 +293,8 @@ void LaunchController::login()
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
case AccountState::Expired: {
|
case AccountState::Expired: {
|
||||||
auto errorString = tr("The account has expired and needs to be logged into manually again.");
|
if (reauthenticateCurrentAccount())
|
||||||
QMessageBox::warning(m_parentWidget, tr("Account refresh failed"), errorString, QMessageBox::StandardButton::Ok,
|
continue;
|
||||||
QMessageBox::StandardButton::Ok);
|
|
||||||
emitFailed(errorString);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case AccountState::Disabled: {
|
case AccountState::Disabled: {
|
||||||
|
|
@ -314,6 +318,33 @@ void LaunchController::login()
|
||||||
emitFailed(tr("Failed to launch."));
|
emitFailed(tr("Failed to launch."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool LaunchController::reauthenticateCurrentAccount()
|
||||||
|
{
|
||||||
|
auto button =
|
||||||
|
QMessageBox::warning(m_parentWidget, tr("Account refresh failed"),
|
||||||
|
tr("The account has expired and needs to be reauthenticated. Do you want to reauthenticate this account?"),
|
||||||
|
QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No, QMessageBox::StandardButton::Yes);
|
||||||
|
if (button == QMessageBox::StandardButton::Yes) {
|
||||||
|
auto accounts = APPLICATION->accounts();
|
||||||
|
bool isDefault = accounts->defaultAccount() == m_accountToUse;
|
||||||
|
accounts->removeAccount(accounts->index(accounts->findAccountByProfileId(m_accountToUse->profileId())));
|
||||||
|
if (m_accountToUse->accountType() == AccountType::MSA) {
|
||||||
|
auto newAccount = MSALoginDialog::newAccount(m_parentWidget);
|
||||||
|
accounts->addAccount(newAccount);
|
||||||
|
if (isDefault) {
|
||||||
|
accounts->setDefaultAccount(newAccount);
|
||||||
|
}
|
||||||
|
m_accountToUse = nullptr;
|
||||||
|
decideAccount();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
emitFailed(tr("Account expired and re-login attempt failed"));
|
||||||
|
} else {
|
||||||
|
emitFailed(tr("The account has expired and needs to be reauthenticated"));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void LaunchController::launchInstance()
|
void LaunchController::launchInstance()
|
||||||
{
|
{
|
||||||
Q_ASSERT_X(m_instance != NULL, "launchInstance", "instance is NULL");
|
Q_ASSERT_X(m_instance != NULL, "launchInstance", "instance is NULL");
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ class LaunchController : public Task {
|
||||||
public:
|
public:
|
||||||
void executeTask() override;
|
void executeTask() override;
|
||||||
|
|
||||||
LaunchController(QObject* parent = nullptr);
|
LaunchController();
|
||||||
virtual ~LaunchController() = default;
|
virtual ~LaunchController() = default;
|
||||||
|
|
||||||
void setInstance(InstancePtr instance) { m_instance = instance; }
|
void setInstance(InstancePtr instance) { m_instance = instance; }
|
||||||
|
|
@ -56,6 +56,8 @@ class LaunchController : public Task {
|
||||||
|
|
||||||
void setOnline(bool online) { m_online = online; }
|
void setOnline(bool online) { m_online = online; }
|
||||||
|
|
||||||
|
void setOfflineName(const QString& offlineName) { m_offlineName = offlineName; }
|
||||||
|
|
||||||
void setDemo(bool demo) { m_demo = demo; }
|
void setDemo(bool demo) { m_demo = demo; }
|
||||||
|
|
||||||
void setProfiler(BaseProfilerFactory* profiler) { m_profiler = profiler; }
|
void setProfiler(BaseProfilerFactory* profiler) { m_profiler = profiler; }
|
||||||
|
|
@ -76,6 +78,7 @@ class LaunchController : public Task {
|
||||||
void decideAccount();
|
void decideAccount();
|
||||||
bool askPlayDemo();
|
bool askPlayDemo();
|
||||||
QString askOfflineName(QString playerName, bool demo, bool& ok);
|
QString askOfflineName(QString playerName, bool demo, bool& ok);
|
||||||
|
bool reauthenticateCurrentAccount();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void readyForLaunch();
|
void readyForLaunch();
|
||||||
|
|
@ -87,6 +90,7 @@ class LaunchController : public Task {
|
||||||
private:
|
private:
|
||||||
BaseProfilerFactory* m_profiler = nullptr;
|
BaseProfilerFactory* m_profiler = nullptr;
|
||||||
bool m_online = true;
|
bool m_online = true;
|
||||||
|
QString m_offlineName;
|
||||||
bool m_demo = false;
|
bool m_demo = false;
|
||||||
InstancePtr m_instance;
|
InstancePtr m_instance;
|
||||||
QWidget* m_parentWidget = nullptr;
|
QWidget* m_parentWidget = nullptr;
|
||||||
|
|
|
||||||
|
|
@ -39,8 +39,16 @@ if [ "x$DEPS_LIST" = "x" ]; then
|
||||||
# Just to be sure...
|
# Just to be sure...
|
||||||
chmod +x "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}"
|
chmod +x "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}"
|
||||||
|
|
||||||
|
ARGS=("${LAUNCHER_DIR}/${LAUNCHER_NAME}" "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}")
|
||||||
|
|
||||||
|
if [ -f portable.txt ]; then
|
||||||
|
ARGS+=("-d" "${LAUNCHER_DIR}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
ARGS+=("$@")
|
||||||
|
|
||||||
# Run the launcher
|
# Run the launcher
|
||||||
exec -a "${LAUNCHER_DIR}/${LAUNCHER_NAME}" "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}" -d "${LAUNCHER_DIR}" "$@"
|
exec -a "${ARGS[@]}"
|
||||||
|
|
||||||
# Run the launcher in valgrind
|
# Run the launcher in valgrind
|
||||||
# valgrind --log-file="valgrind.log" --leak-check=full --track-origins=yes "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}" -d "${LAUNCHER_DIR}" "$@"
|
# valgrind --log-file="valgrind.log" --leak-check=full --track-origins=yes "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}" -d "${LAUNCHER_DIR}" "$@"
|
||||||
|
|
|
||||||
|
|
@ -378,7 +378,7 @@ std::optional<QStringList> extractDir(QString fileCompressed, QString dir)
|
||||||
if (fileInfo.size() == 22) {
|
if (fileInfo.size() == 22) {
|
||||||
return QStringList();
|
return QStringList();
|
||||||
}
|
}
|
||||||
qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();
|
qWarning() << "Could not open archive for unpacking:" << fileCompressed << "Error:" << zip.getZipError();
|
||||||
;
|
;
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
@ -395,7 +395,7 @@ std::optional<QStringList> extractDir(QString fileCompressed, QString subdir, QS
|
||||||
if (fileInfo.size() == 22) {
|
if (fileInfo.size() == 22) {
|
||||||
return QStringList();
|
return QStringList();
|
||||||
}
|
}
|
||||||
qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();
|
qWarning() << "Could not open archive for unpacking:" << fileCompressed << "Error:" << zip.getZipError();
|
||||||
;
|
;
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
@ -412,7 +412,7 @@ bool extractFile(QString fileCompressed, QString file, QString target)
|
||||||
if (fileInfo.size() == 22) {
|
if (fileInfo.size() == 22) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();
|
qWarning() << "Could not open archive for unpacking:" << fileCompressed << "Error:" << zip.getZipError();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return extractRelFile(&zip, file, target);
|
return extractRelFile(&zip, file, target);
|
||||||
|
|
@ -577,7 +577,7 @@ auto ExtractZipTask::extractZip() -> ZipResult
|
||||||
|
|
||||||
auto relative_file_name = QDir::fromNativeSeparators(file_name.mid(m_subdirectory.size()));
|
auto relative_file_name = QDir::fromNativeSeparators(file_name.mid(m_subdirectory.size()));
|
||||||
auto original_name = relative_file_name;
|
auto original_name = relative_file_name;
|
||||||
setStatus("Unziping: " + relative_file_name);
|
setStatus("Unpacking: " + relative_file_name);
|
||||||
|
|
||||||
// Fix subdirs/files ending with a / getting transformed into absolute paths
|
// Fix subdirs/files ending with a / getting transformed into absolute paths
|
||||||
if (relative_file_name.startsWith('/'))
|
if (relative_file_name.startsWith('/'))
|
||||||
|
|
|
||||||
71
launcher/PSaveFile.h
Normal file
71
launcher/PSaveFile.h
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QSaveFile>
|
||||||
|
#include "Application.h"
|
||||||
|
|
||||||
|
#if defined(LAUNCHER_APPLICATION)
|
||||||
|
|
||||||
|
/* PSaveFile
|
||||||
|
* A class that mimics QSaveFile for Windows.
|
||||||
|
*
|
||||||
|
* When reading resources, we need to avoid accessing temporary files
|
||||||
|
* generated by QSaveFile. If we start reading such a file, we may
|
||||||
|
* inadvertently keep it open while QSaveFile is trying to remove it,
|
||||||
|
* or we might detect the file just before it is removed, leading to
|
||||||
|
* race conditions and errors.
|
||||||
|
*
|
||||||
|
* Unfortunately, QSaveFile doesn't provide a way to retrieve the
|
||||||
|
* temporary file name or to set a specific template for the temporary
|
||||||
|
* file name it uses. By default, QSaveFile appends a `.XXXXXX` suffix
|
||||||
|
* to the original file name, where the `XXXXXX` part is dynamically
|
||||||
|
* generated to ensure uniqueness.
|
||||||
|
*
|
||||||
|
* This class acts like a lock by adding and removing the target file
|
||||||
|
* name into/from a global string set, helping to manage access to
|
||||||
|
* files during critical operations.
|
||||||
|
*
|
||||||
|
* Note: Please do not use the `setFileName` function directly, as it
|
||||||
|
* is not virtual and cannot be overridden.
|
||||||
|
*/
|
||||||
|
class PSaveFile : public QSaveFile {
|
||||||
|
public:
|
||||||
|
PSaveFile(const QString& name) : QSaveFile(name) { addPath(name); }
|
||||||
|
PSaveFile(const QString& name, QObject* parent) : QSaveFile(name, parent) { addPath(name); }
|
||||||
|
virtual ~PSaveFile()
|
||||||
|
{
|
||||||
|
if (auto app = APPLICATION_DYN) {
|
||||||
|
app->removeQSavePath(m_absoluteFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void addPath(const QString& path)
|
||||||
|
{
|
||||||
|
m_absoluteFilePath = QFileInfo(path).absoluteFilePath() + "."; // add dot for tmp files only
|
||||||
|
if (auto app = APPLICATION_DYN) {
|
||||||
|
app->addQSavePath(m_absoluteFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QString m_absoluteFilePath;
|
||||||
|
};
|
||||||
|
#else
|
||||||
|
#define PSaveFile QSaveFile
|
||||||
|
#endif
|
||||||
|
|
@ -33,7 +33,7 @@ class shared_qobject_ptr : public QSharedPointer<T> {
|
||||||
{}
|
{}
|
||||||
|
|
||||||
void reset() { QSharedPointer<T>::reset(); }
|
void reset() { QSharedPointer<T>::reset(); }
|
||||||
void reset(T*&& other)
|
void reset(T* other)
|
||||||
{
|
{
|
||||||
shared_qobject_ptr<T> t(other);
|
shared_qobject_ptr<T> t(other);
|
||||||
this->swap(t);
|
this->swap(t);
|
||||||
|
|
|
||||||
|
|
@ -35,9 +35,9 @@ ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
|
||||||
QString custom_target_folder)
|
QString custom_target_folder)
|
||||||
: m_pack(std::move(pack)), m_pack_version(std::move(version)), m_pack_model(packs), m_custom_target_folder(custom_target_folder)
|
: m_pack(std::move(pack)), m_pack_version(std::move(version)), m_pack_model(packs), m_custom_target_folder(custom_target_folder)
|
||||||
{
|
{
|
||||||
if (auto model = dynamic_cast<ModFolderModel*>(m_pack_model.get()); model && is_indexed) {
|
if (is_indexed) {
|
||||||
m_update_task.reset(new LocalModUpdateTask(model->indexDir(), *m_pack, m_pack_version));
|
m_update_task.reset(new LocalResourceUpdateTask(m_pack_model->indexDir(), *m_pack, m_pack_version));
|
||||||
connect(m_update_task.get(), &LocalModUpdateTask::hasOldMod, this, &ResourceDownloadTask::hasOldResource);
|
connect(m_update_task.get(), &LocalResourceUpdateTask::hasOldResource, this, &ResourceDownloadTask::hasOldResource);
|
||||||
|
|
||||||
addTask(m_update_task);
|
addTask(m_update_task);
|
||||||
}
|
}
|
||||||
|
|
@ -91,12 +91,8 @@ void ResourceDownloadTask::downloadSucceeded()
|
||||||
m_filesNetJob.reset();
|
m_filesNetJob.reset();
|
||||||
auto name = std::get<0>(to_delete);
|
auto name = std::get<0>(to_delete);
|
||||||
auto filename = std::get<1>(to_delete);
|
auto filename = std::get<1>(to_delete);
|
||||||
if (!name.isEmpty() && filename != m_pack_version.fileName) {
|
if (!name.isEmpty() && filename != m_pack_version.fileName)
|
||||||
if (auto model = dynamic_cast<ModFolderModel*>(m_pack_model.get()); model)
|
m_pack_model->uninstallResource(filename, true);
|
||||||
model->uninstallMod(filename, true);
|
|
||||||
else
|
|
||||||
m_pack_model->uninstallResource(filename);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ResourceDownloadTask::downloadFailed(QString reason)
|
void ResourceDownloadTask::downloadFailed(QString reason)
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
#include "net/NetJob.h"
|
#include "net/NetJob.h"
|
||||||
#include "tasks/SequentialTask.h"
|
#include "tasks/SequentialTask.h"
|
||||||
|
|
||||||
#include "minecraft/mod/tasks/LocalModUpdateTask.h"
|
#include "minecraft/mod/tasks/LocalResourceUpdateTask.h"
|
||||||
#include "modplatform/ModIndex.h"
|
#include "modplatform/ModIndex.h"
|
||||||
|
|
||||||
class ResourceFolderModel;
|
class ResourceFolderModel;
|
||||||
|
|
@ -50,7 +50,7 @@ class ResourceDownloadTask : public SequentialTask {
|
||||||
QString m_custom_target_folder;
|
QString m_custom_target_folder;
|
||||||
|
|
||||||
NetJob::Ptr m_filesNetJob;
|
NetJob::Ptr m_filesNetJob;
|
||||||
LocalModUpdateTask::Ptr m_update_task;
|
LocalResourceUpdateTask::Ptr m_update_task;
|
||||||
|
|
||||||
void downloadProgressChanged(qint64 current, qint64 total);
|
void downloadProgressChanged(qint64 current, qint64 total);
|
||||||
void downloadFailed(QString reason);
|
void downloadFailed(QString reason);
|
||||||
|
|
|
||||||
|
|
@ -81,9 +81,9 @@ QString getSupportedJavaArchitecture()
|
||||||
if (arch == "arm64")
|
if (arch == "arm64")
|
||||||
return "mac-os-arm64";
|
return "mac-os-arm64";
|
||||||
if (arch.contains("64"))
|
if (arch.contains("64"))
|
||||||
return "mac-os-64";
|
return "mac-os-x64";
|
||||||
if (arch.contains("86"))
|
if (arch.contains("86"))
|
||||||
return "mac-os-86";
|
return "mac-os-x86";
|
||||||
// Unknown, maybe something new, appending arch
|
// Unknown, maybe something new, appending arch
|
||||||
return "mac-os-" + arch;
|
return "mac-os-" + arch;
|
||||||
} else if (sys == "linux") {
|
} else if (sys == "linux") {
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ void Version::parse()
|
||||||
if (m_string.isEmpty())
|
if (m_string.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto classChange = [&](QChar lastChar, QChar currentChar) {
|
auto classChange = [¤tSection](QChar lastChar, QChar currentChar) {
|
||||||
if (lastChar.isNull())
|
if (lastChar.isNull())
|
||||||
return false;
|
return false;
|
||||||
if (lastChar.isDigit() != currentChar.isDigit())
|
if (lastChar.isDigit() != currentChar.isDigit())
|
||||||
|
|
|
||||||
|
|
@ -104,11 +104,11 @@ void FileLinkApp::joinServer(QString server)
|
||||||
|
|
||||||
in.setDevice(&socket);
|
in.setDevice(&socket);
|
||||||
|
|
||||||
connect(&socket, &QLocalSocket::connected, this, [&]() { qDebug() << "connected to server"; });
|
connect(&socket, &QLocalSocket::connected, this, []() { qDebug() << "connected to server"; });
|
||||||
|
|
||||||
connect(&socket, &QLocalSocket::readyRead, this, &FileLinkApp::readPathPairs);
|
connect(&socket, &QLocalSocket::readyRead, this, &FileLinkApp::readPathPairs);
|
||||||
|
|
||||||
connect(&socket, &QLocalSocket::errorOccurred, this, [&](QLocalSocket::LocalSocketError socketError) {
|
connect(&socket, &QLocalSocket::errorOccurred, this, [this](QLocalSocket::LocalSocketError socketError) {
|
||||||
m_status = Failed;
|
m_status = Failed;
|
||||||
switch (socketError) {
|
switch (socketError) {
|
||||||
case QLocalSocket::ServerNotFoundError:
|
case QLocalSocket::ServerNotFoundError:
|
||||||
|
|
@ -132,7 +132,7 @@ void FileLinkApp::joinServer(QString server)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(&socket, &QLocalSocket::disconnected, this, [&]() {
|
connect(&socket, &QLocalSocket::disconnected, this, [this]() {
|
||||||
qDebug() << "disconnected from server, should exit";
|
qDebug() << "disconnected from server, should exit";
|
||||||
m_status = Succeeded;
|
m_status = Succeeded;
|
||||||
exit();
|
exit();
|
||||||
|
|
|
||||||
|
|
@ -47,24 +47,24 @@
|
||||||
|
|
||||||
#define MAX_SIZE 1024
|
#define MAX_SIZE 1024
|
||||||
|
|
||||||
IconList::IconList(const QStringList& builtinPaths, QString path, QObject* parent) : QAbstractListModel(parent)
|
IconList::IconList(const QStringList& builtinPaths, const QString& path, QObject* parent) : QAbstractListModel(parent)
|
||||||
{
|
{
|
||||||
QSet<QString> builtinNames;
|
QSet<QString> builtinNames;
|
||||||
|
|
||||||
// add builtin icons
|
// add builtin icons
|
||||||
for (auto& builtinPath : builtinPaths) {
|
for (const auto& builtinPath : builtinPaths) {
|
||||||
QDir instance_icons(builtinPath);
|
QDir instanceIcons(builtinPath);
|
||||||
auto file_info_list = instance_icons.entryInfoList(QDir::Files, QDir::Name);
|
auto fileInfoList = instanceIcons.entryInfoList(QDir::Files, QDir::Name);
|
||||||
for (auto file_info : file_info_list) {
|
for (const auto& fileInfo : fileInfoList) {
|
||||||
builtinNames.insert(file_info.completeBaseName());
|
builtinNames.insert(fileInfo.baseName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (auto& builtinName : builtinNames) {
|
for (const auto& builtinName : builtinNames) {
|
||||||
addThemeIcon(builtinName);
|
addThemeIcon(builtinName);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_watcher.reset(new QFileSystemWatcher());
|
m_watcher.reset(new QFileSystemWatcher());
|
||||||
is_watching = false;
|
m_isWatching = false;
|
||||||
connect(m_watcher.get(), &QFileSystemWatcher::directoryChanged, this, &IconList::directoryChanged);
|
connect(m_watcher.get(), &QFileSystemWatcher::directoryChanged, this, &IconList::directoryChanged);
|
||||||
connect(m_watcher.get(), &QFileSystemWatcher::fileChanged, this, &IconList::fileChanged);
|
connect(m_watcher.get(), &QFileSystemWatcher::fileChanged, this, &IconList::fileChanged);
|
||||||
|
|
||||||
|
|
@ -77,91 +77,131 @@ IconList::IconList(const QStringList& builtinPaths, QString path, QObject* paren
|
||||||
void IconList::sortIconList()
|
void IconList::sortIconList()
|
||||||
{
|
{
|
||||||
qDebug() << "Sorting icon list...";
|
qDebug() << "Sorting icon list...";
|
||||||
std::sort(icons.begin(), icons.end(), [](const MMCIcon& a, const MMCIcon& b) { return a.m_key.localeAwareCompare(b.m_key) < 0; });
|
std::sort(m_icons.begin(), m_icons.end(), [](const MMCIcon& a, const MMCIcon& b) {
|
||||||
|
bool aIsSubdir = a.m_key.contains(QDir::separator());
|
||||||
|
bool bIsSubdir = b.m_key.contains(QDir::separator());
|
||||||
|
if (aIsSubdir != bIsSubdir) {
|
||||||
|
return !aIsSubdir; // root-level icons come first
|
||||||
|
}
|
||||||
|
return a.m_key.localeAwareCompare(b.m_key) < 0;
|
||||||
|
});
|
||||||
reindex();
|
reindex();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to add directories recursively
|
||||||
|
bool IconList::addPathRecursively(const QString& path)
|
||||||
|
{
|
||||||
|
QDir dir(path);
|
||||||
|
if (!dir.exists())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Add the directory itself
|
||||||
|
bool watching = m_watcher->addPath(path);
|
||||||
|
|
||||||
|
// Add all subdirectories
|
||||||
|
QFileInfoList entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||||
|
for (const QFileInfo& entry : entries) {
|
||||||
|
if (addPathRecursively(entry.absoluteFilePath())) {
|
||||||
|
watching = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return watching;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList IconList::getIconFilePaths() const
|
||||||
|
{
|
||||||
|
QStringList iconFiles{};
|
||||||
|
QStringList directories{ m_dir.absolutePath() };
|
||||||
|
while (!directories.isEmpty()) {
|
||||||
|
QString first = directories.takeFirst();
|
||||||
|
QDir dir(first);
|
||||||
|
for (QFileInfo& fileInfo : dir.entryInfoList(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot, QDir::Name)) {
|
||||||
|
if (fileInfo.isDir())
|
||||||
|
directories.push_back(fileInfo.absoluteFilePath());
|
||||||
|
else
|
||||||
|
iconFiles.push_back(fileInfo.absoluteFilePath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return iconFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString formatName(const QDir& iconsDir, const QFileInfo& iconFile)
|
||||||
|
{
|
||||||
|
if (iconFile.dir() == iconsDir)
|
||||||
|
return iconFile.baseName();
|
||||||
|
|
||||||
|
constexpr auto delimiter = " » ";
|
||||||
|
QString relativePathWithoutExtension = iconsDir.relativeFilePath(iconFile.dir().path()) + QDir::separator() + iconFile.baseName();
|
||||||
|
return relativePathWithoutExtension.replace(QDir::separator(), delimiter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Split into a separate function because the preprocessing impedes readability
|
||||||
|
QSet<QString> toStringSet(const QList<QString>& list)
|
||||||
|
{
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||||
|
QSet<QString> set(list.begin(), list.end());
|
||||||
|
#else
|
||||||
|
QSet<QString> set = list.toSet();
|
||||||
|
#endif
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
void IconList::directoryChanged(const QString& path)
|
void IconList::directoryChanged(const QString& path)
|
||||||
{
|
{
|
||||||
QDir new_dir(path);
|
QDir newDir(path);
|
||||||
if (m_dir.absolutePath() != new_dir.absolutePath()) {
|
if (m_dir.absolutePath() != newDir.absolutePath()) {
|
||||||
m_dir.setPath(path);
|
if (!path.startsWith(m_dir.absolutePath()))
|
||||||
|
m_dir.setPath(path);
|
||||||
m_dir.refresh();
|
m_dir.refresh();
|
||||||
if (is_watching)
|
if (m_isWatching)
|
||||||
stopWatching();
|
stopWatching();
|
||||||
startWatching();
|
startWatching();
|
||||||
}
|
}
|
||||||
if (!m_dir.exists())
|
if (!m_dir.exists() && !FS::ensureFolderPathExists(m_dir.absolutePath()))
|
||||||
if (!FS::ensureFolderPathExists(m_dir.absolutePath()))
|
return;
|
||||||
return;
|
|
||||||
m_dir.refresh();
|
m_dir.refresh();
|
||||||
auto new_list = m_dir.entryList(QDir::Files, QDir::Name);
|
const QStringList newFileNamesList = getIconFilePaths();
|
||||||
for (auto it = new_list.begin(); it != new_list.end(); it++) {
|
const QSet<QString> newSet = toStringSet(newFileNamesList);
|
||||||
QString& foo = (*it);
|
QSet<QString> currentSet;
|
||||||
foo = m_dir.filePath(foo);
|
for (const MMCIcon& it : m_icons) {
|
||||||
}
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
|
||||||
QSet<QString> new_set(new_list.begin(), new_list.end());
|
|
||||||
#else
|
|
||||||
auto new_set = new_list.toSet();
|
|
||||||
#endif
|
|
||||||
QList<QString> current_list;
|
|
||||||
for (auto& it : icons) {
|
|
||||||
if (!it.has(IconType::FileBased))
|
if (!it.has(IconType::FileBased))
|
||||||
continue;
|
continue;
|
||||||
current_list.push_back(it.m_images[IconType::FileBased].filename);
|
currentSet.insert(it.m_images[IconType::FileBased].filename);
|
||||||
}
|
}
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
QSet<QString> toRemove = currentSet - newSet;
|
||||||
QSet<QString> current_set(current_list.begin(), current_list.end());
|
QSet<QString> toAdd = newSet - currentSet;
|
||||||
#else
|
|
||||||
QSet<QString> current_set = current_list.toSet();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
QSet<QString> to_remove = current_set;
|
for (const QString& removedPath : toRemove) {
|
||||||
to_remove -= new_set;
|
qDebug() << "Removing icon " << removedPath;
|
||||||
|
QFileInfo removedFile(removedPath);
|
||||||
QSet<QString> to_add = new_set;
|
QString key = m_dir.relativeFilePath(removedFile.absoluteFilePath());
|
||||||
to_add -= current_set;
|
|
||||||
|
|
||||||
for (auto remove : to_remove) {
|
|
||||||
qDebug() << "Removing " << remove;
|
|
||||||
QFileInfo rmfile(remove);
|
|
||||||
QString key = rmfile.completeBaseName();
|
|
||||||
|
|
||||||
QString suffix = rmfile.suffix();
|
|
||||||
// The icon doesnt have a suffix, but it can have other .s in the name, so we account for those as well
|
|
||||||
if (!IconUtils::isIconSuffix(suffix))
|
|
||||||
key = rmfile.fileName();
|
|
||||||
|
|
||||||
int idx = getIconIndex(key);
|
int idx = getIconIndex(key);
|
||||||
if (idx == -1)
|
if (idx == -1)
|
||||||
continue;
|
continue;
|
||||||
icons[idx].remove(IconType::FileBased);
|
m_icons[idx].remove(FileBased);
|
||||||
if (icons[idx].type() == IconType::ToBeDeleted) {
|
if (m_icons[idx].type() == ToBeDeleted) {
|
||||||
beginRemoveRows(QModelIndex(), idx, idx);
|
beginRemoveRows(QModelIndex(), idx, idx);
|
||||||
icons.remove(idx);
|
m_icons.remove(idx);
|
||||||
reindex();
|
reindex();
|
||||||
endRemoveRows();
|
endRemoveRows();
|
||||||
} else {
|
} else {
|
||||||
dataChanged(index(idx), index(idx));
|
dataChanged(index(idx), index(idx));
|
||||||
}
|
}
|
||||||
m_watcher->removePath(remove);
|
m_watcher->removePath(removedPath);
|
||||||
emit iconUpdated(key);
|
emit iconUpdated(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto add : to_add) {
|
for (const QString& addedPath : toAdd) {
|
||||||
qDebug() << "Adding " << add;
|
qDebug() << "Adding icon " << addedPath;
|
||||||
|
|
||||||
QFileInfo addfile(add);
|
QFileInfo addfile(addedPath);
|
||||||
QString key = addfile.completeBaseName();
|
QString relativePath = m_dir.relativeFilePath(addfile.absoluteFilePath());
|
||||||
|
QString key = QFileInfo(relativePath).completeBaseName();
|
||||||
|
QString name = formatName(m_dir, addfile);
|
||||||
|
|
||||||
QString suffix = addfile.suffix();
|
if (addIcon(key, name, addfile.filePath(), IconType::FileBased)) {
|
||||||
// The icon doesnt have a suffix, but it can have other .s in the name, so we account for those as well
|
m_watcher->addPath(addedPath);
|
||||||
if (!IconUtils::isIconSuffix(suffix))
|
|
||||||
key = addfile.fileName();
|
|
||||||
|
|
||||||
if (addIcon(key, QString(), addfile.filePath(), IconType::FileBased)) {
|
|
||||||
m_watcher->addPath(add);
|
|
||||||
emit iconUpdated(key);
|
emit iconUpdated(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -171,24 +211,24 @@ void IconList::directoryChanged(const QString& path)
|
||||||
|
|
||||||
void IconList::fileChanged(const QString& path)
|
void IconList::fileChanged(const QString& path)
|
||||||
{
|
{
|
||||||
qDebug() << "Checking " << path;
|
qDebug() << "Checking icon " << path;
|
||||||
QFileInfo checkfile(path);
|
QFileInfo checkfile(path);
|
||||||
if (!checkfile.exists())
|
if (!checkfile.exists())
|
||||||
return;
|
return;
|
||||||
QString key = checkfile.completeBaseName();
|
QString key = m_dir.relativeFilePath(checkfile.absoluteFilePath());
|
||||||
int idx = getIconIndex(key);
|
int idx = getIconIndex(key);
|
||||||
if (idx == -1)
|
if (idx == -1)
|
||||||
return;
|
return;
|
||||||
QIcon icon(path);
|
QIcon icon(path);
|
||||||
if (!icon.availableSizes().size())
|
if (icon.availableSizes().empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
icons[idx].m_images[IconType::FileBased].icon = icon;
|
m_icons[idx].m_images[IconType::FileBased].icon = icon;
|
||||||
dataChanged(index(idx), index(idx));
|
dataChanged(index(idx), index(idx));
|
||||||
emit iconUpdated(key);
|
emit iconUpdated(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
void IconList::SettingChanged(const Setting& setting, QVariant value)
|
void IconList::SettingChanged(const Setting& setting, const QVariant& value)
|
||||||
{
|
{
|
||||||
if (setting.id() != "IconsDir")
|
if (setting.id() != "IconsDir")
|
||||||
return;
|
return;
|
||||||
|
|
@ -200,8 +240,8 @@ void IconList::startWatching()
|
||||||
{
|
{
|
||||||
auto abs_path = m_dir.absolutePath();
|
auto abs_path = m_dir.absolutePath();
|
||||||
FS::ensureFolderPathExists(abs_path);
|
FS::ensureFolderPathExists(abs_path);
|
||||||
is_watching = m_watcher->addPath(abs_path);
|
m_isWatching = addPathRecursively(abs_path);
|
||||||
if (is_watching) {
|
if (m_isWatching) {
|
||||||
qDebug() << "Started watching " << abs_path;
|
qDebug() << "Started watching " << abs_path;
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "Failed to start watching " << abs_path;
|
qDebug() << "Failed to start watching " << abs_path;
|
||||||
|
|
@ -212,7 +252,7 @@ void IconList::stopWatching()
|
||||||
{
|
{
|
||||||
m_watcher->removePaths(m_watcher->files());
|
m_watcher->removePaths(m_watcher->files());
|
||||||
m_watcher->removePaths(m_watcher->directories());
|
m_watcher->removePaths(m_watcher->directories());
|
||||||
is_watching = false;
|
m_isWatching = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList IconList::mimeTypes() const
|
QStringList IconList::mimeTypes() const
|
||||||
|
|
@ -242,7 +282,7 @@ bool IconList::dropMimeData(const QMimeData* data,
|
||||||
if (data->hasUrls()) {
|
if (data->hasUrls()) {
|
||||||
auto urls = data->urls();
|
auto urls = data->urls();
|
||||||
QStringList iconFiles;
|
QStringList iconFiles;
|
||||||
for (auto url : urls) {
|
for (const auto& url : urls) {
|
||||||
// only local files may be dropped...
|
// only local files may be dropped...
|
||||||
if (!url.isLocalFile())
|
if (!url.isLocalFile())
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -263,33 +303,33 @@ Qt::ItemFlags IconList::flags(const QModelIndex& index) const
|
||||||
QVariant IconList::data(const QModelIndex& index, int role) const
|
QVariant IconList::data(const QModelIndex& index, int role) const
|
||||||
{
|
{
|
||||||
if (!index.isValid())
|
if (!index.isValid())
|
||||||
return QVariant();
|
return {};
|
||||||
|
|
||||||
int row = index.row();
|
int row = index.row();
|
||||||
|
|
||||||
if (row < 0 || row >= icons.size())
|
if (row < 0 || row >= m_icons.size())
|
||||||
return QVariant();
|
return {};
|
||||||
|
|
||||||
switch (role) {
|
switch (role) {
|
||||||
case Qt::DecorationRole:
|
case Qt::DecorationRole:
|
||||||
return icons[row].icon();
|
return m_icons[row].icon();
|
||||||
case Qt::DisplayRole:
|
case Qt::DisplayRole:
|
||||||
return icons[row].name();
|
return m_icons[row].name();
|
||||||
case Qt::UserRole:
|
case Qt::UserRole:
|
||||||
return icons[row].m_key;
|
return m_icons[row].m_key;
|
||||||
default:
|
default:
|
||||||
return QVariant();
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int IconList::rowCount(const QModelIndex& parent) const
|
int IconList::rowCount(const QModelIndex& parent) const
|
||||||
{
|
{
|
||||||
return parent.isValid() ? 0 : icons.size();
|
return parent.isValid() ? 0 : m_icons.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
void IconList::installIcons(const QStringList& iconFiles)
|
void IconList::installIcons(const QStringList& iconFiles)
|
||||||
{
|
{
|
||||||
for (QString file : iconFiles)
|
for (const QString& file : iconFiles)
|
||||||
installIcon(file, {});
|
installIcon(file, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -312,12 +352,13 @@ bool IconList::iconFileExists(const QString& key) const
|
||||||
return iconEntry && iconEntry->has(IconType::FileBased);
|
return iconEntry && iconEntry->has(IconType::FileBased);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the icon with the given key or nullptr if it doesn't exist.
|
||||||
const MMCIcon* IconList::icon(const QString& key) const
|
const MMCIcon* IconList::icon(const QString& key) const
|
||||||
{
|
{
|
||||||
int iconIdx = getIconIndex(key);
|
int iconIdx = getIconIndex(key);
|
||||||
if (iconIdx == -1)
|
if (iconIdx == -1)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
return &icons[iconIdx];
|
return &m_icons[iconIdx];
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IconList::deleteIcon(const QString& key)
|
bool IconList::deleteIcon(const QString& key)
|
||||||
|
|
@ -332,22 +373,22 @@ bool IconList::trashIcon(const QString& key)
|
||||||
|
|
||||||
bool IconList::addThemeIcon(const QString& key)
|
bool IconList::addThemeIcon(const QString& key)
|
||||||
{
|
{
|
||||||
auto iter = name_index.find(key);
|
auto iter = m_nameIndex.find(key);
|
||||||
if (iter != name_index.end()) {
|
if (iter != m_nameIndex.end()) {
|
||||||
auto& oldOne = icons[*iter];
|
auto& oldOne = m_icons[*iter];
|
||||||
oldOne.replace(Builtin, key);
|
oldOne.replace(Builtin, key);
|
||||||
dataChanged(index(*iter), index(*iter));
|
dataChanged(index(*iter), index(*iter));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// add a new icon
|
// add a new icon
|
||||||
beginInsertRows(QModelIndex(), icons.size(), icons.size());
|
beginInsertRows(QModelIndex(), m_icons.size(), m_icons.size());
|
||||||
{
|
{
|
||||||
MMCIcon mmc_icon;
|
MMCIcon mmc_icon;
|
||||||
mmc_icon.m_name = key;
|
mmc_icon.m_name = key;
|
||||||
mmc_icon.m_key = key;
|
mmc_icon.m_key = key;
|
||||||
mmc_icon.replace(Builtin, key);
|
mmc_icon.replace(Builtin, key);
|
||||||
icons.push_back(mmc_icon);
|
m_icons.push_back(mmc_icon);
|
||||||
name_index[key] = icons.size() - 1;
|
m_nameIndex[key] = m_icons.size() - 1;
|
||||||
}
|
}
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -359,22 +400,22 @@ bool IconList::addIcon(const QString& key, const QString& name, const QString& p
|
||||||
QIcon icon(path);
|
QIcon icon(path);
|
||||||
if (icon.isNull())
|
if (icon.isNull())
|
||||||
return false;
|
return false;
|
||||||
auto iter = name_index.find(key);
|
auto iter = m_nameIndex.find(key);
|
||||||
if (iter != name_index.end()) {
|
if (iter != m_nameIndex.end()) {
|
||||||
auto& oldOne = icons[*iter];
|
auto& oldOne = m_icons[*iter];
|
||||||
oldOne.replace(type, icon, path);
|
oldOne.replace(type, icon, path);
|
||||||
dataChanged(index(*iter), index(*iter));
|
dataChanged(index(*iter), index(*iter));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// add a new icon
|
// add a new icon
|
||||||
beginInsertRows(QModelIndex(), icons.size(), icons.size());
|
beginInsertRows(QModelIndex(), m_icons.size(), m_icons.size());
|
||||||
{
|
{
|
||||||
MMCIcon mmc_icon;
|
MMCIcon mmc_icon;
|
||||||
mmc_icon.m_name = name;
|
mmc_icon.m_name = name;
|
||||||
mmc_icon.m_key = key;
|
mmc_icon.m_key = key;
|
||||||
mmc_icon.replace(type, icon, path);
|
mmc_icon.replace(type, icon, path);
|
||||||
icons.push_back(mmc_icon);
|
m_icons.push_back(mmc_icon);
|
||||||
name_index[key] = icons.size() - 1;
|
m_nameIndex[key] = m_icons.size() - 1;
|
||||||
}
|
}
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -389,33 +430,32 @@ void IconList::saveIcon(const QString& key, const QString& path, const char* for
|
||||||
|
|
||||||
void IconList::reindex()
|
void IconList::reindex()
|
||||||
{
|
{
|
||||||
name_index.clear();
|
m_nameIndex.clear();
|
||||||
int i = 0;
|
for (int i = 0; i < m_icons.size(); i++) {
|
||||||
for (auto& iter : icons) {
|
m_nameIndex[m_icons[i].m_key] = i;
|
||||||
name_index[iter.m_key] = i;
|
emit iconUpdated(m_icons[i].m_key); // prevents incorrect indices with proxy model
|
||||||
i++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QIcon IconList::getIcon(const QString& key) const
|
QIcon IconList::getIcon(const QString& key) const
|
||||||
{
|
{
|
||||||
int icon_index = getIconIndex(key);
|
int iconIndex = getIconIndex(key);
|
||||||
|
|
||||||
if (icon_index != -1)
|
if (iconIndex != -1)
|
||||||
return icons[icon_index].icon();
|
return m_icons[iconIndex].icon();
|
||||||
|
|
||||||
// Fallback for icons that don't exist.
|
// Fallback for icons that don't exist.b
|
||||||
icon_index = getIconIndex("grass");
|
iconIndex = getIconIndex("grass");
|
||||||
|
|
||||||
if (icon_index != -1)
|
if (iconIndex != -1)
|
||||||
return icons[icon_index].icon();
|
return m_icons[iconIndex].icon();
|
||||||
return QIcon();
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
int IconList::getIconIndex(const QString& key) const
|
int IconList::getIconIndex(const QString& key) const
|
||||||
{
|
{
|
||||||
auto iter = name_index.find(key == "default" ? "grass" : key);
|
auto iter = m_nameIndex.find(key == "default" ? "grass" : key);
|
||||||
if (iter != name_index.end())
|
if (iter != m_nameIndex.end())
|
||||||
return *iter;
|
return *iter;
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
|
|
@ -425,3 +465,15 @@ QString IconList::getDirectory() const
|
||||||
{
|
{
|
||||||
return m_dir.absolutePath();
|
return m_dir.absolutePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the directory of the icon with the given key or the default directory if it's a builtin icon.
|
||||||
|
QString IconList::iconDirectory(const QString& key) const
|
||||||
|
{
|
||||||
|
for (const auto& mmcIcon : m_icons) {
|
||||||
|
if (mmcIcon.m_key == key && mmcIcon.has(IconType::FileBased)) {
|
||||||
|
QFileInfo iconFile(mmcIcon.getFilePath());
|
||||||
|
return iconFile.dir().path();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getDirectory();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ class QFileSystemWatcher;
|
||||||
class IconList : public QAbstractListModel {
|
class IconList : public QAbstractListModel {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit IconList(const QStringList& builtinPaths, QString path, QObject* parent = 0);
|
explicit IconList(const QStringList& builtinPaths, const QString& path, QObject* parent = 0);
|
||||||
virtual ~IconList() {};
|
virtual ~IconList() {};
|
||||||
|
|
||||||
QIcon getIcon(const QString& key) const;
|
QIcon getIcon(const QString& key) const;
|
||||||
|
|
@ -72,6 +72,7 @@ class IconList : public QAbstractListModel {
|
||||||
bool deleteIcon(const QString& key);
|
bool deleteIcon(const QString& key);
|
||||||
bool trashIcon(const QString& key);
|
bool trashIcon(const QString& key);
|
||||||
bool iconFileExists(const QString& key) const;
|
bool iconFileExists(const QString& key) const;
|
||||||
|
QString iconDirectory(const QString& key) const;
|
||||||
|
|
||||||
void installIcons(const QStringList& iconFiles);
|
void installIcons(const QStringList& iconFiles);
|
||||||
void installIcon(const QString& file, const QString& name);
|
void installIcon(const QString& file, const QString& name);
|
||||||
|
|
@ -91,18 +92,20 @@ class IconList : public QAbstractListModel {
|
||||||
IconList& operator=(const IconList&) = delete;
|
IconList& operator=(const IconList&) = delete;
|
||||||
void reindex();
|
void reindex();
|
||||||
void sortIconList();
|
void sortIconList();
|
||||||
|
bool addPathRecursively(const QString& path);
|
||||||
|
QStringList getIconFilePaths() const;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void directoryChanged(const QString& path);
|
void directoryChanged(const QString& path);
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
void fileChanged(const QString& path);
|
void fileChanged(const QString& path);
|
||||||
void SettingChanged(const Setting& setting, QVariant value);
|
void SettingChanged(const Setting& setting, const QVariant& value);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
shared_qobject_ptr<QFileSystemWatcher> m_watcher;
|
shared_qobject_ptr<QFileSystemWatcher> m_watcher;
|
||||||
bool is_watching;
|
bool m_isWatching;
|
||||||
QMap<QString, int> name_index;
|
QMap<QString, int> m_nameIndex;
|
||||||
QVector<MMCIcon> icons;
|
QVector<MMCIcon> m_icons;
|
||||||
QDir m_dir;
|
QDir m_dir;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -44,8 +44,8 @@
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
#include "java/JavaUtils.h"
|
#include "java/JavaUtils.h"
|
||||||
|
|
||||||
JavaChecker::JavaChecker(QString path, QString args, int minMem, int maxMem, int permGen, int id, QObject* parent)
|
JavaChecker::JavaChecker(QString path, QString args, int minMem, int maxMem, int permGen, int id)
|
||||||
: Task(parent), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen), m_id(id)
|
: Task(), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen), m_id(id)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
void JavaChecker::executeTask()
|
void JavaChecker::executeTask()
|
||||||
|
|
@ -171,7 +171,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
|
||||||
auto os_arch = results["os.arch"];
|
auto os_arch = results["os.arch"];
|
||||||
auto java_version = results["java.version"];
|
auto java_version = results["java.version"];
|
||||||
auto java_vendor = results["java.vendor"];
|
auto java_vendor = results["java.vendor"];
|
||||||
bool is_64 = os_arch == "x86_64" || os_arch == "amd64" || os_arch == "aarch64" || os_arch == "arm64";
|
bool is_64 = os_arch == "x86_64" || os_arch == "amd64" || os_arch == "aarch64" || os_arch == "arm64" || os_arch == "riscv64";
|
||||||
|
|
||||||
result.validity = Result::Validity::Valid;
|
result.validity = Result::Validity::Valid;
|
||||||
result.is_64bit = is_64;
|
result.is_64bit = is_64;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include "JavaVersion.h"
|
#include "JavaVersion.h"
|
||||||
#include "QObjectPtr.h"
|
#include "QObjectPtr.h"
|
||||||
|
|
@ -26,7 +25,7 @@ class JavaChecker : public Task {
|
||||||
enum class Validity { Errored, ReturnedInvalidData, Valid } validity = Validity::Errored;
|
enum class Validity { Errored, ReturnedInvalidData, Valid } validity = Validity::Errored;
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit JavaChecker(QString path, QString args, int minMem = 0, int maxMem = 0, int permGen = 0, int id = 0, QObject* parent = 0);
|
explicit JavaChecker(QString path, QString args, int minMem = 0, int maxMem = 0, int permGen = 0, int id = 0);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void checkFinished(const Result& result);
|
void checkFinished(const Result& result);
|
||||||
|
|
|
||||||
|
|
@ -163,7 +163,7 @@ void JavaListLoadTask::executeTask()
|
||||||
JavaUtils ju;
|
JavaUtils ju;
|
||||||
QList<QString> candidate_paths = m_only_managed_versions ? getPrismJavaBundle() : ju.FindJavaPaths();
|
QList<QString> candidate_paths = m_only_managed_versions ? getPrismJavaBundle() : ju.FindJavaPaths();
|
||||||
|
|
||||||
ConcurrentTask::Ptr job(new ConcurrentTask(this, "Java detection", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()));
|
ConcurrentTask::Ptr job(new ConcurrentTask("Java detection", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()));
|
||||||
m_job.reset(job);
|
m_job.reset(job);
|
||||||
connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished);
|
connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished);
|
||||||
connect(m_job.get(), &Task::progress, this, &Task::setProgress);
|
connect(m_job.get(), &Task::progress, this, &Task::setProgress);
|
||||||
|
|
@ -171,7 +171,7 @@ void JavaListLoadTask::executeTask()
|
||||||
qDebug() << "Probing the following Java paths: ";
|
qDebug() << "Probing the following Java paths: ";
|
||||||
int id = 0;
|
int id = 0;
|
||||||
for (QString candidate : candidate_paths) {
|
for (QString candidate : candidate_paths) {
|
||||||
auto checker = new JavaChecker(candidate, "", 0, 0, 0, id, this);
|
auto checker = new JavaChecker(candidate, "", 0, 0, 0, id);
|
||||||
connect(checker, &JavaChecker::checkFinished, [this](const JavaChecker::Result& result) { m_results << result; });
|
connect(checker, &JavaChecker::checkFinished, [this](const JavaChecker::Result& result) { m_results << result; });
|
||||||
job->addTask(Task::Ptr(checker));
|
job->addTask(Task::Ptr(checker));
|
||||||
id++;
|
id++;
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,8 @@ QProcessEnvironment CleanEnviroment()
|
||||||
QString newValue = stripVariableEntries(key, value, rawenv.value("LAUNCHER_" + key));
|
QString newValue = stripVariableEntries(key, value, rawenv.value("LAUNCHER_" + key));
|
||||||
|
|
||||||
qDebug() << "Env: stripped" << key << value << "to" << newValue;
|
qDebug() << "Env: stripped" << key << value << "to" << newValue;
|
||||||
|
|
||||||
|
value = newValue;
|
||||||
}
|
}
|
||||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
||||||
// Strip IBus
|
// Strip IBus
|
||||||
|
|
@ -403,7 +405,7 @@ QList<QString> JavaUtils::FindJavaPaths()
|
||||||
{
|
{
|
||||||
QList<QString> javas;
|
QList<QString> javas;
|
||||||
javas.append(this->GetDefaultJava()->path);
|
javas.append(this->GetDefaultJava()->path);
|
||||||
auto scanJavaDir = [&](
|
auto scanJavaDir = [&javas](
|
||||||
const QString& dirPath,
|
const QString& dirPath,
|
||||||
const std::function<bool(const QFileInfo&)>& filter = [](const QFileInfo&) { return true; }) {
|
const std::function<bool(const QFileInfo&)>& filter = [](const QFileInfo&) { return true; }) {
|
||||||
QDir dir(dirPath);
|
QDir dir(dirPath);
|
||||||
|
|
@ -422,7 +424,7 @@ QList<QString> JavaUtils::FindJavaPaths()
|
||||||
};
|
};
|
||||||
// java installed in a snap is installed in the standard directory, but underneath $SNAP
|
// java installed in a snap is installed in the standard directory, but underneath $SNAP
|
||||||
auto snap = qEnvironmentVariable("SNAP");
|
auto snap = qEnvironmentVariable("SNAP");
|
||||||
auto scanJavaDirs = [&](const QString& dirPath) {
|
auto scanJavaDirs = [scanJavaDir, snap](const QString& dirPath) {
|
||||||
scanJavaDir(dirPath);
|
scanJavaDir(dirPath);
|
||||||
if (!snap.isNull()) {
|
if (!snap.isNull()) {
|
||||||
scanJavaDir(snap + dirPath);
|
scanJavaDir(snap + dirPath);
|
||||||
|
|
@ -440,9 +442,15 @@ QList<QString> JavaUtils::FindJavaPaths()
|
||||||
QString fileName = info.fileName();
|
QString fileName = info.fileName();
|
||||||
return fileName.startsWith("openjdk-") || fileName.startsWith("openj9-");
|
return fileName.startsWith("openjdk-") || fileName.startsWith("openj9-");
|
||||||
};
|
};
|
||||||
|
// AOSC OS's locations for openjdk
|
||||||
|
auto aoscFilter = [](const QFileInfo& info) {
|
||||||
|
QString fileName = info.fileName();
|
||||||
|
return fileName == "java" || fileName.startsWith("java-");
|
||||||
|
};
|
||||||
scanJavaDir("/usr/lib64", gentooFilter);
|
scanJavaDir("/usr/lib64", gentooFilter);
|
||||||
scanJavaDir("/usr/lib", gentooFilter);
|
scanJavaDir("/usr/lib", gentooFilter);
|
||||||
scanJavaDir("/opt", gentooFilter);
|
scanJavaDir("/opt", gentooFilter);
|
||||||
|
scanJavaDir("/usr/lib", aoscFilter);
|
||||||
// javas stored in Prism Launcher's folder
|
// javas stored in Prism Launcher's folder
|
||||||
scanJavaDirs("java");
|
scanJavaDirs("java");
|
||||||
// manually installed JDKs in /opt
|
// manually installed JDKs in /opt
|
||||||
|
|
@ -544,12 +552,12 @@ QStringList getPrismJavaBundle()
|
||||||
{
|
{
|
||||||
QList<QString> javas;
|
QList<QString> javas;
|
||||||
|
|
||||||
auto scanDir = [&](QString prefix) {
|
auto scanDir = [&javas](QString prefix) {
|
||||||
javas.append(FS::PathCombine(prefix, "jre", "bin", JavaUtils::javaExecutable));
|
javas.append(FS::PathCombine(prefix, "jre", "bin", JavaUtils::javaExecutable));
|
||||||
javas.append(FS::PathCombine(prefix, "bin", JavaUtils::javaExecutable));
|
javas.append(FS::PathCombine(prefix, "bin", JavaUtils::javaExecutable));
|
||||||
javas.append(FS::PathCombine(prefix, JavaUtils::javaExecutable));
|
javas.append(FS::PathCombine(prefix, JavaUtils::javaExecutable));
|
||||||
};
|
};
|
||||||
auto scanJavaDir = [&](const QString& dirPath) {
|
auto scanJavaDir = [scanDir](const QString& dirPath) {
|
||||||
QDir dir(dirPath);
|
QDir dir(dirPath);
|
||||||
if (!dir.exists())
|
if (!dir.exists())
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -86,11 +86,10 @@ void ManifestDownloadTask::downloadJava(const QJsonDocument& doc)
|
||||||
if (type == "directory") {
|
if (type == "directory") {
|
||||||
FS::ensureFolderPathExists(file);
|
FS::ensureFolderPathExists(file);
|
||||||
} else if (type == "link") {
|
} else if (type == "link") {
|
||||||
// this is linux only !
|
// this is *nix only !
|
||||||
auto path = Json::ensureString(meta, "target");
|
auto path = Json::ensureString(meta, "target");
|
||||||
if (!path.isEmpty()) {
|
if (!path.isEmpty()) {
|
||||||
auto target = FS::PathCombine(file, "../" + path);
|
QFile::link(path, file);
|
||||||
QFile(target).link(file);
|
|
||||||
}
|
}
|
||||||
} else if (type == "file") {
|
} else if (type == "file") {
|
||||||
// TODO download compressed version if it exists ?
|
// TODO download compressed version if it exists ?
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
#include "LaunchStep.h"
|
#include "LaunchStep.h"
|
||||||
#include "LaunchTask.h"
|
#include "LaunchTask.h"
|
||||||
|
|
||||||
LaunchStep::LaunchStep(LaunchTask* parent) : Task(parent), m_parent(parent)
|
LaunchStep::LaunchStep(LaunchTask* parent) : Task(), m_parent(parent)
|
||||||
{
|
{
|
||||||
connect(this, &LaunchStep::readyForLaunch, parent, &LaunchTask::onReadyForLaunch);
|
connect(this, &LaunchStep::readyForLaunch, parent, &LaunchTask::onReadyForLaunch);
|
||||||
connect(this, &LaunchStep::logLine, parent, &LaunchTask::onLogLine);
|
connect(this, &LaunchStep::logLine, parent, &LaunchTask::onLogLine);
|
||||||
|
|
|
||||||
|
|
@ -254,20 +254,60 @@ void LaunchTask::emitFailed(QString reason)
|
||||||
Task::emitFailed(reason);
|
Task::emitFailed(reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LaunchTask::substituteVariables(QStringList& args) const
|
QString expandVariables(const QString& input, QProcessEnvironment dict)
|
||||||
{
|
{
|
||||||
auto env = m_instance->createEnvironment();
|
QString result = input;
|
||||||
|
|
||||||
for (auto key : env.keys()) {
|
enum { base, maybeBrace, variable, brace } state = base;
|
||||||
args.replaceInStrings("$" + key, env.value(key));
|
int startIdx = -1;
|
||||||
|
for (int i = 0; i < result.length();) {
|
||||||
|
QChar c = result.at(i++);
|
||||||
|
switch (state) {
|
||||||
|
case base:
|
||||||
|
if (c == '$')
|
||||||
|
state = maybeBrace;
|
||||||
|
break;
|
||||||
|
case maybeBrace:
|
||||||
|
if (c == '{') {
|
||||||
|
state = brace;
|
||||||
|
startIdx = i;
|
||||||
|
} else if (c.isLetterOrNumber() || c == '_') {
|
||||||
|
state = variable;
|
||||||
|
startIdx = i - 1;
|
||||||
|
} else {
|
||||||
|
state = base;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case brace:
|
||||||
|
if (c == '}') {
|
||||||
|
const auto res = dict.value(result.mid(startIdx, i - 1 - startIdx), "");
|
||||||
|
if (!res.isEmpty()) {
|
||||||
|
result.replace(startIdx - 2, i - startIdx + 2, res);
|
||||||
|
i = startIdx - 2 + res.length();
|
||||||
|
}
|
||||||
|
state = base;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case variable:
|
||||||
|
if (!c.isLetterOrNumber() && c != '_') {
|
||||||
|
const auto res = dict.value(result.mid(startIdx, i - startIdx - 1), "");
|
||||||
|
if (!res.isEmpty()) {
|
||||||
|
result.replace(startIdx - 1, i - startIdx, res);
|
||||||
|
i = startIdx - 1 + res.length();
|
||||||
|
}
|
||||||
|
state = base;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (state == variable) {
|
||||||
|
if (const auto res = dict.value(result.mid(startIdx), ""); !res.isEmpty())
|
||||||
|
result.replace(startIdx - 1, result.length() - startIdx + 1, res);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LaunchTask::substituteVariables(QString& cmd) const
|
QString LaunchTask::substituteVariables(QString& cmd, bool isLaunch) const
|
||||||
{
|
{
|
||||||
auto env = m_instance->createEnvironment();
|
return expandVariables(cmd, isLaunch ? m_instance->createLaunchEnvironment() : m_instance->createEnvironment());
|
||||||
|
|
||||||
for (auto key : env.keys()) {
|
|
||||||
cmd.replace("$" + key, env.value(key));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -87,8 +87,7 @@ class LaunchTask : public Task {
|
||||||
shared_qobject_ptr<LogModel> getLogModel();
|
shared_qobject_ptr<LogModel> getLogModel();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void substituteVariables(QStringList& args) const;
|
QString substituteVariables(QString& cmd, bool isLaunch = false) const;
|
||||||
void substituteVariables(QString& cmd) const;
|
|
||||||
QString censorPrivateInfo(QString in);
|
QString censorPrivateInfo(QString in);
|
||||||
|
|
||||||
protected: /* methods */
|
protected: /* methods */
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ class LogModel : public QAbstractListModel {
|
||||||
|
|
||||||
private /* types */:
|
private /* types */:
|
||||||
struct entry {
|
struct entry {
|
||||||
MessageLevel::Enum level;
|
MessageLevel::Enum level = MessageLevel::Enum::Unknown;
|
||||||
QString line;
|
QString line;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ void CheckJava::executeTask()
|
||||||
// if timestamps are not the same, or something is missing, check!
|
// if timestamps are not the same, or something is missing, check!
|
||||||
if (m_javaSignature != storedSignature || storedVersion.size() == 0 || storedArchitecture.size() == 0 ||
|
if (m_javaSignature != storedSignature || storedVersion.size() == 0 || storedArchitecture.size() == 0 ||
|
||||||
storedRealArchitecture.size() == 0 || storedVendor.size() == 0) {
|
storedRealArchitecture.size() == 0 || storedVendor.size() == 0) {
|
||||||
m_JavaChecker.reset(new JavaChecker(realJavaPath, "", 0, 0, 0, 0, this));
|
m_JavaChecker.reset(new JavaChecker(realJavaPath, "", 0, 0, 0, 0));
|
||||||
emit logLine(QString("Checking Java version..."), MessageLevel::Launcher);
|
emit logLine(QString("Checking Java version..."), MessageLevel::Launcher);
|
||||||
connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished);
|
connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished);
|
||||||
m_JavaChecker->start();
|
m_JavaChecker->start();
|
||||||
|
|
|
||||||
|
|
@ -47,25 +47,21 @@ PostLaunchCommand::PostLaunchCommand(LaunchTask* parent) : LaunchStep(parent)
|
||||||
|
|
||||||
void PostLaunchCommand::executeTask()
|
void PostLaunchCommand::executeTask()
|
||||||
{
|
{
|
||||||
// FIXME: where to put this?
|
auto cmd = m_parent->substituteVariables(m_command);
|
||||||
|
emit logLine(tr("Running Post-Launch command: %1").arg(cmd), MessageLevel::Launcher);
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||||
auto args = QProcess::splitCommand(m_command);
|
auto args = QProcess::splitCommand(cmd);
|
||||||
m_parent->substituteVariables(args);
|
|
||||||
|
|
||||||
emit logLine(tr("Running Post-Launch command: %1").arg(args.join(' ')), MessageLevel::Launcher);
|
|
||||||
const QString program = args.takeFirst();
|
const QString program = args.takeFirst();
|
||||||
m_process.start(program, args);
|
m_process.start(program, args);
|
||||||
#else
|
#else
|
||||||
m_parent->substituteVariables(m_command);
|
m_process.start(cmd);
|
||||||
|
|
||||||
emit logLine(tr("Running Post-Launch command: %1").arg(m_command), MessageLevel::Launcher);
|
|
||||||
m_process.start(m_command);
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void PostLaunchCommand::on_state(LoggedProcess::State state)
|
void PostLaunchCommand::on_state(LoggedProcess::State state)
|
||||||
{
|
{
|
||||||
auto getError = [&]() { return tr("Post-Launch command failed with code %1.\n\n").arg(m_process.exitCode()); };
|
auto getError = [this]() { return tr("Post-Launch command failed with code %1.\n\n").arg(m_process.exitCode()); };
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case LoggedProcess::Aborted:
|
case LoggedProcess::Aborted:
|
||||||
case LoggedProcess::Crashed:
|
case LoggedProcess::Crashed:
|
||||||
|
|
|
||||||
|
|
@ -47,25 +47,20 @@ PreLaunchCommand::PreLaunchCommand(LaunchTask* parent) : LaunchStep(parent)
|
||||||
|
|
||||||
void PreLaunchCommand::executeTask()
|
void PreLaunchCommand::executeTask()
|
||||||
{
|
{
|
||||||
// FIXME: where to put this?
|
auto cmd = m_parent->substituteVariables(m_command);
|
||||||
|
emit logLine(tr("Running Pre-Launch command: %1").arg(cmd), MessageLevel::Launcher);
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||||
auto args = QProcess::splitCommand(m_command);
|
auto args = QProcess::splitCommand(cmd);
|
||||||
m_parent->substituteVariables(args);
|
|
||||||
|
|
||||||
emit logLine(tr("Running Pre-Launch command: %1").arg(args.join(' ')), MessageLevel::Launcher);
|
|
||||||
const QString program = args.takeFirst();
|
const QString program = args.takeFirst();
|
||||||
m_process.start(program, args);
|
m_process.start(program, args);
|
||||||
#else
|
#else
|
||||||
m_parent->substituteVariables(m_command);
|
m_process.start(cmd);
|
||||||
|
|
||||||
emit logLine(tr("Running Pre-Launch command: %1").arg(m_command), MessageLevel::Launcher);
|
|
||||||
m_process.start(m_command);
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreLaunchCommand::on_state(LoggedProcess::State state)
|
void PreLaunchCommand::on_state(LoggedProcess::State state)
|
||||||
{
|
{
|
||||||
auto getError = [&]() { return tr("Pre-Launch command failed with code %1.\n\n").arg(m_process.exitCode()); };
|
auto getError = [this]() { return tr("Pre-Launch command failed with code %1.\n\n").arg(m_process.exitCode()); };
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case LoggedProcess::Aborted:
|
case LoggedProcess::Aborted:
|
||||||
case LoggedProcess::Crashed:
|
case LoggedProcess::Crashed:
|
||||||
|
|
|
||||||
|
|
@ -140,8 +140,8 @@ Task::Ptr Index::loadVersion(const QString& uid, const QString& version, Net::Mo
|
||||||
}
|
}
|
||||||
|
|
||||||
auto versionList = get(uid);
|
auto versionList = get(uid);
|
||||||
auto loadTask = makeShared<SequentialTask>(
|
auto loadTask =
|
||||||
this, tr("Load meta for %1:%2", "This is for the task name that loads the meta index.").arg(uid, version));
|
makeShared<SequentialTask>(tr("Load meta for %1:%2", "This is for the task name that loads the meta index.").arg(uid, version));
|
||||||
if (status() != BaseEntity::LoadStatus::Remote || force) {
|
if (status() != BaseEntity::LoadStatus::Remote || force) {
|
||||||
loadTask->addTask(this->loadTask(mode));
|
loadTask->addTask(this->loadTask(mode));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,7 @@ VersionList::VersionList(const QString& uid, QObject* parent) : BaseVersionList(
|
||||||
|
|
||||||
Task::Ptr VersionList::getLoadTask()
|
Task::Ptr VersionList::getLoadTask()
|
||||||
{
|
{
|
||||||
auto loadTask =
|
auto loadTask = makeShared<SequentialTask>(tr("Load meta for %1", "This is for the task name that loads the meta index.").arg(m_uid));
|
||||||
makeShared<SequentialTask>(this, tr("Load meta for %1", "This is for the task name that loads the meta index.").arg(m_uid));
|
|
||||||
loadTask->addTask(APPLICATION->metadataIndex()->loadTask(Net::Mode::Online));
|
loadTask->addTask(APPLICATION->metadataIndex()->loadTask(Net::Mode::Online));
|
||||||
loadTask->addTask(this->loadTask(Net::Mode::Online));
|
loadTask->addTask(this->loadTask(Net::Mode::Online));
|
||||||
return loadTask;
|
return loadTask;
|
||||||
|
|
@ -159,8 +158,8 @@ Version::Ptr VersionList::getVersion(const QString& version)
|
||||||
|
|
||||||
bool VersionList::hasVersion(QString version) const
|
bool VersionList::hasVersion(QString version) const
|
||||||
{
|
{
|
||||||
auto ver =
|
auto ver = std::find_if(m_versions.constBegin(), m_versions.constEnd(),
|
||||||
std::find_if(m_versions.constBegin(), m_versions.constEnd(), [&](Meta::Version::Ptr const& a) { return a->version() == version; });
|
[version](Meta::Version::Ptr const& a) { return a->version() == version; });
|
||||||
return (ver != m_versions.constEnd());
|
return (ver != m_versions.constEnd());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -222,11 +222,12 @@ bool Component::isMoveable()
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Component::isVersionChangeable()
|
bool Component::isVersionChangeable(bool wait)
|
||||||
{
|
{
|
||||||
auto list = getVersionList();
|
auto list = getVersionList();
|
||||||
if (list) {
|
if (list) {
|
||||||
list->waitToLoad();
|
if (wait)
|
||||||
|
list->waitToLoad();
|
||||||
return list->count() != 0;
|
return list->count() != 0;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ class Component : public QObject, public ProblemProvider {
|
||||||
bool isRevertible();
|
bool isRevertible();
|
||||||
bool isRemovable();
|
bool isRemovable();
|
||||||
bool isCustom();
|
bool isCustom();
|
||||||
bool isVersionChangeable();
|
bool isVersionChangeable(bool wait = true);
|
||||||
bool isKnownModloader();
|
bool isKnownModloader();
|
||||||
QStringList knownConflictingComponents();
|
QStringList knownConflictingComponents();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@
|
||||||
* If the component list changes, start over.
|
* If the component list changes, start over.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ComponentUpdateTask::ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* list, QObject* parent) : Task(parent)
|
ComponentUpdateTask::ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* list) : Task()
|
||||||
{
|
{
|
||||||
d.reset(new ComponentUpdateTaskData);
|
d.reset(new ComponentUpdateTaskData);
|
||||||
d->m_profile = list;
|
d->m_profile = list;
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ class ComponentUpdateTask : public Task {
|
||||||
enum class Mode { Launch, Resolution };
|
enum class Mode { Launch, Resolution };
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* list, QObject* parent = 0);
|
explicit ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* list);
|
||||||
virtual ~ComponentUpdateTask();
|
virtual ~ComponentUpdateTask();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ void Library::getApplicableFiles(const RuntimeContext& runtimeContext,
|
||||||
{
|
{
|
||||||
bool local = isLocal();
|
bool local = isLocal();
|
||||||
// Lambda function to get the absolute file path
|
// Lambda function to get the absolute file path
|
||||||
auto actualPath = [&](QString relPath) {
|
auto actualPath = [this, local, overridePath](QString relPath) {
|
||||||
relPath = FS::RemoveInvalidPathChars(relPath);
|
relPath = FS::RemoveInvalidPathChars(relPath);
|
||||||
QFileInfo out(FS::PathCombine(storagePrefix(), relPath));
|
QFileInfo out(FS::PathCombine(storagePrefix(), relPath));
|
||||||
if (local && !overridePath.isEmpty()) {
|
if (local && !overridePath.isEmpty()) {
|
||||||
|
|
@ -115,7 +115,7 @@ QList<Net::NetRequest::Ptr> Library::getDownloads(const RuntimeContext& runtimeC
|
||||||
bool local = isLocal();
|
bool local = isLocal();
|
||||||
|
|
||||||
// Lambda function to check if a local file exists
|
// Lambda function to check if a local file exists
|
||||||
auto check_local_file = [&](QString storage) {
|
auto check_local_file = [overridePath, &failedLocalFiles](QString storage) {
|
||||||
QFileInfo fileinfo(storage);
|
QFileInfo fileinfo(storage);
|
||||||
QString fileName = fileinfo.fileName();
|
QString fileName = fileinfo.fileName();
|
||||||
auto fullPath = FS::PathCombine(overridePath, fileName);
|
auto fullPath = FS::PathCombine(overridePath, fileName);
|
||||||
|
|
@ -128,7 +128,7 @@ QList<Net::NetRequest::Ptr> Library::getDownloads(const RuntimeContext& runtimeC
|
||||||
};
|
};
|
||||||
|
|
||||||
// Lambda function to add a download request
|
// Lambda function to add a download request
|
||||||
auto add_download = [&](QString storage, QString url, QString sha1) {
|
auto add_download = [this, local, check_local_file, cache, stale, &out](QString storage, QString url, QString sha1) {
|
||||||
if (local) {
|
if (local) {
|
||||||
return check_local_file(storage);
|
return check_local_file(storage);
|
||||||
}
|
}
|
||||||
|
|
@ -198,7 +198,7 @@ QList<Net::NetRequest::Ptr> Library::getDownloads(const RuntimeContext& runtimeC
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
auto raw_dl = [&]() {
|
auto raw_dl = [this, raw_storage]() {
|
||||||
if (!m_absoluteURL.isEmpty()) {
|
if (!m_absoluteURL.isEmpty()) {
|
||||||
return m_absoluteURL;
|
return m_absoluteURL;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -91,13 +91,56 @@
|
||||||
#include "tools/BaseProfiler.h"
|
#include "tools/BaseProfiler.h"
|
||||||
|
|
||||||
#include <QActionGroup>
|
#include <QActionGroup>
|
||||||
|
#include <QMainWindow>
|
||||||
|
#include <QScreen>
|
||||||
|
#include <QWindow>
|
||||||
|
|
||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
#include "MangoHud.h"
|
#include "MangoHud.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef WITH_QTDBUS
|
||||||
|
#include <QtDBus/QtDBus>
|
||||||
|
#endif
|
||||||
|
|
||||||
#define IBUS "@im=ibus"
|
#define IBUS "@im=ibus"
|
||||||
|
|
||||||
|
static bool switcherooSetupGPU(QProcessEnvironment& env)
|
||||||
|
{
|
||||||
|
#ifdef WITH_QTDBUS
|
||||||
|
if (!QDBusConnection::systemBus().isConnected())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
QDBusInterface switcheroo("net.hadess.SwitcherooControl", "/net/hadess/SwitcherooControl", "org.freedesktop.DBus.Properties",
|
||||||
|
QDBusConnection::systemBus());
|
||||||
|
|
||||||
|
if (!switcheroo.isValid())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
QDBusReply<QDBusVariant> reply =
|
||||||
|
switcheroo.call(QStringLiteral("Get"), QStringLiteral("net.hadess.SwitcherooControl"), QStringLiteral("GPUs"));
|
||||||
|
if (!reply.isValid())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
QDBusArgument arg = qvariant_cast<QDBusArgument>(reply.value().variant());
|
||||||
|
QList<QVariantMap> gpus;
|
||||||
|
arg >> gpus;
|
||||||
|
|
||||||
|
for (const auto& gpu : gpus) {
|
||||||
|
QString name = qvariant_cast<QString>(gpu[QStringLiteral("Name")]);
|
||||||
|
bool defaultGpu = qvariant_cast<bool>(gpu[QStringLiteral("Default")]);
|
||||||
|
if (!defaultGpu) {
|
||||||
|
QStringList envList = qvariant_cast<QStringList>(gpu[QStringLiteral("Environment")]);
|
||||||
|
for (int i = 0; i + 1 < envList.size(); i += 2) {
|
||||||
|
env.insert(envList[i], envList[i + 1]);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// all of this because keeping things compatible with deprecated old settings
|
// all of this because keeping things compatible with deprecated old settings
|
||||||
// if either of the settings {a, b} is true, this also resolves to true
|
// if either of the settings {a, b} is true, this also resolves to true
|
||||||
class OrSetting : public Setting {
|
class OrSetting : public Setting {
|
||||||
|
|
@ -530,7 +573,7 @@ QStringList MinecraftInstance::javaArguments()
|
||||||
QString MinecraftInstance::getLauncher()
|
QString MinecraftInstance::getLauncher()
|
||||||
{
|
{
|
||||||
// use legacy launcher if the traits are set
|
// use legacy launcher if the traits are set
|
||||||
if (traits().contains("legacyLaunch") || traits().contains("alphaLaunch"))
|
if (isLegacy())
|
||||||
return "legacy";
|
return "legacy";
|
||||||
|
|
||||||
return "standard";
|
return "standard";
|
||||||
|
|
@ -551,6 +594,13 @@ QMap<QString, QString> MinecraftInstance::getVariables()
|
||||||
out.insert("INST_JAVA", settings()->get("JavaPath").toString());
|
out.insert("INST_JAVA", settings()->get("JavaPath").toString());
|
||||||
out.insert("INST_JAVA_ARGS", javaArguments().join(' '));
|
out.insert("INST_JAVA_ARGS", javaArguments().join(' '));
|
||||||
out.insert("NO_COLOR", "1");
|
out.insert("NO_COLOR", "1");
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
// get library for Steam overlay support
|
||||||
|
QString steamDyldInsertLibraries = qEnvironmentVariable("STEAM_DYLD_INSERT_LIBRARIES");
|
||||||
|
if (!steamDyldInsertLibraries.isEmpty()) {
|
||||||
|
out.insert("DYLD_INSERT_LIBRARIES", steamDyldInsertLibraries);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -606,6 +656,7 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment()
|
||||||
// dlsym variant is only needed for OpenGL and not included in the vulkan layer
|
// dlsym variant is only needed for OpenGL and not included in the vulkan layer
|
||||||
appendLib("libMangoHud_dlsym.so");
|
appendLib("libMangoHud_dlsym.so");
|
||||||
appendLib("libMangoHud_opengl.so");
|
appendLib("libMangoHud_opengl.so");
|
||||||
|
appendLib("libMangoHud_shim.so");
|
||||||
preloadList << mangoHudLibString;
|
preloadList << mangoHudLibString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -614,12 +665,14 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings()->get("UseDiscreteGpu").toBool()) {
|
if (settings()->get("UseDiscreteGpu").toBool()) {
|
||||||
// Open Source Drivers
|
if (!switcherooSetupGPU(env)) {
|
||||||
env.insert("DRI_PRIME", "1");
|
// Open Source Drivers
|
||||||
// Proprietary Nvidia Drivers
|
env.insert("DRI_PRIME", "1");
|
||||||
env.insert("__NV_PRIME_RENDER_OFFLOAD", "1");
|
// Proprietary Nvidia Drivers
|
||||||
env.insert("__VK_LAYER_NV_optimus", "NVIDIA_only");
|
env.insert("__NV_PRIME_RENDER_OFFLOAD", "1");
|
||||||
env.insert("__GLX_VENDOR_LIBRARY_NAME", "nvidia");
|
env.insert("__VK_LAYER_NV_optimus", "NVIDIA_only");
|
||||||
|
env.insert("__GLX_VENDOR_LIBRARY_NAME", "nvidia");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings()->get("UseZink").toBool()) {
|
if (settings()->get("UseZink").toBool()) {
|
||||||
|
|
@ -752,11 +805,34 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftT
|
||||||
// window size, title and state, legacy
|
// window size, title and state, legacy
|
||||||
{
|
{
|
||||||
QString windowParams;
|
QString windowParams;
|
||||||
if (settings()->get("LaunchMaximized").toBool())
|
if (settings()->get("LaunchMaximized").toBool()) {
|
||||||
windowParams = "maximized";
|
// FIXME doesn't support maximisation
|
||||||
else
|
if (!isLegacy()) {
|
||||||
|
auto screen = QGuiApplication::primaryScreen();
|
||||||
|
auto screenGeometry = screen->availableSize();
|
||||||
|
|
||||||
|
// small hack to get the widow decorations
|
||||||
|
for (auto w : QApplication::topLevelWidgets()) {
|
||||||
|
auto mainWindow = qobject_cast<QMainWindow*>(w);
|
||||||
|
if (mainWindow) {
|
||||||
|
auto m = mainWindow->windowHandle()->frameMargins();
|
||||||
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
||||||
|
screenGeometry = screenGeometry.shrunkBy(m);
|
||||||
|
#else
|
||||||
|
screenGeometry = { screenGeometry.width() - m.left() - m.right(), screenGeometry.height() - m.top() - m.bottom() };
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
windowParams = QString("%1x%2").arg(screenGeometry.width()).arg(screenGeometry.height());
|
||||||
|
} else {
|
||||||
|
windowParams = "maximized";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
windowParams =
|
windowParams =
|
||||||
QString("%1x%2").arg(settings()->get("MinecraftWinWidth").toInt()).arg(settings()->get("MinecraftWinHeight").toInt());
|
QString("%1x%2").arg(settings()->get("MinecraftWinWidth").toInt()).arg(settings()->get("MinecraftWinHeight").toInt());
|
||||||
|
}
|
||||||
launchScript += "windowTitle " + windowTitle() + "\n";
|
launchScript += "windowTitle " + windowTitle() + "\n";
|
||||||
launchScript += "windowParams " + windowParams + "\n";
|
launchScript += "windowParams " + windowParams + "\n";
|
||||||
}
|
}
|
||||||
|
|
@ -828,7 +904,7 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
|
||||||
out << "Libraries:";
|
out << "Libraries:";
|
||||||
QStringList jars, nativeJars;
|
QStringList jars, nativeJars;
|
||||||
profile->getLibraryFiles(runtimeContext(), jars, nativeJars, getLocalLibraryPath(), binRoot());
|
profile->getLibraryFiles(runtimeContext(), jars, nativeJars, getLocalLibraryPath(), binRoot());
|
||||||
auto printLibFile = [&](const QString& path) {
|
auto printLibFile = [&out](const QString& path) {
|
||||||
QFileInfo info(path);
|
QFileInfo info(path);
|
||||||
if (info.exists()) {
|
if (info.exists()) {
|
||||||
out << " " + path;
|
out << " " + path;
|
||||||
|
|
@ -848,7 +924,7 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
|
||||||
}
|
}
|
||||||
|
|
||||||
// mods and core mods
|
// mods and core mods
|
||||||
auto printModList = [&](const QString& label, ModFolderModel& model) {
|
auto printModList = [&out](const QString& label, ModFolderModel& model) {
|
||||||
if (model.size()) {
|
if (model.size()) {
|
||||||
out << QString("%1:").arg(label);
|
out << QString("%1:").arg(label);
|
||||||
auto modList = model.allMods();
|
auto modList = model.allMods();
|
||||||
|
|
@ -1083,17 +1159,10 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
|
||||||
process->appendStep(step);
|
process->appendStep(step);
|
||||||
}
|
}
|
||||||
|
|
||||||
// run pre-launch command if that's needed
|
|
||||||
if (getPreLaunchCommand().size()) {
|
|
||||||
auto step = makeShared<PreLaunchCommand>(pptr);
|
|
||||||
step->setWorkingDirectory(gameRoot());
|
|
||||||
process->appendStep(step);
|
|
||||||
}
|
|
||||||
|
|
||||||
// load meta
|
// load meta
|
||||||
{
|
{
|
||||||
auto mode = session->status != AuthSession::PlayableOffline ? Net::Mode::Online : Net::Mode::Offline;
|
auto mode = session->status != AuthSession::PlayableOffline ? Net::Mode::Online : Net::Mode::Offline;
|
||||||
process->appendStep(makeShared<TaskStepWrapper>(pptr, makeShared<MinecraftLoadAndCheck>(this, mode, pptr)));
|
process->appendStep(makeShared<TaskStepWrapper>(pptr, makeShared<MinecraftLoadAndCheck>(this, mode)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// check java
|
// check java
|
||||||
|
|
@ -1102,6 +1171,13 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
|
||||||
process->appendStep(makeShared<CheckJava>(pptr));
|
process->appendStep(makeShared<CheckJava>(pptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// run pre-launch command if that's needed
|
||||||
|
if (getPreLaunchCommand().size()) {
|
||||||
|
auto step = makeShared<PreLaunchCommand>(pptr);
|
||||||
|
step->setWorkingDirectory(gameRoot());
|
||||||
|
process->appendStep(step);
|
||||||
|
}
|
||||||
|
|
||||||
// if we aren't in offline mode,.
|
// if we aren't in offline mode,.
|
||||||
if (session->status != AuthSession::PlayableOffline) {
|
if (session->status != AuthSession::PlayableOffline) {
|
||||||
if (!session->demo) {
|
if (!session->demo) {
|
||||||
|
|
@ -1177,7 +1253,7 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::loaderModList()
|
||||||
{
|
{
|
||||||
if (!m_loader_mod_list) {
|
if (!m_loader_mod_list) {
|
||||||
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
|
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
|
||||||
m_loader_mod_list.reset(new ModFolderModel(modsRoot(), this, is_indexed));
|
m_loader_mod_list.reset(new ModFolderModel(modsRoot(), this, is_indexed, true));
|
||||||
}
|
}
|
||||||
return m_loader_mod_list;
|
return m_loader_mod_list;
|
||||||
}
|
}
|
||||||
|
|
@ -1186,7 +1262,7 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::coreModList()
|
||||||
{
|
{
|
||||||
if (!m_core_mod_list) {
|
if (!m_core_mod_list) {
|
||||||
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
|
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
|
||||||
m_core_mod_list.reset(new ModFolderModel(coreModsDir(), this, is_indexed));
|
m_core_mod_list.reset(new ModFolderModel(coreModsDir(), this, is_indexed, true));
|
||||||
}
|
}
|
||||||
return m_core_mod_list;
|
return m_core_mod_list;
|
||||||
}
|
}
|
||||||
|
|
@ -1203,7 +1279,8 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::nilModList()
|
||||||
std::shared_ptr<ResourcePackFolderModel> MinecraftInstance::resourcePackList()
|
std::shared_ptr<ResourcePackFolderModel> MinecraftInstance::resourcePackList()
|
||||||
{
|
{
|
||||||
if (!m_resource_pack_list) {
|
if (!m_resource_pack_list) {
|
||||||
m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir(), this));
|
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
|
||||||
|
m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir(), this, is_indexed, true));
|
||||||
}
|
}
|
||||||
return m_resource_pack_list;
|
return m_resource_pack_list;
|
||||||
}
|
}
|
||||||
|
|
@ -1211,7 +1288,8 @@ std::shared_ptr<ResourcePackFolderModel> MinecraftInstance::resourcePackList()
|
||||||
std::shared_ptr<TexturePackFolderModel> MinecraftInstance::texturePackList()
|
std::shared_ptr<TexturePackFolderModel> MinecraftInstance::texturePackList()
|
||||||
{
|
{
|
||||||
if (!m_texture_pack_list) {
|
if (!m_texture_pack_list) {
|
||||||
m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir(), this));
|
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
|
||||||
|
m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir(), this, is_indexed, true));
|
||||||
}
|
}
|
||||||
return m_texture_pack_list;
|
return m_texture_pack_list;
|
||||||
}
|
}
|
||||||
|
|
@ -1219,11 +1297,17 @@ std::shared_ptr<TexturePackFolderModel> MinecraftInstance::texturePackList()
|
||||||
std::shared_ptr<ShaderPackFolderModel> MinecraftInstance::shaderPackList()
|
std::shared_ptr<ShaderPackFolderModel> MinecraftInstance::shaderPackList()
|
||||||
{
|
{
|
||||||
if (!m_shader_pack_list) {
|
if (!m_shader_pack_list) {
|
||||||
m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir(), this));
|
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
|
||||||
|
m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir(), this, is_indexed, true));
|
||||||
}
|
}
|
||||||
return m_shader_pack_list;
|
return m_shader_pack_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QList<std::shared_ptr<ResourceFolderModel>> MinecraftInstance::resourceLists()
|
||||||
|
{
|
||||||
|
return { loaderModList(), coreModList(), nilModList(), resourcePackList(), texturePackList(), shaderPackList() };
|
||||||
|
}
|
||||||
|
|
||||||
std::shared_ptr<WorldList> MinecraftInstance::worldList()
|
std::shared_ptr<WorldList> MinecraftInstance::worldList()
|
||||||
{
|
{
|
||||||
if (!m_world_list) {
|
if (!m_world_list) {
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,7 @@ class MinecraftInstance : public BaseInstance {
|
||||||
std::shared_ptr<ResourcePackFolderModel> resourcePackList();
|
std::shared_ptr<ResourcePackFolderModel> resourcePackList();
|
||||||
std::shared_ptr<TexturePackFolderModel> texturePackList();
|
std::shared_ptr<TexturePackFolderModel> texturePackList();
|
||||||
std::shared_ptr<ShaderPackFolderModel> shaderPackList();
|
std::shared_ptr<ShaderPackFolderModel> shaderPackList();
|
||||||
|
QList<std::shared_ptr<ResourceFolderModel>> resourceLists();
|
||||||
std::shared_ptr<WorldList> worldList();
|
std::shared_ptr<WorldList> worldList();
|
||||||
std::shared_ptr<GameOptions> gameOptionsModel();
|
std::shared_ptr<GameOptions> gameOptionsModel();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,16 @@
|
||||||
#include "MinecraftInstance.h"
|
#include "MinecraftInstance.h"
|
||||||
#include "PackProfile.h"
|
#include "PackProfile.h"
|
||||||
|
|
||||||
MinecraftLoadAndCheck::MinecraftLoadAndCheck(MinecraftInstance* inst, Net::Mode netmode, QObject* parent)
|
MinecraftLoadAndCheck::MinecraftLoadAndCheck(MinecraftInstance* inst, Net::Mode netmode) : m_inst(inst), m_netmode(netmode) {}
|
||||||
: Task(parent), m_inst(inst), m_netmode(netmode)
|
|
||||||
{}
|
|
||||||
|
|
||||||
void MinecraftLoadAndCheck::executeTask()
|
void MinecraftLoadAndCheck::executeTask()
|
||||||
{
|
{
|
||||||
// add offline metadata load task
|
// add offline metadata load task
|
||||||
auto components = m_inst->getPackProfile();
|
auto components = m_inst->getPackProfile();
|
||||||
components->reload(m_netmode);
|
if (auto result = components->reload(m_netmode); !result) {
|
||||||
|
emitFailed(result.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
m_task = components->getCurrentTask();
|
m_task = components->getCurrentTask();
|
||||||
|
|
||||||
if (!m_task) {
|
if (!m_task) {
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ class MinecraftInstance;
|
||||||
class MinecraftLoadAndCheck : public Task {
|
class MinecraftLoadAndCheck : public Task {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit MinecraftLoadAndCheck(MinecraftInstance* inst, Net::Mode netmode, QObject* parent = nullptr);
|
explicit MinecraftLoadAndCheck(MinecraftInstance* inst, Net::Mode netmode);
|
||||||
virtual ~MinecraftLoadAndCheck() = default;
|
virtual ~MinecraftLoadAndCheck() = default;
|
||||||
void executeTask() override;
|
void executeTask() override;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -176,7 +176,7 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument& doc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto readLibs = [&](const char* which, QList<LibraryPtr>& outList) {
|
auto readLibs = [&root, &out, &filename](const char* which, QList<LibraryPtr>& outList) {
|
||||||
for (auto libVal : requireArray(root.value(which))) {
|
for (auto libVal : requireArray(root.value(which))) {
|
||||||
QJsonObject libObj = requireObject(libVal);
|
QJsonObject libObj = requireObject(libVal);
|
||||||
// parse the library
|
// parse the library
|
||||||
|
|
|
||||||
|
|
@ -173,29 +173,32 @@ static bool savePackProfile(const QString& filename, const ComponentContainer& c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the given file into component containers
|
// Read the given file into component containers
|
||||||
static bool loadPackProfile(PackProfile* parent,
|
static PackProfile::Result loadPackProfile(PackProfile* parent,
|
||||||
const QString& filename,
|
const QString& filename,
|
||||||
const QString& componentJsonPattern,
|
const QString& componentJsonPattern,
|
||||||
ComponentContainer& container)
|
ComponentContainer& container)
|
||||||
{
|
{
|
||||||
QFile componentsFile(filename);
|
QFile componentsFile(filename);
|
||||||
if (!componentsFile.exists()) {
|
if (!componentsFile.exists()) {
|
||||||
qCWarning(instanceProfileC) << "Components file" << filename << "doesn't exist. This should never happen.";
|
auto message = QObject::tr("Components file %1 doesn't exist. This should never happen.").arg(filename);
|
||||||
return false;
|
qCWarning(instanceProfileC) << message;
|
||||||
|
return PackProfile::Result::Error(message);
|
||||||
}
|
}
|
||||||
if (!componentsFile.open(QFile::ReadOnly)) {
|
if (!componentsFile.open(QFile::ReadOnly)) {
|
||||||
qCCritical(instanceProfileC) << "Couldn't open" << componentsFile.fileName() << " for reading:" << componentsFile.errorString();
|
auto message = QObject::tr("Couldn't open %1 for reading: %2").arg(componentsFile.fileName(), componentsFile.errorString());
|
||||||
|
qCCritical(instanceProfileC) << message;
|
||||||
qCWarning(instanceProfileC) << "Ignoring overridden order";
|
qCWarning(instanceProfileC) << "Ignoring overridden order";
|
||||||
return false;
|
return PackProfile::Result::Error(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// and it's valid JSON
|
// and it's valid JSON
|
||||||
QJsonParseError error;
|
QJsonParseError error;
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(componentsFile.readAll(), &error);
|
QJsonDocument doc = QJsonDocument::fromJson(componentsFile.readAll(), &error);
|
||||||
if (error.error != QJsonParseError::NoError) {
|
if (error.error != QJsonParseError::NoError) {
|
||||||
qCCritical(instanceProfileC) << "Couldn't parse" << componentsFile.fileName() << ":" << error.errorString();
|
auto message = QObject::tr("Couldn't parse %1 as json: %2").arg(componentsFile.fileName(), error.errorString());
|
||||||
|
qCCritical(instanceProfileC) << message;
|
||||||
qCWarning(instanceProfileC) << "Ignoring overridden order";
|
qCWarning(instanceProfileC) << "Ignoring overridden order";
|
||||||
return false;
|
return PackProfile::Result::Error(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// and then read it and process it if all above is true.
|
// and then read it and process it if all above is true.
|
||||||
|
|
@ -212,11 +215,13 @@ static bool loadPackProfile(PackProfile* parent,
|
||||||
container.append(componentFromJsonV1(parent, componentJsonPattern, comp_obj));
|
container.append(componentFromJsonV1(parent, componentJsonPattern, comp_obj));
|
||||||
}
|
}
|
||||||
} catch ([[maybe_unused]] const JSONValidationError& err) {
|
} catch ([[maybe_unused]] const JSONValidationError& err) {
|
||||||
qCCritical(instanceProfileC) << "Couldn't parse" << componentsFile.fileName() << ": bad file format";
|
auto message = QObject::tr("Couldn't parse %1 : bad file format").arg(componentsFile.fileName());
|
||||||
|
qCCritical(instanceProfileC) << message;
|
||||||
|
qCWarning(instanceProfileC) << "error:" << err.what();
|
||||||
container.clear();
|
container.clear();
|
||||||
return false;
|
return PackProfile::Result::Error(message);
|
||||||
}
|
}
|
||||||
return true;
|
return PackProfile::Result::Success();
|
||||||
}
|
}
|
||||||
|
|
||||||
// END: component file format
|
// END: component file format
|
||||||
|
|
@ -283,44 +288,43 @@ void PackProfile::save_internal()
|
||||||
d->dirty = false;
|
d->dirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PackProfile::load()
|
PackProfile::Result PackProfile::load()
|
||||||
{
|
{
|
||||||
auto filename = componentsFilePath();
|
auto filename = componentsFilePath();
|
||||||
|
|
||||||
// load the new component list and swap it with the current one...
|
// load the new component list and swap it with the current one...
|
||||||
ComponentContainer newComponents;
|
ComponentContainer newComponents;
|
||||||
if (!loadPackProfile(this, filename, patchesPattern(), newComponents)) {
|
if (auto result = loadPackProfile(this, filename, patchesPattern(), newComponents); !result) {
|
||||||
qCritical() << d->m_instance->name() << "|" << "Failed to load the component config";
|
qCritical() << d->m_instance->name() << "|" << "Failed to load the component config";
|
||||||
return false;
|
return result;
|
||||||
} else {
|
|
||||||
// FIXME: actually use fine-grained updates, not this...
|
|
||||||
beginResetModel();
|
|
||||||
// disconnect all the old components
|
|
||||||
for (auto component : d->components) {
|
|
||||||
disconnect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged);
|
|
||||||
}
|
|
||||||
d->components.clear();
|
|
||||||
d->componentIndex.clear();
|
|
||||||
for (auto component : newComponents) {
|
|
||||||
if (d->componentIndex.contains(component->m_uid)) {
|
|
||||||
qWarning() << d->m_instance->name() << "|" << "Ignoring duplicate component entry" << component->m_uid;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
connect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged);
|
|
||||||
d->components.append(component);
|
|
||||||
d->componentIndex[component->m_uid] = component;
|
|
||||||
}
|
|
||||||
endResetModel();
|
|
||||||
d->loaded = true;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
// FIXME: actually use fine-grained updates, not this...
|
||||||
|
beginResetModel();
|
||||||
|
// disconnect all the old components
|
||||||
|
for (auto component : d->components) {
|
||||||
|
disconnect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged);
|
||||||
|
}
|
||||||
|
d->components.clear();
|
||||||
|
d->componentIndex.clear();
|
||||||
|
for (auto component : newComponents) {
|
||||||
|
if (d->componentIndex.contains(component->m_uid)) {
|
||||||
|
qWarning() << d->m_instance->name() << "|" << "Ignoring duplicate component entry" << component->m_uid;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
connect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged);
|
||||||
|
d->components.append(component);
|
||||||
|
d->componentIndex[component->m_uid] = component;
|
||||||
|
}
|
||||||
|
endResetModel();
|
||||||
|
d->loaded = true;
|
||||||
|
return Result::Success();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PackProfile::reload(Net::Mode netmode)
|
PackProfile::Result PackProfile::reload(Net::Mode netmode)
|
||||||
{
|
{
|
||||||
// Do not reload when the update/resolve task is running. It is in control.
|
// Do not reload when the update/resolve task is running. It is in control.
|
||||||
if (d->m_updateTask) {
|
if (d->m_updateTask) {
|
||||||
return;
|
return Result::Success();
|
||||||
}
|
}
|
||||||
|
|
||||||
// flush any scheduled saves to not lose state
|
// flush any scheduled saves to not lose state
|
||||||
|
|
@ -329,9 +333,11 @@ void PackProfile::reload(Net::Mode netmode)
|
||||||
// FIXME: differentiate when a reapply is required by propagating state from components
|
// FIXME: differentiate when a reapply is required by propagating state from components
|
||||||
invalidateLaunchProfile();
|
invalidateLaunchProfile();
|
||||||
|
|
||||||
if (load()) {
|
if (auto result = load(); !result) {
|
||||||
resolve(netmode);
|
return result;
|
||||||
}
|
}
|
||||||
|
resolve(netmode);
|
||||||
|
return Result::Success();
|
||||||
}
|
}
|
||||||
|
|
||||||
Task::Ptr PackProfile::getCurrentTask()
|
Task::Ptr PackProfile::getCurrentTask()
|
||||||
|
|
@ -746,7 +752,7 @@ bool PackProfile::removeComponent_internal(ComponentPtr patch)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: we need a generic way of removing local resources, not just jar mods...
|
// FIXME: we need a generic way of removing local resources, not just jar mods...
|
||||||
auto preRemoveJarMod = [&](LibraryPtr jarMod) -> bool {
|
auto preRemoveJarMod = [this](LibraryPtr jarMod) -> bool {
|
||||||
if (!jarMod->isLocal()) {
|
if (!jarMod->isLocal()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,19 @@ class PackProfile : public QAbstractListModel {
|
||||||
public:
|
public:
|
||||||
enum Columns { NameColumn = 0, VersionColumn, NUM_COLUMNS };
|
enum Columns { NameColumn = 0, VersionColumn, NUM_COLUMNS };
|
||||||
|
|
||||||
|
struct Result {
|
||||||
|
bool success;
|
||||||
|
QString error;
|
||||||
|
|
||||||
|
// Implicit conversion to bool
|
||||||
|
operator bool() const { return success; }
|
||||||
|
|
||||||
|
// Factory methods for convenience
|
||||||
|
static Result Success() { return { true, "" }; }
|
||||||
|
|
||||||
|
static Result Error(const QString& errorMessage) { return { false, errorMessage }; }
|
||||||
|
};
|
||||||
|
|
||||||
explicit PackProfile(MinecraftInstance* instance);
|
explicit PackProfile(MinecraftInstance* instance);
|
||||||
virtual ~PackProfile();
|
virtual ~PackProfile();
|
||||||
|
|
||||||
|
|
@ -102,7 +115,7 @@ class PackProfile : public QAbstractListModel {
|
||||||
bool revertToBase(int index);
|
bool revertToBase(int index);
|
||||||
|
|
||||||
/// reload the list, reload all components, resolve dependencies
|
/// reload the list, reload all components, resolve dependencies
|
||||||
void reload(Net::Mode netmode);
|
Result reload(Net::Mode netmode);
|
||||||
|
|
||||||
// reload all components, resolve dependencies
|
// reload all components, resolve dependencies
|
||||||
void resolve(Net::Mode netmode);
|
void resolve(Net::Mode netmode);
|
||||||
|
|
@ -169,7 +182,7 @@ class PackProfile : public QAbstractListModel {
|
||||||
void disableInteraction(bool disable);
|
void disableInteraction(bool disable);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool load();
|
Result load();
|
||||||
bool installJarMods_internal(QStringList filepaths);
|
bool installJarMods_internal(QStringList filepaths);
|
||||||
bool installCustomJar_internal(QString filepath);
|
bool installCustomJar_internal(QString filepath);
|
||||||
bool installAgents_internal(QStringList filepaths);
|
bool installAgents_internal(QStringList filepaths);
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,6 @@
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QDirIterator>
|
#include <QDirIterator>
|
||||||
#include <QSaveFile>
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
#include <FileSystem.h>
|
#include <FileSystem.h>
|
||||||
|
|
@ -57,6 +56,7 @@
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
|
#include "PSaveFile.h"
|
||||||
|
|
||||||
using std::nullopt;
|
using std::nullopt;
|
||||||
using std::optional;
|
using std::optional;
|
||||||
|
|
@ -183,7 +183,7 @@ bool putLevelDatDataToFS(const QFileInfo& file, QByteArray& data)
|
||||||
if (fullFilePath.isNull()) {
|
if (fullFilePath.isNull()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
QSaveFile f(fullFilePath);
|
PSaveFile f(fullFilePath);
|
||||||
if (!f.open(QIODevice::WriteOnly)) {
|
if (!f.open(QIODevice::WriteOnly)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
|
|
||||||
#include <Application.h>
|
#include <Application.h>
|
||||||
|
|
||||||
AuthFlow::AuthFlow(AccountData* data, Action action, QObject* parent) : Task(parent), m_data(data)
|
AuthFlow::AuthFlow(AccountData* data, Action action) : Task(), m_data(data)
|
||||||
{
|
{
|
||||||
if (data->type == AccountType::MSA) {
|
if (data->type == AccountType::MSA) {
|
||||||
if (action == Action::DeviceCode) {
|
if (action == Action::DeviceCode) {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ class AuthFlow : public Task {
|
||||||
public:
|
public:
|
||||||
enum class Action { Refresh, Login, DeviceCode };
|
enum class Action { Refresh, Login, DeviceCode };
|
||||||
|
|
||||||
explicit AuthFlow(AccountData* data, Action action = Action::Refresh, QObject* parent = 0);
|
explicit AuthFlow(AccountData* data, Action action = Action::Refresh);
|
||||||
virtual ~AuthFlow() = default;
|
virtual ~AuthFlow() = default;
|
||||||
|
|
||||||
void executeTask() override;
|
void executeTask() override;
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,7 @@ shared_qobject_ptr<AuthFlow> MinecraftAccount::login(bool useDeviceCode)
|
||||||
{
|
{
|
||||||
Q_ASSERT(m_currentTask.get() == nullptr);
|
Q_ASSERT(m_currentTask.get() == nullptr);
|
||||||
|
|
||||||
m_currentTask.reset(new AuthFlow(&data, useDeviceCode ? AuthFlow::Action::DeviceCode : AuthFlow::Action::Login, this));
|
m_currentTask.reset(new AuthFlow(&data, useDeviceCode ? AuthFlow::Action::DeviceCode : AuthFlow::Action::Login));
|
||||||
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
||||||
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
|
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
|
||||||
connect(m_currentTask.get(), &Task::aborted, this, [this] { authFailed(tr("Aborted")); });
|
connect(m_currentTask.get(), &Task::aborted, this, [this] { authFailed(tr("Aborted")); });
|
||||||
|
|
@ -135,7 +135,7 @@ shared_qobject_ptr<AuthFlow> MinecraftAccount::refresh()
|
||||||
return m_currentTask;
|
return m_currentTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_currentTask.reset(new AuthFlow(&data, AuthFlow::Action::Refresh, this));
|
m_currentTask.reset(new AuthFlow(&data, AuthFlow::Action::Refresh));
|
||||||
|
|
||||||
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
||||||
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
|
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,7 @@ class MinecraftAccount : public QObject, public Usable {
|
||||||
|
|
||||||
[[nodiscard]] AccountType accountType() const noexcept { return data.type; }
|
[[nodiscard]] AccountType accountType() const noexcept { return data.type; }
|
||||||
|
|
||||||
bool ownsMinecraft() const { return data.type != AccountType::Offline && data.minecraftEntitlement.ownsMinecraft; }
|
bool ownsMinecraft() const { return true; }
|
||||||
|
|
||||||
bool hasProfile() const { return data.profileId().size() != 0; }
|
bool hasProfile() const { return data.profileId().size() != 0; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -74,12 +74,12 @@ void MSADeviceCodeStep::perform()
|
||||||
m_task->setAskRetry(false);
|
m_task->setAskRetry(false);
|
||||||
m_task->addNetAction(m_request);
|
m_task->addNetAction(m_request);
|
||||||
|
|
||||||
connect(m_task.get(), &Task::finished, this, &MSADeviceCodeStep::deviceAutorizationFinished);
|
connect(m_task.get(), &Task::finished, this, &MSADeviceCodeStep::deviceAuthorizationFinished);
|
||||||
|
|
||||||
m_task->start();
|
m_task->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DeviceAutorizationResponse {
|
struct DeviceAuthorizationResponse {
|
||||||
QString device_code;
|
QString device_code;
|
||||||
QString user_code;
|
QString user_code;
|
||||||
QString verification_uri;
|
QString verification_uri;
|
||||||
|
|
@ -90,17 +90,17 @@ struct DeviceAutorizationResponse {
|
||||||
QString error_description;
|
QString error_description;
|
||||||
};
|
};
|
||||||
|
|
||||||
DeviceAutorizationResponse parseDeviceAutorizationResponse(const QByteArray& data)
|
DeviceAuthorizationResponse parseDeviceAuthorizationResponse(const QByteArray& data)
|
||||||
{
|
{
|
||||||
QJsonParseError err;
|
QJsonParseError err;
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(data, &err);
|
QJsonDocument doc = QJsonDocument::fromJson(data, &err);
|
||||||
if (err.error != QJsonParseError::NoError) {
|
if (err.error != QJsonParseError::NoError) {
|
||||||
qWarning() << "Failed to parse device autorization response due to err:" << err.errorString();
|
qWarning() << "Failed to parse device authorization response due to err:" << err.errorString();
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!doc.isObject()) {
|
if (!doc.isObject()) {
|
||||||
qWarning() << "Device autorization response is not an object";
|
qWarning() << "Device authorization response is not an object";
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
auto obj = doc.object();
|
auto obj = doc.object();
|
||||||
|
|
@ -111,9 +111,9 @@ DeviceAutorizationResponse parseDeviceAutorizationResponse(const QByteArray& dat
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void MSADeviceCodeStep::deviceAutorizationFinished()
|
void MSADeviceCodeStep::deviceAuthorizationFinished()
|
||||||
{
|
{
|
||||||
auto rsp = parseDeviceAutorizationResponse(*m_response);
|
auto rsp = parseDeviceAuthorizationResponse(*m_response);
|
||||||
if (!rsp.error.isEmpty() || !rsp.error_description.isEmpty()) {
|
if (!rsp.error.isEmpty() || !rsp.error_description.isEmpty()) {
|
||||||
qWarning() << "Device authorization failed:" << rsp.error;
|
qWarning() << "Device authorization failed:" << rsp.error;
|
||||||
emit finished(AccountTaskState::STATE_FAILED_HARD,
|
emit finished(AccountTaskState::STATE_FAILED_HARD,
|
||||||
|
|
@ -208,12 +208,12 @@ AuthenticationResponse parseAuthenticationResponse(const QByteArray& data)
|
||||||
QJsonParseError err;
|
QJsonParseError err;
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(data, &err);
|
QJsonDocument doc = QJsonDocument::fromJson(data, &err);
|
||||||
if (err.error != QJsonParseError::NoError) {
|
if (err.error != QJsonParseError::NoError) {
|
||||||
qWarning() << "Failed to parse device autorization response due to err:" << err.errorString();
|
qWarning() << "Failed to parse device authorization response due to err:" << err.errorString();
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!doc.isObject()) {
|
if (!doc.isObject()) {
|
||||||
qWarning() << "Device autorization response is not an object";
|
qWarning() << "Device authorization response is not an object";
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
auto obj = doc.object();
|
auto obj = doc.object();
|
||||||
|
|
@ -274,4 +274,4 @@ void MSADeviceCodeStep::authenticationFinished()
|
||||||
m_data->msaToken.refresh_token = rsp.refresh_token;
|
m_data->msaToken.refresh_token = rsp.refresh_token;
|
||||||
m_data->msaToken.token = rsp.access_token;
|
m_data->msaToken.token = rsp.access_token;
|
||||||
emit finished(AccountTaskState::STATE_WORKING, tr("Got"));
|
emit finished(AccountTaskState::STATE_WORKING, tr("Got"));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ class MSADeviceCodeStep : public AuthStep {
|
||||||
void authorizeWithBrowser(QString url, QString code, int expiresIn);
|
void authorizeWithBrowser(QString url, QString code, int expiresIn);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void deviceAutorizationFinished();
|
void deviceAuthorizationFinished();
|
||||||
void startPoolTimer();
|
void startPoolTimer();
|
||||||
void authenticateUser();
|
void authenticateUser();
|
||||||
void authenticationFinished();
|
void authenticationFinished();
|
||||||
|
|
|
||||||
|
|
@ -85,8 +85,7 @@ class CustomOAuthOobReplyHandler : public QOAuthOobReplyHandler {
|
||||||
MSAStep::MSAStep(AccountData* data, bool silent) : AuthStep(data), m_silent(silent)
|
MSAStep::MSAStep(AccountData* data, bool silent) : AuthStep(data), m_silent(silent)
|
||||||
{
|
{
|
||||||
m_clientId = APPLICATION->getMSAClientID();
|
m_clientId = APPLICATION->getMSAClientID();
|
||||||
if (QCoreApplication::applicationFilePath().startsWith("/tmp/.mount_") ||
|
if (QCoreApplication::applicationFilePath().startsWith("/tmp/.mount_") || APPLICATION->isPortable() || !isSchemeHandlerRegistered())
|
||||||
QFile::exists(FS::PathCombine(APPLICATION->root(), "portable.txt")) || !isSchemeHandlerRegistered())
|
|
||||||
|
|
||||||
{
|
{
|
||||||
auto replyHandler = new QOAuthHttpServerReplyHandler(this);
|
auto replyHandler = new QOAuthHttpServerReplyHandler(this);
|
||||||
|
|
|
||||||
|
|
@ -57,9 +57,7 @@
|
||||||
#include "tasks/SequentialTask.h"
|
#include "tasks/SequentialTask.h"
|
||||||
|
|
||||||
AutoInstallJava::AutoInstallJava(LaunchTask* parent)
|
AutoInstallJava::AutoInstallJava(LaunchTask* parent)
|
||||||
: LaunchStep(parent)
|
: LaunchStep(parent), m_instance(m_parent->instance()), m_supported_arch(SysInfo::getSupportedJavaArchitecture()) {};
|
||||||
, m_instance(m_parent->instance())
|
|
||||||
, m_supported_arch(SysInfo::getSupportedJavaArchitecture()) {};
|
|
||||||
|
|
||||||
void AutoInstallJava::executeTask()
|
void AutoInstallJava::executeTask()
|
||||||
{
|
{
|
||||||
|
|
@ -78,7 +76,7 @@ void AutoInstallJava::executeTask()
|
||||||
auto java = std::dynamic_pointer_cast<JavaInstall>(javas->at(i));
|
auto java = std::dynamic_pointer_cast<JavaInstall>(javas->at(i));
|
||||||
if (java && packProfile->getProfile()->getCompatibleJavaMajors().contains(java->id.major())) {
|
if (java && packProfile->getProfile()->getCompatibleJavaMajors().contains(java->id.major())) {
|
||||||
if (!java->is_64bit) {
|
if (!java->is_64bit) {
|
||||||
emit logLine(tr("The automatic Java mechanism detected a 32-bit installation of Java."), MessageLevel::Info);
|
emit logLine(tr("The automatic Java mechanism detected a 32-bit installation of Java."), MessageLevel::Launcher);
|
||||||
}
|
}
|
||||||
setJavaPath(java->path);
|
setJavaPath(java->path);
|
||||||
return;
|
return;
|
||||||
|
|
@ -136,7 +134,7 @@ void AutoInstallJava::setJavaPath(QString path)
|
||||||
settings->set("OverrideJavaLocation", true);
|
settings->set("OverrideJavaLocation", true);
|
||||||
settings->set("JavaPath", path);
|
settings->set("JavaPath", path);
|
||||||
settings->set("AutomaticJava", true);
|
settings->set("AutomaticJava", true);
|
||||||
emit logLine(tr("Compatible Java found at: %1.").arg(path), MessageLevel::Info);
|
emit logLine(tr("Compatible Java found at: %1.").arg(path), MessageLevel::Launcher);
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,7 +177,7 @@ void AutoInstallJava::downloadJava(Meta::Version::Ptr version, QString javaName)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#if defined(Q_OS_MACOS)
|
#if defined(Q_OS_MACOS)
|
||||||
auto seq = makeShared<SequentialTask>(this, tr("Install Java"));
|
auto seq = makeShared<SequentialTask>(tr("Install Java"));
|
||||||
seq->addTask(m_current_task);
|
seq->addTask(m_current_task);
|
||||||
seq->addTask(makeShared<Java::SymlinkTask>(final_path));
|
seq->addTask(makeShared<Java::SymlinkTask>(final_path));
|
||||||
m_current_task = seq;
|
m_current_task = seq;
|
||||||
|
|
|
||||||
|
|
@ -54,8 +54,8 @@ LauncherPartLaunch::LauncherPartLaunch(LaunchTask* parent)
|
||||||
{
|
{
|
||||||
if (parent->instance()->settings()->get("CloseAfterLaunch").toBool()) {
|
if (parent->instance()->settings()->get("CloseAfterLaunch").toBool()) {
|
||||||
std::shared_ptr<QMetaObject::Connection> connection{ new QMetaObject::Connection };
|
std::shared_ptr<QMetaObject::Connection> connection{ new QMetaObject::Connection };
|
||||||
*connection =
|
*connection = connect(
|
||||||
connect(&m_process, &LoggedProcess::log, this, [=](const QStringList& lines, [[maybe_unused]] MessageLevel::Enum level) {
|
&m_process, &LoggedProcess::log, this, [connection](const QStringList& lines, [[maybe_unused]] MessageLevel::Enum level) {
|
||||||
qDebug() << lines;
|
qDebug() << lines;
|
||||||
if (lines.filter(QRegularExpression(".*Setting user.+", QRegularExpression::CaseInsensitiveOption)).length() != 0) {
|
if (lines.filter(QRegularExpression(".*Setting user.+", QRegularExpression::CaseInsensitiveOption)).length() != 0) {
|
||||||
APPLICATION->closeAllWindows();
|
APPLICATION->closeAllWindows();
|
||||||
|
|
@ -131,6 +131,7 @@ void LauncherPartLaunch::executeTask()
|
||||||
|
|
||||||
QString wrapperCommandStr = instance->getWrapperCommand().trimmed();
|
QString wrapperCommandStr = instance->getWrapperCommand().trimmed();
|
||||||
if (!wrapperCommandStr.isEmpty()) {
|
if (!wrapperCommandStr.isEmpty()) {
|
||||||
|
wrapperCommandStr = m_parent->substituteVariables(wrapperCommandStr);
|
||||||
auto wrapperArgs = Commandline::splitArgs(wrapperCommandStr);
|
auto wrapperArgs = Commandline::splitArgs(wrapperCommandStr);
|
||||||
auto wrapperCommand = wrapperArgs.takeFirst();
|
auto wrapperCommand = wrapperArgs.takeFirst();
|
||||||
auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand);
|
auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand);
|
||||||
|
|
@ -170,6 +171,7 @@ void LauncherPartLaunch::on_state(LoggedProcess::State state)
|
||||||
case LoggedProcess::Aborted:
|
case LoggedProcess::Aborted:
|
||||||
case LoggedProcess::Crashed: {
|
case LoggedProcess::Crashed: {
|
||||||
m_parent->setPid(-1);
|
m_parent->setPid(-1);
|
||||||
|
m_parent->instance()->setMinecraftRunning(false);
|
||||||
emitFailed(tr("Game crashed."));
|
emitFailed(tr("Game crashed."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,33 +26,48 @@
|
||||||
// launcher/minecraft/mod/Mod.h
|
// launcher/minecraft/mod/Mod.h
|
||||||
class Mod;
|
class Mod;
|
||||||
|
|
||||||
/* Abstraction file for easily changing the way metadata is stored / handled
|
namespace Metadata {
|
||||||
* Needs to be a class because of -Wunused-function and no C++17 [[maybe_unused]]
|
using ModStruct = Packwiz::V1::Mod;
|
||||||
* */
|
using ModSide = Packwiz::V1::Side;
|
||||||
class Metadata {
|
|
||||||
public:
|
|
||||||
using ModStruct = Packwiz::V1::Mod;
|
|
||||||
using ModSide = Packwiz::V1::Side;
|
|
||||||
|
|
||||||
static auto create(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> ModStruct
|
inline auto create(const QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> ModStruct
|
||||||
{
|
{
|
||||||
return Packwiz::V1::createModFormat(index_dir, mod_pack, mod_version);
|
return Packwiz::V1::createModFormat(index_dir, mod_pack, mod_version);
|
||||||
}
|
}
|
||||||
|
|
||||||
static auto create(QDir& index_dir, Mod& internal_mod, QString mod_slug) -> ModStruct
|
inline auto create(const QDir& index_dir, Mod& internal_mod, QString mod_slug) -> ModStruct
|
||||||
{
|
{
|
||||||
return Packwiz::V1::createModFormat(index_dir, internal_mod, mod_slug);
|
return Packwiz::V1::createModFormat(index_dir, internal_mod, std::move(mod_slug));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void update(QDir& index_dir, ModStruct& mod) { Packwiz::V1::updateModIndex(index_dir, mod); }
|
inline void update(const QDir& index_dir, ModStruct& mod)
|
||||||
|
{
|
||||||
|
Packwiz::V1::updateModIndex(index_dir, mod);
|
||||||
|
}
|
||||||
|
|
||||||
static void remove(QDir& index_dir, QString mod_slug) { Packwiz::V1::deleteModIndex(index_dir, mod_slug); }
|
inline void remove(const QDir& index_dir, QString mod_slug)
|
||||||
|
{
|
||||||
|
Packwiz::V1::deleteModIndex(index_dir, mod_slug);
|
||||||
|
}
|
||||||
|
|
||||||
static void remove(QDir& index_dir, QVariant& mod_id) { Packwiz::V1::deleteModIndex(index_dir, mod_id); }
|
inline void remove(const QDir& index_dir, QVariant& mod_id)
|
||||||
|
{
|
||||||
|
Packwiz::V1::deleteModIndex(index_dir, mod_id);
|
||||||
|
}
|
||||||
|
|
||||||
static auto get(QDir& index_dir, QString mod_slug) -> ModStruct { return Packwiz::V1::getIndexForMod(index_dir, mod_slug); }
|
inline auto get(const QDir& index_dir, QString mod_slug) -> ModStruct
|
||||||
|
{
|
||||||
|
return Packwiz::V1::getIndexForMod(index_dir, std::move(mod_slug));
|
||||||
|
}
|
||||||
|
|
||||||
static auto get(QDir& index_dir, QVariant& mod_id) -> ModStruct { return Packwiz::V1::getIndexForMod(index_dir, mod_id); }
|
inline auto get(const QDir& index_dir, QVariant& mod_id) -> ModStruct
|
||||||
|
{
|
||||||
|
return Packwiz::V1::getIndexForMod(index_dir, mod_id);
|
||||||
|
}
|
||||||
|
|
||||||
static auto modSideToString(ModSide side) -> QString { return Packwiz::V1::sideToString(side); }
|
inline auto modSideToString(ModSide side) -> QString
|
||||||
};
|
{
|
||||||
|
return Packwiz::V1::sideToString(side);
|
||||||
|
}
|
||||||
|
|
||||||
|
}; // namespace Metadata
|
||||||
|
|
|
||||||
|
|
@ -38,16 +38,15 @@
|
||||||
#include "Mod.h"
|
#include "Mod.h"
|
||||||
#include <qpixmap.h>
|
#include <qpixmap.h>
|
||||||
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
#include "MTPixmapCache.h"
|
#include "MTPixmapCache.h"
|
||||||
#include "MetadataHandler.h"
|
#include "MetadataHandler.h"
|
||||||
|
#include "Resource.h"
|
||||||
#include "Version.h"
|
#include "Version.h"
|
||||||
#include "minecraft/mod/ModDetails.h"
|
#include "minecraft/mod/ModDetails.h"
|
||||||
#include "minecraft/mod/Resource.h"
|
|
||||||
#include "minecraft/mod/tasks/LocalModParseTask.h"
|
#include "minecraft/mod/tasks/LocalModParseTask.h"
|
||||||
#include "modplatform/ModIndex.h"
|
#include "modplatform/ModIndex.h"
|
||||||
|
|
||||||
|
|
@ -56,24 +55,6 @@ Mod::Mod(const QFileInfo& file) : Resource(file), m_local_details()
|
||||||
m_enabled = (file.suffix() != "disabled");
|
m_enabled = (file.suffix() != "disabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
Mod::Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata) : Mod(mods_dir.absoluteFilePath(metadata.filename))
|
|
||||||
{
|
|
||||||
m_name = metadata.name;
|
|
||||||
m_local_details.metadata = std::make_shared<Metadata::ModStruct>(std::move(metadata));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Mod::setStatus(ModStatus status)
|
|
||||||
{
|
|
||||||
m_local_details.status = status;
|
|
||||||
}
|
|
||||||
void Mod::setMetadata(std::shared_ptr<Metadata::ModStruct>&& metadata)
|
|
||||||
{
|
|
||||||
if (status() == ModStatus::NoMetadata)
|
|
||||||
setStatus(ModStatus::Installed);
|
|
||||||
|
|
||||||
m_local_details.metadata = metadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Mod::setDetails(const ModDetails& details)
|
void Mod::setDetails(const ModDetails& details)
|
||||||
{
|
{
|
||||||
m_local_details = details;
|
m_local_details = details;
|
||||||
|
|
@ -101,33 +82,28 @@ int Mod::compare(const Resource& other, SortType type) const
|
||||||
return -1;
|
return -1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SortType::PROVIDER: {
|
|
||||||
return QString::compare(provider().value_or("Unknown"), cast_other->provider().value_or("Unknown"), Qt::CaseInsensitive);
|
|
||||||
}
|
|
||||||
case SortType::SIDE: {
|
case SortType::SIDE: {
|
||||||
if (side() > cast_other->side())
|
auto compare_result = QString::compare(side(), cast_other->side(), Qt::CaseInsensitive);
|
||||||
return 1;
|
if (compare_result != 0)
|
||||||
else if (side() < cast_other->side())
|
return compare_result;
|
||||||
return -1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SortType::LOADERS: {
|
|
||||||
if (loaders() > cast_other->loaders())
|
|
||||||
return 1;
|
|
||||||
else if (loaders() < cast_other->loaders())
|
|
||||||
return -1;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SortType::MC_VERSIONS: {
|
case SortType::MC_VERSIONS: {
|
||||||
auto thisVersion = mcVersions().join(",");
|
auto compare_result = QString::compare(mcVersions(), cast_other->mcVersions(), Qt::CaseInsensitive);
|
||||||
auto otherVersion = cast_other->mcVersions().join(",");
|
if (compare_result != 0)
|
||||||
return QString::compare(thisVersion, otherVersion, Qt::CaseInsensitive);
|
return compare_result;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SortType::LOADERS: {
|
||||||
|
auto compare_result = QString::compare(loaders(), cast_other->loaders(), Qt::CaseInsensitive);
|
||||||
|
if (compare_result != 0)
|
||||||
|
return compare_result;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
case SortType::RELEASE_TYPE: {
|
case SortType::RELEASE_TYPE: {
|
||||||
if (releaseType() > cast_other->releaseType())
|
auto compare_result = QString::compare(releaseType(), cast_other->releaseType(), Qt::CaseInsensitive);
|
||||||
return 1;
|
if (compare_result != 0)
|
||||||
else if (releaseType() < cast_other->releaseType())
|
return compare_result;
|
||||||
return -1;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -148,28 +124,6 @@ bool Mod::applyFilter(QRegularExpression filter) const
|
||||||
return Resource::applyFilter(filter);
|
return Resource::applyFilter(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Mod::destroy(QDir& index_dir, bool preserve_metadata, bool attempt_trash) -> bool
|
|
||||||
{
|
|
||||||
if (!preserve_metadata) {
|
|
||||||
qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name());
|
|
||||||
|
|
||||||
destroyMetadata(index_dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Resource::destroy(attempt_trash);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Mod::destroyMetadata(QDir& index_dir)
|
|
||||||
{
|
|
||||||
if (metadata()) {
|
|
||||||
Metadata::remove(index_dir, metadata()->slug);
|
|
||||||
} else {
|
|
||||||
auto n = name();
|
|
||||||
Metadata::remove(index_dir, n);
|
|
||||||
}
|
|
||||||
m_local_details.metadata = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Mod::details() const -> const ModDetails&
|
auto Mod::details() const -> const ModDetails&
|
||||||
{
|
{
|
||||||
return m_local_details;
|
return m_local_details;
|
||||||
|
|
@ -181,10 +135,7 @@ auto Mod::name() const -> QString
|
||||||
if (!d_name.isEmpty())
|
if (!d_name.isEmpty())
|
||||||
return d_name;
|
return d_name;
|
||||||
|
|
||||||
if (metadata())
|
return Resource::name();
|
||||||
return metadata()->name;
|
|
||||||
|
|
||||||
return m_name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Mod::version() const -> QString
|
auto Mod::version() const -> QString
|
||||||
|
|
@ -192,16 +143,52 @@ auto Mod::version() const -> QString
|
||||||
return details().version;
|
return details().version;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Mod::homeurl() const -> QString
|
auto Mod::homepage() const -> QString
|
||||||
{
|
{
|
||||||
return details().homeurl;
|
QString metaUrl = Resource::homepage();
|
||||||
|
|
||||||
|
if (metaUrl.isEmpty())
|
||||||
|
return details().homeurl;
|
||||||
|
else
|
||||||
|
return metaUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Mod::metaurl() const -> QString
|
auto Mod::loaders() const -> QString
|
||||||
{
|
{
|
||||||
if (metadata() == nullptr)
|
if (metadata()) {
|
||||||
return homeurl();
|
QStringList loaders;
|
||||||
return ModPlatform::getMetaURL(metadata()->provider, metadata()->project_id);
|
auto modLoaders = metadata()->loaders;
|
||||||
|
for (auto loader : ModPlatform::modLoaderTypesToList(modLoaders)) {
|
||||||
|
loaders << getModLoaderAsString(loader);
|
||||||
|
}
|
||||||
|
return loaders.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Mod::side() const -> QString
|
||||||
|
{
|
||||||
|
if (metadata())
|
||||||
|
return Metadata::modSideToString(metadata()->side);
|
||||||
|
|
||||||
|
return Metadata::modSideToString(Metadata::ModSide::UniversalSide);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Mod::mcVersions() const -> QString
|
||||||
|
{
|
||||||
|
if (metadata())
|
||||||
|
return metadata()->mcVersions.join(", ");
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Mod::releaseType() const -> QString
|
||||||
|
{
|
||||||
|
if (metadata())
|
||||||
|
return metadata()->releaseType.toString();
|
||||||
|
|
||||||
|
return ModPlatform::IndexedVersionType().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Mod::description() const -> QString
|
auto Mod::description() const -> QString
|
||||||
|
|
@ -214,73 +201,17 @@ auto Mod::authors() const -> QStringList
|
||||||
return details().authors;
|
return details().authors;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Mod::status() const -> ModStatus
|
|
||||||
{
|
|
||||||
return details().status;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Mod::metadata() -> std::shared_ptr<Metadata::ModStruct>
|
|
||||||
{
|
|
||||||
return m_local_details.metadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Mod::metadata() const -> const std::shared_ptr<Metadata::ModStruct>
|
|
||||||
{
|
|
||||||
return m_local_details.metadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Mod::finishResolvingWithDetails(ModDetails&& details)
|
void Mod::finishResolvingWithDetails(ModDetails&& details)
|
||||||
{
|
{
|
||||||
m_is_resolving = false;
|
m_is_resolving = false;
|
||||||
m_is_resolved = true;
|
m_is_resolved = true;
|
||||||
|
|
||||||
std::shared_ptr<Metadata::ModStruct> metadata = details.metadata;
|
|
||||||
if (details.status == ModStatus::Unknown)
|
|
||||||
details.status = m_local_details.status;
|
|
||||||
|
|
||||||
m_local_details = std::move(details);
|
m_local_details = std::move(details);
|
||||||
if (metadata)
|
|
||||||
setMetadata(std::move(metadata));
|
|
||||||
if (!iconPath().isEmpty()) {
|
if (!iconPath().isEmpty()) {
|
||||||
m_packImageCacheKey.wasReadAttempt = false;
|
m_packImageCacheKey.wasReadAttempt = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Mod::provider() const -> std::optional<QString>
|
|
||||||
{
|
|
||||||
if (metadata())
|
|
||||||
return ModPlatform::ProviderCapabilities::readableName(metadata()->provider);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Mod::side() const -> Metadata::ModSide
|
|
||||||
{
|
|
||||||
if (metadata())
|
|
||||||
return metadata()->side;
|
|
||||||
return Metadata::ModSide::UniversalSide;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Mod::releaseType() const -> ModPlatform::IndexedVersionType
|
|
||||||
{
|
|
||||||
if (metadata())
|
|
||||||
return metadata()->releaseType;
|
|
||||||
return ModPlatform::IndexedVersionType::VersionType::Unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Mod::loaders() const -> ModPlatform::ModLoaderTypes
|
|
||||||
{
|
|
||||||
if (metadata())
|
|
||||||
return metadata()->loaders;
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Mod::mcVersions() const -> QStringList
|
|
||||||
{
|
|
||||||
if (metadata())
|
|
||||||
return metadata()->mcVersions;
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Mod::licenses() const -> const QList<ModLicense>&
|
auto Mod::licenses() const -> const QList<ModLicense>&
|
||||||
{
|
{
|
||||||
return details().licenses;
|
return details().licenses;
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,6 @@
|
||||||
|
|
||||||
#include "ModDetails.h"
|
#include "ModDetails.h"
|
||||||
#include "Resource.h"
|
#include "Resource.h"
|
||||||
#include "modplatform/ModIndex.h"
|
|
||||||
|
|
||||||
class Mod : public Resource {
|
class Mod : public Resource {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
@ -58,24 +57,20 @@ class Mod : public Resource {
|
||||||
|
|
||||||
Mod() = default;
|
Mod() = default;
|
||||||
Mod(const QFileInfo& file);
|
Mod(const QFileInfo& file);
|
||||||
Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata);
|
|
||||||
Mod(QString file_path) : Mod(QFileInfo(file_path)) {}
|
Mod(QString file_path) : Mod(QFileInfo(file_path)) {}
|
||||||
|
|
||||||
auto details() const -> const ModDetails&;
|
auto details() const -> const ModDetails&;
|
||||||
auto name() const -> QString override;
|
auto name() const -> QString override;
|
||||||
auto version() const -> QString;
|
auto version() const -> QString;
|
||||||
auto homeurl() const -> QString;
|
auto homepage() const -> QString override;
|
||||||
auto description() const -> QString;
|
auto description() const -> QString;
|
||||||
auto authors() const -> QStringList;
|
auto authors() const -> QStringList;
|
||||||
auto status() const -> ModStatus;
|
|
||||||
auto provider() const -> std::optional<QString>;
|
|
||||||
auto licenses() const -> const QList<ModLicense>&;
|
auto licenses() const -> const QList<ModLicense>&;
|
||||||
auto issueTracker() const -> QString;
|
auto issueTracker() const -> QString;
|
||||||
auto metaurl() const -> QString;
|
auto side() const -> QString;
|
||||||
auto side() const -> Metadata::ModSide;
|
auto loaders() const -> QString;
|
||||||
auto loaders() const -> ModPlatform::ModLoaderTypes;
|
auto mcVersions() const -> QString;
|
||||||
auto mcVersions() const -> QStringList;
|
auto releaseType() const -> QString;
|
||||||
auto releaseType() const -> ModPlatform::IndexedVersionType;
|
|
||||||
|
|
||||||
/** Get the intneral path to the mod's icon file*/
|
/** Get the intneral path to the mod's icon file*/
|
||||||
QString iconPath() const { return m_local_details.icon_file; }
|
QString iconPath() const { return m_local_details.icon_file; }
|
||||||
|
|
@ -84,17 +79,11 @@ class Mod : public Resource {
|
||||||
/** Thread-safe. */
|
/** Thread-safe. */
|
||||||
QPixmap setIcon(QImage new_image) const;
|
QPixmap setIcon(QImage new_image) const;
|
||||||
|
|
||||||
auto metadata() -> std::shared_ptr<Metadata::ModStruct>;
|
|
||||||
auto metadata() const -> const std::shared_ptr<Metadata::ModStruct>;
|
|
||||||
|
|
||||||
void setStatus(ModStatus status);
|
|
||||||
void setMetadata(std::shared_ptr<Metadata::ModStruct>&& metadata);
|
|
||||||
void setMetadata(const Metadata::ModStruct& metadata) { setMetadata(std::make_shared<Metadata::ModStruct>(metadata)); }
|
|
||||||
void setDetails(const ModDetails& details);
|
void setDetails(const ModDetails& details);
|
||||||
|
|
||||||
bool valid() const override;
|
bool valid() const override;
|
||||||
|
|
||||||
[[nodiscard]] int compare(Resource const& other, SortType type) const override;
|
[[nodiscard]] int compare(const Resource & other, SortType type) const override;
|
||||||
[[nodiscard]] bool applyFilter(QRegularExpression filter) const override;
|
[[nodiscard]] bool applyFilter(QRegularExpression filter) const override;
|
||||||
|
|
||||||
// Delete all the files of this mod
|
// Delete all the files of this mod
|
||||||
|
|
|
||||||
|
|
@ -43,13 +43,6 @@
|
||||||
|
|
||||||
#include "minecraft/mod/MetadataHandler.h"
|
#include "minecraft/mod/MetadataHandler.h"
|
||||||
|
|
||||||
enum class ModStatus {
|
|
||||||
Installed, // Both JAR and Metadata are present
|
|
||||||
NotInstalled, // Only the Metadata is present
|
|
||||||
NoMetadata, // Only the JAR is present
|
|
||||||
Unknown, // Default status
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ModLicense {
|
struct ModLicense {
|
||||||
QString name = {};
|
QString name = {};
|
||||||
QString id = {};
|
QString id = {};
|
||||||
|
|
@ -149,12 +142,6 @@ struct ModDetails {
|
||||||
/* Path of mod logo */
|
/* Path of mod logo */
|
||||||
QString icon_file = {};
|
QString icon_file = {};
|
||||||
|
|
||||||
/* Installation status of the mod */
|
|
||||||
ModStatus status = ModStatus::Unknown;
|
|
||||||
|
|
||||||
/* Metadata information, if any */
|
|
||||||
std::shared_ptr<Metadata::ModStruct> metadata = nullptr;
|
|
||||||
|
|
||||||
ModDetails() = default;
|
ModDetails() = default;
|
||||||
|
|
||||||
/** Metadata should be handled manually to properly set the mod status. */
|
/** Metadata should be handled manually to properly set the mod status. */
|
||||||
|
|
@ -169,40 +156,9 @@ struct ModDetails {
|
||||||
, issue_tracker(other.issue_tracker)
|
, issue_tracker(other.issue_tracker)
|
||||||
, licenses(other.licenses)
|
, licenses(other.licenses)
|
||||||
, icon_file(other.icon_file)
|
, icon_file(other.icon_file)
|
||||||
, status(other.status)
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
ModDetails& operator=(const ModDetails& other)
|
ModDetails& operator=(const ModDetails& other) = default;
|
||||||
{
|
|
||||||
this->mod_id = other.mod_id;
|
|
||||||
this->name = other.name;
|
|
||||||
this->version = other.version;
|
|
||||||
this->mcversion = other.mcversion;
|
|
||||||
this->homeurl = other.homeurl;
|
|
||||||
this->description = other.description;
|
|
||||||
this->authors = other.authors;
|
|
||||||
this->issue_tracker = other.issue_tracker;
|
|
||||||
this->licenses = other.licenses;
|
|
||||||
this->icon_file = other.icon_file;
|
|
||||||
this->status = other.status;
|
|
||||||
|
|
||||||
return *this;
|
ModDetails& operator=(ModDetails&& other) = default;
|
||||||
}
|
|
||||||
|
|
||||||
ModDetails& operator=(const ModDetails&& other)
|
|
||||||
{
|
|
||||||
this->mod_id = other.mod_id;
|
|
||||||
this->name = other.name;
|
|
||||||
this->version = other.version;
|
|
||||||
this->mcversion = other.mcversion;
|
|
||||||
this->homeurl = other.homeurl;
|
|
||||||
this->description = other.description;
|
|
||||||
this->authors = other.authors;
|
|
||||||
this->issue_tracker = other.issue_tracker;
|
|
||||||
this->licenses = other.licenses;
|
|
||||||
this->icon_file = other.icon_file;
|
|
||||||
this->status = other.status;
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -51,18 +51,10 @@
|
||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
|
||||||
#include "Json.h"
|
|
||||||
#include "minecraft/mod/MetadataHandler.h"
|
|
||||||
#include "minecraft/mod/Resource.h"
|
|
||||||
#include "minecraft/mod/tasks/LocalModParseTask.h"
|
#include "minecraft/mod/tasks/LocalModParseTask.h"
|
||||||
#include "minecraft/mod/tasks/LocalModUpdateTask.h"
|
|
||||||
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
|
|
||||||
#include "modplatform/ModIndex.h"
|
|
||||||
#include "modplatform/flame/FlameAPI.h"
|
|
||||||
#include "modplatform/flame/FlameModIndex.h"
|
|
||||||
|
|
||||||
ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir)
|
ModFolderModel::ModFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent)
|
||||||
: ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed)
|
: ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent)
|
||||||
{
|
{
|
||||||
m_column_names = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider", "Size", "Side", "Loaders",
|
m_column_names = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider", "Size", "Side", "Loaders",
|
||||||
"Minecraft Versions", "Release Type" });
|
"Minecraft Versions", "Release Type" });
|
||||||
|
|
@ -92,7 +84,7 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
|
||||||
case NameColumn:
|
case NameColumn:
|
||||||
return m_resources[row]->name();
|
return m_resources[row]->name();
|
||||||
case VersionColumn: {
|
case VersionColumn: {
|
||||||
switch (m_resources[row]->type()) {
|
switch (at(row).type()) {
|
||||||
case ResourceType::FOLDER:
|
case ResourceType::FOLDER:
|
||||||
return tr("Folder");
|
return tr("Folder");
|
||||||
case ResourceType::SINGLEFILE:
|
case ResourceType::SINGLEFILE:
|
||||||
|
|
@ -100,64 +92,50 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return at(row)->version();
|
return at(row).version();
|
||||||
}
|
}
|
||||||
case DateColumn:
|
case DateColumn:
|
||||||
return m_resources[row]->dateTimeChanged();
|
return at(row).dateTimeChanged();
|
||||||
case ProviderColumn: {
|
case ProviderColumn: {
|
||||||
auto provider = at(row)->provider();
|
return at(row).provider();
|
||||||
if (!provider.has_value()) {
|
|
||||||
//: Unknown mod provider (i.e. not Modrinth, CurseForge, etc...)
|
|
||||||
return tr("Unknown");
|
|
||||||
}
|
|
||||||
|
|
||||||
return provider.value();
|
|
||||||
}
|
}
|
||||||
case SideColumn: {
|
case SideColumn: {
|
||||||
return Metadata::modSideToString(at(row)->side());
|
return at(row).side();
|
||||||
}
|
}
|
||||||
case LoadersColumn: {
|
case LoadersColumn: {
|
||||||
QStringList loaders;
|
return at(row).loaders();
|
||||||
auto modLoaders = at(row)->loaders();
|
|
||||||
for (auto loader : { ModPlatform::NeoForge, ModPlatform::Forge, ModPlatform::Cauldron, ModPlatform::LiteLoader,
|
|
||||||
ModPlatform::Fabric, ModPlatform::Quilt }) {
|
|
||||||
if (modLoaders & loader) {
|
|
||||||
loaders << getModLoaderAsString(loader);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return loaders.join(", ");
|
|
||||||
}
|
}
|
||||||
case McVersionsColumn: {
|
case McVersionsColumn: {
|
||||||
return at(row)->mcVersions().join(", ");
|
return at(row).mcVersions();
|
||||||
}
|
}
|
||||||
case ReleaseTypeColumn: {
|
case ReleaseTypeColumn: {
|
||||||
return at(row)->releaseType().toString();
|
return at(row).releaseType();
|
||||||
}
|
}
|
||||||
case SizeColumn:
|
case SizeColumn:
|
||||||
return m_resources[row]->sizeStr();
|
return at(row).sizeStr();
|
||||||
default:
|
default:
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
case Qt::ToolTipRole:
|
case Qt::ToolTipRole:
|
||||||
if (column == NameColumn) {
|
if (column == NameColumn) {
|
||||||
if (at(row)->isSymLinkUnder(instDirPath())) {
|
if (at(row).isSymLinkUnder(instDirPath())) {
|
||||||
return m_resources[row]->internal_id() +
|
return m_resources[row]->internal_id() +
|
||||||
tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
|
tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
|
||||||
"\nCanonical Path: %1")
|
"\nCanonical Path: %1")
|
||||||
.arg(at(row)->fileinfo().canonicalFilePath());
|
.arg(at(row).fileinfo().canonicalFilePath());
|
||||||
}
|
}
|
||||||
if (at(row)->isMoreThanOneHardLink()) {
|
if (at(row).isMoreThanOneHardLink()) {
|
||||||
return m_resources[row]->internal_id() +
|
return m_resources[row]->internal_id() +
|
||||||
tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original.");
|
tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return m_resources[row]->internal_id();
|
return m_resources[row]->internal_id();
|
||||||
case Qt::DecorationRole: {
|
case Qt::DecorationRole: {
|
||||||
if (column == NameColumn && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink()))
|
if (column == NameColumn && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink()))
|
||||||
return APPLICATION->getThemedIcon("status-yellow");
|
return APPLICATION->getThemedIcon("status-yellow");
|
||||||
if (column == ImageColumn) {
|
if (column == ImageColumn) {
|
||||||
return at(row)->icon({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
|
return at(row).icon({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
@ -169,7 +147,7 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
|
||||||
case Qt::CheckStateRole:
|
case Qt::CheckStateRole:
|
||||||
switch (column) {
|
switch (column) {
|
||||||
case ActiveColumn:
|
case ActiveColumn:
|
||||||
return at(row)->enabled() ? Qt::Checked : Qt::Unchecked;
|
return at(row).enabled() ? Qt::Checked : Qt::Unchecked;
|
||||||
default:
|
default:
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
@ -210,7 +188,7 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio
|
||||||
case DateColumn:
|
case DateColumn:
|
||||||
return tr("The date and time this mod was last changed (or added).");
|
return tr("The date and time this mod was last changed (or added).");
|
||||||
case ProviderColumn:
|
case ProviderColumn:
|
||||||
return tr("Where the mod was downloaded from.");
|
return tr("The source provider of the mod.");
|
||||||
case SideColumn:
|
case SideColumn:
|
||||||
return tr("On what environment the mod is running.");
|
return tr("On what environment the mod is running.");
|
||||||
case LoadersColumn:
|
case LoadersColumn:
|
||||||
|
|
@ -235,133 +213,16 @@ int ModFolderModel::columnCount(const QModelIndex& parent) const
|
||||||
return parent.isValid() ? 0 : NUM_COLUMNS;
|
return parent.isValid() ? 0 : NUM_COLUMNS;
|
||||||
}
|
}
|
||||||
|
|
||||||
Task* ModFolderModel::createUpdateTask()
|
|
||||||
{
|
|
||||||
auto index_dir = indexDir();
|
|
||||||
auto task = new ModFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load);
|
|
||||||
m_first_folder_load = false;
|
|
||||||
return task;
|
|
||||||
}
|
|
||||||
|
|
||||||
Task* ModFolderModel::createParseTask(Resource& resource)
|
Task* ModFolderModel::createParseTask(Resource& resource)
|
||||||
{
|
{
|
||||||
return new LocalModParseTask(m_next_resolution_ticket, resource.type(), resource.fileinfo());
|
return new LocalModParseTask(m_next_resolution_ticket, resource.type(), resource.fileinfo());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ModFolderModel::uninstallMod(const QString& filename, bool preserve_metadata)
|
|
||||||
{
|
|
||||||
for (auto mod : allMods()) {
|
|
||||||
if (mod->getOriginalFileName() == filename) {
|
|
||||||
auto index_dir = indexDir();
|
|
||||||
mod->destroy(index_dir, preserve_metadata, false);
|
|
||||||
|
|
||||||
update();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ModFolderModel::deleteMods(const QModelIndexList& indexes)
|
|
||||||
{
|
|
||||||
if (indexes.isEmpty())
|
|
||||||
return true;
|
|
||||||
|
|
||||||
for (auto i : indexes) {
|
|
||||||
if (i.column() != 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
auto m = at(i.row());
|
|
||||||
auto index_dir = indexDir();
|
|
||||||
m->destroy(index_dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
update();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ModFolderModel::deleteModsMetadata(const QModelIndexList& indexes)
|
|
||||||
{
|
|
||||||
if (indexes.isEmpty())
|
|
||||||
return true;
|
|
||||||
|
|
||||||
for (auto i : indexes) {
|
|
||||||
if (i.column() != 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
auto m = at(i.row());
|
|
||||||
auto index_dir = indexDir();
|
|
||||||
m->destroyMetadata(index_dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
update();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ModFolderModel::isValid()
|
bool ModFolderModel::isValid()
|
||||||
{
|
{
|
||||||
return m_dir.exists() && m_dir.isReadable();
|
return m_dir.exists() && m_dir.isReadable();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ModFolderModel::startWatching()
|
|
||||||
{
|
|
||||||
// Remove orphaned metadata next time
|
|
||||||
m_first_folder_load = true;
|
|
||||||
return ResourceFolderModel::startWatching({ m_dir.absolutePath(), indexDir().absolutePath() });
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ModFolderModel::stopWatching()
|
|
||||||
{
|
|
||||||
return ResourceFolderModel::stopWatching({ m_dir.absolutePath(), indexDir().absolutePath() });
|
|
||||||
}
|
|
||||||
|
|
||||||
auto ModFolderModel::selectedMods(QModelIndexList& indexes) -> QList<Mod*>
|
|
||||||
{
|
|
||||||
QList<Mod*> selected_resources;
|
|
||||||
for (auto i : indexes) {
|
|
||||||
if (i.column() != 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
selected_resources.push_back(at(i.row()));
|
|
||||||
}
|
|
||||||
return selected_resources;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto ModFolderModel::allMods() -> QList<Mod*>
|
|
||||||
{
|
|
||||||
QList<Mod*> mods;
|
|
||||||
|
|
||||||
for (auto& res : qAsConst(m_resources)) {
|
|
||||||
mods.append(static_cast<Mod*>(res.get()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return mods;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModFolderModel::onUpdateSucceeded()
|
|
||||||
{
|
|
||||||
auto update_results = static_cast<ModFolderLoadTask*>(m_current_update_task.get())->result();
|
|
||||||
|
|
||||||
auto& new_mods = update_results->mods;
|
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
|
||||||
auto current_list = m_resources_index.keys();
|
|
||||||
QSet<QString> current_set(current_list.begin(), current_list.end());
|
|
||||||
|
|
||||||
auto new_list = new_mods.keys();
|
|
||||||
QSet<QString> new_set(new_list.begin(), new_list.end());
|
|
||||||
#else
|
|
||||||
QSet<QString> current_set(m_resources_index.keys().toSet());
|
|
||||||
QSet<QString> new_set(new_mods.keys().toSet());
|
|
||||||
#endif
|
|
||||||
|
|
||||||
applyUpdates(current_set, new_set, new_mods);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModFolderModel::onParseSucceeded(int ticket, QString mod_id)
|
void ModFolderModel::onParseSucceeded(int ticket, QString mod_id)
|
||||||
{
|
{
|
||||||
auto iter = m_active_parse_tasks.constFind(ticket);
|
auto iter = m_active_parse_tasks.constFind(ticket);
|
||||||
|
|
@ -379,51 +240,7 @@ void ModFolderModel::onParseSucceeded(int ticket, QString mod_id)
|
||||||
|
|
||||||
auto result = cast_task->result();
|
auto result = cast_task->result();
|
||||||
if (result && resource)
|
if (result && resource)
|
||||||
resource->finishResolvingWithDetails(std::move(result->details));
|
static_cast<Mod*>(resource.get())->finishResolvingWithDetails(std::move(result->details));
|
||||||
|
|
||||||
emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1));
|
emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
static const FlameAPI flameAPI;
|
|
||||||
bool ModFolderModel::installMod(QString file_path, ModPlatform::IndexedVersion& vers)
|
|
||||||
{
|
|
||||||
if (vers.addonId.isValid()) {
|
|
||||||
ModPlatform::IndexedPack pack{
|
|
||||||
vers.addonId,
|
|
||||||
ModPlatform::ResourceProvider::FLAME,
|
|
||||||
};
|
|
||||||
|
|
||||||
QEventLoop loop;
|
|
||||||
|
|
||||||
auto response = std::make_shared<QByteArray>();
|
|
||||||
auto job = flameAPI.getProject(vers.addonId.toString(), response);
|
|
||||||
|
|
||||||
QObject::connect(job.get(), &Task::failed, [&loop] { loop.quit(); });
|
|
||||||
QObject::connect(job.get(), &Task::aborted, &loop, &QEventLoop::quit);
|
|
||||||
QObject::connect(job.get(), &Task::succeeded, [response, this, &vers, &loop, &pack] {
|
|
||||||
QJsonParseError parse_error{};
|
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
|
||||||
if (parse_error.error != QJsonParseError::NoError) {
|
|
||||||
qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset
|
|
||||||
<< " reason: " << parse_error.errorString();
|
|
||||||
qDebug() << *response;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
auto obj = Json::requireObject(Json::requireObject(doc), "data");
|
|
||||||
FlameMod::loadIndexedPack(pack, obj);
|
|
||||||
} catch (const JSONValidationError& e) {
|
|
||||||
qDebug() << doc;
|
|
||||||
qWarning() << "Error while reading mod info: " << e.cause();
|
|
||||||
}
|
|
||||||
LocalModUpdateTask update_metadata(indexDir(), pack, vers);
|
|
||||||
QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit);
|
|
||||||
update_metadata.start();
|
|
||||||
});
|
|
||||||
|
|
||||||
job->start();
|
|
||||||
|
|
||||||
loop.exec();
|
|
||||||
}
|
|
||||||
return ResourceFolderModel::installResource(file_path);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -47,11 +47,6 @@
|
||||||
#include "Mod.h"
|
#include "Mod.h"
|
||||||
#include "ResourceFolderModel.h"
|
#include "ResourceFolderModel.h"
|
||||||
|
|
||||||
#include "minecraft/mod/tasks/LocalModParseTask.h"
|
|
||||||
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
|
|
||||||
#include "modplatform/ModIndex.h"
|
|
||||||
|
|
||||||
class LegacyInstance;
|
|
||||||
class BaseInstance;
|
class BaseInstance;
|
||||||
class QFileSystemWatcher;
|
class QFileSystemWatcher;
|
||||||
|
|
||||||
|
|
@ -76,8 +71,7 @@ class ModFolderModel : public ResourceFolderModel {
|
||||||
ReleaseTypeColumn,
|
ReleaseTypeColumn,
|
||||||
NUM_COLUMNS
|
NUM_COLUMNS
|
||||||
};
|
};
|
||||||
enum ModStatusAction { Disable, Enable, Toggle };
|
ModFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr);
|
||||||
ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed = false, bool create_dir = true);
|
|
||||||
|
|
||||||
virtual QString id() const override { return "mods"; }
|
virtual QString id() const override { return "mods"; }
|
||||||
|
|
||||||
|
|
@ -86,34 +80,13 @@ class ModFolderModel : public ResourceFolderModel {
|
||||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||||
int columnCount(const QModelIndex& parent) const override;
|
int columnCount(const QModelIndex& parent) const override;
|
||||||
|
|
||||||
[[nodiscard]] Task* createUpdateTask() override;
|
[[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new Mod(file); }
|
||||||
[[nodiscard]] Task* createParseTask(Resource&) override;
|
[[nodiscard]] Task* createParseTask(Resource&) override;
|
||||||
|
|
||||||
bool installMod(QString file_path) { return ResourceFolderModel::installResource(file_path); }
|
|
||||||
bool installMod(QString file_path, ModPlatform::IndexedVersion& vers);
|
|
||||||
bool uninstallMod(const QString& filename, bool preserve_metadata = false);
|
|
||||||
|
|
||||||
/// Deletes all the selected mods
|
|
||||||
bool deleteMods(const QModelIndexList& indexes);
|
|
||||||
bool deleteModsMetadata(const QModelIndexList& indexes);
|
|
||||||
|
|
||||||
bool isValid();
|
bool isValid();
|
||||||
|
|
||||||
bool startWatching() override;
|
|
||||||
bool stopWatching() override;
|
|
||||||
|
|
||||||
QDir indexDir() { return { QString("%1/.index").arg(dir().absolutePath()) }; }
|
|
||||||
|
|
||||||
auto selectedMods(QModelIndexList& indexes) -> QList<Mod*>;
|
|
||||||
auto allMods() -> QList<Mod*>;
|
|
||||||
|
|
||||||
RESOURCE_HELPERS(Mod)
|
RESOURCE_HELPERS(Mod)
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onUpdateSucceeded() override;
|
|
||||||
void onParseSucceeded(int ticket, QString resource_id) override;
|
void onParseSucceeded(int ticket, QString resource_id) override;
|
||||||
|
|
||||||
protected:
|
|
||||||
bool m_is_indexed;
|
|
||||||
bool m_first_folder_load = true;
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ void Resource::setFile(QFileInfo file_info)
|
||||||
parseFile();
|
parseFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::tuple<QString, qint64> calculateFileSize(const QFileInfo& file)
|
static std::tuple<QString, qint64> calculateFileSize(const QFileInfo& file)
|
||||||
{
|
{
|
||||||
if (file.isDir()) {
|
if (file.isDir()) {
|
||||||
auto dir = QDir(file.absoluteFilePath());
|
auto dir = QDir(file.absoluteFilePath());
|
||||||
|
|
@ -72,6 +72,14 @@ void Resource::parseFile()
|
||||||
m_changed_date_time = m_file_info.lastModified();
|
m_changed_date_time = m_file_info.lastModified();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto Resource::name() const -> QString
|
||||||
|
{
|
||||||
|
if (metadata())
|
||||||
|
return metadata()->name;
|
||||||
|
|
||||||
|
return m_name;
|
||||||
|
}
|
||||||
|
|
||||||
static void removeThePrefix(QString& string)
|
static void removeThePrefix(QString& string)
|
||||||
{
|
{
|
||||||
QRegularExpression regex(QStringLiteral("^(?:the|teh) +"), QRegularExpression::CaseInsensitiveOption);
|
QRegularExpression regex(QStringLiteral("^(?:the|teh) +"), QRegularExpression::CaseInsensitiveOption);
|
||||||
|
|
@ -79,6 +87,30 @@ static void removeThePrefix(QString& string)
|
||||||
string = string.trimmed();
|
string = string.trimmed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto Resource::provider() const -> QString
|
||||||
|
{
|
||||||
|
if (metadata())
|
||||||
|
return ModPlatform::ProviderCapabilities::readableName(metadata()->provider);
|
||||||
|
|
||||||
|
return tr("Unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Resource::homepage() const -> QString
|
||||||
|
{
|
||||||
|
if (metadata())
|
||||||
|
return ModPlatform::getMetaURL(metadata()->provider, metadata()->project_id);
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Resource::setMetadata(std::shared_ptr<Metadata::ModStruct>&& metadata)
|
||||||
|
{
|
||||||
|
if (status() == ResourceStatus::NO_METADATA)
|
||||||
|
setStatus(ResourceStatus::INSTALLED);
|
||||||
|
|
||||||
|
m_metadata = metadata;
|
||||||
|
}
|
||||||
|
|
||||||
int Resource::compare(const Resource& other, SortType type) const
|
int Resource::compare(const Resource& other, SortType type) const
|
||||||
{
|
{
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
|
@ -93,6 +125,7 @@ int Resource::compare(const Resource& other, SortType type) const
|
||||||
QString this_name{ name() };
|
QString this_name{ name() };
|
||||||
QString other_name{ other.name() };
|
QString other_name{ other.name() };
|
||||||
|
|
||||||
|
// TODO do we need this? it could result in 0 being returned
|
||||||
removeThePrefix(this_name);
|
removeThePrefix(this_name);
|
||||||
removeThePrefix(other_name);
|
removeThePrefix(other_name);
|
||||||
|
|
||||||
|
|
@ -118,6 +151,12 @@ int Resource::compare(const Resource& other, SortType type) const
|
||||||
return -1;
|
return -1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case SortType::PROVIDER: {
|
||||||
|
auto compare_result = QString::compare(provider(), other.provider(), Qt::CaseInsensitive);
|
||||||
|
if (compare_result != 0)
|
||||||
|
return compare_result;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -174,10 +213,27 @@ bool Resource::enable(EnableAction action)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Resource::destroy(bool attemptTrash)
|
auto Resource::destroy(const QDir& index_dir, bool preserve_metadata, bool attempt_trash) -> bool
|
||||||
{
|
{
|
||||||
m_type = ResourceType::UNKNOWN;
|
m_type = ResourceType::UNKNOWN;
|
||||||
return (attemptTrash && FS::trash(m_file_info.filePath())) || FS::deletePath(m_file_info.filePath());
|
|
||||||
|
if (!preserve_metadata) {
|
||||||
|
qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name());
|
||||||
|
destroyMetadata(index_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (attempt_trash && FS::trash(m_file_info.filePath())) || FS::deletePath(m_file_info.filePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Resource::destroyMetadata(const QDir& index_dir) -> void
|
||||||
|
{
|
||||||
|
if (metadata()) {
|
||||||
|
Metadata::remove(index_dir, metadata()->slug);
|
||||||
|
} else {
|
||||||
|
auto n = name();
|
||||||
|
Metadata::remove(index_dir, n);
|
||||||
|
}
|
||||||
|
m_metadata = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Resource::isSymLinkUnder(const QString& instPath) const
|
bool Resource::isSymLinkUnder(const QString& instPath) const
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
|
|
||||||
|
#include "MetadataHandler.h"
|
||||||
#include "QObjectPtr.h"
|
#include "QObjectPtr.h"
|
||||||
|
|
||||||
enum class ResourceType {
|
enum class ResourceType {
|
||||||
|
|
@ -50,7 +51,14 @@ enum class ResourceType {
|
||||||
LITEMOD, //!< The resource is a litemod
|
LITEMOD, //!< The resource is a litemod
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class SortType { NAME, DATE, VERSION, ENABLED, PACK_FORMAT, PROVIDER, SIZE, SIDE, LOADERS, MC_VERSIONS, RELEASE_TYPE };
|
enum class ResourceStatus {
|
||||||
|
INSTALLED, // Both JAR and Metadata are present
|
||||||
|
NOT_INSTALLED, // Only the Metadata is present
|
||||||
|
NO_METADATA, // Only the JAR is present
|
||||||
|
UNKNOWN, // Default status
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class SortType { NAME, DATE, VERSION, ENABLED, PACK_FORMAT, PROVIDER, SIZE, SIDE, MC_VERSIONS, LOADERS, RELEASE_TYPE };
|
||||||
|
|
||||||
enum class EnableAction { ENABLE, DISABLE, TOGGLE };
|
enum class EnableAction { ENABLE, DISABLE, TOGGLE };
|
||||||
|
|
||||||
|
|
@ -84,9 +92,19 @@ class Resource : public QObject {
|
||||||
[[nodiscard]] QString sizeStr() const { return m_size_str; }
|
[[nodiscard]] QString sizeStr() const { return m_size_str; }
|
||||||
[[nodiscard]] qint64 sizeInfo() const { return m_size_info; }
|
[[nodiscard]] qint64 sizeInfo() const { return m_size_info; }
|
||||||
|
|
||||||
[[nodiscard]] virtual auto name() const -> QString { return m_name; }
|
[[nodiscard]] virtual auto name() const -> QString;
|
||||||
[[nodiscard]] virtual bool valid() const { return m_type != ResourceType::UNKNOWN; }
|
[[nodiscard]] virtual bool valid() const { return m_type != ResourceType::UNKNOWN; }
|
||||||
|
|
||||||
|
[[nodiscard]] auto status() const -> ResourceStatus { return m_status; };
|
||||||
|
[[nodiscard]] auto metadata() -> std::shared_ptr<Metadata::ModStruct> { return m_metadata; }
|
||||||
|
[[nodiscard]] auto metadata() const -> std::shared_ptr<const Metadata::ModStruct> { return m_metadata; }
|
||||||
|
[[nodiscard]] auto provider() const -> QString;
|
||||||
|
[[nodiscard]] virtual auto homepage() const -> QString;
|
||||||
|
|
||||||
|
void setStatus(ResourceStatus status) { m_status = status; }
|
||||||
|
void setMetadata(std::shared_ptr<Metadata::ModStruct>&& metadata);
|
||||||
|
void setMetadata(const Metadata::ModStruct& metadata) { setMetadata(std::make_shared<Metadata::ModStruct>(metadata)); }
|
||||||
|
|
||||||
/** Compares two Resources, for sorting purposes, considering a ascending order, returning:
|
/** Compares two Resources, for sorting purposes, considering a ascending order, returning:
|
||||||
* > 0: 'this' comes after 'other'
|
* > 0: 'this' comes after 'other'
|
||||||
* = 0: 'this' is equal to 'other'
|
* = 0: 'this' is equal to 'other'
|
||||||
|
|
@ -117,7 +135,9 @@ class Resource : public QObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete all files of this resource.
|
// Delete all files of this resource.
|
||||||
bool destroy(bool attemptTrash = true);
|
auto destroy(const QDir& index_dir, bool preserve_metadata = false, bool attempt_trash = true) -> bool;
|
||||||
|
// Delete the metadata only.
|
||||||
|
auto destroyMetadata(const QDir& index_dir) -> void;
|
||||||
|
|
||||||
[[nodiscard]] auto isSymLink() const -> bool { return m_file_info.isSymLink(); }
|
[[nodiscard]] auto isSymLink() const -> bool { return m_file_info.isSymLink(); }
|
||||||
|
|
||||||
|
|
@ -146,6 +166,11 @@ class Resource : public QObject {
|
||||||
/* The type of file we're dealing with. */
|
/* The type of file we're dealing with. */
|
||||||
ResourceType m_type = ResourceType::UNKNOWN;
|
ResourceType m_type = ResourceType::UNKNOWN;
|
||||||
|
|
||||||
|
/* Installation status of the resource. */
|
||||||
|
ResourceStatus m_status = ResourceStatus::UNKNOWN;
|
||||||
|
|
||||||
|
std::shared_ptr<Metadata::ModStruct> m_metadata = nullptr;
|
||||||
|
|
||||||
/* Whether the resource is enabled (e.g. shows up in the game) or not. */
|
/* Whether the resource is enabled (e.g. shows up in the game) or not. */
|
||||||
bool m_enabled = true;
|
bool m_enabled = true;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,20 +11,23 @@
|
||||||
#include <QStyle>
|
#include <QStyle>
|
||||||
#include <QThreadPool>
|
#include <QThreadPool>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
|
|
||||||
#include "QVariantUtils.h"
|
#include "minecraft/mod/tasks/ResourceFolderLoadTask.h"
|
||||||
#include "StringUtils.h"
|
|
||||||
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
|
|
||||||
|
|
||||||
|
#include "Json.h"
|
||||||
|
#include "minecraft/mod/tasks/LocalResourceUpdateTask.h"
|
||||||
|
#include "modplatform/flame/FlameAPI.h"
|
||||||
|
#include "modplatform/flame/FlameModIndex.h"
|
||||||
#include "settings/Setting.h"
|
#include "settings/Setting.h"
|
||||||
#include "tasks/Task.h"
|
#include "tasks/Task.h"
|
||||||
#include "ui/dialogs/CustomMessageBox.h"
|
#include "ui/dialogs/CustomMessageBox.h"
|
||||||
|
|
||||||
ResourceFolderModel::ResourceFolderModel(QDir dir, BaseInstance* instance, QObject* parent, bool create_dir)
|
ResourceFolderModel::ResourceFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent)
|
||||||
: QAbstractListModel(parent), m_dir(dir), m_instance(instance), m_watcher(this)
|
: QAbstractListModel(parent), m_dir(dir), m_instance(instance), m_watcher(this), m_is_indexed(is_indexed)
|
||||||
{
|
{
|
||||||
if (create_dir) {
|
if (create_dir) {
|
||||||
FS::ensureFolderPathExists(m_dir.absolutePath());
|
FS::ensureFolderPathExists(m_dir.absolutePath());
|
||||||
|
|
@ -48,6 +51,9 @@ ResourceFolderModel::~ResourceFolderModel()
|
||||||
|
|
||||||
bool ResourceFolderModel::startWatching(const QStringList& paths)
|
bool ResourceFolderModel::startWatching(const QStringList& paths)
|
||||||
{
|
{
|
||||||
|
// Remove orphaned metadata next time
|
||||||
|
m_first_folder_load = true;
|
||||||
|
|
||||||
if (m_is_watching)
|
if (m_is_watching)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|
@ -158,11 +164,55 @@ bool ResourceFolderModel::installResource(QString original_path)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ResourceFolderModel::uninstallResource(QString file_name)
|
bool ResourceFolderModel::installResourceWithFlameMetadata(QString path, ModPlatform::IndexedVersion& vers)
|
||||||
|
{
|
||||||
|
if (vers.addonId.isValid()) {
|
||||||
|
ModPlatform::IndexedPack pack{
|
||||||
|
vers.addonId,
|
||||||
|
ModPlatform::ResourceProvider::FLAME,
|
||||||
|
};
|
||||||
|
|
||||||
|
QEventLoop loop;
|
||||||
|
|
||||||
|
auto response = std::make_shared<QByteArray>();
|
||||||
|
auto job = FlameAPI().getProject(vers.addonId.toString(), response);
|
||||||
|
|
||||||
|
QObject::connect(job.get(), &Task::failed, [&loop] { loop.quit(); });
|
||||||
|
QObject::connect(job.get(), &Task::aborted, &loop, &QEventLoop::quit);
|
||||||
|
QObject::connect(job.get(), &Task::succeeded, [response, this, &vers, &loop, &pack] {
|
||||||
|
QJsonParseError parse_error{};
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
||||||
|
if (parse_error.error != QJsonParseError::NoError) {
|
||||||
|
qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset
|
||||||
|
<< " reason: " << parse_error.errorString();
|
||||||
|
qDebug() << *response;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
auto obj = Json::requireObject(Json::requireObject(doc), "data");
|
||||||
|
FlameMod::loadIndexedPack(pack, obj);
|
||||||
|
} catch (const JSONValidationError& e) {
|
||||||
|
qDebug() << doc;
|
||||||
|
qWarning() << "Error while reading mod info: " << e.cause();
|
||||||
|
}
|
||||||
|
LocalResourceUpdateTask update_metadata(indexDir(), pack, vers);
|
||||||
|
QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit);
|
||||||
|
update_metadata.start();
|
||||||
|
});
|
||||||
|
|
||||||
|
job->start();
|
||||||
|
|
||||||
|
loop.exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
return installResource(std::move(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ResourceFolderModel::uninstallResource(QString file_name, bool preserve_metadata)
|
||||||
{
|
{
|
||||||
for (auto& resource : m_resources) {
|
for (auto& resource : m_resources) {
|
||||||
if (resource->fileinfo().fileName() == file_name) {
|
if (resource->fileinfo().fileName() == file_name) {
|
||||||
auto res = resource->destroy(false);
|
auto res = resource->destroy(indexDir(), preserve_metadata, false);
|
||||||
|
|
||||||
update();
|
update();
|
||||||
|
|
||||||
|
|
@ -178,13 +228,11 @@ bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
for (auto i : indexes) {
|
for (auto i : indexes) {
|
||||||
if (i.column() != 0) {
|
if (i.column() != 0)
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
auto& resource = m_resources.at(i.row());
|
auto& resource = m_resources.at(i.row());
|
||||||
|
resource->destroy(indexDir());
|
||||||
resource->destroy();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
update();
|
update();
|
||||||
|
|
@ -192,6 +240,22 @@ bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ResourceFolderModel::deleteMetadata(const QModelIndexList& indexes)
|
||||||
|
{
|
||||||
|
if (indexes.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (auto i : indexes) {
|
||||||
|
if (i.column() != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto& resource = m_resources.at(i.row());
|
||||||
|
resource->destroyMetadata(indexDir());
|
||||||
|
}
|
||||||
|
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
bool ResourceFolderModel::setResourceEnabled(const QModelIndexList& indexes, EnableAction action)
|
bool ResourceFolderModel::setResourceEnabled(const QModelIndexList& indexes, EnableAction action)
|
||||||
{
|
{
|
||||||
if (indexes.isEmpty())
|
if (indexes.isEmpty())
|
||||||
|
|
@ -245,7 +309,7 @@ bool ResourceFolderModel::update()
|
||||||
connect(m_current_update_task.get(), &Task::failed, this, &ResourceFolderModel::onUpdateFailed, Qt::ConnectionType::QueuedConnection);
|
connect(m_current_update_task.get(), &Task::failed, this, &ResourceFolderModel::onUpdateFailed, Qt::ConnectionType::QueuedConnection);
|
||||||
connect(
|
connect(
|
||||||
m_current_update_task.get(), &Task::finished, this,
|
m_current_update_task.get(), &Task::finished, this,
|
||||||
[=] {
|
[this] {
|
||||||
m_current_update_task.reset();
|
m_current_update_task.reset();
|
||||||
if (m_scheduled_update) {
|
if (m_scheduled_update) {
|
||||||
m_scheduled_update = false;
|
m_scheduled_update = false;
|
||||||
|
|
@ -261,7 +325,7 @@ bool ResourceFolderModel::update()
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ResourceFolderModel::resolveResource(Resource* res)
|
void ResourceFolderModel::resolveResource(Resource::Ptr res)
|
||||||
{
|
{
|
||||||
if (!res->shouldResolve()) {
|
if (!res->shouldResolve()) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -277,11 +341,14 @@ void ResourceFolderModel::resolveResource(Resource* res)
|
||||||
m_active_parse_tasks.insert(ticket, task);
|
m_active_parse_tasks.insert(ticket, task);
|
||||||
|
|
||||||
connect(
|
connect(
|
||||||
task.get(), &Task::succeeded, this, [=] { onParseSucceeded(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection);
|
task.get(), &Task::succeeded, this, [this, ticket, res] { onParseSucceeded(ticket, res->internal_id()); },
|
||||||
connect(task.get(), &Task::failed, this, [=] { onParseFailed(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection);
|
Qt::ConnectionType::QueuedConnection);
|
||||||
|
connect(
|
||||||
|
task.get(), &Task::failed, this, [this, ticket, res] { onParseFailed(ticket, res->internal_id()); },
|
||||||
|
Qt::ConnectionType::QueuedConnection);
|
||||||
connect(
|
connect(
|
||||||
task.get(), &Task::finished, this,
|
task.get(), &Task::finished, this,
|
||||||
[=] {
|
[this, ticket] {
|
||||||
m_active_parse_tasks.remove(ticket);
|
m_active_parse_tasks.remove(ticket);
|
||||||
emit parseFinished();
|
emit parseFinished();
|
||||||
},
|
},
|
||||||
|
|
@ -296,7 +363,7 @@ void ResourceFolderModel::resolveResource(Resource* res)
|
||||||
|
|
||||||
void ResourceFolderModel::onUpdateSucceeded()
|
void ResourceFolderModel::onUpdateSucceeded()
|
||||||
{
|
{
|
||||||
auto update_results = static_cast<BasicFolderLoadTask*>(m_current_update_task.get())->result();
|
auto update_results = static_cast<ResourceFolderLoadTask*>(m_current_update_task.get())->result();
|
||||||
|
|
||||||
auto& new_resources = update_results->resources;
|
auto& new_resources = update_results->resources;
|
||||||
|
|
||||||
|
|
@ -317,7 +384,7 @@ void ResourceFolderModel::onUpdateSucceeded()
|
||||||
void ResourceFolderModel::onParseSucceeded(int ticket, QString resource_id)
|
void ResourceFolderModel::onParseSucceeded(int ticket, QString resource_id)
|
||||||
{
|
{
|
||||||
auto iter = m_active_parse_tasks.constFind(ticket);
|
auto iter = m_active_parse_tasks.constFind(ticket);
|
||||||
if (iter == m_active_parse_tasks.constEnd())
|
if (iter == m_active_parse_tasks.constEnd() || !m_resources_index.contains(resource_id))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int row = m_resources_index[resource_id];
|
int row = m_resources_index[resource_id];
|
||||||
|
|
@ -326,7 +393,11 @@ void ResourceFolderModel::onParseSucceeded(int ticket, QString resource_id)
|
||||||
|
|
||||||
Task* ResourceFolderModel::createUpdateTask()
|
Task* ResourceFolderModel::createUpdateTask()
|
||||||
{
|
{
|
||||||
return new BasicFolderLoadTask(m_dir);
|
auto index_dir = indexDir();
|
||||||
|
auto task = new ResourceFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load,
|
||||||
|
[this](const QFileInfo& file) { return createResource(file); });
|
||||||
|
m_first_folder_load = false;
|
||||||
|
return task;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ResourceFolderModel::hasPendingParseTasks() const
|
bool ResourceFolderModel::hasPendingParseTasks() const
|
||||||
|
|
@ -416,6 +487,8 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const
|
||||||
return m_resources[row]->name();
|
return m_resources[row]->name();
|
||||||
case DateColumn:
|
case DateColumn:
|
||||||
return m_resources[row]->dateTimeChanged();
|
return m_resources[row]->dateTimeChanged();
|
||||||
|
case ProviderColumn:
|
||||||
|
return m_resources[row]->provider();
|
||||||
case SizeColumn:
|
case SizeColumn:
|
||||||
return m_resources[row]->sizeStr();
|
return m_resources[row]->sizeStr();
|
||||||
default:
|
default:
|
||||||
|
|
@ -487,22 +560,23 @@ QVariant ResourceFolderModel::headerData(int section, [[maybe_unused]] Qt::Orien
|
||||||
case ActiveColumn:
|
case ActiveColumn:
|
||||||
case NameColumn:
|
case NameColumn:
|
||||||
case DateColumn:
|
case DateColumn:
|
||||||
|
case ProviderColumn:
|
||||||
case SizeColumn:
|
case SizeColumn:
|
||||||
return columnNames().at(section);
|
return columnNames().at(section);
|
||||||
default:
|
default:
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
case Qt::ToolTipRole: {
|
case Qt::ToolTipRole: {
|
||||||
|
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
|
||||||
switch (section) {
|
switch (section) {
|
||||||
case ActiveColumn:
|
case ActiveColumn:
|
||||||
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
|
|
||||||
return tr("Is the resource enabled?");
|
return tr("Is the resource enabled?");
|
||||||
case NameColumn:
|
case NameColumn:
|
||||||
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
|
|
||||||
return tr("The name of the resource.");
|
return tr("The name of the resource.");
|
||||||
case DateColumn:
|
case DateColumn:
|
||||||
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
|
|
||||||
return tr("The date and time this resource was last changed (or added).");
|
return tr("The date and time this resource was last changed (or added).");
|
||||||
|
case ProviderColumn:
|
||||||
|
return tr("The source provider of the resource.");
|
||||||
case SizeColumn:
|
case SizeColumn:
|
||||||
return tr("The size of the resource.");
|
return tr("The size of the resource.");
|
||||||
default:
|
default:
|
||||||
|
|
@ -629,7 +703,7 @@ QString ResourceFolderModel::instDirPath() const
|
||||||
void ResourceFolderModel::onParseFailed(int ticket, QString resource_id)
|
void ResourceFolderModel::onParseFailed(int ticket, QString resource_id)
|
||||||
{
|
{
|
||||||
auto iter = m_active_parse_tasks.constFind(ticket);
|
auto iter = m_active_parse_tasks.constFind(ticket);
|
||||||
if (iter == m_active_parse_tasks.constEnd())
|
if (iter == m_active_parse_tasks.constEnd() || !m_resources_index.contains(resource_id))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto removed_index = m_resources_index[resource_id];
|
auto removed_index = m_resources_index[resource_id];
|
||||||
|
|
@ -648,3 +722,126 @@ void ResourceFolderModel::onParseFailed(int ticket, QString resource_id)
|
||||||
}
|
}
|
||||||
endRemoveRows();
|
endRemoveRows();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ResourceFolderModel::applyUpdates(QSet<QString>& current_set, QSet<QString>& new_set, QMap<QString, Resource::Ptr>& new_resources)
|
||||||
|
{
|
||||||
|
// see if the kept resources changed in some way
|
||||||
|
{
|
||||||
|
QSet<QString> kept_set = current_set;
|
||||||
|
kept_set.intersect(new_set);
|
||||||
|
|
||||||
|
for (auto const& kept : kept_set) {
|
||||||
|
auto row_it = m_resources_index.constFind(kept);
|
||||||
|
Q_ASSERT(row_it != m_resources_index.constEnd());
|
||||||
|
auto row = row_it.value();
|
||||||
|
|
||||||
|
auto& new_resource = new_resources[kept];
|
||||||
|
auto const& current_resource = m_resources.at(row);
|
||||||
|
|
||||||
|
if (new_resource->dateTimeChanged() == current_resource->dateTimeChanged()) {
|
||||||
|
// no significant change, ignore...
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the resource is resolving, but something about it changed, we don't want to
|
||||||
|
// continue the resolving.
|
||||||
|
if (current_resource->isResolving()) {
|
||||||
|
auto ticket = current_resource->resolutionTicket();
|
||||||
|
if (m_active_parse_tasks.contains(ticket)) {
|
||||||
|
auto task = (*m_active_parse_tasks.find(ticket)).get();
|
||||||
|
task->abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_resources[row].reset(new_resource);
|
||||||
|
resolveResource(m_resources.at(row));
|
||||||
|
emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove resources no longer present
|
||||||
|
{
|
||||||
|
QSet<QString> removed_set = current_set;
|
||||||
|
removed_set.subtract(new_set);
|
||||||
|
|
||||||
|
QList<int> removed_rows;
|
||||||
|
for (auto& removed : removed_set)
|
||||||
|
removed_rows.append(m_resources_index[removed]);
|
||||||
|
|
||||||
|
std::sort(removed_rows.begin(), removed_rows.end(), std::greater<int>());
|
||||||
|
|
||||||
|
for (auto& removed_index : removed_rows) {
|
||||||
|
auto removed_it = m_resources.begin() + removed_index;
|
||||||
|
|
||||||
|
Q_ASSERT(removed_it != m_resources.end());
|
||||||
|
|
||||||
|
if ((*removed_it)->isResolving()) {
|
||||||
|
auto ticket = (*removed_it)->resolutionTicket();
|
||||||
|
if (m_active_parse_tasks.contains(ticket)) {
|
||||||
|
auto task = (*m_active_parse_tasks.find(ticket)).get();
|
||||||
|
task->abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
beginRemoveRows(QModelIndex(), removed_index, removed_index);
|
||||||
|
m_resources.erase(removed_it);
|
||||||
|
endRemoveRows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add new resources to the end
|
||||||
|
{
|
||||||
|
QSet<QString> added_set = new_set;
|
||||||
|
added_set.subtract(current_set);
|
||||||
|
|
||||||
|
// When you have a Qt build with assertions turned on, proceeding here will abort the application
|
||||||
|
if (added_set.size() > 0) {
|
||||||
|
beginInsertRows(QModelIndex(), static_cast<int>(m_resources.size()),
|
||||||
|
static_cast<int>(m_resources.size() + added_set.size() - 1));
|
||||||
|
|
||||||
|
for (auto& added : added_set) {
|
||||||
|
auto res = new_resources[added];
|
||||||
|
m_resources.append(res);
|
||||||
|
resolveResource(m_resources.last());
|
||||||
|
}
|
||||||
|
|
||||||
|
endInsertRows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update index
|
||||||
|
{
|
||||||
|
m_resources_index.clear();
|
||||||
|
int idx = 0;
|
||||||
|
for (auto const& mod : qAsConst(m_resources)) {
|
||||||
|
m_resources_index[mod->internal_id()] = idx;
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Resource::Ptr ResourceFolderModel::find(QString id)
|
||||||
|
{
|
||||||
|
auto iter =
|
||||||
|
std::find_if(m_resources.constBegin(), m_resources.constEnd(), [&](Resource::Ptr const& r) { return r->internal_id() == id; });
|
||||||
|
if (iter == m_resources.constEnd())
|
||||||
|
return nullptr;
|
||||||
|
return *iter;
|
||||||
|
}
|
||||||
|
QList<Resource*> ResourceFolderModel::allResources()
|
||||||
|
{
|
||||||
|
QList<Resource*> result;
|
||||||
|
result.reserve(m_resources.size());
|
||||||
|
for (const Resource ::Ptr& resource : m_resources)
|
||||||
|
result.append((resource.get()));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
QList<Resource*> ResourceFolderModel::selectedResources(const QModelIndexList& indexes)
|
||||||
|
{
|
||||||
|
QList<Resource*> result;
|
||||||
|
for (const QModelIndex& index : indexes) {
|
||||||
|
if (index.column() != 0)
|
||||||
|
continue;
|
||||||
|
result.append(&at(index.row()));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,38 @@
|
||||||
|
|
||||||
class QSortFilterProxyModel;
|
class QSortFilterProxyModel;
|
||||||
|
|
||||||
|
/* A macro to define useful functions to handle Resource* -> T* more easily on derived classes */
|
||||||
|
#define RESOURCE_HELPERS(T) \
|
||||||
|
[[nodiscard]] T& at(int index) \
|
||||||
|
{ \
|
||||||
|
return *static_cast<T*>(m_resources[index].get()); \
|
||||||
|
} \
|
||||||
|
[[nodiscard]] const T& at(int index) const \
|
||||||
|
{ \
|
||||||
|
return *static_cast<const T*>(m_resources.at(index).get()); \
|
||||||
|
} \
|
||||||
|
QList<T*> selected##T##s(const QModelIndexList& indexes) \
|
||||||
|
{ \
|
||||||
|
QList<T*> result; \
|
||||||
|
for (const QModelIndex& index : indexes) { \
|
||||||
|
if (index.column() != 0) \
|
||||||
|
continue; \
|
||||||
|
\
|
||||||
|
result.append(&at(index.row())); \
|
||||||
|
} \
|
||||||
|
return result; \
|
||||||
|
} \
|
||||||
|
QList<T*> all##T##s() \
|
||||||
|
{ \
|
||||||
|
QList<T*> result; \
|
||||||
|
result.reserve(m_resources.size()); \
|
||||||
|
\
|
||||||
|
for (const Resource::Ptr& resource : m_resources) \
|
||||||
|
result.append(static_cast<T*>(resource.get())); \
|
||||||
|
\
|
||||||
|
return result; \
|
||||||
|
}
|
||||||
|
|
||||||
/** A basic model for external resources.
|
/** A basic model for external resources.
|
||||||
*
|
*
|
||||||
* This model manages a list of resources. As such, external users of such resources do not own them,
|
* This model manages a list of resources. As such, external users of such resources do not own them,
|
||||||
|
|
@ -29,7 +61,7 @@ class QSortFilterProxyModel;
|
||||||
class ResourceFolderModel : public QAbstractListModel {
|
class ResourceFolderModel : public QAbstractListModel {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
ResourceFolderModel(QDir, BaseInstance* instance, QObject* parent = nullptr, bool create_dir = true);
|
ResourceFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr);
|
||||||
~ResourceFolderModel() override;
|
~ResourceFolderModel() override;
|
||||||
|
|
||||||
virtual QString id() const { return "resource"; }
|
virtual QString id() const { return "resource"; }
|
||||||
|
|
@ -49,8 +81,10 @@ class ResourceFolderModel : public QAbstractListModel {
|
||||||
bool stopWatching(const QStringList& paths);
|
bool stopWatching(const QStringList& paths);
|
||||||
|
|
||||||
/* Helper methods for subclasses, using a predetermined list of paths. */
|
/* Helper methods for subclasses, using a predetermined list of paths. */
|
||||||
virtual bool startWatching() { return startWatching({ m_dir.absolutePath() }); }
|
virtual bool startWatching() { return startWatching({ indexDir().absolutePath(), m_dir.absolutePath() }); }
|
||||||
virtual bool stopWatching() { return stopWatching({ m_dir.absolutePath() }); }
|
virtual bool stopWatching() { return stopWatching({ indexDir().absolutePath(), m_dir.absolutePath() }); }
|
||||||
|
|
||||||
|
QDir indexDir() { return { QString("%1/.index").arg(dir().absolutePath()) }; }
|
||||||
|
|
||||||
/** Given a path in the system, install that resource, moving it to its place in the
|
/** Given a path in the system, install that resource, moving it to its place in the
|
||||||
* instance file hierarchy.
|
* instance file hierarchy.
|
||||||
|
|
@ -59,12 +93,15 @@ class ResourceFolderModel : public QAbstractListModel {
|
||||||
*/
|
*/
|
||||||
virtual bool installResource(QString path);
|
virtual bool installResource(QString path);
|
||||||
|
|
||||||
|
virtual bool installResourceWithFlameMetadata(QString path, ModPlatform::IndexedVersion& vers);
|
||||||
|
|
||||||
/** Uninstall (i.e. remove all data about it) a resource, given its file name.
|
/** Uninstall (i.e. remove all data about it) a resource, given its file name.
|
||||||
*
|
*
|
||||||
* Returns whether the removal was successful.
|
* Returns whether the removal was successful.
|
||||||
*/
|
*/
|
||||||
virtual bool uninstallResource(QString file_name);
|
virtual bool uninstallResource(QString file_name, bool preserve_metadata = false);
|
||||||
virtual bool deleteResources(const QModelIndexList&);
|
virtual bool deleteResources(const QModelIndexList&);
|
||||||
|
virtual void deleteMetadata(const QModelIndexList&);
|
||||||
|
|
||||||
/** Applies the given 'action' to the resources in 'indexes'.
|
/** Applies the given 'action' to the resources in 'indexes'.
|
||||||
*
|
*
|
||||||
|
|
@ -76,13 +113,17 @@ class ResourceFolderModel : public QAbstractListModel {
|
||||||
virtual bool update();
|
virtual bool update();
|
||||||
|
|
||||||
/** Creates a new parse task, if needed, for 'res' and start it.*/
|
/** Creates a new parse task, if needed, for 'res' and start it.*/
|
||||||
virtual void resolveResource(Resource* res);
|
virtual void resolveResource(Resource::Ptr res);
|
||||||
|
|
||||||
[[nodiscard]] qsizetype size() const { return m_resources.size(); }
|
[[nodiscard]] qsizetype size() const { return m_resources.size(); }
|
||||||
[[nodiscard]] bool empty() const { return size() == 0; }
|
[[nodiscard]] bool empty() const { return size() == 0; }
|
||||||
[[nodiscard]] Resource& at(int index) { return *m_resources.at(index); }
|
|
||||||
[[nodiscard]] Resource const& at(int index) const { return *m_resources.at(index); }
|
[[nodiscard]] Resource& at(int index) { return *m_resources[index].get(); }
|
||||||
[[nodiscard]] QList<Resource::Ptr> const& all() const { return m_resources; }
|
[[nodiscard]] const Resource& at(int index) const { return *m_resources.at(index).get(); }
|
||||||
|
QList<Resource*> selectedResources(const QModelIndexList& indexes);
|
||||||
|
QList<Resource*> allResources();
|
||||||
|
|
||||||
|
[[nodiscard]] Resource::Ptr find(QString id);
|
||||||
|
|
||||||
[[nodiscard]] QDir const& dir() const { return m_dir; }
|
[[nodiscard]] QDir const& dir() const { return m_dir; }
|
||||||
|
|
||||||
|
|
@ -96,7 +137,8 @@ class ResourceFolderModel : public QAbstractListModel {
|
||||||
/* Qt behavior */
|
/* Qt behavior */
|
||||||
|
|
||||||
/* Basic columns */
|
/* Basic columns */
|
||||||
enum Columns { ActiveColumn = 0, NameColumn, DateColumn, SizeColumn, NUM_COLUMNS };
|
enum Columns { ActiveColumn = 0, NameColumn, DateColumn, ProviderColumn, SizeColumn, NUM_COLUMNS };
|
||||||
|
|
||||||
QStringList columnNames(bool translated = true) const { return translated ? m_column_names_translated : m_column_names; }
|
QStringList columnNames(bool translated = true) const { return translated ? m_column_names_translated : m_column_names; }
|
||||||
|
|
||||||
[[nodiscard]] int rowCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : static_cast<int>(size()); }
|
[[nodiscard]] int rowCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : static_cast<int>(size()); }
|
||||||
|
|
@ -153,7 +195,9 @@ class ResourceFolderModel : public QAbstractListModel {
|
||||||
* This Task is normally executed when opening a page, so it shouldn't contain much heavy work.
|
* This Task is normally executed when opening a page, so it shouldn't contain much heavy work.
|
||||||
* If such work is needed, try using it in the Task create by createParseTask() instead!
|
* If such work is needed, try using it in the Task create by createParseTask() instead!
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] virtual Task* createUpdateTask();
|
[[nodiscard]] Task* createUpdateTask();
|
||||||
|
|
||||||
|
[[nodiscard]] virtual Resource* createResource(const QFileInfo& info) { return new Resource(info); }
|
||||||
|
|
||||||
/** This creates a new parse task to be executed by onUpdateSucceeded().
|
/** This creates a new parse task to be executed by onUpdateSucceeded().
|
||||||
*
|
*
|
||||||
|
|
@ -167,10 +211,8 @@ class ResourceFolderModel : public QAbstractListModel {
|
||||||
* It uses set operations to find differences between the current state and the updated state,
|
* It uses set operations to find differences between the current state and the updated state,
|
||||||
* to act only on those disparities.
|
* to act only on those disparities.
|
||||||
*
|
*
|
||||||
* The implementation is at the end of this header.
|
|
||||||
*/
|
*/
|
||||||
template <typename T>
|
void applyUpdates(QSet<QString>& current_set, QSet<QString>& new_set, QMap<QString, Resource::Ptr>& new_resources);
|
||||||
void applyUpdates(QSet<QString>& current_set, QSet<QString>& new_set, QMap<QString, T>& new_resources);
|
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
void directoryChanged(QString);
|
void directoryChanged(QString);
|
||||||
|
|
@ -195,19 +237,22 @@ class ResourceFolderModel : public QAbstractListModel {
|
||||||
protected:
|
protected:
|
||||||
// Represents the relationship between a column's index (represented by the list index), and it's sorting key.
|
// Represents the relationship between a column's index (represented by the list index), and it's sorting key.
|
||||||
// As such, the order in with they appear is very important!
|
// As such, the order in with they appear is very important!
|
||||||
QList<SortType> m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE, SortType::SIZE };
|
QList<SortType> m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE, SortType::PROVIDER, SortType::SIZE };
|
||||||
QStringList m_column_names = { "Enable", "Name", "Last Modified", "Size" };
|
QStringList m_column_names = { "Enable", "Name", "Last Modified", "Provider", "Size" };
|
||||||
QStringList m_column_names_translated = { tr("Enable"), tr("Name"), tr("Last Modified"), tr("Size") };
|
QStringList m_column_names_translated = { tr("Enable"), tr("Name"), tr("Last Modified"), tr("Provider"), tr("Size") };
|
||||||
QList<QHeaderView::ResizeMode> m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive,
|
QList<QHeaderView::ResizeMode> m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive,
|
||||||
QHeaderView::Interactive };
|
QHeaderView::Interactive, QHeaderView::Interactive };
|
||||||
QList<bool> m_columnsHideable = { false, false, true, true };
|
QList<bool> m_columnsHideable = { false, false, true, true, true };
|
||||||
QList<bool> m_columnsHiddenByDefault = { false, false, false, false };
|
QList<bool> m_columnsHiddenByDefault = { false, false, false, false, true };
|
||||||
|
|
||||||
QDir m_dir;
|
QDir m_dir;
|
||||||
BaseInstance* m_instance;
|
BaseInstance* m_instance;
|
||||||
QFileSystemWatcher m_watcher;
|
QFileSystemWatcher m_watcher;
|
||||||
bool m_is_watching = false;
|
bool m_is_watching = false;
|
||||||
|
|
||||||
|
bool m_is_indexed;
|
||||||
|
bool m_first_folder_load = true;
|
||||||
|
|
||||||
Task::Ptr m_current_update_task = nullptr;
|
Task::Ptr m_current_update_task = nullptr;
|
||||||
bool m_scheduled_update = false;
|
bool m_scheduled_update = false;
|
||||||
|
|
||||||
|
|
@ -220,133 +265,3 @@ class ResourceFolderModel : public QAbstractListModel {
|
||||||
QMap<int, Task::Ptr> m_active_parse_tasks;
|
QMap<int, Task::Ptr> m_active_parse_tasks;
|
||||||
std::atomic<int> m_next_resolution_ticket = 0;
|
std::atomic<int> m_next_resolution_ticket = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* A macro to define useful functions to handle Resource* -> T* more easily on derived classes */
|
|
||||||
#define RESOURCE_HELPERS(T) \
|
|
||||||
[[nodiscard]] T* operator[](int index) \
|
|
||||||
{ \
|
|
||||||
return static_cast<T*>(m_resources[index].get()); \
|
|
||||||
} \
|
|
||||||
[[nodiscard]] T* at(int index) \
|
|
||||||
{ \
|
|
||||||
return static_cast<T*>(m_resources[index].get()); \
|
|
||||||
} \
|
|
||||||
[[nodiscard]] const T* at(int index) const \
|
|
||||||
{ \
|
|
||||||
return static_cast<const T*>(m_resources.at(index).get()); \
|
|
||||||
} \
|
|
||||||
[[nodiscard]] T* first() \
|
|
||||||
{ \
|
|
||||||
return static_cast<T*>(m_resources.first().get()); \
|
|
||||||
} \
|
|
||||||
[[nodiscard]] T* last() \
|
|
||||||
{ \
|
|
||||||
return static_cast<T*>(m_resources.last().get()); \
|
|
||||||
} \
|
|
||||||
[[nodiscard]] T* find(QString id) \
|
|
||||||
{ \
|
|
||||||
auto iter = std::find_if(m_resources.constBegin(), m_resources.constEnd(), \
|
|
||||||
[&](Resource::Ptr const& r) { return r->internal_id() == id; }); \
|
|
||||||
if (iter == m_resources.constEnd()) \
|
|
||||||
return nullptr; \
|
|
||||||
return static_cast<T*>((*iter).get()); \
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Template definition to avoid some code duplication */
|
|
||||||
template <typename T>
|
|
||||||
void ResourceFolderModel::applyUpdates(QSet<QString>& current_set, QSet<QString>& new_set, QMap<QString, T>& new_resources)
|
|
||||||
{
|
|
||||||
// see if the kept resources changed in some way
|
|
||||||
{
|
|
||||||
QSet<QString> kept_set = current_set;
|
|
||||||
kept_set.intersect(new_set);
|
|
||||||
|
|
||||||
for (auto const& kept : kept_set) {
|
|
||||||
auto row_it = m_resources_index.constFind(kept);
|
|
||||||
Q_ASSERT(row_it != m_resources_index.constEnd());
|
|
||||||
auto row = row_it.value();
|
|
||||||
|
|
||||||
auto& new_resource = new_resources[kept];
|
|
||||||
auto const& current_resource = m_resources.at(row);
|
|
||||||
|
|
||||||
if (new_resource->dateTimeChanged() == current_resource->dateTimeChanged()) {
|
|
||||||
// no significant change, ignore...
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the resource is resolving, but something about it changed, we don't want to
|
|
||||||
// continue the resolving.
|
|
||||||
if (current_resource->isResolving()) {
|
|
||||||
auto ticket = current_resource->resolutionTicket();
|
|
||||||
if (m_active_parse_tasks.contains(ticket)) {
|
|
||||||
auto task = (*m_active_parse_tasks.find(ticket)).get();
|
|
||||||
task->abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_resources[row].reset(new_resource);
|
|
||||||
resolveResource(m_resources.at(row).get());
|
|
||||||
emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove resources no longer present
|
|
||||||
{
|
|
||||||
QSet<QString> removed_set = current_set;
|
|
||||||
removed_set.subtract(new_set);
|
|
||||||
|
|
||||||
QList<int> removed_rows;
|
|
||||||
for (auto& removed : removed_set)
|
|
||||||
removed_rows.append(m_resources_index[removed]);
|
|
||||||
|
|
||||||
std::sort(removed_rows.begin(), removed_rows.end(), std::greater<int>());
|
|
||||||
|
|
||||||
for (auto& removed_index : removed_rows) {
|
|
||||||
auto removed_it = m_resources.begin() + removed_index;
|
|
||||||
|
|
||||||
Q_ASSERT(removed_it != m_resources.end());
|
|
||||||
|
|
||||||
if ((*removed_it)->isResolving()) {
|
|
||||||
auto ticket = (*removed_it)->resolutionTicket();
|
|
||||||
if (m_active_parse_tasks.contains(ticket)) {
|
|
||||||
auto task = (*m_active_parse_tasks.find(ticket)).get();
|
|
||||||
task->abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
beginRemoveRows(QModelIndex(), removed_index, removed_index);
|
|
||||||
m_resources.erase(removed_it);
|
|
||||||
endRemoveRows();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add new resources to the end
|
|
||||||
{
|
|
||||||
QSet<QString> added_set = new_set;
|
|
||||||
added_set.subtract(current_set);
|
|
||||||
|
|
||||||
// When you have a Qt build with assertions turned on, proceeding here will abort the application
|
|
||||||
if (added_set.size() > 0) {
|
|
||||||
beginInsertRows(QModelIndex(), static_cast<int>(m_resources.size()),
|
|
||||||
static_cast<int>(m_resources.size() + added_set.size() - 1));
|
|
||||||
|
|
||||||
for (auto& added : added_set) {
|
|
||||||
auto res = new_resources[added];
|
|
||||||
m_resources.append(res);
|
|
||||||
resolveResource(m_resources.last().get());
|
|
||||||
}
|
|
||||||
|
|
||||||
endInsertRows();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// update index
|
|
||||||
{
|
|
||||||
m_resources_index.clear();
|
|
||||||
int idx = 0;
|
|
||||||
for (auto const& mod : qAsConst(m_resources)) {
|
|
||||||
m_resources_index[mod->internal_id()] = idx;
|
|
||||||
idx++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -44,19 +44,19 @@
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
#include "Version.h"
|
#include "Version.h"
|
||||||
|
|
||||||
#include "minecraft/mod/Resource.h"
|
|
||||||
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
|
|
||||||
#include "minecraft/mod/tasks/LocalResourcePackParseTask.h"
|
#include "minecraft/mod/tasks/LocalResourcePackParseTask.h"
|
||||||
|
|
||||||
ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance)
|
ResourcePackFolderModel::ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent)
|
||||||
|
: ResourceFolderModel(dir, instance, is_indexed, create_dir, parent)
|
||||||
{
|
{
|
||||||
m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified", "Size" });
|
m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified", "Provider", "Size" });
|
||||||
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified"), tr("Size") });
|
m_column_names_translated =
|
||||||
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE, SortType::SIZE };
|
QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified"), tr("Provider"), tr("Size") });
|
||||||
m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch,
|
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT,
|
||||||
|
SortType::DATE, SortType::PROVIDER, SortType::SIZE };
|
||||||
|
m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive,
|
||||||
QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive };
|
QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive };
|
||||||
m_columnsHideable = { false, true, false, true, true, true };
|
m_columnsHideable = { false, true, false, true, true, true, true };
|
||||||
m_columnsHiddenByDefault = { false, false, false, false, false, false };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
|
QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
|
||||||
|
|
@ -73,12 +73,12 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
|
||||||
case NameColumn:
|
case NameColumn:
|
||||||
return m_resources[row]->name();
|
return m_resources[row]->name();
|
||||||
case PackFormatColumn: {
|
case PackFormatColumn: {
|
||||||
auto resource = at(row);
|
auto& resource = at(row);
|
||||||
auto pack_format = resource->packFormat();
|
auto pack_format = resource.packFormat();
|
||||||
if (pack_format == 0)
|
if (pack_format == 0)
|
||||||
return tr("Unrecognized");
|
return tr("Unrecognized");
|
||||||
|
|
||||||
auto version_bounds = resource->compatibleVersions();
|
auto version_bounds = resource.compatibleVersions();
|
||||||
if (version_bounds.first.toString().isEmpty())
|
if (version_bounds.first.toString().isEmpty())
|
||||||
return QString::number(pack_format);
|
return QString::number(pack_format);
|
||||||
|
|
||||||
|
|
@ -87,17 +87,18 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
|
||||||
}
|
}
|
||||||
case DateColumn:
|
case DateColumn:
|
||||||
return m_resources[row]->dateTimeChanged();
|
return m_resources[row]->dateTimeChanged();
|
||||||
|
case ProviderColumn:
|
||||||
|
return m_resources[row]->provider();
|
||||||
case SizeColumn:
|
case SizeColumn:
|
||||||
return m_resources[row]->sizeStr();
|
return m_resources[row]->sizeStr();
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
case Qt::DecorationRole: {
|
case Qt::DecorationRole: {
|
||||||
if (column == NameColumn && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink()))
|
if (column == NameColumn && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink()))
|
||||||
return APPLICATION->getThemedIcon("status-yellow");
|
return APPLICATION->getThemedIcon("status-yellow");
|
||||||
if (column == ImageColumn) {
|
if (column == ImageColumn) {
|
||||||
return at(row)->image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
|
return at(row).image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
@ -107,14 +108,14 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
|
||||||
return tr("The resource pack format ID, as well as the Minecraft versions it was designed for.");
|
return tr("The resource pack format ID, as well as the Minecraft versions it was designed for.");
|
||||||
}
|
}
|
||||||
if (column == NameColumn) {
|
if (column == NameColumn) {
|
||||||
if (at(row)->isSymLinkUnder(instDirPath())) {
|
if (at(row).isSymLinkUnder(instDirPath())) {
|
||||||
return m_resources[row]->internal_id() +
|
return m_resources[row]->internal_id() +
|
||||||
tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
|
tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
|
||||||
"\nCanonical Path: %1")
|
"\nCanonical Path: %1")
|
||||||
.arg(at(row)->fileinfo().canonicalFilePath());
|
.arg(at(row).fileinfo().canonicalFilePath());
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
if (at(row)->isMoreThanOneHardLink()) {
|
if (at(row).isMoreThanOneHardLink()) {
|
||||||
return m_resources[row]->internal_id() +
|
return m_resources[row]->internal_id() +
|
||||||
tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original.");
|
tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original.");
|
||||||
}
|
}
|
||||||
|
|
@ -129,7 +130,7 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
|
||||||
case Qt::CheckStateRole:
|
case Qt::CheckStateRole:
|
||||||
switch (column) {
|
switch (column) {
|
||||||
case ActiveColumn:
|
case ActiveColumn:
|
||||||
return at(row)->enabled() ? Qt::Checked : Qt::Unchecked;
|
return at(row).enabled() ? Qt::Checked : Qt::Unchecked;
|
||||||
default:
|
default:
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
@ -148,6 +149,7 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O
|
||||||
case PackFormatColumn:
|
case PackFormatColumn:
|
||||||
case DateColumn:
|
case DateColumn:
|
||||||
case ImageColumn:
|
case ImageColumn:
|
||||||
|
case ProviderColumn:
|
||||||
case SizeColumn:
|
case SizeColumn:
|
||||||
return columnNames().at(section);
|
return columnNames().at(section);
|
||||||
default:
|
default:
|
||||||
|
|
@ -157,7 +159,7 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O
|
||||||
case Qt::ToolTipRole:
|
case Qt::ToolTipRole:
|
||||||
switch (section) {
|
switch (section) {
|
||||||
case ActiveColumn:
|
case ActiveColumn:
|
||||||
return tr("Is the resource pack enabled? (Only valid for ZIPs)");
|
return tr("Is the resource pack enabled?");
|
||||||
case NameColumn:
|
case NameColumn:
|
||||||
return tr("The name of the resource pack.");
|
return tr("The name of the resource pack.");
|
||||||
case PackFormatColumn:
|
case PackFormatColumn:
|
||||||
|
|
@ -165,6 +167,8 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O
|
||||||
return tr("The resource pack format ID, as well as the Minecraft versions it was designed for.");
|
return tr("The resource pack format ID, as well as the Minecraft versions it was designed for.");
|
||||||
case DateColumn:
|
case DateColumn:
|
||||||
return tr("The date and time this resource pack was last changed (or added).");
|
return tr("The date and time this resource pack was last changed (or added).");
|
||||||
|
case ProviderColumn:
|
||||||
|
return tr("The source provider of the resource pack.");
|
||||||
case SizeColumn:
|
case SizeColumn:
|
||||||
return tr("The size of the resource pack.");
|
return tr("The size of the resource pack.");
|
||||||
default:
|
default:
|
||||||
|
|
@ -185,11 +189,6 @@ int ResourcePackFolderModel::columnCount(const QModelIndex& parent) const
|
||||||
return parent.isValid() ? 0 : NUM_COLUMNS;
|
return parent.isValid() ? 0 : NUM_COLUMNS;
|
||||||
}
|
}
|
||||||
|
|
||||||
Task* ResourcePackFolderModel::createUpdateTask()
|
|
||||||
{
|
|
||||||
return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return makeShared<ResourcePack>(entry); });
|
|
||||||
}
|
|
||||||
|
|
||||||
Task* ResourcePackFolderModel::createParseTask(Resource& resource)
|
Task* ResourcePackFolderModel::createParseTask(Resource& resource)
|
||||||
{
|
{
|
||||||
return new LocalResourcePackParseTask(m_next_resolution_ticket, static_cast<ResourcePack&>(resource));
|
return new LocalResourcePackParseTask(m_next_resolution_ticket, static_cast<ResourcePack&>(resource));
|
||||||
|
|
|
||||||
|
|
@ -7,18 +7,18 @@
|
||||||
class ResourcePackFolderModel : public ResourceFolderModel {
|
class ResourcePackFolderModel : public ResourceFolderModel {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, SizeColumn, NUM_COLUMNS };
|
enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, ProviderColumn, SizeColumn, NUM_COLUMNS };
|
||||||
|
|
||||||
explicit ResourcePackFolderModel(const QString& dir, BaseInstance* instance);
|
explicit ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr);
|
||||||
|
|
||||||
virtual QString id() const override { return "resourcepacks"; }
|
QString id() const override { return "resourcepacks"; }
|
||||||
|
|
||||||
[[nodiscard]] QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
[[nodiscard]] QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||||
|
|
||||||
[[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
[[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||||
[[nodiscard]] int columnCount(const QModelIndex& parent) const override;
|
[[nodiscard]] int columnCount(const QModelIndex& parent) const override;
|
||||||
|
|
||||||
[[nodiscard]] Task* createUpdateTask() override;
|
[[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new ResourcePack(file); }
|
||||||
[[nodiscard]] Task* createParseTask(Resource&) override;
|
[[nodiscard]] Task* createParseTask(Resource&) override;
|
||||||
|
|
||||||
RESOURCE_HELPERS(ResourcePack)
|
RESOURCE_HELPERS(ResourcePack)
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue