Compare commits

...

167 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
Johannes Frohnmeyer 3480e0d965
Fix launch 2022-09-18 20:11:06 +02:00
Johannes Frohnmeyer 58ffd7d11e
Some nullability in InceptumConfig 2022-09-18 19:48:12 +02:00
Johannes Frohnmeyer 67bb89abe7
Attempt to add launchwrapper, replacing manual forceload library injection 2022-09-18 19:28:26 +02:00
Johannes Frohnmeyer d2c0979d16
Fix auth, add modrinth export (untested) 2022-09-18 18:56:44 +02:00
Johannes Frohnmeyer f7a3d5be53
Rework exporting 2022-09-18 15:15:30 +02:00
Johannes Frohnmeyer da621e0eba
.iceignore to exclude files from exports 2022-09-16 16:30:12 +02:00
Johannes Frohnmeyer cdf81fcb57
Use GuiEnvBackend when launching dist with GuiCommand 2022-09-07 18:55:15 +02:00
Johannes Frohnmeyer 5cc2b416be
Add comments to config, make entries static and fix VersionInfo cloning 2022-09-07 18:06:34 +02:00
Johannes Frohnmeyer 165cad8e6b
Fix early logs being written to wrong backend 2022-09-06 21:38:54 +02:00
Johannes Frohnmeyer c7aafad507
More attempts at fixing the wrapper (also get rid of slf4j) 2022-09-06 21:20:59 +02:00
Johannes Frohnmeyer f1d1979977
Portable: don't package inceptum as the way it was done is incompatible with the new wrapper 2022-09-06 16:21:22 +02:00
Johannes Frohnmeyer f4302a1a3f
CI: attempt to fix portable build 2022-09-06 16:04:20 +02:00
Johannes Frohnmeyer 8445bce486
Use mdbook for docs 2022-09-06 16:03:17 +02:00
Johannes Frohnmeyer 1f9f56d413
Rework CI script 2022-09-06 15:49:19 +02:00
Johannes Frohnmeyer 81f9cd270a
Attempt to fix 4 2022-09-06 15:05:44 +02:00
Johannes Frohnmeyer ad18164d69
Attempt to fix 3 2022-09-06 14:55:36 +02:00
Johannes Frohnmeyer 3dd66de0a7
Attempt to fix 2 2022-09-06 13:28:28 +02:00
Johannes Frohnmeyer be41e8d60d
Attempt to fix 2022-09-06 12:35:29 +02:00
Johannes Frohnmeyer 2c2efcacda
Update CI script 2022-09-06 11:24:50 +02:00
Johannes Frohnmeyer 63bf7df080
Experiment with reworking wrapper 2022-09-06 11:15:21 +02:00
Johannes Frohnmeyer d777b9f39d
Major refactors 2022-09-04 21:21:24 +02:00
Johannes Frohnmeyer 547889ce7f Duct-tape fix for builds since the rewrite is still not released 2022-08-05 13:33:39 +00:00
Johannes Frohnmeyer 35ffea6feb
Get rid of JGit, please use a normal git install.
.gitignore is now parsed manually during export
2022-08-02 16:55:16 +02:00
375 changed files with 11670 additions and 6558 deletions

3
.gitignore vendored
View File

@ -5,4 +5,5 @@ run*/
imgui.ini
hs_err_pid*
inceptum.log
bin
bin
public

View File

@ -1,94 +0,0 @@
image: gradle:jdk18
variables:
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
before_script:
- export GRADLE_USER_HOME=`pwd`/.gradle
stages:
- build
- portable
- deploy
build_test:
stage: build
script:
- TIMESTAMP=$(date +%s)
- gradle --build-cache build publish -Pflavor=nogui -Ppublic -Ptimestamp=$TIMESTAMP
- gradle --build-cache build publish -Pflavor=fat -Ppublic -Ptimestamp=$TIMESTAMP
- gradle --build-cache build publish -Pflavor=windows -Ppublic -Ptimestamp=$TIMESTAMP
- gradle --build-cache build publish -Pflavor=linux -Ppublic -Ptimestamp=$TIMESTAMP
- gradle --build-cache build publish -Pflavor=macos -Ppublic -Ptimestamp=$TIMESTAMP
- mkdir -p build/libs
- cp launcher/build/libs/* build/libs/
- cp wrapper/build/libs/* build/libs/
- cp launcher/build/libs/*-*-*-*.jar ./
- cp wrapper/build/libs/*.exe wrapper.exe
- cp wrapper/build/libs/*-all.jar wrapper.jar
- for f in *-*-*.jar; do mv "$f" "latest-${f##*-}";done
- mv latest-fat.jar latest.jar
- unzip -p latest.jar version.json > version.json
artifacts:
paths:
- build/libs
- latest.jar
- wrapper.jar
- wrapper.exe
- latest-*.jar
- version.json
expire_in: 2 days
only:
- master
portable:
stage: portable
image: archlinux:latest
script:
- pacman -Sy p7zip curl jq --noconfirm
- mkdir -p portable/run/libraries/io/gitlab/jfronny/inceptum/Inceptum
- mkdir -p portable/jvm
- cp wrapper.jar portable/
- main="$(find build/libs/ -type f -iname "Inceptum-*-windows.jar")"
- dir="${main##*/Inceptum-}"
- dir="portable/run/libraries/io/gitlab/jfronny/inceptum/Inceptum/${dir%%-windows*}"
- mkdir -p "$dir"
- mv "$main" "$dir/${main##*/}"
- 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/17/ga/windows/x64/jdk/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
deploy:
rules:
- if: $CI_COMMIT_TAG && '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^master/'
stage: deploy
script:
- gradle --build-cache build publish -Pflavor=nogui -Ppublic -Prelease
- gradle --build-cache build publish -Pflavor=fat -Ppublic -Prelease
- gradle --build-cache build publish -Pflavor=windows -Ppublic -Prelease
- gradle --build-cache build publish -Pflavor=linux -Ppublic -Prelease
- gradle --build-cache build publish -Pflavor=macos -Ppublic -Prelease
pages:
image: python:3.8-buster
stage: deploy
script:
- pip install mkdocs
- mkdocs build
artifacts:
paths:
- public
only:
- master

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

