Compare commits

...

144 Commits

Author SHA1 Message Date
Johannes Frohnmeyer aede030a7f
chore: refactor build scripts and update java-gi
ci/woodpecker/push/woodpecker Pipeline failed Details
ci/woodpecker/push/docs Pipeline was successful Details
2024-03-01 21:25:11 +01:00
Johannes Frohnmeyer 3e70e9e4e1
chore: bump to JDK 21 (gava-gi seems to segfault again, figure that out later)
ci/woodpecker/manual/woodpecker Pipeline was successful Details
ci/woodpecker/manual/docs Pipeline failed Details
2023-12-01 08:01:02 +01:00
Johannes Frohnmeyer 4629d5f68e
fix: migrate components that I forgot when upgrading java-commons 2023-11-09 07:55:15 +01:00
Johannes Frohnmeyer 7dd1d4dee2
fix: prevent lag spike until MDS is completed when entering Mods tab. Still laggy as hell, but this should at least prevent ANRs or WL disconnects
ci/woodpecker/push/woodpecker Pipeline failed Details
ci/woodpecker/push/docs Pipeline failed Details
2023-11-08 09:20:08 +01:00
Johannes Frohnmeyer f4ed8a4bcb
chore: bump commons and update for new modules 2023-11-08 09:15:59 +01:00
Johannes Frohnmeyer af84674c26
chore: bump subrepo
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/push/docs Pipeline was successful Details
2023-10-27 16:41:42 +02:00
Johannes Frohnmeyer 4aa1f24461
fix: apparently the previous fix worked during testing but broke after the merge.
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/push/docs Pipeline was successful Details
Somehow.
2023-10-21 17:50:19 +02:00
Johannes Frohnmeyer 9fd5d870cf
chore: clean up annotations and imports
ci/woodpecker/push/docs Pipeline is pending Details
ci/woodpecker/push/woodpecker Pipeline failed Details
2023-10-21 17:48:23 +02:00
Johannes Frohnmeyer e61df73dd3
Merge branch 'gtk_mod_browsing'
ci/woodpecker/push/docs Pipeline is pending Details
ci/woodpecker/push/woodpecker Pipeline failed Details
# Conflicts:
#	build.gradle.kts
#	launcher-gtk/build.gradle.kts
#	launcher-gtk/src/main/kotlin/io/gitlab/jfronny/inceptum/gtk/GtkMain.kt
#	launcher-gtk/src/main/kotlin/io/gitlab/jfronny/inceptum/gtk/control/assistant/KAssistant.kt
#	launcher-gtk/src/main/kotlin/io/gitlab/jfronny/inceptum/gtk/util/UIExt.kt
#	launcher-gtk/src/main/kotlin/io/gitlab/jfronny/inceptum/gtk/window/create/NewInstanceWindow.kt
#	launcher-imgui/src/main/java/io/gitlab/jfronny/inceptum/imgui/window/AddModWindow.java
2023-10-21 17:41:20 +02:00
Johannes Frohnmeyer c94c8b59af
fix: prevent segfault by not exporting account menu.
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/push/docs Pipeline failed Details
Why does this work? No idea. But it seems to fix the crash.
2023-10-21 17:38:55 +02:00
Johannes Frohnmeyer 475717b6b4
chore: use setVisible instead of show 2023-10-21 16:49:49 +02:00
Johannes Frohnmeyer 5cc650921b
fix: prevent instance not exiting setup stage when redownload cancelled 2023-10-21 15:50:35 +02:00
Johannes Frohnmeyer 5b79987dcf
chore: move logging in kotlin code to Log object 2023-10-21 13:35:02 +02:00
Johannes Frohnmeyer 502026bb22
fix: add quickAction suffix 2023-10-21 13:35:02 +02:00
Johannes Frohnmeyer 3746e30ec7
fix: wrap Entry.setText to prevent segfault for empty strings 2023-10-21 13:35:01 +02:00
Johannes Frohnmeyer ad033711f9
chore: basic mod browsing (untested due to GTK4 issues) 2023-10-21 13:35:01 +02:00
Johannes Frohnmeyer f1f2e95dd2
chore: bump javagi 2023-10-21 13:34:57 +02:00
Johannes Frohnmeyer 9e1c20737d
style: don't explicitly depend on gtk transitive dependencies
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/push/docs Pipeline was successful Details
2023-10-16 16:14:46 +02:00
Johannes Frohnmeyer 04d8121ca2
fix: update java-gi and, in doing so, fix segfault 2023-10-16 16:00:57 +02:00
Johannes Frohnmeyer f4157bae09
fix: correctly assign permissions
ci/woodpecker/push/woodpecker Pipeline failed Details
ci/woodpecker/push/docs Pipeline was successful Details
2023-10-06 08:52:00 +02:00
Johannes Frohnmeyer c7eabc37d3
fix: handle run directory permissions correctly (inspired by signald)
ci/woodpecker/push/woodpecker Pipeline failed Details
ci/woodpecker/push/docs Pipeline failed Details
2023-10-06 08:33:53 +02:00
Johannes Frohnmeyer 0bd675fc7c
fix: prevent endlessly recursive serialization due to misinterpretation of checked method as getter
ci/woodpecker/push/woodpecker Pipeline failed Details
ci/woodpecker/push/docs Pipeline was successful Details
2023-10-03 20:16:00 +02:00
Johannes Frohnmeyer bcd4e34f7a
fix: defensively use deleteIfExists to prevent races in Instance 2023-10-03 20:15:11 +02:00
Johannes Frohnmeyer 7a7d009e29
fix: use proper dir in ModrinthImporter 2023-10-03 20:14:41 +02:00
Johannes Frohnmeyer e14294fdd6
chore: perform more actions in the builder stage for simpleDialog
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/push/docs Pipeline was successful Details
2023-08-19 16:20:17 +02:00
Johannes Frohnmeyer 98cc37405a
fix: update AddModWindow for ModrinthApi change 2023-08-19 16:19:50 +02:00
Johannes Frohnmeyer b1b82c423a
fix: prevent segfault in simpleDialog
ci/woodpecker/push/woodpecker Pipeline failed Details
ci/woodpecker/push/docs Pipeline was successful Details
2023-08-19 16:13:45 +02:00
Johannes Frohnmeyer 32a01547c0
build: get rid of toolchains
ci/woodpecker/push/woodpecker Pipeline failed Details
ci/woodpecker/push/docs Pipeline was successful Details
2023-08-19 16:05:07 +02:00
Johannes Frohnmeyer e19d7cfb3b
chore: optimize imports
ci/woodpecker/push/woodpecker Pipeline failed Details
ci/woodpecker/push/docs Pipeline was successful Details
2023-08-19 15:54:27 +02:00
Johannes Frohnmeyer d7ddde6c4d
chore: move logging in kotlin code to Log object
ci/woodpecker/push/docs Pipeline is pending Details
ci/woodpecker/push/woodpecker Pipeline failed Details
2023-08-19 15:53:09 +02:00
Johannes Frohnmeyer f091cb7a2b
fix: add quickAction suffix
ci/woodpecker/push/docs Pipeline is pending Details
ci/woodpecker/push/woodpecker Pipeline failed Details
2023-08-19 15:52:24 +02:00
Johannes Frohnmeyer 5faa505235
fix: wrap Entry.setText to prevent segfault for empty strings
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline failed Details
2023-08-19 15:32:42 +02:00
Johannes Frohnmeyer 8aa0555a2a
fix: use deleteIfExists to work around potential race condition 2023-08-19 15:32:09 +02:00
Johannes Frohnmeyer a7a135c598
chore: basic mod browsing (untested due to GTK4 issues) 2023-07-24 14:29:06 +02:00
Johannes Frohnmeyer 7f168ded43
chore: bump javagi 2023-07-15 22:15:14 +02:00
Johannes Frohnmeyer cbba1ab70a
support for testing offline via gradle-side reflection hacks and firejail
ci/woodpecker/push/woodpecker Pipeline failed Details
ci/woodpecker/push/docs Pipeline was successful Details
2023-07-15 00:00:27 +02:00
Johannes Frohnmeyer ec67a80389
more cleanup
ci/woodpecker/push/woodpecker Pipeline failed Details
ci/woodpecker/push/docs Pipeline was successful Details
2023-07-14 16:25:52 +02:00
Johannes Frohnmeyer 1759e5c3de
refactor SignalListItemFactory usage with custom wrapper 2023-07-14 16:19:37 +02:00
Johannes Frohnmeyer b8f30247ea
clean up gtk kt 2023-07-14 15:24:32 +02:00
Johannes Frohnmeyer 7805400e43
it's fixed in GTK 2023-07-14 15:10:23 +02:00
Johannes Frohnmeyer 55b9e5986f
use string list with IDs, fixes crash on remove, scaffold mods tab 2023-07-14 14:29:23 +02:00
Johannes Frohnmeyer 7cefa88dcb
fix: instance creation + dialog
ci/woodpecker/push/woodpecker Pipeline failed Details
ci/woodpecker/push/docs Pipeline was successful Details
2023-06-30 23:39:05 +02:00
Johannes Frohnmeyer 8f46e4887f
Group 2023-06-30 23:22:20 +02:00
Johannes Frohnmeyer 1be0d68a56
Implement new instance window, bump java and GTK 2023-06-30 14:02:31 +02:00
Johannes Frohnmeyer e9f8af5617
Disable irrelevant check 2023-06-30 10:22:32 +02:00
Johannes Frohnmeyer 9d287c6521 I forgor
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/push/docs Pipeline was successful Details
2023-06-16 09:40:03 +02:00
Johannes Frohnmeyer 7c4461a275
Check if exists
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/push/docs Pipeline was successful Details
2023-05-06 10:12:30 +02:00
Johannes Frohnmeyer d8bd25b438
Centralize mv logic
ci/woodpecker/push/woodpecker Pipeline failed Details
ci/woodpecker/push/docs Pipeline was successful Details
2023-05-06 10:06:42 +02:00
Johannes Frohnmeyer 218e12714d
InstanceMeta.checkArguments to centralize null checking + remove mutability from Arguments and kotlin lambda indy
ci/woodpecker/push/docs Pipeline is pending Details
ci/woodpecker/push/woodpecker Pipeline failed Details
2023-05-06 09:54:40 +02:00
Johannes Frohnmeyer 3d73879bed
Use nio because that somehow makes things work
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/push/docs Pipeline was successful Details
2023-05-05 23:53:31 +02:00
Johannes Frohnmeyer d1c6b746f0
No install 2023-05-05 23:38:44 +02:00
Johannes Frohnmeyer 71e784fdff
Aptless
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/push/docs Pipeline was successful Details
2023-05-05 23:27:39 +02:00
Johannes Frohnmeyer 1db7bcde18
Does this works?
ci/woodpecker/push/woodpecker Pipeline failed Details
ci/woodpecker/push/docs Pipeline was successful Details
2023-05-05 23:19:29 +02:00
Johannes Frohnmeyer 8f056468a7
Workaround
ci/woodpecker/push/woodpecker Pipeline failed Details
ci/woodpecker/push/docs Pipeline was successful Details
2023-05-05 23:06:58 +02:00
Johannes Frohnmeyer 25874c40f7
Set -xe + jammy
ci/woodpecker/push/woodpecker Pipeline failed Details
ci/woodpecker/push/docs Pipeline was successful Details
2023-05-05 22:41:23 +02:00
Johannes Frohnmeyer 3aaf7e3652
Try moving build_platform_jars gradle invocation to shell script
ci/woodpecker/push/docs Pipeline is pending Details
ci/woodpecker/push/woodpecker Pipeline failed Details
2023-05-05 22:36:10 +02:00
Johannes Frohnmeyer 17e89ba829
GTK: JPMS + optimize imports
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline failed Details
2023-05-05 21:37:16 +02:00
Johannes Frohnmeyer a89f51aa5d
Initial port of launcher-gtk to kotlin. With this, manifold is completely gone from the codebase.
Might have introduced some new bugs + crashes, though.
2023-05-05 21:27:00 +02:00
Johannes Frohnmeyer 441a9b26b2
Remove manifold and add JPMS in every project except wrapper, launchwrapper and launcher-gtk
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline failed Details
2023-05-05 18:49:56 +02:00
Johannes Frohnmeyer 50e9db1fcc
Use jpackage for windows/linux distribution
ci/woodpecker/push/woodpecker Pipeline failed Details
ci/woodpecker/push/docs Pipeline was successful Details
2023-05-05 15:27:25 +02:00
Johannes Frohnmeyer 6151f0e71e
Optimize imports
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/push/docs Pipeline was successful Details
2023-05-04 11:31:54 +02:00
Johannes Frohnmeyer 8d45fdff84
Bump other dependencies 2023-05-04 11:25:37 +02:00
Johannes Frohnmeyer 2cb852c5cb
Bump java-gi 2023-05-04 11:14:34 +02:00
Johannes Frohnmeyer fc256a1376
Support release candidates
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-03-12 22:07:07 +01:00
Johannes Frohnmeyer 1353de777e
Die URL
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-01-29 23:14:16 +01:00
Johannes Frohnmeyer 3f273bf6f8
Update copyright
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-01-29 18:39:17 +01:00
Johannes Frohnmeyer 7a80132ab0
GTK: Clean up leftovers
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline failed Details
2023-01-29 18:37:31 +01:00
Johannes Frohnmeyer c027885364
GTK: Start working on instance creation flow
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline failed Details
2023-01-29 18:34:33 +01:00
Johannes Frohnmeyer 4967410f51
GTK: Showcase configuration using broadwayd
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-01-29 17:19:02 +01:00
Johannes Frohnmeyer 14a23fdfed
GTK: Implement account management
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-01-29 16:33:59 +01:00
Johannes Frohnmeyer 7284193981
GTK: Start working on accounts UI and complete more menu entries
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-01-29 15:45:51 +01:00
Johannes Frohnmeyer eb9601d6cf
GTK: Memory Settings
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-01-29 12:39:46 +01:00
Johannes Frohnmeyer 5c9ce78ebf
GTK: Support search in IRow dropdown
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-01-29 11:16:18 +01:00
Johannes Frohnmeyer fe0c23b97b
GTK: Instance Settings: custom java version
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-01-28 21:05:41 +01:00
Johannes Frohnmeyer 94dbaadaf1
GTK: Some of the cleanest code ever to be written (ignoring all other code)
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-01-28 20:37:57 +01:00
Johannes Frohnmeyer cd3c8a1852
GTK: More work on settings screens
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-01-28 19:25:52 +01:00
Johannes Frohnmeyer 18c4953b36
GTK: More work on InstanceSettingsWindow
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-01-28 15:31:33 +01:00
Johannes Frohnmeyer 0cb3df331a
GTK: Fix instance list auto-updating
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-01-28 14:53:16 +01:00
Johannes Frohnmeyer 08bb13f994
GTK: Fix launching
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-01-28 14:15:17 +01:00
Johannes Frohnmeyer a93f2c8411
GTK: Implement instance exporting, break launching
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-01-28 13:26:50 +01:00
Johannes Frohnmeyer e15ef8c485
Use a "stable" java-gi version (0.3) and mention it in the README
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-01-28 10:02:01 +01:00
Johannes Frohnmeyer 2ca25a7bee
Some TODOs
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-01-24 18:51:41 +01:00
Johannes Frohnmeyer bdd86c7683
Start working on instance settings
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-01-24 18:25:18 +01:00
Johannes Frohnmeyer 442d462843
GTK: fix list view
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-01-22 19:15:09 +01:00
Johannes Frohnmeyer 37872e6c79
Bump commons + gson-compile. Introduces safe writing (no more half-written config files!)
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-01-22 16:17:43 +01:00
Johannes Frohnmeyer dff05af62f
More gi changes
ci/woodpecker/manual/docs Pipeline failed Details
ci/woodpecker/manual/woodpecker Pipeline was successful Details
2023-01-20 15:47:49 +01:00
Johannes Frohnmeyer 05a18765c9
Clean
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline failed Details
2023-01-15 11:23:02 +01:00
Johannes Frohnmeyer 36f462597a
Fix menu not interactable
ci/woodpecker/push/docs Pipeline failed Details
ci/woodpecker/push/woodpecker Pipeline failed Details
2023-01-15 11:19:57 +01:00
Johannes Frohnmeyer c52f1f3350
Tweak main window
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-01-13 19:34:29 +01:00
Johannes Frohnmeyer d4a016771f
It werks
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline failed Details
2023-01-13 14:20:16 +01:00
Johannes Frohnmeyer 8af7c214d2
Make it compile against java-gi changes (and some patches) 2023-01-10 21:20:01 +01:00
Johannes Frohnmeyer 71faae3b9a
Update java-gi unibranch
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline failed Details
2022-12-19 14:07:37 +01:00
Johannes Frohnmeyer 379f02c41c
Try to fix
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline failed Details
2022-12-18 20:46:46 +01:00
Johannes Frohnmeyer be8252ce58
Initial work on porting to java-gi, currently broken
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
2022-12-17 21:53:21 +01:00
Johannes Frohnmeyer fb56c9e922
Fix (don't use extension methods for now)
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
2022-11-24 21:11:07 +01:00
Johannes Frohnmeyer 18810b255b
Refactor to use manifold and records for models
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
2022-11-18 21:58:40 +01:00
Johannes Frohnmeyer 3370495207
Update scripts
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline failed Details
2022-11-13 12:41:09 +01:00
Johannes Frohnmeyer b0326536c7
Move tests for LambdaMetafactory to source tree
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
2022-11-13 10:29:26 +01:00
Johannes Frohnmeyer eb6c2538b5
Revert "Revert "Revert "One last release on gitlab"""
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
2022-11-04 19:10:27 +01:00
Johannes Frohnmeyer 3563d7449b
Revert "Revert "One last release on gitlab""
This reverts commit 7dee85292c.
2022-11-04 18:49:50 +01:00
Johannes Frohnmeyer 7dee85292c
Revert "One last release on gitlab"
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
This reverts commit d2b041ef59.
2022-11-04 18:16:31 +01:00
Johannes Frohnmeyer d2b041ef59
One last release on gitlab 2022-11-04 18:09:40 +01:00
Johannes Frohnmeyer 8eee28f353
Slight launchwrapper formatting
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/tag/docs Pipeline was successful Details
ci/woodpecker/tag/woodpecker Pipeline was successful Details
2022-11-04 17:09:07 +01:00
Johannes Frohnmeyer e81c5765df
Get rid of (unused) grgit
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline failed Details
2022-11-04 17:05:45 +01:00
Johannes Frohnmeyer 5dd3c4b0a2
Reformatting, instance ordering, "Inceptum" version name
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline failed Details
2022-11-04 17:02:24 +01:00
Johannes Frohnmeyer 45fe59df83
Confuse confusing argument to varargs method
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
2022-11-04 16:16:06 +01:00
Johannes Frohnmeyer 3de1bd0218
Attempt to fix launchwrapper
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
2022-11-04 16:04:26 +01:00
Johannes Frohnmeyer 22c27bb9ec
Some preliminary fixes for launching and experiments in launchwrapper
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
2022-11-04 14:40:32 +01:00
Johannes Frohnmeyer 2c30e2e76a
Show error for wrong wrapper or java version before trying to update
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
2022-11-04 13:00:37 +01:00
Johannes Frohnmeyer 0b65231175
Fix maven downloads with -SNAPSHOT
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
2022-11-04 12:41:41 +01:00
Johannes Frohnmeyer 11a40975d9
Attempt to set up updater for gitea
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
2022-11-03 17:07:55 +01:00
Johannes Frohnmeyer 182d65a442
Attempt to move some things to the CI server config
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
2022-11-03 16:42:45 +01:00
Johannes Frohnmeyer 2b1a8b86b2
Install proper 7zip
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline failed Details
2022-11-03 16:29:29 +01:00
Johannes Frohnmeyer ee72dc9a9c
Package proper wrapper
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline failed Details
2022-11-03 15:54:09 +01:00
Johannes Frohnmeyer 705f4182aa
Adjust paths
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline failed Details
2022-11-03 15:39:44 +01:00
Johannes Frohnmeyer 04b59ebac6
Include proper clone
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline failed Details
2022-11-03 15:05:19 +01:00
Johannes Frohnmeyer 958fb90822
Use proper maven
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline failed Details
2022-11-03 14:38:05 +01:00
Johannes Frohnmeyer 9bf6446082
No quotes
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline failed Details
2022-11-03 00:10:49 +01:00
Johannes Frohnmeyer ab5304dcd9
Switch to jammy
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline failed Details
2022-11-03 00:06:02 +01:00
Johannes Frohnmeyer fcf729e65a
Escape move
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline failed Details
2022-11-02 23:52:03 +01:00
Johannes Frohnmeyer 94c9e46f8b
What is going on here?
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline failed Details
2022-11-02 23:47:22 +01:00
Johannes Frohnmeyer 109402731d
Update wiki url
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline failed Details
2022-11-02 23:43:22 +01:00
Johannes Frohnmeyer 915c8cea6c
Use http url for packaging/arch-linux
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline failed Details
2022-11-02 23:04:29 +01:00
Johannes Frohnmeyer 19a5aa3d9f
Link proper yml
ci/woodpecker/push/docs Pipeline failed Details
ci/woodpecker/push/woodpecker Pipeline failed Details
2022-11-02 22:59:25 +01:00
Johannes Frohnmeyer 3eace3f6b8
Update commons
ci/woodpecker/push/woodpecker Pipeline failed Details
2022-11-02 22:38:41 +01:00
Johannes Frohnmeyer 7ce6a764ec
Switch to gson-compile 2022-11-02 00:38:04 +01:00
Johannes Frohnmeyer 841fa6b3d4
Add include at bottom 2022-10-28 17:53:57 +02:00
Johannes Frohnmeyer b7ca994fa8
Remove include to test things
ci/woodpecker/push/woodpecker Pipeline failed Details
2022-10-28 17:52:56 +02:00
Johannes Frohnmeyer 36063054c0
Revert "Move CI to dir"
This reverts commit 9937b7c258.
2022-10-28 17:28:54 +02:00
Johannes Frohnmeyer 9937b7c258
Move CI to dir 2022-10-28 17:25:53 +02:00
Johannes Frohnmeyer a31a1abc6c
Attempt to implement CI 2022-10-28 16:36:18 +02:00
Johannes Frohnmeyer 0ad54e978e
Fix 2022-10-28 14:56:44 +02:00
Johannes Frohnmeyer ff7dddbd35
Refactor mods once again 2022-10-27 20:54:55 +02:00
Johannes Frohnmeyer 7e42e6b02f
Move more things 2022-10-19 21:44:33 +02:00
Johannes Frohnmeyer 5c6266634a
Introduce cleaner "Instance" abstraction 2022-10-19 21:26:06 +02:00
Johannes Frohnmeyer 390f6bc59b
Model views after boxes again 2022-10-19 20:28:49 +02:00
Johannes Frohnmeyer ef986fab05
Clean up build.gradle.kts w/ jfmods scripts 2022-10-13 21:06:56 +02:00
Johannes Frohnmeyer ab81759d4d
Utilize jfmods scripts 2022-10-13 19:37:35 +02:00
Johannes Frohnmeyer b87889f5b4
Enhance list entries 2022-09-30 18:07:18 +02:00
Johannes Frohnmeyer 62dd7da114
Split windows from main class 2022-09-29 18:19:43 +02:00
Johannes Frohnmeyer e3b3e50bd2
Finish up header bar (at least for now) 2022-09-29 17:53:49 +02:00
Johannes Frohnmeyer b0a6146d49
"Fix" input dialog 2022-09-29 16:28:05 +02:00
Johannes Frohnmeyer eb3fb83673
Start working on GTK port (far from finished) 2022-09-26 19:09:02 +02:00
Johannes Frohnmeyer 92109d353e
Implement importing (untested) 2022-09-19 21:26:23 +02:00
278 changed files with 7898 additions and 3597 deletions

View File

@ -1,94 +0,0 @@
variables:
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
default:
image: gradle:jdk18
before_script:
- export GRADLE_USER_HOME=`pwd`/.gradle
- export TIMESTAMP=$(date -d "$CI_PIPELINE_CREATED_AT" +%s)
build_test:
stage: build
script:
- gradle --build-cache :exportMetadata -Ppublic -Ptimestamp=$TIMESTAMP
artifacts:
paths:
- version.json
expire_in: 2 days
build_platform_jars:
stage: build
script:
- gradle --build-cache :launcher-dist:build -Pflavor=fat -Ppublic -Ptimestamp=$TIMESTAMP
- gradle --build-cache :launcher-dist:build -Pflavor=windows -Ppublic -Ptimestamp=$TIMESTAMP
- gradle --build-cache :launcher-dist:build -Pflavor=linux -Ppublic -Ptimestamp=$TIMESTAMP
- gradle --build-cache :launcher-dist:build -Pflavor=macos -Ppublic -Ptimestamp=$TIMESTAMP
- for f in launcher-dist/build/libs/Inceptum-*-*-*.jar; do mv "$f" "Inceptum-${f##*-}";done
- mv Inceptum-fat.jar Inceptum.jar
artifacts:
paths:
- launcher-dist/build/libs
- Inceptum-*.jar
- Inceptum.jar
expire_in: 2 days
build_wrapper:
stage: build
script:
- gradle --build-cache :wrapper:build -Pflavor=windows -Ppublic -Ptimestamp=$TIMESTAMP
- cp wrapper/build/libs/*.exe wrapper.exe
- cp wrapper/build/libs/*-all.jar wrapper.jar
artifacts:
paths:
- wrapper/build/libs
- wrapper.jar
- wrapper.exe
expire_in: 2 days
publish_debug:
stage: deploy
script:
- gradle --build-cache build publish -Pflavor=maven -Ppublic -Ptimestamp=$TIMESTAMP
- gradle --build-cache build :launcher-dist:publish -Pflavor=fat -Pdist.platformOnly -Ppublic -Ptimestamp=$TIMESTAMP
- gradle --build-cache build :launcher-dist:publish -Pflavor=windows -Pdist.platformOnly -Ppublic -Ptimestamp=$TIMESTAMP
- gradle --build-cache build :launcher-dist:publish -Pflavor=linux -Pdist.platformOnly -Ppublic -Ptimestamp=$TIMESTAMP
- gradle --build-cache build :launcher-dist:publish -Pflavor=macos -Pdist.platformOnly -Ppublic -Ptimestamp=$TIMESTAMP
only:
- master
publish_release:
rules:
- if: $CI_COMMIT_TAG && '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^master/'
stage: deploy
script:
- gradle --build-cache build publish -Pflavor=maven -Ppublic -Prelease
- gradle --build-cache build :launcher-dist:publish -Pflavor=fat -Pdist.platformOnly -Ppublic -Prelease
- gradle --build-cache build :launcher-dist:publish -Pflavor=windows -Pdist.platformOnly -Ppublic -Prelease
- gradle --build-cache build :launcher-dist:publish -Pflavor=linux -Pdist.platformOnly -Ppublic -Prelease
- gradle --build-cache build :launcher-dist:publish -Pflavor=macos -Pdist.platformOnly -Ppublic -Prelease
portable:
stage: deploy
image: archlinux:latest
script:
- pacman -Sy p7zip curl jq --noconfirm
- mkdir -p portable/jvm
- cp wrapper.jar portable/
- curl -L "https://github.com/pal1000/mesa-dist-win/releases/download/21.2.5/mesa3d-21.2.5-release-msvc.7z" --output mesa.7z
- 7z e mesa.7z -oportable/run/natives/forceload x64/dxil.dll x64/libglapi.dll x64/opengl32.dll
- curl -L "https://api.adoptium.net/v3/binary/latest/18/ga/windows/x64/jre/hotspot/normal/eclipse?project=jdk" --output jvm.zip
- 7z x jvm.zip -oportable/
- mv portable/jdk*/* portable/jvm/
- rm -r portable/jdk*
- cp packaging/windows/launch.bat portable/
- cd portable
- 7z a ../portable.7z * -mx9
artifacts:
paths:
- portable.7z
expire_in: 2 days
only:
- master
include:
- remote: 'https://jfmods.gitlab.io/scripts/docs.yml'

2
.gitmodules vendored
View File

@ -1,3 +1,3 @@
[submodule "packaging/arch-linux"]
path = packaging/arch-linux
url = ssh://aur@aur.archlinux.org/inceptum-git.git
url = https://aur.archlinux.org/inceptum-git.git

84
.woodpecker.yml Normal file
View File

@ -0,0 +1,84 @@
#link https://pages.frohnmeyer-wds.de/scripts/docs.yml
#include https://pages.frohnmeyer-wds.de/scripts/clone.yml
pipeline:
export_metadata:
image: gradle:jdk21-jammy
pull: true
commands:
- mkdir public
- gradle --build-cache :exportMetadata -Ppublic -Ptimestamp=${CI_PIPELINE_STARTED}
- mv version.json public/
build_platform_jars:
image: git.frohnmeyer-wds.de/johannes/ci-wine
pull: true
commands:
- ./platform_jars.sh
build_wrapper:
image: gradle:jdk21-jammy
commands:
- gradle --build-cache :wrapper:build -Pflavor=windows -Ppublic -Ptimestamp=${CI_PIPELINE_STARTED}
- cp wrapper/build/libs/*.exe public/wrapper.exe
- cp wrapper/build/libs/*-all.jar public/wrapper.jar
publish_debug:
image: gradle:jdk21-jammy
commands:
- gradle --build-cache build publish -Pflavor=maven -Ppublic -Ptimestamp=${CI_PIPELINE_STARTED}
secrets: [ maven_token, maven_name ]
when:
- branch: master
publish_release:
image: gradle:jdk21-jammy
commands:
- gradle --build-cache build publish -Pflavor=maven -Ppublic -Prelease
secrets: [ maven_token, maven_name ]
when:
- event: tag
branch: master
portable:
image: git.frohnmeyer-wds.de/johannes/ci-wine
commands:
- mkdir -p portable/jvm
- cp public/wrapper.jar portable/
- curl -L "https://github.com/pal1000/mesa-dist-win/releases/download/21.2.5/mesa3d-21.2.5-release-msvc.7z" --output mesa.7z
- 7z e mesa.7z -oportable/run/natives/forceload x64/dxil.dll x64/libglapi.dll x64/opengl32.dll
- curl -L "https://api.adoptium.net/v3/binary/latest/19/ga/windows/x64/jre/hotspot/normal/eclipse?project=jdk" --output jvm.zip
- 7z x jvm.zip -oportable/
- mv portable/jdk*/* portable/jvm/
- rm -r portable/jdk*
- cp packaging/windows/launch.bat portable/
- cd portable
- 7z a ../public/portable.7z * -mx9
when:
- branch: master
publish:
image: woodpeckerci/plugin-s3
settings:
bucket: pages
region: nebula
path_style: true
endpoint: https://s3.frohnmeyer-wds.de
access_key: pages
secret_key:
from_secret: pages_secret
source: public/**/*
target: /${CI_REPO}/artifacts
strip_prefix: public/
when:
- branch: master
publishRelease:
image: woodpeckerci/plugin-s3
settings:
bucket: pages
region: nebula
path_style: true
endpoint: https://s3.frohnmeyer-wds.de
access_key: pages
secret_key:
from_secret: pages_secret
source: public/**/*
target: /${CI_REPO}/stable
strip_prefix: public/
when:
- event: tag
branch: master

View File

@ -1,5 +1,5 @@
Inceptum - A FOSS Launcher for Minecraft written in Java
Copyright (C) 2021 JFronny
Copyright (C) 2021-2023 JFronny
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

View File

@ -2,7 +2,7 @@
An advanced FOSS Launcher for Minecraft written in Java
For documentation on how to use or install Inceptum, please head to the [wiki](https://jfmods.gitlab.io/inceptum)
For documentation on how to use or install Inceptum, please head to the [wiki](https://pages.frohnmeyer-wds.de/JfMods/Inceptum/)
## Licenses
@ -14,7 +14,9 @@ Inceptum utilizes code/libraries/assets from:
- [imgui-java](https://github.com/SpaiR/imgui-java): The library used for UI
- [Dear ImGui](https://github.com/ocornut/imgui): Included and wrapped in imgui-java, UI library
- [LWJGL](https://github.com/LWJGL/lwjgl3): Used as a backend for imgui-java
- [java-gi](https://github.com/jwharm/java-gi): The library used for the new UI
- [GTK4](https://www.gtk.org/) (and dependencies): Wrapped in java-gi-generated code, the core UI library
- [gson](https://github.com/google/gson): Used for interacting with various APIs and configs
- [Ubuntu](https://design.ubuntu.com/font/): Used with nerd font symbols as the font
- [meteor-client](https://github.com/MeteorDevelopment/meteor-client): A simple HTTP client
- Several of [my other projects](https://gitlab.com/jfmods)
- Several of [my other projects](https://git.frohnmeyer-wds.de/explore/repos)

View File

@ -10,7 +10,7 @@ description = "Inceptum Docs"
build-dir = "public"
[output.html]
git-repository-url = "https://gitlab.com/jfmods/Inceptum"
git-repository-icon = "fa-gitlab"
edit-url-template = "https://gitlab.com/jfmods/Inceptum/edit/master/{path}"
site-url = "https://jfmods.gitlab.io/inceptum"
git-repository-url = "https://git.frohnmeyer-wds.de/JfMods/Inceptum"
git-repository-icon = "fa-git-alt"
edit-url-template = "https://git.frohnmeyer-wds.de/JfMods/Inceptum/_edit/master/{path}"
site-url = "https://pages.frohnmeyer-wds.de/JfMods/Inceptum/"

View File

@ -1,49 +1,31 @@
import org.ajoberstar.grgit.Grgit
import org.gradle.internal.os.OperatingSystem
import io.gitlab.jfronny.scripts.*
plugins {
id("org.ajoberstar.grgit") version "5.0.0" apply false
id("jf.autoversion")
}
var currentVer = "0.0.0+nogit"
if (File(".git").exists()) {
val grgit: Grgit = Grgit.open(mapOf("dir" to rootProject.projectDir.toString()))
currentVer = "0.0.0+notag"
val tagList = grgit.tag.list()
tagList.sortWith { left, right -> right.commit.dateTime.compareTo(left.commit.dateTime) }
if (tagList.isNotEmpty()) {
currentVer = tagList[0].name
}
}
val timestamp: Long = if (project.hasProperty("timestamp")) "${project.property("timestamp")}".toLong()
else (System.currentTimeMillis() / 1000L)
allprojects {
version = currentVer + if (project.hasProperty("release")) "" else "-$timestamp"
version = rootProject.version
group = "io.gitlab.jfronny.inceptum"
}
println("Using Inceptum Build Script $version")
// common
val jfCommonsVersion by extra(libs.versions.jf.commons.get())
val gsonCompileVersion by extra(libs.versions.gson.compile.get())
val jbAnnotationsVersion by extra(libs.versions.annotations.get())
// launcher-imgui
val lwjglVersion by extra(libs.versions.lwjgl.get())
val imguiVersion by extra(libs.versions.imgui.get())
// launcher-gtk
val javagiVersion by extra(libs.versions.javagi.get())
val lwjglVersion by extra("3.3.1")
val imguiVersion by extra("1.86.4")
val jfCommonsVersion by extra("2022.9.18+16-50-22")
val jlhttpVersion by extra("2.6")
val flavorProp: String by extra(if (project.hasProperty("flavor")) "${project.property("flavor")}" else "custom")
if (flavorProp != "custom" && flavorProp != "maven" && flavorProp != "fat" && flavorProp != "windows" && flavorProp != "linux" && flavorProp != "macos")
throw IllegalStateException("Unsupported flavor: $flavorProp")
val flavor: String by extra(
if (flavorProp != "custom") flavorProp else when (OperatingSystem.current()) {
OperatingSystem.WINDOWS -> "windows"
OperatingSystem.LINUX -> "linux"
OperatingSystem.MAC_OS -> "macos"
else -> throw IllegalStateException("Unsupported OS: ${OperatingSystem.current()}")
}
)
val flavorProp: String by extra(prop("flavor", "custom"))
if (!setOf("custom", "maven", "fat", "windows", "linux", "macos").contains(flavorProp)) throw IllegalStateException("Unsupported flavor: $flavorProp")
val flavor: String by extra(if (flavorProp != "custom") flavorProp else OS.TYPE.codename)
val isPublic by extra(project.hasProperty("public"))
val isRelease by extra(project.hasProperty("release"))
val buildTime by extra(System.currentTimeMillis())
val wrapperVersion by extra(1)
tasks.register("exportMetadata") {
@ -53,12 +35,13 @@ tasks.register("exportMetadata") {
{
"wrapperVersion": $wrapperVersion,
"version": "$version",
"buildTime": $buildTime,
"isPublic": $isPublic,
"isRelease": $isRelease,
"jvm": ${project(":common").extra["javaVersion"]},
"repositories": [
"https://repo.maven.apache.org/maven2/",
"https://gitlab.com/api/v4/projects/35745143/packages/maven"
"https://maven.frohnmeyer-wds.de/artifacts/"
],
"natives": {
"windows": [

View File

@ -4,9 +4,12 @@ plugins {
repositories {
gradlePluginPortal()
maven("https://maven.frohnmeyer-wds.de/artifacts")
}
dependencies {
implementation("gradle.plugin.com.github.johnrengelman:shadow:7.1.2")
implementation("de.undercouch:gradle-download-task:5.1.2")
implementation(libs.plugin.shadow)
implementation(libs.plugin.download)
implementation(libs.plugin.jf.convention)
implementation(libs.plugin.jlink)
}

2
buildSrc/java-offline Executable file
View File

@ -0,0 +1,2 @@
#!/bin/bash
firejail --net=none "$G_ORIGINAL_EXECUTABLE" "$@"

View File

@ -1 +1,8 @@
rootProject.name="inceptum-conventions"
rootProject.name="inceptum-conventions"
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}

View File

@ -1,11 +1,11 @@
import java.io.FileOutputStream
import de.undercouch.gradle.tasks.download.Download
import java.io.FileOutputStream
plugins {
application
id("inceptum.java-conventions")
id("com.github.johnrengelman.shadow")
id("de.undercouch.download")
id("inceptum.java")
com.github.johnrengelman.shadow
de.undercouch.download
}
abstract class FileOutput : DefaultTask() {
@ -18,14 +18,14 @@ val bootstrapArch = "i686"
val downloadBootstrap by tasks.registering(Download::class) {
src("https://maven.fabricmc.net/net/fabricmc/fabric-installer-native-bootstrap/windows-${bootstrapArch}/${bootstrapVersion}/windows-${bootstrapArch}-${bootstrapVersion}.exe")
dest(project.buildDir)
dest(project.layout.buildDirectory)
}
val nativeExe by tasks.registering(FileOutput::class) {
dependsOn(downloadBootstrap)
dependsOn(tasks.shadowJar)
output = file("$buildDir/libs/${project.name}-${project.version}.exe")
output = project.layout.buildDirectory.file("libs/${project.name}-${project.version}.exe").get().asFile
outputs.upToDateWhen { false }
doFirst {
@ -43,4 +43,8 @@ val nativeExe by tasks.registering(FileOutput::class) {
if (rootProject.extra["flavor"] == "windows") {
tasks.build.get().dependsOn(nativeExe)
}
tasks.runShadow {
workingDir = rootProject.projectDir
}

View File

@ -1,6 +1,6 @@
plugins {
application
id("inceptum.java-conventions")
id("inceptum.java")
}
publishing {
@ -9,4 +9,6 @@ publishing {
from(components["java"])
}
}
}
}
tasks.run.get().workingDir = rootProject.projectDir

View File

@ -0,0 +1,14 @@
plugins {
id("inceptum.library")
}
val libs = extensions.getByType<VersionCatalogsExtension>().named("libs")
dependencies {
api(libs.findLibrary("gson-compile-core").orElseThrow())
compileOnly(libs.findLibrary("gson-compile-annotations").orElseThrow())
annotationProcessor(libs.findLibrary("gson-compile-processor").orElseThrow())
}
tasks.withType<JavaCompile> {
options.compilerArgs.add("-AgsonCompileNoReflect")
}

View File

@ -1,35 +0,0 @@
plugins {
`java-library`
`maven-publish`
}
repositories {
mavenCentral()
maven {
setUrl("https://gitlab.com/api/v4/projects/35745143/packages/maven")
}
}
dependencies {
compileOnly("org.jetbrains:annotations:23.0.0")
}
publishing {
repositories {
if (rootProject.extra["isPublic"] == true) {
maven {
url = uri("https://gitlab.com/api/v4/projects/30862253/packages/maven")
name = "gitlab"
credentials(HttpHeaderCredentials::class) {
name = "Job-Token"
value = System.getenv()["CI_JOB_TOKEN"]
}
authentication {
create<HttpHeaderAuthentication>("header")
}
}
}
mavenLocal()
}
}

View File

@ -0,0 +1,52 @@
plugins {
jf.java
`maven-publish`
}
repositories {
mavenCentral()
maven("https://maven.frohnmeyer-wds.de/artifacts")
}
val libs = extensions.getByType<VersionCatalogsExtension>().named("libs")
dependencies {
compileOnly(libs.findLibrary("annotations").orElseThrow())
}
publishing {
repositories {
mavenLocal()
if (rootProject.extra["isPublic"] == true) {
maven("https://maven.frohnmeyer-wds.de/artifacts") {
name = "public"
credentials(PasswordCredentials::class) {
username = System.getenv()["MAVEN_NAME"]
password = System.getenv()["MAVEN_TOKEN"]
}
authentication {
create<BasicAuthentication>("basic")
}
}
}
}
}
afterEvaluate {
if (hasProperty("offline")) {
tasks.withType(JavaExec::class) {
environment("G_ORIGINAL_EXECUTABLE", executable ?: "java")
val originalMetadata = javaLauncher.get().metadata
val field = org.gradle.api.internal.provider.AbstractProperty::class.java.getDeclaredField("value")
field.isAccessible = true
val customLauncher = object: JavaLauncher {
override fun getMetadata(): JavaInstallationMetadata = originalMetadata
override fun getExecutablePath(): RegularFile = rootProject.layout.projectDirectory
.dir("buildSrc")
.file("java-offline")
}
field.set(javaLauncher, org.gradle.api.internal.provider.Providers.of(customLauncher))
}
}
}

View File

@ -1,5 +1,5 @@
plugins {
id("inceptum.java-conventions")
id("inceptum.java")
}
publishing {

1
common/.gitignore vendored
View File

@ -1 +0,0 @@
src/main/java/io/gitlab/jfronny/inceptum/common/BuildMetadata.java

View File

@ -1,25 +1,32 @@
import io.gitlab.jfronny.scripts.*
import javax.lang.model.element.Modifier
plugins {
id("inceptum.library-conventions")
inceptum.library
jf.codegen
inceptum.`gson-compile`
}
dependencies {
api("io.gitlab.jfronny:commons:${rootProject.extra["jfCommonsVersion"]}")
api("io.gitlab.jfronny:commons-gson:${rootProject.extra["jfCommonsVersion"]}")
api(libs.bundles.commons)
}
val javaVersion by extra(project.java.targetCompatibility)
projectDir.resolve("src/main/java/io/gitlab/jfronny/inceptum/common/BuildMetadata.java").writeText(
"""
package io.gitlab.jfronny.inceptum.common;
sourceSets {
main {
generate(project) {
`class`("io.gitlab.jfronny.inceptum.common", "BuildMetadata") {
modifiers(Modifier.PUBLIC)
val modifiers = arrayOf(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
import io.gitlab.jfronny.commons.ComparableVersion;
public class BuildMetadata {
public static final ComparableVersion VERSION = new ComparableVersion("$version");
public static final boolean IS_PUBLIC = ${rootProject.extra["isPublic"]};
public static final boolean IS_RELEASE = ${rootProject.extra["isRelease"]};
public static final int VM_VERSION = $javaVersion;
public static final int WRAPPER_VERSION = ${rootProject.extra["wrapperVersion"]};
field("VERSION", versionS, *modifiers)
field("BUILD_TIME", rootProject.extra["buildTime"] as Long, *modifiers)
field("IS_PUBLIC", rootProject.extra["isPublic"] as Boolean, *modifiers)
field("IS_RELEASE", rootProject.extra["isRelease"] as Boolean, *modifiers)
field("VM_VERSION", javaVersion.majorVersion.toInt(), *modifiers)
field("WRAPPER_VERSION", rootProject.extra["wrapperVersion"] as Int, *modifiers)
}
}
}
""".trimIndent())
}

View File

@ -0,0 +1,34 @@
package io.gitlab.jfronny.inceptum.common;
import io.gitlab.jfronny.gson.stream.JsonReader;
import io.gitlab.jfronny.gson.stream.JsonWriter;
public class GsonPreset {
public static class Config {
public static void configure(JsonReader reader) {
reader.setSerializeSpecialFloatingPointValues(true);
reader.setLenient(true);
}
public static void configure(JsonWriter writer) {
writer.setSerializeNulls(true);
writer.setSerializeSpecialFloatingPointValues(true);
writer.setLenient(true);
writer.setIndent(" ");
writer.setOmitQuotes(true);
}
}
public static class Api {
public static void configure(JsonReader reader) {
reader.setSerializeSpecialFloatingPointValues(true);
reader.setLenient(true);
}
public static void configure(JsonWriter writer) {
writer.setSerializeNulls(false);
writer.setSerializeSpecialFloatingPointValues(true);
writer.setLenient(false);
}
}
}

View File

@ -1,20 +1,30 @@
package io.gitlab.jfronny.inceptum.common;
import io.gitlab.jfronny.commons.serialize.gson.api.GsonHolder;
import io.gitlab.jfronny.gson.stream.*;
import io.gitlab.jfronny.gson.compile.annotations.GComment;
import io.gitlab.jfronny.gson.compile.annotations.GSerializable;
import io.gitlab.jfronny.inceptum.common.model.inceptum.UpdateChannel;
import java.io.*;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@GSerializable(configure = GsonPreset.Config.class, isStatic = true)
public class InceptumConfig {
@GComment("Whether to show snapshots in the version selector for new instances")
public static boolean snapshots = false;
@GComment("Whether to launch the ImGUI in dark mode\nConfigurable in Settings->Dark Theme")
public static boolean darkTheme = false;
@GComment("Whether the GTK UI should default to a list view instead of a grid")
public static boolean listView = false;
@GComment("Whether to require an account to launch the game\nIntended to allow running the game from USB sticks on constrained networks")
public static boolean enforceAccount = true;
@GComment("The currently selected account\nUsed to launch the game")
public static String lastAccount = null;
@GComment("The last name used for an offline session")
public static String offlineAccountLastName = null;
@GComment("The update channel. Either \"CI\" or \"Stable\"\nI personally recommend the CI channel as it gets the latest fixes and features quicker")
public static UpdateChannel channel = UpdateChannel.Stable;
@GComment("The author name to add to packs where the metadata format requires specifying one")
public static String authorName = "Inceptum";
public static void load() throws IOException {
@ -25,81 +35,20 @@ public class InceptumConfig {
Path json = MetaHolder.BASE_PATH.resolve("inceptum.json");
if (Files.exists(gLaunch2)) {
Files.move(gLaunch2, MetaHolder.CONFIG_PATH);
} if (Files.exists(json)) {
} else if (Files.exists(json)) {
Files.move(json, MetaHolder.CONFIG_PATH);
} else {
saveConfig();
}
}
try (Reader reader = Files.newBufferedReader(MetaHolder.CONFIG_PATH);
JsonReader jr = GsonHolder.getGson().newJsonReader(reader)) {
jr.beginObject();
while (jr.peek() != JsonToken.END_OBJECT) {
String name = null;
try {
name = jr.nextName();
switch (name) {
case "snapshots" -> snapshots = jr.nextBoolean();
case "darkTheme" -> darkTheme = jr.nextBoolean();
case "enforceAccount" -> enforceAccount = jr.nextBoolean();
case "lastAccount" -> lastAccount = nullableString(jr);
case "offlineAccountLastName" -> offlineAccountLastName = nullableString(jr);
case "channel" -> {
try {
channel = UpdateChannel.valueOf(jr.nextString());
} catch (IllegalArgumentException e) {
Utils.LOGGER.error("Could not read channel", e);
}
}
case "authorName" -> authorName = jr.nextString();
default -> {
Utils.LOGGER.error("Unexpected entry name: " + name);
jr.skipValue();
}
}
} catch (Throwable t) {
if (name == null) Utils.LOGGER.error("Could not read config entry", t);
else Utils.LOGGER.error("Could not read config entry: " + name, t);
return;
}
}
jr.endObject();
}
GC_InceptumConfig.read(MetaHolder.CONFIG_PATH);
}
public static void saveConfig() {
try (Writer writer = Files.newBufferedWriter(MetaHolder.CONFIG_PATH);
JsonWriter jw = GsonHolder.getGson().newJsonWriter(writer)) {
jw.beginObject()
.comment("Whether to show snapshots in the version selector for new instances")
.name("snapshots").value(snapshots)
.comment("Whether to launch the GUI in dark mode")
.comment("Configurable in Settings->Dark Theme")
.name("darkTheme").value(darkTheme)
.comment("Whether to require an account to launch the game")
.comment("Intended to allow running the game from USB sticks on constrained networks")
.name("enforceAccount").value(enforceAccount)
.comment("The currently selected account")
.comment("Used to launch the game")
.name("lastAccount").value(lastAccount)
.comment("The last name used for an offline session")
.name("offlineAccountLastName").value(offlineAccountLastName)
.comment("The update channel. Either \"CI\" or \"Stable\"")
.comment("I personally recommend the CI channel as it gets the latest fixes and features quicker")
.name("channel").value(channel.toString())
.comment("The author name to add to packs where the metadata format requires specifying one")
.name("authorName").value(authorName)
.endObject();
try {
GC_InceptumConfig.write(MetaHolder.CONFIG_PATH);
} catch (IOException e) {
Utils.LOGGER.error("Could not save config", e);
}
}
private static String nullableString(JsonReader jr) throws IOException {
if (jr.peek() == JsonToken.NULL) {
jr.nextNull();
return null;
}
return jr.nextString();
}
}

View File

@ -1,21 +1,19 @@
package io.gitlab.jfronny.inceptum.common;
import io.gitlab.jfronny.commons.HttpUtils;
import io.gitlab.jfronny.commons.log.Logger;
import io.gitlab.jfronny.commons.log.StdoutLogger;
import io.gitlab.jfronny.commons.serialize.gson.api.GsonHolder;
import io.gitlab.jfronny.commons.http.client.HttpClient;
import io.gitlab.jfronny.commons.logging.Logger;
import io.gitlab.jfronny.commons.logging.StdoutLogger;
import java.io.IOException;
public class InceptumEnvironmentInitializer {
public static void initialize() throws IOException {
Logger.registerFactory(InceptumEnvironmentInitializer::defaultFactory);
HttpUtils.setUserAgent("jfmods/inceptum/" + BuildMetadata.VERSION);
GsonHolder.register();
HttpClient.setUserAgent("jfmods/inceptum/" + BuildMetadata.VERSION);
InceptumConfig.load();
}
public static Logger defaultFactory(String name) {
return new StdoutLogger(name, true, true, true);
return StdoutLogger.fancy(name);
}
}

View File

@ -17,7 +17,7 @@ public class MetaHolder {
case WINDOWS -> getPath(System.getenv("APPDATA"));
case MAC_OS -> getPath(System.getProperty("user.home")).resolve("Library").resolve("Application Support");
case LINUX -> {
String s = System.getenv().get("XDG_CONFIG_HOME");
String s = System.getenv("XDG_CONFIG_HOME");
if (s == null)
yield getPath(System.getProperty("user.home")).resolve(".config");
else

View File

@ -1,14 +1,12 @@
package io.gitlab.jfronny.inceptum.common;
import io.gitlab.jfronny.commons.HashUtils;
import io.gitlab.jfronny.commons.HttpUtils;
import io.gitlab.jfronny.commons.cache.FileBackedOperationResultCache;
import io.gitlab.jfronny.commons.serialize.gson.api.GsonHolder;
import io.gitlab.jfronny.commons.http.client.HttpClient;
import io.gitlab.jfronny.commons.io.HashUtils;
import io.gitlab.jfronny.commons.throwable.ThrowingFunction;
import io.gitlab.jfronny.commons.throwable.ThrowingSupplier;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
@ -17,10 +15,10 @@ import java.nio.file.Path;
import java.util.Map;
public class Net {
private static final FileBackedOperationResultCache OBJECT_CACHE = new FileBackedOperationResultCache(MetaHolder.CACHE_DIR);
private static final ObjectCache OBJECT_CACHE = new ObjectCache(MetaHolder.CACHE_DIR);
public static byte[] downloadData(String url) throws IOException, URISyntaxException {
try (InputStream is = HttpUtils.get(url).sendInputStream()) {
try (InputStream is = HttpClient.get(url).sendInputStream()) {
return is.readAllBytes();
}
}
@ -32,38 +30,30 @@ public class Net {
return buf;
}
public static <T> T downloadObject(String url, Class<T> type) throws IOException {
return downloadObject(url, type, true);
public static <T> T downloadObject(String url, ThrowingFunction<String, T, IOException> func) throws IOException {
return downloadObject(url, func, true);
}
public static <T> T downloadObject(String url, Class<T> type, boolean cache) throws IOException {
return downloadObject(url, () -> HttpUtils.get(url).sendString(), type, cache);
public static <T> T downloadObject(String url, ThrowingFunction<String, T, IOException> func, boolean cache) throws IOException {
return downloadObject(url, () -> HttpClient.get(url).sendString(), func, cache);
}
public static <T> T downloadObject(String url, Type type) throws IOException {
return downloadObject(url, type, true);
public static <T> T downloadObject(String url, ThrowingFunction<String, T, IOException> func, String apiKey) throws IOException {
return downloadObject(url, () -> downloadStringAuthenticated(url, apiKey), func, true);
}
public static <T> T downloadObject(String url, Type type, String apiKey) throws IOException {
return downloadObject(url, () -> HttpUtils.get(url).header("x-api-key", apiKey).sendString(), type, true);
public static <T> T downloadObject(String url, String sha1, ThrowingFunction<String, T, IOException> func) throws IOException {
return downloadObject(url, sha1, func, true);
}
public static <T> T downloadObject(String url, Type type, boolean cache) throws IOException {
return downloadObject(url, () -> HttpUtils.get(url).sendString(), type, cache);
public static <T> T downloadObject(String url, String sha1, ThrowingFunction<String, T, IOException> func, boolean cache) throws IOException {
return downloadObject(url, () -> downloadString(url, sha1), func, cache);
}
public static <T> T downloadObject(String url, String sha1, Class<T> type) throws IOException {
return downloadObject(url, sha1, type, true);
}
public static <T> T downloadObject(String url, String sha1, Class<T> type, boolean cache) throws IOException {
return downloadObject(url, () -> downloadString(url, sha1), type, cache);
}
private static <T> T downloadObject(String url, ThrowingSupplier<String, Exception> sourceString, Type type, boolean cache) throws IOException {
private static <T> T downloadObject(String url, ThrowingSupplier<String, Exception> sourceString, ThrowingFunction<String, T, IOException> func, boolean cache) throws IOException {
try {
ThrowingSupplier<T, Exception> builder = () -> GsonHolder.getGson().fromJson(sourceString.get(), type);
return cache ? OBJECT_CACHE.get(HashUtils.sha1(url.getBytes(StandardCharsets.UTF_8)), builder, type) : builder.get();
ThrowingSupplier<T, Exception> builder = () -> func.apply(sourceString.get());
return cache ? OBJECT_CACHE.get(HashUtils.sha1(url.getBytes(StandardCharsets.UTF_8)), sourceString, func) : builder.get();
} catch (Exception e) {
throw new IOException("Could not download object and no cache exists", e);
}
@ -85,13 +75,17 @@ public class Net {
}
public static String downloadString(String url) throws IOException, URISyntaxException {
return HttpUtils.get(url).sendString();
return HttpClient.get(url).sendString();
}
public static String downloadString(String url, String sha1) throws IOException, URISyntaxException {
return new String(downloadData(url, sha1), StandardCharsets.UTF_8);
}
public static String downloadStringAuthenticated(String url, String apiKey) throws IOException, URISyntaxException {
return HttpClient.get(url).header("x-api-key", apiKey).sendString();
}
public static void downloadFile(String url, Path path) throws IOException, URISyntaxException {
if (!Files.exists(path.getParent())) Files.createDirectories(path.getParent());
Files.write(path, downloadData(url));

View File

@ -0,0 +1,39 @@
package io.gitlab.jfronny.inceptum.common;
import io.gitlab.jfronny.commons.io.JFiles;
import io.gitlab.jfronny.commons.throwable.ThrowingFunction;
import io.gitlab.jfronny.commons.throwable.ThrowingSupplier;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.ConcurrentHashMap;
public class ObjectCache {
private final ConcurrentHashMap<String, Object> container = new ConcurrentHashMap<>();
private final Path cacheDir;
public ObjectCache(Path cacheDir) {
this.cacheDir = cacheDir;
}
public void remove(String key) throws IOException {
container.remove(key);
Files.delete(cacheDir.resolve(key));
}
public void clear() throws IOException {
container.clear();
JFiles.clearDirectory(cacheDir);
}
public <T, TEx extends Throwable> T get(String key, ThrowingSupplier<String, ? extends TEx> download, ThrowingFunction<String, T, ? extends TEx> builder) throws IOException, TEx {
if (!container.containsKey(key)) {
Path cd = cacheDir.resolve(key);
if (Files.exists(cd)) container.put(key, builder.apply(Files.readString(cd)));
else container.put(key, builder.apply(download.get()));
}
//noinspection unchecked
return (T) container.get(key);
}
}

View File

@ -1,7 +0,0 @@
package io.gitlab.jfronny.inceptum.common;
public class OutdatedException extends RuntimeException {
public OutdatedException(String message) {
super(message);
}
}

View File

@ -1,15 +0,0 @@
package io.gitlab.jfronny.inceptum.common;
/**
* Class containing methods for nop lambdas. Use these instead of () -> {}
*/
public class R {
public static void nop() {
}
public static void nop(Object a1) {
}
public static void nop(Object a1, Object a2) {
}
}

View File

@ -1,19 +1,14 @@
package io.gitlab.jfronny.inceptum.common;
import io.gitlab.jfronny.commons.ComparableVersion;
import io.gitlab.jfronny.commons.OSUtils;
import io.gitlab.jfronny.commons.io.JFiles;
import io.gitlab.jfronny.inceptum.common.api.GitlabApi;
import io.gitlab.jfronny.inceptum.common.api.MavenApi;
import io.gitlab.jfronny.inceptum.common.model.gitlab.*;
import io.gitlab.jfronny.inceptum.common.model.inceptum.*;
import io.gitlab.jfronny.inceptum.common.model.maven.*;
import org.jetbrains.annotations.Nullable;
import org.xml.sax.SAXException;
import javax.xml.stream.XMLStreamException;
import java.io.File;
import java.io.IOException;
import java.io.*;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
@ -23,9 +18,12 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Updater {
public static final String PROJECT_MAVEN = "https://maven.frohnmeyer-wds.de/artifacts/";
private static final String ARTIFACTS_URL = "https://pages.frohnmeyer-wds.de/JfMods/Inceptum/artifacts/";
private static final String STABLE_URL = "https://pages.frohnmeyer-wds.de/JfMods/Inceptum/stable/";
public static UpdateMetadata getUpdate() {
return Updater.check(InceptumConfig.channel, BuildMetadata.VERSION, channel -> {
public static UpdateMetadata getUpdate(boolean versionCompare, boolean checkEnv) throws UpdateCheckException {
return Updater.check(InceptumConfig.channel, versionCompare, checkEnv, channel -> {
Utils.LOGGER.error("No stable version was found, switching to experimental channel");
InceptumConfig.channel = channel;
InceptumConfig.saveConfig();
@ -33,36 +31,29 @@ public class Updater {
}
public static void update(UpdateMetadata source, boolean relaunch) throws IOException, URISyntaxException {
if (Runtime.version().feature() < source.jvm) {
throw new OutdatedException("A newer JVM version is required for the current build of Inceptum. Please update!");
} else if (source.wrapperVersion > BuildMetadata.WRAPPER_VERSION) {
throw new OutdatedException("The current build of Inceptum requires a newer wrapper version. Please update!");
} else if (source.wrapperVersion < BuildMetadata.WRAPPER_VERSION) {
throw new OutdatedException("The current build of Inceptum requires an older wrapper version. Please update!");
}
Utils.LOGGER.info("Downloading version " + source.version());
Utils.LOGGER.info("Downloading version " + source.version);
WrapperConfig config = new WrapperConfig(
new LinkedHashSet<>(),
new LinkedHashSet<>(source.repositories()),
new HashMap<>()
);
source.natives().forEach((k, v) -> config.natives().put(k, new LinkedHashSet<>(v)));
WrapperConfig config = new WrapperConfig();
config.natives = new HashMap<>();
config.libraries = new LinkedHashSet<>();
config.repositories = new LinkedHashSet<>(source.repositories);
source.natives.forEach((k, v) -> config.natives.put(k, new LinkedHashSet<>(v)));
DependencyNode node = downloadLibrary(source.repositories, "io.gitlab.jfronny.inceptum:launcher-dist:" + source.version, config.libraries);
DependencyNode node = downloadLibrary(source.repositories(), "io.gitlab.jfronny.inceptum:launcher-dist:" + source.version(), config.libraries());
Utils.LOGGER.info("Downloaded Dependencies:\n" + node);
List<String> currentLibraries = new LinkedList<>(config.libraries);
if (source.natives.containsKey(Utils.getCurrentFlavor())) {
List<String> currentLibraries = new LinkedList<>(config.libraries());
if (source.natives().containsKey(Utils.getCurrentFlavor())) {
Set<String> natives = new LinkedHashSet<>();
for (String lib : source.natives.get(Utils.getCurrentFlavor())){
downloadLibrary(source.repositories, lib, natives);
for (String lib : source.natives().get(Utils.getCurrentFlavor())) {
downloadLibrary(source.repositories(), lib, natives);
}
currentLibraries.addAll(natives);
config.natives.put(Utils.getCurrentFlavor(), natives);
config.natives().put(Utils.getCurrentFlavor(), natives);
}
JFiles.writeObject(MetaHolder.WRAPPER_CONFIG_PATH, config);
GC_WrapperConfig.write(config, MetaHolder.WRAPPER_CONFIG_PATH);
if (relaunch) {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
@ -71,7 +62,7 @@ public class Updater {
"-cp",
buildClasspath(currentLibraries.stream())
.map(Path::toString)
.collect(Collectors.joining("" + File.pathSeparatorChar))
.collect(Collectors.joining(String.valueOf(File.pathSeparatorChar)))
).inheritIO().start();
} catch (IOException e) {
Utils.LOGGER.error("Could not relaunch", e);
@ -81,107 +72,81 @@ public class Updater {
}
public static List<Path> getLaunchClasspath(WrapperConfig wrapperConfig) throws IOException, URISyntaxException {
Set<String> natives = wrapperConfig.natives.get(Utils.getCurrentFlavor());
Set<String> natives = wrapperConfig.natives().get(Utils.getCurrentFlavor());
if (natives == null) natives = new LinkedHashSet<>();
Set<String> libs = wrapperConfig.libraries;
Set<String> libs = wrapperConfig.libraries();
if (libs == null) libs = new LinkedHashSet<>();
boolean configChanged = false;
for (String lib : libs) {
Path p = artifactToPath(lib);
Path p = ArtifactMeta.parse(lib).getLocalPath();
if (!Files.exists(p)) {
configChanged = true;
downloadLibrary(wrapperConfig.repositories, lib, libs);
downloadLibrary(wrapperConfig.repositories(), lib, libs);
}
}
for (String lib : natives) {
Path p = artifactToPath(lib);
Path p = ArtifactMeta.parse(lib).getLocalPath();
if (!Files.exists(p)) {
configChanged = true;
downloadLibrary(wrapperConfig.repositories, lib, natives);
downloadLibrary(wrapperConfig.repositories(), lib, natives);
}
}
if (configChanged) JFiles.writeObject(MetaHolder.WRAPPER_CONFIG_PATH, wrapperConfig);
if (configChanged) GC_WrapperConfig.write(wrapperConfig, MetaHolder.WRAPPER_CONFIG_PATH);
return buildClasspath(Stream.concat(libs.stream(), natives.stream())).toList();
}
private static Stream<Path> buildClasspath(Stream<String> libraries) {
return libraries.map(Updater::artifactToPath);
return libraries.map(ArtifactMeta::parse).map(ArtifactMeta::getLocalPath);
}
private static DependencyNode downloadLibrary(Set<String> repositories, final String artifact, Set<String> libraries) throws IOException, URISyntaxException {
List<Exception> exceptions = new LinkedList<>();
for (String repository : Stream.concat(Stream.of(GitlabApi.PROJECT_MAVEN), repositories.stream()).toList()) {
List<FileNotFoundException> suppressed = new LinkedList<>();
for (String repository : Stream.concat(Stream.of(PROJECT_MAVEN), repositories.stream()).toList()) {
ArtifactMeta meta;
try {
meta = MavenApi.getMetadata(repository, artifact);
} catch (FileNotFoundException ignored) {
meta = ArtifactMeta.parse(artifact);
} catch (IOException | URISyntaxException | SAXException e) {
throw new IOException("Could not download artifact from " + repository, e);
}
Pom pom;
try {
pom = MavenApi.getPom(repository, artifact);
} catch (IOException | URISyntaxException | XMLStreamException | SAXException e) {
exceptions.add(new Exception("Could not download artifact from " + repository, e));
pom = MavenApi.getPom(repository, meta);
} catch (FileNotFoundException notFound) {
suppressed.add(notFound);
continue;
} catch (IOException | URISyntaxException | XMLStreamException | SAXException e) {
throw new IOException("Could not download artifact " + meta.getMavenNotation() + " from " + repository, e);
}
Set<DependencyNode> dependencies = new LinkedHashSet<>();
if (pom.dependencies != null) {
for (MavenDependency dependency : pom.dependencies) {
String mvnName = dependency.groupId + ":" + dependency.artifactId + ":" + dependency.version;
if (pom.dependencies() != null) {
for (MavenDependency dependency : pom.dependencies()) {
String mvnName = dependency.groupId() + ":" + dependency.artifactId() + ":" + dependency.version();
dependencies.add(downloadLibrary(repositories, mvnName, libraries));
}
}
MavenApi.downloadLibrary(repository, artifact);
MavenApi.downloadLibrary(repository, meta);
libraries.add(artifact);
return new DependencyNode(pom, dependencies);
return new DependencyNode(artifact, dependencies);
}
IOException exception = new IOException("Could not find any repository containing the artifact " + artifact + " (searched: " + String.join(", ", repositories) + ")");
for (Exception e : exceptions) {
exception.addSuppressed(e);
}
throw exception;
IOException e = new IOException("Could not find any repository containing the artifact " + artifact + " (searched: " + String.join(", ", repositories) + ")");
for (FileNotFoundException ex : suppressed) e.addSuppressed(ex);
throw e;
}
private static Path artifactToPath(String artifact) {
return MetaHolder.LIBRARIES_DIR.resolve(MavenApi.mavenNotationToJarPath(artifact)).toAbsolutePath();
}
public static @Nullable UpdateMetadata check(UpdateChannel channel, ComparableVersion current, Consumer<UpdateChannel> channelInvalid) {
public static @Nullable UpdateMetadata check(UpdateChannel channel, boolean versionCompare, boolean checkEnv, Consumer<UpdateChannel> channelInvalid) throws UpdateCheckException {
try {
int jvm = Runtime.version().feature();
GitlabProject project = GitlabApi.getProject(GitlabApi.PROJECT_ID);
UpdateMetadata experimental = null;
UpdateMetadata experimental = Net.downloadObject(ARTIFACTS_URL + "version.json", json -> GC_UpdateMetadata.read(json));
UpdateMetadata stable = null;
packageLoop:for (GitlabPackage info : GitlabApi.getPackages(project)) {
if (info.status.equals("default") && info.name.equals("io/gitlab/jfronny/inceptum/Inceptum")) {
pipelineLoop:for (GitlabPipeline pipeline : info.pipelines) {
if (!pipeline.ref.equals("master")) continue pipelineLoop;
if (!pipeline.status.equals("success")) {
Utils.LOGGER.warn("Skipping failed CI build");
continue pipelineLoop;
}
for (GitlabJob job : GitlabApi.getJobs(project, pipeline.id)) {
if (!job.name.equals("build_test")) continue;
try {
UpdateMetadata update = Net.downloadObject(GitlabApi.PROJECTS + project.id + "/jobs/" + job.id + "/artifacts/version.json", UpdateMetadata.class);
if (update.jvm > jvm) {
Utils.LOGGER.error("A newer JVM is required to use the latest inceptum version. Please update!");
continue packageLoop;
}
if (experimental == null || experimental.version.compareTo(update.version) < 0) {
experimental = update;
}
if (!info.version.contains("-") && (stable == null || stable.version.compareTo(update.version) < 0)) {
stable = update;
}
} catch (IOException e) {
continue packageLoop;
}
}
}
}
}
if (experimental == null) {
throw new IOException("No version could be found");
} else if (stable == null && channel == UpdateChannel.Stable) {
try {
stable = Net.downloadObject(STABLE_URL + "version.json", json -> GC_UpdateMetadata.read(json));
} catch (Throwable ignored) {}
if (stable == null && channel == UpdateChannel.Stable) {
channel = UpdateChannel.CI;
channelInvalid.accept(channel);
}
@ -189,19 +154,39 @@ public class Updater {
case CI -> experimental;
case Stable -> stable;
};
Utils.LOGGER.info("Latest version is " + info.version + ", current is " + current);
if (current.compareTo(info.version) >= 0) {
Utils.LOGGER.info("Up-to-date");
return null;
if (checkEnv) {
if (info.jvm() > Runtime.version().feature()) throw new UpdateCheckException("A newer JVM is required to use the latest inceptum version. Please update!", "Outdated Java");
if (info.wrapperVersion() != BuildMetadata.WRAPPER_VERSION) throw new UpdateCheckException("A different version of the Inceptum Wrapper is required for this update!", "Mismatched Wrapper");
}
if (versionCompare) {
Utils.LOGGER.info("Latest version is " + info.version() + ", current is " + BuildMetadata.VERSION);
if (BuildMetadata.BUILD_TIME >= info.buildTime()) {
Utils.LOGGER.info("Up-to-date");
return null;
}
}
return info;
} catch (IOException | URISyntaxException e) {
} catch (IOException e) {
Utils.LOGGER.error("Could not check for updates", e);
}
return null;
}
public static String getShadowJarUrl(UpdateMetadata metadata) {
return GitlabApi.PROJECT_MAVEN + "io/gitlab/jfronny/inceptum/Inceptum/" + metadata.version + "/Inceptum-" + metadata.version + "-" + Utils.getCurrentFlavor() + ".jar";
public static String getShadowJarUrl(UpdateChannel channel) {
return switch (channel) {
case CI -> ARTIFACTS_URL;
case Stable -> STABLE_URL;
} + "/Inceptum-" + Utils.getCurrentFlavor() + ".jar";
}
public static class UpdateCheckException extends Exception {
public final String message;
public final String title;
public UpdateCheckException(String message, String title) {
super(message);
this.message = message;
this.title = title;
}
}
}

View File

@ -2,7 +2,7 @@ package io.gitlab.jfronny.inceptum.common;
import io.gitlab.jfronny.commons.OSUtils;
import io.gitlab.jfronny.commons.io.JFiles;
import io.gitlab.jfronny.commons.log.Logger;
import io.gitlab.jfronny.commons.logging.Logger;
import java.awt.*;
import java.io.File;
@ -17,6 +17,7 @@ import java.util.stream.Collectors;
public class Utils {
public static final int CACHE_SIZE = 128;
public static final Pattern NEW_LINE = Pattern.compile("[\r\n]+");
public static final Pattern VALID_FILENAME = Pattern.compile("[a-zA-Z0-9_\\-.][a-zA-Z0-9 _\\-.]*[a-zA-Z0-9_\\-.]");
public static final Logger LOGGER = Logger.forName("Inceptum");
private static ClassLoader SYSTEM_LOADER = ClassLoader.getSystemClassLoader();
@ -41,7 +42,7 @@ public class Utils {
Desktop.getDesktop().open(file);
}
} catch (Exception e) {
Utils.LOGGER.error("Error opening web browser!", e);
Utils.LOGGER.error("Error opening file!", e);
}
}
@ -58,8 +59,9 @@ public class Utils {
/**
* Joins strings with the provided separator but removes separators from the start and end of the strings
* Example: join('/', "some/path/", "/some/subpath/", "example/") -> "some/path/some/subpath/example
*
* @param separator The separator to join with
* @param segments The strings to join
* @param segments The strings to join
* @return The joined string
*/
public static String join(String separator, String... segments) {

View File

@ -1,48 +0,0 @@
package io.gitlab.jfronny.inceptum.common.api;
import io.gitlab.jfronny.commons.HttpUtils;
import io.gitlab.jfronny.gson.reflect.TypeToken;
import io.gitlab.jfronny.inceptum.common.model.gitlab.*;
import java.io.IOException;
import java.lang.reflect.Type;
import java.net.URISyntaxException;
import java.util.List;
import java.util.function.Predicate;
public class GitlabApi {
public static final String PROJECTS = "https://gitlab.com/api/v4/projects/";
public static final long PROJECT_ID = 30862253L;
public static final String PROJECT_MAVEN = "https://gitlab.com/api/v4/projects/" + PROJECT_ID + "/packages/maven/";
private static final Type packageInfoListType = new TypeToken<List<GitlabPackage>>() {}.getType();
private static final Type jobListType = new TypeToken<List<GitlabJob>>() {}.getType();
private static final Type packageFileInfoListType = new TypeToken<List<GitlabPackageFile>>() {}.getType();
public static GitlabProject getProject(Long projectId) throws IOException, URISyntaxException {
return HttpUtils.get(PROJECTS + projectId).sendSerialized(GitlabProject.class);
}
public static List<GitlabPackage> getPackages(GitlabProject project) throws IOException, URISyntaxException {
return HttpUtils.get(PROJECTS + project.id + "/packages?order_by=created_at&sort=desc").sendSerialized(packageInfoListType);
}
public static List<GitlabJob> getJobs(GitlabProject project, Long pipelineId) throws IOException, URISyntaxException {
List<GitlabJob> list = HttpUtils.get(PROJECTS + project.id + "/pipelines/" + pipelineId + "/jobs").sendSerialized(jobListType);
list.sort((left, right) -> right.created_at.compareTo(left.created_at));
return list;
}
public static GitlabPackageFile getFile(GitlabProject project, GitlabPackage packageInfo, Predicate<GitlabPackageFile> isValid) throws IOException, URISyntaxException {
int page = 0;
while (true) {
List<GitlabPackageFile> files = HttpUtils.get(PROJECTS + project.id + "/packages/" + packageInfo.id + "/package_files?per_page=100&page=" + ++page).sendSerialized(packageFileInfoListType);
if (files.isEmpty()) return null;
for (GitlabPackageFile file : files) {
if (isValid.test(file))
return file;
}
}
}
}

View File

@ -1,9 +1,9 @@
package io.gitlab.jfronny.inceptum.common.api;
import io.gitlab.jfronny.commons.HttpUtils;
import io.gitlab.jfronny.inceptum.common.*;
import io.gitlab.jfronny.inceptum.common.model.maven.MavenDependency;
import io.gitlab.jfronny.inceptum.common.model.maven.Pom;
import io.gitlab.jfronny.commons.http.client.HttpClient;
import io.gitlab.jfronny.inceptum.common.Net;
import io.gitlab.jfronny.inceptum.common.Utils;
import io.gitlab.jfronny.inceptum.common.model.maven.*;
import org.jetbrains.annotations.Nullable;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
@ -28,125 +28,170 @@ public class MavenApi {
}
}
public static Path downloadLibrary(String repo, String artifact) throws IOException, URISyntaxException {
String path = mavenNotationToJarPath(artifact);
Path res = MetaHolder.LIBRARIES_DIR.resolve(path);
Net.downloadFile(Utils.join("/", repo, path), res);
public static Path downloadLibrary(String repo, ArtifactMeta meta) throws IOException, URISyntaxException {
Path res = meta.getLocalPath();
Net.downloadFile(Utils.join("/", repo, meta.getJarPath(true)), res);
return res;
}
public static Pom getPom(String repo, String artifact) throws IOException, SAXException, URISyntaxException, XMLStreamException {
try (InputStream is = HttpUtils.get(Utils.join("/", repo, mavenNotationToPomPath(artifact))).sendInputStream()) {
public static Pom getPom(String repo, ArtifactMeta meta) throws IOException, SAXException, URISyntaxException, XMLStreamException {
try (InputStream is = HttpClient.get(Utils.join("/", repo, meta.getPomPath())).sendInputStream()) {
Document doc = FACTORY.parse(is);
doc.getDocumentElement().normalize();
Pom result = new Pom();
if (!"project".equals(doc.getDocumentElement().getNodeName())) throw new IOException("Illegal document name");
boolean hasModelVersion = false;
boolean hasGroupId = false;
boolean hasArtifactId = false;
boolean hasVersion = false;
for (Node node : iterable(doc.getDocumentElement().getChildNodes())) {
String modelVersion = null;
String groupId = null;
String artifactId = null;
String version = null;
String packaging = null;
List<MavenDependency> dependencies = null;
String classifier = null;
for (Node node : children(doc.getDocumentElement())) {
switch (node.getNodeName()) {
case "modelVersion" -> {
hasModelVersion = true;
result.modelVersion = node.getTextContent();
}
case "modelVersion" -> modelVersion = node.getTextContent();
case "parent" -> {
// Dirty hack to get slf4j working: simply assume the groupId and version of the parent is also the groupId of this
if (!hasGroupId) {
for (Node child : iterable(node.getChildNodes())) {
if (groupId == null) {
for (Node child : children(node)) {
switch (child.getNodeName()) {
case "groupId" -> {
if (!hasGroupId) {
hasGroupId = true;
result.groupId = node.getTextContent();
if (groupId == null) {
groupId = child.getTextContent();
}
}
case "version" -> {
if (!hasVersion) {
hasVersion = true;
result.version = node.getTextContent();
if (version == null) {
version = child.getTextContent();
}
}
}
}
}
}
case "groupId" -> {
hasGroupId = true;
result.groupId = node.getTextContent();
}
case "groupId" -> groupId = node.getTextContent();
case "artifactId" -> {
hasArtifactId = true;
result.artifactId = node.getTextContent();
artifactId = node.getTextContent();
}
case "version" -> {
hasVersion = true;
result.version = node.getTextContent();
version = node.getTextContent();
}
case "packaging" -> result.packaging = node.getTextContent();
case "packaging" -> packaging = node.getTextContent();
case "dependencies" -> {
result.dependencies = new LinkedList<>();
for (Node dep : iterable(node.getChildNodes())) {
dependencies = new LinkedList<>();
for (Node dep : children(node)) {
MavenDependency resolved = parseDependency(dep);
if (resolved != null) {
result.dependencies.add(resolved);
dependencies.add(resolved);
}
}
}
case "classifier" -> classifier = node.getTextContent();
default -> {}
}
}
if (!hasModelVersion) throw new IOException("Pom lacks modelVersion");
if (!hasGroupId) throw new IOException("Pom lacks groupId");
if (!hasArtifactId) throw new IOException("Pom lacks artifactId");
if (!hasVersion) throw new IOException("Pom lacks version");
return result;
if (modelVersion == null) throw new IOException("Pom lacks modelVersion");
if (groupId == null) throw new IOException("Pom lacks groupId");
if (artifactId == null) throw new IOException("Pom lacks artifactId");
if (version == null) throw new IOException("Pom lacks version");
return new Pom(modelVersion, groupId, artifactId, version, classifier, null, packaging, dependencies);
}
}
private static @Nullable MavenDependency parseDependency(Node doc) throws IOException {
MavenDependency result = new MavenDependency();
boolean hasGroupId = false;
boolean hasArtifactId = false;
boolean hasVersion = false;
boolean hasScope = false;
for (Node node : iterable(doc.getChildNodes())) {
String groupId = null;
String artifactId = null;
String version = null;
String scope = null;
for (Node node : children(doc)) {
switch (node.getNodeName()) {
case "groupId" -> {
hasGroupId = true;
result.groupId = node.getTextContent();
}
case "artifactId" -> {
hasArtifactId = true;
result.artifactId = node.getTextContent();
}
case "version" -> {
hasVersion = true;
result.version = node.getTextContent();
}
case "groupId" -> groupId = node.getTextContent();
case "artifactId" -> artifactId = node.getTextContent();
case "version" -> version = node.getTextContent();
case "scope" -> {
hasScope = true;
result.scope = node.getTextContent();
if (!RUNTIME_SCOPES.contains(result.scope)) return null;
scope = node.getTextContent();
if (!RUNTIME_SCOPES.contains(scope)) return null;
}
case "optional" -> {
if (node.getTextContent().equals("true")) return null;
}
}
}
if (!hasGroupId) throw new IOException("Pom lacks groupId");
if (!hasArtifactId) throw new IOException("Pom lacks artifactId");
if (!hasVersion) {
if (result.groupId.equals("org.lwjgl")) {
if (groupId == null) throw new IOException("Pom lacks groupId");
if (artifactId == null) throw new IOException("Pom lacks artifactId");
if (version == null) {
if (groupId.equals("org.lwjgl")) {
// Lwjgl uses a shared bom for versions which I don't want to support
// The required modules are explicit dependencies of launcher-imgui anyway
return null;
}
throw new IOException("Dependency " + result.groupId + ":" + result.artifactId + " lacks version");
throw new IOException("Dependency " + groupId + ":" + artifactId + " lacks version");
}
if (!hasScope) throw new IOException("Pom lacks scope");
return result;
if (scope == null) throw new IOException("Pom lacks scope");
return new MavenDependency(groupId, artifactId, version, scope);
}
public static ArtifactMeta getMetadata(String repo, String artifact) throws IOException, SAXException, URISyntaxException {
ArtifactMeta sourceMeta = ArtifactMeta.parse(artifact);
try (InputStream is = HttpClient.get(Utils.join("/", repo, sourceMeta.getMetadataPath())).sendInputStream()) {
Document doc = FACTORY.parse(is);
doc.getDocumentElement().normalize();
if (!"metadata".equals(doc.getDocumentElement().getNodeName())) throw new IOException("Illegal document name");
String groupId = null;
String artifactId = null;
String version = null;
String snapshotVersion = null;
for (Node node : children(doc.getDocumentElement())) {
switch (node.getNodeName()) {
case "groupId" -> groupId = node.getTextContent();
case "artifactId" -> artifactId = node.getTextContent();
case "version" -> version = node.getTextContent();
case "versioning" -> {
for (Node node1 : children(node)) {
if (node1.getNodeName().equals("snapshot")) {
String timestamp = null;
String buildNumber = null;
for (Node node2 : children(node1)) {
switch (node2.getNodeName()) {
case "timestamp" -> timestamp = node2.getTextContent();
case "buildNumber" -> buildNumber = node2.getTextContent();
default -> {}
}
}
if (timestamp == null) throw new IOException("Pom snapshots lack timestamp");
if (buildNumber == null) throw new IOException("Pom snapshots lack buildNumber");
snapshotVersion = timestamp + '-' + buildNumber;
}
}
}
default -> {}
}
}
if (groupId == null) throw new IOException("Pom lacks groupId");
if (artifactId == null) throw new IOException("Pom lacks artifactId");
if (version == null) throw new IOException("Pom lacks version");
return new ArtifactMeta(groupId, artifactId, version, sourceMeta.classifier(), snapshotVersion);
}
}
private static Iterable<Node> children(Node node) {
return () -> new Iterator<Node>() {
NodeList children = node.getChildNodes();
int index = 0;
@Override
public boolean hasNext() {
while (index < children.getLength() && isWhitespace(children.item(index))) {
index++;
}
return index < children.getLength();
}
@Override
public Node next() {
if (!hasNext()) throw new NoSuchElementException();
return children.item(index++);
}
};
}
private static boolean isWhitespace(Node node) {
@ -154,67 +199,4 @@ public class MavenApi {
if (node.getNodeType() == Node.COMMENT_NODE) return true;
return false;
}
private static Iterable<Node> iterable(NodeList list) {
return () -> new Iterator<>() {
int index = 0;
@Override
public boolean hasNext() {
while (index < list.getLength() && isWhitespace(list.item(index))) {
index++;
}
return index < list.getLength();
}
@Override
public Node next() {
if (!hasNext()) throw new NoSuchElementException();
return list.item(index++);
}
};
}
/**
* Converts an artifact in maven notation to a jar file path. The following are supported:
* - some.base.path:artifact:version -> some/base/path/artifact/version/artifact-version.jar
* - some.base.path:artifact:version:classifier -> some/base/path/artifact/version/artifact-version-classifier.jar
* @param mavenNotation An artifact in maven notation
* @return A file path
*/
public static String mavenNotationToJarPath(String mavenNotation) {
if (Objects.requireNonNull(mavenNotation).isEmpty()) throw new IllegalArgumentException("The notation is empty");
String[] lib = mavenNotation.split(":");
if (lib.length <= 1) throw new IllegalArgumentException("Not in maven notation");
if (lib.length == 2) throw new IllegalArgumentException("Skipping versions is not supported");
if (lib.length >= 5) throw new IllegalArgumentException("Unkown elements in maven notation");
String path = lib[0].replace('.', '/') + '/'; // Base
path += lib[1] + '/'; // Artifact name
path += lib[2] + '/'; // Version
if (lib.length == 3) { // artifact-version.jar
path += lib[1] + '-' + lib[2];
} else { // artifact-version-classifier.jar
path += lib[1] + '-' + lib[2] + "-" + lib[3];
}
return path + ".jar";
}
/**
* Converts an artifact in maven notation to a pom file path. The following are supported:
* - some.base.path:artifact:version -> some/base/path/artifact/version/artifact-version.pom
* - some.base.path:artifact:version:classifier -> some/base/path/artifact/version/artifact-version.pom
* @param mavenNotation An artifact in maven notation
* @return A file path
*/
public static String mavenNotationToPomPath(String mavenNotation) {
if (Objects.requireNonNull(mavenNotation).isEmpty()) throw new IllegalArgumentException("The notation is empty");
String[] lib = mavenNotation.split(":");
if (lib.length <= 1) throw new IllegalArgumentException("Not in maven notation");
if (lib.length == 2) throw new IllegalArgumentException("Skipping versions is not supported");
if (lib.length >= 5) throw new IllegalArgumentException("Unkown elements in maven notation");
String path = lib[0].replace('.', '/') + '/'; // Base
path += lib[1] + '/'; // Artifact name
path += lib[2] + '/'; // Version
path += lib[1] + '-' + lib[2]; // artifact-version
return path + ".pom";
}
}

View File

@ -1,8 +0,0 @@
package io.gitlab.jfronny.inceptum.common.model.gitlab;
public class GitlabArtifact {
public String file_type;
public Long size;
public String filename;
public String file_format;
}

View File

@ -1,20 +0,0 @@
package io.gitlab.jfronny.inceptum.common.model.gitlab;
import java.util.Date;
import java.util.List;
public class GitlabCommit {
public String id;
public String short_id;
public Date created_at;
public List<String> parent_ids;
public String title;
public String message;
public String author_name;
public String author_email;
public Date authored_date;
public String committer_name;
public String committer_email;
public Date committed_date;
public String web_url;
}

View File

@ -1,26 +0,0 @@
package io.gitlab.jfronny.inceptum.common.model.gitlab;
import java.util.Date;
import java.util.List;
public class GitlabJob {
public Long id;
public String status;
public String stage;
public String name;
public String ref;
public Boolean tag;
public Boolean allow_failure;
public Date created_at;
public Date started_at;
public Date finished_at;
public Double duration;
public Double queued_duration;
public GitlabUser user;
public GitlabCommit commit;
public GitlabPipeline pipeline;
public String web_url;
public List<GitlabArtifact> artifacts;
public GitlabRunner runner;
public Date artifacts_expire_at;
}

View File

@ -1,14 +0,0 @@
package io.gitlab.jfronny.inceptum.common.model.gitlab;
import java.util.Date;
import java.util.List;
public class GitlabPackage {
public Long id;
public String name;
public String version;
public String package_type;
public String status;
public Date created_at;
public List<GitlabPipeline> pipelines;
}

View File

@ -1,16 +0,0 @@
package io.gitlab.jfronny.inceptum.common.model.gitlab;
import java.util.Date;
import java.util.List;
public class GitlabPackageFile {
public Long id;
public Long package_id;
public Date created_at;
public String file_name;
public Integer size;
public String file_md5;
public String file_sha1;
public String file_sha256;
public List<GitlabPipeline> pipelines;
}

View File

@ -1,16 +0,0 @@
package io.gitlab.jfronny.inceptum.common.model.gitlab;
import java.util.Date;
public class GitlabPipeline {
public long id;
public long project_id;
public String sha;
public String ref;
public String status;
public String source;
public Date created_at;
public Date updated_at;
public String web_url;
public GitlabUser user;
}

View File

@ -1,86 +0,0 @@
package io.gitlab.jfronny.inceptum.common.model.gitlab;
import java.util.Date;
public class GitlabProject {
public Long id;
public String description;
public String name;
public String name_with_namespace;
public String path;
public String path_with_namespace;
public String created_at;
public String default_branch;
public String ssh_url_to_repo;
public String http_url_to_repo;
public String web_url;
public String readme_url;
public String avatar_url;
public Integer forks_count;
public Integer star_count;
public Date last_activity_at;
public Boolean packages_enabled;
public Boolean empty_repo;
public Boolean archived;
public String visibility;
public GitlabUser owner;
public Boolean resolve_outdated_diff_discussions;
public Boolean issues_enabled;
public Boolean merge_requests_enabled;
public Boolean wiki_enabled;
public Boolean jobs_enabled;
public Boolean snippets_enabled;
public Boolean container_registry_enabled;
public Boolean service_desk_enabled;
public String service_desk_address;
public Boolean can_create_merge_request_in;
public String issues_access_level;
public String repository_access_level;
public String merge_request_access_level;
public String forking_access_level;
public String wiki_access_level;
public String builds_access_level;
public String snippets_access_level;
public String pages_access_level;
public String operations_access_level;
public String analytics_access_level;
public String container_registry_access_level;
public Boolean emails_disabled;
public Boolean shared_runners_enabled;
public Boolean lfs_enabled;
public Long creator_id;
public String import_status;
public Integer open_issues_count;
public String runners_token;
public Integer ci_default_git_depth;
public Boolean ci_forward_deployment_enabled;
public Boolean ci_job_token_scope_enabled;
public Boolean public_jobs;
public String build_git_strategy;
public Integer build_timeout;
public String auto_cancel_pending_pipelines;
public String build_coverage_regex;
public String ci_config_path;
public Boolean only_allow_merge_if_pipeline_succeeds;
public Boolean restrict_user_defined_variables;
public Boolean request_access_enabled;
public Boolean only_allow_merge_if_all_discussions_are_resolved;
public Boolean remove_source_branch_after_merge;
public Boolean printing_merge_request_link_enabled;
public String merge_method;
public String squash_option;
public String suggestion_commit_message;
public Boolean auto_devops_enabled;
public String auto_devops_strategy;
public Boolean autoclose_referenced_issues;
public Boolean keep_latest_artifact;
public Integer approvals_before_merge;
public Boolean mirror;
public String external_authorization_classification_label;
public Integer requirements_enabled;
public Integer security_and_compliance_enabled;
public String issues_template;
public String merge_requests_template;
public Boolean merge_pipelines_enabled;
public Boolean merge_trains_enabled;
}

View File

@ -1,13 +0,0 @@
package io.gitlab.jfronny.inceptum.common.model.gitlab;
public class GitlabRunner {
public Long id;
public String description;
public String ip_address;
public Boolean active;
public Boolean is_shared;
public String runner_type;
public String name;
public Boolean online;
public String deprecated_rest_status;
}

View File

@ -1,10 +0,0 @@
package io.gitlab.jfronny.inceptum.common.model.gitlab;
public class GitlabUser {
public long id;
public String name;
public String username;
public String state;
public String avatar_url;
public String web_url;
}

View File

@ -1,15 +1,18 @@
package io.gitlab.jfronny.inceptum.common.model.inceptum;
import io.gitlab.jfronny.commons.ComparableVersion;
import io.gitlab.jfronny.gson.compile.annotations.GSerializable;
import io.gitlab.jfronny.inceptum.common.GsonPreset;
import java.util.*;
import java.util.Map;
import java.util.Set;
public class UpdateMetadata {
public Integer wrapperVersion;
public ComparableVersion version;
public Boolean isPublic;
public Boolean isRelease;
public Integer jvm;
public Set<String> repositories;
public Map<String, Set<String>> natives;
@GSerializable(configure = GsonPreset.Api.class)
public record UpdateMetadata(int wrapperVersion,
String version,
long buildTime,
boolean isPublic,
boolean isRelease,
int jvm,
Set<String> repositories,
Map<String, Set<String>> natives) {
}

View File

@ -1,9 +1,11 @@
package io.gitlab.jfronny.inceptum.common.model.inceptum;
import java.util.*;
import io.gitlab.jfronny.gson.compile.annotations.GSerializable;
import io.gitlab.jfronny.inceptum.common.GsonPreset;
public class WrapperConfig {
public Set<String> libraries;
public Set<String> repositories;
public Map<String, Set<String>> natives;
import java.util.Map;
import java.util.Set;
@GSerializable(configure = GsonPreset.Config.class)
public record WrapperConfig(Set<String> libraries, Set<String> repositories, Map<String, Set<String>> natives) {
}

View File

@ -0,0 +1,57 @@
package io.gitlab.jfronny.inceptum.common.model.maven;
import io.gitlab.jfronny.inceptum.common.MetaHolder;
import org.jetbrains.annotations.Nullable;
import java.nio.file.Path;
import java.util.Objects;
public record ArtifactMeta(String groupId, String artifactId, String version, @Nullable String classifier, @Nullable String snapshotVersion) {
public ArtifactMeta(String groupId, String artifactId, String version) {
this(groupId, artifactId, version, null, null);
}
public static ArtifactMeta parse(String mavenNotation) {
if (Objects.requireNonNull(mavenNotation).isEmpty()) throw new IllegalArgumentException("The notation is empty");
String[] lib = mavenNotation.split(":");
if (lib.length <= 1) throw new IllegalArgumentException("Not in maven notation");
if (lib.length == 2) throw new IllegalArgumentException("Skipping versions is not supported");
if (lib.length >= 5) throw new IllegalArgumentException("Unkown elements in maven notation");
return new ArtifactMeta(lib[0], lib[1], lib[2], lib.length > 3 ? lib[3] : null, null);
}
public String getPomPath() {
String path = groupId.replace('.', '/') + '/';
path += artifactId + '/';
path += version + '/';
path += artifactId + '-';
if (snapshotVersion != null) path += version.replace("SNAPSHOT", snapshotVersion);
else path += version;
return path + ".pom";
}
public String getJarPath(boolean respectSnapshotVersion) {
String path = groupId.replace('.', '/') + '/';
path += artifactId + '/';
path += version + '/';
path += artifactId + '-';
if (snapshotVersion != null && respectSnapshotVersion) path += version.replace("SNAPSHOT", snapshotVersion);
else path += version;
if (classifier != null) path += '-' + classifier;
return path + ".jar";
}
public String getMavenNotation() {
String notation = groupId + ':' + artifactId + ':' + version;
if (classifier != null) notation += ':' + classifier;
return notation;
}
public String getMetadataPath() {
return groupId.replace('.', '/') + '/' + artifactId + '/' + version + "/maven-metadata.xml";
}
public Path getLocalPath() {
return MetaHolder.LIBRARIES_DIR.resolve(getJarPath(false));
}
}

View File

@ -1,30 +1,9 @@
package io.gitlab.jfronny.inceptum.common.model.maven;
import java.util.*;
public class DependencyNode {
private final String name;
private final Set<DependencyNode> dependencies;
public DependencyNode(Pom pom, Set<DependencyNode> dependencies) {
Objects.requireNonNull(pom);
this.name = pom.groupId + ":" + pom.artifactId + ":" + pom.version;
this.dependencies = Objects.requireNonNull(dependencies);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DependencyNode that = (DependencyNode) o;
return name.equals(that.name) && dependencies.equals(that.dependencies);
}
@Override
public int hashCode() {
return Objects.hash(name, dependencies);
}
import java.util.Iterator;
import java.util.Set;
public record DependencyNode(String name, Set<DependencyNode> dependencies) {
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
@ -34,7 +13,7 @@ public class DependencyNode {
private void generateTree(StringBuilder sb, String prefix, String childrenPrefix) {
sb.append(prefix).append(name).append('\n');
for (Iterator<DependencyNode> it = dependencies.iterator(); it.hasNext();) {
for (Iterator<DependencyNode> it = dependencies.iterator(); it.hasNext(); ) {
DependencyNode next = it.next();
if (it.hasNext()) {
next.generateTree(sb, childrenPrefix + "├── ", childrenPrefix + "");

View File

@ -1,8 +1,4 @@
package io.gitlab.jfronny.inceptum.common.model.maven;
public class MavenDependency {
public String groupId;
public String artifactId;
public String version;
public String scope;
public record MavenDependency(String groupId, String artifactId, String version, String scope) {
}

View File

@ -4,11 +4,12 @@ import org.jetbrains.annotations.Nullable;
import java.util.List;
public class Pom {
public String modelVersion;
public String groupId;
public String artifactId;
public String version;
@Nullable public String packaging;
@Nullable public List<MavenDependency> dependencies;
public record Pom(String modelVersion,
String groupId,
String artifactId,
String version,
String classifier,
String snapshotVersion,
@Nullable String packaging,
@Nullable List<MavenDependency> dependencies) {
}

View File

@ -0,0 +1,18 @@
module io.gitlab.jfronny.inceptum.common {
exports io.gitlab.jfronny.inceptum.common;
exports io.gitlab.jfronny.inceptum.common.api;
exports io.gitlab.jfronny.inceptum.common.model.inceptum;
exports io.gitlab.jfronny.inceptum.common.model.maven;
requires transitive java.desktop;
requires java.xml;
requires transitive io.gitlab.jfronny.commons;
requires transitive io.gitlab.jfronny.commons.gson;
requires transitive io.gitlab.jfronny.commons.http.client;
requires transitive io.gitlab.jfronny.commons.io;
requires transitive io.gitlab.jfronny.commons.logging;
requires transitive io.gitlab.jfronny.gson;
requires transitive io.gitlab.jfronny.gson.compile.core;
requires static org.jetbrains.annotations;
requires static io.gitlab.jfronny.gson.compile.annotations;
}

View File

@ -1,11 +1,11 @@
# CLI
Inceptum provides a CLI which performs similar functions to the GUI. If you have feature requests, [open an issue](https://gitlab.com/jfmods/inceptum/-/issues)
Inceptum provides a CLI which performs similar functions to the GUI. If you have feature requests, [open an issue](https://helpdesk.frohnmeyer-wds.de/issue?repo=Inceptum&owner=JfMods)
To view up-to-date information on the commands provided by Inceptum, run `inceptum help` or `inceptum help <command>`, this page is intended to explain more advanced features
## The inceptum wrapper
Inceptum Wrapper looks through the libraries dir and launches the latest available Inceptum version.
If it doesn't find a usable version, it will download the latest from GitLab.
If it doesn't find a usable version, it will download the latest available version automatically.
Launching is performed through a custom ClassLoader and an internal flag is set to inform the launched version about the wrappers' presence.
## The "batch" command

View File

@ -5,7 +5,7 @@ All of these are subject to change, though automatic migrations will likely be p
## inceptum.json (Main Config)
```json
```json5
{
// Whether to show snapshots in the version selector for new instances
"snapshots": false,
@ -38,7 +38,7 @@ It stores your minecraft account login and CAN BE USED TO IMPERSONATE YOU!
Please note that all entries except for "version" are optional
```json
```json5
{
// The version to use for launching this
// Can be a fabric loader version (as seen here) or a normal minecraft version (like "1.17.1")
@ -74,7 +74,7 @@ Please note that all entries except for "version" are optional
## *.imod (Mod Metadata)
```json
```json5
{
// Where the JAR file for this mod can be obtained
"sources": [
@ -121,6 +121,8 @@ Please note that all entries except for "version" are optional
// A list of dependencies by their file names
"dependencies": [
"someOtherMod.imod"
]
],
// Whether this mod was explicitly installed (used during mod removal)
"explicit": true
}
```

View File

@ -6,11 +6,11 @@ Inceptum can be installed in a number of ways, all of which are documented below
First, download the Inceptum build appropriate for your system:
- [Windows x86_64](https://gitlab.com/JFronny/inceptum/-/jobs/artifacts/master/raw/Inceptum-windows.jar?job=build_platform_jars)
- [MacOS x86_64](https://gitlab.com/JFronny/inceptum/-/jobs/artifacts/master/raw/Inceptum-macos.jar?job=build_platform_jars) (untested)
- [Linux x86_64](https://gitlab.com/JFronny/inceptum/-/jobs/artifacts/master/raw/Inceptum-linux.jar?job=build_platform_jars)
- [Windows x86_64](https://pages.frohnmeyer-wds.de/JfMods/Inceptum/artifacts/Inceptum-windows.jar)
- [MacOS x86_64](https://pages.frohnmeyer-wds.de/JfMods/Inceptum/artifacts/Inceptum-macos.jar) (untested)
- [Linux x86_64](https://pages.frohnmeyer-wds.de/JfMods/Inceptum/artifacts/Inceptum-linux.jar)
You can also download a [single jar](https://gitlab.com/JFronny/inceptum/-/jobs/artifacts/master/raw/Inceptum.jar?job=build_platform_jars)
You can also download a [single jar](https://pages.frohnmeyer-wds.de/JfMods/Inceptum/artifacts/Inceptum.jar)
that contains support for all of these systems (though doing so is not recommended)
Once you have a jar, run it with an [up-to-date java version](https://adoptium.net/).
@ -32,10 +32,10 @@ If this parameter is specified, all other locations will be ignored.
## Simple installation with updates
To use automatic updates, you must use the Inceptum Wrapper.
Simply launch this [cross-platform jar](https://gitlab.com/JFronny/inceptum/-/jobs/artifacts/master/raw/wrapper.jar?job=build_wrapper)
Simply launch this [cross-platform jar](https://pages.frohnmeyer-wds.de/JfMods/Inceptum/artifacts/wrapper.jar)
and Inceptum will launch as described above (though the initial startup may take a bit longer). The same rules for config locations apply.
You may also download the [windows exe](https://gitlab.com/jfmods/inceptum/-/jobs/artifacts/master/raw/wrapper.exe?job=build_wrapper)
You may also download the [windows exe](https://pages.frohnmeyer-wds.de/JfMods/Inceptum/artifacts/wrapper.exe)
which uses fabric-installer-native-bootstrap to locate the JVM used by the official minecraft launcher and launch Inceptum using that.
Please be aware that this is pretty much untested
@ -48,6 +48,6 @@ For more information on the syntax of this services config file, go [here](Comma
## Using Inceptum on Windows without OpenGL drivers
Download the portable build from [here](https://gitlab.com/jfmods/inceptum/-/jobs/artifacts/master/raw/portable.7z?job=portable)
Download the portable build from [here](https://pages.frohnmeyer-wds.de/JfMods/Inceptum/artifacts/portable.7z)
This archive includes Inceptum using the Inceptum wrapper, a JVM and a Mesa build for CPU-based graphics.
Please be aware that using this WILL result in worse performance.

52
gradle/libs.versions.toml Normal file
View File

@ -0,0 +1,52 @@
[versions]
jf-commons = "1.5-SNAPSHOT"
gson-compile = "1.4-SNAPSHOT"
annotations = "24.0.1"
lwjgl = "3.3.2"
imgui = "1.86.10"
javagi = "0.9.0"
kotlin = "1.9.20"
[libraries]
plugin-shadow = "gradle.plugin.com.github.johnrengelman:shadow:7.1.2"
plugin-download = "de.undercouch:gradle-download-task:5.1.2"
plugin-jf-convention = "io.gitlab.jfronny:convention:1.5-SNAPSHOT"
plugin-jlink = "org.beryx:badass-jlink-plugin:3.0.1"
lwjgl-core = { module = "org.lwjgl:lwjgl", version.ref = "lwjgl" }
lwjgl-glfw = { module = "org.lwjgl:lwjgl-glfw", version.ref = "lwjgl" }
lwjgl-opengl = { module = "org.lwjgl:lwjgl-opengl", version.ref = "lwjgl" }
lwjgl-tinyfd = { module = "org.lwjgl:lwjgl-tinyfd", version.ref = "lwjgl" }
lwjgl-core-natives = { module = "org.lwjgl:lwjgl", version.ref = "lwjgl" }
lwjgl-glfw-natives = { module = "org.lwjgl:lwjgl-glfw", version.ref = "lwjgl" }
lwjgl-opengl-natives = { module = "org.lwjgl:lwjgl-opengl", version.ref = "lwjgl" }
lwjgl-tinyfd-natives = { module = "org.lwjgl:lwjgl-tinyfd", version.ref = "lwjgl" }
imgui = { module = "io.github.spair:imgui-java-binding", version.ref = "imgui" } # https://github.com/SpaiR/imgui-java
imgui-lwjgl = { module = "io.github.spair:imgui-java-lwjgl3", version.ref = "imgui" }
imgui-natives-linux = { module = "io.github.spair:imgui-java-natives-linux", version.ref = "imgui" }
imgui-natives-windows = { module = "io.github.spair:imgui-java-natives-windows", version.ref = "imgui" }
imgui-natives-macos = { module = "io.github.spair:imgui-java-natives-macos", version.ref = "imgui" }
javagi-glib = { module = "io.github.jwharm.javagi:glib", version.ref = "javagi" }
javagi-gtk = { module = "io.github.jwharm.javagi:gtk", version.ref = "javagi" }
javagi-adw = { module = "io.github.jwharm.javagi:adw", version.ref = "javagi" }
commons = { module = "io.gitlab.jfronny:commons", version.ref = "jf-commons" }
commons-http-client = { module = "io.gitlab.jfronny:commons-http-client", version.ref = "jf-commons" }
commons-http-server = { module = "io.gitlab.jfronny:commons-http-server", version.ref = "jf-commons" }
commons-io = { module = "io.gitlab.jfronny:commons-io", version.ref = "jf-commons" }
commons-logging = { module = "io.gitlab.jfronny:commons-logging", version.ref = "jf-commons" }
commons-serialize-gson = { module = "io.gitlab.jfronny:commons-serialize-gson", version.ref = "jf-commons" }
gson-compile-core = { module = "io.gitlab.jfronny.gson:gson-compile-core", version.ref = "gson-compile" }
gson-compile-annotations = { module = "io.gitlab.jfronny.gson:gson-compile-annotations", version.ref = "gson-compile" }
gson-compile-processor = { module = "io.gitlab.jfronny.gson:gson-compile-processor", version.ref = "gson-compile" }
annotations = { module = "org.jetbrains:annotations", version.ref = "annotations" }
[bundles]
lwjgl = ["lwjgl-core", "lwjgl-glfw", "lwjgl-opengl", "lwjgl-tinyfd"]
lwjgl-natives = ["lwjgl-core-natives", "lwjgl-glfw-natives", "lwjgl-opengl-natives", "lwjgl-tinyfd-natives"]
javagi = ["javagi-glib", "javagi-gtk", "javagi-adw"]
commons = ["commons", "commons-http-client", "commons-io", "commons-logging", "commons-serialize-gson"]

View File

@ -1,5 +1,5 @@
plugins {
id("inceptum.application-conventions")
inceptum.application
}
application {
@ -7,5 +7,5 @@ application {
}
dependencies {
implementation(project(":launcher"))
implementation(projects.launcher)
}

View File

@ -1,9 +1,10 @@
package io.gitlab.jfronny.inceptum.cli;
import io.gitlab.jfronny.commons.io.JFiles;
import io.gitlab.jfronny.inceptum.common.MetaHolder;
import io.gitlab.jfronny.inceptum.common.Utils;
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta;
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.GC_InstanceMeta;
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance;
import io.gitlab.jfronny.inceptum.launcher.system.instance.InstanceList;
import java.io.IOException;
import java.nio.file.Files;
@ -11,11 +12,7 @@ import java.nio.file.Path;
import java.util.List;
public abstract class BaseInstanceCommand extends Command {
public BaseInstanceCommand(String help, String usage, String... aliases) {
super(help, mutateUsage(usage), aliases);
}
public BaseInstanceCommand(String help, String usage, List<String> aliases, List<Command> subCommands) {
protected BaseInstanceCommand(String help, String usage, List<String> aliases, List<Command> subCommands) {
super(help, mutateUsage(usage), aliases, subCommands);
}
@ -31,31 +28,34 @@ public abstract class BaseInstanceCommand extends Command {
}
@Override
protected void invoke(CommandArgs args) {
protected void invoke(CommandArgs args) throws Exception {
if (args.length == 0) {
Utils.LOGGER.error("You must specify an instance to commit in");
return;
}
Path instancePath = MetaHolder.INSTANCE_DIR.resolve(args.get(0));
Path instanceMetaPath = instancePath.resolve("instance.json");
if (!Files.exists(instanceMetaPath)) {
Utils.LOGGER.error("Invalid instance: \"" + args.get(0) + "\"");
return;
}
InstanceMeta meta;
try {
meta = JFiles.readObject(instanceMetaPath, InstanceMeta.class);
} catch (IOException e) {
Utils.LOGGER.error("Could not read instance metadata", e);
return;
}
try {
invoke(args.subArgs(), instancePath, meta);
} catch (Exception e) {
Utils.LOGGER.error("Could not execute command", e);
return;
Instance instance;
Path normalPath = Path.of(args.get(0));
if (Files.exists(normalPath.resolve(Instance.CONFIG_NAME))) {
instance = new Instance(normalPath, GC_InstanceMeta.read(normalPath.resolve(Instance.CONFIG_NAME)));
} else {
Path instancePath = MetaHolder.INSTANCE_DIR.resolve(args.get(0)).normalize();
if (!instancePath.startsWith(MetaHolder.INSTANCE_DIR)) {
Utils.LOGGER.error("Specified instance path doesn't exist");
return;
}
if (!Files.exists(instancePath)) {
Utils.LOGGER.error("Invalid instance: \"" + args.get(0) + "\"");
return;
}
try {
instance = InstanceList.read(instancePath);
} catch (IOException e) {
Utils.LOGGER.error("Could not read instance metadata", e);
return;
}
}
invoke(args.subArgs(), instance);
}
protected abstract void invoke(CommandArgs args, Path instancePath, InstanceMeta meta) throws Exception;
protected abstract void invoke(CommandArgs args, Instance instance) throws Exception;
}

View File

@ -13,6 +13,7 @@ public class CliMain {
new LaunchCommand(),
new ListCommand(),
new ModCommand(),
new ImportCommand(),
new ExportCommand(),
new JvmStateCommand(),
new BatchCommand()
@ -46,6 +47,8 @@ public class CliMain {
try {
command.invoke();
} catch (Exception e) {
Utils.LOGGER.error("Could not execute command", e);
} finally {
LauncherEnv.terminate();
}

View File

@ -41,7 +41,7 @@ public class HelpBuilder {
builder.append(", ");
}
builder.append(": ").append(help.replace("\n", "\n " + indent));
if (level == 0 && !usage.isBlank() && !aliases.isEmpty()) {
if (level == 0 && !usage.isBlank()) {
StringBuilder usagePrefix = new StringBuilder("inceptum");
for (String s : upper) {
usagePrefix.append(" ").append(s);

View File

@ -1,13 +1,10 @@
package io.gitlab.jfronny.inceptum.cli.commands;
import io.gitlab.jfronny.inceptum.cli.*;
import io.gitlab.jfronny.inceptum.common.R;
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta;
import io.gitlab.jfronny.inceptum.launcher.system.export.Exporters;
import io.gitlab.jfronny.inceptum.launcher.system.mds.ModsDirScanner;
import io.gitlab.jfronny.inceptum.launcher.system.exporter.Exporters;
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance;
import io.gitlab.jfronny.inceptum.launcher.util.ProcessState;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
@ -25,14 +22,11 @@ public class ExportCommand extends BaseInstanceCommand {
}
@Override
protected void invoke(CommandArgs args, Path instancePath, InstanceMeta meta) throws Exception {
protected void invoke(CommandArgs args, Instance instance) throws Exception {
if (args.length == 0) throw new IllegalAccessException("You must specify a target path");
if (args.length == 1) throw new IllegalAccessException("You must specify a version number");
if (args.length != 2) throw new IllegalAccessException("Too many arguments");
ProcessState state = new ProcessState();
ModsDirScanner mds = ModsDirScanner.get(instancePath.resolve("mods"), meta);
mds.runOnce(R::nop);
Exporters.CURSE_FORGE.generate(state, instancePath, meta, mds, Paths.get(args.get(0)), args.get(1));
if (args.length > 2) throw new IllegalAccessException("Too many arguments");
if (args.length > 1) instance.meta().instanceVersion = args.get(1);
Exporters.CURSE_FORGE.generate(new ProcessState(), instance, Paths.get(args.get(0)));
}
private static class MultiMCExportCommand extends BaseInstanceCommand {
@ -41,13 +35,10 @@ public class ExportCommand extends BaseInstanceCommand {
}
@Override
protected void invoke(CommandArgs args, Path instancePath, InstanceMeta meta) throws Exception {
protected void invoke(CommandArgs args, Instance instance) throws Exception {
if (args.length == 0) throw new IllegalAccessException("You must specify a target path");
if (args.length != 1) throw new IllegalAccessException("Too many arguments");
ProcessState state = new ProcessState();
ModsDirScanner mds = ModsDirScanner.get(instancePath.resolve("mods"), meta);
mds.runOnce(R::nop);
Exporters.MULTI_MC.generate(state, instancePath, meta, mds, Paths.get(args.get(0)), "1.0");
Exporters.MULTI_MC.generate(new ProcessState(), instance, Paths.get(args.get(0)));
}
}
@ -57,14 +48,11 @@ public class ExportCommand extends BaseInstanceCommand {
}
@Override
protected void invoke(CommandArgs args, Path instancePath, InstanceMeta meta) throws Exception {
protected void invoke(CommandArgs args, Instance instance) throws Exception {
if (args.length == 0) throw new IllegalAccessException("You must specify a target path");
if (args.length == 1) throw new IllegalAccessException("You must specify a version number");
if (args.length != 2) throw new IllegalAccessException("Too many arguments");
ProcessState state = new ProcessState();
ModsDirScanner mds = ModsDirScanner.get(instancePath.resolve("mods"), meta);
mds.runOnce(R::nop);
Exporters.MODRINTH.generate(state, instancePath, meta, mds, Paths.get(args.get(0)), args.get(1));
if (args.length > 2) throw new IllegalAccessException("Too many arguments");
if (args.length > 1) instance.meta().instanceVersion = args.get(1);
Exporters.MODRINTH.generate(new ProcessState(), instance, Paths.get(args.get(0)));
}
}
}

View File

@ -0,0 +1,25 @@
package io.gitlab.jfronny.inceptum.cli.commands;
import io.gitlab.jfronny.commons.logging.OutputColors;
import io.gitlab.jfronny.inceptum.cli.Command;
import io.gitlab.jfronny.inceptum.cli.CommandArgs;
import io.gitlab.jfronny.inceptum.launcher.system.importer.Importers;
import io.gitlab.jfronny.inceptum.launcher.util.ProcessState;
import java.nio.file.Paths;
import java.util.List;
public class ImportCommand extends Command {
public ImportCommand() {
super("Import a CurseForge, Modrinth or MultiMC instance", "<pack file>", List.of("import"), List.of());
}
@Override
protected void invoke(CommandArgs args) throws Exception {
if (args.length == 0) throw new IllegalAccessException("You must specify a pack file");
if (args.length != 1) throw new IllegalAccessException("Too many arguments");
ProcessState state = new ProcessState();
String name = Importers.importPack(Paths.get(args.get(0)), state).path().getFileName().toString();
System.out.println(OutputColors.GREEN_BOLD + "Imported as " + name + OutputColors.RESET);
}
}

View File

@ -25,7 +25,6 @@ public class JvmStateCommand extends Command {
else
System.out.println("\t(cannot display components as not a URLClassLoader)");
if (loader.getParent() != null)
dumpClasspath(loader.getParent());
if (loader.getParent() != null) dumpClasspath(loader.getParent());
}
}

View File

@ -2,15 +2,16 @@ package io.gitlab.jfronny.inceptum.cli.commands;
import io.gitlab.jfronny.inceptum.cli.*;
import io.gitlab.jfronny.inceptum.common.Utils;
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta;
import io.gitlab.jfronny.inceptum.launcher.system.launch.InstanceLauncher;
import io.gitlab.jfronny.inceptum.launcher.util.InstanceLock;
import io.gitlab.jfronny.inceptum.launcher.api.account.AccountManager;
import io.gitlab.jfronny.inceptum.launcher.system.install.Steps;
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta;
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance;
import io.gitlab.jfronny.inceptum.launcher.system.launch.*;
import io.gitlab.jfronny.inceptum.launcher.system.setup.Steps;
import java.io.IOException;
import java.nio.file.Path;
import java.util.*;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class LaunchCommand extends BaseInstanceCommand {
private final boolean server;
@ -19,7 +20,7 @@ public class LaunchCommand extends BaseInstanceCommand {
public LaunchCommand() {
super("Launches an instance of the game (client by default). Non-blocking (batch commands will continue if this is ran)",
"[game arguments...]",
List.of("run", "launch", "start"),
List.of("run", "instance.launch", "start"),
List.of(
new LaunchCommand("Explicitly launch a client", "client", false, false),
new LaunchCommand("Launch a server", "server", true, false,
@ -37,29 +38,28 @@ public class LaunchCommand extends BaseInstanceCommand {
}
@Override
protected void invoke(CommandArgs args, Path instancePath, InstanceMeta meta) throws IOException, InstanceLauncher.LaunchException {
if (InstanceLock.isSetupLocked(instancePath)) {
protected void invoke(CommandArgs args, Instance instance) throws IOException, LaunchException {
if (instance.isSetupLocked()) {
Utils.LOGGER.error("This instance is still being set up");
return;
}
if (InstanceLock.isRunningLocked(instancePath)) {
if (instance.isRunningLocked()) {
Utils.LOGGER.error("This instance is already running");
return;
}
if (args.length > 1) {
if (meta.arguments == null) meta.arguments = new InstanceMeta.Arguments();
meta.arguments.client = meta.arguments.client == null ? new ArrayList<>() : new ArrayList<>(meta.arguments.client);
meta.arguments.server = meta.arguments.server == null ? new ArrayList<>() : new ArrayList<>(meta.arguments.server);
meta.arguments.jvm = meta.arguments.jvm == null ? new ArrayList<>() : new ArrayList<>(meta.arguments.jvm);
meta.arguments.client.addAll(args.after(0));
meta.arguments.server.addAll(args.after(0));
InstanceMeta meta = instance.meta();
meta.checkArguments();
meta.arguments = meta.arguments
.withClient(Stream.concat(meta.arguments.client().stream(), args.after(0).stream()).toList())
.withServer(Stream.concat(meta.arguments.server().stream(), args.after(0).stream()).toList());
}
Steps.reDownload(instancePath, Steps.createProcessState());
Steps.reDownload(instance, Steps.createProcessState());
if (server) {
InstanceLauncher.launch(instancePath, meta, InstanceLauncher.LaunchType.Server, restart, AccountManager.NULL_AUTH);
InstanceLauncher.launch(instance, LaunchType.Server, restart, AccountManager.NULL_AUTH);
} else {
AccountManager.loadAccounts();
InstanceLauncher.launchClient(instancePath, meta);
InstanceLauncher.launchClient(instance);
}
}
}

View File

@ -1,12 +1,12 @@
package io.gitlab.jfronny.inceptum.cli.commands;
import io.gitlab.jfronny.commons.io.JFiles;
import io.gitlab.jfronny.inceptum.common.MetaHolder;
import io.gitlab.jfronny.inceptum.common.Utils;
import io.gitlab.jfronny.inceptum.cli.Command;
import io.gitlab.jfronny.inceptum.cli.CommandArgs;
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta;
import io.gitlab.jfronny.inceptum.launcher.util.InstanceLock;
import io.gitlab.jfronny.inceptum.common.MetaHolder;
import io.gitlab.jfronny.inceptum.common.Utils;
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance;
import io.gitlab.jfronny.inceptum.launcher.system.instance.InstanceList;
import java.io.IOException;
import java.nio.file.Files;
@ -23,29 +23,29 @@ public class ListCommand extends Command {
List<Path> paths = JFiles.list(MetaHolder.INSTANCE_DIR);
if (paths.isEmpty()) System.out.println("No instances are currently present");
for (Path path : paths) {
if (!Files.exists(path.resolve("instance.json"))) {
if (!Files.exists(path.resolve(Instance.CONFIG_NAME))) {
System.out.println("- Invalid instance: " + path + " (no instance metadata)");
continue;
}
System.out.println("- \"" + path.getFileName().toString() + "\"");
if (InstanceLock.isSetupLocked(path)) {
System.out.println(" Status: Setting up");
continue;
}
InstanceMeta instance;
Instance instance;
try {
instance = JFiles.readObject(path.resolve("instance.json"), InstanceMeta.class);
instance = InstanceList.read(path);
} catch (IOException e) {
Utils.LOGGER.error(" Could not load instance.json", e);
continue;
}
System.out.println(" Status: " + (InstanceLock.isRunningLocked(path) ? "Running" : "Stopped"));
System.out.println(" Version: " + instance.getMinecraftVersion());
if (instance.isSetupLocked()) {
System.out.println(" Status: Setting up");
continue;
}
System.out.println(" Status: " + (instance.isRunningLocked() ? "Running" : "Stopped"));
System.out.println(" Version: " + instance.getGameVersion());
if (instance.isFabric()) System.out.println(" Fabric Loader: " + instance.getLoaderVersion());
if (instance.java != null) System.out.println(" Custom Java: " + instance.java);
if (instance.minMem != null || instance.maxMem != null)
System.out.println(" Memory:" + (instance.minMem != null ? " Minimum: " + instance.minMem : "")
+ (instance.maxMem != null ? " Maximum: " + instance.maxMem : ""));
if (instance.meta().java != null) System.out.println(" Custom Java: " + instance.meta().java);
if (instance.meta().minMem != null || instance.meta().maxMem != null)
System.out.println(" Memory:" + (instance.meta().minMem != null ? " Minimum: " + instance.meta().minMem : "")
+ (instance.meta().maxMem != null ? " Maximum: " + instance.meta().maxMem : ""));
}
}
}

View File

@ -4,12 +4,10 @@ import io.gitlab.jfronny.commons.io.JFiles;
import io.gitlab.jfronny.commons.throwable.ThrowingBiFunction;
import io.gitlab.jfronny.inceptum.cli.*;
import io.gitlab.jfronny.inceptum.common.Utils;
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta;
import io.gitlab.jfronny.inceptum.launcher.util.ModManager;
import io.gitlab.jfronny.inceptum.launcher.util.ModPath;
import io.gitlab.jfronny.inceptum.launcher.system.mds.IWModDescription;
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance;
import io.gitlab.jfronny.inceptum.launcher.system.instance.Mod;
import io.gitlab.jfronny.inceptum.launcher.system.mds.ModsDirScanner;
import io.gitlab.jfronny.inceptum.launcher.system.source.ModSource;
import io.gitlab.jfronny.inceptum.launcher.util.Unchecked;
import java.io.FileNotFoundException;
import java.io.IOException;
@ -47,16 +45,15 @@ public class ModCommand extends Command {
}
@Override
protected void invoke(CommandArgs args, Path instancePath, InstanceMeta meta) throws IOException {
if (!meta.isFabric()) {
protected void invoke(CommandArgs args, Instance instance) throws IOException {
if (!instance.isFabric()) {
System.err.println("This is not a fabric instance");
return;
}
System.out.println("Scanning installed mods, this might take a while");
ModsDirScanner mds = ModsDirScanner.get(instancePath.resolve("mods"), meta);
mds.runOnce((path, mod) -> {
boolean hasSources = mod.mod().isPresent() && !mod.mod().get().sources.isEmpty();
boolean updatable = hasSources && mod.mod().get().sources.values().stream().anyMatch(Optional::isPresent);
instance.mds().runOnce((path, mod) -> {
boolean hasSources = !mod.getMetadata().sources().isEmpty();
boolean updatable = hasSources && mod.getMetadata().sources().values().stream().anyMatch(Optional::isPresent);
if (filterUpdatable && !updatable) return;
System.out.println("- " + path.getFileName().toString());
System.out.println(" " + mod.getName());
@ -65,11 +62,10 @@ public class ModCommand extends Command {
}
if (hasSources) {
System.out.println(" Sources:");
for (Map.Entry<ModSource, Optional<ModSource>> entry : mod.mod().get().sources.entrySet()) {
for (var entry : mod.getMetadata().sources().entrySet()) {
System.out.println(" - " + entry.getKey().getName() + " (" + entry.getKey().getVersion() + ")");
System.out.println(" Local: " + entry.getKey().getJarPath().toString());
if (entry.getValue().isPresent())
System.out.println(" Updatable to: " + entry.getValue().get().getVersion());
if (entry.getValue().isPresent()) System.out.println(" Updatable to: " + entry.getValue().get().getVersion());
}
}
});
@ -91,8 +87,8 @@ public class ModCommand extends Command {
}
@Override
protected void invoke(CommandArgs args, Path instancePath, InstanceMeta meta) throws IOException {
if (!meta.isFabric()) {
protected void invoke(CommandArgs args, Instance instance) throws IOException {
if (!instance.isFabric()) {
Utils.LOGGER.error("This is not a fabric instance");
return;
}
@ -102,22 +98,22 @@ public class ModCommand extends Command {
}
Set<Path> mods = new HashSet<>();
for (String arg : args) {
Path p = instancePath.resolve("mods").resolve(arg);
if (!Files.exists(p)) p = instancePath.resolve("mods").resolve(arg + ".imod");
Path p = instance.getModsDir().resolve(arg);
if (!Files.exists(p)) p = instance.getModsDir().resolve(arg + ".imod");
if (!Files.exists(p)) {
Utils.LOGGER.error("Nonexistant mod file: " + instancePath.resolve("mods").resolve(arg));
Utils.LOGGER.error("Nonexistant mod file: " + instance.getModsDir().resolve(arg));
return;
}
mods.add(p);
}
ModsDirScanner mds = ModsDirScanner.get(instancePath.resolve("mods"), meta);
ModsDirScanner mds = instance.mds();
if (!ignoreDependencies && !mds.isComplete()) {
Utils.LOGGER.error("Scanning mods dir to search for dependencies. This might take a while");
mds.runOnce((path, mod) -> System.out.println("Scanned " + path));
}
for (Path mod : mods) {
try {
ModManager.delete(mds.get(mod), instancePath.resolve("mods"), mds);
mds.get(mod).delete();
} catch (IOException e) {
Utils.LOGGER.error("Could not delete " + mod, e);
return;
@ -152,50 +148,40 @@ public class ModCommand extends Command {
}
@Override
protected void invoke(CommandArgs args, Path instancePath, InstanceMeta meta) throws IOException {
if (!meta.isFabric()) {
protected void invoke(CommandArgs args, Instance instance) throws IOException {
if (!instance.isFabric()) {
throw new IOException("This is not a fabric instance");
}
if (args.length == 0) {
throw new IllegalArgumentException("You must specify mods to remove");
}
Set<Path> mods = pathSupplier.apply(args, instancePath);
ModsDirScanner mds = ModsDirScanner.get(instancePath.resolve("mods"), meta);
Set<Path> mods = pathSupplier.apply(args, instance.path());
ModsDirScanner mds = instance.mds();
if (!mds.isComplete()) {
Utils.LOGGER.error("Scanning mods dir to search for dependencies. This might take a while");
mds.runOnce((path, mod) -> System.out.println("Scanned " + path));
}
for (Path mod : mods) {
try {
Utils.LOGGER.info("Updating " + mod);
ModManager.delete(mds.get(mod), instancePath.resolve("mods"), mds);
IWModDescription md = mds.get(mod);
if (md.mod().isEmpty()) {
throw new IOException("Could not load mod description");
}
boolean found = false;
for (Map.Entry<ModSource, Optional<ModSource>> source : md.mod().get().sources.entrySet()) {
Optional<ModSource> ms = source.getValue();
if (ms.isPresent()) {
try {
Utils.LOGGER.info("Updating to " + ms.get().getVersion());
Path imodPath = md.imod().isPresent() ? md.imod().get() : instancePath.resolve("mods").resolve(ms.get().getShortName() + ModPath.EXT_IMOD);
ModManager.DownloadMeta dm = ModManager.download(ms.get(), imodPath, mds);
Files.delete(md.path());
if (md.imod().isPresent() && Files.exists(md.imod().get()))
Files.delete(md.imod().get());
dm.write();
mds.invalidate(imodPath);
Utils.LOGGER.info("Update completed");
found = true;
} catch (IOException e) {
throw new IOException("Update failed", e);
}
}
}
if (!found) Utils.LOGGER.error("Could not find any update");
Mod md = mds.get(mod);
md.delete();
md.getMetadata().sources().values().stream()
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst()
.ifPresentOrElse(update -> {
try {
Utils.LOGGER.info("Updating " + mod + " to " + update.getVersion());
md.update(update);
Utils.LOGGER.info("Update completed");
} catch (IOException e) {
throw new Unchecked(e);
}
}, () -> Utils.LOGGER.error("Could not find any update for " + mod));
} catch (IOException e) {
throw new IOException("Could not delete " + mod, e);
} catch (Unchecked e) {
throw new IOException("Could not delete " + mod, e.exception);
}
}
}

View File

@ -0,0 +1,6 @@
module io.gitlab.jfronny.inceptum.launcher.cli {
exports io.gitlab.jfronny.inceptum.cli;
requires transitive io.gitlab.jfronny.inceptum.launcher;
requires static org.jetbrains.annotations;
}

View File

@ -1,15 +1,22 @@
import io.gitlab.jfronny.scripts.*
import java.nio.file.Files
import java.nio.file.Path
plugins {
id("inceptum.application-standalone-conventions")
inceptum.`application-standalone`
org.beryx.jlink
}
application {
mainClass.set("io.gitlab.jfronny.inceptum.Inceptum")
mainModule.set("io.gitlab.jfronny.inceptum.launcher.dist")
applicationName = "Inceptum"
}
dependencies {
implementation(project(":launcher"))
implementation(project(":launcher-cli"))
implementation(project(":launcher-imgui"))
implementation(projects.launcher)
implementation(projects.launcherCli)
implementation(projects.launcherImgui)
}
tasks.shadowJar {
@ -26,17 +33,103 @@ tasks.shadowJar {
publishing {
publications {
if (rootProject.hasProperty("dist.platformOnly")) {
create<MavenPublication>("shadowed") {
artifact(tasks.shadowJar) {
builtBy(tasks.shadowJar)
artifactId = "Inceptum"
}
create<MavenPublication>("mavenJava") {
from(components["java"])
}
}
}
val flavor: String by rootProject.extra
val os = org.gradle.internal.os.OperatingSystem.current()!!
val crosscompile = flavor == "windows" && os.isLinux && project.hasProperty("crosscompile")
val verifyFlavorConfiguration by tasks.registering {
when (flavor) {
"macos" -> if (!os.isMacOsX) throw IllegalArgumentException("Cross-compilation to MacOS is unsupported!")
"windows" -> if (!os.isWindows && !crosscompile) {
if (os.isLinux) throw IllegalArgumentException("Cross-compilation to Windows is not enabled, please do so to build this")
else throw IllegalArgumentException("Cross-compilation to Windows from this platform is unsupported!")
}
"linux" -> if (!os.isLinux) throw IllegalArgumentException("Cross-compilation to Linux is unsupported!")
else -> throw IllegalArgumentException("Unexpected flavor: $flavor")
}
}
tasks.prepareMergedJarsDir { dependsOn(verifyFlavorConfiguration) }
tasks.createMergedModule { dependsOn(verifyFlavorConfiguration) }
tasks.createDelegatingModules { dependsOn(verifyFlavorConfiguration) }
tasks.prepareModulesDir { dependsOn(verifyFlavorConfiguration) }
tasks.jlink { dependsOn(verifyFlavorConfiguration) }
tasks.jlinkZip { dependsOn(verifyFlavorConfiguration) }
tasks.suggestMergedModuleInfo { dependsOn(verifyFlavorConfiguration) }
tasks.jpackageImage { dependsOn(verifyFlavorConfiguration) }
tasks.jpackage { dependsOn(verifyFlavorConfiguration) }
if (crosscompile) System.setProperty("badass.jlink.jpackage.home", "/root/jpackage-win")
jlink {
if (crosscompile) javaHome.set("/root/jpackage-win")
addOptions(
"--strip-debug",
"--compress", "2",
"--no-header-files",
"--no-man-pages",
"--verbose"
)
launcher {
name = application.applicationName
}
if (crosscompile) targetPlatform("win", "/root/java")
jpackage {
imageName = application.applicationName
if (crosscompile) {
outputDir = "/root/jpackage-out"
imageOutputDir = File(outputDir)
installerOutputDir = File(outputDir)
}
appVersion = versionStripped
// vendor
when (flavor) {
"macos" -> {
installerType = "app-image"
installerOptions.addAll(listOf(
"--mac-package-name", "inceptum"
))
}
} else {
create<MavenPublication>("mavenJava") {
from(components["java"])
"windows" -> {
installerType = "msi"
installerOptions.addAll(listOf(
"--win-per-user-install",
"--win-dir-chooser",
"--win-menu",
"--win-upgrade-uuid", "180becd8-a867-40d4-86ef-20949cae68b5" // Update this UUID if you fork the project!!!
))
//imageOptions.add("--win-console") // Enable this for debugging
}
else -> {
// might also be able to push rpm with appropriate image
installerType = "deb"
installerOptions.addAll(listOf(
"--linux-package-name", "inceptum",
"--linux-shortcut"
))
}
}
}
}
}
if (crosscompile) {
tasks.jpackage {
doLast {
val src = Path.of("/root/jpackage-out")
val trg = layout.buildDirectory.dir("jpackage").get().asFile.toPath()
Files.createDirectories(trg)
Files.list(src).use {
it.filter { Files.isRegularFile(it) }.forEach {
val t = trg.resolve(it.fileName.toString())
println("Moving $it to $t")
Files.move(it, t)
}
}
}
}
}

View File

@ -14,7 +14,7 @@ public class GuiCommand extends Command {
@Override
public void invoke(CommandArgs args) {
LauncherEnv.updateBackend(new GuiEnvBackend());
GuiMain.main();
GuiMain.showGui();
}
@Override

View File

@ -30,19 +30,25 @@ public class UpdateCheckCommand extends Command {
Utils.LOGGER.error("Automatic updates are not supported without the wrapper");
return;
}
UpdateMetadata updateUrl = BuildMetadata.IS_PUBLIC ? Updater.getUpdate() : null;
if (updateUrl == null) {
UpdateMetadata update;
try {
update = BuildMetadata.IS_PUBLIC ? Updater.getUpdate(true, true) : null;
} catch (Updater.UpdateCheckException e) {
Utils.LOGGER.error("Latest update is not compatible: " + e.message);
return;
}
if (update == null) {
Utils.LOGGER.info("No update was found");
} else {
if (install) {
Utils.LOGGER.info("Installing from " + updateUrl);
Utils.LOGGER.info("Installing from " + update);
try {
Updater.update(updateUrl, false);
Updater.update(update, false);
} catch (IOException | URISyntaxException e) {
Utils.LOGGER.error("Could not download update", e);
}
} else {
Utils.LOGGER.info("An update was found: " + updateUrl);
Utils.LOGGER.info("An update was found: " + update);
}
}
}

View File

@ -0,0 +1,4 @@
module io.gitlab.jfronny.inceptum.launcher.dist {
requires io.gitlab.jfronny.inceptum.launcher.imgui;
requires io.gitlab.jfronny.inceptum.launcher.cli;
}

View File

@ -0,0 +1,49 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
inceptum.application
com.github.johnrengelman.shadow
kotlin("jvm") version libs.versions.kotlin
kotlin("plugin.sam.with.receiver") version libs.versions.kotlin
}
application {
mainClass.set("io.gitlab.jfronny.inceptum.gtk.GtkMain")
}
samWithReceiver {
annotation("io.gitlab.jfronny.commons.SamWithReceiver")
}
dependencies {
val javagiVersion: String by rootProject.extra
implementation(libs.bundles.javagi)
implementation(projects.launcher)
}
tasks.compileJava {
options.compilerArgs.add("--enable-preview")
}
tasks.runShadow {
if (project.hasProperty("showcase")) {
environment("GTK_THEME", "Adwaita")
environment("GDK_BACKEND", "broadway")
environment("BROADWAY_DISPLAY", ":5")
var proc: Process? = null
doFirst {
proc = Runtime.getRuntime().exec(arrayOf("gtk4-broadwayd", ":5"))
Runtime.getRuntime().exec(arrayOf("xdg-open", "http://127.0.0.1:8085"))
Thread.sleep(1000)
}
doLast {
if (proc != null) Runtime.getRuntime().exec(arrayOf("kill", proc!!.pid().toString()))
}
}
workingDir = rootProject.projectDir
environment("GTK_DEBUG", "interactive") // interactive:actions
jvmArgs("--enable-preview", "--enable-native-access=ALL-UNNAMED")
}
tasks.withType(KotlinCompile::class) { compilerOptions.freeCompilerArgs.addAll("-Xlambdas=indy") }

View File

@ -0,0 +1,117 @@
package io.gitlab.jfronny.inceptum.gtk
import io.gitlab.jfronny.commons.StringFormatter
import io.gitlab.jfronny.inceptum.gtk.util.I18n
import io.gitlab.jfronny.inceptum.gtk.util.Log
import io.gitlab.jfronny.inceptum.gtk.window.dialog.MicrosoftLoginDialog
import io.gitlab.jfronny.inceptum.gtk.window.dialog.StringInputDialog
import io.gitlab.jfronny.inceptum.launcher.LauncherEnv.EnvBackend
import io.gitlab.jfronny.inceptum.launcher.api.account.MicrosoftAccount
import org.gnome.gio.Cancellable
import org.gnome.gtk.*
import java.util.function.Consumer
object GtkEnvBackend : EnvBackend {
var dialogParent: Window? = null
override fun showError(message: String, title: String) {
Log.error(message)
simpleDialog(message, title, null, null)
}
override fun showError(message: String, t: Throwable) {
simpleDialog(StringFormatter.toString(t), message, null, null)
}
override fun showInfo(message: String, title: String) {
Log.info(message)
simpleDialog(message, title, null, null)
}
override fun showOkCancel(message: String, title: String, ok: Runnable, cancel: Runnable, defaultCancel: Boolean) {
Log.info(message)
simpleDialog(message, title, ok, cancel)
}
override fun getInput(
prompt: String,
details: String,
defaultValue: String,
ok: Consumer<String>,
cancel: Runnable
) = schedule {
var flags = DialogFlags.DESTROY_WITH_PARENT
if (dialogParent != null) flags = flags.or(DialogFlags.MODAL)
val dialog = StringInputDialog(
dialogParent,
flags,
MessageType.QUESTION,
ButtonsType.OK_CANCEL,
details,
defaultValue
)
dialog.title = prompt
dialog.onResponse(processResponses(dialog, { ok.accept(dialog.input) }, cancel))
dialog.visible = true
}
override fun showLoginRefreshPrompt(account: MicrosoftAccount) =
schedule { MicrosoftLoginDialog(dialogParent, account).visible = true }
private fun simpleDialog(
markup: String,
title: String,
ok: Runnable?,
cancel: Runnable?
) = schedule { simpleDialog(dialogParent, markup, title, ok, cancel) }
fun simpleDialog(
parent: Window?,
markup: String,
title: String,
ok: Runnable?,
cancel: Runnable?
) {
val dialog = AlertDialog.builder()
.setMessage(title)
.setDetail(markup)
.setModal(true)
when {
cancel == null -> dialog.setButtons(arrayOf(I18n["ok"]))
.setDefaultButton(0)
.setCancelButton(-1)
ok == null -> dialog.setButtons(arrayOf("Cancel"))
.setDefaultButton(-1)
.setCancelButton(0)
else -> dialog.setButtons(arrayOf("OK", "Cancel"))
.setDefaultButton(0)
.setCancelButton(1)
}
dialog.build().apply {
choose(parent, Cancellable()) { _, res, _ ->
val result = chooseFinish(res)
val cancelIdx = cancelButton
val defaultIdx = defaultButton
if (result == cancelIdx) cancel?.run()
if (result == defaultIdx) ok?.run()
}
}
}
private fun processResponses(dialog: Dialog, ok: Runnable?, cancel: Runnable?): Dialog.ResponseCallback = Dialog.ResponseCallback { responseId: Int ->
when (ResponseType.of(responseId)) {
ResponseType.OK -> {
dialog.close()
ok?.run()
}
ResponseType.CLOSE, ResponseType.CANCEL -> {
dialog.close()
cancel?.run()
}
ResponseType.DELETE_EVENT -> dialog.destroy()
else -> Log.error("Unexpected response type: $responseId")
}
}
}

View File

@ -0,0 +1,58 @@
package io.gitlab.jfronny.inceptum.gtk
import io.gitlab.jfronny.inceptum.common.BuildMetadata
import io.gitlab.jfronny.inceptum.common.MetaHolder
import io.gitlab.jfronny.inceptum.gtk.util.Log
import io.gitlab.jfronny.inceptum.gtk.window.MainWindow
import io.gitlab.jfronny.inceptum.launcher.LauncherEnv
import io.gitlab.jfronny.inceptum.launcher.api.account.AccountManager
import org.gnome.gio.ApplicationFlags
import org.gnome.glib.GLib
import org.gnome.gtk.*
import java.io.IOException
import java.util.*
import kotlin.system.exitProcess
object GtkMain {
const val ID = "io.gitlab.jfronny.inceptum"
@Throws(IOException::class)
@JvmStatic
fun main(args: Array<String>) {
LauncherEnv.initialize(GtkEnvBackend)
Log.info("Launching Inceptum v" + BuildMetadata.VERSION)
Log.info("Loading from " + MetaHolder.BASE_PATH)
exitProcess(try {
showGui(args)
} catch (_: Throwable) {
-1
} finally {
LauncherEnv.terminate()
})
}
fun showGui(args: Array<String>): Int = setupApplication(args) {
//TODO update check
AccountManager.loadAccounts()
GtkMenubar.create(this)
val window = MainWindow(this)
window.visible = true
GtkEnvBackend.dialogParent = window
window.onCloseRequest {
GtkEnvBackend.dialogParent = null
this.quit()
false
}
}
fun setupApplication(args: Array<String>, onActivate: Application.() -> Unit): Int {
val app = Application(ID, ApplicationFlags.FLAGS_NONE)
app.onActivate {
GLib.idleAdd(GLib.PRIORITY_DEFAULT_IDLE) {
runScheduledTasks()
true
}
onActivate(app)
}
return app.run(args)
}
}

View File

@ -0,0 +1,215 @@
package io.gitlab.jfronny.inceptum.gtk
import io.gitlab.jfronny.commons.OSUtils
import io.gitlab.jfronny.commons.io.JFiles
import io.gitlab.jfronny.inceptum.common.MetaHolder
import io.gitlab.jfronny.inceptum.gtk.menu.MenuBuilder
import io.gitlab.jfronny.inceptum.gtk.util.I18n
import io.gitlab.jfronny.inceptum.gtk.util.Log
import io.gitlab.jfronny.inceptum.gtk.window.AboutWindow
import io.gitlab.jfronny.inceptum.gtk.window.create.NewInstanceWindow
import io.gitlab.jfronny.inceptum.gtk.window.dialog.MicrosoftLoginDialog
import io.gitlab.jfronny.inceptum.gtk.window.dialog.ProcessStateWatcherDialog
import io.gitlab.jfronny.inceptum.gtk.window.settings.launcher.LauncherSettingsWindow
import io.gitlab.jfronny.inceptum.launcher.LauncherEnv
import io.gitlab.jfronny.inceptum.launcher.api.account.AccountManager
import io.gitlab.jfronny.inceptum.launcher.api.account.MicrosoftAccount
import io.gitlab.jfronny.inceptum.launcher.system.importer.Importers
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance
import io.gitlab.jfronny.inceptum.launcher.system.instance.InstanceList
import io.gitlab.jfronny.inceptum.launcher.system.launch.InstanceLauncher
import io.gitlab.jfronny.inceptum.launcher.system.launch.LaunchType
import io.gitlab.jfronny.inceptum.launcher.system.setup.Steps
import io.gitlab.jfronny.inceptum.launcher.util.ProcessState
import org.gnome.gio.Cancellable
import org.gnome.gio.Menu
import org.gnome.gtk.*
import java.awt.Toolkit
import java.awt.datatransfer.DataFlavor
import java.io.IOException
import java.nio.file.Path
object GtkMenubar {
var newMenu: MenuBuilder? = null
var accountsMenu: MenuBuilder? = null
var launchMenu: MenuBuilder? = null
fun create(app: Application) {
val menu = MenuBuilder(app, Menu()) // this should be MenuBuilder(app), but that breaks this
val file = menu.submenu("file")
newMenu = file.submenu("new")
generateNewMenu(app)
file.button("redownload") {
val state = ProcessState(3 + Steps.STEPS.size * InstanceList.size(), "Initializing")
ProcessStateWatcherDialog.show(
GtkEnvBackend.dialogParent,
"Reloading data",
"Could not execute refresh task",
state
) {
state.incrementStep("Clearing cache directories")
JFiles.clearDirectory(MetaHolder.ASSETS_DIR)
JFiles.clearDirectory(MetaHolder.LIBRARIES_DIR) { path: Path -> !path.startsWith(MetaHolder.LIBRARIES_DIR.resolve("io/gitlab/jfronny")) }
JFiles.clearDirectory(MetaHolder.NATIVES_DIR) { path: Path -> !path.startsWith(MetaHolder.NATIVES_DIR.resolve("forceload")) }
JFiles.clearDirectory(MetaHolder.CACHE_DIR)
if (state.isCancelled) return@show
state.incrementStep("Reloading instance list")
InstanceList.reset()
InstanceList.forEach<IOException> { instance: Instance? ->
if (state.isCancelled) return@forEach
Steps.reDownload(instance, state)
}
}
}
file.button("exit") { app.quit() }
launchMenu = menu.submenu("launch")
generateLaunchMenu(app)
accountsMenu = menu.submenu("account")
generateAccountsMenu(app)
val help = menu.submenu("help")
help.button("about") { AboutWindow.createAndShow() }
help.button("log") {
//TODO
}
}
fun generateNewMenu(app: Application) {
newMenu!!.clear()
newMenu!!.button("new") { NewInstanceWindow(app).visible = true }
newMenu!!.button("file") {
val dialog = FileChooserNative(
I18n["menu.file.new.file"],
GtkEnvBackend.dialogParent,
FileChooserAction.OPEN,
"_" + I18n["select"],
"_" + I18n["cancel"]
)
val filter = FileFilter()
filter.addPattern("*.zip")
filter.addPattern("*.mrpack")
dialog.addFilter(filter)
dialog.onResponse { responseId: Int ->
if (responseId == ResponseType.ACCEPT.value) {
val file = dialog.file!!.path
if (file == null) {
LauncherEnv.showError("The path returned by the file dialog is null", "Could not import")
return@onResponse
}
val state = ProcessState(Importers.MAX_STEPS, "Initializing")
ProcessStateWatcherDialog.show(
GtkEnvBackend.dialogParent,
I18n["menu.file.new.file"],
I18n["menu.file.new.file.error"],
state
) {
Importers.importPack(Path.of(file), state)
}
}
}
dialog.show()
}
newMenu!!.button("url") {
readClipboard { clipboard ->
LauncherEnv.getInput(
I18n["menu.file.new.url"],
I18n["menu.file.new.url.details"],
clipboard ?: "",
{ s: String? ->
val state = ProcessState(Importers.MAX_STEPS, "Initializing")
ProcessStateWatcherDialog.show(
GtkEnvBackend.dialogParent,
I18n["menu.file.new.url"],
I18n["menu.file.new.url.error"],
state
) {
Importers.importPack(s, state)
}
}, {})
}
}
}
private fun readClipboard(continuation: (String?) -> Unit) {
if (OSUtils.TYPE == OSUtils.Type.LINUX) {
val clipboard = GtkEnvBackend.dialogParent!!.display.clipboard
clipboard.readTextAsync(Cancellable()) { _, res, _ ->
continuation(clipboard.readTextFinish(res))
}
} else {
continuation(Toolkit.getDefaultToolkit().systemClipboard.getData(DataFlavor.stringFlavor) as String?)
}
}
fun generateLaunchMenu(app: Application) {
launchMenu!!.clear()
try {
InstanceList.forEach<RuntimeException> { entry: Instance ->
launchMenu!!.literalButton(entry.id + ".launch", entry.toString()) {
launch(entry, LaunchType.Client)
}
}
} catch (e: IOException) {
Log.error("Could not generate launch menu", e)
}
}
fun launch(instance: Instance, launchType: LaunchType) {
if (instance.isSetupLocked) {
LauncherEnv.showError(I18n["instance.launch.locked.setup"], I18n["instance.launch.locked"])
} else if (instance.isRunningLocked) {
LauncherEnv.showOkCancel(
I18n["instance.launch.locked.running"],
I18n["instance.launch.locked"]
) { forceLaunch(instance, launchType) }
} else forceLaunch(instance, launchType)
}
private fun forceLaunch(instance: Instance, launchType: LaunchType) {
val state = Steps.createProcessState()
ProcessStateWatcherDialog.show(
GtkEnvBackend.dialogParent,
I18n["instance.launch.title"],
I18n["instance.launch.error"],
state
) {
try {
Steps.reDownload(instance, state)
} catch (e: IOException) {
Log.error("Could not fetch instance, trying to start anyways", e)
}
if (state.isCancelled) return@show
state.updateStep("Starting Game")
try {
if (launchType == LaunchType.Client) InstanceLauncher.launchClient(instance)
else InstanceLauncher.launch(
instance,
launchType,
false,
AccountManager.NULL_AUTH
)
} catch (e: Throwable) {
LauncherEnv.showError("Could not start instance", e)
}
}
}
fun generateAccountsMenu(app: Application) {
accountsMenu!!.clear()
accountsMenu!!.button("new") { MicrosoftLoginDialog(GtkEnvBackend.dialogParent).visible = true }
accountsMenu!!.button("manage") {
val window = LauncherSettingsWindow(app)
window.activePage = "settings.accounts"
window.visible = true
}
val accounts: MutableList<MicrosoftAccount?> = ArrayList(AccountManager.getAccounts())
accounts.add(null)
accountsMenu!!.literalRadio(
"account",
accounts[AccountManager.getSelectedIndex()],
accounts,
{ _, acc: MicrosoftAccount? ->
if (acc == null) return@literalRadio I18n["account.none"]
acc.minecraftUsername
}) { account: MicrosoftAccount? -> AccountManager.switchAccount(account) }
}
}

View File

@ -0,0 +1,21 @@
package io.gitlab.jfronny.inceptum.gtk
import io.gitlab.jfronny.inceptum.gtk.util.Log
import java.util.*
private val SCHEDULED: Queue<Runnable> = ArrayDeque()
fun schedule(task: Runnable) {
SCHEDULED.add(task)
}
fun runScheduledTasks() {
var r: Runnable?
while (SCHEDULED.poll().also { r = it } != null) {
try {
r!!.run()
} catch (t: Throwable) {
Log.error("Could not run scheduled task", t)
}
}
}

View File

@ -0,0 +1,52 @@
package io.gitlab.jfronny.inceptum.gtk.control
import io.gitlab.jfronny.inceptum.gtk.GtkMain
import io.gitlab.jfronny.inceptum.gtk.util.I18n
import org.gnome.gtk.CssProvider
import org.gnome.gtk.Gtk
import org.gnome.gtk.Label
import org.jetbrains.annotations.PropertyKey
class ILabel(
str: @PropertyKey(resourceBundle = I18n.BUNDLE) String,
mode: Mode,
vararg args: Any?
) : Label(I18n.get(str, *args)) {
constructor(str: @PropertyKey(resourceBundle = I18n.BUNDLE) String, vararg args: Any?) : this(str, Mode.NORMAL, *args)
init {
theme(this, mode)
}
enum class Mode {
NORMAL,
HEADING,
SUBTITLE
}
companion object {
private val provider by lazy {
val provider = CssProvider()
try {
GtkMain::class.java.classLoader.getResourceAsStream("inceptum.css")!!
.use {
val bytes = it.readAllBytes()
provider.loadFromData(String(bytes), bytes.size.toLong())
}
} catch (t: Throwable) {
throw RuntimeException(t)
}
provider
}
fun theme(label: Label, mode: Mode) = when (mode) {
Mode.HEADING -> label.addCssClass("heading")
Mode.SUBTITLE -> {
label.addCssClass("jf-subtitle")
label.styleContext.addProvider(provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
}
Mode.NORMAL -> {}
}
}
}

View File

@ -0,0 +1,77 @@
package io.gitlab.jfronny.inceptum.gtk.control
import io.gitlab.jfronny.inceptum.gtk.util.get
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance
import org.gnome.gtk.*
import org.gnome.pango.EllipsizeMode
import org.gnome.pango.WrapMode
class InstanceGridEntryFactory(
private val instanceList: List<Instance>
) : KSignalListItemFactory<InstanceGridEntryFactory.Decomposed, Box>() {
//TODO better design
override fun setup(): Box {
val box = Box(Orientation.VERTICAL, 5)
val thumbnail = InstanceThumbnail()
box.append(thumbnail)
val label = Label(null as String?)
label.setSizeRequest(192, -1)
label.maxWidthChars = 20
label.justify = Justification.CENTER
label.halign = Align.START
label.hexpand = true
label.valign = Align.CENTER
label.ellipsize = EllipsizeMode.MIDDLE
label.lines = 3
label.wrap = true
label.wrapMode = WrapMode.WORD_CHAR
label.marginTop = 10
box.append(label)
// Label label = new Label(Str.NULL);
// label.setXalign(0);
// label.setWidthChars(20);
// label.setMarginEnd(10);
// box.append(label);
//
// Button launch = new Button();
// launch.setIconName(new Str("computer-symbolic"));
// launch.setTooltipText(I18n.str("instance.launch"));
// launch.setHasTooltip(GTK.TRUE);
// box.append(launch);
//
// Button openDir = new Button();
// openDir.setIconName(new Str("folder-symbolic"));
// openDir.setTooltipText(I18n.str("instance.directory"));
// openDir.setHasTooltip(GTK.TRUE);
// box.append(openDir);
//TODO server launch with network-server-symbolic
//TODO kill current instance
return box
}
override fun BindContext.bind(widget: Box, data: Decomposed) {
// Label label = new Label(item.getChild().getFirstChild().cast());
// Button launch = new Button(label.getNextSibling().cast());
// Button openDir = new Button(launch.getNextSibling().cast());
// InstanceList.Entry instance = instanceList.get(ListIndex.toIndex(item));
// label.setText(new Str(instance.toString()));
// launch.onClicked(() -> GtkMenubar.launch(instance));
// openDir.onClicked(() -> Utils.openFile(instance.path().toFile()));
val thumbnail = InstanceThumbnail.castFrom(widget.firstChild as Stack)
val label = thumbnail.nextSibling as Label
val instance = instanceList[data.id]
thumbnail.bind(instance!!)
label.text = instance.toString()
}
override fun UnbindContext.unbind(widget: Box, data: Decomposed) {
}
override fun ActionContext.castWidget(widget: Widget): Box = widget as Box
override fun ActionContext.lookup(id: String, widget: Box): Decomposed = Decomposed(id)
class Decomposed(val id: String)
}

View File

@ -0,0 +1,151 @@
package io.gitlab.jfronny.inceptum.gtk.control
import io.gitlab.jfronny.commons.io.JFiles
import io.gitlab.jfronny.commons.ref.R
import io.gitlab.jfronny.inceptum.common.MetaHolder
import io.gitlab.jfronny.inceptum.common.Utils
import io.gitlab.jfronny.inceptum.gtk.GtkMenubar
import io.gitlab.jfronny.inceptum.gtk.menu.MenuBuilder
import io.gitlab.jfronny.inceptum.gtk.util.I18n
import io.gitlab.jfronny.inceptum.gtk.util.fixSubtitle
import io.gitlab.jfronny.inceptum.gtk.util.get
import io.gitlab.jfronny.inceptum.gtk.util.margin
import io.gitlab.jfronny.inceptum.gtk.window.settings.instance.InstanceSettingsWindow
import io.gitlab.jfronny.inceptum.launcher.LauncherEnv
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance
import io.gitlab.jfronny.inceptum.launcher.system.instance.InstanceNameTool
import io.gitlab.jfronny.inceptum.launcher.system.launch.LaunchType
import org.gnome.adw.ActionRow
import org.gnome.gdk.Gdk
import org.gnome.gio.Menu
import org.gnome.gtk.*
import java.io.IOException
class InstanceListEntryFactory(
private val app: Application?,
private val instanceList: List<Instance>
) : KSignalListItemFactory<InstanceListEntryFactory.Decomposed, ActionRow>() {
override fun setup(): ActionRow {
val thumbnail = InstanceThumbnail()
thumbnail.name = "inceptum-thumbnail"
val launch = Button.fromIconName("media-playback-start-symbolic")
launch.addCssClass("flat")
launch.name = "inceptum-launch"
launch.tooltipText = I18n["instance.launch"]
val menu = MenuButton()
menu.addCssClass("flat")
menu.iconName = "view-more-symbolic"
menu.setPopover(PopoverMenu.fromModel(Menu()))
val row = ActionRow()
row.margin = 8
row.name = "inceptum-row"
row.removeCssClass("activatable") //TODO remove this workaround if a better way to support opening the menu is found
row.addPrefix(thumbnail)
row.addSuffix(launch)
row.addSuffix(menu)
row.fixSubtitle()
val rightClicked = GestureClick()
rightClicked.button = Gdk.BUTTON_SECONDARY
rightClicked.onPressed { nPress, _, _ -> if (nPress == 1) menu.emitActivate() }
row.addController(rightClicked)
return row
}
override fun BindContext.bind(widget: ActionRow, data: Decomposed) {
if (data.instance?.isLocked ?: true) {
data.item.activatable = false
widget.subtitle = if (data.instance?.isRunningLocked ?: false) I18n["instance.launch.locked.running"]
else I18n["instance.launch.locked.setup"]
}
widget.title = data.instance.toString()
data.thumbnail.bind(data.instance!!)
val menuBuilder = MenuBuilder(data.popoverMenu, data.instanceId)
val launchSection = menuBuilder.literalSection("launch", null)
val kill = launchSection.literalButton("kill", I18n["instance.kill"]) {
//TODO test
LauncherEnv.showOkCancel(I18n["instance.kill.prompt"], I18n["instance.kill.details"]) {
if (!data.instance.kill()) LauncherEnv.showError(I18n["instance.kill.fail"], I18n["failed"])
}
}
kill.enabled = data.instance.isRunningLocked
launchSection.literalButton(
"launch.client", I18n["instance.launch.client"]
) { GtkMenubar.launch(data.instance, LaunchType.Client) }.iconName = "media-playback-start-symbolic"
launchSection.literalButton(
"launch.server", I18n["instance.launch.server"]
) { GtkMenubar.launch(data.instance, LaunchType.Server) }.iconName = "network-server-symbolic"
val settingsSection = menuBuilder.literalSection("settings", null)
settingsSection.literalButton("settings", I18n["instance.settings"]) {
//TODO keep track of properties windows and don't allow opening two
InstanceSettingsWindow(app, data.instance).visible = true
}.iconName = "document-edit-symbolic"
settingsSection.literalButton(
"directory", I18n["instance.directory"]
) { Utils.openFile(data.instance.path.toFile()) }.iconName = "folder-symbolic"
settingsSection.literalButton("copy", I18n["instance.copy"]) {
LauncherEnv.getInput(
I18n["instance.copy.prompt"],
I18n["instance.copy.details"],
InstanceNameTool.getNextValid(data.instance.name),
{ s: String? ->
try {
JFiles.copyRecursive(
data.instance.path,
MetaHolder.INSTANCE_DIR.resolve(InstanceNameTool.getNextValid(s))
)
} catch (e: IOException) {
LauncherEnv.showError(I18n["instance.copy.fail"], e)
}
}) { R.nop() }
}.iconName = "edit-copy-symbolic"
settingsSection.literalButton("delete", I18n["instance.delete"]) {
LauncherEnv.showOkCancel(
I18n["instance.delete.confirm"],
I18n["instance.delete.confirm.title"]
) {
try {
JFiles.deleteRecursive(data.instance.path)
} catch (e: IOException) {
LauncherEnv.showError(I18n["instance.delete.fail"], e)
}
}
}.iconName = "edit-delete-symbolic"
registerForUnbind(data.launch.onClicked { GtkMenubar.launch(data.instance, LaunchType.Client) })
}
override fun UnbindContext.unbind(widget: ActionRow, data: Decomposed) {
data.popoverMenu.insertActionGroup(data.instanceId, null)
}
override fun ActionContext.castWidget(widget: Widget): ActionRow = widget as ActionRow
override fun ActionContext.lookup(id: String, widget: ActionRow): Decomposed {
val instance = instanceList[id]
val prefixes = widget.firstChild!!.firstChild as Box
val suffixes = widget.firstChild!!.lastChild as Box
val thumbnail = InstanceThumbnail.castFrom(prefixes.firstChild as Stack)
val launch = suffixes.firstChild as Button
val menuButton = launch.nextSibling as MenuButton
val popoverMenu = menuButton.popover as PopoverMenu
return Decomposed(listItem, id, instance, thumbnail, launch, popoverMenu)
}
class Decomposed(
val item: ListItem,
val instanceId: String,
val instance: Instance?,
val thumbnail: InstanceThumbnail,
val launch: Button,
val popoverMenu: PopoverMenu
)
}

View File

@ -0,0 +1,46 @@
package io.gitlab.jfronny.inceptum.gtk.control
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance
import org.gnome.gtk.Image
import org.gnome.gtk.Spinner
import org.gnome.gtk.Stack
import java.lang.foreign.MemorySegment
class InstanceThumbnail : Stack {
private constructor(address: MemorySegment) : super(address)
constructor() : super() {
val spinner = Spinner()
val image = Image()
val generic = Image()
spinner.name = SPINNER
image.name = IMAGE
generic.name = GENERIC
generic.setFromIconName("media-playback-start-symbolic") //TODO better default icon
addNamed(spinner, SPINNER)
addNamed(image, IMAGE)
addNamed(generic, GENERIC)
}
fun bind(entry: Instance) {
val spinner = getChildByName(SPINNER) as Spinner
val image = getChildByName(IMAGE) as Image //TODO
val generic = getChildByName(GENERIC) as Image
//TODO mark instance being played
visibleChild = if (entry.isSetupLocked) {
spinner
} else if (false) { // if the instance has an image, load the image data and set it as the visible child
image
} else {
generic
}
}
companion object {
private const val SPINNER = "spinner"
private const val IMAGE = "image"
private const val GENERIC = "generic"
fun castFrom(stack: Stack): InstanceThumbnail = InstanceThumbnail(stack.handle())
}
}

View File

@ -0,0 +1,36 @@
package io.gitlab.jfronny.inceptum.gtk.control
import io.gitlab.jfronny.inceptum.gtk.util.I18n
import org.gnome.gtk.DropDown
import org.gnome.gtk.PropertyExpression
import org.gnome.gtk.StringList
import org.gnome.gtk.StringObject
import org.jetbrains.annotations.PropertyKey
import java.util.function.IntConsumer
class KDropDown<T>(options: Array<T>, private val stringify: (T) -> String, selected: Int): DropDown(options.toModel(stringify), null) {
private val onChange = ArrayList<IntConsumer>()
init {
this.selected = selected
onNotify("selected") { _ -> onChange.forEach { it.accept(this.selected) } }
expression = PropertyExpression(StringObject.getType(), null, "string")
}
fun onChange(changed: IntConsumer) {
onChange.add(changed)
}
fun updateOptions(newOptions: Array<T>, newSelected: Int) {
selected = 0
model = newOptions.toModel(stringify)
selected = newSelected
}
companion object {
private fun <T> Array<T>.toModel(stringify: (T) -> String) = StringList(map(stringify).toTypedArray())
}
}
fun KDropDown(vararg options: @PropertyKey(resourceBundle = I18n.BUNDLE) String, selected: Int = 0): KDropDown<String> =
KDropDown(options.map { it }.toTypedArray(), { I18n[it] }, selected)

View File

@ -0,0 +1,17 @@
package io.gitlab.jfronny.inceptum.gtk.control
import org.gnome.gtk.Entry
import java.util.function.Consumer
class KEntry(value: String? = ""): Entry() {
private val onChange = ArrayList<Consumer<String>>()
init {
text = value ?: ""
onChanged { onChange.forEach { it.accept(text) } }
}
fun onChange(changed: Consumer<String>) {
onChange.add(changed)
}
}

View File

@ -0,0 +1,58 @@
package io.gitlab.jfronny.inceptum.gtk.control
import io.github.jwharm.javagi.gobject.SignalConnection
import org.gnome.gtk.ListItem
import org.gnome.gtk.SignalListItemFactory
import org.gnome.gtk.StringObject
import org.gnome.gtk.Widget
abstract class KSignalListItemFactory<TData, TWidget : Widget> : SignalListItemFactory() {
private val toDisconnect: MutableMap<String, MutableSet<SignalConnection<*>>> = HashMap()
init {
onSetup {
val li = it as ListItem
li.child = setup()
}
onBind {
val li = it as ListItem
val id = (li.item as StringObject).string
val context = BindContextImpl(id, li)
val widget = context.castWidget(li.child!!)
context.bind(widget, context.lookup(id, widget))
}
onUnbind {
val li = it as ListItem
val id = (li.item as StringObject).string
val context = UnbindContextImpl(li)
val widget = context.castWidget(li.child!!)
toDisconnect.remove(id)?.forEach { it.disconnect() }
context.unbind(widget, context.lookup(id, widget))
}
}
abstract fun setup(): TWidget
abstract fun BindContext.bind(widget: TWidget, data: TData)
abstract fun UnbindContext.unbind(widget: TWidget, data: TData)
abstract fun ActionContext.lookup(id: String, widget: TWidget): TData
abstract fun ActionContext.castWidget(widget: Widget): TWidget
interface ActionContext {
val listItem: ListItem
}
interface BindContext: ActionContext {
fun registerForUnbind(signal: SignalConnection<*>)
}
interface UnbindContext: ActionContext {
}
private inner class BindContextImpl(private val id: String, override val listItem: ListItem) : BindContext {
override fun registerForUnbind(signal: SignalConnection<*>) {
toDisconnect.computeIfAbsent(id) { _ -> HashSet() }
.add(signal)
}
}
private inner class UnbindContextImpl(override val listItem: ListItem): UnbindContext
}

View File

@ -0,0 +1,37 @@
package io.gitlab.jfronny.inceptum.gtk.control
import org.gnome.gtk.Align
import org.gnome.gtk.ProgressBar
import org.gnome.gtk.Revealer
import org.gnome.gtk.RevealerTransitionType
class LoadingRevealer: Revealer() {
private val progressBar: ProgressBar
init {
transitionType = RevealerTransitionType.CROSSFADE
revealChild = false
vexpand = false
hexpand = false
valign = Align.CENTER
halign = Align.FILL
progressBar = ProgressBar().apply {
hexpand = true
vexpand = false
addCssClass("osd")
}
child = progressBar
}
fun setRunning(state: Boolean) {
revealChild = state
}
fun setProgress(progress: Double) {
progressBar.fraction = progress.coerceIn(0.0, 1.0)
}
fun pulse() {
progressBar.pulse()
}
}

View File

@ -0,0 +1,15 @@
package io.gitlab.jfronny.inceptum.gtk.control.assistant
import org.gnome.gtk.AssistantPageType
import org.gnome.gtk.Box
import org.gnome.gtk.Orientation
class AssistantPage(val title: String, val type: AssistantPageType): Box(Orientation.VERTICAL, 8) {
lateinit var setComplete: (Boolean) -> Unit
private val onOpen = ArrayList<Runnable>()
fun onOpen(action: Runnable) {
onOpen.add(action)
}
fun emitOpen() = onOpen.forEach { it.run() }
}

View File

@ -0,0 +1,33 @@
package io.gitlab.jfronny.inceptum.gtk.control.assistant
import io.gitlab.jfronny.inceptum.gtk.util.Log
import org.gnome.gtk.Application
import org.gnome.gtk.Assistant
import org.gnome.gtk.AssistantPageType
open class KAssistant(app: Application) : Assistant() {
private val pages = ArrayList<AssistantPage>()
init {
application = app
onPrepare { next ->
val page = pages.firstOrNull { it.handle() == next.handle() }
if (page == null) {
Log.error("Unknown page opened in assistant")
} else {
page.emitOpen()
}
}
}
fun page(title: String, type: AssistantPageType, setup: AssistantPage.() -> Unit): Int {
val page = AssistantPage(title, type)
val idx = appendPage(page)
pages.add(page)
page.setComplete = { setPageComplete(page, it) }
page.setup()
setPageType(page, page.type)
setPageTitle(page, page.title)
return idx
}
}

View File

@ -0,0 +1,83 @@
package io.gitlab.jfronny.inceptum.gtk.control.settings
import io.gitlab.jfronny.inceptum.gtk.control.ILabel
import io.gitlab.jfronny.inceptum.gtk.control.KDropDown
import io.gitlab.jfronny.inceptum.gtk.control.KEntry
import io.gitlab.jfronny.inceptum.gtk.util.I18n
import io.gitlab.jfronny.inceptum.gtk.util.margin
import org.gnome.gtk.*
import org.jetbrains.annotations.PropertyKey
import java.util.function.Consumer
import java.util.function.DoubleConsumer
import java.util.function.IntConsumer
class IRow(
title: @PropertyKey(resourceBundle = I18n.BUNDLE) String,
subtitle: @PropertyKey(resourceBundle = I18n.BUNDLE) String?,
vararg args: Any?
) : Box(Orientation.HORIZONTAL, 40) {
init {
margin = 8
val head: Widget
val lab = ILabel(title, *args)
lab.halign = Align.START
if (subtitle != null) {
val headB = Box(Orientation.VERTICAL, 0)
headB.append(lab)
val lab1 = ILabel(subtitle, ILabel.Mode.SUBTITLE, *args)
lab1.halign = Align.START
headB.append(lab1)
head = headB
} else {
head = lab
}
head.halign = Align.START
head.valign = Align.CENTER
append(head)
}
fun setButton(text: @PropertyKey(resourceBundle = I18n.BUNDLE) String, action: Button.ClickedCallback?): Button =
Button.withLabel(I18n[text]).apply {
packSmallEnd()
onClicked(action)
}
fun setDropdown(options: Array<String>, defaultIndex: Int, changed: IntConsumer): KDropDown<String> =
KDropDown(options, { it } , defaultIndex).apply {
onChange(changed)
packSmallEnd()
}
fun setSwitch(value: Boolean, changed: Consumer<Boolean>): Switch =
Switch().apply {
packSmallEnd()
active = value
onStateSet { state: Boolean ->
changed.accept(state)
false
}
}
fun setSpinButton(value: Double, min: Double, max: Double, step: Double, changed: DoubleConsumer): SpinButton =
SpinButton.withRange(min, max, step).apply {
packSmallEnd()
this.value = value
onValueChanged { changed.accept(this.value) }
}
fun setEntry(value: String?, changed: Consumer<String>): KEntry =
KEntry(value).apply {
hexpand = true
valign = Align.CENTER
halign = Align.FILL
onChange(changed)
append(this)
}
private fun Widget.packSmallEnd() {
firstChild!!.hexpand = true
valign = Align.CENTER
halign = Align.END
this@IRow.append(this)
}
}

View File

@ -0,0 +1,74 @@
package io.gitlab.jfronny.inceptum.gtk.control.settings
import io.gitlab.jfronny.inceptum.gtk.control.ILabel
import io.gitlab.jfronny.inceptum.gtk.util.I18n
import io.gitlab.jfronny.inceptum.gtk.util.marginHorizontal
import org.gnome.gtk.*
import org.jetbrains.annotations.PropertyKey
import java.util.concurrent.atomic.AtomicInteger
open class SectionedSettingsTab<W : Window>(window: W?): SettingsTab<Box, W>(window, Box(Orientation.VERTICAL, 8)) {
init {
content.marginHorizontal = 24
content.marginTop = 12
}
fun append(child: Widget) = content.append(child)
protected fun section(title: @PropertyKey(resourceBundle = I18n.BUNDLE) String?, builder: Section.() -> Unit) {
if (title != null) append(ILabel(title, ILabel.Mode.HEADING))
val frame = Frame(null as String?)
val listBox = ListBox()
listBox.selectionMode = SelectionMode.NONE
listBox.showSeparators = true
frame.child = listBox
val count = AtomicInteger(0)
builder(object : Section {
override fun row(title: String, subtitle: String?, vararg args: Any?, build: IRow.() -> Unit): IRow {
val row = IRow(title, subtitle, *args)
row(row)
build(row)
return row
}
override fun row(row: Widget): ListBoxRow {
listBox.append(row)
return listBox.getRowAtIndex(count.getAndIncrement())!!
}
override fun remove(row: Widget) {
listBox.remove(row)
count.decrementAndGet()
}
override fun clear() {
var i = 0
val len = count.getAndSet(0)
while (i < len) {
listBox.remove(listBox.getRowAtIndex(0))
i++
}
}
})
append(frame)
}
interface Section {
fun row(
title: @PropertyKey(resourceBundle = I18n.BUNDLE) String,
subtitle: @PropertyKey(resourceBundle = I18n.BUNDLE) String?,
vararg args: Any?
): IRow = row(title, subtitle, *args, build = {})
fun row(
title: @PropertyKey(resourceBundle = I18n.BUNDLE) String,
subtitle: @PropertyKey(resourceBundle = I18n.BUNDLE) String?,
vararg args: Any?,
build: IRow.() -> Unit
): IRow
fun row(row: Widget): ListBoxRow?
fun remove(row: Widget)
fun clear()
}
}

View File

@ -0,0 +1,20 @@
package io.gitlab.jfronny.inceptum.gtk.control.settings
import io.gitlab.jfronny.commons.StringFormatter
import io.gitlab.jfronny.inceptum.gtk.GtkEnvBackend
import org.gnome.gtk.Widget
import org.gnome.gtk.Window
open class SettingsTab<T : Widget, W : Window>(
protected val window: W?,
val content: T
) {
protected fun showError(message: String, t: Throwable) =
GtkEnvBackend.simpleDialog(
window,
StringFormatter.toString(t),
message,
null,
null
)
}

View File

@ -0,0 +1,50 @@
package io.gitlab.jfronny.inceptum.gtk.control.settings
import io.gitlab.jfronny.inceptum.gtk.util.I18n
import org.gnome.adw.HeaderBar
import org.gnome.adw.ViewStack
import org.gnome.adw.ViewSwitcherBar
import org.gnome.adw.ViewSwitcherTitle
import org.gnome.gobject.BindingFlags
import org.gnome.gtk.*
import org.jetbrains.annotations.PropertyKey
open class SettingsWindow(app: Application?) : Window() {
protected val stack: ViewStack
init {
application = app
stack = ViewStack()
val header = HeaderBar()
val viewSwitcher = ViewSwitcherTitle()
viewSwitcher.stack = stack
header.titleWidget = viewSwitcher
titlebar = header
val scroll = ScrolledWindow()
scroll.setPolicy(PolicyType.NEVER, PolicyType.AUTOMATIC)
scroll.child = stack
scroll.vexpand = true
val bottomBar = ViewSwitcherBar()
bottomBar.stack = stack
viewSwitcher.bindProperty("title-visible", bottomBar, "reveal", BindingFlags.DEFAULT)
val view = Box(Orientation.VERTICAL, 0)
view.append(scroll)
view.append(bottomBar)
child = view
setDefaultSize(720, 360)
}
fun addTab(tab: SettingsTab<*, *>, title: @PropertyKey(resourceBundle = I18n.BUNDLE) String, iconName: String) {
stack.addTitledWithIcon(tab.content, title, I18n[title], iconName)
}
var activePage: String
get() = stack.visibleChildName!!
set(@PropertyKey(resourceBundle = I18n.BUNDLE) title) { stack.visibleChildName = title }
}

View File

@ -0,0 +1,6 @@
package io.gitlab.jfronny.inceptum.gtk.menu
import org.gnome.gio.MenuItem
import org.gnome.gio.SimpleAction
class BuiltButtonItem(action: SimpleAction, menuItem: MenuItem?) : BuiltMenuItem(action, menuItem)

View File

@ -0,0 +1,23 @@
package io.gitlab.jfronny.inceptum.gtk.menu
import org.gnome.gio.MenuItem
import org.gnome.gio.SimpleAction
import org.gnome.gio.ThemedIcon
abstract class BuiltMenuItem protected constructor(action: SimpleAction, protected val menuItem: MenuItem?) {
protected val action: SimpleAction
init {
this.action = action
}
var enabled: Boolean
get() = action.enabled
set(enabled) {
action.enabled = enabled
}
var iconName: String?
set(iconName) { menuItem!!.setIcon(ThemedIcon(iconName)) }
get() = throw NotImplementedError()
}

View File

@ -0,0 +1,12 @@
package io.gitlab.jfronny.inceptum.gtk.menu
import org.gnome.gio.SimpleAction
import org.gnome.glib.Variant
class BuiltRadioItem<T>(action: SimpleAction, private val options: List<T>) : BuiltMenuItem(action, null) {
var selected: T
get() = options[action.state!!.int32]
set(selected) {
action.state = Variant.int32(options.indexOf(selected))
}
}

View File

@ -0,0 +1,19 @@
package io.gitlab.jfronny.inceptum.gtk.menu
import org.gnome.gio.MenuItem
import org.gnome.gio.SimpleAction
import org.gnome.glib.Variant
class BuiltToggleItem(action: SimpleAction, menuItem: MenuItem?) : BuiltMenuItem(action, menuItem) {
var state: Boolean
get() = action.state!!.boolean
set(state) {
action.state = Variant.boolean_(state)
}
fun toggle(): Boolean {
val toggled = !state
state = toggled
return toggled
}
}

View File

@ -0,0 +1,169 @@
package io.gitlab.jfronny.inceptum.gtk.menu
import io.github.jwharm.javagi.glib.types.VariantTypes
import io.gitlab.jfronny.commons.throwable.ThrowingRunnable
import io.gitlab.jfronny.inceptum.gtk.util.I18n
import io.gitlab.jfronny.inceptum.gtk.util.Log
import org.gnome.gio.*
import org.gnome.glib.Variant
import org.gnome.gtk.Application
import org.gnome.gtk.MenuButton
import org.gnome.gtk.PopoverMenu
import java.util.function.BiFunction
import java.util.function.Consumer
class MenuBuilder private constructor(map: ActionMap, menu: Menu, prefix: String, groupName: String) {
private val map: ActionMap
private val refs: MutableMap<String, Action> = LinkedHashMap()
val menu: Menu
private val prefix: String
private val groupName: String
constructor(menu: PopoverMenu, groupName: String) : this(
insertMap(menu, groupName),
menu.menuModel as Menu,
"",
groupName
)
constructor(app: Application, menu: Menu = getRootMenu(app), prefix: String = "") : this(app, menu, prefix, "app.")
init {
fun String.suffix() = if (isNotEmpty() && !endsWith(".")) "$this." else this
this.map = map
this.menu = menu
this.prefix = prefix.suffix()
this.groupName = groupName.suffix()
}
fun button(name: String, onClick: ThrowingRunnable<*>): BuiltButtonItem {
return literalButton(name, I18n["menu.$prefix$name"], onClick)
}
fun literalButton(internalName: String, label: String?, onClick: ThrowingRunnable<*>): BuiltButtonItem {
var internalName = internalName
internalName = prefix + internalName
val action = SimpleAction(internalName, null)
addAction(internalName, action)
action.onActivate { _ ->
try {
onClick.run()
} catch (e: Throwable) {
Log.error("Could not execute action", e)
}
}
val menuItem = MenuItem(label, groupName + internalName)
menu.appendItem(menuItem)
action.enabled = true
return BuiltButtonItem(action, menuItem)
}
fun toggle(name: String, initial: Boolean, onToggle: Consumer<Boolean?>): BuiltToggleItem {
var name = name
name = prefix + name
val action = SimpleAction.stateful(name, null, Variant.boolean_(initial))
addAction(name, action)
action.onActivate { _ ->
val state = !action.getState()!!.boolean
action.state = Variant.boolean_(state)
onToggle.accept(state)
}
val menuItem = MenuItem(I18n["menu.$name"], groupName + name)
menu.appendItem(menuItem)
return BuiltToggleItem(action, menuItem)
}
fun <T> radio(name: String, initial: T, options: List<T>, onCheck: Consumer<T>): BuiltRadioItem<T> {
return literalRadio(name, initial, options, { i, _ -> I18n["menu.$prefix$name", i] }, onCheck)
}
fun <T> literalRadio(
name: String,
initial: T,
options: List<T>,
stringifier: BiFunction<Int, T, String?>,
onCheck: Consumer<T>
): BuiltRadioItem<T> {
var name = name
name = prefix + name
val action = SimpleAction.stateful(name, VariantTypes.INT32, Variant.int32(options.indexOf(initial)))
addAction(name, action)
action.onActivate { variant: Variant? ->
action.state = variant
onCheck.accept(options[variant!!.int32])
}
for ((i, option) in options.withIndex()) {
menu.appendItem(MenuItem(stringifier.apply(i, option), "$groupName$name($i)"))
}
return BuiltRadioItem(action, options)
}
fun submenu(name: String): MenuBuilder {
return literalSubmenu(name, I18n["menu.$prefix$name"])
}
fun literalSubmenu(name: String, label: String?): MenuBuilder {
var name = name
name = prefix + name
val submenu = Menu()
menu.appendSubmenu(label, submenu)
return MenuBuilder(map, submenu, name, groupName)
}
fun section(name: String): MenuBuilder {
return literalSection(name, I18n["section.$prefix$name"])
}
fun literalSection(name: String, label: String?): MenuBuilder {
var name = name
name = prefix + name
val section = Menu()
menu.appendSection(label, section)
return MenuBuilder(map, section, name, groupName)
}
fun clear() {
menu.removeAll()
refs.forEach { (name, _) -> map.removeAction(name) }
refs.clear()
}
private fun addAction(name: String, action: SimpleAction) {
map.addAction(action)
refs[name] = action
}
fun asPopover(): PopoverMenu {
return PopoverMenu.fromModel(menu)
}
companion object {
private val LOCK = Any()
private fun getRootMenu(app: Application): Menu {
synchronized(LOCK) {
val currentMenu = app.menubar
return if (currentMenu == null) {
val menu = Menu()
app.menubar = menu
menu
} else {
currentMenu as Menu
}
}
}
fun create(target: MenuButton, groupName: String): MenuBuilder {
val menu = Menu()
val pm = PopoverMenu.fromModel(menu)
target.setPopover(pm)
return MenuBuilder(pm, groupName)
}
private fun insertMap(menu: PopoverMenu, groupName: String): ActionMap {
val ag = SimpleActionGroup()
menu.insertActionGroup(groupName, ag)
return ag
}
}
}

View File

@ -0,0 +1,15 @@
package io.gitlab.jfronny.inceptum.gtk.util
import org.jetbrains.annotations.PropertyKey
import java.util.*
object I18n {
const val BUNDLE = "inceptum"
private val bundle = ResourceBundle.getBundle(BUNDLE)
operator fun get(key: @PropertyKey(resourceBundle = BUNDLE) String): String =
bundle.getString(key)
operator fun get(key: @PropertyKey(resourceBundle = BUNDLE) String, vararg args: Any?): String =
String.format(bundle.getString(key), *args)
}

View File

@ -0,0 +1,5 @@
package io.gitlab.jfronny.inceptum.gtk.util
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance
operator fun List<Instance>.get(id: String) = firstOrNull { it.id == id }

View File

@ -0,0 +1,6 @@
package io.gitlab.jfronny.inceptum.gtk.util
import io.gitlab.jfronny.commons.logging.Logger
import io.gitlab.jfronny.inceptum.common.Utils
object Log : Logger by Utils.LOGGER

View File

@ -0,0 +1,92 @@
package io.gitlab.jfronny.inceptum.gtk.util
import io.gitlab.jfronny.commons.OSUtils
import java.io.IOException
import java.nio.file.Files
import java.nio.file.Path
import java.util.regex.Pattern
object Memory {
const val KB: Long = 1024
const val MB = KB * 1024
const val GB = MB * 1024
private val impl = when (OSUtils.TYPE) {
OSUtils.Type.LINUX -> LinuxMI
OSUtils.Type.WINDOWS -> WindowsMI
OSUtils.Type.MAC_OS -> MacOsMI
}
private val totalMemory by lazy { impl.getTotalMemory() }
val maxMBForInstance: Long get() = (totalMemory / MB - 1024).coerceAtLeast(1024)
private interface MI {
fun getTotalMemory(): Long
}
private object LinuxMI : MI {
override fun getTotalMemory(): Long {
try {
Files.lines(Path.of("/proc/meminfo")).use { stream ->
val memTotal = stream
.filter { s: String -> s.startsWith("MemTotal:") }
.map { s: String -> s.substring("MemTotal:".length) }
.map { obj: String -> obj.trim { it <= ' ' } }
.findFirst()
return if (memTotal.isPresent()) {
parseDecimalMemorySizeToBinary(memTotal.get())
} else {
Log.error("Could not find total memory")
32 * GB
}
}
} catch (e: IOException) {
Log.error("Could not get total memory", e)
return 32 * GB
}
}
// Taken from oshi
private val BYTES_PATTERN = Pattern.compile("(\\d+) ?([kKMGT]?B?).*")
private val WHITESPACES = Pattern.compile("\\s+")
private fun parseDecimalMemorySizeToBinary(size: String): Long {
var mem = WHITESPACES.split(size)
if (mem.size < 2) {
// If no spaces, use regexp
val matcher = BYTES_PATTERN.matcher(size.trim { it <= ' ' })
if (matcher.find() && matcher.groupCount() == 2) {
mem = arrayOfNulls(2)
mem[0] = matcher.group(1)
mem[1] = matcher.group(2)
}
}
var capacity = parseLongOrDefault(mem[0], 0L)
if (mem.size == 2 && mem[1]!!.length > 1) {
when (mem[1]!![0]) {
'T' -> capacity = capacity shl 40
'G' -> capacity = capacity shl 30
'M' -> capacity = capacity shl 20
'K', 'k' -> capacity = capacity shl 10
else -> {}
}
}
return capacity
}
private fun parseLongOrDefault(s: String, defaultLong: Long): Long = try {
s.toLong()
} catch (e: NumberFormatException) {
defaultLong
}
}
private object WindowsMI : MI {
override fun getTotalMemory(): Long {
return 32 * GB // This is currently unsupported, but any implementations by Windows user using panama are welcome
}
}
private object MacOsMI : MI {
override fun getTotalMemory(): Long {
return 32 * GB // This is currently unsupported, but any implementations by MacOS user using panama are welcome
}
}
}

View File

@ -0,0 +1,5 @@
package io.gitlab.jfronny.inceptum.gtk.util
import java.util.stream.Stream
inline fun <reified T> Stream<T>.toTypedArray(): Array<T> = toArray(::arrayOfNulls)

View File

@ -0,0 +1,10 @@
package io.gitlab.jfronny.inceptum.gtk.util
import org.gnome.gio.ListModel
import org.gnome.gtk.StringList
fun StringList.clear() = splice(0, size, null)
fun StringList.addAll(values: Array<String>) = splice(size, 0, values)
fun StringList.replaceAll(values: Array<String>) = splice(0, size, values)
val ListModel.size get() = nItems

View File

@ -0,0 +1,46 @@
package io.gitlab.jfronny.inceptum.gtk.util
import io.gitlab.jfronny.inceptum.gtk.control.ILabel
import org.gnome.adw.ActionRow
import org.gnome.gtk.EntryBuffer
import org.gnome.gtk.Label
import org.gnome.gtk.MessageDialog
import org.gnome.gtk.Widget
var Widget.margin: Int
set(value) {
marginVertical = value
marginHorizontal = value
}
get() = throw NotImplementedError()
var Widget.marginVertical: Int
set(value) {
marginTop = value
marginBottom = value
}
get() = throw NotImplementedError()
var Widget.marginHorizontal: Int
set(value) {
marginStart = value
marginEnd = value
}
get() = throw NotImplementedError()
var MessageDialog.markup: String
set(value) { setMarkup(value.escapedMarkup) }
get() = throw NotImplementedError()
var Label.markup: String
set(value) { setMarkup(value.escapedMarkup) }
get() = throw NotImplementedError()
val String.escapedMarkup: String
get() = replace(unmatchedAnd, "&amp;")
private val unmatchedAnd = Regex("&(?![a-z]{1,6};|#[0-9]+;|#x[0-9A-F]+;)")
fun ActionRow.fixSubtitle() = ILabel.theme(firstChild!!.lastChild!!.prevSibling!!.lastChild as Label, ILabel.Mode.SUBTITLE)
fun EntryBuffer.clear() = deleteText(0, length)

View File

@ -0,0 +1,30 @@
package io.gitlab.jfronny.inceptum.gtk.window
import io.gitlab.jfronny.inceptum.common.BuildMetadata
import io.gitlab.jfronny.inceptum.gtk.util.I18n
import org.gnome.gtk.AboutDialog
import org.gnome.gtk.License
class AboutWindow : AboutDialog() {
init {
programName = "Inceptum"
copyright = "Copyright (C) 2021-2023 JFronny"
version = BuildMetadata.VERSION
licenseType = License.MIT_X11
license = I18n["about.license"]
websiteLabel = I18n["about.contact"]
website = "https://jfronny.gitlab.io/contact.html"
if (!BuildMetadata.IS_PUBLIC) {
comments = I18n["about.unsupported-build"]
}
val vm = Runtime.version().feature()
systemInformation = I18n[if (BuildMetadata.VM_VERSION == vm) "about.jvm" else "about.jvm.unsupported", vm]
//TODO setLogo
}
companion object {
fun createAndShow() {
AboutWindow().visible = true
}
}
}

View File

@ -0,0 +1,166 @@
package io.gitlab.jfronny.inceptum.gtk.window
import io.gitlab.jfronny.inceptum.common.Utils
import io.gitlab.jfronny.inceptum.gtk.GtkMenubar
import io.gitlab.jfronny.inceptum.gtk.control.InstanceListEntryFactory
import io.gitlab.jfronny.inceptum.gtk.menu.MenuBuilder
import io.gitlab.jfronny.inceptum.gtk.util.*
import io.gitlab.jfronny.inceptum.gtk.window.settings.launcher.LauncherSettingsWindow
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance
import io.gitlab.jfronny.inceptum.launcher.system.instance.InstanceList
import io.gitlab.jfronny.inceptum.launcher.system.instance.InstanceListWatcher
import io.gitlab.jfronny.inceptum.launcher.system.launch.LaunchType
import org.gnome.adw.Clamp
import org.gnome.adw.StatusPage
import org.gnome.gio.*
import org.gnome.glib.GLib
import org.gnome.gtk.*
import org.gnome.gtk.Application
import java.io.IOException
import java.net.URI
class MainWindow(app: Application) : ApplicationWindow(app) {
// private val listButton: Button
// private val gridButton: Button
private val stack: Stack
private val empty: StatusPage
private val listContainer: Clamp
// private val gridView: GridView
private val instanceList: MutableList<Instance>
private val instanceListModel: StringList
init {
val header = HeaderBar()
val newButton = MenuButton()
newButton.iconName = "list-add-symbolic"
newButton.menuModel = GtkMenubar.newMenu!!.menu
val accountsButton = MenuButton()
accountsButton.iconName = "avatar-default-symbolic"
accountsButton.menuModel = GtkMenubar.accountsMenu!!.menu
// listButton = Button.newFromIconName("view-list-symbolic")
// listButton.onClicked {
// InceptumConfig.listView = true
// InceptumConfig.saveConfig()
// generateWindowBody()
// }
// gridButton = Button.newFromIconName("view-grid-symbolic")
// gridButton.onClicked {
// InceptumConfig.listView = false
// InceptumConfig.saveConfig()
// generateWindowBody()
// }
//TODO search button like boxes
val uiMenu = MenuBuilder(app, Menu(), "hamburger")
uiMenu.button("support") { Utils.openWebBrowser(URI("https://git.frohnmeyer-wds.de/JfMods/Inceptum/issues")) }
uiMenu.button("preferences") { LauncherSettingsWindow(app).visible = true }
uiMenu.button("about") { AboutWindow.createAndShow() }
val menuButton = MenuButton()
menuButton.iconName = "open-menu-symbolic"
menuButton.menuModel = uiMenu.menu
header.packStart(newButton)
header.packEnd(menuButton)
// header.packEnd(gridButton)
// header.packEnd(listButton)
header.packEnd(accountsButton)
instanceList = ArrayList()
instanceListModel = StringList(arrayOf())
val selection = NoSelection(instanceListModel)
val listView = ListView(selection, InstanceListEntryFactory(app, instanceList))
listView.addCssClass("rich-list")
listView.showSeparators = true
listView.onActivate { position: Int ->
// Double click
GtkMenubar.launch(instanceList[position], LaunchType.Client)
}
val frame = Frame(null as String?)
frame.child = listView
frame.marginHorizontal = 24
frame.marginVertical = 12
frame.valign = Align.START
listContainer = Clamp()
listContainer.maximumSize = 900
listContainer.child = frame
// gridView = GridView(selection, InstanceGridEntryFactory(instanceList))
empty = StatusPage()
empty.title = I18n["main.empty.title"]
empty.description = I18n["main.empty.description"]
//TODO empty.setIconName(new Str());
stack = Stack()
stack.addChild(listContainer)
// stack.addChild(gridView)
stack.addChild(empty)
val scroll = ScrolledWindow()
scroll.setPolicy(PolicyType.NEVER, PolicyType.AUTOMATIC)
scroll.child = stack
setDefaultSize(720, 360)
title = "Inceptum"
titlebar = header
showMenubar = false
child = scroll
generateWindowBody()
//TODO DropTarget to add mods/instances
try {
setupDirWatcher()
} catch (e: IOException) {
Log.error(
"Could not set up watch service, live updates of the instance dir will be unavailable",
e
)
}
}
@Throws(IOException::class)
private fun setupDirWatcher() {
val isw = InstanceListWatcher()
addTickCallback { _, _ ->
try {
if (isw.poll()) generateWindowBody()
} catch (e: IOException) {
Log.error("Could not run update task", e)
}
GLib.SOURCE_CONTINUE
}
onCloseRequest {
try {
isw.close()
} catch (ignored: IOException) {}
false
}
}
private fun generateWindowBody() {
// listButton.visible = !InceptumConfig.listView
// gridButton.visible = InceptumConfig.listView
try {
// Unbind then clear
instanceListModel.clear()
instanceList.clear()
// Add new entries
instanceList.addAll(InstanceList.ordered())
instanceListModel.addAll(instanceList.map { it.id }.toTypedArray())
// Choose view for this amount of entries
// stack.visibleChild = if (InstanceList.isEmpty()) empty
// else if (InceptumConfig.listView) listContainer
// else gridView
stack.visibleChild = if (InstanceList.isEmpty()) empty else listContainer
// This is called from a tick callback, so re-render
stack.queueResize()
stack.queueDraw()
} catch (e: IOException) {
Log.error("Could not generate window body", e)
}
}
}

View File

@ -0,0 +1,178 @@
package io.gitlab.jfronny.inceptum.gtk.window.create
import io.gitlab.jfronny.commons.StringFormatter
import io.gitlab.jfronny.inceptum.common.InceptumConfig
import io.gitlab.jfronny.inceptum.gtk.control.KDropDown
import io.gitlab.jfronny.inceptum.gtk.control.KEntry
import io.gitlab.jfronny.inceptum.gtk.control.assistant.KAssistant
import io.gitlab.jfronny.inceptum.gtk.schedule
import io.gitlab.jfronny.inceptum.gtk.util.I18n
import io.gitlab.jfronny.inceptum.gtk.util.Log
import io.gitlab.jfronny.inceptum.gtk.util.markup
import io.gitlab.jfronny.inceptum.gtk.util.toTypedArray
import io.gitlab.jfronny.inceptum.gtk.window.dialog.ProcessStateWatcherDialog
import io.gitlab.jfronny.inceptum.launcher.api.FabricMetaApi
import io.gitlab.jfronny.inceptum.launcher.api.McApi
import io.gitlab.jfronny.inceptum.launcher.model.fabric.FabricVersionLoaderInfo
import io.gitlab.jfronny.inceptum.launcher.model.mojang.VersionsListInfo
import io.gitlab.jfronny.inceptum.launcher.system.instance.InstanceNameTool
import io.gitlab.jfronny.inceptum.launcher.system.instance.LoaderInfo
import io.gitlab.jfronny.inceptum.launcher.system.setup.SetupStepInfo
import io.gitlab.jfronny.inceptum.launcher.system.setup.Steps
import org.gnome.glib.GLib
import org.gnome.gtk.*
class NewInstanceWindow(app: Application) : KAssistant(app) {
companion object {
private val VERSIONS = McApi.getVersions()
}
init {
var gameVersion: VersionsListInfo? = null
var useFabric = false
var fabricVersion: FabricVersionLoaderInfo? = null
var name = "New Instance"
var failureMessage = "Unknown error, please look at the log!"
var isFailure = false
page("Welcome", AssistantPageType.INTRO) {
append(Label("This assistant will guide you through the process of setting up a Minecraft instance.\nTo begin, please choose the game version you want to use"))
val versions = VERSIONS.versions.stream()
.filter { InceptumConfig.snapshots || it.type == "release" }
.toTypedArray()
val def = versions.withIndex().firstOrNull { it.value.id == VERSIONS.latest.release }?.index ?: 0
gameVersion = versions[def]
append(KDropDown(versions, { it.id }, def).apply {
onChange { gameVersion = versions[it] }
})
setComplete(true)
}
page("Loader", AssistantPageType.CONTENT) {
append(Label("Select a mod loader if you want to use mods in this instance. This can be changed later."))
var lastGameVersion: VersionsListInfo? = null
var versions = arrayOf<FabricVersionLoaderInfo>()
var def = 0
val none = CheckButton.withLabel("None")
val fabric = CheckButton.withLabel("Fabric")
none.onToggled { useFabric = false }
none.onToggled { useFabric = true }
fabric.setGroup(none)
append(none)
val fabricVersionDropdown = KDropDown(versions, { it.loader.version }, def)
fabricVersionDropdown.onChange { fabricVersion = versions[it] }
append(Box(Orientation.HORIZONTAL, 8).apply {
append(fabric)
append(fabricVersionDropdown)
})
onOpen {
if (lastGameVersion == null || lastGameVersion != gameVersion) {
versions = FabricMetaApi.getLoaderVersions(gameVersion!!).toTypedArray()
def = versions.withIndex().firstOrNull { it.value.loader.stable }?.index ?: 0
fabricVersionDropdown.updateOptions(versions, def)
lastGameVersion = gameVersion
none.active = true
fabric.active = false
useFabric = false
}
if (versions.isEmpty()) {
none.active = true
fabric.active = false
useFabric = false
fabric.sensitive = false
}
}
setComplete(true)
}
page("Name", AssistantPageType.CONTENT) {
append(Label(I18n["instance.settings.general.name.placeholder"]))
val entry = KEntry(name)
entry.placeholderText = I18n["instance.settings.general.name.placeholder"]
entry.valign
entry.onChange { name = InstanceNameTool.getNextValid(it) }
append(entry)
onOpen {
name = InstanceNameTool.getDefaultName(gameVersion!!.id, useFabric)
entry.text = name
}
setComplete(true)
}
page("Creating", AssistantPageType.PROGRESS) {
append(Label("Creating Instance"))
val progress = ProgressBar()
append(progress)
val stage = Label("")
append(stage)
onOpen {
commit()
val pState = Steps.createProcessState()
val state = SetupStepInfo(
McApi.getVersionInfo(gameVersion),
if (useFabric) LoaderInfo(fabricVersion!!.loader) else LoaderInfo.NONE,
name,
pState
)
var finished = false
var cachedState: ProcessStateWatcherDialog.State? = null
addTickCallback { widget, _ ->
if (finished) return@addTickCallback GLib.SOURCE_REMOVE
val nc = ProcessStateWatcherDialog.State(pState)
if (nc != cachedState) {
cachedState = nc
stage.markup = cachedState!!.msg
progress.fraction = cachedState!!.progress.coerceAtMost(1f).toDouble()
widget.queueDraw()
}
GLib.SOURCE_CONTINUE
}
onClose { pState.cancel() }
onCancel { pState.cancel() }
pState.updateStep("Starting install process")
Thread {
try {
for (step in Steps.STEPS) {
if (state.isCancelled) {
state.tryRemoveInstance()
return@Thread
}
pState.incrementStep(step.name)
step.execute(state)
}
state.clearSetupLock()
} catch (e: Throwable) {
pState.cancel()
Log.error("Could not create instance")
failureMessage = StringFormatter.toString(e)
isFailure = true
state.tryRemoveInstance()
} finally {
finished = true
schedule { setComplete(true) }
schedule { nextPage() }
}
}.start()
}
}
page("Done", AssistantPageType.SUMMARY) {
val status = Label("")
onOpen {
if (isFailure) {
status.markup = "Something went wrong while creating the instance.\n\n$failureMessage"
} else {
status.markup = "The instance was successfully created. You can now launch it using the main menu"
}
}
setComplete(true)
}
}
}

View File

@ -0,0 +1,75 @@
package io.gitlab.jfronny.inceptum.gtk.window.dialog
import io.gitlab.jfronny.inceptum.common.Utils
import io.gitlab.jfronny.inceptum.gtk.util.I18n
import io.gitlab.jfronny.inceptum.gtk.util.Log
import io.gitlab.jfronny.inceptum.launcher.api.account.MicrosoftAccount
import io.gitlab.jfronny.inceptum.launcher.api.account.MicrosoftAuthAPI
import io.gitlab.jfronny.inceptum.launcher.api.account.MicrosoftAuthServer
import org.gnome.gtk.*
import java.net.URI
import java.net.URISyntaxException
class MicrosoftLoginDialog(
parent: Window?,
account: MicrosoftAccount? = null,
onClose: Runnable? = null
) : MessageDialog(
parent,
flags(parent != null),
MessageType.QUESTION,
ButtonsType.CLOSE,
I18n["auth.description"]
) {
constructor(parent: Window?, onClose: Runnable?) : this(parent, null, onClose)
init {
title = I18n["auth.title"]
val server = MicrosoftAuthServer(account)
try {
server.start()
} catch (e: Exception) {
Log.error("Could not start mc login server", e)
}
val finalize = Runnable {
server.close()
onClose?.run()
}
onResponse { responseId: Int ->
when (ResponseType.of(responseId)) {
ResponseType.CLOSE, ResponseType.CANCEL -> {
finalize.run()
close()
}
ResponseType.DELETE_EVENT -> {
finalize.run()
destroy()
}
else -> Log.error("Unexpected response type: $responseId")
}
}
val btn = Button.withLabel(I18n["auth.open-browser"])
(messageArea as Box).append(btn)
btn.onClicked {
try {
Utils.openWebBrowser(URI(MicrosoftAuthAPI.MICROSOFT_LOGIN_URL))
} catch (e: URISyntaxException) {
Log.error("Could not open browser", e)
}
}
onCloseRequest {
finalize.run()
false
}
}
companion object {
private fun flags(modal: Boolean): DialogFlags {
var flags = DialogFlags.DESTROY_WITH_PARENT
if (modal) flags = flags.or(DialogFlags.MODAL)
return flags
}
}
}

Some files were not shown because too many files have changed in this diff Show More