Compare commits

...
Sign in to create a new pull request.

384 commits

Author SHA1 Message Date
HerozDotExe
6799b772de docs: explain the point of this fork
Some checks failed
CodeQL Code Scanning / CodeQL (push) Has been cancelled
Nix / Build (x86_64-darwin) (push) Has been cancelled
Nix / Build (aarch64-darwin) (push) Has been cancelled
Nix / Build (x86_64-linux) (push) Has been cancelled
Nix / Build (aarch64-linux) (push) Has been cancelled
Build Application / Build Debug (push) Has been cancelled
2025-02-23 10:52:10 +01:00
HerozDotExe
3b87452699 feat: allow offline accounts 2025-02-23 10:50:49 +01:00
Seth Flynn
0af021fef2
ci: always run nix workflow on push (#3433) 2025-02-19 02:17:41 -05:00
Alexandru Ionut Tripon
d24066c965
chore(deps): update actions/cache action to v4.2.1 (#3432) 2025-02-19 08:20:04 +02:00
Seth Flynn
46385e671d
ci: always run nix workflow on push
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-02-18 22:55:51 -05:00
renovate[bot]
73bb740bbb
chore(deps): update actions/cache action to v4.2.1 2025-02-18 18:14:21 +00:00
Alexandru Ionut Tripon
50ab1b2c40
Recognize riscv64 as a 64-bit platform (#3425) 2025-02-17 20:33:17 +02:00
Alexandru Ionut Tripon
d472b966dc
chore(nix): update lockfile (#3401) 2025-02-17 18:04:45 +02:00
Alexandru Ionut Tripon
beee93e199
Update hendrikmuhs/ccache-action action to v1.2.17 (#3388) 2025-02-17 18:04:33 +02:00
thonkdifferent
8f1750df51
Recognize riscv64 as a 64-bit platform
Currently PrismLauncher complains about the installed JDK being a 32-bit version, despite it being compiled for 64-bit RISC-V `riscv64`. 

This PR fixes this issue.

Signed-off-by: thonkdifferent <41342923+thonkdifferent@users.noreply.github.com>
2025-02-16 19:30:31 +02:00
github-actions[bot]
bdf87fa02f chore(nix): update lockfile
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/3a228057f5b619feb3186e986dbe76278d707b6e?narHash=sha256-xvTo0Aw0%2Bveek7hvEVLzErmJyQkEcRk6PSR4zsRQFEc%3D' (2025-02-01)
  → 'github:NixOS/nixpkgs/2ff53fe64443980e139eaa286017f53f88336dd0?narHash=sha256-%2B/bYK3DbPxMIvSL4zArkMX0LQvS7rzBKXnDXLfKyRVc%3D' (2025-02-13)
2025-02-16 00:25:49 +00:00
Rachel Powers
3295b0717a
add libMangoHud_shim.so to mangohub preloadlist (#3413) 2025-02-13 14:41:01 -07:00
Rachel Powers
7b97863685
make sure if user changes java path also disable java management (#3414) 2025-02-13 14:40:40 -07:00
Alexandru Ionut Tripon
3e4fc2ff3b
Increase the stale operations (#3393) 2025-02-13 13:44:01 +02:00
Alexandru Ionut Tripon
d22a20ffa6
Update .github/workflows/stale.yml
Co-authored-by: TheKodeToad <TheKodeToad@proton.me>
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
2025-02-13 08:14:20 +02:00
TheKodeToad
de86e63611
Update .github/workflows/stale.yml
Co-authored-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-02-13 00:05:14 +00:00
TheKodeToad
e41a60e57e
remove some unused files (#3412) 2025-02-13 00:04:45 +00:00
Trial97
174cddcf42
make sure if user changes java path also disable java management
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-02-12 20:40:09 +02:00
Trial97
9e954548dd
add libMangoHud_shim.so to mangohub preloadlist
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-02-12 20:21:45 +02:00
Trial97
14b653e08d
remove some unused files
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-02-12 20:16:00 +02:00
TheKodeToad
79180db663
Fix some undefined behaviour (#3407) 2025-02-11 19:04:56 +00:00
Alexandru Ionut Tripon
22b59e760c
Update launcher/ui/widgets/CheckComboBox.cpp
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
2025-02-11 21:03:04 +02:00
Alexandru Ionut Tripon
1d1a4f1b30
Apply suggestions from code review
Co-authored-by: TheKodeToad <TheKodeToad@proton.me>
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
2025-02-11 21:02:27 +02:00
Trial97
bf1084d7fa
Fix some undefined behaviour
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-02-11 10:59:10 +02:00
Trial97
ef825a7bc5
update stale action
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-02-06 09:58:41 +02:00
Alexandru Ionut Tripon
8d143c7b95
Fixed when game crashes, it doesn't log any time played (#3392) 2025-02-06 09:35:23 +02:00
Riley Richard
9ea33e0651 DCO Remediation Commit for Riley Richard <bubbalou112@gmail.com>
I, Riley Richard <bubbalou112@gmail.com>, hereby add my Signed-off-by to this commit: cb83f8e52d

Signed-off-by: Riley Richard <bubbalou112@gmail.com>
2025-02-05 18:10:31 -05:00
Trial97
f1209c1279
Increase the stale operations
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-02-05 23:43:38 +02:00
Riley Richard
17e95c0d07 forgot to sign
Signed-off-by: Riley Richard <bubbalou112@gmail.com>
2025-02-05 16:33:15 -05:00
Riley Richard
cb83f8e52d fixed thing 2025-02-05 16:13:22 -05:00
Alexandru Ionut Tripon
7d202e348d
Use the same UI for instance and global settings (#3341) 2025-02-05 23:04:54 +02:00
Alexandru Ionut Tripon
364b5be856
Add stale workflow (#3389) 2025-02-05 23:04:38 +02:00
TheKodeToad
e1896d5b6b
Update launcher/ui/pages/global/MinecraftPage.h
Co-authored-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-02-05 13:45:49 +00:00
TheKodeToad
6e3dd04a1f
Update launcher/ui/pages/global/MinecraftPage.h
Co-authored-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-02-05 12:09:32 +00:00
TheKodeToad
f7a401ab22
Update launcher/ui/pages/instance/InstanceSettingsPage.h
Co-authored-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-02-05 12:09:26 +00:00
TheKodeToad
9ed6801b15
Update launcher/ui/pages/global/JavaPage.h
Co-authored-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-02-05 12:09:12 +00:00
TheKodeToad
06b50dfd33
Fix more header mistakes
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-02-05 11:11:45 +00:00
TheKodeToad
5a76d9b7e1
Make requested changes
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-02-05 10:58:06 +00:00
Alexandru Ionut Tripon
34438824c1
Update .github/workflows/stale.yml
Co-authored-by: Seth Flynn <getchoo@tuta.io>
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
2025-02-04 23:15:18 +02:00
Alexandru Ionut Tripon
ac598740b3
Update .github/workflows/stale.yml
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
2025-02-04 22:08:46 +02:00
Alexandru Ionut Tripon
85c2710e33
Apply suggestions from code review
Co-authored-by: Seth Flynn <getchoo@tuta.io>
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
2025-02-04 22:08:16 +02:00
Alexandru Ionut Tripon
afe6d4836d
Update .github/workflows/stale.yml
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
2025-02-04 17:41:36 +02:00
Trial97
9c26ba5932
add stale workflow
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-02-04 16:13:55 +02:00
renovate[bot]
2a987b5a4a
chore(deps): update hendrikmuhs/ccache-action action to v1.2.17 2025-02-03 22:11:56 +00:00
Alexandru Ionut Tripon
1a375dc243
Merge pull request #3382 from PrismLauncher/update_flake_lock_action
chore(nix): update lockfile
2025-02-03 00:28:59 +02:00
Alexandru Ionut Tripon
ab3792b080
Merge pull request #3381 from razzeee/patch-1
Add PackageManager category
2025-02-03 00:28:42 +02:00
Seth Flynn
f9dcc31843
Merge pull request #3374 from getchoo-contrib/ci/fix-nix-out-paths
Fix Nix workflow
2025-02-02 17:18:03 -05:00
Seth Flynn
989e6c6cb6
ci: directly print nix out paths to step summary
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-02-01 22:55:39 -05:00
Seth Flynn
138684a84f
Revert "ci: add nix out paths to job summary"
This reverts commit 588e0d6305.

Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-02-01 22:54:26 -05:00
github-actions[bot]
faf7e6b06e chore(nix): update lockfile
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/825479c345a7f806485b7f00dbe3abb50641b083?narHash=sha256-nU6AezEX4EuahTO1YopzueAXfjFfmCHylYEFCagduHU%3D' (2025-01-24)
  → 'github:NixOS/nixpkgs/3a228057f5b619feb3186e986dbe76278d707b6e?narHash=sha256-xvTo0Aw0%2Bveek7hvEVLzErmJyQkEcRk6PSR4zsRQFEc%3D' (2025-02-01)
2025-02-02 00:24:39 +00:00
Kolja
f1671e3ded
Add PackageManager category
Signed-off-by: Kolja <razzeee@gmail.com>
2025-02-01 20:25:01 +01:00
Alexandru Ionut Tripon
6404d81ada
Merge pull request #3373 from Trial97/fix_crash2
Show warning when adding new resources with no instances
2025-01-31 23:03:55 +02:00
Alexandru Ionut Tripon
d6eb96df5c
Merge pull request #3260 from Trial97/fix_modrinth_shapshot
Map Minecraft snapshots to Modrinth
2025-01-31 23:02:55 +02:00
Trial97
c5efe081b4
fix add resource with no instance
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-01-31 22:03:57 +02:00
Seth Flynn
a639bc019e
Merge pull request #3368 from getchoo-contrib/ci/print-nix-out-paths 2025-01-31 07:00:37 -05:00
Seth Flynn
588e0d6305
ci: add nix out paths to job summary
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-30 13:34:34 -05:00
Sefa Eyeoglu
d7eddedf41
Merge pull request #3359 from PrismLauncher/ci/flakehub 2025-01-30 16:06:58 +01:00
Alexandru Ionut Tripon
6ae9fa4df4
Merge pull request #3314 from Trial97/update_modules
update submodules
2025-01-29 23:35:08 +02:00
Seth Flynn
86cc6d3c5e
feat: publish on flakehub
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-29 16:34:31 -05:00
Seth Flynn
61d7f08883
ci: use generic workflow for publishing
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-29 16:34:27 -05:00
Seth Flynn
0deb5e20ae
Merge pull request #3362 from getchoo-contrib/ci/flakehub-cache 2025-01-29 16:34:02 -05:00
Alexandru Ionut Tripon
fcb3dd5e9d
Merge pull request #3346 from PrismLauncher/update_flake_lock_action
chore(nix): update lockfile
2025-01-29 23:33:01 +02:00
Seth Flynn
169f5728b1
ci: run nix workflow on pull_request_target
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-29 16:22:44 -05:00
Seth Flynn
8816be1668
ci: use flakehub cache
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-29 16:17:01 -05:00
Alexandru Ionut Tripon
bd752c025c
Merge pull request #3360 from getchoo-contrib/ci/split
Split Flatpak and Nix CI jobs from main build workflow
2025-01-29 22:51:07 +02:00
Seth Flynn
49d734f314
ci: separate flatpak job
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-29 08:32:49 -05:00
Seth Flynn
417688089d
ci: separate nix job
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-29 08:31:45 -05:00
Seth Flynn
de21b77ae1
Merge pull request #3334 from PrismLauncher/renovate/determinatesystems-magic-nix-cache-action-9.x
chore(deps): update determinatesystems/magic-nix-cache-action action to v9
2025-01-29 07:44:06 -05:00
TheKodeToad
68bf409a98
Fix formatting issues
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-01-27 19:14:57 +00:00
TheKodeToad
5d179671bf
Fix non-Linux build
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-01-27 18:55:24 +00:00
TheKodeToad
dc6f536843
Fix Qt 5 compilation
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-01-27 18:04:41 +00:00
TheKodeToad
cc504f4a6c
Merge remote-tracking branch 'upstream/develop' into unify-mc-settings
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-01-27 17:02:50 +00:00
TheKodeToad
a92c68adbd
Remove duplicate console section
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-01-27 16:54:45 +00:00
TheKodeToad
7aaa9fa93c
Finish off MinecraftSettingsWidget implementation
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-01-27 16:46:26 +00:00
TheKodeToad
81769b9047
Use MinecraftInstancePtr instance of InstancePtr
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-01-27 14:34:07 +00:00
TheKodeToad
147b315c14
Update thresholds when min. memory value is changed too
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-01-27 14:15:07 +00:00
TheKodeToad
9d0918a064
Remove outdated JavaWizardWidget comment
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-01-27 13:19:10 +00:00
TheKodeToad
98ab686348
Merge remote-tracking branch 'upstream/develop' into unify-mc-settings
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-01-27 13:18:19 +00:00
TheKodeToad
39ae2fbd4a
Fix tab inconsistencies
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-01-26 22:44:43 +00:00
TheKodeToad
36a35d7925
Fix tab order
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-01-26 22:36:48 +00:00
TheKodeToad
1b4b36df33
Unify Java page
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-01-26 22:25:26 +00:00
github-actions[bot]
3d9e073721 chore(nix): update lockfile
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/5df43628fdf08d642be8ba5b3625a6c70731c19c?narHash=sha256-Tbk1MZbtV2s5aG%2BiM99U8FqwxU/YNArMcWAv6clcsBc%3D' (2025-01-16)
  → 'github:NixOS/nixpkgs/825479c345a7f806485b7f00dbe3abb50641b083?narHash=sha256-nU6AezEX4EuahTO1YopzueAXfjFfmCHylYEFCagduHU%3D' (2025-01-24)
2025-01-26 00:23:21 +00:00
Alexandru Ionut Tripon
fdb4159f2a
Merge pull request #3332 from eff3ry/modrinth-filter-tweak
Update Modrinth Client/Server Filters to match Modrinth website & launcher.
2025-01-21 23:42:37 +02:00
Alexandru Ionut Tripon
98be91c9d5
Merge pull request #3294 from Trial97/fix_flame_update
Fix updating Curseforge mods with missing mod loader
2025-01-21 23:40:21 +02:00
renovate[bot]
705375c0df
chore(deps): update determinatesystems/magic-nix-cache-action action to v9 2025-01-21 21:35:18 +00:00
TheKodeToad
8d3684c484
Merge pull request #1413 from Trial97/fix_curse_import
feat: updated flame mod resolution
2025-01-21 13:53:00 +00:00
Alexandru Ionut Tripon
0d4eeb9965
Merge pull request #3330 from getchoo-contrib/mingw-cfg
Use better compile flags for MINGW
2025-01-21 15:18:43 +02:00
Jeffery Tolmie
caccaefb39
Change "none" to ""
Co-authored-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
Signed-off-by: Jeffery Tolmie <jeffery.tolmie@gmail.com>
2025-01-22 02:06:49 +13:00
eff3ry
532fec7748 DCO Remediation Commit
DCO Remediation Commit for eff3ry <jeffery.tolmie@gmail.com>

I, eff3ry <jeffery.tolmie@gmail.com>, hereby add my Signed-off-by to this commit: 2a403b3bfe
I, eff3ry <jeffery.tolmie@gmail.com>, hereby add my Signed-off-by to this commit: 4ba3fd559d

Signed-off-by: eff3ry <jeffery.tolmie@gmail.com>
2025-01-22 01:48:04 +13:00
eff3ry
4ba3fd559d Update Filter Checks
Updates the filter checks so that options both and none can be differentiated.

Signed-off-by: Jeffery Tolmie Jeffery.tolmie@gmail.com
2025-01-22 01:40:59 +13:00
eff3ry
2a403b3bfe Edit Modrinth search faucets
Edit Modrinth search faucets to alter the client and server filters to be more in-line with the Modrinth website and Launcher, making it easier to find client-side only mods etc.

Signed-off-by: Jeffery Tolmie Jeffery.tolmie@gmail.com
2025-01-22 01:39:28 +13:00
Seth Flynn
f8a504e254
Merge pull request #3299 from Trial97/appimage2
Use Ubuntu 22.04 for Linux builds
2025-01-20 23:43:45 -05:00
Seth Flynn
e66f447ce5
feat: use better compile flags for mingw
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-20 20:36:48 -05:00
Seth Flynn
67b218b1f3
Merge pull request #2895 from Edgars-Cirulis/qt-6.8
chore: update to qt 6.8.x
2025-01-20 18:53:05 -05:00
Alexandru Ionut Tripon
db766574a4
Update .github/workflows/build.yml
Co-authored-by: Seth Flynn <getchoo@tuta.io>
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
2025-01-21 01:38:13 +02:00
timoreo
18da01c9a6
Merge pull request #3320 from PrismLauncher/update_flake_lock_action
chore(nix): update lockfile
2025-01-20 09:48:11 +01:00
Alexandru Ionut Tripon
c79ad73311
Merge pull request #3319 from getchoo-contrib/flatpak-arm
Build Flatpaks for ARM
2025-01-19 10:16:41 +02:00
Sefa Eyeoglu
fb3926a85d
Merge pull request #3318 from getchoo-contrib/nix-arm
Build Nix packages for aarch64-linux
2025-01-19 09:08:17 +01:00
github-actions[bot]
8d4f279539 chore(nix): update lockfile
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/bffc22eb12172e6db3c5dde9e3e5628f8e3e7912?narHash=sha256-8YVQ9ZbSfuUk2bUf2KRj60NRraLPKPS0Q4QFTbc%2Bc2c%3D' (2025-01-08)
  → 'github:NixOS/nixpkgs/5df43628fdf08d642be8ba5b3625a6c70731c19c?narHash=sha256-Tbk1MZbtV2s5aG%2BiM99U8FqwxU/YNArMcWAv6clcsBc%3D' (2025-01-16)
2025-01-19 00:24:56 +00:00
Alexandru Ionut Tripon
1b5d3c2bf9
Update .github/workflows/build.yml
Co-authored-by: seth <getchoo@tuta.io>
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
2025-01-19 02:03:21 +02:00
Trial97
be3eca8c97
remove specific step for qt6
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-01-19 01:05:06 +02:00
seth
66f0397087
ci: build flatpaks for arm
Signed-off-by: seth <getchoo@tuta.io>
2025-01-18 17:16:02 -05:00
seth
8e8538b506
ci: build nix packages for aarch64-linux
Signed-off-by: seth <getchoo@tuta.io>
2025-01-18 16:55:38 -05:00
Trial97
dc00c47f2e
update submodules
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-01-18 13:27:18 +02:00
Trial97
7d3238d234
rename variables
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-01-17 21:09:44 +02:00
Alexandru Ionut Tripon
f17599c8b8
Merge pull request #1689 from Trial97/progrss_bar_interaction
Added mouse interactions for labels in ProgressDialog
2025-01-17 20:59:52 +02:00
Trial97
8e5a7c6e33
ensure that the snapshot mapping is on all apis
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-01-17 10:35:13 +02:00
Trial97
1d8bf1d5a7
map modrinth snapshots
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-01-17 09:19:39 +02:00
Alexandru Ionut Tripon
c2f4389e5f
Merge pull request #3254 from Trial97/remove_multimc
remove EditAccountDialog
2025-01-17 08:51:44 +02:00
Alexandru Ionut Tripon
b7d117cc77
Merge pull request #2737 from maskersss/log-upload
Truncate logs for mclo.gs to fit 25k line limit
2025-01-17 08:49:49 +02:00
seth
1cce582d56
Merge pull request #3292 from Trial97/fix_modrinth_link
fix modrinth link
2025-01-16 20:42:00 -05:00
seth
867aaf881e
Merge pull request #3286 from PrismLauncher/update_flake_lock_action
chore(nix): update lockfile
2025-01-16 20:40:44 -05:00
seth
14b348ffe7
Merge pull request #3285 from PrismLauncher/renovate/hendrikmuhs-ccache-action-1.x
chore(deps): update hendrikmuhs/ccache-action action to v1.2.16
2025-01-16 20:40:17 -05:00
Edgars Cīrulis
181610a8c1
Merge branch 'PrismLauncher:develop' into qt-6.8 2025-01-17 03:04:43 +02:00
seth
b7f2175718
Merge pull request #3179 from sshcrack/fix-inst-java
Fix $INST_JAVA not being set for auto download java
2025-01-16 20:03:26 -05:00
seth
7a1dc0e9c9
Merge pull request #1555 from Trial97/env_vars
correctly expand env vars for pre/post launch commands
2025-01-16 19:45:31 -05:00
Trial97
9317a675ed
removed unused type
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-01-16 17:07:44 +02:00
Trial97
7cb8deeb90
feat: updated flame mod resolution
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-01-16 17:05:36 +02:00
Trial97
bca517b8d3
fix appimage
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-01-16 12:10:19 +02:00
Trial97
e4ad4051c8
fix curseforge with empty loader as newest version
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-01-13 13:56:55 +02:00
Trial97
3ee73916ca
fix modrinth link
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-01-13 08:16:12 +02:00
github-actions[bot]
03834e1455 chore(nix): update lockfile
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/6df24922a1400241dae323af55f30e4318a6ca65?narHash=sha256-dklw3AXr3OGO4/XT1Tu3Xz9n/we8GctZZ75ZWVqAVhk%3D' (2025-01-02)
  → 'github:NixOS/nixpkgs/bffc22eb12172e6db3c5dde9e3e5628f8e3e7912?narHash=sha256-8YVQ9ZbSfuUk2bUf2KRj60NRraLPKPS0Q4QFTbc%2Bc2c%3D' (2025-01-08)
2025-01-12 00:26:01 +00:00
renovate[bot]
54da41c625
chore(deps): update hendrikmuhs/ccache-action action to v1.2.16 2025-01-11 22:11:18 +00:00
Alexandru Ionut Tripon
f4f1d5f0d7
Merge pull request #3228 from Trial97/delete_block
Add settings to control the if prism should move the downloaded mods
2025-01-09 00:12:12 +02:00
Alexandru Ionut Tripon
fbd886e9db
Merge pull request #3255 from Trial97/propagate_load
propagate load component error
2025-01-09 00:11:48 +02:00
Alexandru Ionut Tripon
0fb99d837f
Merge pull request #3230 from PrismLauncher/update_flake_lock_action
chore(nix): update lockfile
2025-01-09 00:11:24 +02:00
Alexandru Ionut Tripon
cb8320ca41
Merge pull request #3278 from Trial97/remove_drop
remove drop label
2025-01-09 00:10:50 +02:00
Alexandru Ionut Tripon
33981b9870
Merge pull request #3215 from Trial97/fix_custom_instance_icons
Fix curseforge/modrinth instance icons
2025-01-09 00:10:39 +02:00
Alexandru Ionut Tripon
07a0a15457
Merge pull request #3277 from Trial97/drop_file
fix drop file on resource page
2025-01-08 10:46:55 +02:00
Trial97
0f9ea5c949
remove drop label
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-01-08 01:55:45 +02:00
Trial97
d40c8b704c
fix drop file on resource page
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-01-08 01:52:11 +02:00
github-actions[bot]
82a500f4e8 chore(nix): update lockfile
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/5d67ea6b4b63378b9c13be21e2ec9d1afc921713?narHash=sha256-Pj39hSoUA86ZePPF/UXiYHHM7hMIkios8TYG29kQT4g%3D' (2024-12-11)
  → 'github:NixOS/nixpkgs/6df24922a1400241dae323af55f30e4318a6ca65?narHash=sha256-dklw3AXr3OGO4/XT1Tu3Xz9n/we8GctZZ75ZWVqAVhk%3D' (2025-01-02)
2025-01-05 00:25:34 +00:00
Trial97
6ebfcb91cf
propagate load component error
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-12-31 13:44:22 +02:00
Trial97
e01df73deb
remove EditAccountDialog
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-12-31 11:13:01 +02:00
Trial97
7dde35fef2
Add settings to control the if prism should move the downloaded mods
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-12-20 19:57:09 +02:00
Alexandru Ionut Tripon
0b6ce5525d
Merge pull request #3216 from Trial97/fix_auto_mod_provider
Fix automatically choose mod provider option
2024-12-19 09:16:59 +02:00
timoreo
f17faadf8a
Merge pull request #3174 from PrismLauncher/update_flake_lock_action
chore(nix): update lockfile
2024-12-18 19:54:44 +01:00
timoreo
c7b6977692
Merge pull request #3043 from Trial97/fix_flame_loader_match
fix the flame loaders match
2024-12-18 19:53:22 +01:00
timoreo
423d72a6e8
Merge pull request #3197 from Trial97/fix_curseforge_import
Do not fail curseforge import if modrinth file check fails
2024-12-18 19:48:05 +01:00
timoreo
2a2ed9f8bd
Merge pull request #3214 from kthchew/fix/manifest-symlink
Correct symbolic link destination on manifest (Mojang) Java download
2024-12-18 19:42:25 +01:00
Trial97
0412ed3f78
Fix automatically choose mod provider option
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-12-16 14:51:59 +02:00
Trial97
f4d93a08ba
Fix curseforge/modrinth instance icons
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-12-16 13:27:44 +02:00
Kenneth Chew
8d53242952
Correct symbolic link destination on manifest Java download
Signed-off-by: Kenneth Chew <79120643+kthchew@users.noreply.github.com>
2024-12-16 03:30:09 -05:00
github-actions[bot]
706e263050 chore(nix): update lockfile
Flake lock file updates:

• Updated input 'flake-compat':
    'github:edolstra/flake-compat/9ed2ac151eada2306ca8c418ebd97807bb08f6ac?narHash=sha256-HRJ/18p%2BWoXpWJkcdsk9St5ZiukCqSDgbOGFa8Okehg%3D' (2024-11-27)
  → 'github:edolstra/flake-compat/ff81ac966bb2cae68946d5ed5fc4994f96d0ffec?narHash=sha256-NeCCThCEP3eCl2l/%2B27kNNK7QrwZB1IJCrXfrbv5oqU%3D' (2024-12-04)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/970e93b9f82e2a0f3675757eb0bfc73297cc6370?narHash=sha256-jNRNr49UiuIwaarqijgdTR2qLPifxsVhlJrKzQ8XUIE%3D' (2024-11-28)
  → 'github:NixOS/nixpkgs/5d67ea6b4b63378b9c13be21e2ec9d1afc921713?narHash=sha256-Pj39hSoUA86ZePPF/UXiYHHM7hMIkios8TYG29kQT4g%3D' (2024-12-11)
2024-12-15 00:27:47 +00:00
Alexandru Ionut Tripon
507faf9f00
Merge pull request #3150 from kthchew/fix/unneeded-hr-exceptions
Remove unnecessary hardened runtime exceptions on macOS
2024-12-14 19:30:11 +02:00
Alexandru Ionut Tripon
c0803365ef
Merge pull request #3202 from kthchew/chore/bump-sparkle
[macOS] Update Sparkle to v2.6.4
2024-12-14 18:18:04 +02:00
Alexandru Ionut Tripon
02e527c15b
Merge pull request #3199 from kthchew/fix/launcher-settings-tab
Fix tab order in launcher settings
2024-12-14 18:17:08 +02:00
Alexandru Ionut Tripon
5659dad23d
Merge pull request #3198 from kthchew/fix/local-net-use-desc
[macOS] Add local network usage description
2024-12-14 18:16:34 +02:00
Alexandru Ionut Tripon
647bb04a0d
Merge pull request #3181 from Trial97/fix_mrpack_crash
fix crash with invalid mrpack format
2024-12-14 18:15:50 +02:00
Kenneth Chew
b6cd46ad27
Update Sparkle to v2.6.4
Signed-off-by: Kenneth Chew <79120643+kthchew@users.noreply.github.com>
2024-12-14 00:42:08 -05:00
Kenneth Chew
c3712ba648
Fix tab order in launcher settings
Signed-off-by: Kenneth Chew <79120643+kthchew@users.noreply.github.com>
2024-12-12 14:53:39 -05:00
Kenneth Chew
c3e44554ab
Add local network usage description
Signed-off-by: Kenneth Chew <79120643+kthchew@users.noreply.github.com>
2024-12-12 14:43:28 -05:00
Trial97
a1c1c0b769
Do not fail curseforge import if modrinth file check fails
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-12-12 17:29:46 +02:00
Alexandru Ionut Tripon
15e58589d5
Merge pull request #3170 from leia-uwu/fix-icon-theme-path
[Linux] Don't use fallback icon search paths
2024-12-09 09:14:25 +02:00
Edgars Cirulis
eb8c375ec5
chore: update Qt to 6.8.1
Signed-off-by: Edgars Cirulis <edgarsciruliss@gmail.com>
2024-12-09 00:40:05 +02:00
Trial97
1dd0728a58
fix crash with invalid mrpack format
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-12-08 22:04:16 +02:00
sshcrack
cf2dcbd431
load meta first
Signed-off-by: sshcrack <34072808+sshcrack@users.noreply.github.com>
2024-12-08 18:27:57 +01:00
sshcrack
dedb7a2343
change order of steps
Signed-off-by: sshcrack <34072808+sshcrack@users.noreply.github.com>
2024-12-08 14:39:06 +01:00
leia uwu
614574f15c
fix icon theme search paths
using fallback search paths breaks qadwaitadecorations

Signed-off-by: leia uwu <leia@tutamail.com>
2024-12-06 23:26:46 -03:00
Alexandru Ionut Tripon
28a471777a
Merge pull request #2800 from QazCetelic/advanced-icon-dialog
Add search and support for subdirectories to icon picker
2024-12-06 12:37:45 +02:00
Alexandru Ionut Tripon
93526637cb
Merge pull request #3166 from PrismLauncher/renovate/actions-cache-4.x
chore(deps): update actions/cache action to v4.2.0
2024-12-06 08:47:04 +02:00
renovate[bot]
94c893bd86
chore(deps): update actions/cache action to v4.2.0 2024-12-05 20:29:11 +00:00
Alexandru Ionut Tripon
c99876f3d7
Merge pull request #3154 from PrismLauncher/update_flake_lock_action
chore(nix): update lockfile
2024-12-03 16:17:50 +02:00
Alexandru Ionut Tripon
a3b2f1a931
Merge pull request #3119 from guihkx/flathub-manifest-sync
Sync Flatpak manifest with Flathub's
2024-12-02 17:30:19 +02:00
TheKodeToad
488dc7815c
Merge pull request #3135 from Trial97/italic
Make FTB Import note italic to match others
2024-12-02 14:07:42 +00:00
Tayou
dbee4bee14
Merge pull request #3152 from leia-uwu/fix-system-icons
Fix system icons on Linux
2024-12-02 15:00:43 +01:00
github-actions[bot]
2cfd116947 chore(nix): update lockfile
Flake lock file updates:

• Updated input 'flake-compat':
    'github:edolstra/flake-compat/0f9255e01c2351cc7d116c072cb317785dd33b33?narHash=sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U%3D' (2023-10-04)
  → 'github:edolstra/flake-compat/9ed2ac151eada2306ca8c418ebd97807bb08f6ac?narHash=sha256-HRJ/18p%2BWoXpWJkcdsk9St5ZiukCqSDgbOGFa8Okehg%3D' (2024-11-27)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/23e89b7da85c3640bbc2173fe04f4bd114342367?narHash=sha256-y/MEyuJ5oBWrWAic/14LaIr/u5E0wRVzyYsouYY3W6w%3D' (2024-11-19)
  → 'github:NixOS/nixpkgs/970e93b9f82e2a0f3675757eb0bfc73297cc6370?narHash=sha256-jNRNr49UiuIwaarqijgdTR2qLPifxsVhlJrKzQ8XUIE%3D' (2024-11-28)
2024-12-01 00:30:03 +00:00
leia uwu
3f67ef968b
fix: icon search paths memory leak
Signed-off-by: leia uwu <leia@tutamail.com>
2024-11-30 12:51:39 -03:00
leia uwu
fd9c80db62
fix: fix system icons
This sets the fallback icon theme to the current(system default) icon theme before
launcher specific themes are applied

And removes `Inherits` line of multimc/legacy icon theme because it can end up making it
inherit a default theme set from /usr/share/icons/default/index.theme
instead of the user configured theme (probably a qt bug?)

Signed-off-by: leia uwu <leia@tutamail.com>
2024-11-30 11:35:41 -03:00
Kenneth Chew
7e76638f2c
Only disable library validation if using ad-hoc signature
While not needed for regular releases, it is needed for CI when an Apple Developer account isn't available and you can only perform an ad-hoc signature.

Signed-off-by: Kenneth Chew <79120643+kthchew@users.noreply.github.com>
2024-11-29 21:18:07 -05:00
Kenneth Chew
884f15da1e
Remove unnecessary hardened runtime exceptions
These are cruft from an older time when the launcher was ad-hoc signed, and the OS couldn't tell the libraries and launcher were signed by the same party.

Additionally, add a workaround to send the necessary library injection to the game if Steam overlay is used despite library validation being on for the launcher.

Signed-off-by: Kenneth Chew <79120643+kthchew@users.noreply.github.com>
2024-11-29 21:18:06 -05:00
TheKodeToad
616aa002e1
Merge pull request #2875 from Jan200101/PR/switcheroo
[Linux] Integrate switcheroo-control for discrete GPU support
2024-11-28 20:02:58 +00:00
guihkx
e0faee7f26
ci(flatpak): update build artifact name
Just aligning the name of the Flatpak package with other build artifacts.

Signed-off-by: guihkx <626206+guihkx@users.noreply.github.com>
2024-11-27 14:35:15 -03:00
guihkx
f6770a847a
flatpak: update KDE runtime to 6.8
This also switches to the Docker image provided and maintained by
Flathub collaborators through the 'flathub-infra' organization on
GitHub, because it looks better maintained at the moment.

Signed-off-by: guihkx <626206+guihkx@users.noreply.github.com>
2024-11-26 13:51:09 -03:00
guihkx
4a50e94967
flatpak: update xrandr to 1.5.3
Signed-off-by: guihkx <626206+guihkx@users.noreply.github.com>
2024-11-26 13:51:09 -03:00
Jan200101
514ec99a03
Integrate switcheroo-control for discrete GPU support
Signed-off-by: Jan200101 <sentrycraft123@gmail.com>
2024-11-25 22:29:39 +01:00
TheKodeToad
6c062c48b4
Merge pull request #3130 from Kationor/trimversion
Improve MANIFEST.MF parsing
2024-11-25 10:05:02 +00:00
Kationor
b40a1973bf
Improve MANIFEST.MF parsing
Previously, we would only properly parse LF-encoded manifests, and even
those only if they used the recommended casing.

This commit allows the parser to recognise CR and CRLF newlines, and
also makes the name comparison case insensitive to align with the
specification. (Though not completely: we still don't support multiline
values)

Signed-off-by: Kationor <n96211028@gmail.com>
2024-11-24 19:30:59 +01:00
Trial97
a09af619ce
Make FTB Import note italic to match others
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-11-24 20:29:07 +02:00
TheKodeToad
835944387b
Merge pull request #3128 from QazCetelic/naming-clang-tidy
Add naming check in `.clang-tidy` file
2024-11-24 16:10:41 +00:00
QazCetelic
d927c539b5 Update CONTRIBUTING.md conventions section
Signed-off-by: QazCetelic <qaz.cetelic@protonmail.com>
2024-11-23 19:55:40 +01:00
QazCetelic
14454faac8 Use old list style
Signed-off-by: QazCetelic <qaz.cetelic@protonmail.com>
2024-11-23 18:21:03 +01:00
QazCetelic
2351531e84 Add naming checks to clang-tidy
Signed-off-by: QazCetelic <qaz.cetelic@protonmail.com>
2024-11-23 18:15:07 +01:00
QazCetelic
33ff3b4f36 Fix naming
Signed-off-by: QazCetelic <qaz.cetelic@protonmail.com>
2024-11-23 18:10:33 +01:00
seth
11e7b218d4
Merge pull request #3113 from PrismLauncher/update_flake_lock_action
chore(nix): adopt new darwin SDK pattern
2024-11-22 18:25:21 -05:00
seth
fbbd64ddb7
chore(nix): sync with upstream nixpkgs
Signed-off-by: seth <getchoo@tuta.io>
2024-11-22 17:33:16 -05:00
github-actions[bot]
41db87c8cf chore(nix): update lockfile
Flake lock file updates:

• Updated input 'nix-filter':
    'github:numtide/nix-filter/776e68c1d014c3adde193a18db9d738458cd2ba4?narHash=sha256-SCHiL%2B1f7q9TAnxpasriP6fMarWE5H43t25F5/9e28I%3D' (2024-10-29)
  → 'github:numtide/nix-filter/f7653272fd234696ae94229839a99b73c9ab7de0?narHash=sha256-oRam5PS1vcrr5UPgALW0eo1m/5/pls27Z/pabHNy2Ms%3D' (2024-11-13)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/4aa36568d413aca0ea84a1684d2d46f55dbabad7?narHash=sha256-Zwl8YgTVJTEum%2BL%2B0zVAWvXAGbWAuXHax3KzuejaDyo%3D' (2024-11-05)
  → 'github:NixOS/nixpkgs/23e89b7da85c3640bbc2173fe04f4bd114342367?narHash=sha256-y/MEyuJ5oBWrWAic/14LaIr/u5E0wRVzyYsouYY3W6w%3D' (2024-11-19)
2024-11-22 22:26:01 +00:00
Alexandru Ionut Tripon
79dd912126
Merge pull request #3091 from Awqre/develop
Lossless image asset compression
2024-11-20 10:12:34 +02:00
Awqre
00fa65c18b
Merge branch 'PrismLauncher:develop' into develop 2024-11-20 02:08:31 +00:00
seth
59db80cb0e
Merge pull request #3068 from Scrumplex/chore/update-nixos-readme
[Linux] Update Nix README
2024-11-19 10:09:11 -05:00
Alexandru Ionut Tripon
e5a4dfdf16
Merge pull request #2962 from AOSC-Tracking/upstream/detect-aosc-os-jdks
javautils: add a filter for detecting Java paths on AOSC OS
2024-11-19 13:44:57 +02:00
QazCetelic
6ca18c6266 Refactoring
Signed-off-by: QazCetelic <qaz.cetelic@protonmail.com>
2024-11-18 19:19:02 +01:00
QazCetelic
e74592fa09 Code style conventions (camelCase, m_ prefix)
Signed-off-by: QazCetelic <qaz.cetelic@protonmail.com>
2024-11-17 20:43:09 +01:00
QazCetelic
abbebff400 Removed bug when renaming icon file in nested folder while application is running
Signed-off-by: QazCetelic <qaz.cetelic@protonmail.com>
2024-11-17 20:35:24 +01:00
Awqre
3c88eccf17
Merge branch 'PrismLauncher:develop' into develop 2024-11-17 08:19:35 +00:00
TheKodeToad
48235f5b71
Merge pull request #3083 from Trial97/fix_resource_search
Fix resource search
2024-11-15 23:09:28 +00:00
timoreo
736ecf266d
Merge pull request #3019 from Trial97/task_parent
fixed double deletion for tasks
2024-11-15 06:19:40 +01:00
timoreo
b2a37452be
Merge pull request #3024 from Trial97/leak
fix leak on resource search
2024-11-15 06:18:33 +01:00
timoreo
54f2e75b9d
Merge pull request #3070 from TheKodeToad/general-scrollbar
Add scrollbar to Settings -> Launcher -> Features
2024-11-15 06:16:54 +01:00
timoreo
f7620a43aa
Merge pull request #3094 from Trial97/remove_resource_warning
Fix warnings regarding Resource static cast
2024-11-15 06:15:55 +01:00
timoreo
bcbe5da87f
Merge pull request #3096 from Trial97/dangling_file
Fix file remaingin open after program closure
2024-11-15 06:14:25 +01:00
Alexandru Ionut Tripon
e735ea7e63
Merge pull request #3003 from PrismLauncher/update_flake_lock_action
chore(nix): update lockfile
2024-11-14 22:50:53 +02:00
Trial97
ce61ed2f86 Fix file remaingin open after program closure
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-11-14 13:27:16 +02:00
Trial97
172e43cd3f Fix warnings regarding Resource static cast
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-11-13 23:11:33 +02:00
Awqre
9f0b2bafd8 Further additional even better(er) compression
Truly unprecedented levels of optimization we're reaching here. (OxiPNG/ZopfliPNG/ECT/DeflOpt)

Signed-off-by: Awqre <32497511+Awqre@users.noreply.github.com>
2024-11-13 14:31:20 -05:00
txtsd
03b34e39f8
feat: Compression script with compressed images
Also modified genicons.sh

Signed-off-by: txtsd <code@ihavea.quest>
2024-11-13 19:04:31 +05:30
Awqre
885fc66c14 Lossless image compression
Applied lossless zopfli/ect/deflopt compression to PNG files including ones contained in ICO files.

Repacked ICO files with optidash icopack

Signed-off-by: Awqre <32497511+Awqre@users.noreply.github.com>
2024-11-12 20:17:17 -05:00
github-actions[bot]
f41380d91c chore(nix): update lockfile
Flake lock file updates:

• Updated input 'nix-filter':
    'github:numtide/nix-filter/3342559a24e85fc164b295c3444e8a139924675b?narHash=sha256-1Wvk8UP7PXdf8bCCaEoMnOT1qe5/Duqgj%2BrL8sRQsSM%3D' (2024-03-11)
  → 'github:numtide/nix-filter/776e68c1d014c3adde193a18db9d738458cd2ba4?narHash=sha256-SCHiL%2B1f7q9TAnxpasriP6fMarWE5H43t25F5/9e28I%3D' (2024-10-29)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/4c2fcb090b1f3e5b47eaa7bd33913b574a11e0a0?narHash=sha256-/uilDXvCIEs3C9l73JTACm4quuHUsIHcns1c%2BcHUJwA%3D' (2024-10-18)
  → 'github:NixOS/nixpkgs/4aa36568d413aca0ea84a1684d2d46f55dbabad7?narHash=sha256-Zwl8YgTVJTEum%2BL%2B0zVAWvXAGbWAuXHax3KzuejaDyo%3D' (2024-11-05)
2024-11-10 00:24:54 +00:00
Trial97
66883db645 Fix removed signal handler for filter resource
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-11-09 23:30:04 +02:00
Tayou
df45e5518f
Merge pull request #3081 from Trial97/fix_markdown
fix CONTRIBUTING.md formating
2024-11-09 22:19:41 +01:00
Trial97
4c8c6e543e fix contrributing
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-11-08 00:18:12 +02:00
Alexandru Ionut Tripon
1d9508c268
Merge pull request #3074 from Trial97/lock
Close the window using the invokeMethod to not block
2024-11-07 23:45:44 +02:00
Alexandru Ionut Tripon
7d03cb114d
Merge pull request #3031 from Trial97/lambda
do not capture by default all values in lambdas
2024-11-07 23:45:31 +02:00
Tayou
2fcf03ead3
Merge pull request #3036 from Trial97/typo4 2024-11-07 17:30:47 +01:00
TheKodeToad
012bbca197
JavaSettingsWidget -> JavaWizardWidget
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-11-07 14:51:43 +00:00
TheKodeToad
8132828ad7
Phrasing fix
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-11-07 13:36:33 +00:00
Trial97
9601fbf2d5
Close the window using the invokeMethod to not block
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-11-07 15:07:17 +02:00
TheKodeToad
6e55239cec
Merge pull request #3073 from TheKodeToad/fix-formatting-guidelines-oversight
Fix formatting oversight
2024-11-07 13:01:15 +00:00
TheKodeToad
900235642c
Fix formatting oversight
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-11-07 12:16:22 +00:00
TheKodeToad
f667f56fb7
Merge pull request #3067 from TheKodeToad/updated-formatting-guidelines
Updated formatting guidelines
2024-11-07 11:43:12 +00:00
TheKodeToad
e06b3e7a7a
Implement open global settings
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-11-07 00:47:32 +00:00
TheKodeToad
223b6b3b2b
Fix non-Linux build
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-11-07 00:22:22 +00:00
TheKodeToad
71e55f8829
Add scrollbar to Settings -> Launcher -> Features
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-11-07 00:11:27 +00:00
TheKodeToad
e6d4e5d78d
Unify instance and global settings pt.1
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-11-06 23:46:30 +00:00
TheKodeToad
8ecc21c093
Make Ryex's suggested change
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-11-06 22:12:22 +00:00
Sefa Eyeoglu
0a80da28fa
chore: update Nix readme
Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
2024-11-06 19:29:57 +01:00
TheKodeToad
b67d400f60
Update formatting guidelines
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-11-06 16:27:03 +00:00
timoreo
1ab10bdd84
Merge pull request #3060 from Ryex/ci/bump_linux_qt
ci: bump linux qt version
2024-11-05 11:25:27 +01:00
Rachel Powers
ba6743d134
ci: libxcb-curcsor_dev ?
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
2024-11-05 01:30:55 -07:00
Rachel Powers
d63a336411
ci: bump linux qt version
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
2024-11-05 01:10:38 -07:00
Alexandru Ionut Tripon
4a92573235
Merge pull request #3042 from Trial97/isportable
use isPortable to determine if the MSAStep should check for url handler
2024-11-03 11:55:55 +02:00
Alexandru Ionut Tripon
db3d796275
Merge pull request #3045 from Trial97/ask_retry3
disable retry for modrinth currentVersions API
2024-11-03 11:55:37 +02:00
Trial97
6f2f3c2d3b
disable retry for modrinth currentVersions API
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-11-01 17:34:29 +02:00
Trial97
f77f0207f7
fix the flame loaders match
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-11-01 14:30:47 +02:00
Trial97
99cfb62370
use isPortable to determine if the MSAStep should check for url handler
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-11-01 11:03:47 +02:00
Trial97
2b590f24ed
remove task parent from constuctor
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-11-01 00:11:45 +02:00
Trial97
670c932f32 Fix installed typo
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-31 14:51:18 +02:00
TheKodeToad
ba07a136dc
Merge pull request #3035 from PrismLauncher/remove-BasicFolderLoadTask
Remove BasicFolderLoadTask
2024-10-31 09:17:27 +00:00
TheKodeToad
9ae16921bc
Remove BasicFolderLoadTask
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-30 21:10:43 +00:00
Tayou
887e4a964a
Merge pull request #3010 from Trial97/lock3 2024-10-30 21:56:17 +01:00
seth
8faff9ac6a
Merge pull request #3030 from Trial97/sys 2024-10-30 15:16:38 -04:00
Trial97
0e80aae1b8
Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into lambda 2024-10-30 19:35:25 +02:00
TheKodeToad
c142d38f08
Merge pull request #1588 from TheKodeToad/resource-meta
Implement tracking and updates for files other than mods
2024-10-30 17:03:54 +00:00
Trial97
82bffabb69
do not capture by default all values in lambdas
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-30 18:16:43 +02:00
Trial97
14c95b9d37
fix macos system detection
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-30 17:15:42 +02:00
Alexandru Ionut Tripon
b7598c6a41
Merge pull request #2331 from michaeltyson/develop
Support for launching from command line in offline mode
2024-10-30 09:49:33 +02:00
Michael Tyson
ced2f9d1a0 Applied clang formatting
Signed-off-by: Michael Tyson <michael@atastypixel.com>
2024-10-30 12:51:34 +11:00
Michael Tyson
5661444414 Removed “--name” flag in favour of specifying offline player name via “--offline” flag
Signed-off-by: Michael Tyson <michael@atastypixel.com>
2024-10-30 12:37:14 +11:00
Michael Tyson
afa1e0599a Improved formatting
Signed-off-by: Michael Tyson <michael@atastypixel.com>
2024-10-30 12:34:33 +11:00
Michael Tyson
43e4481b70 Support for launching from command line in offline mode
This allows launching an offline instance with --offline --name=OfflineName. This is useful for playing split screen by creating two side-by-side instances, which is impossible online, unless one is using two separate paid accounts.

With this PR, it makes it possible to launch from a script - otherwise, one has to launch manually, which is a pain, or create offline profiles for each instance, which interferes with some functionality like skins (my autistic son takes great issue with his skin not being visible, when using offline profiles!).

Implementation is based on MultiMC, which supports this feature.

See also https://github.com/PrismLauncher/PrismLauncher/issues/1059 for discussion.

Signed-off-by: Michael Tyson <michael@atastypixel.com>
2024-10-30 12:30:20 +11:00
Tayou
65b0fdf08d
Merge pull request #3027 from benthetechguy/metadata 2024-10-29 23:06:01 +01:00
Ben Westover
f6511c601e
Fix some typos
Signed-off-by: Ben Westover <me@benthetechguy.net>
2024-10-29 20:45:10 +00:00
Ben Westover
fd109c4740
Shorten metainfo.xml app summary
Signed-off-by: Ben Westover <me@benthetechguy.net>
2024-10-29 20:45:06 +00:00
TheKodeToad
2368521a9a
Add missing semicolon
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-29 17:54:03 +00:00
TheKodeToad
d49e6187cd
Make requested changes
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-29 17:17:51 +00:00
TheKodeToad
c1c198b84b
Combine if statements
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-29 15:45:30 +00:00
TheKodeToad
e7ff3146fe
Merge pull request #1953 from Trial97/account2
Auto reauthenticate account if expired
2024-10-29 14:08:50 +00:00
Trial97
8e7d84d05b
fix leak on resource search
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-29 00:40:13 +02:00
TheKodeToad
d157159106
Fix license headers and formatting
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-28 22:29:43 +00:00
TheKodeToad
bd8b30c606
Implement link for InfoFrame::updateWithResource
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-28 22:17:16 +00:00
TheKodeToad
f56a8feade
Merge pull request #2986 from Trial97/bump_10.x
bump develop to 10
2024-10-28 21:12:53 +00:00
TheKodeToad
f3cae427f1
Fix tests (make them green)
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-28 21:09:45 +00:00
Alexandru Ionut Tripon
51b7abec24
Merge pull request #3005 from Trial97/installed
add installed text to version select
2024-10-28 22:55:40 +02:00
Alexandru Ionut Tripon
d90ed89a30
Merge pull request #2989 from Trial97/what
add extra protection against empty download link
2024-10-28 22:55:21 +02:00
TheKodeToad
ad1d5ddcdf
Revert unwise refactor
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-28 20:42:59 +00:00
Alexandru Ionut Tripon
2dee936246
Merge pull request #2837 from Trial97/translate_standeard_butons
translate standard buttons
2024-10-28 22:31:47 +02:00
Alexandru Ionut Tripon
a0a74612e1
Merge pull request #1894 from Trial97/maximize
Fixed the maximized option on the newer Minecraft versions
2024-10-28 22:30:09 +02:00
Alexandru Ionut Tripon
fbd3aa36c4
Merge pull request #3006 from Trial97/relase_type
add release_type filer for mod/modpacks
2024-10-28 22:29:38 +02:00
Trial97
94f65f8727
add qassert
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-28 22:27:40 +02:00
TheKodeToad
2b22a1aebe
Reimplement View Homepage
I removed it for some reason, but now it's back for all pages!

Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-28 20:14:56 +00:00
TheKodeToad
354a0ed46e
Fix column sizes
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-28 18:01:01 +00:00
Trial97
9cdda4377b
remove message box
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-28 19:19:06 +02:00
TheKodeToad
49044e23de
Fix Export List placement
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-28 16:21:00 +00:00
TheKodeToad
f57ca1e79e
Add Change Version action to all pages
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-28 16:07:41 +00:00
Trial97
0f42f3cc5c
Added warning regarding maximized option
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-28 17:19:18 +02:00
Trial97
81c5866fa9
Made sure that the qt version check is in place
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-28 17:19:18 +02:00
Trial97
1be29076ea
When java disappoints try C++
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-28 17:19:18 +02:00
Trial97
3c17f606ed
Patched the maximized option on the newer Minecraft versions
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-28 17:19:18 +02:00
Trial97
09a118e85e
expand env from wrapped cmd
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-28 16:23:33 +02:00
Trial97
c12beb43a0
correctly expand env vars
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-28 16:23:33 +02:00
Trial97
08f5148a9a
Added mouse interactions for labels in ProgressDialog
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-28 16:22:52 +02:00
Trial97
14ad720c58
fix conflict
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-28 16:21:40 +02:00
Trial97
0ee5665c95
Revert "Removed auto reauthenticate"
This reverts commit 400b518bc2.

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-28 16:21:39 +02:00
Trial97
bd82737efb
translate standard buttons
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-28 15:46:47 +02:00
Trial97
f75e049675
add release_type filer for mod/modpacks
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-28 15:36:47 +02:00
Tayou
1f8ce9eb92
Merge pull request #3002 from Trial97/typo2 2024-10-28 14:19:53 +01:00
Alexandru Ionut Tripon
d4bf61da50
Merge pull request #3007 from Trial97/open_source
add open source filter
2024-10-28 15:04:33 +02:00
Tayou
33754db9b9
Merge pull request #3009 from Trial97/order 2024-10-28 14:04:20 +01:00
TheKodeToad
1809858fc7
Ignore .index in ExportPackDialog
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-28 11:01:30 +00:00
Trial97
0cafac84ac
replace currentData with itemData on QComboBox::currentIndexChanged slots
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-28 12:54:21 +02:00
Trial97
9eb1ce3ad7
replace unzipping with unpacking
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-28 12:39:26 +02:00
TheKodeToad
6a6fe60a5b
Revert "Use non-mod metadata in ModrinthPackExportTask"
Out-of-scope

Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-28 10:29:49 +00:00
Alexandru Ionut Tripon
6338e2b362
Update launcher/ui/widgets/ModFilterWidget.ui
Co-authored-by: TheKodeToad <TheKodeToad@proton.me>
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
2024-10-28 00:34:41 +02:00
txtsd
b1113f4293
Merge pull request #3008 from Trial97/about
change contributors name
2024-10-28 01:01:43 +05:30
Trial97
c4cd7cf6c1
change ZekeZ name
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-27 13:17:58 +02:00
Trial97
11d4d948aa
lock m_instanceExtras
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-27 12:36:14 +02:00
Trial97
61448a61ea
swap search with filter button for modpacks
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-27 12:06:45 +02:00
Trial97
f373a5cea0
change contributors name
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-27 11:58:34 +02:00
Trial97
935b2e8fb2
add open source filter
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-27 11:56:48 +02:00
Trial97
b1c8ab9350
add installed text to version select
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-27 10:56:14 +02:00
Trial97
b593ff09e9
fix #3001
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-27 03:05:56 +02:00
Trial97
44894a29b1
rename snake_case to camelCase
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-27 03:05:30 +02:00
Trial97
685d3c79ac
fix unzipping typo
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-27 03:02:13 +03:00
TheKodeToad
b66d6b2812
Unhardcode PREFIXES
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-26 13:24:03 +01:00
seth
5b8ea89092
Merge pull request #2992 from getchoo-contrib/nix/sync 2024-10-25 20:56:44 -04:00
TheKodeToad
13e13ea8fc
Use non-mod metadata in ModrinthPackExportTask
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-25 19:13:41 +01:00
seth
63b10738b2
chore(nix): use self for version
Signed-off-by: seth <getchoo@tuta.io>
2024-10-25 02:35:53 -04:00
seth
a5c554cf6e
chore(nix): sync with nixpkgs
Signed-off-by: seth <getchoo@tuta.io>
2024-10-25 02:35:53 -04:00
seth
6a2ed4a631
Merge pull request #2982 from TheKodeToad/launcher-level-autoinstalljava
Use Launcher log level in AutoInstallJava
2024-10-25 02:23:02 -04:00
seth
b7152e7010
Merge pull request #2963 from PrismLauncher/renovate/actions-cache-4.x
chore(deps): update actions/cache action to v4.1.2
2024-10-25 02:22:12 -04:00
seth
d766de5fbf
Merge pull request #2980 from Arcitec/flatpak-enhancements
fix: bring back Flathub manifest improvements to the repo
2024-10-25 02:19:09 -04:00
Trial97
8daa1219a5
add extra protection against empty download link
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-25 01:13:05 +03:00
Trial97
3bc40120cd
bump develop to 10
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-25 00:17:34 +03:00
Arcitec
4ce025c0a2 fix: bring back Flathub manifest improvements to the repo
- Brings back all manifest improvements, such as the incredibly important Mojang Java Downloader (without it, the Flatpak doesn't work), and important fixes for Wayland.

Signed-off-by: Arcitec <38923130+Arcitec@users.noreply.github.com>
2024-10-24 17:36:45 +02:00
TheKodeToad
d8702e1357
Use Launcher log level in AutoInstallJava
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-24 12:46:01 +01:00
TheKodeToad
9fe81ff564
Merge pull request #2975 from Ryex/fix/version-page-load-speed
fix: don't hang the ui for a full version load
2024-10-24 11:24:35 +01:00
TheKodeToad
5eff9b0934
Fix column crashes
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-24 01:23:11 +01:00
TheKodeToad
8e586c883c
Fix Modrinth update crashing
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-24 01:16:06 +01:00
Rachel Powers
7bd04ae928
fix don't hang the ui for a full version load
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
2024-10-23 16:23:10 -07:00
TheKodeToad
4a7d624e9b
Merge pull request #2972 from TheKodeToad/fix-clean-env
Fix launching Minecraft in portable Linux
2024-10-23 22:30:42 +01:00
TheKodeToad
92a906e0d4
Move everything in CheckUpdateTask::Update
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-23 14:58:52 +01:00
TheKodeToad
24cecf7b63
Fix compile
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-23 14:42:54 +01:00
TheKodeToad
89a327d363
Merge remote-tracking branch 'upstream/develop' into resource-meta
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-23 14:19:07 +01:00
TheKodeToad
e7cd8fe078
Fix CleanEnviroment()'s usage of stripVariableEntries
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-23 13:44:39 +01:00
Kaiyang Wu
3d82bc37af
javautils: add a filter for detecting Java paths on AOSC OS
Signed-off-by: Kaiyang Wu <origincode@aosc.io>
2024-10-22 11:58:00 -07:00
TheKodeToad
f13e875f0d
Try to fix ModrinthCheckUpdate (not working)
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-22 18:46:10 +01:00
Alexandru Ionut Tripon
79439a0217
Merge pull request #2958 from Trial97/crash_QsaveFile
skip parsing open QSaveFile temprary files as resources
2024-10-22 17:38:32 +03:00
renovate[bot]
210d0d8aa2
chore(deps): update actions/cache action to v4.1.2 2024-10-22 13:48:29 +00:00
Trial97
836aebc0d9
fix small leak
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-22 13:43:16 +03:00
Trial97
73d33f93b3
Replaced QSet with QHash
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-22 09:41:00 +03:00
Trial97
562c301326
skip QSaveFile temprary files
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-22 01:22:31 +03:00
Alexandru Ionut Tripon
a43ebefc97
Merge pull request #2954 from Trial97/skin_warn
do not try to import skin if path is empty
2024-10-22 00:14:16 +03:00
Alexandru Ionut Tripon
69028969f1
Update launcher/ui/dialogs/skins/SkinManageDialog.cpp
Co-authored-by: TheKodeToad <TheKodeToad@proton.me>
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
2024-10-21 22:59:51 +03:00
Alexandru Ionut Tripon
671d5270eb
Merge pull request #2953 from TheKodeToad/portable-fix
Fix removing portable.txt on Linux portable build
2024-10-21 22:44:44 +03:00
Trial97
9a5b773e69
do not try to import skin if path is empty
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-21 19:28:34 +03:00
TheKodeToad
f3f4c44620
Fix removing portable.txt on Linux portable build
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-21 16:30:08 +01:00
TheKodeToad
b049e92697
Merge pull request #2947 from Trial97/fix_initial_setup
do not require java if auto-download is enabled
2024-10-21 15:57:47 +01:00
Tayou
ea703ba26e
Merge pull request #2948 from TheKodeToad/vc-redist-norestart 2024-10-21 16:54:18 +02:00
TheKodeToad
78e24962f9
Fix /norestart
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-21 14:41:48 +01:00
Trial97
fcadbbb739
do not require java if auto-download is enabled
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-21 16:17:48 +03:00
Alexandru Ionut Tripon
a8c3a49cb9
Merge pull request #2938 from Trial97/fix_sparkle
fix sparkle signature
2024-10-21 00:05:57 +03:00
Trial97
0586d38e03
fix sparkle signature
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-20 23:53:23 +03:00
Edgars Cirulis
0beaa94311
ci: uprev qt install action to v4
Signed-off-by: Edgars Cirulis <edgarsciruliss@gmail.com>
2024-10-10 15:26:36 +03:00
Edgars Cirulis
b39098dbc5
ci: fix qt-6.8 workflow
Signed-off-by: Edgars Cirulis <edgarsciruliss@gmail.com>
2024-10-10 15:26:19 +03:00
Edgars Cirulis
5b6d551650
chore: update Qt to 6.8.0
Signed-off-by: Edgars Cirulis <edgarsciruliss@gmail.com>
2024-10-10 03:45:35 +03:00
TheKodeToad
4b66320f63
Move x-prism-launcher to unnamed table for consistency
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-08 22:51:03 +01:00
TheKodeToad
a76f37760c
Fix log spam when opening files from older versions
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-08 22:30:50 +01:00
TheKodeToad
538815e033
Merge remote-tracking branch 'upstream/develop' into resource-meta
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-08 17:16:10 +01:00
TheKodeToad
3fae659b30
Merge remote-tracking branch 'upstream/develop' into resource-meta
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-08 17:15:42 +01:00
QazCetelic
f641f3acda Refactoring
Signed-off-by: QazCetelic <qaz.cetelic@protonmail.com>
2024-09-24 12:32:49 +02:00
QazCetelic
b675406b1a Split set creation into a separate function for readability
Signed-off-by: QazCetelic <qaz.cetelic@protonmail.com>
2024-09-24 10:16:26 +02:00
QazCetelic
a58e81d744 snake_case to camelCase
Signed-off-by: QazCetelic <qaz.cetelic@protonmail.com>
2024-09-24 09:58:47 +02:00
QazCetelic
e35faa5522 Feedback I
Signed-off-by: QazCetelic <qaz.cetelic@protonmail.com>
2024-09-15 09:04:23 +02:00
QazCetelic
7b49fd6508 fixes for ci errors IV
Signed-off-by: QazCetelic <qaz.cetelic@protonmail.com>
2024-09-11 10:07:05 +02:00
QazCetelic
f7f7c4b43e fixes for ci errors III
Signed-off-by: QazCetelic <qaz.cetelic@protonmail.com>
2024-09-11 09:55:27 +02:00
QazCetelic
506ec64273 fixes for ci errors II
Signed-off-by: QazCetelic <qaz.cetelic@protonmail.com>
2024-09-11 09:43:45 +02:00
QazCetelic
0a576a0f67 fixes for ci errors
Signed-off-by: QazCetelic <qaz.cetelic@protonmail.com>
2024-09-11 09:31:16 +02:00
QazCetelic
40c3866f63 clang-tidy formatting
Signed-off-by: QazCetelic <qaz.cetelic@protonmail.com>
2024-09-11 09:22:28 +02:00
QazCetelic
6b0007291b Added search and support for subdirectories to icon picker
Signed-off-by: QazCetelic <qaz.cetelic@protonmail.com>
2024-09-10 22:30:29 +02:00
maskers
6a12c43c78 fix off by one error
Signed-off-by: maskers <97827489+mskrss@users.noreply.github.com>
2024-08-25 16:02:32 +03:00
TheKodeToad
43fc9ba932
Confirm Truncate -> Truncation
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-08-25 13:05:08 +01:00
maskers
762d24bd02 don't mention prism in the middle of the log
Signed-off-by: maskers <97827489+mskrss@users.noreply.github.com>
2024-08-22 21:44:53 +03:00
maskers
a910337e9d
apply suggestion from code review
Co-authored-by: TheKodeToad <TheKodeToad@proton.me>
Signed-off-by: maskers <97827489+maskersss@users.noreply.github.com>
2024-08-22 21:42:05 +03:00
maskers
6d017b5f0b fix formatting
Signed-off-by: maskers <97827489+mskrss@users.noreply.github.com>
2024-08-21 22:39:27 +03:00
maskers
65f8526152
add a Cancel option
Co-authored-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
Signed-off-by: maskers <97827489+maskersss@users.noreply.github.com>
2024-08-21 22:14:11 +03:00
maskers
99bd4a8937
Apply suggestions from code review
Co-authored-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
Signed-off-by: maskers <97827489+maskersss@users.noreply.github.com>
2024-08-21 19:42:35 +03:00
maskers
cf914526bf add a warning about the log being too large
Signed-off-by: maskers <97827489+mskrss@users.noreply.github.com>
2024-08-21 15:59:51 +03:00
maskers
858f6aa9b8 fix formatting
Signed-off-by: maskers <97827489+mskrss@users.noreply.github.com>
2024-08-20 22:16:40 +03:00
maskers
e6f30c0ebe fix formatting
Signed-off-by: maskers <97827489+mskrss@users.noreply.github.com>
2024-08-20 22:14:40 +03:00
maskers
014fc14291 truncate logs for mclo.gs upload to fit 25k line limit
Signed-off-by: maskers <97827489+mskrss@users.noreply.github.com>
2024-08-20 21:53:05 +03:00
TheKodeToad
60bd8104c4
Merge remote-tracking branch 'upstream/develop' into resource-meta
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-04-26 01:53:51 +01:00
TheKodeToad
9ce10231ed
Remove redundant event handler
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-04-26 01:44:43 +01:00
TheKodeToad
803e26a401
Make actions more consistent
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-04-26 01:40:18 +01:00
TheKodeToad
27780cc7ae
Add update UI for all resource types
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-03-06 14:56:04 +00:00
TheKodeToad
82d0f204e2
De-OOP ProviderCapabilities
There was no reason for it to be a class, and imo it created quite a code-smell needing to initialise it everywhere.

Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-01-26 03:06:05 +00:00
TheKodeToad
97ee0a19b5
Refactor updating mechanisms to work with all resources
Summary:
- It compiles
- I need to go to bed

Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-01-26 02:53:57 +00:00
TheKodeToad
2c18d0f1a5
Store current version in packwiz metadata (temporarily using x-prismlauncher-version-number)
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-01-25 15:41:34 +00:00
TheKodeToad
35b5c8097c
Merge branch 'develop' into resource-meta
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-01-23 23:20:40 +00:00
TheKodeToad
f4940e26c6
Merge remote-tracking branch 'upstream/develop' into resource-meta
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2023-11-12 19:02:14 +00:00
TheKodeToad
179abfa03e
Resource provider column
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2023-09-01 22:05:44 +01:00
TheKodeToad
ad16d61208
Resource metadata writing
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2023-09-01 17:42:05 +01:00
TheKodeToad
6aecbfc38f
Fix crashes
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2023-09-01 12:50:20 +01:00
TheKodeToad
ee48766996
More generalistaion for ResourceFolderModels
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2023-09-01 00:27:05 +01:00
TheKodeToad
93876e27f8
Generalise resource metadata
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2023-08-31 17:54:22 +01:00
490 changed files with 6915 additions and 6724 deletions

View file

@ -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 }

View file

@ -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
View 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
View 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
View 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
View 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

View file

@ -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 }}

View file

@ -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 }}

View file

@ -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 }}

View file

@ -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

View file

@ -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

View file

@ -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">

View file

@ -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
View file

@ -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": {

View file

@ -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
View 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.]+)$"
}
}
]
}