@ -1,19 +1,22 @@
# Inceptum Launcher
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
Inceptum utilizes code/libraries/assets from:
- [ATLauncher](https://github.com/ATLauncher/ATLauncher): Microsoft authentication
- [JLHTTP](https://www.freeutils.net/source/jlhttp/): Used in MS Auth
- [MultiMC](https://github.com/MultiMC/Launcher): Used as a reference for some implementations
- [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
- [slf4j](https://github.com/qos-ch/slf4j): Used for logging
- [logback](https://github.com/qos-ch/logback): An implementation of slf4j
- [JGit](https://www.eclipse.org/jgit/): Used to allow syncing repositories
- [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)

16
book.toml Normal file
View File

@ -0,0 +1,16 @@
[book]
authors = ["JFronny"]
language = "en"
multilingual = false
src = "docs"
title = "LibJF"
description = "Inceptum Docs"
[build]
build-dir = "public"
[output.html]
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,39 +1,73 @@
import org.gradle.internal.os.OperatingSystem
import org.ajoberstar.grgit.Grgit
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
}
}
println("Building Inceptum $currentVer")
allprojects {
version = currentVer + if (project.hasProperty("release")) "" else "-" + (if (project.hasProperty("timestamp")) project.property("timestamp") else "${System.currentTimeMillis() / 1000L}")
version = rootProject.version
group = "io.gitlab.jfronny.inceptum"
}
val lwjglVersion by extra("3.3.1")
val imguiVersion by extra("1.86.4")
val logbackVersion by extra("1.3.0-alpha15")
val jfCommonsVersion by extra("2022.7.4+11-13-3")
val jgitVersion by extra("6.2.0.202206071550-r")
val flavorProp: String by extra(if (project.hasProperty("flavor")) "${project.property("flavor")}" else "custom")
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()
})
// 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 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 isRelease by extra(project.hasProperty("release"))
val buildTime by extra(System.currentTimeMillis())
val wrapperVersion by extra(1)
tasks.register("exportMetadata") {
doLast {
projectDir.resolve("version.json").writeText(
"""
{
"wrapperVersion": $wrapperVersion,
"version": "$version",
"buildTime": $buildTime,
"isPublic": $isPublic,
"isRelease": $isRelease,
"jvm": ${project(":common").extra["javaVersion"]},
"repositories": [
"https://repo.maven.apache.org/maven2/",
"https://maven.frohnmeyer-wds.de/artifacts/"
],
"natives": {
"windows": [
"org.lwjgl:lwjgl:$lwjglVersion:natives-windows",
"org.lwjgl:lwjgl-opengl:$lwjglVersion:natives-windows",
"org.lwjgl:lwjgl-glfw:$lwjglVersion:natives-windows",
"org.lwjgl:lwjgl-tinyfd:$lwjglVersion:natives-windows",
"io.github.spair:imgui-java-natives-windows:$imguiVersion"
],
"linux": [
"org.lwjgl:lwjgl:$lwjglVersion:natives-linux",
"org.lwjgl:lwjgl-opengl:$lwjglVersion:natives-linux",
"org.lwjgl:lwjgl-glfw:$lwjglVersion:natives-linux",
"org.lwjgl:lwjgl-tinyfd:$lwjglVersion:natives-linux",
"io.github.spair:imgui-java-natives-linux:$imguiVersion"
],
"macos": [
"org.lwjgl:lwjgl:$lwjglVersion:natives-macos",
"org.lwjgl:lwjgl-opengl:$lwjglVersion:natives-macos",
"org.lwjgl:lwjgl-glfw:$lwjglVersion:natives-macos",
"org.lwjgl:lwjgl-tinyfd:$lwjglVersion:natives-macos",
"io.github.spair:imgui-java-natives-macos:$imguiVersion"
]
}
}
""".trimIndent()
)
}
}

15
buildSrc/build.gradle.kts Normal file
View File

@ -0,0 +1,15 @@
plugins {
`kotlin-dsl`
}
repositories {
gradlePluginPortal()
maven("https://maven.frohnmeyer-wds.de/artifacts")
}
dependencies {
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

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

View File

@ -0,0 +1,50 @@
import de.undercouch.gradle.tasks.download.Download
import java.io.FileOutputStream
plugins {
application
id("inceptum.java")
com.github.johnrengelman.shadow
de.undercouch.download
}
abstract class FileOutput : DefaultTask() {
@get:OutputFile
abstract var output: File
}
val bootstrapVersion = "0.3.2"
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.layout.buildDirectory)
}
val nativeExe by tasks.registering(FileOutput::class) {
dependsOn(downloadBootstrap)
dependsOn(tasks.shadowJar)
output = project.layout.buildDirectory.file("libs/${project.name}-${project.version}.exe").get().asFile
outputs.upToDateWhen { false }
doFirst {
output.delete()
}
doLast {
output.createNewFile()
FileOutputStream(output).use { w ->
w.write(downloadBootstrap.get().outputFiles.first().readBytes())
w.write(tasks.shadowJar.get().archiveFile.get().asFile.readBytes())
}
}
}
if (rootProject.extra["flavor"] == "windows") {
tasks.build.get().dependsOn(nativeExe)
}
tasks.runShadow {
workingDir = rootProject.projectDir
}

View File

@ -0,0 +1,14 @@
plugins {
application
id("inceptum.java")
}
publishing {
publications {
create<MavenPublication>("mavenJava") {
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

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

@ -0,0 +1,11 @@
plugins {
id("inceptum.java")
}
publishing {
publications {
create<MavenPublication>("mavenJava") {
from(components["java"])
}
}
}

View File

@ -1,29 +1,32 @@
plugins {
`java-library`
}
import io.gitlab.jfronny.scripts.*
import javax.lang.model.element.Modifier
repositories {
mavenCentral()
maven {
setUrl("https://gitlab.com/api/v4/projects/35745143/packages/maven")
}
plugins {
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"]}")
implementation("io.gitlab.jfronny:commons-slf4j:${rootProject.extra["jfCommonsVersion"]}")
implementation("ch.qos.logback:logback-classic:${rootProject.extra["logbackVersion"]}")
api(libs.bundles.commons)
}
tasks.processResources {
filesMatching("version.json") {
expand(
"version" to project.version,
"flavor" to rootProject.extra["flavorProp"],
"isPublic" to rootProject.extra["isPublic"],
"isRelease" to rootProject.extra["isRelease"],
"jvm" to project.java.targetCompatibility
)
val javaVersion by extra(project.java.targetCompatibility)
sourceSets {
main {
generate(project) {
`class`("io.gitlab.jfronny.inceptum.common", "BuildMetadata") {
modifiers(Modifier.PUBLIC)
val modifiers = arrayOf(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
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)
}
}
}
}
}

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

@ -0,0 +1,54 @@
package io.gitlab.jfronny.inceptum.common;
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.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 {
if (!Files.exists(MetaHolder.CONFIG_PATH.getParent()))
Files.createDirectories(MetaHolder.CONFIG_PATH.getParent());
if (!Files.exists(MetaHolder.CONFIG_PATH)) {
Path gLaunch2 = MetaHolder.BASE_PATH.resolve("glaunch2.json");
Path json = MetaHolder.BASE_PATH.resolve("inceptum.json");
if (Files.exists(gLaunch2)) {
Files.move(gLaunch2, MetaHolder.CONFIG_PATH);
} else if (Files.exists(json)) {
Files.move(json, MetaHolder.CONFIG_PATH);
} else {
saveConfig();
}
}
GC_InceptumConfig.read(MetaHolder.CONFIG_PATH);
}
public static void saveConfig() {
try {
GC_InceptumConfig.write(MetaHolder.CONFIG_PATH);
} catch (IOException e) {
Utils.LOGGER.error("Could not save config", e);
}
}
}

View File

@ -0,0 +1,19 @@
package io.gitlab.jfronny.inceptum.common;
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);
HttpClient.setUserAgent("jfmods/inceptum/" + BuildMetadata.VERSION);
InceptumConfig.load();
}
public static Logger defaultFactory(String name) {
return StdoutLogger.fancy(name);
}
}

View File

@ -0,0 +1,56 @@
package io.gitlab.jfronny.inceptum.common;
import io.gitlab.jfronny.commons.OSUtils;
import java.nio.file.*;
public class MetaHolder {
public static final Path BASE_PATH;
static {
if (System.getProperty("inceptum.base") == null) {
Path runDir = getPath("run");
if (!BuildMetadata.IS_RELEASE) BASE_PATH = runDir;
else if (Files.exists(runDir)) BASE_PATH = runDir;
else {
Path configsDir = switch (OSUtils.TYPE) {
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("XDG_CONFIG_HOME");
if (s == null)
yield getPath(System.getProperty("user.home")).resolve(".config");
else
yield getPath(s);
}
};
BASE_PATH = configsDir.resolve("Inceptum");
}
} else {
BASE_PATH = getPath(System.getProperty("inceptum.base"));
}
}
public static final Path ACCOUNTS_PATH = BASE_PATH.resolve("accounts.json");
public static final Path CONFIG_PATH = BASE_PATH.resolve("inceptum.json5");
public static final Path WRAPPER_CONFIG_PATH = BASE_PATH.resolve("wrapper.json");
public static final Path NATIVES_DIR = BASE_PATH.resolve("natives");
public static final Path FORCE_LOAD_PATH = NATIVES_DIR.resolve("forceload");
public static final Path LIBRARIES_DIR = BASE_PATH.resolve("libraries");
public static final Path ASSETS_DIR = BASE_PATH.resolve("assets");
public static final Path INSTANCE_DIR = BASE_PATH.resolve("instances");
public static final Path CACHE_DIR = BASE_PATH.resolve("cache");
private static boolean isWrapper = false;
private static Path getPath(String text) {
return Paths.get(text).toAbsolutePath().normalize();
}
public static void setWrapperFlag() {
isWrapper = true;
}
public static boolean isWrapper() {
return isWrapper;
}
}

View File

@ -0,0 +1,98 @@
package io.gitlab.jfronny.inceptum.common;
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.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
public class Net {
private static final ObjectCache OBJECT_CACHE = new ObjectCache(MetaHolder.CACHE_DIR);
public static byte[] downloadData(String url) throws IOException, URISyntaxException {
try (InputStream is = HttpClient.get(url).sendInputStream()) {
return is.readAllBytes();
}
}
public static byte[] downloadData(String url, String sha1) throws IOException, URISyntaxException {
byte[] buf = downloadData(url);
if (sha1 == null) return buf;
if (!HashUtils.sha1(buf).equals(sha1)) throw new IOException("Invalid hash");
return buf;
}
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, 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, ThrowingFunction<String, T, IOException> func, String apiKey) throws IOException {
return downloadObject(url, () -> downloadStringAuthenticated(url, apiKey), func, 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, String sha1, ThrowingFunction<String, T, IOException> func, boolean cache) throws IOException {
return downloadObject(url, () -> downloadString(url, sha1), func, cache);
}
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 = () -> 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);
}
}
public static String buildUrl(String host, String url, Map<String, String> params) {
StringBuilder res = new StringBuilder(host);
if (res.toString().endsWith("/")) res = new StringBuilder(res.substring(0, res.length() - 1));
if (url.startsWith("/")) res.append(url);
else res.append("/").append(url);
int i = 0;
for (Map.Entry<String, String> entry : params.entrySet()) {
res.append(i++ == 0 ? '?' : '&')
.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8))
.append('=')
.append(URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8));
}
return res.toString();
}
public static String downloadString(String url) throws IOException, URISyntaxException {
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));
}
public static void downloadFile(String url, String sha1, Path path) throws IOException, URISyntaxException {
if (!Files.exists(path.getParent())) Files.createDirectories(path.getParent());
Files.write(path, downloadData(url, sha1));
}
}

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

