Start working on GTK port (far from finished)

This commit is contained in:
Johannes Frohnmeyer 2022-09-26 19:09:02 +02:00
parent 92109d353e
commit eb3fb83673
Signed by: Johannes
GPG Key ID: E76429612C2929F4
24 changed files with 673 additions and 58 deletions

View File

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

View File

@ -0,0 +1,19 @@
plugins {
id("inceptum.application-conventions")
id("com.github.johnrengelman.shadow")
}
application {
mainClass.set("io.gitlab.jfronny.inceptum.gtk.GtkMain")
}
repositories {
maven { url = uri("https://jitpack.io") }
}
dependencies {
implementation("com.github.bailuk:java-gtk:0.2")
implementation(project(":launcher"))
}
tasks.runShadow.get().workingDir = rootProject.projectDir

View File

@ -0,0 +1,95 @@
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 java.io.IOException;
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);
MessageDialog dialog = new MessageDialog(dialogParent, DialogFlags.MODAL | DialogFlags.DESTROY_WITH_PARENT, MessageType.ERROR, ButtonsType.CLOSE, null);
dialog.setTitle(new Str(title));
dialog.setMarkup(new Str(message));
dialog.show();
}
@Override
public void showError(String message, Throwable t) {
Utils.LOGGER.error(message, t);
MessageDialog dialog = new MessageDialog(dialogParent, DialogFlags.MODAL | DialogFlags.DESTROY_WITH_PARENT, MessageType.ERROR, ButtonsType.CLOSE, null);
dialog.setTitle(new Str(message));
dialog.setMarkup(new Str(StringFormatter.toString(t)));
dialog.show();
}
@Override
public void showInfo(String message, String title) {
Utils.LOGGER.info(message);
MessageDialog dialog = new MessageDialog(dialogParent, DialogFlags.MODAL | DialogFlags.DESTROY_WITH_PARENT, MessageType.INFO, ButtonsType.CLOSE, null);
dialog.setTitle(new Str(title));
dialog.setMarkup(new Str(message));
dialog.show();
}
@Override
public void showOkCancel(String message, String title, Runnable ok, Runnable cancel, boolean defaultCancel) {
Utils.LOGGER.info(message);
MessageDialog dialog = new MessageDialog(dialogParent, DialogFlags.MODAL | DialogFlags.DESTROY_WITH_PARENT, MessageType.QUESTION, ButtonsType.OK_CANCEL, null);
dialog.setTitle(new Str(title));
dialog.setMarkup(new Str(message));
dialog.onResponse(response_id -> {
if (response_id == ResponseType.OK) ok.run();
else if (response_id == ResponseType.CANCEL) cancel.run();
else {
Utils.LOGGER.error("Unexpected response type: " + response_id);
cancel.run();
}
});
dialog.show();
}
@Override
public void getInput(String prompt, String details, String defaultValue, Consumer<String> ok, Runnable cancel) throws IOException {
Dialog dialog = new Dialog();
if (dialogParent != null) dialog.setParent(dialogParent);
dialog.setModal(GTK.TRUE);
dialog.setDestroyWithParent(GTK.TRUE);
dialog.setTitle(new Str(prompt));
Box box = new Box(Orientation.VERTICAL, 0);
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.setChild(box);
dialog.addButton(I18n.str("ok"), ResponseType.OK);
dialog.addButton(I18n.str("cancel"), ResponseType.CANCEL);
dialog.onResponse(response_id -> {
if (response_id == ResponseType.OK) ok.accept(entryEditable.getText().toString());
else if (response_id == ResponseType.CANCEL) cancel.run();
else {
Utils.LOGGER.error("Unexpected response type: " + response_id);
cancel.run();
}
});
dialog.show();
}
@Override
public void showLoginRefreshPrompt(MicrosoftAccount account) {
//TODO
}
}

View File