View file

@ -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"
]
} }

View file

@ -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

View file

@ -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,

View file

@ -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;
}

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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;
}

View file

@ -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;
}; };

View file

@ -411,3 +411,8 @@ void BaseInstance::updateRuntimeContext()
{ {
// NOOP // NOOP
} }
bool BaseInstance::isLegacy()
{
return traits().contains("legacyLaunch") || traits().contains("alphaLaunch");
}

View file

@ -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);

View file

@ -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/")

View file

@ -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);

View file

@ -18,7 +18,7 @@
class DataMigrationTask : public Task { class DataMigrationTask : public Task {
Q_OBJECT Q_OBJECT
public: public:
explicit DataMigrationTask(QObject* parent, const QString& sourcePath, const QString& targetPath, IPathMatcher::Ptr pathmatcher); explicit DataMigrationTask(const QString& sourcePath, const QString& targetPath, IPathMatcher::Ptr pathmatcher);
~DataMigrationTask() override = default; ~DataMigrationTask() override = default;
protected: protected:

View file

@ -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;

View file

@ -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!";
} }

View file

@ -61,6 +61,6 @@ void InstanceCreationTask::executeTask()
return; return;
} }
} }
if (!m_abort)
emitSucceeded(); emitSucceeded();
} }

View file

@ -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();
} }