@ -0,0 +1,192 @@
package io.gitlab.jfronny.inceptum.common;
import io.gitlab.jfronny.commons.OSUtils;
import io.gitlab.jfronny.inceptum.common.api.MavenApi;
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.*;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Consumer;
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(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();
});
}
public static void update(UpdateMetadata source, boolean relaunch) throws IOException, URISyntaxException {
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)));
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())) {
Set<String> natives = new LinkedHashSet<>();
for (String lib : source.natives().get(Utils.getCurrentFlavor())) {
downloadLibrary(source.repositories(), lib, natives);
}
currentLibraries.addAll(natives);
config.natives().put(Utils.getCurrentFlavor(), natives);
}
GC_WrapperConfig.write(config, MetaHolder.WRAPPER_CONFIG_PATH);
if (relaunch) {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
new ProcessBuilder(OSUtils.getJvmBinary(),
"-cp",
buildClasspath(currentLibraries.stream())
.map(Path::toString)
.collect(Collectors.joining(String.valueOf(File.pathSeparatorChar)))
).inheritIO().start();
} catch (IOException e) {
Utils.LOGGER.error("Could not relaunch", e);
}
}));
}
}
public static List<Path> getLaunchClasspath(WrapperConfig wrapperConfig) throws IOException, URISyntaxException {
Set<String> natives = wrapperConfig.natives().get(Utils.getCurrentFlavor());
if (natives == null) natives = new LinkedHashSet<>();
Set<String> libs = wrapperConfig.libraries();
if (libs == null) libs = new LinkedHashSet<>();
boolean configChanged = false;
for (String lib : libs) {
Path p = ArtifactMeta.parse(lib).getLocalPath();
if (!Files.exists(p)) {
configChanged = true;
downloadLibrary(wrapperConfig.repositories(), lib, libs);
}
}
for (String lib : natives) {
Path p = ArtifactMeta.parse(lib).getLocalPath();
if (!Files.exists(p)) {
configChanged = true;
downloadLibrary(wrapperConfig.repositories(), lib, natives);
}
}
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(ArtifactMeta::parse).map(ArtifactMeta::getLocalPath);
}
private static DependencyNode downloadLibrary(Set<String> repositories, final String artifact, Set<String> libraries) throws IOException, URISyntaxException {
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, 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();
dependencies.add(downloadLibrary(repositories, mvnName, libraries));
}
}
MavenApi.downloadLibrary(repository, meta);
libraries.add(artifact);
return new DependencyNode(artifact, dependencies);
}
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;
}
public static @Nullable UpdateMetadata check(UpdateChannel channel, boolean versionCompare, boolean checkEnv, Consumer<UpdateChannel> channelInvalid) throws UpdateCheckException {
try {
UpdateMetadata experimental = Net.downloadObject(ARTIFACTS_URL + "version.json", json -> GC_UpdateMetadata.read(json));
UpdateMetadata stable = null;
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);
}
UpdateMetadata info = switch (channel) {
case CI -> experimental;
case Stable -> stable;
};
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 e) {
Utils.LOGGER.error("Could not check for updates", e);
}
return null;
}
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

@ -0,0 +1,82 @@
package io.gitlab.jfronny.inceptum.common;
import io.gitlab.jfronny.commons.OSUtils;
import io.gitlab.jfronny.commons.io.JFiles;
import io.gitlab.jfronny.commons.logging.Logger;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.FileSystem;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.regex.Pattern;
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();
public static void openWebBrowser(URI uri) {
try {
if (OSUtils.TYPE == OSUtils.Type.LINUX && OSUtils.executablePathContains("xdg-open")) {
Runtime.getRuntime().exec(new String[]{"xdg-open", uri.toString()});
} else if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
Desktop.getDesktop().browse(uri);
}
} catch (Exception e) {
Utils.LOGGER.error("Error opening web browser!", e);
}
}
public static void openFile(File file) {
try {
if (OSUtils.TYPE == OSUtils.Type.LINUX && OSUtils.executablePathContains("xdg-open")) {
Runtime.getRuntime().exec(new String[]{"xdg-open", file.getAbsoluteFile().toString()});
} else if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
Desktop.getDesktop().open(file);
}
} catch (Exception e) {
Utils.LOGGER.error("Error opening file!", e);
}
}
public static FileSystem openZipFile(Path zip, boolean create) throws IOException, URISyntaxException {
return JFiles.openZipFile(zip, create, SYSTEM_LOADER);
}
@SuppressWarnings("unused") // Called through reflection from wrapper
public static void wrapperInit(ClassLoader loader) {
SYSTEM_LOADER = loader;
MetaHolder.setWrapperFlag();
}
/**
* 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
* @return The joined string
*/
public static String join(String separator, String... segments) {
return Arrays.stream(segments)
.map(s -> s.startsWith(separator) ? s.substring(separator.length()) : s)
.map(s -> s.endsWith(separator) ? s.substring(0, s.length() - separator.length()) : s)
.filter(s -> !s.isEmpty())
.collect(Collectors.joining(separator));
}
public static String getCurrentFlavor() {
return switch (OSUtils.TYPE) {
case WINDOWS -> "windows";
case MAC_OS -> "macos";
case LINUX -> "linux";
};
}
}

View File

@ -0,0 +1,202 @@
package io.gitlab.jfronny.inceptum.common.api;
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;
import javax.xml.parsers.*;
import javax.xml.stream.XMLStreamException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.util.*;
public class MavenApi {
private static final DocumentBuilder FACTORY;
private static final Set<String> RUNTIME_SCOPES = Set.of("compile", "runtime");
static {
try {
FACTORY = DocumentBuilderFactory.newInstance().newDocumentBuilder();
} catch (ParserConfigurationException e) {
throw new RuntimeException("Could not create document builder", e);
}
}
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, 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();
if (!"project".equals(doc.getDocumentElement().getNodeName())) throw new IOException("Illegal document name");
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" -> 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 (groupId == null) {
for (Node child : children(node)) {
switch (child.getNodeName()) {
case "groupId" -> {
if (groupId == null) {
groupId = child.getTextContent();
}
}
case "version" -> {
if (version == null) {
version = child.getTextContent();
}
}
}
}
}
}
case "groupId" -> groupId = node.getTextContent();
case "artifactId" -> {
artifactId = node.getTextContent();
}
case "version" -> {
version = node.getTextContent();
}
case "packaging" -> packaging = node.getTextContent();
case "dependencies" -> {
dependencies = new LinkedList<>();
for (Node dep : children(node)) {
MavenDependency resolved = parseDependency(dep);
if (resolved != null) {
dependencies.add(resolved);
}
}
}
case "classifier" -> classifier = node.getTextContent();
default -> {}
}
}
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 {
String groupId = null;
String artifactId = null;
String version = null;
String scope = null;
for (Node node : children(doc)) {
switch (node.getNodeName()) {
case "groupId" -> groupId = node.getTextContent();
case "artifactId" -> artifactId = node.getTextContent();
case "version" -> version = node.getTextContent();
case "scope" -> {
scope = node.getTextContent();
if (!RUNTIME_SCOPES.contains(scope)) return null;
}
case "optional" -> {
if (node.getTextContent().equals("true")) return null;
}
}
}
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 " + groupId + ":" + artifactId + " lacks version");
}
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) {
if (node.getNodeType() == Node.TEXT_NODE && node.getTextContent().isBlank()) return true;
if (node.getNodeType() == Node.COMMENT_NODE) return true;
return false;
}
}

View File

@ -0,0 +1,5 @@
package io.gitlab.jfronny.inceptum.common.model.inceptum;
public enum UpdateChannel {
Stable, CI
}

View File

@ -0,0 +1,18 @@
package io.gitlab.jfronny.inceptum.common.model.inceptum;
import io.gitlab.jfronny.gson.compile.annotations.GSerializable;
import io.gitlab.jfronny.inceptum.common.GsonPreset;
import java.util.Map;
import java.util.Set;
@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

@ -0,0 +1,11 @@
package io.gitlab.jfronny.inceptum.common.model.inceptum;
import io.gitlab.jfronny.gson.compile.annotations.GSerializable;
import io.gitlab.jfronny.inceptum.common.GsonPreset;
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

@ -0,0 +1,25 @@
package io.gitlab.jfronny.inceptum.common.model.maven;
import java.util.Iterator;
import java.util.Set;
public record DependencyNode(String name, Set<DependencyNode> dependencies) {
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
generateTree(sb, "", "");
return sb.toString();
}
private void generateTree(StringBuilder sb, String prefix, String childrenPrefix) {
sb.append(prefix).append(name).append('\n');
for (Iterator<DependencyNode> it = dependencies.iterator(); it.hasNext(); ) {
DependencyNode next = it.next();
if (it.hasNext()) {
next.generateTree(sb, childrenPrefix + "├── ", childrenPrefix + "");
} else {
next.generateTree(sb, childrenPrefix + "└── ", childrenPrefix + " ");
}
}
}
}

View File

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

View File

@ -0,0 +1,15 @@
package io.gitlab.jfronny.inceptum.common.model.maven;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public record Pom(String modelVersion,
String groupId,
String artifactId,
String version,
String classifier,
String snapshotVersion,
@Nullable String packaging,
@Nullable List<MavenDependency> dependencies) {
}

View File

@ -1,8 +0,0 @@
package io.gitlab.jfronny.inceptum.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.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.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.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.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.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.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.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.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,25 +0,0 @@
package io.gitlab.jfronny.inceptum.model.inceptum;
import java.util.Map;
public class Config {
public boolean snapshots = false;
public boolean darkTheme = false;
public boolean enforceAccount = true;
public String lastAccount;
public String offlineAccountLastName = null;
public UpdateChannel channel = UpdateChannel.Stable;
public GitConfig git = new GitConfig();
public static class GitConfig {
public Map<String, GitAuth> instanceAuths;
public String commitUsername = "Inceptum";
public String commitMail = "inceptum@jfronny.gitlab.io";
public Boolean signCommits = false;
public static class GitAuth {
public String username;
public String password;
}
}
}

View File

@ -1,11 +0,0 @@
package io.gitlab.jfronny.inceptum.model.inceptum;
import io.gitlab.jfronny.commons.ComparableVersion;
public class InceptumVersion {
public ComparableVersion version;
public String flavor;
public Boolean isPublic;
public Boolean isRelease;
public Integer jvm;
}

View File

@ -1,5 +0,0 @@
package io.gitlab.jfronny.inceptum.model.inceptum;
public enum UpdateChannel {
Stable, CI
}

View File

@ -1,6 +0,0 @@
package io.gitlab.jfronny.inceptum.model.inceptum;
import io.gitlab.jfronny.commons.ComparableVersion;
public record UpdateInfo(String url, String sha1, ComparableVersion newVersion) {
}

View File