@ -0,0 +1,144 @@
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.*;
import io.gitlab.jfronny.inceptum.gtk.menu.MenuBuilder;
import io.gitlab.jfronny.inceptum.gtk.util.I18n;
import io.gitlab.jfronny.inceptum.launcher.LauncherEnv;
import io.gitlab.jfronny.inceptum.launcher.api.account.*;
import io.gitlab.jfronny.inceptum.launcher.util.InstanceList;
import io.gitlab.jfronny.inceptum.launcher.system.install.Steps;
import io.gitlab.jfronny.inceptum.launcher.system.launch.InstanceLauncher;
import java.io.IOException;
import java.util.*;
public class GtkMain {
public static final Str ID = new Str("io.gitlab.jfronny.inceptum");
public static final Str TITLE = new Str("Inceptum");
public static final int WIDTH = 720 / 2;
public static final int HEIGHT = 1440 / 2;
private static MenuBuilder launchMenu;
private static MenuBuilder accountsMenu;
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 int showGui(String[] args) throws IOException {
var app = new Application(ID, ApplicationFlags.FLAGS_NONE);
app.onActivate(() -> {
var menu = new MenuBuilder(app);
var file = menu.submenu("file");
file.button("new", () -> {
//TODO
});
file.button("redownload", () -> {
//TODO
});
file.button("exit", () -> {
//TODO
});
launchMenu = menu.submenu("launch");
generateLaunchMenu();
accountsMenu = menu.submenu("account");
generateAccountsMenu();
var help = menu.submenu("help");
help.button("about", GtkMain::showAboutDialog);
help.button("log", () -> {
//TODO
});
var header = new HeaderBar();
var button = new MenuButton();
button.setPopover(launchMenu.asPopover()); //TODO another menu makes more sense here
header.packEnd(button);
var window = new ApplicationWindow(app);
window.setDefaultSize(WIDTH, HEIGHT);
window.setTitle(TITLE);
window.setTitlebar(header);
window.setChild(new TextView());
//TODO create UI based on gnome boxes?
window.show();
GtkEnvBackend.INSTANCE.dialogParent = window;
window.onCloseRequest(() -> {
GtkEnvBackend.INSTANCE.dialogParent = null;
return GTK.FALSE;
});
});
return app.run(args.length, new Strs(args));
}
private static void showAboutDialog() {
AboutDialog dialog = new AboutDialog();
dialog.setProgramName(new Str("Inceptum"));
dialog.setCopyright(new Str("Copyright (C) 2021 JFronny"));
dialog.setVersion(new Str(BuildMetadata.VERSION.toString()));
dialog.setLicenseType(License.MIT_X11);
dialog.setLicense(I18n.str("about.license"));
dialog.setWebsiteLabel(I18n.str("about.contact"));
dialog.setWebsite(new Str("https://jfronny.gitlab.io/contact.html"));
if (!BuildMetadata.IS_PUBLIC) {
dialog.setComments(I18n.str("about.unsupported-build"));
}
int vm = Runtime.version().feature();
dialog.setSystemInformation(I18n.str(BuildMetadata.VM_VERSION == vm ? "about.jvm" : "about.jvm.unsupported", vm));
//TODO setLogo
dialog.show();
}
private static void generateLaunchMenu() {
Objects.requireNonNull(launchMenu);
launchMenu.clear();
try {
InstanceList.forEachLaunchable(entry -> {
Utils.LOGGER.info("Entry " + entry.toString());
launchMenu.literalButton(entry.id(), entry.toString(), () -> {
try {
Steps.reDownload(entry.path(), Steps.createProcessState());
} catch (IOException e) {
Utils.LOGGER.error("Could not redownload instance, trying to start anyways", e);
}
InstanceLauncher.launchClient(entry.path(), entry.meta());
//TODO figure out why this crashes
});
});
} catch (IOException e) {
Utils.LOGGER.error("Could not generate launch menu", e);
}
}
private 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);
}
}

View File

@ -0,0 +1,9 @@
package io.gitlab.jfronny.inceptum.gtk.menu;
import ch.bailu.gtk.gio.SimpleAction;
public class ButtonItem extends MenuItem {
public ButtonItem(SimpleAction action) {
super(action);
}
}

View File

@ -0,0 +1,128 @@
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.inceptum.gtk.util.I18n;
import java.util.*;
import java.util.function.*;
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) {
this.map = Objects.requireNonNull(map);
this.menu = Objects.requireNonNull(menu);
this.prefix = Objects.requireNonNull(prefix);
}
public ButtonItem button(String name, Runnable onClick) {
return literalButton(name, I18n.get("menu." + prefix + name), onClick);
}
public ButtonItem literalButton(String internalName, String label, Runnable onClick) {
internalName = prefix + internalName;
SimpleAction sAct = new SimpleAction(new Str(internalName), null);
addAction(internalName, sAct);
sAct.onActivate(v -> onClick.run());
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);
}
}

View File

@ -0,0 +1,23 @@
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));
}
}

View File

@ -0,0 +1,23 @@
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());
}
}

View File