View file

@ -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,

View file

@ -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, &currentItem]() {
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();

View file

@ -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));

View file

@ -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();
} }

View file

@ -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");

View file

@ -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;

View file

@ -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}" "$@"

View file

@ -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
View 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

View file

@ -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);

View file

@ -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)

View file

@ -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);

View file

@ -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") {

View file

@ -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 = [&currentSection](QChar lastChar, QChar currentChar) {
if (lastChar.isNull()) if (lastChar.isNull())
return false; return false;
if (lastChar.isDigit() != currentChar.isDigit()) if (lastChar.isDigit() != currentChar.isDigit())

View file

@ -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();

View file

@ -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();
}

View file

@ -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;
}; };

View file

@ -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;

View file

@ -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);

View file

@ -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++;

View file

@ -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;

View file

@ -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 ?

View file

@ -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);

View file

@ -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));
}
} }

View file

@ -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 */

View file

@ -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;
}; };

View file

@ -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();

View file

@ -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:

View file

@ -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:

View file

@ -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));
} }

View file

@ -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());
} }

View file

@ -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;

View file

@ -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();

View file

@ -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;

View file

@ -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:

View file

@ -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;
} }

View file

@ -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) {

View file

@ -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();

View file

@ -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) {

View file

@ -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;

View file

@ -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

View file

@ -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;
} }

View file

@ -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);

View file

@ -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;
} }

View file

@ -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) {

View file

@ -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;

View file

@ -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);

View file

@ -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; }

View file

@ -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"));
} }

View file

@ -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();

View file

@ -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);

View file

@ -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;

View file

@ -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;
} }

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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;
}
}; };

View file

@ -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);
}

View file

@ -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;
}; };

View file

@ -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

View file

@ -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;

View file

@ -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;
}

View file

@ -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++;
}
}
}

View file

@ -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));

View file

@ -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