@ -1,44 +0,0 @@
package io.gitlab.jfronny.inceptum.util;
import io.gitlab.jfronny.inceptum.model.inceptum.Config;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
public class ConfigHolder {
public static Config CONFIG;
public static void load() throws IOException {
if (!Files.exists(MetaHolder.CONFIG_PATH.getParent())) Files.createDirectories(MetaHolder.CONFIG_PATH.getParent());
if (!Files.exists(MetaHolder.CONFIG_PATH)) {
Path gLaunch2 = MetaHolder.BASE_PATH.resolve("glaunch2.json");
if (Files.exists(gLaunch2)) {
Files.move(gLaunch2, MetaHolder.CONFIG_PATH);
}
else {
Utils.writeObject(MetaHolder.CONFIG_PATH, new Config());
}
}
CONFIG = Utils.loadObject(MetaHolder.CONFIG_PATH, Config.class);
boolean changed = false;
if (CONFIG.git == null) {
CONFIG.git = new Config.GitConfig();
changed = true;
}
if (CONFIG.git.instanceAuths == null) {
CONFIG.git.instanceAuths = new HashMap<>();
changed = true;
}
if (changed) saveConfig();
}
public static void saveConfig() {
try {
Utils.writeObject(MetaHolder.CONFIG_PATH, CONFIG);
} catch (IOException e) {
Utils.LOGGER.error("Could not save config", e);
}
}
}

View File

@ -1,14 +0,0 @@
package io.gitlab.jfronny.inceptum.util;
import io.gitlab.jfronny.commons.HttpUtils;
import io.gitlab.jfronny.commons.serialize.gson.api.GsonHolder;
import java.io.IOException;
public class InceptumEnvironmentInitializer {
public static void initialize() throws IOException {
HttpUtils.setUserAgent("jfmods/inceptum/" + MetaHolder.VERSION.version.toString());
GsonHolder.register();
ConfigHolder.load();
}
}

View File

@ -1,27 +0,0 @@
package io.gitlab.jfronny.inceptum.util;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.AppenderBase;
import java.time.Instant;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class MapAppender extends AppenderBase<ILoggingEvent> {
public static final ConcurrentMap<String, ILoggingEvent> EVENT_MAP = new ConcurrentHashMap<>();
public static final Set<String> LOG = new LinkedHashSet<>();
@Override
protected void append(ILoggingEvent event) {
EVENT_MAP.put(Instant.now().toString(), event);
LOG.add(event.getLevel().toString() +
" | " +
Instant.now().toString() +
" | [" +
event.getThreadName() +
"] " +
event.getFormattedMessage());
}
}

View File

@ -1,81 +0,0 @@
package io.gitlab.jfronny.inceptum.util;
import io.gitlab.jfronny.commons.*;
import io.gitlab.jfronny.commons.serialize.gson.api.*;
import io.gitlab.jfronny.inceptum.model.inceptum.*;
import java.io.*;
import java.net.*;
import java.nio.file.*;
public class MetaHolder {
public static final InceptumVersion VERSION;
public static final Path BASE_PATH;
static {
try (InputStream is = MetaHolder.class.getClassLoader().getResourceAsStream("version.json");
InputStreamReader isr = new InputStreamReader(is)) {
VERSION = GsonHolder.getGson().fromJson(isr, InceptumVersion.class);
if (System.getProperty("inceptum.base") == null) {
Path runDir = getRunDir();
BASE_PATH = VERSION.isRelease && !Files.exists(runDir)
? getConfigPath().resolve("Inceptum")
: runDir;
}
else {
BASE_PATH = Paths.get(System.getProperty("inceptum.base"));
}
} catch (IOException e) {
throw new RuntimeException("Could not get version info", e);
}
}
public static final Path ACCOUNTS_PATH = BASE_PATH.resolve("accounts.json");
public static final Path CONFIG_PATH = BASE_PATH.resolve("inceptum.json");
public static final Path NATIVES_DIR = BASE_PATH.resolve("natives");
public static final Path FORCE_LOAD_PATH = NATIVES_DIR.resolve("forceload");
public static final Path LIBRARIES_DIR = BASE_PATH.resolve("libraries");
public static final Path ASSETS_DIR = BASE_PATH.resolve("assets");
public static final Path INSTANCE_DIR = BASE_PATH.resolve("instances");
public static final Path CACHE_DIR = BASE_PATH.resolve("cache");
private static boolean isWrapper = false;
private static Path getConfigPath() {
return switch (OSUtils.TYPE) {
case WINDOWS -> Paths.get(System.getenv("APPDATA"));
case MAC_OS -> Paths.get(System.getProperty("user.home")).resolve("Library").resolve("Application Support");
case LINUX -> {
String s = System.getenv().get("XDG_CONFIG_HOME");
if (s == null)
yield Paths.get(System.getProperty("user.home")).resolve(".config");
else
yield Paths.get(s);
}
};
}
private static Path getRunDir() {
Path simpleRunDir = Path.of(".").resolve("run");
if (Files.exists(simpleRunDir)) return simpleRunDir;
URL url = MetaHolder.class.getProtectionDomain().getCodeSource().getLocation();
String p = url.getPath();
if (p.endsWith(".jar") && !p.endsWith("/build/libs/wrapper-" + VERSION.version + ".jar")) {
try {
return new File(url.toURI()).toPath().getParent().resolve("run");
} catch (URISyntaxException e) {
Utils.LOGGER.error("Could not resolve dev env run dir");
return simpleRunDir;
}
} else {
System.out.println("Not running in a jar, using ./run");
return simpleRunDir;
}
}
public static void setWrapperFlag() {
isWrapper = true;
}
public static boolean isWrapper() {
return isWrapper;
}
}

View File

@ -1,85 +0,0 @@
package io.gitlab.jfronny.inceptum.util;
import io.gitlab.jfronny.commons.ComparableVersion;
import io.gitlab.jfronny.commons.HttpUtils;
import io.gitlab.jfronny.inceptum.model.gitlab.*;
import io.gitlab.jfronny.inceptum.model.inceptum.InceptumVersion;
import io.gitlab.jfronny.inceptum.model.inceptum.UpdateChannel;
import io.gitlab.jfronny.inceptum.model.inceptum.UpdateInfo;
import io.gitlab.jfronny.inceptum.util.api.GitlabApi;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.function.Consumer;
public class UpdateChecker {
private static final long PROJECT_ID = 30862253L;
public static UpdateInfo check(UpdateChannel channel, ComparableVersion current, String flavor, Consumer<UpdateChannel> channelInvalid) {
try {
int jvm = Runtime.version().feature();
if (flavor.equals("custom")) {
Utils.LOGGER.error("Custom build, skipping update check");
return null;
}
GitlabProject project = GitlabApi.getProject(PROJECT_ID);
GitlabPackage experimental = null;
ComparableVersion experimentalVersion = null;
GitlabPackage stable = null;
ComparableVersion stableVersion = null;
packageLoop: for (GitlabPackage info : GitlabApi.getPackages(project)) {
if (info.status.equals("default") && info.name.equals("io/gitlab/jfronny/inceptum/Inceptum")) {
for (GitlabPipeline pipeline : info.pipelines) {
for (GitlabJob job : GitlabApi.getJobs(project, pipeline.id)) {
if (!job.name.equals("build_test")) continue;
try {
InceptumVersion iv = HttpUtils.get(GitlabApi.PROJECTS + project.id + "/jobs/" + job.id + "/artifacts/version.json").sendSerialized(InceptumVersion.class);
if (iv.jvm > jvm) {
Utils.LOGGER.error("A newer JVM is required to use the latest inceptum version. Please update!");
continue packageLoop;
}
}
catch (IOException | URISyntaxException e) {
continue packageLoop;
}
}
if (pipeline.ref.equals("master") && pipeline.status.equals("success")) {
ComparableVersion cvNew = new ComparableVersion(info.version);
if (experimentalVersion == null || experimentalVersion.compareTo(cvNew) < 0) {
experimental = info;
experimentalVersion = cvNew;
}
if (!info.version.contains("-") && (stableVersion == null || stableVersion.compareTo(cvNew) < 0)) {
stable = info;
stableVersion = cvNew;
}
}
}
}
}
if (experimental == null) {
throw new IOException("No version could be found");
}
else if (stable == null && channel == UpdateChannel.Stable) {
channel = UpdateChannel.CI;
channelInvalid.accept(channel);
}
GitlabPackage info = switch (channel) {
case CI -> experimental;
case Stable -> stable;
};
Utils.LOGGER.info("Latest version is " + info.version + ", current is " + current);
if (current.compareTo(new ComparableVersion(info.version)) >= 0) {
Utils.LOGGER.info("Up-to-date");
return null;
}
GitlabPackageFile file = GitlabApi.getFile(project, info, f -> f.file_name.endsWith('-' + flavor + ".jar"));
if (file == null)
Utils.LOGGER.error("No valid package was discovered");
else return new UpdateInfo("https://gitlab.com/" + project.path_with_namespace + "/-/package_files/" + file.id + "/download", file.file_sha1, new ComparableVersion(info.version));
} catch (IOException | URISyntaxException e) {
Utils.LOGGER.error("Could not check for updates", e);
}
return null;
}
}

View File