@ -0,0 +1,25 @@
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;
}
}

View File

@ -0,0 +1,25 @@
package io.gitlab.jfronny.inceptum.gtk.util;
import ch.bailu.gtk.type.Str;
import java.util.ResourceBundle;
public class I18n {
private static final ResourceBundle bundle = ResourceBundle.getBundle("inceptum");
public static String get(String key) {
return bundle.getString(key);
}
public static String get(String key, Object... args) {
return String.format(bundle.getString(key), args);
}
public static Str str(String key) {
return new Str(get(key));
}
public static Str str(String key, Object... args) {
return new Str(get(key, args));
}
}

View File

@ -0,0 +1,20 @@
account.none=None
about.contact=Contact
about.jvm=Java VM %d
about.license=This program comes with ABSOLUTELY NO WARRANTY.\
This is free software, and you are welcome to redistribute it under certain conditions.
menu.account=Account
menu.account.manage=Manage
menu.account.new=New
menu.file=File
menu.file.exit=Exit
menu.file.new=New Instance
menu.file.redownload=Re-download resources
menu.help=Help
menu.help.about=About
menu.help.log=Log
menu.launch=Launch
about.unsupported-build=This is a custom build. No support will be provided.
about.jvm.unsupported=Java VM %d (unsupported)
ok=Ok
cancel=Cancel

View File

@ -0,0 +1,19 @@
account.none=Keiner
about.contact=Kontakt
about.license=Dieses Programm komt OHNE JEGLICHE GARANTIE.\
Dies ist freie Software, die Sie unter bestimmten Bedingungen gerne weitergeben dürfen.
menu.account=Konto
menu.account.manage=Verwalten
menu.account.new=Neu
menu.file=Datei
menu.file.exit=Beenden
menu.file.new=Neue Instanz
menu.file.redownload=Ressourcen neu herunterladen
menu.help=Hilfe
menu.help.about=Über
menu.help.log=Protokoll
menu.launch=Starte
about.unsupported-build=Dies ist ein selbst-erstelltes Build. Es wird kein Support geleistet.
about.jvm.unsupported=Java VM %d (nicht unterstützt)
ok=OK
cancel=Abbrechen

View File

