Compare commits
106 Commits
Author | SHA1 | Date | |
---|---|---|---|
4e43d43777 | |||
b2e5ed83a4 | |||
787833f732 | |||
aae97feeca | |||
aede030a7f | |||
3e70e9e4e1 | |||
4629d5f68e | |||
7dd1d4dee2 | |||
f4ed8a4bcb | |||
af84674c26 | |||
4aa1f24461 | |||
9fd5d870cf | |||
e61df73dd3 | |||
c94c8b59af | |||
475717b6b4 | |||
5cc650921b | |||
5b79987dcf | |||
502026bb22 | |||
3746e30ec7 | |||
ad033711f9 | |||
f1f2e95dd2 | |||
9e1c20737d | |||
04d8121ca2 | |||
f4157bae09 | |||
c7eabc37d3 | |||
0bd675fc7c | |||
bcd4e34f7a | |||
7a7d009e29 | |||
e14294fdd6 | |||
98cc37405a | |||
b1b82c423a | |||
32a01547c0 | |||
e19d7cfb3b | |||
d7ddde6c4d | |||
f091cb7a2b | |||
5faa505235 | |||
8aa0555a2a | |||
a7a135c598 | |||
7f168ded43 | |||
cbba1ab70a | |||
ec67a80389 | |||
1759e5c3de | |||
b8f30247ea | |||
7805400e43 | |||
55b9e5986f | |||
7cefa88dcb | |||
8f46e4887f | |||
1be0d68a56 | |||
e9f8af5617 | |||
9d287c6521 | |||
7c4461a275 | |||
d8bd25b438 | |||
218e12714d | |||
3d73879bed | |||
d1c6b746f0 | |||
71e784fdff | |||
1db7bcde18 | |||
8f056468a7 | |||
25874c40f7 | |||
3aaf7e3652 | |||
17e89ba829 | |||
a89f51aa5d | |||
441a9b26b2 | |||
50e9db1fcc | |||
6151f0e71e | |||
8d45fdff84 | |||
2cb852c5cb | |||
fc256a1376 | |||
1353de777e | |||
3f273bf6f8 | |||
7a80132ab0 | |||
c027885364 | |||
4967410f51 | |||
14a23fdfed | |||
7284193981 | |||
eb9601d6cf | |||
5c9ce78ebf | |||
fe0c23b97b | |||
94dbaadaf1 | |||
cd3c8a1852 | |||
18c4953b36 | |||
0cb3df331a | |||
08bb13f994 | |||
a93f2c8411 | |||
e15ef8c485 | |||
2ca25a7bee | |||
bdd86c7683 | |||
442d462843 | |||
37872e6c79 | |||
dff05af62f | |||
05a18765c9 | |||
36f462597a | |||
c52f1f3350 | |||
d4a016771f | |||
8af7c214d2 | |||
71faae3b9a | |||
379f02c41c | |||
be8252ce58 | |||
fb56c9e922 | |||
18810b255b | |||
3370495207 | |||
b0326536c7 | |||
eb6c2538b5 | |||
3563d7449b | |||
7dee85292c | |||
d2b041ef59 |
|
@ -1,38 +1,34 @@
|
||||||
#link https://pages.frohnmeyer-wds.de/scripts/docs.yml
|
#link https://pages.frohnmeyer-wds.de/scripts/docs.yml
|
||||||
#include https://pages.frohnmeyer-wds.de/scripts/clone.yml
|
#include https://pages.frohnmeyer-wds.de/scripts/clone.yml
|
||||||
|
|
||||||
pipeline:
|
steps:
|
||||||
export_metadata:
|
export_metadata:
|
||||||
image: gradle:jammy
|
image: gradle:jdk22-jammy
|
||||||
pull: true
|
pull: true
|
||||||
commands:
|
commands:
|
||||||
- mkdir public
|
- mkdir public
|
||||||
- gradle --build-cache :exportMetadata -Ppublic -Ptimestamp=${CI_PIPELINE_STARTED}
|
- gradle --build-cache :exportMetadata -Ppublic -Ptimestamp=${CI_PIPELINE_STARTED}
|
||||||
- mv version.json public/
|
- mv version.json public/
|
||||||
build_platform_jars:
|
build_platform_jars:
|
||||||
image: gradle:jammy
|
image: git.frohnmeyer-wds.de/johannes/ci-wine
|
||||||
|
pull: true
|
||||||
commands:
|
commands:
|
||||||
- gradle --build-cache :launcher-dist:build -Pflavor=fat -Ppublic -Ptimestamp=${CI_PIPELINE_STARTED}
|
- ./platform_jars.sh
|
||||||
- gradle --build-cache :launcher-dist:build -Pflavor=windows -Ppublic -Ptimestamp=${CI_PIPELINE_STARTED}
|
|
||||||
- gradle --build-cache :launcher-dist:build -Pflavor=linux -Ppublic -Ptimestamp=${CI_PIPELINE_STARTED}
|
|
||||||
- gradle --build-cache :launcher-dist:build -Pflavor=macos -Ppublic -Ptimestamp=${CI_PIPELINE_STARTED}
|
|
||||||
- for f in launcher-dist/build/libs/Inceptum-*-*-*.jar; do mv "$f" "public/Inceptum-$${f##*-}"; done
|
|
||||||
- mv public/Inceptum-fat.jar public/Inceptum.jar
|
|
||||||
build_wrapper:
|
build_wrapper:
|
||||||
image: gradle:jammy
|
image: gradle:jdk22-jammy
|
||||||
commands:
|
commands:
|
||||||
- gradle --build-cache :wrapper:build -Pflavor=windows -Ppublic -Ptimestamp=${CI_PIPELINE_STARTED}
|
- gradle --build-cache :wrapper:build -Pflavor=windows -Ppublic -Ptimestamp=${CI_PIPELINE_STARTED}
|
||||||
- cp wrapper/build/libs/*.exe public/wrapper.exe
|
- cp wrapper/build/libs/*.exe public/wrapper.exe
|
||||||
- cp wrapper/build/libs/*-all.jar public/wrapper.jar
|
- cp wrapper/build/libs/*-all.jar public/wrapper.jar
|
||||||
publish_debug:
|
publish_debug:
|
||||||
image: gradle:jammy
|
image: gradle:jdk22-jammy
|
||||||
commands:
|
commands:
|
||||||
- gradle --build-cache build publish -Pflavor=maven -Ppublic -Ptimestamp=${CI_PIPELINE_STARTED}
|
- gradle --build-cache build publish -Pflavor=maven -Ppublic -Ptimestamp=${CI_PIPELINE_STARTED}
|
||||||
secrets: [ maven_token, maven_name ]
|
secrets: [ maven_token, maven_name ]
|
||||||
when:
|
when:
|
||||||
- branch: master
|
- branch: master
|
||||||
publish_release:
|
publish_release:
|
||||||
image: gradle:jammy
|
image: gradle:jdk22-jammy
|
||||||
commands:
|
commands:
|
||||||
- gradle --build-cache build publish -Pflavor=maven -Ppublic -Prelease
|
- gradle --build-cache build publish -Pflavor=maven -Ppublic -Prelease
|
||||||
secrets: [ maven_token, maven_name ]
|
secrets: [ maven_token, maven_name ]
|
||||||
|
@ -40,15 +36,13 @@ pipeline:
|
||||||
- event: tag
|
- event: tag
|
||||||
branch: master
|
branch: master
|
||||||
portable:
|
portable:
|
||||||
image: gradle:jammy
|
image: git.frohnmeyer-wds.de/johannes/ci-wine
|
||||||
commands:
|
commands:
|
||||||
- apt update
|
|
||||||
- apt install -y p7zip-full curl jq
|
|
||||||
- mkdir -p portable/jvm
|
- mkdir -p portable/jvm
|
||||||
- cp public/wrapper.jar portable/
|
- 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
|
- 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
|
- 7z e mesa.7z -oportable/run/natives/forceload x64/dxil.dll x64/libglapi.dll x64/opengl32.dll
|
||||||
- curl -L "https://api.adoptium.net/v3/binary/latest/18/ga/windows/x64/jre/hotspot/normal/eclipse?project=jdk" --output jvm.zip
|
- 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/
|
- 7z x jvm.zip -oportable/
|
||||||
- mv portable/jdk*/* portable/jvm/
|
- mv portable/jdk*/* portable/jvm/
|
||||||
- rm -r portable/jdk*
|
- rm -r portable/jdk*
|
||||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,5 +1,5 @@
|
||||||
Inceptum - A FOSS Launcher for Minecraft written in Java
|
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
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|
|
@ -14,6 +14,8 @@ Inceptum utilizes code/libraries/assets from:
|
||||||
- [imgui-java](https://github.com/SpaiR/imgui-java): The library used for UI
|
- [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
|
- [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
|
- [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
|
- [gson](https://github.com/google/gson): Used for interacting with various APIs and configs
|
||||||
- [Ubuntu](https://design.ubuntu.com/font/): Used with nerd font symbols as the font
|
- [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
|
- [meteor-client](https://github.com/MeteorDevelopment/meteor-client): A simple HTTP client
|
||||||
|
|
|
@ -9,11 +9,6 @@ allprojects {
|
||||||
group = "io.gitlab.jfronny.inceptum"
|
group = "io.gitlab.jfronny.inceptum"
|
||||||
}
|
}
|
||||||
|
|
||||||
val lwjglVersion by extra("3.3.1")
|
|
||||||
val imguiVersion by extra("1.86.4")
|
|
||||||
val jfCommonsVersion by extra("1.0-SNAPSHOT")
|
|
||||||
val gsonCompileVersion by extra("1.0-SNAPSHOT")
|
|
||||||
val jlhttpVersion by extra("2.6")
|
|
||||||
val flavorProp: String by extra(prop("flavor", "custom"))
|
val flavorProp: String by extra(prop("flavor", "custom"))
|
||||||
if (!setOf("custom", "maven", "fat", "windows", "linux", "macos").contains(flavorProp)) throw IllegalStateException("Unsupported flavor: $flavorProp")
|
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 flavor: String by extra(if (flavorProp != "custom") flavorProp else OS.TYPE.codename)
|
||||||
|
@ -23,6 +18,8 @@ val isRelease by extra(project.hasProperty("release"))
|
||||||
val buildTime by extra(System.currentTimeMillis())
|
val buildTime by extra(System.currentTimeMillis())
|
||||||
val wrapperVersion by extra(1)
|
val wrapperVersion by extra(1)
|
||||||
|
|
||||||
|
val lwjglVersion = libs.versions.lwjgl.get()
|
||||||
|
val imguiVersion = libs.versions.imgui.get()
|
||||||
tasks.register("exportMetadata") {
|
tasks.register("exportMetadata") {
|
||||||
doLast {
|
doLast {
|
||||||
projectDir.resolve("version.json").writeText(
|
projectDir.resolve("version.json").writeText(
|
||||||
|
|
|
@ -4,9 +4,12 @@ plugins {
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
gradlePluginPortal()
|
gradlePluginPortal()
|
||||||
|
maven("https://maven.frohnmeyer-wds.de/artifacts")
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("gradle.plugin.com.github.johnrengelman:shadow:7.1.2")
|
implementation(libs.plugin.shadow)
|
||||||
implementation("de.undercouch:gradle-download-task:5.1.2")
|
implementation(libs.plugin.download)
|
||||||
|
implementation(libs.plugin.jf.convention)
|
||||||
|
implementation(libs.plugin.jlink)
|
||||||
}
|
}
|
2
buildSrc/java-offline
Executable file
2
buildSrc/java-offline
Executable file
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/bash
|
||||||
|
firejail --net=none "$G_ORIGINAL_EXECUTABLE" "$@"
|
|
@ -1 +1,8 @@
|
||||||
rootProject.name="inceptum-conventions"
|
rootProject.name="inceptum-conventions"
|
||||||
|
dependencyResolutionManagement {
|
||||||
|
versionCatalogs {
|
||||||
|
create("libs") {
|
||||||
|
from(files("../gradle/libs.versions.toml"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
import java.io.FileOutputStream
|
|
||||||
import de.undercouch.gradle.tasks.download.Download
|
import de.undercouch.gradle.tasks.download.Download
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
application
|
application
|
||||||
id("inceptum.java-conventions")
|
id("inceptum.java")
|
||||||
id("com.github.johnrengelman.shadow")
|
com.github.johnrengelman.shadow
|
||||||
id("de.undercouch.download")
|
de.undercouch.download
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class FileOutput : DefaultTask() {
|
abstract class FileOutput : DefaultTask() {
|
||||||
|
@ -18,14 +18,14 @@ val bootstrapArch = "i686"
|
||||||
|
|
||||||
val downloadBootstrap by tasks.registering(Download::class) {
|
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")
|
src("https://maven.fabricmc.net/net/fabricmc/fabric-installer-native-bootstrap/windows-${bootstrapArch}/${bootstrapVersion}/windows-${bootstrapArch}-${bootstrapVersion}.exe")
|
||||||
dest(project.buildDir)
|
dest(project.layout.buildDirectory)
|
||||||
}
|
}
|
||||||
|
|
||||||
val nativeExe by tasks.registering(FileOutput::class) {
|
val nativeExe by tasks.registering(FileOutput::class) {
|
||||||
dependsOn(downloadBootstrap)
|
dependsOn(downloadBootstrap)
|
||||||
dependsOn(tasks.shadowJar)
|
dependsOn(tasks.shadowJar)
|
||||||
|
|
||||||
output = file("$buildDir/libs/${project.name}-${project.version}.exe")
|
output = project.layout.buildDirectory.file("libs/${project.name}-${project.version}.exe").get().asFile
|
||||||
outputs.upToDateWhen { false }
|
outputs.upToDateWhen { false }
|
||||||
|
|
||||||
doFirst {
|
doFirst {
|
||||||
|
@ -43,4 +43,8 @@ val nativeExe by tasks.registering(FileOutput::class) {
|
||||||
|
|
||||||
if (rootProject.extra["flavor"] == "windows") {
|
if (rootProject.extra["flavor"] == "windows") {
|
||||||
tasks.build.get().dependsOn(nativeExe)
|
tasks.build.get().dependsOn(nativeExe)
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.runShadow {
|
||||||
|
workingDir = rootProject.projectDir
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
plugins {
|
plugins {
|
||||||
application
|
application
|
||||||
id("inceptum.java-conventions")
|
id("inceptum.java")
|
||||||
}
|
}
|
||||||
|
|
||||||
publishing {
|
publishing {
|
||||||
|
@ -9,4 +9,6 @@ publishing {
|
||||||
from(components["java"])
|
from(components["java"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.run.get().workingDir = rootProject.projectDir
|
|
@ -1,15 +1,13 @@
|
||||||
import org.gradle.kotlin.dsl.extra
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("inceptum.library-conventions")
|
id("inceptum.library")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val libs = extensions.getByType<VersionCatalogsExtension>().named("libs")
|
||||||
dependencies {
|
dependencies {
|
||||||
api("io.gitlab.jfronny.gson:gson-compile-core:${rootProject.extra["gsonCompileVersion"]}")
|
compileOnly(libs.findLibrary("commons-serialize-generator-annotations").orElseThrow())
|
||||||
compileOnly("io.gitlab.jfronny.gson:gson-compile-annotations:${rootProject.extra["gsonCompileVersion"]}")
|
annotationProcessor(libs.findLibrary("commons-serialize-generator").orElseThrow())
|
||||||
annotationProcessor("io.gitlab.jfronny.gson:gson-compile-processor:${rootProject.extra["gsonCompileVersion"]}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<JavaCompile> {
|
tasks.withType<JavaCompile> {
|
||||||
options.compilerArgs.add("-AgsonCompileNoReflect")
|
options.compilerArgs.addAll(listOf("-AserializeProcessorNoReflect"))
|
||||||
}
|
}
|
|
@ -1,33 +0,0 @@
|
||||||
plugins {
|
|
||||||
`java-library`
|
|
||||||
`maven-publish`
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
maven("https://maven.frohnmeyer-wds.de/artifacts")
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compileOnly("org.jetbrains:annotations:23.0.0")
|
|
||||||
}
|
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
52
buildSrc/src/main/kotlin/inceptum.java.gradle.kts
Normal file
52
buildSrc/src/main/kotlin/inceptum.java.gradle.kts
Normal 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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
plugins {
|
plugins {
|
||||||
id("inceptum.java-conventions")
|
id("inceptum.java")
|
||||||
}
|
}
|
||||||
|
|
||||||
publishing {
|
publishing {
|
|
@ -1,14 +1,14 @@
|
||||||
import io.gitlab.jfronny.scripts.*
|
import io.gitlab.jfronny.scripts.*
|
||||||
|
import javax.lang.model.element.Modifier
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("inceptum.library-conventions")
|
inceptum.library
|
||||||
id("jf.codegen")
|
jf.codegen
|
||||||
id("inceptum.gson-compile")
|
inceptum.`gson-compile`
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api("io.gitlab.jfronny:commons:${rootProject.extra["jfCommonsVersion"]}")
|
api(libs.bundles.commons)
|
||||||
api("io.gitlab.jfronny:commons-gson:${rootProject.extra["jfCommonsVersion"]}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val javaVersion by extra(project.java.targetCompatibility)
|
val javaVersion by extra(project.java.targetCompatibility)
|
||||||
|
@ -17,14 +17,15 @@ sourceSets {
|
||||||
main {
|
main {
|
||||||
generate(project) {
|
generate(project) {
|
||||||
`class`("io.gitlab.jfronny.inceptum.common", "BuildMetadata") {
|
`class`("io.gitlab.jfronny.inceptum.common", "BuildMetadata") {
|
||||||
val modifiers = "public static final"
|
modifiers(Modifier.PUBLIC)
|
||||||
|
val modifiers = arrayOf(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
|
||||||
|
|
||||||
field("VERSION", versionS, modifiers)
|
field("VERSION", versionS, *modifiers)
|
||||||
field("BUILD_TIME", rootProject.extra["buildTime"] as Long, modifiers)
|
field("BUILD_TIME", rootProject.extra["buildTime"] as Long, *modifiers)
|
||||||
field("IS_PUBLIC", rootProject.extra["isPublic"] as Boolean, modifiers)
|
field("IS_PUBLIC", rootProject.extra["isPublic"] as Boolean, *modifiers)
|
||||||
field("IS_RELEASE", rootProject.extra["isRelease"] as Boolean, modifiers)
|
field("IS_RELEASE", rootProject.extra["isRelease"] as Boolean, *modifiers)
|
||||||
field("VM_VERSION", javaVersion.majorVersion.toInt(), modifiers)
|
field("VM_VERSION", javaVersion.majorVersion.toInt(), *modifiers)
|
||||||
field("WRAPPER_VERSION", rootProject.extra["wrapperVersion"] as Int, modifiers)
|
field("WRAPPER_VERSION", rootProject.extra["wrapperVersion"] as Int, *modifiers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
package io.gitlab.jfronny.inceptum.common;
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.commons.serialize.SerializeReader;
|
||||||
|
import io.gitlab.jfronny.commons.serialize.Token;
|
||||||
|
import io.gitlab.jfronny.commons.serialize.json.JsonWriter;
|
||||||
|
import io.gitlab.jfronny.commons.throwable.ThrowingBiConsumer;
|
||||||
|
import io.gitlab.jfronny.commons.throwable.ThrowingFunction;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class GList {
|
||||||
|
public static <T, TEx extends Exception, Reader extends SerializeReader<TEx, Reader>> List<T> read(Reader reader, ThrowingFunction<Reader, T, TEx> read) throws TEx {
|
||||||
|
if (reader.isLenient() && reader.peek() != Token.BEGIN_ARRAY) return List.of(read.apply(reader));
|
||||||
|
reader.beginArray();
|
||||||
|
List<T> res = new LinkedList<>();
|
||||||
|
while (reader.hasNext()) {
|
||||||
|
if (reader.peek() == Token.NULL) {
|
||||||
|
reader.nextNull();
|
||||||
|
res.add(null);
|
||||||
|
} else res.add(read.apply(reader));
|
||||||
|
}
|
||||||
|
reader.endArray();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> void write(JsonWriter writer, List<T> list, ThrowingBiConsumer<T, JsonWriter, IOException> write) throws IOException {
|
||||||
|
writer.beginArray();
|
||||||
|
for (T t : list) write.accept(t, writer);
|
||||||
|
writer.endArray();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
package io.gitlab.jfronny.inceptum.common;
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.commons.serialize.SerializeReader;
|
||||||
|
import io.gitlab.jfronny.commons.serialize.SerializeWriter;
|
||||||
|
import io.gitlab.jfronny.commons.serialize.json.JsonReader;
|
||||||
|
import io.gitlab.jfronny.commons.serialize.json.JsonTransport;
|
||||||
|
import io.gitlab.jfronny.commons.serialize.json.JsonWriter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.io.Writer;
|
||||||
|
|
||||||
|
public class GsonPreset {
|
||||||
|
public static Config CONFIG = new Config();
|
||||||
|
public static Api API = new Api();
|
||||||
|
|
||||||
|
public static class Config extends JsonTransport {
|
||||||
|
public static <TEx extends Exception, Reader extends SerializeReader<TEx, Reader>> void configure(Reader reader) {
|
||||||
|
reader.setSerializeSpecialFloatingPointValues(true);
|
||||||
|
reader.setLenient(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <TEx extends Exception, Writer extends SerializeWriter<TEx, Writer>> void configure(Writer writer) {
|
||||||
|
writer.setSerializeNulls(true);
|
||||||
|
writer.setSerializeSpecialFloatingPointValues(true);
|
||||||
|
writer.setLenient(true);
|
||||||
|
if (writer instanceof JsonWriter jw) {
|
||||||
|
jw.setIndent(" ");
|
||||||
|
jw.setOmitQuotes(true);
|
||||||
|
jw.setNewline("\n");
|
||||||
|
jw.setCommentUnexpectedNames(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonWriter createWriter(Writer target) throws IOException {
|
||||||
|
JsonWriter jw = super.createWriter(target);
|
||||||
|
configure(jw);
|
||||||
|
return jw;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonReader createReader(Reader source) {
|
||||||
|
JsonReader jr = super.createReader(source);
|
||||||
|
configure(jr);
|
||||||
|
return jr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Api extends JsonTransport {
|
||||||
|
public static <TEx extends Exception, Reader extends SerializeReader<TEx, Reader>> void configure(Reader reader) {
|
||||||
|
reader.setSerializeSpecialFloatingPointValues(true);
|
||||||
|
reader.setLenient(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <TEx extends Exception, Writer extends SerializeWriter<TEx, Writer>> void configure(Writer writer) {
|
||||||
|
writer.setSerializeNulls(false);
|
||||||
|
writer.setSerializeSpecialFloatingPointValues(true);
|
||||||
|
writer.setLenient(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonWriter createWriter(Writer target) throws IOException {
|
||||||
|
JsonWriter jw = super.createWriter(target);
|
||||||
|
configure(jw);
|
||||||
|
return jw;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonReader createReader(Reader source) {
|
||||||
|
JsonReader jr = super.createReader(source);
|
||||||
|
configure(jr);
|
||||||
|
return jr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,20 +1,30 @@
|
||||||
package io.gitlab.jfronny.inceptum.common;
|
package io.gitlab.jfronny.inceptum.common;
|
||||||
|
|
||||||
import io.gitlab.jfronny.gson.stream.*;
|
import io.gitlab.jfronny.commons.serialize.generator.annotations.GComment;
|
||||||
|
import io.gitlab.jfronny.commons.serialize.generator.annotations.GSerializable;
|
||||||
import io.gitlab.jfronny.inceptum.common.model.inceptum.UpdateChannel;
|
import io.gitlab.jfronny.inceptum.common.model.inceptum.UpdateChannel;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
@GSerializable(isStatic = true)
|
||||||
public class InceptumConfig {
|
public class InceptumConfig {
|
||||||
|
@GComment("Whether to show snapshots in the version selector for new instances")
|
||||||
public static boolean snapshots = false;
|
public static boolean snapshots = false;
|
||||||
|
@GComment("Whether to launch the ImGUI in dark mode\nConfigurable in Settings->Dark Theme")
|
||||||
public static boolean darkTheme = false;
|
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;
|
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;
|
public static boolean enforceAccount = true;
|
||||||
|
@GComment("The currently selected account\nUsed to launch the game")
|
||||||
public static String lastAccount = null;
|
public static String lastAccount = null;
|
||||||
|
@GComment("The last name used for an offline session")
|
||||||
public static String offlineAccountLastName = null;
|
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;
|
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 String authorName = "Inceptum";
|
||||||
|
|
||||||
public static void load() throws IOException {
|
public static void load() throws IOException {
|
||||||
|
@ -31,80 +41,14 @@ public class InceptumConfig {
|
||||||
saveConfig();
|
saveConfig();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try (JsonReader jr = new JsonReader(Files.newBufferedReader(MetaHolder.CONFIG_PATH))) {
|
GC_InceptumConfig.deserialize(MetaHolder.CONFIG_PATH, GsonPreset.CONFIG);
|
||||||
jr.setLenient(true);
|
|
||||||
jr.beginObject();
|
|
||||||
while (jr.peek() != JsonToken.END_OBJECT) {
|
|
||||||
String name = null;
|
|
||||||
try {
|
|
||||||
name = jr.nextName();
|
|
||||||
switch (name) {
|
|
||||||
case "snapshots" -> snapshots = jr.nextBoolean();
|
|
||||||
case "darkTheme" -> darkTheme = jr.nextBoolean();
|
|
||||||
case "listView" -> listView = jr.nextBoolean();
|
|
||||||
case "enforceAccount" -> enforceAccount = jr.nextBoolean();
|
|
||||||
case "lastAccount" -> lastAccount = nullableString(jr);
|
|
||||||
case "offlineAccountLastName" -> offlineAccountLastName = nullableString(jr);
|
|
||||||
case "channel" -> {
|
|
||||||
try {
|
|
||||||
channel = UpdateChannel.valueOf(jr.nextString());
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
Utils.LOGGER.error("Could not read channel", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "authorName" -> authorName = jr.nextString();
|
|
||||||
default -> {
|
|
||||||
Utils.LOGGER.error("Unexpected entry name: " + name);
|
|
||||||
jr.skipValue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Throwable t) {
|
|
||||||
if (name == null) Utils.LOGGER.error("Could not read config entry", t);
|
|
||||||
else Utils.LOGGER.error("Could not read config entry: " + name, t);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
jr.endObject();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void saveConfig() {
|
public static void saveConfig() {
|
||||||
try (JsonWriter jw = new JsonWriter(Files.newBufferedWriter(MetaHolder.CONFIG_PATH))) {
|
try {
|
||||||
jw.setLenient(true);
|
GC_InceptumConfig.serialize(MetaHolder.CONFIG_PATH, GsonPreset.CONFIG);
|
||||||
jw.setOmitQuotes(true);
|
|
||||||
jw.setIndent(" ");
|
|
||||||
jw.beginObject()
|
|
||||||
.comment("Whether to show snapshots in the version selector for new instances")
|
|
||||||
.name("snapshots").value(snapshots)
|
|
||||||
.comment("Whether to launch the GUI in dark mode")
|
|
||||||
.comment("Configurable in Settings->Dark Theme")
|
|
||||||
.name("darkTheme").value(darkTheme)
|
|
||||||
.comment("Whether the GTK UI should default to a list view instead of a grid")
|
|
||||||
.name("listView").value(listView)
|
|
||||||
.comment("Whether to require an account to launch the game")
|
|
||||||
.comment("Intended to allow running the game from USB sticks on constrained networks")
|
|
||||||
.name("enforceAccount").value(enforceAccount)
|
|
||||||
.comment("The currently selected account")
|
|
||||||
.comment("Used to launch the game")
|
|
||||||
.name("lastAccount").value(lastAccount)
|
|
||||||
.comment("The last name used for an offline session")
|
|
||||||
.name("offlineAccountLastName").value(offlineAccountLastName)
|
|
||||||
.comment("The update channel. Either \"CI\" or \"Stable\"")
|
|
||||||
.comment("I personally recommend the CI channel as it gets the latest fixes and features quicker")
|
|
||||||
.name("channel").value(channel.toString())
|
|
||||||
.comment("The author name to add to packs where the metadata format requires specifying one")
|
|
||||||
.name("authorName").value(authorName)
|
|
||||||
.endObject();
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Utils.LOGGER.error("Could not save config", e);
|
Utils.LOGGER.error("Could not save config", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String nullableString(JsonReader jr) throws IOException {
|
|
||||||
if (jr.peek() == JsonToken.NULL) {
|
|
||||||
jr.nextNull();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return jr.nextString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
package io.gitlab.jfronny.inceptum.common;
|
package io.gitlab.jfronny.inceptum.common;
|
||||||
|
|
||||||
import io.gitlab.jfronny.commons.HttpUtils;
|
import io.gitlab.jfronny.commons.http.client.HttpClient;
|
||||||
import io.gitlab.jfronny.commons.log.Logger;
|
import io.gitlab.jfronny.commons.logger.HotswapLoggerFinder;
|
||||||
import io.gitlab.jfronny.commons.log.StdoutLogger;
|
import io.gitlab.jfronny.commons.logger.StdoutLogger;
|
||||||
|
import io.gitlab.jfronny.commons.logger.SystemLoggerPlus;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
public class InceptumEnvironmentInitializer {
|
public class InceptumEnvironmentInitializer {
|
||||||
public static void initialize() throws IOException {
|
public static void initialize() throws IOException {
|
||||||
Logger.registerFactory(InceptumEnvironmentInitializer::defaultFactory);
|
HotswapLoggerFinder.updateAllStrategies(InceptumEnvironmentInitializer::defaultFactory);
|
||||||
HttpUtils.setUserAgent("jfmods/inceptum/" + BuildMetadata.VERSION);
|
HttpClient.setUserAgent("jfmods/inceptum/" + BuildMetadata.VERSION);
|
||||||
InceptumConfig.load();
|
InceptumConfig.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Logger defaultFactory(String name) {
|
public static SystemLoggerPlus defaultFactory(String name, Module module, System.Logger.Level level) {
|
||||||
return new StdoutLogger(name, true, true, true);
|
return StdoutLogger.fancy(name, module, level);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ public class MetaHolder {
|
||||||
case WINDOWS -> getPath(System.getenv("APPDATA"));
|
case WINDOWS -> getPath(System.getenv("APPDATA"));
|
||||||
case MAC_OS -> getPath(System.getProperty("user.home")).resolve("Library").resolve("Application Support");
|
case MAC_OS -> getPath(System.getProperty("user.home")).resolve("Library").resolve("Application Support");
|
||||||
case LINUX -> {
|
case LINUX -> {
|
||||||
String s = System.getenv().get("XDG_CONFIG_HOME");
|
String s = System.getenv("XDG_CONFIG_HOME");
|
||||||
if (s == null)
|
if (s == null)
|
||||||
yield getPath(System.getProperty("user.home")).resolve(".config");
|
yield getPath(System.getProperty("user.home")).resolve(".config");
|
||||||
else
|
else
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
package io.gitlab.jfronny.inceptum.common;
|
package io.gitlab.jfronny.inceptum.common;
|
||||||
|
|
||||||
import io.gitlab.jfronny.commons.HashUtils;
|
import io.gitlab.jfronny.commons.http.client.HttpClient;
|
||||||
import io.gitlab.jfronny.commons.HttpUtils;
|
import io.gitlab.jfronny.commons.io.HashUtils;
|
||||||
|
import io.gitlab.jfronny.commons.serialize.json.JsonReader;
|
||||||
|
import io.gitlab.jfronny.commons.serialize.json.JsonTransport;
|
||||||
|
import io.gitlab.jfronny.commons.throwable.ThrowingBiFunction;
|
||||||
import io.gitlab.jfronny.commons.throwable.ThrowingFunction;
|
import io.gitlab.jfronny.commons.throwable.ThrowingFunction;
|
||||||
import io.gitlab.jfronny.commons.throwable.ThrowingSupplier;
|
import io.gitlab.jfronny.commons.throwable.ThrowingSupplier;
|
||||||
|
|
||||||
|
@ -18,7 +21,7 @@ public class Net {
|
||||||
private static final ObjectCache OBJECT_CACHE = new ObjectCache(MetaHolder.CACHE_DIR);
|
private static final ObjectCache OBJECT_CACHE = new ObjectCache(MetaHolder.CACHE_DIR);
|
||||||
|
|
||||||
public static byte[] downloadData(String url) throws IOException, URISyntaxException {
|
public static byte[] downloadData(String url) throws IOException, URISyntaxException {
|
||||||
try (InputStream is = HttpUtils.get(url).sendInputStream()) {
|
try (InputStream is = HttpClient.get(url).sendInputStream()) {
|
||||||
return is.readAllBytes();
|
return is.readAllBytes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,22 +37,42 @@ public class Net {
|
||||||
return downloadObject(url, func, true);
|
return downloadObject(url, func, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T> T downloadJObject(String url, ThrowingFunction<JsonReader, T, IOException> func) throws IOException {
|
||||||
|
return downloadJObject(url, func, true);
|
||||||
|
}
|
||||||
|
|
||||||
public static <T> T downloadObject(String url, ThrowingFunction<String, T, IOException> func, boolean cache) throws IOException {
|
public static <T> T downloadObject(String url, ThrowingFunction<String, T, IOException> func, boolean cache) throws IOException {
|
||||||
return downloadObject(url, () -> HttpUtils.get(url).sendString(), func, cache);
|
return downloadObject(url, () -> HttpClient.get(url).sendString(), func, cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T downloadJObject(String url, ThrowingFunction<JsonReader, T, IOException> func, boolean cache) throws IOException {
|
||||||
|
return downloadJObject(url, () -> HttpClient.get(url).sendString(), func, cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> T downloadObject(String url, ThrowingFunction<String, T, IOException> func, String apiKey) throws IOException {
|
public static <T> T downloadObject(String url, ThrowingFunction<String, T, IOException> func, String apiKey) throws IOException {
|
||||||
return downloadObject(url, () -> HttpUtils.get(url).header("x-api-key", apiKey).sendString(), func, true);
|
return downloadObject(url, () -> downloadStringAuthenticated(url, apiKey), func, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T downloadJObject(String url, ThrowingFunction<JsonReader, T, IOException> func, String apiKey) throws IOException {
|
||||||
|
return downloadJObject(url, () -> downloadStringAuthenticated(url, apiKey), func, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> T downloadObject(String url, String sha1, ThrowingFunction<String, T, IOException> func) throws IOException {
|
public static <T> T downloadObject(String url, String sha1, ThrowingFunction<String, T, IOException> func) throws IOException {
|
||||||
return downloadObject(url, sha1, func, true);
|
return downloadObject(url, sha1, func, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T> T downloadJObject(String url, String sha1, ThrowingFunction<JsonReader, T, IOException> func) throws IOException {
|
||||||
|
return downloadJObject(url, sha1, func, true);
|
||||||
|
}
|
||||||
|
|
||||||
public static <T> T downloadObject(String url, String sha1, ThrowingFunction<String, T, IOException> func, boolean cache) throws IOException {
|
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);
|
return downloadObject(url, () -> downloadString(url, sha1), func, cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T> T downloadJObject(String url, String sha1, ThrowingFunction<JsonReader, T, IOException> func, boolean cache) throws IOException {
|
||||||
|
return downloadJObject(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 {
|
private static <T> T downloadObject(String url, ThrowingSupplier<String, Exception> sourceString, ThrowingFunction<String, T, IOException> func, boolean cache) throws IOException {
|
||||||
try {
|
try {
|
||||||
ThrowingSupplier<T, Exception> builder = () -> func.apply(sourceString.get());
|
ThrowingSupplier<T, Exception> builder = () -> func.apply(sourceString.get());
|
||||||
|
@ -59,6 +82,10 @@ public class Net {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T> T downloadJObject(String url, ThrowingSupplier<String, Exception> sourceString, ThrowingFunction<JsonReader, T, IOException> func, boolean cache) throws IOException {
|
||||||
|
return downloadObject(url, sourceString, s -> func.apply(GsonPreset.API.createReader(s)), cache);
|
||||||
|
}
|
||||||
|
|
||||||
public static String buildUrl(String host, String url, Map<String, String> params) {
|
public static String buildUrl(String host, String url, Map<String, String> params) {
|
||||||
StringBuilder res = new StringBuilder(host);
|
StringBuilder res = new StringBuilder(host);
|
||||||
if (res.toString().endsWith("/")) res = new StringBuilder(res.substring(0, res.length() - 1));
|
if (res.toString().endsWith("/")) res = new StringBuilder(res.substring(0, res.length() - 1));
|
||||||
|
@ -75,13 +102,17 @@ public class Net {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String downloadString(String url) throws IOException, URISyntaxException {
|
public static String downloadString(String url) throws IOException, URISyntaxException {
|
||||||
return HttpUtils.get(url).sendString();
|
return HttpClient.get(url).sendString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String downloadString(String url, String sha1) throws IOException, URISyntaxException {
|
public static String downloadString(String url, String sha1) throws IOException, URISyntaxException {
|
||||||
return new String(downloadData(url, sha1), StandardCharsets.UTF_8);
|
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 {
|
public static void downloadFile(String url, Path path) throws IOException, URISyntaxException {
|
||||||
if (!Files.exists(path.getParent())) Files.createDirectories(path.getParent());
|
if (!Files.exists(path.getParent())) Files.createDirectories(path.getParent());
|
||||||
Files.write(path, downloadData(url));
|
Files.write(path, downloadData(url));
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
package io.gitlab.jfronny.inceptum.common;
|
|
||||||
|
|
||||||
public class OutdatedException extends RuntimeException {
|
|
||||||
public OutdatedException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
package io.gitlab.jfronny.inceptum.common;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class containing methods for nop lambdas. Use these instead of () -> {}
|
|
||||||
*/
|
|
||||||
public class R {
|
|
||||||
public static void nop() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void nop(Object a1) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void nop(Object a1, Object a2) {
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,7 @@
|
||||||
package io.gitlab.jfronny.inceptum.common;
|
package io.gitlab.jfronny.inceptum.common;
|
||||||
|
|
||||||
import io.gitlab.jfronny.commons.OSUtils;
|
import io.gitlab.jfronny.commons.OSUtils;
|
||||||
|
import io.gitlab.jfronny.commons.serialize.json.JsonTransport;
|
||||||
import io.gitlab.jfronny.inceptum.common.api.MavenApi;
|
import io.gitlab.jfronny.inceptum.common.api.MavenApi;
|
||||||
import io.gitlab.jfronny.inceptum.common.model.inceptum.*;
|
import io.gitlab.jfronny.inceptum.common.model.inceptum.*;
|
||||||
import io.gitlab.jfronny.inceptum.common.model.maven.*;
|
import io.gitlab.jfronny.inceptum.common.model.maven.*;
|
||||||
|
@ -31,28 +32,29 @@ public class Updater {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void update(UpdateMetadata source, boolean relaunch) throws IOException, URISyntaxException {
|
public static void update(UpdateMetadata source, boolean relaunch) throws IOException, URISyntaxException {
|
||||||
Utils.LOGGER.info("Downloading version " + source.version);
|
Utils.LOGGER.info("Downloading version " + source.version());
|
||||||
|
|
||||||
WrapperConfig config = new WrapperConfig();
|
WrapperConfig config = new WrapperConfig(
|
||||||
config.natives = new HashMap<>();
|
new LinkedHashSet<>(),
|
||||||
config.libraries = new LinkedHashSet<>();
|
new LinkedHashSet<>(source.repositories()),
|
||||||
config.repositories = new LinkedHashSet<>(source.repositories);
|
new HashMap<>()
|
||||||
source.natives.forEach((k, v) -> config.natives.put(k, new LinkedHashSet<>(v)));
|
);
|
||||||
|
source.natives().forEach((k, v) -> config.natives().put(k, new LinkedHashSet<>(v)));
|
||||||
|
|
||||||
DependencyNode node = downloadLibrary(source.repositories, "io.gitlab.jfronny.inceptum:launcher-dist:" + source.version, config.libraries);
|
DependencyNode node = downloadLibrary(source.repositories(), "io.gitlab.jfronny.inceptum:launcher-dist:" + source.version(), config.libraries());
|
||||||
Utils.LOGGER.info("Downloaded Dependencies:\n" + node);
|
Utils.LOGGER.info("Downloaded Dependencies:\n" + node);
|
||||||
|
|
||||||
List<String> currentLibraries = new LinkedList<>(config.libraries);
|
List<String> currentLibraries = new LinkedList<>(config.libraries());
|
||||||
if (source.natives.containsKey(Utils.getCurrentFlavor())) {
|
if (source.natives().containsKey(Utils.getCurrentFlavor())) {
|
||||||
Set<String> natives = new LinkedHashSet<>();
|
Set<String> natives = new LinkedHashSet<>();
|
||||||
for (String lib : source.natives.get(Utils.getCurrentFlavor())) {
|
for (String lib : source.natives().get(Utils.getCurrentFlavor())) {
|
||||||
downloadLibrary(source.repositories, lib, natives);
|
downloadLibrary(source.repositories(), lib, natives);
|
||||||
}
|
}
|
||||||
currentLibraries.addAll(natives);
|
currentLibraries.addAll(natives);
|
||||||
config.natives.put(Utils.getCurrentFlavor(), natives);
|
config.natives().put(Utils.getCurrentFlavor(), natives);
|
||||||
}
|
}
|
||||||
|
|
||||||
GC_WrapperConfig.write(MetaHolder.WRAPPER_CONFIG_PATH, config);
|
GC_WrapperConfig.serialize(config, MetaHolder.WRAPPER_CONFIG_PATH, GsonPreset.CONFIG);
|
||||||
|
|
||||||
if (relaunch) {
|
if (relaunch) {
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||||
|
@ -61,7 +63,7 @@ public class Updater {
|
||||||
"-cp",
|
"-cp",
|
||||||
buildClasspath(currentLibraries.stream())
|
buildClasspath(currentLibraries.stream())
|
||||||
.map(Path::toString)
|
.map(Path::toString)
|
||||||
.collect(Collectors.joining("" + File.pathSeparatorChar))
|
.collect(Collectors.joining(String.valueOf(File.pathSeparatorChar)))
|
||||||
).inheritIO().start();
|
).inheritIO().start();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Utils.LOGGER.error("Could not relaunch", e);
|
Utils.LOGGER.error("Could not relaunch", e);
|
||||||
|
@ -71,9 +73,9 @@ public class Updater {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<Path> getLaunchClasspath(WrapperConfig wrapperConfig) throws IOException, URISyntaxException {
|
public static List<Path> getLaunchClasspath(WrapperConfig wrapperConfig) throws IOException, URISyntaxException {
|
||||||
Set<String> natives = wrapperConfig.natives.get(Utils.getCurrentFlavor());
|
Set<String> natives = wrapperConfig.natives().get(Utils.getCurrentFlavor());
|
||||||
if (natives == null) natives = new LinkedHashSet<>();
|
if (natives == null) natives = new LinkedHashSet<>();
|
||||||
Set<String> libs = wrapperConfig.libraries;
|
Set<String> libs = wrapperConfig.libraries();
|
||||||
if (libs == null) libs = new LinkedHashSet<>();
|
if (libs == null) libs = new LinkedHashSet<>();
|
||||||
|
|
||||||
boolean configChanged = false;
|
boolean configChanged = false;
|
||||||
|
@ -82,18 +84,18 @@ public class Updater {
|
||||||
Path p = ArtifactMeta.parse(lib).getLocalPath();
|
Path p = ArtifactMeta.parse(lib).getLocalPath();
|
||||||
if (!Files.exists(p)) {
|
if (!Files.exists(p)) {
|
||||||
configChanged = true;
|
configChanged = true;
|
||||||
downloadLibrary(wrapperConfig.repositories, lib, libs);
|
downloadLibrary(wrapperConfig.repositories(), lib, libs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (String lib : natives) {
|
for (String lib : natives) {
|
||||||
Path p = ArtifactMeta.parse(lib).getLocalPath();
|
Path p = ArtifactMeta.parse(lib).getLocalPath();
|
||||||
if (!Files.exists(p)) {
|
if (!Files.exists(p)) {
|
||||||
configChanged = true;
|
configChanged = true;
|
||||||
downloadLibrary(wrapperConfig.repositories, lib, natives);
|
downloadLibrary(wrapperConfig.repositories(), lib, natives);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (configChanged) GC_WrapperConfig.write(MetaHolder.WRAPPER_CONFIG_PATH, wrapperConfig);
|
if (configChanged) GC_WrapperConfig.serialize(wrapperConfig, MetaHolder.WRAPPER_CONFIG_PATH, GsonPreset.CONFIG);
|
||||||
|
|
||||||
return buildClasspath(Stream.concat(libs.stream(), natives.stream())).toList();
|
return buildClasspath(Stream.concat(libs.stream(), natives.stream())).toList();
|
||||||
}
|
}
|
||||||
|
@ -123,9 +125,9 @@ public class Updater {
|
||||||
throw new IOException("Could not download artifact " + meta.getMavenNotation() + " from " + repository, e);
|
throw new IOException("Could not download artifact " + meta.getMavenNotation() + " from " + repository, e);
|
||||||
}
|
}
|
||||||
Set<DependencyNode> dependencies = new LinkedHashSet<>();
|
Set<DependencyNode> dependencies = new LinkedHashSet<>();
|
||||||
if (pom.dependencies != null) {
|
if (pom.dependencies() != null) {
|
||||||
for (MavenDependency dependency : pom.dependencies) {
|
for (MavenDependency dependency : pom.dependencies()) {
|
||||||
String mvnName = dependency.groupId + ":" + dependency.artifactId + ":" + dependency.version;
|
String mvnName = dependency.groupId() + ":" + dependency.artifactId() + ":" + dependency.version();
|
||||||
dependencies.add(downloadLibrary(repositories, mvnName, libraries));
|
dependencies.add(downloadLibrary(repositories, mvnName, libraries));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -140,10 +142,10 @@ public class Updater {
|
||||||
|
|
||||||
public static @Nullable UpdateMetadata check(UpdateChannel channel, boolean versionCompare, boolean checkEnv, Consumer<UpdateChannel> channelInvalid) throws UpdateCheckException {
|
public static @Nullable UpdateMetadata check(UpdateChannel channel, boolean versionCompare, boolean checkEnv, Consumer<UpdateChannel> channelInvalid) throws UpdateCheckException {
|
||||||
try {
|
try {
|
||||||
UpdateMetadata experimental = Net.downloadObject(ARTIFACTS_URL + "version.json", GC_UpdateMetadata::read);
|
UpdateMetadata experimental = Net.downloadObject(ARTIFACTS_URL + "version.json", json -> GC_UpdateMetadata.deserialize(json, GsonPreset.API));
|
||||||
UpdateMetadata stable = null;
|
UpdateMetadata stable = null;
|
||||||
try {
|
try {
|
||||||
stable = Net.downloadObject(STABLE_URL + "version.json", GC_UpdateMetadata::read);
|
stable = Net.downloadObject(STABLE_URL + "version.json", json -> GC_UpdateMetadata.deserialize(json, GsonPreset.API));
|
||||||
} catch (Throwable ignored) {}
|
} catch (Throwable ignored) {}
|
||||||
if (stable == null && channel == UpdateChannel.Stable) {
|
if (stable == null && channel == UpdateChannel.Stable) {
|
||||||
channel = UpdateChannel.CI;
|
channel = UpdateChannel.CI;
|
||||||
|
@ -154,12 +156,12 @@ public class Updater {
|
||||||
case Stable -> stable;
|
case Stable -> stable;
|
||||||
};
|
};
|
||||||
if (checkEnv) {
|
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.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 (info.wrapperVersion() != BuildMetadata.WRAPPER_VERSION) throw new UpdateCheckException("A different version of the Inceptum Wrapper is required for this update!", "Mismatched Wrapper");
|
||||||
}
|
}
|
||||||
if (versionCompare) {
|
if (versionCompare) {
|
||||||
Utils.LOGGER.info("Latest version is " + info.version + ", current is " + BuildMetadata.VERSION);
|
Utils.LOGGER.info("Latest version is " + info.version() + ", current is " + BuildMetadata.VERSION);
|
||||||
if (BuildMetadata.BUILD_TIME >= info.buildTime) {
|
if (BuildMetadata.BUILD_TIME >= info.buildTime()) {
|
||||||
Utils.LOGGER.info("Up-to-date");
|
Utils.LOGGER.info("Up-to-date");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ package io.gitlab.jfronny.inceptum.common;
|
||||||
|
|
||||||
import io.gitlab.jfronny.commons.OSUtils;
|
import io.gitlab.jfronny.commons.OSUtils;
|
||||||
import io.gitlab.jfronny.commons.io.JFiles;
|
import io.gitlab.jfronny.commons.io.JFiles;
|
||||||
import io.gitlab.jfronny.commons.log.Logger;
|
import io.gitlab.jfronny.commons.logger.SystemLoggerPlus;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -17,8 +17,9 @@ import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class Utils {
|
public class Utils {
|
||||||
public static final int CACHE_SIZE = 128;
|
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 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");
|
public static final SystemLoggerPlus LOGGER = SystemLoggerPlus.forName("Inceptum");
|
||||||
private static ClassLoader SYSTEM_LOADER = ClassLoader.getSystemClassLoader();
|
private static ClassLoader SYSTEM_LOADER = ClassLoader.getSystemClassLoader();
|
||||||
|
|
||||||
public static void openWebBrowser(URI uri) {
|
public static void openWebBrowser(URI uri) {
|
||||||
|
@ -41,7 +42,7 @@ public class Utils {
|
||||||
Desktop.getDesktop().open(file);
|
Desktop.getDesktop().open(file);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Utils.LOGGER.error("Error opening web browser!", e);
|
Utils.LOGGER.error("Error opening file!", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package io.gitlab.jfronny.inceptum.common.api;
|
package io.gitlab.jfronny.inceptum.common.api;
|
||||||
|
|
||||||
import io.gitlab.jfronny.commons.HttpUtils;
|
import io.gitlab.jfronny.commons.http.client.HttpClient;
|
||||||
import io.gitlab.jfronny.inceptum.common.Net;
|
import io.gitlab.jfronny.inceptum.common.Net;
|
||||||
import io.gitlab.jfronny.inceptum.common.Utils;
|
import io.gitlab.jfronny.inceptum.common.Utils;
|
||||||
import io.gitlab.jfronny.inceptum.common.model.maven.*;
|
import io.gitlab.jfronny.inceptum.common.model.maven.*;
|
||||||
|
@ -35,150 +35,122 @@ public class MavenApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Pom getPom(String repo, ArtifactMeta meta) throws IOException, SAXException, URISyntaxException, XMLStreamException {
|
public static Pom getPom(String repo, ArtifactMeta meta) throws IOException, SAXException, URISyntaxException, XMLStreamException {
|
||||||
try (InputStream is = HttpUtils.get(Utils.join("/", repo, meta.getPomPath())).sendInputStream()) {
|
try (InputStream is = HttpClient.get(Utils.join("/", repo, meta.getPomPath())).sendInputStream()) {
|
||||||
Document doc = FACTORY.parse(is);
|
Document doc = FACTORY.parse(is);
|
||||||
doc.getDocumentElement().normalize();
|
doc.getDocumentElement().normalize();
|
||||||
Pom result = new Pom();
|
|
||||||
if (!"project".equals(doc.getDocumentElement().getNodeName())) throw new IOException("Illegal document name");
|
if (!"project".equals(doc.getDocumentElement().getNodeName())) throw new IOException("Illegal document name");
|
||||||
boolean hasModelVersion = false;
|
String modelVersion = null;
|
||||||
boolean hasGroupId = false;
|
String groupId = null;
|
||||||
boolean hasArtifactId = false;
|
String artifactId = null;
|
||||||
boolean hasVersion = false;
|
String version = null;
|
||||||
for (Node node : iterable(doc.getDocumentElement().getChildNodes())) {
|
String packaging = null;
|
||||||
|
List<MavenDependency> dependencies = null;
|
||||||
|
String classifier = null;
|
||||||
|
for (Node node : children(doc.getDocumentElement())) {
|
||||||
switch (node.getNodeName()) {
|
switch (node.getNodeName()) {
|
||||||
case "modelVersion" -> {
|
case "modelVersion" -> modelVersion = node.getTextContent();
|
||||||
hasModelVersion = true;
|
|
||||||
result.modelVersion = node.getTextContent();
|
|
||||||
}
|
|
||||||
case "parent" -> {
|
case "parent" -> {
|
||||||
// Dirty hack to get slf4j working: simply assume the groupId and version of the parent is also the groupId of this
|
// Dirty hack to get slf4j working: simply assume the groupId and version of the parent is also the groupId of this
|
||||||
if (!hasGroupId) {
|
if (groupId == null) {
|
||||||
for (Node child : iterable(node.getChildNodes())) {
|
for (Node child : children(node)) {
|
||||||
switch (child.getNodeName()) {
|
switch (child.getNodeName()) {
|
||||||
case "groupId" -> {
|
case "groupId" -> {
|
||||||
if (!hasGroupId) {
|
if (groupId == null) {
|
||||||
hasGroupId = true;
|
groupId = child.getTextContent();
|
||||||
result.groupId = node.getTextContent();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "version" -> {
|
case "version" -> {
|
||||||
if (!hasVersion) {
|
if (version == null) {
|
||||||
hasVersion = true;
|
version = child.getTextContent();
|
||||||
result.version = node.getTextContent();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "groupId" -> {
|
case "groupId" -> groupId = node.getTextContent();
|
||||||
hasGroupId = true;
|
|
||||||
result.groupId = node.getTextContent();
|
|
||||||
}
|
|
||||||
case "artifactId" -> {
|
case "artifactId" -> {
|
||||||
hasArtifactId = true;
|
artifactId = node.getTextContent();
|
||||||
result.artifactId = node.getTextContent();
|
|
||||||
}
|
}
|
||||||
case "version" -> {
|
case "version" -> {
|
||||||
hasVersion = true;
|
version = node.getTextContent();
|
||||||
result.version = node.getTextContent();
|
|
||||||
}
|
}
|
||||||
case "packaging" -> result.packaging = node.getTextContent();
|
case "packaging" -> packaging = node.getTextContent();
|
||||||
case "dependencies" -> {
|
case "dependencies" -> {
|
||||||
result.dependencies = new LinkedList<>();
|
dependencies = new LinkedList<>();
|
||||||
for (Node dep : iterable(node.getChildNodes())) {
|
for (Node dep : children(node)) {
|
||||||
MavenDependency resolved = parseDependency(dep);
|
MavenDependency resolved = parseDependency(dep);
|
||||||
if (resolved != null) {
|
if (resolved != null) {
|
||||||
result.dependencies.add(resolved);
|
dependencies.add(resolved);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "classifier" -> result.classifier = node.getTextContent();
|
case "classifier" -> classifier = node.getTextContent();
|
||||||
default -> {}
|
default -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!hasModelVersion) throw new IOException("Pom lacks modelVersion");
|
if (modelVersion == null) throw new IOException("Pom lacks modelVersion");
|
||||||
if (!hasGroupId) throw new IOException("Pom lacks groupId");
|
if (groupId == null) throw new IOException("Pom lacks groupId");
|
||||||
if (!hasArtifactId) throw new IOException("Pom lacks artifactId");
|
if (artifactId == null) throw new IOException("Pom lacks artifactId");
|
||||||
if (!hasVersion) throw new IOException("Pom lacks version");
|
if (version == null) throw new IOException("Pom lacks version");
|
||||||
return result;
|
return new Pom(modelVersion, groupId, artifactId, version, classifier, null, packaging, dependencies);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static @Nullable MavenDependency parseDependency(Node doc) throws IOException {
|
private static @Nullable MavenDependency parseDependency(Node doc) throws IOException {
|
||||||
MavenDependency result = new MavenDependency();
|
String groupId = null;
|
||||||
boolean hasGroupId = false;
|
String artifactId = null;
|
||||||
boolean hasArtifactId = false;
|
String version = null;
|
||||||
boolean hasVersion = false;
|
String scope = null;
|
||||||
boolean hasScope = false;
|
for (Node node : children(doc)) {
|
||||||
for (Node node : iterable(doc.getChildNodes())) {
|
|
||||||
switch (node.getNodeName()) {
|
switch (node.getNodeName()) {
|
||||||
case "groupId" -> {
|
case "groupId" -> groupId = node.getTextContent();
|
||||||
hasGroupId = true;
|
case "artifactId" -> artifactId = node.getTextContent();
|
||||||
result.groupId = node.getTextContent();
|
case "version" -> version = node.getTextContent();
|
||||||
}
|
|
||||||
case "artifactId" -> {
|
|
||||||
hasArtifactId = true;
|
|
||||||
result.artifactId = node.getTextContent();
|
|
||||||
}
|
|
||||||
case "version" -> {
|
|
||||||
hasVersion = true;
|
|
||||||
result.version = node.getTextContent();
|
|
||||||
}
|
|
||||||
case "scope" -> {
|
case "scope" -> {
|
||||||
hasScope = true;
|
scope = node.getTextContent();
|
||||||
result.scope = node.getTextContent();
|
if (!RUNTIME_SCOPES.contains(scope)) return null;
|
||||||
if (!RUNTIME_SCOPES.contains(result.scope)) return null;
|
|
||||||
}
|
}
|
||||||
case "optional" -> {
|
case "optional" -> {
|
||||||
if (node.getTextContent().equals("true")) return null;
|
if (node.getTextContent().equals("true")) return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!hasGroupId) throw new IOException("Pom lacks groupId");
|
if (groupId == null) throw new IOException("Pom lacks groupId");
|
||||||
if (!hasArtifactId) throw new IOException("Pom lacks artifactId");
|
if (artifactId == null) throw new IOException("Pom lacks artifactId");
|
||||||
if (!hasVersion) {
|
if (version == null) {
|
||||||
if (result.groupId.equals("org.lwjgl")) {
|
if (groupId.equals("org.lwjgl")) {
|
||||||
// Lwjgl uses a shared bom for versions which I don't want to support
|
// Lwjgl uses a shared bom for versions which I don't want to support
|
||||||
// The required modules are explicit dependencies of launcher-imgui anyway
|
// The required modules are explicit dependencies of launcher-imgui anyway
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
throw new IOException("Dependency " + result.groupId + ":" + result.artifactId + " lacks version");
|
throw new IOException("Dependency " + groupId + ":" + artifactId + " lacks version");
|
||||||
}
|
}
|
||||||
if (!hasScope) throw new IOException("Pom lacks scope");
|
if (scope == null) throw new IOException("Pom lacks scope");
|
||||||
return result;
|
return new MavenDependency(groupId, artifactId, version, scope);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ArtifactMeta getMetadata(String repo, String artifact) throws IOException, SAXException, URISyntaxException {
|
public static ArtifactMeta getMetadata(String repo, String artifact) throws IOException, SAXException, URISyntaxException {
|
||||||
ArtifactMeta sourceMeta = ArtifactMeta.parse(artifact);
|
ArtifactMeta sourceMeta = ArtifactMeta.parse(artifact);
|
||||||
try (InputStream is = HttpUtils.get(Utils.join("/", repo, sourceMeta.getMetadataPath())).sendInputStream()) {
|
try (InputStream is = HttpClient.get(Utils.join("/", repo, sourceMeta.getMetadataPath())).sendInputStream()) {
|
||||||
Document doc = FACTORY.parse(is);
|
Document doc = FACTORY.parse(is);
|
||||||
doc.getDocumentElement().normalize();
|
doc.getDocumentElement().normalize();
|
||||||
ArtifactMeta result = new ArtifactMeta();
|
|
||||||
if (!"metadata".equals(doc.getDocumentElement().getNodeName())) throw new IOException("Illegal document name");
|
if (!"metadata".equals(doc.getDocumentElement().getNodeName())) throw new IOException("Illegal document name");
|
||||||
boolean hasGroupId = false;
|
String groupId = null;
|
||||||
boolean hasArtifactId = false;
|
String artifactId = null;
|
||||||
boolean hasVersion = false;
|
String version = null;
|
||||||
for (Node node : iterable(doc.getDocumentElement().getChildNodes())) {
|
String snapshotVersion = null;
|
||||||
|
for (Node node : children(doc.getDocumentElement())) {
|
||||||
switch (node.getNodeName()) {
|
switch (node.getNodeName()) {
|
||||||
case "groupId" -> {
|
case "groupId" -> groupId = node.getTextContent();
|
||||||
hasGroupId = true;
|
case "artifactId" -> artifactId = node.getTextContent();
|
||||||
result.groupId = node.getTextContent();
|
case "version" -> version = node.getTextContent();
|
||||||
}
|
|
||||||
case "artifactId" -> {
|
|
||||||
hasArtifactId = true;
|
|
||||||
result.artifactId = node.getTextContent();
|
|
||||||
}
|
|
||||||
case "version" -> {
|
|
||||||
hasVersion = true;
|
|
||||||
result.version = node.getTextContent();
|
|
||||||
}
|
|
||||||
case "versioning" -> {
|
case "versioning" -> {
|
||||||
for (Node node1 : iterable(node.getChildNodes())) {
|
for (Node node1 : children(node)) {
|
||||||
if (node1.getNodeName().equals("snapshot")) {
|
if (node1.getNodeName().equals("snapshot")) {
|
||||||
String timestamp = null;
|
String timestamp = null;
|
||||||
String buildNumber = null;
|
String buildNumber = null;
|
||||||
for (Node node2 : iterable(node1.getChildNodes())) {
|
for (Node node2 : children(node1)) {
|
||||||
switch (node2.getNodeName()) {
|
switch (node2.getNodeName()) {
|
||||||
case "timestamp" -> timestamp = node2.getTextContent();
|
case "timestamp" -> timestamp = node2.getTextContent();
|
||||||
case "buildNumber" -> buildNumber = node2.getTextContent();
|
case "buildNumber" -> buildNumber = node2.getTextContent();
|
||||||
|
@ -187,37 +159,37 @@ public class MavenApi {
|
||||||
}
|
}
|
||||||
if (timestamp == null) throw new IOException("Pom snapshots lack timestamp");
|
if (timestamp == null) throw new IOException("Pom snapshots lack timestamp");
|
||||||
if (buildNumber == null) throw new IOException("Pom snapshots lack buildNumber");
|
if (buildNumber == null) throw new IOException("Pom snapshots lack buildNumber");
|
||||||
result.snapshotVersion = timestamp + '-' + buildNumber;
|
snapshotVersion = timestamp + '-' + buildNumber;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default -> {}
|
default -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!hasGroupId) throw new IOException("Pom lacks groupId");
|
if (groupId == null) throw new IOException("Pom lacks groupId");
|
||||||
if (!hasArtifactId) throw new IOException("Pom lacks artifactId");
|
if (artifactId == null) throw new IOException("Pom lacks artifactId");
|
||||||
if (!hasVersion) throw new IOException("Pom lacks version");
|
if (version == null) throw new IOException("Pom lacks version");
|
||||||
result.classifier = sourceMeta.classifier;
|
return new ArtifactMeta(groupId, artifactId, version, sourceMeta.classifier(), snapshotVersion);
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Iterable<Node> iterable(NodeList list) {
|
private static Iterable<Node> children(Node node) {
|
||||||
return () -> new Iterator<>() {
|
return () -> new Iterator<Node>() {
|
||||||
|
NodeList children = node.getChildNodes();
|
||||||
int index = 0;
|
int index = 0;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasNext() {
|
public boolean hasNext() {
|
||||||
while (index < list.getLength() && isWhitespace(list.item(index))) {
|
while (index < children.getLength() && isWhitespace(children.item(index))) {
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
return index < list.getLength();
|
return index < children.getLength();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Node next() {
|
public Node next() {
|
||||||
if (!hasNext()) throw new NoSuchElementException();
|
if (!hasNext()) throw new NoSuchElementException();
|
||||||
return list.item(index++);
|
return children.item(index++);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
package io.gitlab.jfronny.inceptum.common.model.inceptum;
|
package io.gitlab.jfronny.inceptum.common.model.inceptum;
|
||||||
|
|
||||||
import io.gitlab.jfronny.gson.compile.annotations.GSerializable;
|
import io.gitlab.jfronny.commons.serialize.generator.annotations.GSerializable;
|
||||||
|
import io.gitlab.jfronny.inceptum.common.GsonPreset;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@GSerializable
|
@GSerializable
|
||||||
public class UpdateMetadata {
|
public record UpdateMetadata(int wrapperVersion,
|
||||||
public Integer wrapperVersion;
|
String version,
|
||||||
public String version;
|
long buildTime,
|
||||||
public Long buildTime;
|
boolean isPublic,
|
||||||
public Boolean isPublic;
|
boolean isRelease,
|
||||||
public Boolean isRelease;
|
int jvm,
|
||||||
public Integer jvm;
|
Set<String> repositories,
|
||||||
public Set<String> repositories;
|
Map<String, Set<String>> natives) {
|
||||||
public Map<String, Set<String>> natives;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
package io.gitlab.jfronny.inceptum.common.model.inceptum;
|
package io.gitlab.jfronny.inceptum.common.model.inceptum;
|
||||||
|
|
||||||
import io.gitlab.jfronny.gson.compile.annotations.GSerializable;
|
import io.gitlab.jfronny.commons.serialize.generator.annotations.GSerializable;
|
||||||
|
import io.gitlab.jfronny.inceptum.common.GsonPreset;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@GSerializable
|
@GSerializable
|
||||||
public class WrapperConfig {
|
public record WrapperConfig(Set<String> libraries, Set<String> repositories, Map<String, Set<String>> natives) {
|
||||||
public Set<String> libraries;
|
|
||||||
public Set<String> repositories;
|
|
||||||
public Map<String, Set<String>> natives;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +1,25 @@
|
||||||
package io.gitlab.jfronny.inceptum.common.model.maven;
|
package io.gitlab.jfronny.inceptum.common.model.maven;
|
||||||
|
|
||||||
import io.gitlab.jfronny.inceptum.common.MetaHolder;
|
import io.gitlab.jfronny.inceptum.common.MetaHolder;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public class ArtifactMeta {
|
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) {
|
public static ArtifactMeta parse(String mavenNotation) {
|
||||||
if (Objects.requireNonNull(mavenNotation).isEmpty()) throw new IllegalArgumentException("The notation is empty");
|
if (Objects.requireNonNull(mavenNotation).isEmpty()) throw new IllegalArgumentException("The notation is empty");
|
||||||
String[] lib = mavenNotation.split(":");
|
String[] lib = mavenNotation.split(":");
|
||||||
if (lib.length <= 1) throw new IllegalArgumentException("Not in maven notation");
|
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 == 2) throw new IllegalArgumentException("Skipping versions is not supported");
|
||||||
if (lib.length >= 5) throw new IllegalArgumentException("Unkown elements in maven notation");
|
if (lib.length >= 5) throw new IllegalArgumentException("Unkown elements in maven notation");
|
||||||
ArtifactMeta meta = new ArtifactMeta();
|
return new ArtifactMeta(lib[0], lib[1], lib[2], lib.length > 3 ? lib[3] : null, null);
|
||||||
meta.groupId = lib[0];
|
|
||||||
meta.artifactId = lib[1];
|
|
||||||
meta.version = lib[2];
|
|
||||||
if (lib.length > 3) meta.classifier = lib[3];
|
|
||||||
return meta;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String groupId;
|
|
||||||
public String artifactId;
|
|
||||||
public String version;
|
|
||||||
|
|
||||||
public String classifier;
|
|
||||||
public String snapshotVersion;
|
|
||||||
|
|
||||||
public String getPomPath() {
|
public String getPomPath() {
|
||||||
String path = groupId.replace('.', '/') + '/';
|
String path = groupId.replace('.', '/') + '/';
|
||||||
path += artifactId + '/';
|
path += artifactId + '/';
|
||||||
|
|
|
@ -1,29 +1,9 @@
|
||||||
package io.gitlab.jfronny.inceptum.common.model.maven;
|
package io.gitlab.jfronny.inceptum.common.model.maven;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.Iterator;
|
||||||
|
import java.util.Set;
|
||||||
public class DependencyNode {
|
|
||||||
private final String name;
|
|
||||||
private final Set<DependencyNode> dependencies;
|
|
||||||
|
|
||||||
public DependencyNode(String name, Set<DependencyNode> dependencies) {
|
|
||||||
this.name = Objects.requireNonNull(name);
|
|
||||||
this.dependencies = Objects.requireNonNull(dependencies);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
DependencyNode that = (DependencyNode) o;
|
|
||||||
return name.equals(that.name) && dependencies.equals(that.dependencies);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(name, dependencies);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
public record DependencyNode(String name, Set<DependencyNode> dependencies) {
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
package io.gitlab.jfronny.inceptum.common.model.maven;
|
package io.gitlab.jfronny.inceptum.common.model.maven;
|
||||||
|
|
||||||
public class MavenDependency {
|
public record MavenDependency(String groupId, String artifactId, String version, String scope) {
|
||||||
public String groupId;
|
|
||||||
public String artifactId;
|
|
||||||
public String version;
|
|
||||||
public String scope;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,12 @@ import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class Pom {
|
public record Pom(String modelVersion,
|
||||||
public String modelVersion;
|
String groupId,
|
||||||
public String groupId;
|
String artifactId,
|
||||||
public String artifactId;
|
String version,
|
||||||
public String version;
|
String classifier,
|
||||||
public String classifier;
|
String snapshotVersion,
|
||||||
public String snapshotVersion;
|
@Nullable String packaging,
|
||||||
@Nullable public String packaging;
|
@Nullable List<MavenDependency> dependencies) {
|
||||||
@Nullable public List<MavenDependency> dependencies;
|
|
||||||
}
|
}
|
||||||
|
|
17
common/src/main/java/module-info.java
Normal file
17
common/src/main/java/module-info.java
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
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.http.client;
|
||||||
|
requires transitive io.gitlab.jfronny.commons.io;
|
||||||
|
requires transitive io.gitlab.jfronny.commons.logger;
|
||||||
|
requires transitive io.gitlab.jfronny.commons.serialize.json;
|
||||||
|
requires static org.jetbrains.annotations;
|
||||||
|
requires static io.gitlab.jfronny.commons.serialize.generator.annotations;
|
||||||
|
requires io.gitlab.jfronny.commons.serialize;
|
||||||
|
}
|
49
gradle/libs.versions.toml
Normal file
49
gradle/libs.versions.toml
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
[versions]
|
||||||
|
jf-commons = "1.7-SNAPSHOT"
|
||||||
|
annotations = "24.0.1"
|
||||||
|
lwjgl = "3.3.2"
|
||||||
|
imgui = "1.86.10"
|
||||||
|
javagi = "0.10.1"
|
||||||
|
kotlin = "2.0.0"
|
||||||
|
|
||||||
|
[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-logger = { module = "io.gitlab.jfronny:commons-logger", version.ref = "jf-commons" }
|
||||||
|
commons-serialize-json = { module = "io.gitlab.jfronny:commons-serialize-json", version.ref = "jf-commons" }
|
||||||
|
commons-serialize-generator-annotations = { module = "io.gitlab.jfronny:commons-serialize-generator-annotations", version.ref = "jf-commons" }
|
||||||
|
commons-serialize-generator = { module = "io.gitlab.jfronny:commons-serialize-generator", version.ref = "jf-commons" }
|
||||||
|
|
||||||
|
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-logger", "commons-serialize-json"]
|
|
@ -1,5 +1,5 @@
|
||||||
plugins {
|
plugins {
|
||||||
id("inceptum.application-conventions")
|
inceptum.application
|
||||||
}
|
}
|
||||||
|
|
||||||
application {
|
application {
|
||||||
|
@ -7,5 +7,5 @@ application {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":launcher"))
|
implementation(projects.launcher)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package io.gitlab.jfronny.inceptum.cli;
|
package io.gitlab.jfronny.inceptum.cli;
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.inceptum.common.GsonPreset;
|
||||||
import io.gitlab.jfronny.inceptum.common.MetaHolder;
|
import io.gitlab.jfronny.inceptum.common.MetaHolder;
|
||||||
import io.gitlab.jfronny.inceptum.common.Utils;
|
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.Instance;
|
||||||
import io.gitlab.jfronny.inceptum.launcher.system.instance.InstanceList;
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.InstanceList;
|
||||||
|
|
||||||
|
@ -11,11 +13,7 @@ import java.nio.file.Path;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public abstract class BaseInstanceCommand extends Command {
|
public abstract class BaseInstanceCommand extends Command {
|
||||||
public BaseInstanceCommand(String help, String usage, String... aliases) {
|
protected BaseInstanceCommand(String help, String usage, List<String> aliases, List<Command> subCommands) {
|
||||||
super(help, mutateUsage(usage), aliases);
|
|
||||||
}
|
|
||||||
|
|
||||||
public BaseInstanceCommand(String help, String usage, List<String> aliases, List<Command> subCommands) {
|
|
||||||
super(help, mutateUsage(usage), aliases, subCommands);
|
super(help, mutateUsage(usage), aliases, subCommands);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,29 +29,33 @@ public abstract class BaseInstanceCommand extends Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void invoke(CommandArgs args) {
|
protected void invoke(CommandArgs args) throws Exception {
|
||||||
if (args.length == 0) {
|
if (args.length == 0) {
|
||||||
Utils.LOGGER.error("You must specify an instance to commit in");
|
Utils.LOGGER.error("You must specify an instance to commit in");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Path instancePath = MetaHolder.INSTANCE_DIR.resolve(args.get(0));
|
|
||||||
if (!Files.exists(instancePath)) {
|
|
||||||
Utils.LOGGER.error("Invalid instance: \"" + args.get(0) + "\"");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Instance instance;
|
Instance instance;
|
||||||
try {
|
Path normalPath = Path.of(args.get(0));
|
||||||
instance = InstanceList.read(instancePath);
|
if (Files.exists(normalPath.resolve(Instance.CONFIG_NAME))) {
|
||||||
} catch (IOException e) {
|
instance = new Instance(normalPath, GC_InstanceMeta.deserialize(normalPath.resolve(Instance.CONFIG_NAME), GsonPreset.CONFIG));
|
||||||
Utils.LOGGER.error("Could not read instance metadata", e);
|
} else {
|
||||||
return;
|
Path instancePath = MetaHolder.INSTANCE_DIR.resolve(args.get(0)).normalize();
|
||||||
}
|
if (!instancePath.startsWith(MetaHolder.INSTANCE_DIR)) {
|
||||||
try {
|
Utils.LOGGER.error("Specified instance path doesn't exist");
|
||||||
invoke(args.subArgs(), instance);
|
return;
|
||||||
} catch (Exception e) {
|
}
|
||||||
Utils.LOGGER.error("Could not execute command", e);
|
if (!Files.exists(instancePath)) {
|
||||||
return;
|
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;
|
protected abstract void invoke(CommandArgs args, Instance instance) throws Exception;
|
||||||
|
|
|
@ -47,6 +47,8 @@ public class CliMain {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
command.invoke();
|
command.invoke();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Utils.LOGGER.error("Could not execute command", e);
|
||||||
} finally {
|
} finally {
|
||||||
LauncherEnv.terminate();
|
LauncherEnv.terminate();
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ public class HelpBuilder {
|
||||||
builder.append(", ");
|
builder.append(", ");
|
||||||
}
|
}
|
||||||
builder.append(": ").append(help.replace("\n", "\n " + indent));
|
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");
|
StringBuilder usagePrefix = new StringBuilder("inceptum");
|
||||||
for (String s : upper) {
|
for (String s : upper) {
|
||||||
usagePrefix.append(" ").append(s);
|
usagePrefix.append(" ").append(s);
|
||||||
|
|
|
@ -24,9 +24,9 @@ public class ExportCommand extends BaseInstanceCommand {
|
||||||
@Override
|
@Override
|
||||||
protected void invoke(CommandArgs args, Instance instance) throws Exception {
|
protected void invoke(CommandArgs args, Instance instance) throws Exception {
|
||||||
if (args.length == 0) throw new IllegalAccessException("You must specify a target path");
|
if (args.length == 0) throw new IllegalAccessException("You must specify a target path");
|
||||||
if (args.length == 1) throw new IllegalAccessException("You must specify a version number");
|
if (args.length > 2) throw new IllegalAccessException("Too many arguments");
|
||||||
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)), args.get(1));
|
Exporters.CURSE_FORGE.generate(new ProcessState(), instance, Paths.get(args.get(0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class MultiMCExportCommand extends BaseInstanceCommand {
|
private static class MultiMCExportCommand extends BaseInstanceCommand {
|
||||||
|
@ -38,7 +38,7 @@ public class ExportCommand extends BaseInstanceCommand {
|
||||||
protected void invoke(CommandArgs args, Instance instance) throws Exception {
|
protected void invoke(CommandArgs args, Instance instance) throws Exception {
|
||||||
if (args.length == 0) throw new IllegalAccessException("You must specify a target path");
|
if (args.length == 0) throw new IllegalAccessException("You must specify a target path");
|
||||||
if (args.length != 1) throw new IllegalAccessException("Too many arguments");
|
if (args.length != 1) throw new IllegalAccessException("Too many arguments");
|
||||||
Exporters.MULTI_MC.generate(new ProcessState(), instance, Paths.get(args.get(0)), "1.0");
|
Exporters.MULTI_MC.generate(new ProcessState(), instance, Paths.get(args.get(0)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,9 +50,9 @@ public class ExportCommand extends BaseInstanceCommand {
|
||||||
@Override
|
@Override
|
||||||
protected void invoke(CommandArgs args, Instance instance) throws Exception {
|
protected void invoke(CommandArgs args, Instance instance) throws Exception {
|
||||||
if (args.length == 0) throw new IllegalAccessException("You must specify a target path");
|
if (args.length == 0) throw new IllegalAccessException("You must specify a target path");
|
||||||
if (args.length == 1) throw new IllegalAccessException("You must specify a version number");
|
if (args.length > 2) throw new IllegalAccessException("Too many arguments");
|
||||||
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)), args.get(1));
|
Exporters.MODRINTH.generate(new ProcessState(), instance, Paths.get(args.get(0)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package io.gitlab.jfronny.inceptum.cli.commands;
|
package io.gitlab.jfronny.inceptum.cli.commands;
|
||||||
|
|
||||||
import io.gitlab.jfronny.commons.log.OutputColors;
|
import io.gitlab.jfronny.commons.logger.OutputColors;
|
||||||
import io.gitlab.jfronny.inceptum.cli.Command;
|
import io.gitlab.jfronny.inceptum.cli.Command;
|
||||||
import io.gitlab.jfronny.inceptum.cli.CommandArgs;
|
import io.gitlab.jfronny.inceptum.cli.CommandArgs;
|
||||||
import io.gitlab.jfronny.inceptum.launcher.system.importer.Importers;
|
import io.gitlab.jfronny.inceptum.launcher.system.importer.Importers;
|
||||||
|
@ -19,7 +19,7 @@ public class ImportCommand extends Command {
|
||||||
if (args.length == 0) throw new IllegalAccessException("You must specify a pack file");
|
if (args.length == 0) throw new IllegalAccessException("You must specify a pack file");
|
||||||
if (args.length != 1) throw new IllegalAccessException("Too many arguments");
|
if (args.length != 1) throw new IllegalAccessException("Too many arguments");
|
||||||
ProcessState state = new ProcessState();
|
ProcessState state = new ProcessState();
|
||||||
String name = Importers.importPack(Paths.get(args.get(0)), state).getFileName().toString();
|
String name = Importers.importPack(Paths.get(args.get(0)), state).path().getFileName().toString();
|
||||||
System.out.println(OutputColors.GREEN_BOLD + "Imported as " + name + OutputColors.RESET);
|
System.out.println(OutputColors.GREEN_BOLD + "Imported as " + name + OutputColors.RESET);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,6 @@ public class JvmStateCommand extends Command {
|
||||||
else
|
else
|
||||||
System.out.println("\t(cannot display components as not a URLClassLoader)");
|
System.out.println("\t(cannot display components as not a URLClassLoader)");
|
||||||
|
|
||||||
if (loader.getParent() != null)
|
if (loader.getParent() != null) dumpClasspath(loader.getParent());
|
||||||
dumpClasspath(loader.getParent());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,9 @@ import io.gitlab.jfronny.inceptum.launcher.system.launch.*;
|
||||||
import io.gitlab.jfronny.inceptum.launcher.system.setup.Steps;
|
import io.gitlab.jfronny.inceptum.launcher.system.setup.Steps;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public class LaunchCommand extends BaseInstanceCommand {
|
public class LaunchCommand extends BaseInstanceCommand {
|
||||||
private final boolean server;
|
private final boolean server;
|
||||||
|
@ -47,12 +49,10 @@ public class LaunchCommand extends BaseInstanceCommand {
|
||||||
}
|
}
|
||||||
if (args.length > 1) {
|
if (args.length > 1) {
|
||||||
InstanceMeta meta = instance.meta();
|
InstanceMeta meta = instance.meta();
|
||||||
if (meta.arguments == null) meta.arguments = new InstanceMeta.Arguments();
|
meta.checkArguments();
|
||||||
meta.arguments.client = meta.arguments.client == null ? new ArrayList<>() : new ArrayList<>(meta.arguments.client);
|
meta.arguments = meta.arguments
|
||||||
meta.arguments.server = meta.arguments.server == null ? new ArrayList<>() : new ArrayList<>(meta.arguments.server);
|
.withClient(Stream.concat(meta.arguments.client().stream(), args.after(0).stream()).toList())
|
||||||
meta.arguments.jvm = meta.arguments.jvm == null ? new ArrayList<>() : new ArrayList<>(meta.arguments.jvm);
|
.withServer(Stream.concat(meta.arguments.server().stream(), args.after(0).stream()).toList());
|
||||||
meta.arguments.client.addAll(args.after(0));
|
|
||||||
meta.arguments.server.addAll(args.after(0));
|
|
||||||
}
|
}
|
||||||
Steps.reDownload(instance, Steps.createProcessState());
|
Steps.reDownload(instance, Steps.createProcessState());
|
||||||
if (server) {
|
if (server) {
|
||||||
|
|
|
@ -23,7 +23,7 @@ public class ListCommand extends Command {
|
||||||
List<Path> paths = JFiles.list(MetaHolder.INSTANCE_DIR);
|
List<Path> paths = JFiles.list(MetaHolder.INSTANCE_DIR);
|
||||||
if (paths.isEmpty()) System.out.println("No instances are currently present");
|
if (paths.isEmpty()) System.out.println("No instances are currently present");
|
||||||
for (Path path : paths) {
|
for (Path path : paths) {
|
||||||
if (!Files.exists(path.resolve("instance.json"))) {
|
if (!Files.exists(path.resolve(Instance.CONFIG_NAME))) {
|
||||||
System.out.println("- Invalid instance: " + path + " (no instance metadata)");
|
System.out.println("- Invalid instance: " + path + " (no instance metadata)");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import io.gitlab.jfronny.inceptum.common.Utils;
|
||||||
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance;
|
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.instance.Mod;
|
||||||
import io.gitlab.jfronny.inceptum.launcher.system.mds.ModsDirScanner;
|
import io.gitlab.jfronny.inceptum.launcher.system.mds.ModsDirScanner;
|
||||||
import io.gitlab.jfronny.inceptum.launcher.system.source.ModSource;
|
|
||||||
import io.gitlab.jfronny.inceptum.launcher.util.Unchecked;
|
import io.gitlab.jfronny.inceptum.launcher.util.Unchecked;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
|
@ -53,8 +52,8 @@ public class ModCommand extends Command {
|
||||||
}
|
}
|
||||||
System.out.println("Scanning installed mods, this might take a while");
|
System.out.println("Scanning installed mods, this might take a while");
|
||||||
instance.mds().runOnce((path, mod) -> {
|
instance.mds().runOnce((path, mod) -> {
|
||||||
boolean hasSources = !mod.getMetadata().sources.isEmpty();
|
boolean hasSources = !mod.getMetadata().sources().isEmpty();
|
||||||
boolean updatable = hasSources && mod.getMetadata().sources.values().stream().anyMatch(Optional::isPresent);
|
boolean updatable = hasSources && mod.getMetadata().sources().values().stream().anyMatch(Optional::isPresent);
|
||||||
if (filterUpdatable && !updatable) return;
|
if (filterUpdatable && !updatable) return;
|
||||||
System.out.println("- " + path.getFileName().toString());
|
System.out.println("- " + path.getFileName().toString());
|
||||||
System.out.println(" " + mod.getName());
|
System.out.println(" " + mod.getName());
|
||||||
|
@ -63,11 +62,10 @@ public class ModCommand extends Command {
|
||||||
}
|
}
|
||||||
if (hasSources) {
|
if (hasSources) {
|
||||||
System.out.println(" Sources:");
|
System.out.println(" Sources:");
|
||||||
for (Map.Entry<ModSource, Optional<ModSource>> entry : mod.getMetadata().sources.entrySet()) {
|
for (var entry : mod.getMetadata().sources().entrySet()) {
|
||||||
System.out.println(" - " + entry.getKey().getName() + " (" + entry.getKey().getVersion() + ")");
|
System.out.println(" - " + entry.getKey().getName() + " (" + entry.getKey().getVersion() + ")");
|
||||||
System.out.println(" Local: " + entry.getKey().getJarPath().toString());
|
System.out.println(" Local: " + entry.getKey().getJarPath().toString());
|
||||||
if (entry.getValue().isPresent())
|
if (entry.getValue().isPresent()) System.out.println(" Updatable to: " + entry.getValue().get().getVersion());
|
||||||
System.out.println(" Updatable to: " + entry.getValue().get().getVersion());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -100,10 +98,10 @@ public class ModCommand extends Command {
|
||||||
}
|
}
|
||||||
Set<Path> mods = new HashSet<>();
|
Set<Path> mods = new HashSet<>();
|
||||||
for (String arg : args) {
|
for (String arg : args) {
|
||||||
Path p = instance.modsDir().resolve(arg);
|
Path p = instance.getModsDir().resolve(arg);
|
||||||
if (!Files.exists(p)) p = instance.modsDir().resolve(arg + ".imod");
|
if (!Files.exists(p)) p = instance.getModsDir().resolve(arg + ".imod");
|
||||||
if (!Files.exists(p)) {
|
if (!Files.exists(p)) {
|
||||||
Utils.LOGGER.error("Nonexistant mod file: " + instance.modsDir().resolve(arg));
|
Utils.LOGGER.error("Nonexistant mod file: " + instance.getModsDir().resolve(arg));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mods.add(p);
|
mods.add(p);
|
||||||
|
@ -165,9 +163,9 @@ public class ModCommand extends Command {
|
||||||
}
|
}
|
||||||
for (Path mod : mods) {
|
for (Path mod : mods) {
|
||||||
try {
|
try {
|
||||||
mds.get(mod).delete();
|
|
||||||
Mod md = mds.get(mod);
|
Mod md = mds.get(mod);
|
||||||
md.getMetadata().sources.values().stream()
|
md.delete();
|
||||||
|
md.getMetadata().sources().values().stream()
|
||||||
.filter(Optional::isPresent)
|
.filter(Optional::isPresent)
|
||||||
.map(Optional::get)
|
.map(Optional::get)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
|
|
6
launcher-cli/src/main/java/module-info.java
Normal file
6
launcher-cli/src/main/java/module-info.java
Normal 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;
|
||||||
|
}
|
|
@ -1,15 +1,22 @@
|
||||||
|
import io.gitlab.jfronny.scripts.*
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("inceptum.application-standalone-conventions")
|
inceptum.`application-standalone`
|
||||||
|
org.beryx.jlink
|
||||||
}
|
}
|
||||||
|
|
||||||
application {
|
application {
|
||||||
mainClass.set("io.gitlab.jfronny.inceptum.Inceptum")
|
mainClass.set("io.gitlab.jfronny.inceptum.Inceptum")
|
||||||
|
mainModule.set("io.gitlab.jfronny.inceptum.launcher.dist")
|
||||||
|
applicationName = "Inceptum"
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":launcher"))
|
implementation(projects.launcher)
|
||||||
implementation(project(":launcher-cli"))
|
implementation(projects.launcherCli)
|
||||||
implementation(project(":launcher-imgui"))
|
implementation(projects.launcherImgui)
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.shadowJar {
|
tasks.shadowJar {
|
||||||
|
@ -17,7 +24,8 @@ tasks.shadowJar {
|
||||||
archiveBaseName.set("Inceptum")
|
archiveBaseName.set("Inceptum")
|
||||||
exclude("about.html")
|
exclude("about.html")
|
||||||
exclude("plugin.properties")
|
exclude("plugin.properties")
|
||||||
exclude("META-INF/**")
|
// exclude("META-INF/**")
|
||||||
|
mergeServiceFiles()
|
||||||
}
|
}
|
||||||
|
|
||||||
(components["java"] as AdhocComponentWithVariants).withVariantsFromConfiguration(configurations["shadowRuntimeElements"]) {
|
(components["java"] as AdhocComponentWithVariants).withVariantsFromConfiguration(configurations["shadowRuntimeElements"]) {
|
||||||
|
@ -30,4 +38,99 @@ publishing {
|
||||||
from(components["java"])
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
4
launcher-dist/src/main/java/module-info.java
Normal file
4
launcher-dist/src/main/java/module-info.java
Normal 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;
|
||||||
|
}
|
|
@ -1,19 +1,43 @@
|
||||||
plugins {
|
plugins {
|
||||||
id("inceptum.application-conventions")
|
inceptum.application
|
||||||
id("com.github.johnrengelman.shadow")
|
com.github.johnrengelman.shadow
|
||||||
|
kotlin("jvm") version libs.versions.kotlin
|
||||||
|
kotlin("plugin.sam.with.receiver") version libs.versions.kotlin
|
||||||
}
|
}
|
||||||
|
|
||||||
application {
|
application {
|
||||||
mainClass.set("io.gitlab.jfronny.inceptum.gtk.GtkMain")
|
mainClass.set("io.gitlab.jfronny.inceptum.gtk.GtkMain")
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
samWithReceiver {
|
||||||
maven { url = uri("https://jitpack.io") }
|
annotation("io.gitlab.jfronny.commons.SamWithReceiver")
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("com.github.bailuk:java-gtk:0.2")
|
implementation(libs.bundles.javagi)
|
||||||
implementation(project(":launcher"))
|
implementation(projects.launcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.runShadow.get().workingDir = rootProject.projectDir
|
tasks.shadowJar {
|
||||||
|
mergeServiceFiles()
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
|
@ -1,97 +0,0 @@
|
||||||
package io.gitlab.jfronny.inceptum.gtk;
|
|
||||||
|
|
||||||
import ch.bailu.gtk.GTK;
|
|
||||||
import ch.bailu.gtk.gtk.*;
|
|
||||||
import ch.bailu.gtk.type.Str;
|
|
||||||
import io.gitlab.jfronny.commons.StringFormatter;
|
|
||||||
import io.gitlab.jfronny.inceptum.common.Utils;
|
|
||||||
import io.gitlab.jfronny.inceptum.gtk.util.I18n;
|
|
||||||
import io.gitlab.jfronny.inceptum.launcher.LauncherEnv;
|
|
||||||
import io.gitlab.jfronny.inceptum.launcher.api.account.MicrosoftAccount;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
public enum GtkEnvBackend implements LauncherEnv.EnvBackend { //TODO test
|
|
||||||
INSTANCE;
|
|
||||||
|
|
||||||
public Window dialogParent = null;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void showError(String message, String title) {
|
|
||||||
Utils.LOGGER.error(message);
|
|
||||||
simpleDialog(message, title, MessageType.ERROR, ButtonsType.CLOSE, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void showError(String message, Throwable t) {
|
|
||||||
simpleDialog(StringFormatter.toString(t), message, MessageType.ERROR, ButtonsType.CLOSE, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void showInfo(String message, String title) {
|
|
||||||
Utils.LOGGER.info(message);
|
|
||||||
simpleDialog(message, title, MessageType.INFO, ButtonsType.CLOSE, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void showOkCancel(String message, String title, Runnable ok, Runnable cancel, boolean defaultCancel) {
|
|
||||||
Utils.LOGGER.info(message);
|
|
||||||
simpleDialog(message, title, MessageType.QUESTION, ButtonsType.OK_CANCEL, ok, cancel);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void getInput(String prompt, String details, String defaultValue, Consumer<String> ok, Runnable cancel) {
|
|
||||||
//TODO spacing
|
|
||||||
//TODO run on main thread
|
|
||||||
GtkMain.schedule(() -> {
|
|
||||||
Dialog dialog = new Dialog();
|
|
||||||
if (dialogParent != null) dialog.setTransientFor(dialogParent);
|
|
||||||
dialog.setModal(GTK.TRUE);
|
|
||||||
if (dialogParent != null) dialog.setDestroyWithParent(GTK.TRUE);
|
|
||||||
dialog.setTitle(new Str(prompt));
|
|
||||||
Box box = dialog.getContentArea();
|
|
||||||
box.append(new Label(new Str(details)));
|
|
||||||
Entry entry = new Entry();
|
|
||||||
Editable entryEditable = new Editable(entry.cast());
|
|
||||||
entryEditable.setText(new Str(defaultValue));
|
|
||||||
box.append(entry);
|
|
||||||
dialog.addButton(I18n.str("ok"), ResponseType.OK);
|
|
||||||
dialog.addButton(I18n.str("cancel"), ResponseType.CANCEL);
|
|
||||||
dialog.onResponse(processResponses(dialog, () -> ok.accept(entryEditable.getText().toString()), cancel));
|
|
||||||
dialog.show();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void showLoginRefreshPrompt(MicrosoftAccount account) {
|
|
||||||
//TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
private void simpleDialog(String markup, String title, int type, int buttons, Runnable ok, Runnable cancel) {
|
|
||||||
GtkMain.schedule(() -> {
|
|
||||||
MessageDialog dialog = new MessageDialog(dialogParent, DialogFlags.MODAL | DialogFlags.DESTROY_WITH_PARENT, type, buttons, null);
|
|
||||||
dialog.setTitle(new Str(title));
|
|
||||||
dialog.setMarkup(new Str(markup));
|
|
||||||
dialog.onResponse(processResponses(dialog, ok, cancel));
|
|
||||||
dialog.show();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private Dialog.OnResponse processResponses(Dialog dialog, @Nullable Runnable ok, @Nullable Runnable cancel) {
|
|
||||||
return response_id -> {
|
|
||||||
switch (response_id) {
|
|
||||||
case ResponseType.OK -> {
|
|
||||||
dialog.close();
|
|
||||||
if (ok != null) ok.run();
|
|
||||||
}
|
|
||||||
case ResponseType.CLOSE, ResponseType.CANCEL -> {
|
|
||||||
dialog.close();
|
|
||||||
if (cancel != null) cancel.run();
|
|
||||||
}
|
|
||||||
case ResponseType.DELETE_EVENT -> dialog.destroy();
|
|
||||||
default -> Utils.LOGGER.error("Unexpected response type: " + response_id);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
package io.gitlab.jfronny.inceptum.gtk;
|
|
||||||
|
|
||||||
import ch.bailu.gtk.GTK;
|
|
||||||
import ch.bailu.gtk.gio.ApplicationFlags;
|
|
||||||
import ch.bailu.gtk.glib.Glib;
|
|
||||||
import ch.bailu.gtk.gtk.Application;
|
|
||||||
import ch.bailu.gtk.type.Str;
|
|
||||||
import ch.bailu.gtk.type.Strs;
|
|
||||||
import io.gitlab.jfronny.inceptum.common.*;
|
|
||||||
import io.gitlab.jfronny.inceptum.gtk.window.MainWindow;
|
|
||||||
import io.gitlab.jfronny.inceptum.launcher.LauncherEnv;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
public class GtkMain {
|
|
||||||
public static final Str ID = new Str("io.gitlab.jfronny.inceptum");
|
|
||||||
private static final Queue<Runnable> SCHEDULED = new ArrayDeque<>();
|
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
|
||||||
LauncherEnv.initialize(GtkEnvBackend.INSTANCE);
|
|
||||||
Utils.LOGGER.info("Launching Inceptum v" + BuildMetadata.VERSION);
|
|
||||||
Utils.LOGGER.info("Loading from " + MetaHolder.BASE_PATH);
|
|
||||||
//TODO look into java-gtk samples for window architecture
|
|
||||||
int statusCode = -1;
|
|
||||||
try {
|
|
||||||
statusCode = showGui(args);
|
|
||||||
} finally {
|
|
||||||
LauncherEnv.terminate();
|
|
||||||
System.exit(statusCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void schedule(Runnable task) {
|
|
||||||
SCHEDULED.add(Objects.requireNonNull(task));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int showGui(String[] args) throws IOException {
|
|
||||||
var app = new Application(ID, ApplicationFlags.FLAGS_NONE);
|
|
||||||
app.onActivate(() -> {
|
|
||||||
GtkMenubar.create(app);
|
|
||||||
var window = new MainWindow(app);
|
|
||||||
window.show();
|
|
||||||
Glib.idleAdd(user_data -> {
|
|
||||||
Runnable r;
|
|
||||||
while ((r = SCHEDULED.poll()) != null) {
|
|
||||||
try {
|
|
||||||
r.run();
|
|
||||||
} catch (Throwable t) {
|
|
||||||
Utils.LOGGER.error("Could not run scheduled task", t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return GTK.TRUE;
|
|
||||||
}, null);
|
|
||||||
GtkEnvBackend.INSTANCE.dialogParent = window;
|
|
||||||
window.onCloseRequest(() -> {
|
|
||||||
GtkEnvBackend.INSTANCE.dialogParent = null;
|
|
||||||
return GTK.FALSE;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return app.run(args.length, new Strs(args));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,94 +0,0 @@
|
||||||
package io.gitlab.jfronny.inceptum.gtk;
|
|
||||||
|
|
||||||
import ch.bailu.gtk.gtk.Application;
|
|
||||||
import io.gitlab.jfronny.inceptum.common.R;
|
|
||||||
import io.gitlab.jfronny.inceptum.common.Utils;
|
|
||||||
import io.gitlab.jfronny.inceptum.gtk.menu.MenuBuilder;
|
|
||||||
import io.gitlab.jfronny.inceptum.gtk.util.I18n;
|
|
||||||
import io.gitlab.jfronny.inceptum.gtk.window.AboutWindow;
|
|
||||||
import io.gitlab.jfronny.inceptum.gtk.window.NewInstanceWindow;
|
|
||||||
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.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.setup.Steps;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
public class GtkMenubar {
|
|
||||||
public static MenuBuilder accountsMenu;
|
|
||||||
public static MenuBuilder launchMenu;
|
|
||||||
|
|
||||||
public static void create(Application app) {
|
|
||||||
var menu = new MenuBuilder(app);
|
|
||||||
var file = menu.submenu("file");
|
|
||||||
file.button("new", NewInstanceWindow::createAndShow);
|
|
||||||
file.button("redownload", () -> {
|
|
||||||
//TODO
|
|
||||||
});
|
|
||||||
file.button("exit", app::quit);
|
|
||||||
launchMenu = menu.submenu("launch");
|
|
||||||
generateLaunchMenu();
|
|
||||||
accountsMenu = menu.submenu("account");
|
|
||||||
generateAccountsMenu();
|
|
||||||
var help = menu.submenu("help");
|
|
||||||
help.button("about", AboutWindow::createAndShow);
|
|
||||||
help.button("log", () -> {
|
|
||||||
//TODO
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void generateLaunchMenu() {
|
|
||||||
Objects.requireNonNull(launchMenu);
|
|
||||||
launchMenu.clear();
|
|
||||||
try {
|
|
||||||
InstanceList.forEach(entry -> {
|
|
||||||
launchMenu.literalButton(entry.id(), entry.toString(), () -> launch(entry));
|
|
||||||
});
|
|
||||||
} catch (IOException e) {
|
|
||||||
Utils.LOGGER.error("Could not generate launch menu", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void launch(Instance instance) {
|
|
||||||
//TODO show popup during launch w/ cancel option (lock main UI)
|
|
||||||
Runnable launch = () -> {
|
|
||||||
try {
|
|
||||||
Steps.reDownload(instance, Steps.createProcessState());
|
|
||||||
} catch (IOException e) {
|
|
||||||
Utils.LOGGER.error("Could not redownload instance, trying to start anyways", e);
|
|
||||||
}
|
|
||||||
InstanceLauncher.launchClient(instance);
|
|
||||||
};
|
|
||||||
if (instance.isSetupLocked()) {
|
|
||||||
LauncherEnv.showError(I18n.get("instance.launch.locked.setup"), I18n.get("instance.launch.locked"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (instance.isRunningLocked()) {
|
|
||||||
LauncherEnv.showOkCancel(I18n.get("instance.launch.locked.running"), I18n.get("instance.launch.locked"), () -> {
|
|
||||||
new Thread(launch).start(); //TODO loom
|
|
||||||
}, R::nop);
|
|
||||||
}
|
|
||||||
new Thread(launch).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void generateAccountsMenu() {
|
|
||||||
Objects.requireNonNull(accountsMenu);
|
|
||||||
accountsMenu.clear();
|
|
||||||
accountsMenu.button("new", () -> {
|
|
||||||
//TODO
|
|
||||||
});
|
|
||||||
accountsMenu.button("manage", () -> {
|
|
||||||
//TODO UI to add/remove accounts
|
|
||||||
});
|
|
||||||
List<MicrosoftAccount> accounts = new ArrayList<>(AccountManager.getAccounts());
|
|
||||||
accounts.add(null);
|
|
||||||
accountsMenu.literalRadio("account", accounts.get(AccountManager.getSelectedIndex()), accounts, (i, acc) -> {
|
|
||||||
if (acc == null) return I18n.get("account.none");
|
|
||||||
return acc.minecraftUsername;
|
|
||||||
}, AccountManager::switchAccount);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
package io.gitlab.jfronny.inceptum.gtk;
|
|
||||||
|
|
||||||
import ch.bailu.gtk.GTK;
|
|
||||||
import ch.bailu.gtk.gio.ApplicationFlags;
|
|
||||||
import ch.bailu.gtk.gtk.*;
|
|
||||||
import ch.bailu.gtk.type.Str;
|
|
||||||
import ch.bailu.gtk.type.Strs;
|
|
||||||
import io.gitlab.jfronny.inceptum.common.R;
|
|
||||||
|
|
||||||
public class TestStart {
|
|
||||||
public static void main(String[] args) {
|
|
||||||
var app = new Application(GtkMain.ID, ApplicationFlags.FLAGS_NONE);
|
|
||||||
app.onActivate(() -> {
|
|
||||||
var button = Button.newWithLabelButton(new Str("Test"));
|
|
||||||
button.onClicked(TestStart::test);
|
|
||||||
|
|
||||||
var window = new ApplicationWindow(app);
|
|
||||||
window.setDefaultSize(200, 50);
|
|
||||||
window.setTitle(new Str("Inceptum"));
|
|
||||||
window.setChild(button);
|
|
||||||
window.show();
|
|
||||||
GtkEnvBackend.INSTANCE.dialogParent = window;
|
|
||||||
window.onCloseRequest(() -> {
|
|
||||||
GtkEnvBackend.INSTANCE.dialogParent = null;
|
|
||||||
return GTK.FALSE;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
System.exit(app.run(args.length, new Strs(args)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void test() {
|
|
||||||
GtkEnvBackend backend = GtkEnvBackend.INSTANCE;
|
|
||||||
|
|
||||||
backend.getInput("Ae", "IoU\naee", "Def", R::nop, R::nop);
|
|
||||||
// backend.showInfo("Some message", "Title");
|
|
||||||
// backend.showError("Yes!", "AAee");
|
|
||||||
// backend.showError("Nes!", new ArrayIndexOutOfBoundsException("Top 500 cheese"));
|
|
||||||
// backend.showOkCancel("Some Message\nYes!", "TitL", () -> backend.showInfo("Pressed OK", "OK"), () -> backend.showError("Pressed CANCEL", "CANCEL"));
|
|
||||||
// backend.getInput("This is a prompt", """
|
|
||||||
// These are some extremely interesting and detailed (hence the name) details!
|
|
||||||
// This is a second line, since these details
|
|
||||||
// are just THAT important!""", "Default", value -> backend.showInfo("Received " + value, "Input"), () -> backend.showInfo("Got no input :/", "Input"));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,76 +0,0 @@
|
||||||
package io.gitlab.jfronny.inceptum.gtk.control;
|
|
||||||
|
|
||||||
import ch.bailu.gtk.GTK;
|
|
||||||
import ch.bailu.gtk.bridge.ListIndex;
|
|
||||||
import ch.bailu.gtk.gtk.*;
|
|
||||||
import ch.bailu.gtk.pango.EllipsizeMode;
|
|
||||||
import ch.bailu.gtk.type.Str;
|
|
||||||
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class InstanceGridEntryFactory extends SignalListItemFactory {
|
|
||||||
public InstanceGridEntryFactory(List<Instance> instanceList) {
|
|
||||||
super();
|
|
||||||
//TODO better design
|
|
||||||
onSetup(item -> {
|
|
||||||
var box = new Box(Orientation.VERTICAL, 5);
|
|
||||||
|
|
||||||
var thumbnail = new InstanceThumbnail();
|
|
||||||
box.append(thumbnail);
|
|
||||||
|
|
||||||
var label = new Label(Str.NULL);
|
|
||||||
label.setSizeRequest(192, -1);
|
|
||||||
label.setMaxWidthChars(20);
|
|
||||||
label.setJustify(Justification.CENTER);
|
|
||||||
label.setHalign(Align.START);
|
|
||||||
label.setHexpand(GTK.TRUE);
|
|
||||||
label.setValign(Align.CENTER);
|
|
||||||
label.setEllipsize(EllipsizeMode.MIDDLE);
|
|
||||||
label.setLines(3);
|
|
||||||
label.setWrap(GTK.TRUE);
|
|
||||||
label.setWrapMode(WrapMode.WORD_CHAR);
|
|
||||||
label.setMarginTop(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);
|
|
||||||
|
|
||||||
item.setChild(box);
|
|
||||||
//TODO server launch with network-server-symbolic
|
|
||||||
//TODO kill current instance
|
|
||||||
});
|
|
||||||
onBind(item -> {
|
|
||||||
// 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()));
|
|
||||||
Box box = new Box(item.getChild().cast());
|
|
||||||
InstanceThumbnail thumbnail = new InstanceThumbnail(box.getFirstChild().cast());
|
|
||||||
Label label = new Label(thumbnail.getNextSibling().cast());
|
|
||||||
|
|
||||||
Instance instance = instanceList.get(ListIndex.toIndex(item));
|
|
||||||
thumbnail.bind(instance);
|
|
||||||
label.setText(new Str(instance.toString()));
|
|
||||||
//TODO right click menu + double click action
|
|
||||||
//TODO edit button document-edit-symbolic -> edit-delete-symbolic, edit-copy-symbolic
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
package io.gitlab.jfronny.inceptum.gtk.control;
|
|
||||||
|
|
||||||
import ch.bailu.gtk.GTK;
|
|
||||||
import ch.bailu.gtk.adw.ActionRow;
|
|
||||||
import ch.bailu.gtk.bridge.ListIndex;
|
|
||||||
import ch.bailu.gtk.gtk.*;
|
|
||||||
import ch.bailu.gtk.type.Str;
|
|
||||||
import io.gitlab.jfronny.inceptum.common.Utils;
|
|
||||||
import io.gitlab.jfronny.inceptum.gtk.GtkMenubar;
|
|
||||||
import io.gitlab.jfronny.inceptum.gtk.util.I18n;
|
|
||||||
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class InstanceListEntryFactory extends SignalListItemFactory {
|
|
||||||
public InstanceListEntryFactory(List<Instance> instanceList) {
|
|
||||||
super();
|
|
||||||
onSetup(item -> {
|
|
||||||
var thumbnail = new InstanceThumbnail();
|
|
||||||
thumbnail.setName(new Str("inceptum-thumbnail"));
|
|
||||||
|
|
||||||
var launch = new Button();
|
|
||||||
launch.setName(new Str("inceptum-launch"));
|
|
||||||
launch.setIconName(new Str("computer-symbolic"));
|
|
||||||
launch.setTooltipText(I18n.str("instance.launch"));
|
|
||||||
launch.setHasTooltip(GTK.TRUE);
|
|
||||||
|
|
||||||
var openDir = new Button();
|
|
||||||
openDir.setName(new Str("inceptum-open-dir"));
|
|
||||||
openDir.setIconName(new Str("folder-symbolic"));
|
|
||||||
openDir.setTooltipText(I18n.str("instance.directory"));
|
|
||||||
openDir.setHasTooltip(GTK.TRUE);
|
|
||||||
|
|
||||||
var row = new ActionRow();
|
|
||||||
row.setName(new Str("inceptum-row"));
|
|
||||||
row.setActivatableWidget(launch);
|
|
||||||
row.addPrefix(thumbnail);
|
|
||||||
row.addSuffix(launch);
|
|
||||||
row.addSuffix(openDir);
|
|
||||||
|
|
||||||
item.setChild(row);
|
|
||||||
|
|
||||||
//TODO server launch with network-server-symbolic
|
|
||||||
//TODO kill current instance
|
|
||||||
});
|
|
||||||
onBind(item -> {
|
|
||||||
Instance instance = instanceList.get(ListIndex.toIndex(item));
|
|
||||||
ActionRow row = new ActionRow(item.getChild().cast());
|
|
||||||
Box prefixes = new Box(row.getFirstChild().getFirstChild().cast());
|
|
||||||
Box suffixes = new Box(row.getFirstChild().getLastChild().cast());
|
|
||||||
|
|
||||||
InstanceThumbnail thumbnail = new InstanceThumbnail(prefixes.getFirstChild().cast());
|
|
||||||
Button launch = new Button(suffixes.getFirstChild().cast());
|
|
||||||
Button openDir = new Button(launch.getNextSibling().cast());
|
|
||||||
row.setTitle(new Str(instance.toString()));
|
|
||||||
|
|
||||||
// InstanceThumbnail thumbnail = new InstanceThumbnail(row.getFirstChild().cast());
|
|
||||||
// Label label = new Label(thumbnail.getNextSibling().cast());
|
|
||||||
// Button launch = new Button(label.getNextSibling().cast());
|
|
||||||
// Button openDir = new Button(launch.getNextSibling().cast());
|
|
||||||
|
|
||||||
thumbnail.bind(instance);
|
|
||||||
// label.setText(new Str(instance.toString()));
|
|
||||||
launch.onClicked(() -> GtkMenubar.launch(instance));
|
|
||||||
openDir.onClicked(() -> Utils.openFile(instance.path().toFile()));
|
|
||||||
|
|
||||||
//TODO why the hell does this crash the VM?
|
|
||||||
//TODO GestureClick.setButton(GDK_BUTTON_SECONDARY)
|
|
||||||
// var controller = new EventControllerLegacy();
|
|
||||||
// controller.onEvent(event -> {
|
|
||||||
// if (event.getEventType() == EventType.BUTTON_RELEASE) {
|
|
||||||
// Utils.LOGGER.info("Button " + new ButtonEvent(event.cast()).getButton());
|
|
||||||
// }
|
|
||||||
// return GTK.FALSE;
|
|
||||||
// });
|
|
||||||
// row.addController(controller);
|
|
||||||
//TODO edit button document-edit-symbolic -> edit-delete-symbolic, edit-copy-symbolic
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
package io.gitlab.jfronny.inceptum.gtk.control;
|
|
||||||
|
|
||||||
import ch.bailu.gtk.gtk.*;
|
|
||||||
import ch.bailu.gtk.type.*;
|
|
||||||
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance;
|
|
||||||
|
|
||||||
public class InstanceThumbnail extends Stack {
|
|
||||||
private static final Str SPINNER = new Str("spinner");
|
|
||||||
private static final Str IMAGE = new Str("image");
|
|
||||||
private static final Str GENERIC = new Str("generic");
|
|
||||||
|
|
||||||
public InstanceThumbnail(CPointer pointer) {
|
|
||||||
super(pointer);
|
|
||||||
}
|
|
||||||
|
|
||||||
public InstanceThumbnail() {
|
|
||||||
super();
|
|
||||||
var spinner = new Spinner();
|
|
||||||
var image = new Image();
|
|
||||||
var generic = new Image();
|
|
||||||
spinner.setName(SPINNER);
|
|
||||||
image.setName(IMAGE);
|
|
||||||
generic.setName(GENERIC);
|
|
||||||
generic.setFromIconName(new Str("media-playback-start-symbolic")); //TODO better default icon
|
|
||||||
addNamed(spinner, SPINNER);
|
|
||||||
addNamed(image, IMAGE);
|
|
||||||
addNamed(generic, GENERIC);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void bind(Instance entry) {
|
|
||||||
var spinner = new Spinner(getChildByName(SPINNER).cast());
|
|
||||||
var image = new Image(getChildByName(IMAGE).cast()); //TODO
|
|
||||||
var generic = new Image(getChildByName(GENERIC).cast());
|
|
||||||
//TODO mark instance being played
|
|
||||||
if (entry.isSetupLocked()) {
|
|
||||||
setVisibleChild(spinner);
|
|
||||||
} else if (false) { // if the instance has an image, load the image data and set it as the visible child
|
|
||||||
setVisibleChild(image);
|
|
||||||
} else {
|
|
||||||
setVisibleChild(generic);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
package io.gitlab.jfronny.inceptum.gtk.menu;
|
|
||||||
|
|
||||||
import ch.bailu.gtk.gio.SimpleAction;
|
|
||||||
|
|
||||||
public class ButtonItem extends MenuItem {
|
|
||||||
public ButtonItem(SimpleAction action) {
|
|
||||||
super(action);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,138 +0,0 @@
|
||||||
package io.gitlab.jfronny.inceptum.gtk.menu;
|
|
||||||
|
|
||||||
import ch.bailu.gtk.GTK;
|
|
||||||
import ch.bailu.gtk.gio.MenuItem;
|
|
||||||
import ch.bailu.gtk.gio.*;
|
|
||||||
import ch.bailu.gtk.glib.Variant;
|
|
||||||
import ch.bailu.gtk.glib.VariantType;
|
|
||||||
import ch.bailu.gtk.gtk.Application;
|
|
||||||
import ch.bailu.gtk.gtk.PopoverMenu;
|
|
||||||
import ch.bailu.gtk.type.Pointer;
|
|
||||||
import ch.bailu.gtk.type.Str;
|
|
||||||
import io.gitlab.jfronny.commons.throwable.ThrowingRunnable;
|
|
||||||
import io.gitlab.jfronny.inceptum.common.Utils;
|
|
||||||
import io.gitlab.jfronny.inceptum.gtk.util.I18n;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.function.BiFunction;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
public class MenuBuilder {
|
|
||||||
private static final Object LOCK = new Object();
|
|
||||||
|
|
||||||
private static Menu getRootMenu(Application app) {
|
|
||||||
synchronized (LOCK) {
|
|
||||||
var currentMenu = app.getMenubar();
|
|
||||||
if (currentMenu.equals(Pointer.NULL)) {
|
|
||||||
var menu = new Menu();
|
|
||||||
app.setMenubar(menu);
|
|
||||||
return menu;
|
|
||||||
} else {
|
|
||||||
return new Menu(currentMenu.cast());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final ActionMap map;
|
|
||||||
private final Map<String, Action> refs = new LinkedHashMap<>();
|
|
||||||
private final Menu menu;
|
|
||||||
private final String prefix;
|
|
||||||
|
|
||||||
public MenuBuilder(Application app) {
|
|
||||||
this(app, getRootMenu(app), "");
|
|
||||||
}
|
|
||||||
|
|
||||||
public MenuBuilder(Application app, Menu menu, String prefix) {
|
|
||||||
this(new ActionMap(app.cast()), menu, prefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MenuBuilder(ActionMap map, Menu menu, String prefix) {
|
|
||||||
if (!prefix.isEmpty() && !prefix.endsWith(".")) prefix += ".";
|
|
||||||
this.map = Objects.requireNonNull(map);
|
|
||||||
this.menu = Objects.requireNonNull(menu);
|
|
||||||
this.prefix = Objects.requireNonNull(prefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ButtonItem button(String name, ThrowingRunnable<?> onClick) {
|
|
||||||
return literalButton(name, I18n.get("menu." + prefix + name), onClick);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ButtonItem literalButton(String internalName, String label, ThrowingRunnable<?> onClick) {
|
|
||||||
internalName = prefix + internalName;
|
|
||||||
SimpleAction sAct = new SimpleAction(new Str(internalName), null);
|
|
||||||
addAction(internalName, sAct);
|
|
||||||
sAct.onActivate(v -> {
|
|
||||||
try {
|
|
||||||
onClick.run();
|
|
||||||
} catch (Throwable e) {
|
|
||||||
Utils.LOGGER.error("Could not execute action", e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
menu.appendItem(new MenuItem(new Str(label), new Str("app." + internalName)));
|
|
||||||
return new ButtonItem(sAct);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ToggleItem toggle(String name, boolean initial, Consumer<Boolean> action) {
|
|
||||||
name = prefix + name;
|
|
||||||
SimpleAction sAct = SimpleAction.newStatefulSimpleAction(new Str(name), null, Variant.newBooleanVariant(GTK.is(initial)));
|
|
||||||
Action rAct = addAction(name, sAct);
|
|
||||||
sAct.onActivate(parameter -> {
|
|
||||||
boolean state = !GTK.is(rAct.getState().getBoolean());
|
|
||||||
sAct.setState(Variant.newBooleanVariant(GTK.is(state)));
|
|
||||||
action.accept(state);
|
|
||||||
});
|
|
||||||
menu.appendItem(new MenuItem(I18n.str("menu." + name), new Str("app." + name)));
|
|
||||||
return new ToggleItem(sAct);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> RadioItem<T> radio(String name, T initial, List<T> options, Consumer<T> action) {
|
|
||||||
return literalRadio(name, initial, options, (i, t) -> I18n.get("menu." + name, i), action);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> RadioItem<T> literalRadio(String name, T initial, List<T> options, BiFunction<Integer, T, String> stringifier, Consumer<T> action) {
|
|
||||||
Objects.requireNonNull(options);
|
|
||||||
name = prefix + name;
|
|
||||||
SimpleAction sAct = SimpleAction.newStatefulSimpleAction(new Str(name), new VariantType(new Str("i")), Variant.newInt32Variant(options.indexOf(initial)));
|
|
||||||
addAction(name, sAct);
|
|
||||||
sAct.onActivate(parameter -> {
|
|
||||||
sAct.setState(parameter);
|
|
||||||
action.accept(options.get(parameter.getInt32()));
|
|
||||||
});
|
|
||||||
int i = 0;
|
|
||||||
for (T option : options) {
|
|
||||||
menu.appendItem(new MenuItem(new Str(stringifier.apply(i, option)), new Str("app." + name + "(" + i + ")")));
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
return new RadioItem<>(sAct, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MenuBuilder submenu(String name) {
|
|
||||||
name = prefix + name;
|
|
||||||
Menu submenu = new Menu();
|
|
||||||
menu.appendSubmenu(I18n.str("menu." + name), submenu);
|
|
||||||
return new MenuBuilder(map, submenu, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clear() {
|
|
||||||
menu.removeAll();
|
|
||||||
refs.forEach((name, action) -> {
|
|
||||||
map.removeAction(new Str(name));
|
|
||||||
});
|
|
||||||
refs.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Action addAction(String name, SimpleAction action) {
|
|
||||||
Action rAct = new Action(action.cast());
|
|
||||||
map.addAction(rAct);
|
|
||||||
refs.put(name, rAct);
|
|
||||||
return rAct;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Menu getMenu() {
|
|
||||||
return menu;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PopoverMenu asPopover() {
|
|
||||||
return PopoverMenu.newFromModelPopoverMenu(menu);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
package io.gitlab.jfronny.inceptum.gtk.menu;
|
|
||||||
|
|
||||||
import ch.bailu.gtk.GTK;
|
|
||||||
import ch.bailu.gtk.gio.Action;
|
|
||||||
import ch.bailu.gtk.gio.SimpleAction;
|
|
||||||
|
|
||||||
public abstract class MenuItem {
|
|
||||||
protected final SimpleAction sAction;
|
|
||||||
protected final Action action;
|
|
||||||
|
|
||||||
public MenuItem(SimpleAction action) {
|
|
||||||
this.sAction = action;
|
|
||||||
this.action = new Action(action.cast());
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean getEnabled() {
|
|
||||||
return GTK.is(action.getEnabled());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEnabled(boolean enabled) {
|
|
||||||
sAction.setEnabled(GTK.is(enabled));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
package io.gitlab.jfronny.inceptum.gtk.menu;
|
|
||||||
|
|
||||||
import ch.bailu.gtk.gio.SimpleAction;
|
|
||||||
import ch.bailu.gtk.glib.Variant;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class RadioItem<T> extends MenuItem {
|
|
||||||
private final List<T> options;
|
|
||||||
|
|
||||||
public RadioItem(SimpleAction action, List<T> options) {
|
|
||||||
super(action);
|
|
||||||
this.options = options;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSelected(T selected) {
|
|
||||||
sAction.setState(Variant.newInt32Variant(options.indexOf(selected)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public T getSelected() {
|
|
||||||
return options.get(action.getState().getInt32());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
package io.gitlab.jfronny.inceptum.gtk.menu;
|
|
||||||
|
|
||||||
import ch.bailu.gtk.GTK;
|
|
||||||
import ch.bailu.gtk.gio.SimpleAction;
|
|
||||||
import ch.bailu.gtk.glib.Variant;
|
|
||||||
|
|
||||||
public class ToggleItem extends MenuItem {
|
|
||||||
public ToggleItem(SimpleAction action) {
|
|
||||||
super(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean getState() {
|
|
||||||
return GTK.is(action.getState().getBoolean());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setState(boolean state) {
|
|
||||||
sAction.setState(Variant.newBooleanVariant(GTK.is(state)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean toggle() {
|
|
||||||
boolean toggled = !getState();
|
|
||||||
setState(toggled);
|
|
||||||
return toggled;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
package io.gitlab.jfronny.inceptum.gtk.util;
|
|
||||||
|
|
||||||
import ch.bailu.gtk.gtk.Widget;
|
|
||||||
import ch.bailu.gtk.type.Pointer;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public class Dbg {
|
|
||||||
public static String inspect(Widget ptr) {
|
|
||||||
if (ptr.isNull()) return "<null>";
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
inspect(ptr, sb, "");
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void inspect(Widget ptr, StringBuilder bld, String indent) {
|
|
||||||
bld.append(indent).append("<").append(ptr.getName().toString()).append("#").append(ptr.getCPointer()).append("> ")
|
|
||||||
.append(Arrays.stream(Pointer.toJnaPointer(ptr.getCssClasses().getCPointer()).getPointerArray(0))
|
|
||||||
.map(p -> p.getString(0))
|
|
||||||
.collect(Collectors.joining(", ")));
|
|
||||||
ptr = ptr.getFirstChild();
|
|
||||||
if (ptr.isNotNull()) {
|
|
||||||
while (ptr.isNotNull()) {
|
|
||||||
bld.append("\n");
|
|
||||||
inspect(ptr, bld, indent + " ");
|
|
||||||
ptr = ptr.getNextSibling();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
package io.gitlab.jfronny.inceptum.gtk.util;
|
|
||||||
|
|
||||||
import ch.bailu.gtk.type.Str;
|
|
||||||
import org.jetbrains.annotations.PropertyKey;
|
|
||||||
|
|
||||||
import java.util.ResourceBundle;
|
|
||||||
|
|
||||||
public class I18n {
|
|
||||||
private static final String BUNDLE = "inceptum";
|
|
||||||
private static final ResourceBundle bundle = ResourceBundle.getBundle(BUNDLE);
|
|
||||||
|
|
||||||
public static String get(@PropertyKey(resourceBundle = BUNDLE) String key) {
|
|
||||||
return bundle.getString(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String get(@PropertyKey(resourceBundle = BUNDLE) String key, Object... args) {
|
|
||||||
return String.format(bundle.getString(key), args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Str str(@PropertyKey(resourceBundle = BUNDLE) String key) {
|
|
||||||
return new Str(get(key));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Str str(@PropertyKey(resourceBundle = BUNDLE) String key, Object... args) {
|
|
||||||
return new Str(get(key, args));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
package io.gitlab.jfronny.inceptum.gtk.window;
|
|
||||||
|
|
||||||
import ch.bailu.gtk.gtk.AboutDialog;
|
|
||||||
import ch.bailu.gtk.gtk.License;
|
|
||||||
import ch.bailu.gtk.type.Str;
|
|
||||||
import io.gitlab.jfronny.inceptum.common.BuildMetadata;
|
|
||||||
import io.gitlab.jfronny.inceptum.gtk.util.I18n;
|
|
||||||
|
|
||||||
public class AboutWindow extends AboutDialog {
|
|
||||||
public AboutWindow() {
|
|
||||||
setProgramName(new Str("Inceptum"));
|
|
||||||
setCopyright(new Str("Copyright (C) 2021 JFronny"));
|
|
||||||
setVersion(new Str(BuildMetadata.VERSION.toString()));
|
|
||||||
setLicenseType(License.MIT_X11);
|
|
||||||
setLicense(I18n.str("about.license"));
|
|
||||||
setWebsiteLabel(I18n.str("about.contact"));
|
|
||||||
setWebsite(new Str("https://jfronny.gitlab.io/contact.html"));
|
|
||||||
if (!BuildMetadata.IS_PUBLIC) {
|
|
||||||
setComments(I18n.str("about.unsupported-build"));
|
|
||||||
}
|
|
||||||
int vm = Runtime.version().feature();
|
|
||||||
setSystemInformation(I18n.str(BuildMetadata.VM_VERSION == vm ? "about.jvm" : "about.jvm.unsupported", vm));
|
|
||||||
//TODO setLogo
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void createAndShow() {
|
|
||||||
new AboutWindow().show();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,161 +0,0 @@
|
||||||
package io.gitlab.jfronny.inceptum.gtk.window;
|
|
||||||
|
|
||||||
import ch.bailu.gtk.GTK;
|
|
||||||
import ch.bailu.gtk.adw.Clamp;
|
|
||||||
import ch.bailu.gtk.adw.StatusPage;
|
|
||||||
import ch.bailu.gtk.bridge.ListIndex;
|
|
||||||
import ch.bailu.gtk.gio.Menu;
|
|
||||||
import ch.bailu.gtk.glib.Glib;
|
|
||||||
import ch.bailu.gtk.gtk.*;
|
|
||||||
import ch.bailu.gtk.type.Str;
|
|
||||||
import io.gitlab.jfronny.inceptum.common.*;
|
|
||||||
import io.gitlab.jfronny.inceptum.gtk.GtkMenubar;
|
|
||||||
import io.gitlab.jfronny.inceptum.gtk.control.InstanceGridEntryFactory;
|
|
||||||
import io.gitlab.jfronny.inceptum.gtk.control.InstanceListEntryFactory;
|
|
||||||
import io.gitlab.jfronny.inceptum.gtk.menu.MenuBuilder;
|
|
||||||
import io.gitlab.jfronny.inceptum.gtk.util.I18n;
|
|
||||||
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance;
|
|
||||||
import io.gitlab.jfronny.inceptum.launcher.system.instance.InstanceList;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.nio.file.*;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static java.nio.file.StandardWatchEventKinds.*;
|
|
||||||
|
|
||||||
public class MainWindow extends ApplicationWindow {
|
|
||||||
private final Button listButton;
|
|
||||||
private final Button gridButton;
|
|
||||||
private final Stack stack;
|
|
||||||
private final StatusPage empty;
|
|
||||||
private final Clamp listView;
|
|
||||||
private final GridView gridView;
|
|
||||||
private final List<Instance> instanceList;
|
|
||||||
private final ListIndex instanceListIndex;
|
|
||||||
|
|
||||||
public MainWindow(Application app) {
|
|
||||||
super(app);
|
|
||||||
|
|
||||||
HeaderBar header = new HeaderBar();
|
|
||||||
Button newButton = new Button();
|
|
||||||
newButton.setIconName(new Str("list-add-symbolic"));
|
|
||||||
newButton.onClicked(NewInstanceWindow::createAndShow);
|
|
||||||
|
|
||||||
MenuButton accountsButton = new MenuButton();
|
|
||||||
accountsButton.setIconName(new Str("avatar-default-symbolic"));
|
|
||||||
accountsButton.setPopover(GtkMenubar.accountsMenu.asPopover());
|
|
||||||
|
|
||||||
listButton = new Button();
|
|
||||||
listButton.setIconName(new Str("view-list-symbolic"));
|
|
||||||
listButton.onClicked(() -> {
|
|
||||||
InceptumConfig.listView = true;
|
|
||||||
InceptumConfig.saveConfig();
|
|
||||||
generateWindowBody();
|
|
||||||
});
|
|
||||||
|
|
||||||
gridButton = new Button();
|
|
||||||
gridButton.setIconName(new Str("view-grid-symbolic"));
|
|
||||||
gridButton.onClicked(() -> {
|
|
||||||
InceptumConfig.listView = false;
|
|
||||||
InceptumConfig.saveConfig();
|
|
||||||
generateWindowBody();
|
|
||||||
});
|
|
||||||
|
|
||||||
//TODO search button like boxes
|
|
||||||
|
|
||||||
MenuBuilder uiMenu = new MenuBuilder(app, new Menu(), "hamburger");
|
|
||||||
uiMenu.button("support", () -> Utils.openWebBrowser(new URI("https://gitlab.com/jfmods/inceptum/-/issues")));
|
|
||||||
uiMenu.button("preferences", () -> {}); //TODO preferences UI inspired by boxes
|
|
||||||
uiMenu.button("about", AboutWindow::createAndShow);
|
|
||||||
MenuButton menuButton = new MenuButton();
|
|
||||||
menuButton.setIconName(new Str("open-menu-symbolic"));
|
|
||||||
menuButton.setPopover(uiMenu.asPopover());
|
|
||||||
|
|
||||||
header.packStart(newButton);
|
|
||||||
|
|
||||||
header.packEnd(menuButton);
|
|
||||||
header.packEnd(gridButton);
|
|
||||||
header.packEnd(listButton);
|
|
||||||
header.packEnd(accountsButton);
|
|
||||||
|
|
||||||
instanceList = new ArrayList<>();
|
|
||||||
instanceListIndex = new ListIndex();
|
|
||||||
|
|
||||||
listView = new Clamp();
|
|
||||||
listView.setMaximumSize(900);
|
|
||||||
listView.setChild(new ListView(instanceListIndex.inSelectionModel(), new InstanceListEntryFactory(instanceList)));
|
|
||||||
gridView = new GridView(instanceListIndex.inSelectionModel(), new InstanceGridEntryFactory(instanceList));
|
|
||||||
empty = new StatusPage();
|
|
||||||
empty.setTitle(I18n.str("main.empty.title"));
|
|
||||||
empty.setDescription(I18n.str("main.empty.description"));
|
|
||||||
//TODO empty.setIconName(new Str());
|
|
||||||
stack = new Stack();
|
|
||||||
stack.addChild(listView);
|
|
||||||
stack.addChild(gridView);
|
|
||||||
|
|
||||||
ScrolledWindow scroll = new ScrolledWindow();
|
|
||||||
scroll.setPolicy(PolicyType.NEVER, PolicyType.AUTOMATIC);
|
|
||||||
scroll.setChild(stack);
|
|
||||||
|
|
||||||
setDefaultSize(360, 720);
|
|
||||||
setTitle(new Str("Inceptum"));
|
|
||||||
setTitlebar(header);
|
|
||||||
setShowMenubar(GTK.FALSE);
|
|
||||||
setChild(scroll);
|
|
||||||
|
|
||||||
generateWindowBody();
|
|
||||||
//TODO DropTarget to add mods/instances
|
|
||||||
|
|
||||||
try {
|
|
||||||
setupDirWatcher();
|
|
||||||
} catch (IOException e) {
|
|
||||||
Utils.LOGGER.error("Could not set up watch service, live updates of the instance dir will be unavailable", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupDirWatcher() throws IOException { //TODO test (including after lock state change)
|
|
||||||
WatchService ws = FileSystems.getDefault().newWatchService();
|
|
||||||
MetaHolder.INSTANCE_DIR.register(ws, ENTRY_MODIFY, ENTRY_CREATE, ENTRY_DELETE);
|
|
||||||
int source = Glib.idleAdd(user_data -> {
|
|
||||||
//TODO watch instance dirs for locks
|
|
||||||
WatchKey key = ws.poll();
|
|
||||||
boolean instancesChanged = false;
|
|
||||||
if (key != null) {
|
|
||||||
for (WatchEvent<?> event : key.pollEvents()) {
|
|
||||||
if (event.context() instanceof Path p) {
|
|
||||||
p = MetaHolder.INSTANCE_DIR.resolve(p);
|
|
||||||
instancesChanged |= Files.exists(p.resolve("instance.json"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (instancesChanged) generateWindowBody();
|
|
||||||
return GTK.TRUE;
|
|
||||||
}, null);
|
|
||||||
onCloseRequest(() -> {
|
|
||||||
try {
|
|
||||||
ws.close();
|
|
||||||
} catch (IOException ignored) {
|
|
||||||
}
|
|
||||||
Glib.sourceRemove(source);
|
|
||||||
return GTK.FALSE;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void generateWindowBody() {
|
|
||||||
if (listButton != null) listButton.setVisible(GTK.is(!InceptumConfig.listView));
|
|
||||||
if (gridButton != null) gridButton.setVisible(GTK.is(InceptumConfig.listView));
|
|
||||||
try {
|
|
||||||
instanceList.clear();
|
|
||||||
instanceList.addAll(InstanceList.ordered());
|
|
||||||
instanceListIndex.setSize(instanceList.size());
|
|
||||||
|
|
||||||
if (InstanceList.isEmpty()) stack.setVisibleChild(empty);
|
|
||||||
else if (InceptumConfig.listView) stack.setVisibleChild(listView);
|
|
||||||
else stack.setVisibleChild(gridView);
|
|
||||||
} catch (IOException e) {
|
|
||||||
Utils.LOGGER.error("Could not generate window body", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
package io.gitlab.jfronny.inceptum.gtk.window;
|
|
||||||
|
|
||||||
public class NewInstanceWindow {
|
|
||||||
public static void createAndShow() {
|
|
||||||
//TODO
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
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 {
|
||||||
|
val dialog = StringInputDialog(
|
||||||
|
dialogParent,
|
||||||
|
if (dialogParent == null) setOf(DialogFlags.DESTROY_WITH_PARENT) else setOf(DialogFlags.DESTROY_WITH_PARENT, DialogFlags.MODAL),
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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) }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package io.gitlab.jfronny.inceptum.gtk.control
|
||||||
|
|
||||||
|
import org.gnome.gtk.Align
|
||||||
|
import org.gnome.gtk.ProgressBar
|
||||||
|
import org.gnome.gtk.Revealer
|
||||||
|
import org.gnome.gtk.RevealerTransitionType
|
||||||
|
|
||||||
|
class LoadingRevealer: Revealer() {
|
||||||
|
private val progressBar: ProgressBar
|
||||||
|
|
||||||
|
init {
|
||||||
|
transitionType = RevealerTransitionType.CROSSFADE
|
||||||
|
revealChild = false
|
||||||
|
vexpand = false
|
||||||
|
hexpand = false
|
||||||
|
valign = Align.CENTER
|
||||||
|
halign = Align.FILL
|
||||||
|
progressBar = ProgressBar().apply {
|
||||||
|
hexpand = true
|
||||||
|
vexpand = false
|
||||||
|
addCssClass("osd")
|
||||||
|
}
|
||||||
|
child = progressBar
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setRunning(state: Boolean) {
|
||||||
|
revealChild = state
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setProgress(progress: Double) {
|
||||||
|
progressBar.fraction = progress.coerceIn(0.0, 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pulse() {
|
||||||
|
progressBar.pulse()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package io.gitlab.jfronny.inceptum.gtk.control.assistant
|
||||||
|
|
||||||
|
import org.gnome.gtk.AssistantPageType
|
||||||
|
import org.gnome.gtk.Box
|
||||||
|
import org.gnome.gtk.Orientation
|
||||||
|
|
||||||
|
class AssistantPage(val title: String, val type: AssistantPageType): Box(Orientation.VERTICAL, 8) {
|
||||||
|
lateinit var setComplete: (Boolean) -> Unit
|
||||||
|
private val onOpen = ArrayList<Runnable>()
|
||||||
|
fun onOpen(action: Runnable) {
|
||||||
|
onOpen.add(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun emitOpen() = onOpen.forEach { it.run() }
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package io.gitlab.jfronny.inceptum.gtk.control.assistant
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.Log
|
||||||
|
import org.gnome.gtk.Application
|
||||||
|
import org.gnome.gtk.Assistant
|
||||||
|
import org.gnome.gtk.AssistantPageType
|
||||||
|
|
||||||
|
open class KAssistant(app: Application) : Assistant() {
|
||||||
|
private val pages = ArrayList<AssistantPage>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
application = app
|
||||||
|
onPrepare { next ->
|
||||||
|
val page = pages.firstOrNull { it.handle() == next.handle() }
|
||||||
|
if (page == null) {
|
||||||
|
Log.error("Unknown page opened in assistant")
|
||||||
|
} else {
|
||||||
|
page.emitOpen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun page(title: String, type: AssistantPageType, setup: AssistantPage.() -> Unit): Int {
|
||||||
|
val page = AssistantPage(title, type)
|
||||||
|
val idx = appendPage(page)
|
||||||
|
pages.add(page)
|
||||||
|
page.setComplete = { setPageComplete(page, it) }
|
||||||
|
page.setup()
|
||||||
|
setPageType(page, page.type)
|
||||||
|
setPageTitle(page, page.title)
|
||||||
|
return idx
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
package io.gitlab.jfronny.inceptum.gtk.control.settings
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.control.ILabel
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.control.KDropDown
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.control.KEntry
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.I18n
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.margin
|
||||||
|
import org.gnome.gtk.*
|
||||||
|
import org.jetbrains.annotations.PropertyKey
|
||||||
|
import java.util.function.Consumer
|
||||||
|
import java.util.function.DoubleConsumer
|
||||||
|
import java.util.function.IntConsumer
|
||||||
|
|
||||||
|
class IRow(
|
||||||
|
title: @PropertyKey(resourceBundle = I18n.BUNDLE) String,
|
||||||
|
subtitle: @PropertyKey(resourceBundle = I18n.BUNDLE) String?,
|
||||||
|
vararg args: Any?
|
||||||
|
) : Box(Orientation.HORIZONTAL, 40) {
|
||||||
|
init {
|
||||||
|
margin = 8
|
||||||
|
val head: Widget
|
||||||
|
val lab = ILabel(title, *args)
|
||||||
|
lab.halign = Align.START
|
||||||
|
if (subtitle != null) {
|
||||||
|
val headB = Box(Orientation.VERTICAL, 0)
|
||||||
|
headB.append(lab)
|
||||||
|
val lab1 = ILabel(subtitle, ILabel.Mode.SUBTITLE, *args)
|
||||||
|
lab1.halign = Align.START
|
||||||
|
headB.append(lab1)
|
||||||
|
head = headB
|
||||||
|
} else {
|
||||||
|
head = lab
|
||||||
|
}
|
||||||
|
head.halign = Align.START
|
||||||
|
head.valign = Align.CENTER
|
||||||
|
append(head)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setButton(text: @PropertyKey(resourceBundle = I18n.BUNDLE) String, action: Button.ClickedCallback?): Button =
|
||||||
|
Button.withLabel(I18n[text]).apply {
|
||||||
|
packSmallEnd()
|
||||||
|
onClicked(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setDropdown(options: Array<String>, defaultIndex: Int, changed: IntConsumer): KDropDown<String> =
|
||||||
|
KDropDown(options, { it } , defaultIndex).apply {
|
||||||
|
onChange(changed)
|
||||||
|
packSmallEnd()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSwitch(value: Boolean, changed: Consumer<Boolean>): Switch =
|
||||||
|
Switch().apply {
|
||||||
|
packSmallEnd()
|
||||||
|
active = value
|
||||||
|
onStateSet { state: Boolean ->
|
||||||
|
changed.accept(state)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSpinButton(value: Double, min: Double, max: Double, step: Double, changed: DoubleConsumer): SpinButton =
|
||||||
|
SpinButton.withRange(min, max, step).apply {
|
||||||
|
packSmallEnd()
|
||||||
|
this.value = value
|
||||||
|
onValueChanged { changed.accept(this.value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setEntry(value: String?, changed: Consumer<String>): KEntry =
|
||||||
|
KEntry(value).apply {
|
||||||
|
hexpand = true
|
||||||
|
valign = Align.CENTER
|
||||||
|
halign = Align.FILL
|
||||||
|
onChange(changed)
|
||||||
|
append(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Widget.packSmallEnd() {
|
||||||
|
firstChild!!.hexpand = true
|
||||||
|
valign = Align.CENTER
|
||||||
|
halign = Align.END
|
||||||
|
this@IRow.append(this)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
package io.gitlab.jfronny.inceptum.gtk.control.settings
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.control.ILabel
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.I18n
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.marginHorizontal
|
||||||
|
import org.gnome.gtk.*
|
||||||
|
import org.jetbrains.annotations.PropertyKey
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
|
open class SectionedSettingsTab<W : Window>(window: W?): SettingsTab<Box, W>(window, Box(Orientation.VERTICAL, 8)) {
|
||||||
|
init {
|
||||||
|
content.marginHorizontal = 24
|
||||||
|
content.marginTop = 12
|
||||||
|
}
|
||||||
|
|
||||||
|
fun append(child: Widget) = content.append(child)
|
||||||
|
|
||||||
|
protected fun section(title: @PropertyKey(resourceBundle = I18n.BUNDLE) String?, builder: Section.() -> Unit) {
|
||||||
|
if (title != null) append(ILabel(title, ILabel.Mode.HEADING))
|
||||||
|
val frame = Frame(null as String?)
|
||||||
|
val listBox = ListBox()
|
||||||
|
listBox.selectionMode = SelectionMode.NONE
|
||||||
|
listBox.showSeparators = true
|
||||||
|
frame.child = listBox
|
||||||
|
val count = AtomicInteger(0)
|
||||||
|
builder(object : Section {
|
||||||
|
override fun row(title: String, subtitle: String?, vararg args: Any?, build: IRow.() -> Unit): IRow {
|
||||||
|
val row = IRow(title, subtitle, *args)
|
||||||
|
row(row)
|
||||||
|
build(row)
|
||||||
|
return row
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun row(row: Widget): ListBoxRow {
|
||||||
|
listBox.append(row)
|
||||||
|
return listBox.getRowAtIndex(count.getAndIncrement())!!
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun remove(row: Widget) {
|
||||||
|
listBox.remove(row)
|
||||||
|
count.decrementAndGet()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clear() {
|
||||||
|
var i = 0
|
||||||
|
val len = count.getAndSet(0)
|
||||||
|
while (i < len) {
|
||||||
|
listBox.remove(listBox.getRowAtIndex(0))
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
append(frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Section {
|
||||||
|
fun row(
|
||||||
|
title: @PropertyKey(resourceBundle = I18n.BUNDLE) String,
|
||||||
|
subtitle: @PropertyKey(resourceBundle = I18n.BUNDLE) String?,
|
||||||
|
vararg args: Any?
|
||||||
|
): IRow = row(title, subtitle, *args, build = {})
|
||||||
|
|
||||||
|
fun row(
|
||||||
|
title: @PropertyKey(resourceBundle = I18n.BUNDLE) String,
|
||||||
|
subtitle: @PropertyKey(resourceBundle = I18n.BUNDLE) String?,
|
||||||
|
vararg args: Any?,
|
||||||
|
build: IRow.() -> Unit
|
||||||
|
): IRow
|
||||||
|
|
||||||
|
fun row(row: Widget): ListBoxRow?
|
||||||
|
fun remove(row: Widget)
|
||||||
|
fun clear()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package io.gitlab.jfronny.inceptum.gtk.control.settings
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.commons.StringFormatter
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.GtkEnvBackend
|
||||||
|
import org.gnome.gtk.Widget
|
||||||
|
import org.gnome.gtk.Window
|
||||||
|
|
||||||
|
open class SettingsTab<T : Widget, W : Window>(
|
||||||
|
protected val window: W?,
|
||||||
|
val content: T
|
||||||
|
) {
|
||||||
|
protected fun showError(message: String, t: Throwable) =
|
||||||
|
GtkEnvBackend.simpleDialog(
|
||||||
|
window,
|
||||||
|
StringFormatter.toString(t),
|
||||||
|
message,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package io.gitlab.jfronny.inceptum.gtk.control.settings
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.I18n
|
||||||
|
import org.gnome.adw.HeaderBar
|
||||||
|
import org.gnome.adw.ViewStack
|
||||||
|
import org.gnome.adw.ViewSwitcherBar
|
||||||
|
import org.gnome.adw.ViewSwitcherTitle
|
||||||
|
import org.gnome.gobject.BindingFlags
|
||||||
|
import org.gnome.gtk.*
|
||||||
|
import org.jetbrains.annotations.PropertyKey
|
||||||
|
|
||||||
|
open class SettingsWindow(app: Application?) : Window() {
|
||||||
|
protected val stack: ViewStack
|
||||||
|
|
||||||
|
init {
|
||||||
|
application = app
|
||||||
|
|
||||||
|
stack = ViewStack()
|
||||||
|
|
||||||
|
val header = HeaderBar()
|
||||||
|
val viewSwitcher = ViewSwitcherTitle()
|
||||||
|
viewSwitcher.stack = stack
|
||||||
|
header.titleWidget = viewSwitcher
|
||||||
|
titlebar = header
|
||||||
|
|
||||||
|
val scroll = ScrolledWindow()
|
||||||
|
scroll.setPolicy(PolicyType.NEVER, PolicyType.AUTOMATIC)
|
||||||
|
scroll.child = stack
|
||||||
|
scroll.vexpand = true
|
||||||
|
|
||||||
|
val bottomBar = ViewSwitcherBar()
|
||||||
|
bottomBar.stack = stack
|
||||||
|
viewSwitcher.bindProperty("title-visible", bottomBar, "reveal", BindingFlags.DEFAULT)
|
||||||
|
val view = Box(Orientation.VERTICAL, 0)
|
||||||
|
view.append(scroll)
|
||||||
|
view.append(bottomBar)
|
||||||
|
|
||||||
|
child = view
|
||||||
|
|
||||||
|
setDefaultSize(720, 360)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addTab(tab: SettingsTab<*, *>, title: @PropertyKey(resourceBundle = I18n.BUNDLE) String, iconName: String) {
|
||||||
|
stack.addTitledWithIcon(tab.content, title, I18n[title], iconName)
|
||||||
|
}
|
||||||
|
|
||||||
|
var activePage: String
|
||||||
|
get() = stack.visibleChildName!!
|
||||||
|
set(@PropertyKey(resourceBundle = I18n.BUNDLE) title) { stack.visibleChildName = title }
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package io.gitlab.jfronny.inceptum.gtk.menu
|
||||||
|
|
||||||
|
import org.gnome.gio.MenuItem
|
||||||
|
import org.gnome.gio.SimpleAction
|
||||||
|
|
||||||
|
class BuiltButtonItem(action: SimpleAction, menuItem: MenuItem?) : BuiltMenuItem(action, menuItem)
|
|
@ -0,0 +1,23 @@
|
||||||
|
package io.gitlab.jfronny.inceptum.gtk.menu
|
||||||
|
|
||||||
|
import org.gnome.gio.MenuItem
|
||||||
|
import org.gnome.gio.SimpleAction
|
||||||
|
import org.gnome.gio.ThemedIcon
|
||||||
|
|
||||||
|
abstract class BuiltMenuItem protected constructor(action: SimpleAction, protected val menuItem: MenuItem?) {
|
||||||
|
protected val action: SimpleAction
|
||||||
|
|
||||||
|
init {
|
||||||
|
this.action = action
|
||||||
|
}
|
||||||
|
|
||||||
|
var enabled: Boolean
|
||||||
|
get() = action.enabled
|
||||||
|
set(enabled) {
|
||||||
|
action.enabled = enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
var iconName: String?
|
||||||
|
set(iconName) { menuItem!!.setIcon(ThemedIcon(iconName)) }
|
||||||
|
get() = throw NotImplementedError()
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package io.gitlab.jfronny.inceptum.gtk.menu
|
||||||
|
|
||||||
|
import org.gnome.gio.SimpleAction
|
||||||
|
import org.gnome.glib.Variant
|
||||||
|
|
||||||
|
class BuiltRadioItem<T>(action: SimpleAction, private val options: List<T>) : BuiltMenuItem(action, null) {
|
||||||
|
var selected: T
|
||||||
|
get() = options[action.state!!.int32]
|
||||||
|
set(selected) {
|
||||||
|
action.state = Variant.int32(options.indexOf(selected))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package io.gitlab.jfronny.inceptum.gtk.menu
|
||||||
|
|
||||||
|
import org.gnome.gio.MenuItem
|
||||||
|
import org.gnome.gio.SimpleAction
|
||||||
|
import org.gnome.glib.Variant
|
||||||
|
|
||||||
|
class BuiltToggleItem(action: SimpleAction, menuItem: MenuItem?) : BuiltMenuItem(action, menuItem) {
|
||||||
|
var state: Boolean
|
||||||
|
get() = action.state!!.boolean
|
||||||
|
set(state) {
|
||||||
|
action.state = Variant.boolean_(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggle(): Boolean {
|
||||||
|
val toggled = !state
|
||||||
|
state = toggled
|
||||||
|
return toggled
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,169 @@
|
||||||
|
package io.gitlab.jfronny.inceptum.gtk.menu
|
||||||
|
|
||||||
|
import io.github.jwharm.javagi.glib.types.VariantTypes
|
||||||
|
import io.gitlab.jfronny.commons.throwable.ThrowingRunnable
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.I18n
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.Log
|
||||||
|
import org.gnome.gio.*
|
||||||
|
import org.gnome.glib.Variant
|
||||||
|
import org.gnome.gtk.Application
|
||||||
|
import org.gnome.gtk.MenuButton
|
||||||
|
import org.gnome.gtk.PopoverMenu
|
||||||
|
import java.util.function.BiFunction
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
|
class MenuBuilder private constructor(map: ActionMap, menu: Menu, prefix: String, groupName: String) {
|
||||||
|
private val map: ActionMap
|
||||||
|
private val refs: MutableMap<String, Action> = LinkedHashMap()
|
||||||
|
val menu: Menu
|
||||||
|
private val prefix: String
|
||||||
|
private val groupName: String
|
||||||
|
|
||||||
|
constructor(menu: PopoverMenu, groupName: String) : this(
|
||||||
|
insertMap(menu, groupName),
|
||||||
|
menu.menuModel as Menu,
|
||||||
|
"",
|
||||||
|
groupName
|
||||||
|
)
|
||||||
|
|
||||||
|
constructor(app: Application, menu: Menu = getRootMenu(app), prefix: String = "") : this(app, menu, prefix, "app.")
|
||||||
|
|
||||||
|
init {
|
||||||
|
fun String.suffix() = if (isNotEmpty() && !endsWith(".")) "$this." else this
|
||||||
|
|
||||||
|
this.map = map
|
||||||
|
this.menu = menu
|
||||||
|
this.prefix = prefix.suffix()
|
||||||
|
this.groupName = groupName.suffix()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun button(name: String, onClick: ThrowingRunnable<*>): BuiltButtonItem {
|
||||||
|
return literalButton(name, I18n["menu.$prefix$name"], onClick)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun literalButton(internalName: String, label: String?, onClick: ThrowingRunnable<*>): BuiltButtonItem {
|
||||||
|
var internalName = internalName
|
||||||
|
internalName = prefix + internalName
|
||||||
|
val action = SimpleAction(internalName, null)
|
||||||
|
addAction(internalName, action)
|
||||||
|
action.onActivate { _ ->
|
||||||
|
try {
|
||||||
|
onClick.run()
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Log.error("Could not execute action", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val menuItem = MenuItem(label, groupName + internalName)
|
||||||
|
menu.appendItem(menuItem)
|
||||||
|
action.enabled = true
|
||||||
|
return BuiltButtonItem(action, menuItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggle(name: String, initial: Boolean, onToggle: Consumer<Boolean?>): BuiltToggleItem {
|
||||||
|
var name = name
|
||||||
|
name = prefix + name
|
||||||
|
val action = SimpleAction.stateful(name, null, Variant.boolean_(initial))
|
||||||
|
addAction(name, action)
|
||||||
|
action.onActivate { _ ->
|
||||||
|
val state = !action.getState()!!.boolean
|
||||||
|
action.state = Variant.boolean_(state)
|
||||||
|
onToggle.accept(state)
|
||||||
|
}
|
||||||
|
val menuItem = MenuItem(I18n["menu.$name"], groupName + name)
|
||||||
|
menu.appendItem(menuItem)
|
||||||
|
return BuiltToggleItem(action, menuItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> radio(name: String, initial: T, options: List<T>, onCheck: Consumer<T>): BuiltRadioItem<T> {
|
||||||
|
return literalRadio(name, initial, options, { i, _ -> I18n["menu.$prefix$name", i] }, onCheck)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> literalRadio(
|
||||||
|
name: String,
|
||||||
|
initial: T,
|
||||||
|
options: List<T>,
|
||||||
|
stringifier: BiFunction<Int, T, String?>,
|
||||||
|
onCheck: Consumer<T>
|
||||||
|
): BuiltRadioItem<T> {
|
||||||
|
var name = name
|
||||||
|
name = prefix + name
|
||||||
|
val action = SimpleAction.stateful(name, VariantTypes.INT32, Variant.int32(options.indexOf(initial)))
|
||||||
|
addAction(name, action)
|
||||||
|
action.onActivate { variant: Variant? ->
|
||||||
|
action.state = variant
|
||||||
|
onCheck.accept(options[variant!!.int32])
|
||||||
|
}
|
||||||
|
for ((i, option) in options.withIndex()) {
|
||||||
|
menu.appendItem(MenuItem(stringifier.apply(i, option), "$groupName$name($i)"))
|
||||||
|
}
|
||||||
|
return BuiltRadioItem(action, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun submenu(name: String): MenuBuilder {
|
||||||
|
return literalSubmenu(name, I18n["menu.$prefix$name"])
|
||||||
|
}
|
||||||
|
|
||||||
|
fun literalSubmenu(name: String, label: String?): MenuBuilder {
|
||||||
|
var name = name
|
||||||
|
name = prefix + name
|
||||||
|
val submenu = Menu()
|
||||||
|
menu.appendSubmenu(label, submenu)
|
||||||
|
return MenuBuilder(map, submenu, name, groupName)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun section(name: String): MenuBuilder {
|
||||||
|
return literalSection(name, I18n["section.$prefix$name"])
|
||||||
|
}
|
||||||
|
|
||||||
|
fun literalSection(name: String, label: String?): MenuBuilder {
|
||||||
|
var name = name
|
||||||
|
name = prefix + name
|
||||||
|
val section = Menu()
|
||||||
|
menu.appendSection(label, section)
|
||||||
|
return MenuBuilder(map, section, name, groupName)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
menu.removeAll()
|
||||||
|
refs.forEach { (name, _) -> map.removeAction(name) }
|
||||||
|
refs.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addAction(name: String, action: SimpleAction) {
|
||||||
|
map.addAction(action)
|
||||||
|
refs[name] = action
|
||||||
|
}
|
||||||
|
|
||||||
|
fun asPopover(): PopoverMenu {
|
||||||
|
return PopoverMenu.fromModel(menu)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val LOCK = Any()
|
||||||
|
private fun getRootMenu(app: Application): Menu {
|
||||||
|
synchronized(LOCK) {
|
||||||
|
val currentMenu = app.menubar
|
||||||
|
return if (currentMenu == null) {
|
||||||
|
val menu = Menu()
|
||||||
|
app.menubar = menu
|
||||||
|
menu
|
||||||
|
} else {
|
||||||
|
currentMenu as Menu
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun create(target: MenuButton, groupName: String): MenuBuilder {
|
||||||
|
val menu = Menu()
|
||||||
|
val pm = PopoverMenu.fromModel(menu)
|
||||||
|
target.setPopover(pm)
|
||||||
|
return MenuBuilder(pm, groupName)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun insertMap(menu: PopoverMenu, groupName: String): ActionMap {
|
||||||
|
val ag = SimpleActionGroup()
|
||||||
|
menu.insertActionGroup(groupName, ag)
|
||||||
|
return ag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package io.gitlab.jfronny.inceptum.gtk.util
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.PropertyKey
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
object I18n {
|
||||||
|
const val BUNDLE = "inceptum"
|
||||||
|
private val bundle = ResourceBundle.getBundle(BUNDLE)
|
||||||
|
|
||||||
|
operator fun get(key: @PropertyKey(resourceBundle = BUNDLE) String): String =
|
||||||
|
bundle.getString(key)
|
||||||
|
|
||||||
|
operator fun get(key: @PropertyKey(resourceBundle = BUNDLE) String, vararg args: Any?): String =
|
||||||
|
String.format(bundle.getString(key), *args)
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package io.gitlab.jfronny.inceptum.gtk.util
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance
|
||||||
|
|
||||||
|
operator fun List<Instance>.get(id: String) = firstOrNull { it.id == id }
|
|
@ -0,0 +1,6 @@
|
||||||
|
package io.gitlab.jfronny.inceptum.gtk.util
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.commons.logger.SystemLoggerPlus
|
||||||
|
import io.gitlab.jfronny.inceptum.common.Utils
|
||||||
|
|
||||||
|
object Log : SystemLoggerPlus by Utils.LOGGER
|
|
@ -0,0 +1,92 @@
|
||||||
|
package io.gitlab.jfronny.inceptum.gtk.util
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.commons.OSUtils
|
||||||
|
import java.io.IOException
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
|
object Memory {
|
||||||
|
const val KB: Long = 1024
|
||||||
|
const val MB = KB * 1024
|
||||||
|
const val GB = MB * 1024
|
||||||
|
private val impl = when (OSUtils.TYPE) {
|
||||||
|
OSUtils.Type.LINUX -> LinuxMI
|
||||||
|
OSUtils.Type.WINDOWS -> WindowsMI
|
||||||
|
OSUtils.Type.MAC_OS -> MacOsMI
|
||||||
|
}
|
||||||
|
private val totalMemory by lazy { impl.getTotalMemory() }
|
||||||
|
val maxMBForInstance: Long get() = (totalMemory / MB - 1024).coerceAtLeast(1024)
|
||||||
|
|
||||||
|
private interface MI {
|
||||||
|
fun getTotalMemory(): Long
|
||||||
|
}
|
||||||
|
|
||||||
|
private object LinuxMI : MI {
|
||||||
|
override fun getTotalMemory(): Long {
|
||||||
|
try {
|
||||||
|
Files.lines(Path.of("/proc/meminfo")).use { stream ->
|
||||||
|
val memTotal = stream
|
||||||
|
.filter { s: String -> s.startsWith("MemTotal:") }
|
||||||
|
.map { s: String -> s.substring("MemTotal:".length) }
|
||||||
|
.map { obj: String -> obj.trim { it <= ' ' } }
|
||||||
|
.findFirst()
|
||||||
|
return if (memTotal.isPresent()) {
|
||||||
|
parseDecimalMemorySizeToBinary(memTotal.get())
|
||||||
|
} else {
|
||||||
|
Log.error("Could not find total memory")
|
||||||
|
32 * GB
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.error("Could not get total memory", e)
|
||||||
|
return 32 * GB
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Taken from oshi
|
||||||
|
private val BYTES_PATTERN = Pattern.compile("(\\d+) ?([kKMGT]?B?).*")
|
||||||
|
private val WHITESPACES = Pattern.compile("\\s+")
|
||||||
|
private fun parseDecimalMemorySizeToBinary(size: String): Long {
|
||||||
|
var mem = WHITESPACES.split(size)
|
||||||
|
if (mem.size < 2) {
|
||||||
|
// If no spaces, use regexp
|
||||||
|
val matcher = BYTES_PATTERN.matcher(size.trim { it <= ' ' })
|
||||||
|
if (matcher.find() && matcher.groupCount() == 2) {
|
||||||
|
mem = arrayOfNulls(2)
|
||||||
|
mem[0] = matcher.group(1)
|
||||||
|
mem[1] = matcher.group(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var capacity = parseLongOrDefault(mem[0], 0L)
|
||||||
|
if (mem.size == 2 && mem[1]!!.length > 1) {
|
||||||
|
when (mem[1]!![0]) {
|
||||||
|
'T' -> capacity = capacity shl 40
|
||||||
|
'G' -> capacity = capacity shl 30
|
||||||
|
'M' -> capacity = capacity shl 20
|
||||||
|
'K', 'k' -> capacity = capacity shl 10
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return capacity
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseLongOrDefault(s: String, defaultLong: Long): Long = try {
|
||||||
|
s.toLong()
|
||||||
|
} catch (e: NumberFormatException) {
|
||||||
|
defaultLong
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private object WindowsMI : MI {
|
||||||
|
override fun getTotalMemory(): Long {
|
||||||
|
return 32 * GB // This is currently unsupported, but any implementations by Windows user using panama are welcome
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private object MacOsMI : MI {
|
||||||
|
override fun getTotalMemory(): Long {
|
||||||
|
return 32 * GB // This is currently unsupported, but any implementations by MacOS user using panama are welcome
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package io.gitlab.jfronny.inceptum.gtk.util
|
||||||
|
|
||||||
|
import java.util.stream.Stream
|
||||||
|
|
||||||
|
inline fun <reified T> Stream<T>.toTypedArray(): Array<T> = toArray(::arrayOfNulls)
|
|
@ -0,0 +1,10 @@
|
||||||
|
package io.gitlab.jfronny.inceptum.gtk.util
|
||||||
|
|
||||||
|
import org.gnome.gio.ListModel
|
||||||
|
import org.gnome.gtk.StringList
|
||||||
|
|
||||||
|
fun StringList.clear() = splice(0, size, null)
|
||||||
|
fun StringList.addAll(values: Array<String>) = splice(size, 0, values)
|
||||||
|
fun StringList.replaceAll(values: Array<String>) = splice(0, size, values)
|
||||||
|
|
||||||
|
val ListModel.size get() = nItems
|
|
@ -0,0 +1,46 @@
|
||||||
|
package io.gitlab.jfronny.inceptum.gtk.util
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.control.ILabel
|
||||||
|
import org.gnome.adw.ActionRow
|
||||||
|
import org.gnome.gtk.EntryBuffer
|
||||||
|
import org.gnome.gtk.Label
|
||||||
|
import org.gnome.gtk.MessageDialog
|
||||||
|
import org.gnome.gtk.Widget
|
||||||
|
|
||||||
|
var Widget.margin: Int
|
||||||
|
set(value) {
|
||||||
|
marginVertical = value
|
||||||
|
marginHorizontal = value
|
||||||
|
}
|
||||||
|
get() = throw NotImplementedError()
|
||||||
|
|
||||||
|
var Widget.marginVertical: Int
|
||||||
|
set(value) {
|
||||||
|
marginTop = value
|
||||||
|
marginBottom = value
|
||||||
|
}
|
||||||
|
get() = throw NotImplementedError()
|
||||||
|
|
||||||
|
var Widget.marginHorizontal: Int
|
||||||
|
set(value) {
|
||||||
|
marginStart = value
|
||||||
|
marginEnd = value
|
||||||
|
}
|
||||||
|
get() = throw NotImplementedError()
|
||||||
|
|
||||||
|
var MessageDialog.markup: String
|
||||||
|
set(value) { setMarkup(value.escapedMarkup) }
|
||||||
|
get() = throw NotImplementedError()
|
||||||
|
|
||||||
|
var Label.markup: String
|
||||||
|
set(value) { setMarkup(value.escapedMarkup) }
|
||||||
|
get() = throw NotImplementedError()
|
||||||
|
|
||||||
|
val String.escapedMarkup: String
|
||||||
|
get() = replace(unmatchedAnd, "&")
|
||||||
|
|
||||||
|
private val unmatchedAnd = Regex("&(?![a-z]{1,6};|#[0-9]+;|#x[0-9A-F]+;)")
|
||||||
|
|
||||||
|
fun ActionRow.fixSubtitle() = ILabel.theme(firstChild!!.lastChild!!.prevSibling!!.lastChild as Label, ILabel.Mode.SUBTITLE)
|
||||||
|
|
||||||
|
fun EntryBuffer.clear() = deleteText(0, length)
|
|
@ -0,0 +1,30 @@
|
||||||
|
package io.gitlab.jfronny.inceptum.gtk.window
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.inceptum.common.BuildMetadata
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.I18n
|
||||||
|
import org.gnome.gtk.AboutDialog
|
||||||
|
import org.gnome.gtk.License
|
||||||
|
|
||||||
|
class AboutWindow : AboutDialog() {
|
||||||
|
init {
|
||||||
|
programName = "Inceptum"
|
||||||
|
copyright = "Copyright (C) 2021-2023 JFronny"
|
||||||
|
version = BuildMetadata.VERSION
|
||||||
|
licenseType = License.MIT_X11
|
||||||
|
license = I18n["about.license"]
|
||||||
|
websiteLabel = I18n["about.contact"]
|
||||||
|
website = "https://jfronny.gitlab.io/contact.html"
|
||||||
|
if (!BuildMetadata.IS_PUBLIC) {
|
||||||
|
comments = I18n["about.unsupported-build"]
|
||||||
|
}
|
||||||
|
val vm = Runtime.version().feature()
|
||||||
|
systemInformation = I18n[if (BuildMetadata.VM_VERSION == vm) "about.jvm" else "about.jvm.unsupported", vm]
|
||||||
|
//TODO setLogo
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun createAndShow() {
|
||||||
|
AboutWindow().visible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,166 @@
|
||||||
|
package io.gitlab.jfronny.inceptum.gtk.window
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.inceptum.common.Utils
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.GtkMenubar
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.control.InstanceListEntryFactory
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.menu.MenuBuilder
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.*
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.window.settings.launcher.LauncherSettingsWindow
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.InstanceList
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.InstanceListWatcher
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.launch.LaunchType
|
||||||
|
import org.gnome.adw.Clamp
|
||||||
|
import org.gnome.adw.StatusPage
|
||||||
|
import org.gnome.gio.*
|
||||||
|
import org.gnome.glib.GLib
|
||||||
|
import org.gnome.gtk.*
|
||||||
|
import org.gnome.gtk.Application
|
||||||
|
import java.io.IOException
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
|
class MainWindow(app: Application) : ApplicationWindow(app) {
|
||||||
|
// private val listButton: Button
|
||||||
|
// private val gridButton: Button
|
||||||
|
private val stack: Stack
|
||||||
|
private val empty: StatusPage
|
||||||
|
private val listContainer: Clamp
|
||||||
|
// private val gridView: GridView
|
||||||
|
private val instanceList: MutableList<Instance>
|
||||||
|
private val instanceListModel: StringList
|
||||||
|
|
||||||
|
init {
|
||||||
|
val header = HeaderBar()
|
||||||
|
val newButton = MenuButton()
|
||||||
|
newButton.iconName = "list-add-symbolic"
|
||||||
|
newButton.menuModel = GtkMenubar.newMenu!!.menu
|
||||||
|
val accountsButton = MenuButton()
|
||||||
|
accountsButton.iconName = "avatar-default-symbolic"
|
||||||
|
accountsButton.menuModel = GtkMenubar.accountsMenu!!.menu
|
||||||
|
// listButton = Button.newFromIconName("view-list-symbolic")
|
||||||
|
// listButton.onClicked {
|
||||||
|
// InceptumConfig.listView = true
|
||||||
|
// InceptumConfig.saveConfig()
|
||||||
|
// generateWindowBody()
|
||||||
|
// }
|
||||||
|
// gridButton = Button.newFromIconName("view-grid-symbolic")
|
||||||
|
// gridButton.onClicked {
|
||||||
|
// InceptumConfig.listView = false
|
||||||
|
// InceptumConfig.saveConfig()
|
||||||
|
// generateWindowBody()
|
||||||
|
// }
|
||||||
|
|
||||||
|
//TODO search button like boxes
|
||||||
|
|
||||||
|
val uiMenu = MenuBuilder(app, Menu(), "hamburger")
|
||||||
|
uiMenu.button("support") { Utils.openWebBrowser(URI("https://git.frohnmeyer-wds.de/JfMods/Inceptum/issues")) }
|
||||||
|
uiMenu.button("preferences") { LauncherSettingsWindow(app).visible = true }
|
||||||
|
uiMenu.button("about") { AboutWindow.createAndShow() }
|
||||||
|
val menuButton = MenuButton()
|
||||||
|
menuButton.iconName = "open-menu-symbolic"
|
||||||
|
menuButton.menuModel = uiMenu.menu
|
||||||
|
|
||||||
|
header.packStart(newButton)
|
||||||
|
|
||||||
|
header.packEnd(menuButton)
|
||||||
|
// header.packEnd(gridButton)
|
||||||
|
// header.packEnd(listButton)
|
||||||
|
header.packEnd(accountsButton)
|
||||||
|
|
||||||
|
instanceList = ArrayList()
|
||||||
|
instanceListModel = StringList(arrayOf())
|
||||||
|
val selection = NoSelection(instanceListModel)
|
||||||
|
|
||||||
|
val listView = ListView(selection, InstanceListEntryFactory(app, instanceList))
|
||||||
|
listView.addCssClass("rich-list")
|
||||||
|
listView.showSeparators = true
|
||||||
|
listView.onActivate { position: Int ->
|
||||||
|
// Double click
|
||||||
|
GtkMenubar.launch(instanceList[position], LaunchType.Client)
|
||||||
|
}
|
||||||
|
val frame = Frame(null as String?)
|
||||||
|
frame.child = listView
|
||||||
|
frame.marginHorizontal = 24
|
||||||
|
frame.marginVertical = 12
|
||||||
|
frame.valign = Align.START
|
||||||
|
listContainer = Clamp()
|
||||||
|
listContainer.maximumSize = 900
|
||||||
|
listContainer.child = frame
|
||||||
|
// gridView = GridView(selection, InstanceGridEntryFactory(instanceList))
|
||||||
|
empty = StatusPage()
|
||||||
|
empty.title = I18n["main.empty.title"]
|
||||||
|
empty.description = I18n["main.empty.description"]
|
||||||
|
//TODO empty.setIconName(new Str());
|
||||||
|
stack = Stack()
|
||||||
|
stack.addChild(listContainer)
|
||||||
|
// stack.addChild(gridView)
|
||||||
|
stack.addChild(empty)
|
||||||
|
|
||||||
|
val scroll = ScrolledWindow()
|
||||||
|
scroll.setPolicy(PolicyType.NEVER, PolicyType.AUTOMATIC)
|
||||||
|
scroll.child = stack
|
||||||
|
|
||||||
|
setDefaultSize(720, 360)
|
||||||
|
title = "Inceptum"
|
||||||
|
titlebar = header
|
||||||
|
showMenubar = false
|
||||||
|
child = scroll
|
||||||
|
|
||||||
|
generateWindowBody()
|
||||||
|
//TODO DropTarget to add mods/instances
|
||||||
|
|
||||||
|
try {
|
||||||
|
setupDirWatcher()
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.error(
|
||||||
|
"Could not set up watch service, live updates of the instance dir will be unavailable",
|
||||||
|
e
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
private fun setupDirWatcher() {
|
||||||
|
val isw = InstanceListWatcher()
|
||||||
|
addTickCallback { _, _ ->
|
||||||
|
try {
|
||||||
|
if (isw.poll()) generateWindowBody()
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.error("Could not run update task", e)
|
||||||
|
}
|
||||||
|
GLib.SOURCE_CONTINUE
|
||||||
|
}
|
||||||
|
onCloseRequest {
|
||||||
|
try {
|
||||||
|
isw.close()
|
||||||
|
} catch (ignored: IOException) {}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateWindowBody() {
|
||||||
|
// listButton.visible = !InceptumConfig.listView
|
||||||
|
// gridButton.visible = InceptumConfig.listView
|
||||||
|
try {
|
||||||
|
// Unbind then clear
|
||||||
|
instanceListModel.clear()
|
||||||
|
instanceList.clear()
|
||||||
|
|
||||||
|
// Add new entries
|
||||||
|
instanceList.addAll(InstanceList.ordered())
|
||||||
|
instanceListModel.addAll(instanceList.map { it.id }.toTypedArray())
|
||||||
|
|
||||||
|
// Choose view for this amount of entries
|
||||||
|
// stack.visibleChild = if (InstanceList.isEmpty()) empty
|
||||||
|
// else if (InceptumConfig.listView) listContainer
|
||||||
|
// else gridView
|
||||||
|
stack.visibleChild = if (InstanceList.isEmpty()) empty else listContainer
|
||||||
|
|
||||||
|
// This is called from a tick callback, so re-render
|
||||||
|
stack.queueResize()
|
||||||
|
stack.queueDraw()
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.error("Could not generate window body", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,178 @@
|
||||||
|
package io.gitlab.jfronny.inceptum.gtk.window.create
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.commons.StringFormatter
|
||||||
|
import io.gitlab.jfronny.inceptum.common.InceptumConfig
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.control.KDropDown
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.control.KEntry
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.control.assistant.KAssistant
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.schedule
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.I18n
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.Log
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.markup
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.toTypedArray
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.window.dialog.ProcessStateWatcherDialog
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.api.FabricMetaApi
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.api.McApi
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.model.fabric.FabricVersionLoaderInfo
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.model.mojang.VersionsListInfo
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.InstanceNameTool
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.LoaderInfo
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.setup.SetupStepInfo
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.setup.Steps
|
||||||
|
import org.gnome.glib.GLib
|
||||||
|
import org.gnome.gtk.*
|
||||||
|
|
||||||
|
|
||||||
|
class NewInstanceWindow(app: Application) : KAssistant(app) {
|
||||||
|
companion object {
|
||||||
|
private val VERSIONS = McApi.getVersions()
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
var gameVersion: VersionsListInfo? = null
|
||||||
|
var useFabric = false
|
||||||
|
var fabricVersion: FabricVersionLoaderInfo? = null
|
||||||
|
var name = "New Instance"
|
||||||
|
|
||||||
|
var failureMessage = "Unknown error, please look at the log!"
|
||||||
|
var isFailure = false
|
||||||
|
|
||||||
|
page("Welcome", AssistantPageType.INTRO) {
|
||||||
|
append(Label("This assistant will guide you through the process of setting up a Minecraft instance.\nTo begin, please choose the game version you want to use"))
|
||||||
|
|
||||||
|
val versions = VERSIONS.versions.stream()
|
||||||
|
.filter { InceptumConfig.snapshots || it.type == "release" }
|
||||||
|
.toTypedArray()
|
||||||
|
val def = versions.withIndex().firstOrNull { it.value.id == VERSIONS.latest.release }?.index ?: 0
|
||||||
|
gameVersion = versions[def]
|
||||||
|
|
||||||
|
append(KDropDown(versions, { it.id }, def).apply {
|
||||||
|
onChange { gameVersion = versions[it] }
|
||||||
|
})
|
||||||
|
|
||||||
|
setComplete(true)
|
||||||
|
}
|
||||||
|
page("Loader", AssistantPageType.CONTENT) {
|
||||||
|
append(Label("Select a mod loader if you want to use mods in this instance. This can be changed later."))
|
||||||
|
var lastGameVersion: VersionsListInfo? = null
|
||||||
|
var versions = arrayOf<FabricVersionLoaderInfo>()
|
||||||
|
var def = 0
|
||||||
|
|
||||||
|
val none = CheckButton.withLabel("None")
|
||||||
|
val fabric = CheckButton.withLabel("Fabric")
|
||||||
|
none.onToggled { useFabric = false }
|
||||||
|
none.onToggled { useFabric = true }
|
||||||
|
fabric.setGroup(none)
|
||||||
|
append(none)
|
||||||
|
val fabricVersionDropdown = KDropDown(versions, { it.loader.version }, def)
|
||||||
|
fabricVersionDropdown.onChange { fabricVersion = versions[it] }
|
||||||
|
append(Box(Orientation.HORIZONTAL, 8).apply {
|
||||||
|
append(fabric)
|
||||||
|
append(fabricVersionDropdown)
|
||||||
|
})
|
||||||
|
|
||||||
|
onOpen {
|
||||||
|
if (lastGameVersion == null || lastGameVersion != gameVersion) {
|
||||||
|
versions = FabricMetaApi.getLoaderVersions(gameVersion!!).toTypedArray()
|
||||||
|
def = versions.withIndex().firstOrNull { it.value.loader.stable }?.index ?: 0
|
||||||
|
fabricVersionDropdown.updateOptions(versions, def)
|
||||||
|
lastGameVersion = gameVersion
|
||||||
|
none.active = true
|
||||||
|
fabric.active = false
|
||||||
|
useFabric = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (versions.isEmpty()) {
|
||||||
|
none.active = true
|
||||||
|
fabric.active = false
|
||||||
|
useFabric = false
|
||||||
|
fabric.sensitive = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setComplete(true)
|
||||||
|
}
|
||||||
|
page("Name", AssistantPageType.CONTENT) {
|
||||||
|
append(Label(I18n["instance.settings.general.name.placeholder"]))
|
||||||
|
val entry = KEntry(name)
|
||||||
|
entry.placeholderText = I18n["instance.settings.general.name.placeholder"]
|
||||||
|
entry.valign
|
||||||
|
entry.onChange { name = InstanceNameTool.getNextValid(it) }
|
||||||
|
append(entry)
|
||||||
|
onOpen {
|
||||||
|
name = InstanceNameTool.getDefaultName(gameVersion!!.id, useFabric)
|
||||||
|
entry.text = name
|
||||||
|
}
|
||||||
|
|
||||||
|
setComplete(true)
|
||||||
|
}
|
||||||
|
page("Creating", AssistantPageType.PROGRESS) {
|
||||||
|
append(Label("Creating Instance"))
|
||||||
|
val progress = ProgressBar()
|
||||||
|
append(progress)
|
||||||
|
val stage = Label("")
|
||||||
|
append(stage)
|
||||||
|
onOpen {
|
||||||
|
commit()
|
||||||
|
val pState = Steps.createProcessState()
|
||||||
|
val state = SetupStepInfo(
|
||||||
|
McApi.getVersionInfo(gameVersion),
|
||||||
|
if (useFabric) LoaderInfo(fabricVersion!!.loader) else LoaderInfo.NONE,
|
||||||
|
name,
|
||||||
|
pState
|
||||||
|
)
|
||||||
|
var finished = false
|
||||||
|
var cachedState: ProcessStateWatcherDialog.State? = null
|
||||||
|
addTickCallback { widget, _ ->
|
||||||
|
if (finished) return@addTickCallback GLib.SOURCE_REMOVE
|
||||||
|
val nc = ProcessStateWatcherDialog.State(pState)
|
||||||
|
if (nc != cachedState) {
|
||||||
|
cachedState = nc
|
||||||
|
stage.markup = cachedState!!.msg
|
||||||
|
progress.fraction = cachedState!!.progress.coerceAtMost(1f).toDouble()
|
||||||
|
widget.queueDraw()
|
||||||
|
}
|
||||||
|
GLib.SOURCE_CONTINUE
|
||||||
|
}
|
||||||
|
onClose { pState.cancel() }
|
||||||
|
onCancel { pState.cancel() }
|
||||||
|
pState.updateStep("Starting install process")
|
||||||
|
Thread {
|
||||||
|
try {
|
||||||
|
for (step in Steps.STEPS) {
|
||||||
|
if (state.isCancelled) {
|
||||||
|
state.tryRemoveInstance()
|
||||||
|
return@Thread
|
||||||
|
}
|
||||||
|
pState.incrementStep(step.name)
|
||||||
|
step.execute(state)
|
||||||
|
}
|
||||||
|
state.clearSetupLock()
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
pState.cancel()
|
||||||
|
Log.error("Could not create instance")
|
||||||
|
failureMessage = StringFormatter.toString(e)
|
||||||
|
isFailure = true
|
||||||
|
state.tryRemoveInstance()
|
||||||
|
} finally {
|
||||||
|
finished = true
|
||||||
|
schedule { setComplete(true) }
|
||||||
|
schedule { nextPage() }
|
||||||
|
}
|
||||||
|
}.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
page("Done", AssistantPageType.SUMMARY) {
|
||||||
|
val status = Label("")
|
||||||
|
onOpen {
|
||||||
|
if (isFailure) {
|
||||||
|
status.markup = "Something went wrong while creating the instance.\n\n$failureMessage"
|
||||||
|
} else {
|
||||||
|
status.markup = "The instance was successfully created. You can now launch it using the main menu"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setComplete(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
package io.gitlab.jfronny.inceptum.gtk.window.dialog
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.inceptum.common.Utils
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.I18n
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.Log
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.api.account.MicrosoftAccount
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.api.account.MicrosoftAuthAPI
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.api.account.MicrosoftAuthServer
|
||||||
|
import org.gnome.gtk.*
|
||||||
|
import java.net.URI
|
||||||
|
import java.net.URISyntaxException
|
||||||
|
|
||||||
|
class MicrosoftLoginDialog(
|
||||||
|
parent: Window?,
|
||||||
|
account: MicrosoftAccount? = null,
|
||||||
|
onClose: Runnable? = null
|
||||||
|
) : MessageDialog(
|
||||||
|
parent,
|
||||||
|
flags(parent != null),
|
||||||
|
MessageType.QUESTION,
|
||||||
|
ButtonsType.CLOSE,
|
||||||
|
I18n["auth.description"]
|
||||||
|
) {
|
||||||
|
constructor(parent: Window?, onClose: Runnable?) : this(parent, null, onClose)
|
||||||
|
|
||||||
|
init {
|
||||||
|
title = I18n["auth.title"]
|
||||||
|
val server = MicrosoftAuthServer(account)
|
||||||
|
try {
|
||||||
|
server.start()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.error("Could not start mc login server", e)
|
||||||
|
}
|
||||||
|
val finalize = Runnable {
|
||||||
|
server.close()
|
||||||
|
onClose?.run()
|
||||||
|
}
|
||||||
|
onResponse { responseId: Int ->
|
||||||
|
when (ResponseType.of(responseId)) {
|
||||||
|
ResponseType.CLOSE, ResponseType.CANCEL -> {
|
||||||
|
finalize.run()
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseType.DELETE_EVENT -> {
|
||||||
|
finalize.run()
|
||||||
|
destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> Log.error("Unexpected response type: $responseId")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val btn = Button.withLabel(I18n["auth.open-browser"])
|
||||||
|
(messageArea as Box).append(btn)
|
||||||
|
btn.onClicked {
|
||||||
|
try {
|
||||||
|
Utils.openWebBrowser(URI(MicrosoftAuthAPI.MICROSOFT_LOGIN_URL))
|
||||||
|
} catch (e: URISyntaxException) {
|
||||||
|
Log.error("Could not open browser", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onCloseRequest {
|
||||||
|
finalize.run()
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private fun flags(modal: Boolean): Set<DialogFlags> {
|
||||||
|
return if (!modal) setOf(DialogFlags.DESTROY_WITH_PARENT) else setOf(DialogFlags.DESTROY_WITH_PARENT, DialogFlags.MODAL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
package io.gitlab.jfronny.inceptum.gtk.window.dialog
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.commons.StringFormatter
|
||||||
|
import io.gitlab.jfronny.commons.throwable.ThrowingRunnable
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.GtkEnvBackend
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.schedule
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.I18n
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.Log
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.markup
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.util.ProcessState
|
||||||
|
import org.gnome.glib.GLib
|
||||||
|
import org.gnome.gtk.*
|
||||||
|
|
||||||
|
class ProcessStateWatcherDialog(
|
||||||
|
parent: Window?,
|
||||||
|
title: String,
|
||||||
|
errorMessage: String,
|
||||||
|
private val state: ProcessState,
|
||||||
|
executor: ThrowingRunnable<*>
|
||||||
|
) : MessageDialog(
|
||||||
|
parent,
|
||||||
|
setOf(DialogFlags.MODAL, DialogFlags.DESTROY_WITH_PARENT),
|
||||||
|
MessageType.INFO,
|
||||||
|
ButtonsType.NONE,
|
||||||
|
null
|
||||||
|
) {
|
||||||
|
private var finished = false
|
||||||
|
private var cachedState: State? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
//TODO alternate UI: Only show progress bar by default, but have a dropdown to a "console" with the actual steps
|
||||||
|
// this should make visualizing parallelized steps easier
|
||||||
|
this.title = title
|
||||||
|
addButton(I18n["cancel"], ResponseType.CANCEL.value)
|
||||||
|
onResponse { responseId: Int ->
|
||||||
|
when (ResponseType.of(responseId)) {
|
||||||
|
ResponseType.CLOSE, ResponseType.CANCEL -> {
|
||||||
|
state.cancel()
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseType.DELETE_EVENT -> destroy()
|
||||||
|
else -> Log.error("Unexpected response type: $responseId")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onCloseRequest {
|
||||||
|
if (finished) return@onCloseRequest false
|
||||||
|
state.cancel()
|
||||||
|
false
|
||||||
|
}
|
||||||
|
val progress = ProgressBar()
|
||||||
|
(messageArea as Box).append(progress)
|
||||||
|
addTickCallback { widget, _ ->
|
||||||
|
if (finished) return@addTickCallback GLib.SOURCE_REMOVE
|
||||||
|
val nc = State(state)
|
||||||
|
if (nc != cachedState) {
|
||||||
|
cachedState = nc
|
||||||
|
markup = cachedState!!.msg
|
||||||
|
progress.fraction = cachedState!!.progress.coerceAtMost(1f).toDouble()
|
||||||
|
widget.queueDraw()
|
||||||
|
}
|
||||||
|
GLib.SOURCE_CONTINUE
|
||||||
|
}
|
||||||
|
Thread {
|
||||||
|
try {
|
||||||
|
executor.run()
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
state.cancel()
|
||||||
|
Log.error(errorMessage, e)
|
||||||
|
GtkEnvBackend.simpleDialog(
|
||||||
|
parent,
|
||||||
|
StringFormatter.toString(e),
|
||||||
|
errorMessage,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
finished = true
|
||||||
|
schedule { close() }
|
||||||
|
}
|
||||||
|
}.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
data class State(val msg: String, val progress: Float) {
|
||||||
|
constructor(source: ProcessState) : this(source.currentStep, source.progress)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun show(
|
||||||
|
parent: Window?,
|
||||||
|
title: String,
|
||||||
|
errorMessage: String,
|
||||||
|
state: ProcessState,
|
||||||
|
executor: ThrowingRunnable<*>
|
||||||
|
): ProcessStateWatcherDialog {
|
||||||
|
val dialog = ProcessStateWatcherDialog(parent, title, errorMessage, state, executor)
|
||||||
|
dialog.visible = true
|
||||||
|
return dialog
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package io.gitlab.jfronny.inceptum.gtk.window.dialog
|
||||||
|
|
||||||
|
import org.gnome.gtk.*
|
||||||
|
|
||||||
|
class StringInputDialog(parent: Window?, flags: Set<DialogFlags>, type: MessageType, buttons: ButtonsType, message: String, value: String) : MessageDialog(parent, flags, type, buttons, message) {
|
||||||
|
private val entry = Entry()
|
||||||
|
|
||||||
|
init {
|
||||||
|
(messageArea as Box).append(entry)
|
||||||
|
entry.text = value
|
||||||
|
}
|
||||||
|
|
||||||
|
val input: String get() = entry.text
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user