@ -1,275 +0,0 @@
package io.gitlab.jfronny.inceptum.util;
import io.gitlab.jfronny.commons.*;
import io.gitlab.jfronny.commons.io.MultiAccessFileSystem;
import io.gitlab.jfronny.commons.log.Logger;
import io.gitlab.jfronny.commons.serialize.gson.api.GsonHolder;
import io.gitlab.jfronny.commons.throwable.ThrowingConsumer;
import io.gitlab.jfronny.commons.throwable.ThrowingSupplier;
import io.gitlab.jfronny.inceptum.util.cache.GsonFileCache;
import java.awt.*;
import java.io.*;
import java.lang.reflect.Type;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.List;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Stream;
public class Utils {
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();
private static final GsonFileCache OBJECT_CACHE = new GsonFileCache(MetaHolder.CACHE_DIR);
public static byte[] downloadData(String url) throws IOException, URISyntaxException {
try (InputStream is = HttpUtils.get(url).sendInputStream()) {
return is.readAllBytes();
}
}
public static byte[] downloadData(String url, String sha1) throws IOException, URISyntaxException {
byte[] buf = downloadData(url);
if (sha1 == null) return buf;
if (!HashUtils.sha1(buf).equals(sha1)) throw new IOException("Invalid hash");
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, Class<T> type, boolean cache) throws IOException {
return downloadObject(url, () -> HttpUtils.get(url).sendString(), type, cache);
}
public static <T> T downloadObject(String url, Type type) throws IOException {
return downloadObject(url, type, 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, Type type, boolean cache) throws IOException {
return downloadObject(url, () -> HttpUtils.get(url).sendString(), type, 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 {
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();
} catch (Exception e) {
throw new IOException("Could not download object and no cache exists", e);
}
}
public static String buildUrl(String host, String url, Map<String, String> params) {
StringBuilder res = new StringBuilder(host);
if (res.toString().endsWith("/")) res = new StringBuilder(res.substring(0, res.length() - 1));
if (url.startsWith("/")) res.append(url);
else res.append("/").append(url);
int i = 0;
for (Map.Entry<String, String> entry : params.entrySet()) {
res.append(i++ == 0 ? '?' : '&')
.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8))
.append('=')
.append(URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8));
}
return res.toString();
}
public static <T> T loadObject(Path file, Class<T> type) throws IOException {
try (BufferedReader br = Files.newBufferedReader(file)) {
return GsonHolder.getGson().fromJson(br, type);
}
}
public static <T> T loadObject(Path file, Type type) throws IOException {
try (BufferedReader br = Files.newBufferedReader(file)) {
return GsonHolder.getGson().fromJson(br, type);
}
}
public static <T> void writeObject(Path file, T object) throws IOException {
try (BufferedWriter bw = Files.newBufferedWriter(file, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
GsonHolder.getGson().toJson(object, bw);
}
}
public static String downloadString(String url, String sha1) throws IOException, URISyntaxException {
return new String(downloadData(url, sha1), StandardCharsets.UTF_8);
}
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));
}
public static void downloadFile(String url, String sha1, Path path) throws IOException, URISyntaxException {
if (!Files.exists(path.getParent())) Files.createDirectories(path.getParent());
Files.write(path, downloadData(url, sha1));
}
public static void clearDirectory(Path path) throws IOException {
clearDirectory(path, p -> true);
}
public static void clearDirectory(Path path, Predicate<Path> shouldDelete) throws IOException {
if (!Files.exists(path)) return;
try {
Utils.ls(path, p -> {
if (Files.isDirectory(p)) {
try {
if (shouldDelete.test(p))
deleteRecursive(p, shouldDelete);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
else {
try {
if (shouldDelete.test(p))
Files.delete(p);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
} catch (Throwable t) {
throw new IOException("Could not clear directory", t);
}
}
public static void deleteRecursive(Path path) throws IOException {
deleteRecursive(path, p -> true);
}
public static void deleteRecursive(Path path, Predicate<Path> shouldDelete) throws IOException {
if (Files.isDirectory(path)) {
Files.walkFileTree(path, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
FileVisitResult fv = super.visitFile(file, attrs);
if (fv != FileVisitResult.CONTINUE) return fv;
if (shouldDelete.test(file))
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
FileVisitResult fv = super.postVisitDirectory(dir, exc);
if (fv != FileVisitResult.CONTINUE) return fv;
if (shouldDelete.test(dir) && ls(dir).isEmpty()) {
Files.delete(dir);
}
return FileVisitResult.CONTINUE;
}
});
}
else Files.delete(path);
}
public static void copyContent(Path source, Path destination) throws IOException {
copyContent(source, destination, StandardCopyOption.COPY_ATTRIBUTES);
}
public static void copyContent(Path source, Path destination, CopyOption... copyOptions) throws IOException {
if (!Files.exists(destination)) Files.createDirectories(destination);
if (Files.isDirectory(source)) {
try (Stream<Path> paths = Files.walk(source)) {
for (Path path : paths.toList()) {
if (source.equals(path)) continue;
Path d = destination.resolve(source.relativize(path).toString());
if (Files.exists(d)) continue;
if (Files.isDirectory(d)) Files.createDirectories(d);
else Files.copy(path, d);
}
}
} else if (Files.exists(source)) {
Path target = destination.resolve(source.getFileName().toString());
if (!Files.exists(target)
|| Arrays.asList(copyOptions).contains(StandardCopyOption.REPLACE_EXISTING))
Files.copy(source, target, copyOptions);
} else {
throw new FileNotFoundException(source.toAbsolutePath().toString());
}
}
public static void openWebBrowser(URI uri) {
try {
if (OSUtils.TYPE == OSUtils.Type.LINUX && OSUtils.executablePathContains("xdg-open")) {
Runtime.getRuntime().exec(new String[]{"xdg-open", uri.toString()});
} else if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
Desktop.getDesktop().browse(uri);
}
} catch (Exception e) {
Utils.LOGGER.error("Error opening web browser!", e);
}
}
public static void openFile(File file) {
try {
if (OSUtils.TYPE == OSUtils.Type.LINUX && OSUtils.executablePathContains("xdg-open")) {
Runtime.getRuntime().exec(new String[]{"xdg-open", file.getAbsoluteFile().toString()});
} else if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
Desktop.getDesktop().open(file);
}
} catch (Exception e) {
Utils.LOGGER.error("Error opening web browser!", e);
}
}
public static List<Path> ls(Path dir) throws IOException {
try (Stream<Path> sp = Files.list(dir)) {
return sp.toList();
}
}
public static String[] lsVi(Path dir) throws IOException {
try (Stream<Path> sp = Files.list(dir)) {
return sp.map(p -> Files.isDirectory(p) ? p.getFileName().toString() + "/" : p.getFileName().toString()).toArray(String[]::new);
}
}
public static List<Path> ls(Path dir, Predicate<Path> predicate) throws IOException {
try (Stream<Path> sp = Files.list(dir); Stream<Path> fi = sp.filter(predicate)) {
return fi.toList();
}
}
public static <TEx extends Exception> void ls(Path dir, ThrowingConsumer<Path, TEx> consumer) throws IOException, TEx {
try (Stream<Path> sp = Files.list(dir)) {
for (Path path : sp.toList()) consumer.accept(path);
}
}
private static final Map<Path, MultiAccessFileSystem> zipFsCache = new HashMap<>();
public static FileSystem openZipFile(Path zip, boolean create) throws IOException, URISyntaxException {
synchronized (zipFsCache) {
if (!zipFsCache.containsKey(zip) || zipFsCache.get(zip).isClosed()) {
URI fileUri = zip.toUri();
zipFsCache.put(zip, MultiAccessFileSystem.create(new URI("jar:" + fileUri.getScheme(), fileUri.getPath(), null), create ? Map.of("create", "true") : Map.of(), SYSTEM_LOADER));
}
return zipFsCache.get(zip).createLens();
}
}
public static void setSystemLoader(ClassLoader loader) {
SYSTEM_LOADER = loader;
}
}

View File

@ -1,47 +0,0 @@
package io.gitlab.jfronny.inceptum.util.api;
import io.gitlab.jfronny.commons.HttpUtils;
import io.gitlab.jfronny.gson.reflect.TypeToken;
import io.gitlab.jfronny.inceptum.model.gitlab.GitlabJob;
import io.gitlab.jfronny.inceptum.model.gitlab.GitlabPackage;
import io.gitlab.jfronny.inceptum.model.gitlab.GitlabPackageFile;
import io.gitlab.jfronny.inceptum.model.gitlab.GitlabProject;
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/";
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,55 +0,0 @@
package io.gitlab.jfronny.inceptum.util.cache;
import io.gitlab.jfronny.commons.throwable.ThrowingSupplier;
import io.gitlab.jfronny.inceptum.util.Utils;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.ConcurrentHashMap;
public class GsonFileCache {
private final ConcurrentHashMap<String, Object> container = new ConcurrentHashMap<>();
private final Path cacheDir;
public GsonFileCache(Path cacheDir) {
this.cacheDir = cacheDir;
}
public void remove(String key) {
container.remove(key);
try {
Files.delete(cacheDir.resolve(key));
} catch (IOException e) {
Utils.LOGGER.error("Could not remove cache entry", e);
}
}
public void clear() {
container.clear();
try {
Utils.ls(cacheDir, Files::delete);
} catch (IOException e) {
Utils.LOGGER.error("Could not clear cache backend", e);
}
}
public <T, TEx extends Throwable> T get(String key, ThrowingSupplier<T, TEx> builder, Type klazz) throws TEx {
if (!container.containsKey(key)) {
Path cd = cacheDir.resolve(key);
if (Files.exists(cd)) {
try {
T value = Utils.loadObject(cd, klazz);
Utils.writeObject(cd, value);
container.put(key, value);
} catch (IOException e) {
Utils.LOGGER.error("Could not read cache", e);
}
}
else container.put(key, builder.get());
}
//noinspection unchecked
return (T) container.get(key);
}
}

View File

@ -1,25 +0,0 @@
package io.gitlab.jfronny.inceptum.util.cache;
import io.gitlab.jfronny.commons.throwable.ThrowingSupplier;
import java.util.concurrent.ConcurrentHashMap;
public class MemoryCache<TKey, TValue> {
private final ConcurrentHashMap<TKey, TValue> container = new ConcurrentHashMap<>();
public void remove(TKey key) {
container.remove(key);
}
public void clear() {
container.clear();
}
public void write() {
}
public <TEx extends Throwable> TValue get(TKey key, ThrowingSupplier<TValue, TEx> builder) throws TEx {
if (!container.containsKey(key)) container.put(key, builder.get());
return container.get(key);
}
}

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,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<statusListener class="ch.qos.logback.core.status.NopStatusListener" />
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{HH:mm:ss.SSS} %boldCyan(%thread) %boldGreen(%logger{0}) %highlight(%level) %msg%n</pattern>
</layout>
</appender>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>inceptum.log</file>
<append>false</append>
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{HH:mm:ss.SSS} %thread %logger{0} %level %msg%n</pattern>
</layout>
</appender>
<appender name="MEMORY" class="io.gitlab.jfronny.inceptum.util.MapAppender">
</appender>
<root level="info">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
<appender-ref ref="MEMORY" />
</root>
</configuration>

View File

@ -1,7 +0,0 @@
{
"version": "${version}",
"flavor": "${flavor}",
"isPublic": ${isPublic},
"isRelease": ${isRelease},
"jvm": ${jvm}
}

View File

@ -1,35 +1,31 @@
# 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.
Launching is performed through a custom ClassLoader and the wrapper command is prepended to the arguments provided by the user.
## The "wrapper" command
Inceptum internally uses this command to inform the main Inceptum jar that it was launched through the Inceptum Wrapper and should allow installing updates.
It sets an internal flag and launches the command provided in its arguments.
Manually invoking this command WILL cause problems, so a check has been implemented that should prevent this from starting Inceptum in unintended environments.
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 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
This command will go through every line in the file provided in its arguments and executes the command written there.
It is intended to be used in scripts that only want to run inceptum once, such as the systemd unit in the AUR package.
The batch command has the additional advantage of allowing caches to stay loaded between commands, making execution faster.
For example,
```
git pull icesrv
mod update all icesrv
run server restart icesrv
```
is equivalent to the bash script
```shell
#!/bin/sh
inceptum git pull icesrv
inceptum run server restart icesrv
run server restart server2
```
## The "git" command
The git command does NOT mirror the behavior of the actual git binary.
It merely provides subcommands for running common actions such as cloning, pulling, committing and pushing on instances using the included JGit
and performs additional sanitization.
Using actual git commands is recommended when the commands provided are not enough for a particular purpose.
is equivalent to the bash script
```shell
#!/bin/sh
inceptum mod update all icesrv
inceptum run server restart icesrv
inceptum run server restart server2
```

View File

@ -1,61 +1,44 @@
# File Formats
Inceptum uses several json formats to store metadata and configs.
All of these are subject to change, though automatic migrations will likely be provided.
## inceptum.json (Main Config)
```json
```json5
{
// Whether to show snapshots in the version selector for new instances
"snapshots": false,
// Whether to launch the GUI in dark mode.
// Whether to launch the GUI in dark mode
// Configurable in Settings->Dark Theme
"darkTheme": true,
// Whether to require an account to launch the game.
// Whether to require an account to launch the game
// Intended to allow running the game from USB sticks on constrained networks
"enforceAccount": false,
// The currently selected account.
// The currently selected account
// Used to launch the game
"lastAccount": "some UUID",
// The update channel. Either "CI" or "Stable".
// The last name used for an offline session
"offlineAccountLastName": "some name",
// The update channel. Either "CI" or "Stable"
// I personally recommend the CI channel as it gets the latest fixes and features quicker
"channel": "CI",
// Configuration for the git integration.
// This is also used during instance creation, but you can pretty much ignore it if you use an external client or don't create modpacks
"git": {
// Authentication for git
// Used when pushing or pulling from remote repos
"instanceAuths": {
// The name of an instance. This is equal to its directory name
"someInstance": {
// The username to use for authentication
"username": "yourusername@gmail.com",
// The password to use for authentication
"password": "SomePassword"
},
"someOtherInstance": {
"username": "yourusername@gmail.com",
"password": "SomePassword"
}
},
// The username to use when creating commits
"commitUsername": "Inceptum",
// The E-Mail address to use when creating commits. This default address doesn't exist btw
"commitMail": "inceptum@jfronny.gitlab.io",
// Whether to sign commits.
// If you want this for additional security, don't use the git integration
"signCommits": false
}
// The author name to add to packs where the metadata format requires specifying one
"authorName": "Inceptum"
}
```
## accounts.json
Do not EVER use this file manually!
NEVER upload it anywhere!
It stores your minecraft account login and CAN BE USED TO IMPERSONATE YOU!
## instance.json (Instance Metadata)
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")
@ -90,7 +73,8 @@ 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": [
@ -137,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

@ -1,48 +1,53 @@
# Installing
Inceptum can be installed in a number of ways, all of which are documented below.
## Simple installation
First, download the Inceptum build appropriate for your system:
- [Windows x86_64](https://gitlab.com/JFronny/inceptum/-/jobs/artifacts/master/raw/latest-windows.jar?job=build_test)
- [MacOS x86_64](https://gitlab.com/JFronny/inceptum/-/jobs/artifacts/master/raw/latest-macos.jar?job=build_test) (untested)
- [Linux x86_64](https://gitlab.com/JFronny/inceptum/-/jobs/artifacts/master/raw/latest-linux.jar?job=build_test)
- [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)
The following additional builds are also available but not recommended for normal use:
- [Windows/Mac/Linux x86_64 in one](https://gitlab.com/JFronny/inceptum/-/jobs/artifacts/master/raw/latest.jar?job=build_test)
- [Headless](https://gitlab.com/JFronny/inceptum/-/jobs/artifacts/master/raw/latest-nogui.jar?job=build_test)
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/).
Inceptum will use your config directory ($XDG_CONFIG_HOME, ~/.config/Inceptum, %APPDATA%\Inceptum or ~/Library/Application Support/Inceptum) for saving instances/caches/etc.
If you want them somewhere else, read on
### Config files in current directory
If the Inceptum jar detects a directory called "run" in the directory it is placed in,
it will use that instead of the users config directory.
Create a directory (= Folder) next to the inceptum jar.
### Config files in a custom location
You may specify the java VM parameter `-Dinceptum.base=$DIRECTORY` to use a custom directory for Inceptum.
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_test)
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_test)
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
## Installation from the AUR
Inceptum is available in the AUR as [`inceptum-git`](https://aur.archlinux.org/packages/inceptum-git)
If you use arch linux or a derivative, you may use this package instead of a manual installation.
It also includes a template systemd service which you can copy and customize to launch your minecraft server.
For more information on the syntax of this services config file, go [here](Commands.md)
## 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.

35
docs/Modules.md Normal file
View File

@ -0,0 +1,35 @@
# Modules
Inceptum is split into multiple separate but interdependent modules.
The purpose of this page is to list them, explain their purpose and where to get them
## common
This module contains common, platform-agnostic code shared between the launcher and the wrapper.
It can be obtained through the maven.
## launcher
This module contains common, platform-agnostic code between all frontends of the launcher.
It can be obtained through the maven.
## launcher-cli
This module contains the platform-agnostic command-line interface for the launcher
It can be obtained through the maven or the shadowed Inceptum jar
## launcher-imgui
This module contains a dear-imgui-based frontend for Inceptum.
Builds of this module are platform-specific and dependents must manually ensure the correct imgui and lwjgl natives are imported.
A build without natives can be obtained through the maven and a build with natives through the shadowed Inceptum jar
## launcher-dist/Inceptum
This module builds a shadowed jar of launcher-cli and launcher-imgui to be used by normal users.
It also adds additional, platform-specific commands to the CLI.
A shadowed build can be obtained as "Inceptum" from maven, a build with dependencies as "launcher-dist"
Windows users can also obtain a binary built using fabric-installer-native-bootstrap.
## launchwrapper
This module is added to the minecraft classpath and therefore independent of any other modules.
It handles loading forceload natives
## wrapper
This module serves the purpose of downloading the components necessary for executing Inceptum on the current platform.
A build with shadowed dependencies can be obtained through the maven (with the suffix "all") or as a jar.
Windows users can also obtain a binary built using fabric-installer-native-bootstrap.

View File

@ -1,4 +1,5 @@
# About
Inceptum is advanced FOSS Launcher for Minecraft written in Java
Features:

8
docs/SUMMARY.md Normal file
View File

@ -0,0 +1,8 @@
# Summary
- [About](./README.md)
- [Installing](./Installing.md)
- [CLI](./Commands.md)
- [Syncing instances](./Syncing.md)
- [File Formats](./FileFormats.md)
- [Modules](./Modules.md)

View File

@ -1,14 +1,17 @@
# Syncing instances
Inceptum supports syncing repositories between instances through git.
This is roughly comparable to updatable modpacks in other launchers.
There are two workflows for this, you can choose one based on your use case.
## Uploading a local pack to a game server
This approach assumes that your intention is to have a game server running Inceptum
and to regularly push updates built and tested in an Inceptum instance on your PC.
To set this up, initialize a repo on your server and clone it locally
Server:
```shell
cd /srv/inceptum/instances
mkdir icesrv
@ -16,7 +19,9 @@ cd icesrv
git init
git config receive.denyCurrentBranch ignore
```
Client:
```shell
git clone inceptum@example.com:/srv/inceptum/instances/icesrv
cd icesrv
@ -26,7 +31,9 @@ git add .
git commit -m "Initial commit"
git push
```
After that, you can use a script similar to the following to push changes:
```shell
git push -u origin master
rconc icesrv stop
@ -34,10 +41,12 @@ sleep 1
ssh -t inceptum@example.com "cd /srv/inceptum/instances/icesrv; git reset --hard"
ssh -t admin@example.com "sudo systemctl restart inceptum-icesrv"
```
icesrv is the name of the instance in this example and inceptum-icesrv is a systemd service based on the one included
in the AUR package.
## Providing a pack to multiple users
To do this, create a new repository on a git hosting site (like GitHub or GitLab) and push your local instance there.
Since a git repository is created for every instance, simply following the instructions for pushing an existing
repository or using any graphical tool is enough to set this up.
@ -45,5 +54,5 @@ repository or using any graphical tool is enough to set this up.
Users can add an instance created this way in the GUI under File->New Instance->Inceptum.
To update the pack, simply create a new commit and push it to your repository.
Users can then update the pack under Edit->Git->Pull or using the Inceptum CLI.
Users can then update the pack by pulling the changes into their instance directory through the normal git CLI.
You may also export the pack to other formats to upload it on sites like CurseForge

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

@ -0,0 +1,11 @@
plugins {
inceptum.application
}
application {
mainClass.set("io.gitlab.jfronny.inceptum.cli.CliMain")
}
dependencies {
implementation(projects.launcher)
}

View File

@ -0,0 +1,61 @@
package io.gitlab.jfronny.inceptum.cli;
import io.gitlab.jfronny.inceptum.common.MetaHolder;
import io.gitlab.jfronny.inceptum.common.Utils;
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;
import java.nio.file.Path;
import java.util.List;
public abstract class BaseInstanceCommand extends Command {
protected BaseInstanceCommand(String help, String usage, List<String> aliases, List<Command> subCommands) {
super(help, mutateUsage(usage), aliases, subCommands);
}
private static String mutateUsage(String usage) {
StringBuilder sb = new StringBuilder();
for (String s : usage.split("\n")) {
if (s.isBlank())
sb.append("\n<instance>");
else
sb.append("\n<instance> ").append(s);
}
return sb.substring(1);
}
@Override
protected void invoke(CommandArgs args) throws Exception {
if (args.length == 0) {
Utils.LOGGER.error("You must specify an instance to commit in");
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, Instance instance) throws Exception;
}

View File

@ -0,0 +1,45 @@
package io.gitlab.jfronny.inceptum.cli;
import io.gitlab.jfronny.inceptum.common.Utils;
import io.gitlab.jfronny.inceptum.launcher.LauncherEnv;
import io.gitlab.jfronny.inceptum.launcher.api.account.MicrosoftAccount;
import java.io.IOException;
import java.util.Scanner;
import java.util.function.Consumer;
public class CliEnvBackend implements LauncherEnv.EnvBackend {
@Override
public void showError(String message, String title) {
Utils.LOGGER.error(message);
}
@Override
public void showError(String message, Throwable t) {
Utils.LOGGER.error(message, t);
}
@Override
public void showInfo(String message, String title) {
Utils.LOGGER.info(message);
}
@Override
public void showOkCancel(String message, String title, Runnable ok, Runnable cancel, boolean defaultCancel) {
Utils.LOGGER.info(message);
if (!defaultCancel) ok.run();
}
@Override
public void getInput(String prompt, String details, String defaultValue, Consumer<String> ok, Runnable cancel) throws IOException {
Scanner scanner = new Scanner(System.in);
System.out.println(prompt);
System.out.print("> ");
ok.accept(scanner.nextLine());
}
@Override
public void showLoginRefreshPrompt(MicrosoftAccount account) {
Utils.LOGGER.error("Could not launch using the selected account as its login has expired. Please log in again through the UI!");
}
}

View File

@ -0,0 +1,56 @@
package io.gitlab.jfronny.inceptum.cli;
import io.gitlab.jfronny.inceptum.cli.commands.*;
import io.gitlab.jfronny.inceptum.common.*;
import io.gitlab.jfronny.inceptum.launcher.LauncherEnv;
import java.util.*;
public class CliMain {
public static Command DEFAULT = new HelpCommand();
public static final List<Command> KNOWN_COMMANDS = new LinkedList<>(List.of(
DEFAULT,
new LaunchCommand(),
new ListCommand(),
new ModCommand(),
new ImportCommand(),
new ExportCommand(),
new JvmStateCommand(),
new BatchCommand()
));
public static final Command COMMANDS_ROOT = new Command("Root command", "<command>", List.of(), KNOWN_COMMANDS) {
@Override
protected void invoke(CommandArgs args) {
throw new RuntimeException("Could not find command: " + args.get(0));
}
@Override
public CommandResolution resolve(CommandArgs args) {
if (args.length == 0) return new CommandResolution(DEFAULT, args, new ArrayList<>());
return super.resolve(args);
}
};
public static CommandResolution resolve(String[] args) {
return COMMANDS_ROOT.resolve(new CommandArgs(Arrays.asList(args)));
}
public static void main(String[] args) throws Exception {
LauncherEnv.initialize(new CliEnvBackend());
CommandResolution command = resolve(args);
if (command.command().enableLog()) {
Utils.LOGGER.info("Launching Inceptum v" + BuildMetadata.VERSION);
Utils.LOGGER.info("Loading from " + MetaHolder.BASE_PATH);
}
try {
command.invoke();
} catch (Exception e) {
Utils.LOGGER.error("Could not execute command", e);
} finally {
LauncherEnv.terminate();
}
}
}

View File

@ -1,4 +1,4 @@
package io.gitlab.jfronny.inceptum.frontend.cli;
package io.gitlab.jfronny.inceptum.cli;
import java.util.*;

View File

@ -1,16 +1,14 @@
package io.gitlab.jfronny.inceptum.frontend.cli;
package io.gitlab.jfronny.inceptum.cli;
import java.util.*;
public class CommandArgs implements Iterable<String> {
protected final List<String> args;
public final int length;
public final boolean wrapped;
public CommandArgs(List<String> args, boolean wrapped) {
public CommandArgs(List<String> args) {
this.args = List.copyOf(args);
this.length = args.size();
this.wrapped = wrapped;
}
public boolean contains(String param) {
@ -50,11 +48,7 @@ public class CommandArgs implements Iterable<String> {
}
public CommandArgs subArgs() {
return subArgs(wrapped);
}
public CommandArgs subArgs(boolean wrapped) {
return new CommandArgs(args.subList(1, args.size()), wrapped);
return new CommandArgs(args.subList(1, args.size()));
}
@Override

View File

@ -1,4 +1,4 @@
package io.gitlab.jfronny.inceptum.frontend.cli;
package io.gitlab.jfronny.inceptum.cli;
import java.util.List;

View File

@ -1,4 +1,4 @@
package io.gitlab.jfronny.inceptum.frontend.cli;
package io.gitlab.jfronny.inceptum.cli;
import java.util.ArrayList;
import java.util.List;
@ -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,15 +1,10 @@
package io.gitlab.jfronny.inceptum.frontend.cli.commands;
package io.gitlab.jfronny.inceptum.cli.commands;
import io.gitlab.jfronny.commons.ArgumentsTokenizer;
import io.gitlab.jfronny.inceptum.frontend.cli.Command;
import io.gitlab.jfronny.inceptum.frontend.cli.CommandArgs;
import io.gitlab.jfronny.inceptum.frontend.cli.CommandResolution;
import io.gitlab.jfronny.inceptum.frontend.cli.Commands;
import io.gitlab.jfronny.inceptum.util.Utils;
import io.gitlab.jfronny.inceptum.cli.*;
import io.gitlab.jfronny.inceptum.common.Utils;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.*;
import java.util.LinkedHashSet;
import java.util.Set;
@ -37,9 +32,7 @@ public class BatchCommand extends Command {
}
try {
for (String line : Files.readAllLines(p)) {
CommandResolution resolution = Commands.resolve(ArgumentsTokenizer.tokenize(line), args.wrapped);
if (resolution.command() instanceof WrapperCommand)
throw new Exception("You may not use the wrapper command in a command batch");
CommandResolution resolution = CliMain.resolve(ArgumentsTokenizer.tokenize(line));
resolved.add(resolution);
}
} catch (Exception e) {

View File

@ -0,0 +1,58 @@
package io.gitlab.jfronny.inceptum.cli.commands;
import io.gitlab.jfronny.inceptum.cli.*;
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.Paths;
import java.util.List;
public class ExportCommand extends BaseInstanceCommand {
public ExportCommand() {
this(List.of("export"), List.of(
new ExportCommand(List.of("curseforge", "cf"), List.of()),
new ModrinthExportCommand(List.of("modrinth", "mr"), List.of()),
new MultiMCExportCommand(List.of("multimc", "mmc"), List.of())
));
}
private ExportCommand(List<String> aliases, List<Command> subCommands) {
super("Export a CurseForge instance", "<export path> <version>", aliases, subCommands);
}
@Override
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 > 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 {
public MultiMCExportCommand(List<String> aliases, List<Command> subCommands) {
super("Export a MultiMC instance", "<export path>", aliases, subCommands);
}
@Override
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");
Exporters.MULTI_MC.generate(new ProcessState(), instance, Paths.get(args.get(0)));
}
}
private static class ModrinthExportCommand extends BaseInstanceCommand {
public ModrinthExportCommand(List<String> aliases, List<Command> subCommands) {
super("Export a Modrinth instance", "<export path> <version>", aliases, subCommands);
}
@Override
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 > 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

@ -1,7 +1,7 @@
package io.gitlab.jfronny.inceptum.frontend.cli.commands;
package io.gitlab.jfronny.inceptum.cli.commands;
import io.gitlab.jfronny.inceptum.frontend.cli.*;
import io.gitlab.jfronny.inceptum.util.MetaHolder;
import io.gitlab.jfronny.inceptum.cli.*;
import io.gitlab.jfronny.inceptum.common.BuildMetadata;
import java.util.List;
@ -9,19 +9,19 @@ public class HelpCommand extends Command {
public HelpCommand() {
super("Displays this screen", "[command]", "help");
}
@Override
protected void invoke(CommandArgs args) {
HelpBuilder help = new HelpBuilder();
if (args.length == 0) {
printHeader();
System.out.println("\nCommands:");
Commands.COMMANDS_ROOT.buildHelp(help);
}
else {
CommandResolution resolution = Commands.COMMANDS_ROOT.resolve(args);
CliMain.COMMANDS_ROOT.buildHelp(help);
} else {
CommandResolution resolution = CliMain.COMMANDS_ROOT.resolve(args);
if (resolution.resolvePath().isEmpty()) {
System.err.println("Could not find command matching your input");
invoke(new CommandArgs(List.of(), args.wrapped));
invoke(new CommandArgs(List.of()));
return;
}
printHeader();
@ -32,6 +32,6 @@ public class HelpCommand extends Command {
}
private static void printHeader() {
System.out.println("Inceptum v" + MetaHolder.VERSION.version + " (" + MetaHolder.VERSION.flavor + ")");
System.out.println("Inceptum v" + BuildMetadata.VERSION);
}
}

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

@ -1,7 +1,7 @@
package io.gitlab.jfronny.inceptum.frontend.cli.commands;
package io.gitlab.jfronny.inceptum.cli.commands;
import io.gitlab.jfronny.inceptum.frontend.cli.Command;
import io.gitlab.jfronny.inceptum.frontend.cli.CommandArgs;
import io.gitlab.jfronny.inceptum.cli.Command;
import io.gitlab.jfronny.inceptum.cli.CommandArgs;
import java.net.URLClassLoader;
import java.util.Arrays;
@ -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

@ -0,0 +1,65 @@
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.api.account.AccountManager;
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.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class LaunchCommand extends BaseInstanceCommand {
private final boolean server;
private final boolean restart;
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", "instance.launch", "start"),
List.of(
new LaunchCommand("Explicitly launch a client", "client", false, false),
new LaunchCommand("Launch a server", "server", true, false,
new LaunchCommand("Restart the server if it stops", "restart", true, true)
)
));
this.server = false;
this.restart = false;
}
private LaunchCommand(String help, String name, boolean server, boolean restart, Command... subCommands) {
super(help, "[game arguments...]", List.of(name), Arrays.asList(subCommands));
this.server = server;
this.restart = restart;
}
@Override
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 (instance.isRunningLocked()) {
Utils.LOGGER.error("This instance is already running");
return;
}
if (args.length > 1) {
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(instance, Steps.createProcessState());
if (server) {
InstanceLauncher.launch(instance, LaunchType.Server, restart, AccountManager.NULL_AUTH);
} else {
AccountManager.loadAccounts();
InstanceLauncher.launchClient(instance);
}
}
}

View File

@ -0,0 +1,51 @@
package io.gitlab.jfronny.inceptum.cli.commands;
import io.gitlab.jfronny.commons.io.JFiles;
import io.gitlab.jfronny.inceptum.cli.Command;
import io.gitlab.jfronny.inceptum.cli.CommandArgs;
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;
import java.nio.file.Path;
import java.util.List;
public class ListCommand extends Command {
public ListCommand() {
super("Lists all available instances", "", "list", "ls");
}
@Override
protected void invoke(CommandArgs args) throws IOException {
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.CONFIG_NAME))) {
System.out.println("- Invalid instance: " + path + " (no instance metadata)");
continue;
}
System.out.println("- \"" + path.getFileName().toString() + "\"");
Instance instance;
try {
instance = InstanceList.read(path);
} catch (IOException e) {
Utils.LOGGER.error(" Could not load instance.json", e);
continue;
}
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.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

@ -1,17 +1,13 @@
package io.gitlab.jfronny.inceptum.frontend.cli.commands;
package io.gitlab.jfronny.inceptum.cli.commands;
import io.gitlab.jfronny.commons.io.JFiles;
import io.gitlab.jfronny.commons.throwable.ThrowingBiFunction;
import io.gitlab.jfronny.inceptum.frontend.cli.BaseInstanceCommand;
import io.gitlab.jfronny.inceptum.frontend.cli.Command;
import io.gitlab.jfronny.inceptum.frontend.cli.CommandArgs;
import io.gitlab.jfronny.inceptum.frontend.gui.window.AddModWindow;
import io.gitlab.jfronny.inceptum.model.inceptum.InstanceMeta;
import io.gitlab.jfronny.inceptum.util.ModManager;
import io.gitlab.jfronny.inceptum.util.PathUtil;
import io.gitlab.jfronny.inceptum.util.Utils;
import io.gitlab.jfronny.inceptum.util.mds.IWModDescription;
import io.gitlab.jfronny.inceptum.util.mds.ModsDirScanner;
import io.gitlab.jfronny.inceptum.util.source.ModSource;
import io.gitlab.jfronny.inceptum.cli.*;
import io.gitlab.jfronny.inceptum.common.Utils;
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.util.Unchecked;
import java.io.FileNotFoundException;
import java.io.IOException;
@ -49,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());
@ -67,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());
}
}
});
@ -80,6 +74,7 @@ public class ModCommand extends Command {
public static class ModRemoveCommand extends BaseInstanceCommand {
private final boolean ignoreDependencies;
public ModRemoveCommand() {
this("Removes mods from an instance", List.of("remove", "delete", "rm", "del"), List.of(
new ModRemoveCommand("Skip dependency checks", List.of("ignore-dependencies"), List.of(), true)
@ -92,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;
}
@ -103,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;
@ -132,7 +127,7 @@ public class ModCommand extends Command {
public ModUpdateCommand() {
this("Update mods in an instance", "<mod file>...", List.of("update", "upgrade", "up"), List.of(
new ModUpdateCommand("Update all mods in an instance", "", List.of("all"), List.of(), (args, instancePath) -> Set.copyOf(Utils.ls(instancePath)))
new ModUpdateCommand("Update all mods in an instance", "", List.of("all"), List.of(), (args, instancePath) -> Set.copyOf(JFiles.list(instancePath)))
), (args, instancePath) -> {
Set<Path> mods = new HashSet<>();
for (String arg : args) {
@ -153,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() + PathUtil.EXT_IMOD);
AddModWindow.DownloadMeta dm = AddModWindow.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

@ -0,0 +1,135 @@
import io.gitlab.jfronny.scripts.*
import java.nio.file.Files
import java.nio.file.Path
plugins {
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(projects.launcher)
implementation(projects.launcherCli)
implementation(projects.launcherImgui)
}
tasks.shadowJar {
archiveClassifier.set(rootProject.extra["flavorProp"] as String)
archiveBaseName.set("Inceptum")
exclude("about.html")
exclude("plugin.properties")
exclude("META-INF/**")
}
(components["java"] as AdhocComponentWithVariants).withVariantsFromConfiguration(configurations["shadowRuntimeElements"]) {
skip()
}
publishing {
publications {
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"
))
}
"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

@ -0,0 +1,24 @@
package io.gitlab.jfronny.inceptum;
import io.gitlab.jfronny.inceptum.cli.Command;
import io.gitlab.jfronny.inceptum.cli.CommandArgs;
import io.gitlab.jfronny.inceptum.imgui.GuiEnvBackend;
import io.gitlab.jfronny.inceptum.imgui.GuiMain;
import io.gitlab.jfronny.inceptum.launcher.LauncherEnv;
public class GuiCommand extends Command {
public GuiCommand() {
super("Displays the Inceptum UI", "", "gui", "show");
}
@Override
public void invoke(CommandArgs args) {
LauncherEnv.updateBackend(new GuiEnvBackend());
GuiMain.showGui();
}
@Override
public boolean enableLog() {
return true;
}
}

View File

@ -0,0 +1,14 @@
package io.gitlab.jfronny.inceptum;
import io.gitlab.jfronny.inceptum.cli.CliMain;
public class Inceptum {
private static final GuiCommand GUI_COMMAND = new GuiCommand();
public static void main(String[] args) throws Exception {
CliMain.KNOWN_COMMANDS.add(1, GUI_COMMAND);
CliMain.KNOWN_COMMANDS.add(new UpdateCheckCommand());
CliMain.DEFAULT = GUI_COMMAND;
CliMain.main(args);
}
}

View File

@ -0,0 +1,55 @@
package io.gitlab.jfronny.inceptum;
import io.gitlab.jfronny.inceptum.cli.Command;
import io.gitlab.jfronny.inceptum.cli.CommandArgs;
import io.gitlab.jfronny.inceptum.common.*;
import io.gitlab.jfronny.inceptum.common.model.inceptum.UpdateMetadata;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.List;
public class UpdateCheckCommand extends Command {
private final boolean install;
public UpdateCheckCommand() {
super("Checks for inceptum updates", "", List.of("update"), List.of(
new UpdateCheckCommand("Automatically install updates", "install", true)
));
install = false;
}
private UpdateCheckCommand(String help, String name, boolean install) {
super(help, "", name);
this.install = install;
}
@Override
protected void invoke(CommandArgs args) {
if (install && !MetaHolder.isWrapper()) {
Utils.LOGGER.error("Automatic updates are not supported without the wrapper");
return;
}
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 " + update);
try {
Updater.update(update, false);
} catch (IOException | URISyntaxException e) {
Utils.LOGGER.error("Could not download update", e);
}
} else {
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
}

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