@ -2,6 +2,10 @@ plugins {
id("inceptum.application-conventions")
}
application {
mainClass.set("io.gitlab.jfronny.inceptum.imgui.GuiMain")
}
dependencies {
val flavor: String by rootProject.extra
val lwjglVersion: String by rootProject.extra

View File

@ -48,13 +48,13 @@ public class GuiMain {
Utils.LOGGER.info("Launching Inceptum v" + BuildMetadata.VERSION);
Utils.LOGGER.info("Loading from " + MetaHolder.BASE_PATH);
try {
main();
showGui();
} finally {
LauncherEnv.terminate();
}
}
public static void main() {
public static void showGui() {
Logger.registerFactory(name -> new CompoundLogger(name, InceptumEnvironmentInitializer.defaultFactory(name), MEMLOG));
UpdateMetadata update = BuildMetadata.IS_PUBLIC ? Updater.getUpdate() : null;
AccountManager.loadAccounts();

View File

@ -8,6 +8,7 @@ import io.gitlab.jfronny.inceptum.imgui.GuiMain;
import io.gitlab.jfronny.inceptum.imgui.window.edit.InstanceEditWindow;
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta;
import io.gitlab.jfronny.inceptum.launcher.system.launch.InstanceLauncher;
import io.gitlab.jfronny.inceptum.launcher.util.InstanceList;
import io.gitlab.jfronny.inceptum.launcher.util.InstanceLock;
import io.gitlab.jfronny.inceptum.launcher.system.install.Steps;
@ -17,53 +18,42 @@ import java.nio.file.Path;
import java.util.*;
public class InstanceView {
private static final Map<Path, FileBackedRef<InstanceMeta>> metas = new HashMap<>();
public static void draw(List<Path> paths) {
public static void draw() throws IOException {
List<InstanceList.Entry> entries = new ArrayList<>();
InstanceList.forEach(entries::add);
if (entries.isEmpty()) {
ImGui.text("You have not yet created an instance");
ImGui.text("Use File->New Instance to do so");
}
if (ImGui.beginTable("Instances", 2, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.Borders)) {
for (Path path : paths) {
if (InstanceLock.isSetupLocked(path)) {
for (InstanceList.Entry entry : entries) {
if (InstanceLock.isSetupLocked(entry.path())) {
ImGui.tableNextColumn();
ImGui.text("Setting up");
ImGui.tableNextColumn();
ImGui.text("This instance is currently being set up");
continue;
}
if (!Files.exists(path.resolve("instance.json"))) {
Utils.LOGGER.error("Invalid instance (doesn't contain instance.json): " + path);
continue;
}
InstanceMeta instance;
try {
if (!metas.containsKey(path))
metas.put(path, new FileBackedRef<>(path.resolve("instance.json"), InstanceMeta.class));
instance = metas.get(path).get();
} catch (IOException e) {
Utils.LOGGER.error("Could not load instance.json", e);
if (!Files.exists(entry.path().resolve("instance.json"))) {
Utils.LOGGER.error("Invalid instance (doesn't contain instance.json): " + entry);
continue;
}
ImGui.tableNextColumn();
boolean runDisabled = false;
try {
if (InstanceLock.isRunningLocked(path))
runDisabled = true;
} catch (IOException e) {
continue;
}
boolean runDisabled = InstanceLock.isRunningLocked(entry.path());
if (runDisabled) ImGui.beginDisabled();
if (ImGui.button(path.getFileName().toString())) {
if (ImGui.button(entry.toString())) {
try {
Steps.reDownload(path, Steps.createProcessState());
Steps.reDownload(entry.path(), Steps.createProcessState());
} catch (IOException e) {
Utils.LOGGER.error("Could not redownload instance, trying to start anyways", e);
}
InstanceLauncher.launchClient(path, instance);
InstanceLauncher.launchClient(entry.path(), entry.meta());
}
if (runDisabled) ImGui.endDisabled();
ImGui.tableNextColumn();
if (ImGui.button("Edit##" + path)) {
if (ImGui.button("Edit##" + entry.path())) {
try {
GuiMain.open(new InstanceEditWindow(path, instance));
GuiMain.open(new InstanceEditWindow(entry.path(), entry.meta()));
} catch (IOException e) {
Utils.LOGGER.error("Could not open instance edit window", e);
}

View File

@ -30,12 +30,6 @@ public class AboutWindow extends Window {
try {
ImGui.text("For support, you can head to my ");
ImGui.sameLine();
if (ImGui.button("Discord")) {
Utils.openWebBrowser(new URI("https://discord.gg/UjhHBqt"));
}
ImGui.sameLine();
ImGui.text("guild or");
ImGui.sameLine();
if (ImGui.button("Matrix")) {
Utils.openWebBrowser(new URI("https://matrix.to/#/%23jfronny%3Amatrix.org"));
}

View File

@ -88,17 +88,10 @@ public class MainWindow extends Window {
ImGui.showDemoWindow();
}
List<Path> paths;
try {
if (!Files.exists(MetaHolder.INSTANCE_DIR)) Files.createDirectories(MetaHolder.INSTANCE_DIR);
paths = JFiles.list(MetaHolder.INSTANCE_DIR, Files::isDirectory);
InstanceView.draw();
} catch (IOException e) {
Utils.LOGGER.error("Could not list instances");
return;
Utils.LOGGER.error("Could not show instance list view");
}
if (paths.isEmpty()) {
ImGui.text("You have not yet created an instance");
ImGui.text("Use File->New Instance to do so");
} else InstanceView.draw(paths);
}
}

View File

@ -44,13 +44,8 @@ public class InstanceEditWindow extends Window {
ImGui.text("This instance is still being set up.");
return;
}
try {
if (InstanceLock.isRunningLocked(path)) {
ImGui.text("This instance is running. Edits in this state will result in breakage.");
}
} catch (IOException e) {
ImGui.text("Could not read lock state on this instance");
Utils.LOGGER.error("Could not read lock state", e);
if (InstanceLock.isRunningLocked(path)) {
ImGui.text("This instance is running. Edits in this state will result in breakage.");
}
lastTabWasMods = false;
if (ImGui.beginTabBar("InstanceEdit" + path)) {

View File

@ -12,6 +12,7 @@ public class InstanceMeta {
public String java;
public Long minMem;
public Long maxMem;
public Long lastLaunched;
public Arguments arguments;
public boolean isFabric() {

View File

@ -142,6 +142,13 @@ public class InstanceLauncher {
}
}
}
// Write launch time
instance.lastLaunched = System.currentTimeMillis() / 1000L;
try {
JFiles.writeObject(instancePath.resolve("instance.json"), instance);
} catch (IOException e) {
Utils.LOGGER.error("Could not write instance config", e);
}
// Log command used to start
Utils.LOGGER.info(String.join(" ", args));
// Create process

View File

@ -0,0 +1,94 @@
package io.gitlab.jfronny.inceptum.launcher.util;
import io.gitlab.jfronny.commons.cache.FileBackedRef;
import io.gitlab.jfronny.commons.io.JFiles;
import io.gitlab.jfronny.inceptum.common.MetaHolder;
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Consumer;
public class InstanceList {
private static final Map<Path, IEntry> metas = new LinkedHashMap<>();
public static void forEach(Consumer<Entry> target) throws IOException {
Objects.requireNonNull(target);
if (!Files.exists(MetaHolder.INSTANCE_DIR)) Files.createDirectories(MetaHolder.INSTANCE_DIR);
JFiles.listTo(MetaHolder.INSTANCE_DIR, path -> {
if (!Files.isDirectory(path)) return;
target.accept(read(path));
});
}
public static void forEachLaunchable(Consumer<Entry> target) throws IOException {
forEach(entry -> {
if (!InstanceLock.isLocked(entry.path)) target.accept(entry);
});
}
public static Entry read(Path instancePath) throws IOException {
Objects.requireNonNull(instancePath);
synchronized (metas) {
if (!metas.containsKey(instancePath)) {
metas.put(instancePath, new IEntry(
toId(instancePath.getFileName().toString()),
instancePath,
new FileBackedRef<>(instancePath.resolve("instance.json"), InstanceMeta.class)
));
}
return metas.get(instancePath).toPub();
}
}
/**
* Converts any string into a set of lowercase ascii chars
* @param input string to convert
* @return a string matching [a-p]*
*/
private static String toId(String input) {
StringBuilder result = new StringBuilder();
for (byte b : input.getBytes(StandardCharsets.UTF_8)) {
int by = Byte.toUnsignedInt(b);
int ch2 = by & 15; // right bits
int ch1 = (by - ch2) / 16; // left bits
result.append((char)('a' + ch1));
result.append((char)('a' + ch2));
}
return result.toString();
}
private record IEntry(String id, Path path, FileBackedRef<InstanceMeta> meta) {
@Override
public String toString() {
return path.getFileName().toString();
}
public Entry toPub() throws IOException {
return new Entry(id, path, meta.get());
}
}
public record Entry(String id, Path path, InstanceMeta meta) implements Comparable<Entry> {
@Override
public int compareTo(@NotNull InstanceList.Entry entry) {
long time1 = meta.lastLaunched == null ? 0 : meta.lastLaunched;
long time2 = entry.meta.lastLaunched == null ? 0 : entry.meta.lastLaunched;
if (time1 == 0) {
if (time2 == 0) return path.getFileName().toString().compareTo(entry.path.getFileName().toString());
return -1;
}
if (time2 == 0) return 1;
return Long.compare(time1, time2);
}
@Override
public String toString() {
return path.getFileName().toString();
}
}
}

View File

@ -1,5 +1,7 @@
package io.gitlab.jfronny.inceptum.launcher.util;
import io.gitlab.jfronny.inceptum.common.Utils;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@ -24,15 +26,19 @@ public class InstanceLock {
Files.writeString(instancePath.resolve(INCEPTUM_LOCK), Long.toString(pid));
}
public static boolean isRunningLocked(Path instancePath) throws IOException {
public static boolean isRunningLocked(Path instancePath) {
if (!Files.exists(instancePath.resolve(INCEPTUM_LOCK))) return false;
if (ProcessUtils.isProcessAlive(Files.readString(instancePath.resolve(INCEPTUM_LOCK))))
return true;
Files.delete(instancePath.resolve(INCEPTUM_LOCK));
try {
if (ProcessUtils.isProcessAlive(Files.readString(instancePath.resolve(INCEPTUM_LOCK))))
return true;
Files.delete(instancePath.resolve(INCEPTUM_LOCK));
} catch (IOException e) {
Utils.LOGGER.error("Could not read running lock of " + instancePath.getFileName().toString(), e);
}
return false;
}
public static boolean isLocked(Path instancePath) throws IOException {
public static boolean isLocked(Path instancePath) {
return isSetupLocked(instancePath) || isRunningLocked(instancePath);
}
}

View File

@ -7,3 +7,4 @@ include("launcher-cli")
include("launcher-imgui")
include("launcher-dist")
include("launchwrapper")
include("launcher-gtk")