GTK: Start working on accounts UI and complete more menu entries
This commit is contained in:
parent
eb9601d6cf
commit
7284193981
|
@ -12,7 +12,7 @@ import org.jetbrains.annotations.Nullable;
|
|||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public enum GtkEnvBackend implements LauncherEnv.EnvBackend { //TODO test
|
||||
public enum GtkEnvBackend implements LauncherEnv.EnvBackend {
|
||||
INSTANCE;
|
||||
|
||||
public Window dialogParent = null;
|
||||
|
|
|
@ -3,6 +3,7 @@ package io.gitlab.jfronny.inceptum.gtk;
|
|||
import io.gitlab.jfronny.inceptum.common.*;
|
||||
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.gtk.gio.ApplicationFlags;
|
||||
import org.gtk.glib.GLib;
|
||||
import org.gtk.gtk.Application;
|
||||
|
@ -35,6 +36,8 @@ public class GtkMain {
|
|||
|
||||
public static int showGui(String[] args) {
|
||||
return setupApplication(args, app -> {
|
||||
//TODO update check
|
||||
AccountManager.loadAccounts();
|
||||
GtkMenubar.create(app);
|
||||
var window = new MainWindow(app);
|
||||
window.show();
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
package io.gitlab.jfronny.inceptum.gtk;
|
||||
|
||||
import io.gitlab.jfronny.commons.io.JFiles;
|
||||
import io.gitlab.jfronny.inceptum.common.MetaHolder;
|
||||
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.system.launch.*;
|
||||
import io.gitlab.jfronny.inceptum.launcher.util.ProcessState;
|
||||
import org.gtk.gtk.Application;
|
||||
|
@ -29,13 +33,32 @@ public class GtkMenubar {
|
|||
var file = menu.submenu("file");
|
||||
file.button("new", () -> new NewInstanceWindow(app).show());
|
||||
file.button("redownload", () -> {
|
||||
//TODO
|
||||
ProcessState state = new ProcessState(3 + Steps.STEPS.size() * InstanceList.size(), "Initializing");
|
||||
ProcessStateWatcherDialog.show(
|
||||
GtkEnvBackend.INSTANCE.dialogParent,
|
||||
"Reloading data",
|
||||
"Could not execute refresh task",
|
||||
state,
|
||||
cancel -> {
|
||||
state.incrementStep("Clearing cache directories");
|
||||
JFiles.clearDirectory(MetaHolder.ASSETS_DIR);
|
||||
JFiles.clearDirectory(MetaHolder.LIBRARIES_DIR, path -> !path.startsWith(MetaHolder.LIBRARIES_DIR.resolve("io/gitlab/jfronny")));
|
||||
JFiles.clearDirectory(MetaHolder.NATIVES_DIR, path -> !path.startsWith(MetaHolder.NATIVES_DIR.resolve("forceload")));
|
||||
JFiles.clearDirectory(MetaHolder.CACHE_DIR);
|
||||
if (cancel.get()) return;
|
||||
state.incrementStep("Reloading instance list");
|
||||
InstanceList.reset();
|
||||
InstanceList.forEach(instance -> {
|
||||
if (cancel.get()) return;
|
||||
Steps.reDownload(instance, state, cancel);
|
||||
});
|
||||
}, R::nop);
|
||||
});
|
||||
file.button("exit", app::quit);
|
||||
launchMenu = menu.submenu("launch");
|
||||
generateLaunchMenu();
|
||||
accountsMenu = menu.submenu("account");
|
||||
generateAccountsMenu();
|
||||
generateAccountsMenu(app);
|
||||
var help = menu.submenu("help");
|
||||
help.button("about", AboutWindow::createAndShow);
|
||||
help.button("log", () -> {
|
||||
|
@ -58,17 +81,14 @@ public class GtkMenubar {
|
|||
public static void launch(Instance instance, LaunchType launchType) {
|
||||
if (instance.isSetupLocked) {
|
||||
LauncherEnv.showError(I18n.get("instance.launch.locked.setup"), I18n.get("instance.launch.locked"));
|
||||
return;
|
||||
}
|
||||
if (instance.isRunningLocked) {
|
||||
} else if (instance.isRunningLocked) {
|
||||
LauncherEnv.showOkCancel(
|
||||
I18n.get("instance.launch.locked.running"),
|
||||
I18n.get("instance.launch.locked"),
|
||||
() -> forceLaunch(instance, launchType),
|
||||
R::nop
|
||||
);
|
||||
}
|
||||
forceLaunch(instance, launchType);
|
||||
} else forceLaunch(instance, launchType);
|
||||
}
|
||||
|
||||
private static void forceLaunch(Instance instance, LaunchType launchType) {
|
||||
|
@ -80,31 +100,31 @@ public class GtkMenubar {
|
|||
state,
|
||||
cancel -> {
|
||||
try {
|
||||
Steps.reDownload(instance, Steps.createProcessState(), cancel);
|
||||
Steps.reDownload(instance, state, cancel);
|
||||
} catch (IOException e) {
|
||||
Utils.LOGGER.error("Could not fetch instance, trying to start anyways", e);
|
||||
}
|
||||
if (!cancel.get()) {
|
||||
try {
|
||||
if (launchType == LaunchType.Client) InstanceLauncher.launchClient(instance);
|
||||
else InstanceLauncher.launch(instance, launchType, false, AccountManager.NULL_AUTH);
|
||||
} catch (Throwable e) {
|
||||
LauncherEnv.showError("Could not start instance", e);
|
||||
}
|
||||
if (cancel.get()) return;
|
||||
state.updateStep("Starting Game");
|
||||
try {
|
||||
if (launchType == LaunchType.Client) InstanceLauncher.launchClient(instance);
|
||||
else InstanceLauncher.launch(instance, launchType, false, AccountManager.NULL_AUTH);
|
||||
} catch (Throwable e) {
|
||||
LauncherEnv.showError("Could not start instance", e);
|
||||
}
|
||||
},
|
||||
R::nop
|
||||
);
|
||||
}
|
||||
|
||||
public static void generateAccountsMenu() {
|
||||
public static void generateAccountsMenu(Application app) {
|
||||
Objects.requireNonNull(accountsMenu);
|
||||
accountsMenu.clear();
|
||||
accountsMenu.button("new", () -> {
|
||||
//TODO
|
||||
});
|
||||
accountsMenu.button("new", () -> new MicrosoftLoginDialog(GtkEnvBackend.INSTANCE.dialogParent, null).show());
|
||||
accountsMenu.button("manage", () -> {
|
||||
//TODO UI to add/remove accounts
|
||||
var window = new LauncherSettingsWindow(app);
|
||||
window.activePage = "settings.accounts";
|
||||
window.show();
|
||||
});
|
||||
List<MicrosoftAccount> accounts = new ArrayList<>(AccountManager.accounts);
|
||||
accounts.add(null);
|
||||
|
|
|
@ -9,7 +9,7 @@ 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.ListIndexItem;
|
||||
import io.gitlab.jfronny.inceptum.gtk.window.InstanceSettingsWindow;
|
||||
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;
|
||||
|
@ -79,6 +79,14 @@ public class InstanceListEntryFactory extends SignalListItemFactory {
|
|||
|
||||
var menuBuilder = new MenuBuilder(li.popoverMenu, li.instance.id);
|
||||
var launchSection = menuBuilder.literalSection("launch", null);
|
||||
var kill = launchSection.literalButton("kill", I18n.get("instance.kill"), () -> {
|
||||
//TODO test
|
||||
LauncherEnv.showOkCancel(I18n.get("instance.kill.prompt"), I18n.get("instance.kill.details"), () -> {
|
||||
if (!li.instance.kill()) LauncherEnv.showError(I18n.get("instance.kill.fail"), I18n.get("failed"));
|
||||
}, R::nop);
|
||||
});
|
||||
kill.enabled = li.instance.isRunningLocked;
|
||||
|
||||
launchSection.literalButton("launch.client", I18n.get("instance.launch.client"),
|
||||
() -> GtkMenubar.launch(li.instance, LaunchType.Client))
|
||||
.iconName = "media-playback-start-symbolic";
|
||||
|
@ -98,7 +106,7 @@ public class InstanceListEntryFactory extends SignalListItemFactory {
|
|||
try {
|
||||
JFiles.copyRecursive(li.instance.path, MetaHolder.INSTANCE_DIR.resolve(InstanceNameTool.getNextValid(s)));
|
||||
} catch (IOException e) {
|
||||
LauncherEnv.showError("Could not copy instance", e);
|
||||
LauncherEnv.showError(I18n.get("instance.copy.fail"), e);
|
||||
}
|
||||
}, R::nop);
|
||||
}).iconName = "edit-copy-symbolic";
|
||||
|
@ -107,13 +115,11 @@ public class InstanceListEntryFactory extends SignalListItemFactory {
|
|||
try {
|
||||
JFiles.deleteRecursive(li.instance.path);
|
||||
} catch (IOException e) {
|
||||
LauncherEnv.showError("Could not delete the instance", e);
|
||||
LauncherEnv.showError(I18n.get("instance.delete.fail"), e);
|
||||
}
|
||||
}, R::nop);
|
||||
}).iconName = "edit-delete-symbolic";
|
||||
|
||||
//TODO kill current instance
|
||||
|
||||
Consumer<Signal<?>> dc = s -> toDisconnect.computeIfAbsent(li.instance.id, $ -> new HashSet<>()).add(s);
|
||||
|
||||
dc.accept(li.launch.onClicked(() -> GtkMenubar.launch(li.instance, LaunchType.Client)));
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package io.gitlab.jfronny.inceptum.gtk.control;
|
||||
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 org.gtk.gtk.*;
|
||||
import org.jetbrains.annotations.Nullable;
|
|
@ -1,23 +1,20 @@
|
|||
package io.gitlab.jfronny.inceptum.gtk.window.edit;
|
||||
package io.gitlab.jfronny.inceptum.gtk.control.settings;
|
||||
|
||||
import io.gitlab.jfronny.commons.StringFormatter;
|
||||
import io.gitlab.jfronny.inceptum.gtk.GtkEnvBackend;
|
||||
import io.gitlab.jfronny.inceptum.gtk.control.ILabel;
|
||||
import io.gitlab.jfronny.inceptum.gtk.control.IRow;
|
||||
import io.gitlab.jfronny.inceptum.gtk.control.settings.IRow;
|
||||
import io.gitlab.jfronny.inceptum.gtk.util.I18n;
|
||||
import io.gitlab.jfronny.inceptum.gtk.window.InstanceSettingsWindow;
|
||||
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance;
|
||||
import org.gtk.gtk.*;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.PropertyKey;
|
||||
|
||||
public class SettingsTab extends Box {
|
||||
protected final Instance instance;
|
||||
protected final InstanceSettingsWindow window;
|
||||
protected final Window window;
|
||||
|
||||
public SettingsTab(Instance instance, InstanceSettingsWindow window) {
|
||||
public SettingsTab(Window window) {
|
||||
super(Orientation.VERTICAL, 8);
|
||||
this.instance = instance;
|
||||
this.marginHorizontal = 24;
|
||||
this.marginTop = 12;
|
||||
this.window = window;
|
|
@ -1,20 +1,21 @@
|
|||
package io.gitlab.jfronny.inceptum.gtk.window;
|
||||
package io.gitlab.jfronny.inceptum.gtk.control.settings;
|
||||
|
||||
import io.gitlab.jfronny.inceptum.gtk.util.I18n;
|
||||
import io.gitlab.jfronny.inceptum.gtk.window.edit.*;
|
||||
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance;
|
||||
import org.gnome.adw.HeaderBar;
|
||||
import org.gnome.adw.*;
|
||||
import org.gnome.adw.HeaderBar;
|
||||
import org.gtk.gobject.BindingFlags;
|
||||
import org.gtk.gtk.*;
|
||||
import org.gtk.gtk.Application;
|
||||
import org.gtk.gtk.Window;
|
||||
import org.gtk.gtk.*;
|
||||
import org.jetbrains.annotations.PropertyKey;
|
||||
|
||||
public class InstanceSettingsWindow extends Window {
|
||||
public InstanceSettingsWindow(Application app, Instance instance) {
|
||||
public class SettingsWindow extends Window {
|
||||
protected final ViewStack stack;
|
||||
|
||||
public SettingsWindow(Application app) {
|
||||
this.application = app;
|
||||
|
||||
ViewStack stack = new ViewStack();
|
||||
this.stack = new ViewStack();
|
||||
|
||||
HeaderBar header = new HeaderBar();
|
||||
ViewSwitcherTitle viewSwitcher = new ViewSwitcherTitle();
|
||||
|
@ -35,9 +36,13 @@ public class InstanceSettingsWindow extends Window {
|
|||
view.append(bottomBar);
|
||||
|
||||
child = view;
|
||||
}
|
||||
|
||||
stack.addTitledWithIcon(new GeneralTab(instance, this), null, I18n.get("instance.settings.general"), "preferences-other-symbolic");
|
||||
stack.addTitledWithIcon(new ModsTab(instance, this), null, I18n.get("instance.settings.mods"), "package-x-generic-symbolic");
|
||||
stack.addTitledWithIcon(new ExportTab(instance, this), null, I18n.get("instance.settings.export"), "send-to-symbolic");
|
||||
public void addTab(SettingsTab tab, @PropertyKey(resourceBundle = I18n.BUNDLE) String title, String iconName) {
|
||||
stack.addTitledWithIcon(tab, title, I18n.get(title), iconName);
|
||||
}
|
||||
|
||||
public void setActivePage(@PropertyKey(resourceBundle = I18n.BUNDLE) String title) {
|
||||
stack.visibleChildName = title;
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
package io.gitlab.jfronny.inceptum.gtk.window;
|
||||
|
||||
import io.gitlab.jfronny.inceptum.common.InceptumConfig;
|
||||
import io.gitlab.jfronny.inceptum.common.model.inceptum.UpdateChannel;
|
||||
import io.gitlab.jfronny.inceptum.gtk.control.IRow;
|
||||
import org.gtk.gtk.*;
|
||||
|
||||
public class LauncherSettingsWindow extends Window {
|
||||
public LauncherSettingsWindow(Application app) {
|
||||
this.application = app;
|
||||
|
||||
var box = new Box(Orientation.VERTICAL, 8);
|
||||
box.marginHorizontal = 24;
|
||||
box.marginTop = 12;
|
||||
this.child = box;
|
||||
|
||||
{
|
||||
Frame frame = new Frame(null);
|
||||
ListBox listBox = new ListBox();
|
||||
listBox.selectionMode = SelectionMode.NONE;
|
||||
frame.child = listBox;
|
||||
box.append(frame);
|
||||
{
|
||||
IRow row = new IRow("settings.snapshots", "settings.snapshots.subtitle");
|
||||
listBox.append(row);
|
||||
row.setSwitch(InceptumConfig.snapshots, b -> {
|
||||
InceptumConfig.snapshots = b;
|
||||
InceptumConfig.saveConfig();
|
||||
});
|
||||
}
|
||||
{
|
||||
IRow row = new IRow("settings.update-channel", "settings.update-channel.subtitle");
|
||||
listBox.append(row);
|
||||
row.setDropdown(new String[] {"Stable", "CI"}, InceptumConfig.channel == UpdateChannel.CI ? 1 : 0, state -> {
|
||||
InceptumConfig.channel = state == 1 ? UpdateChannel.CI : UpdateChannel.Stable;
|
||||
InceptumConfig.saveConfig();
|
||||
});
|
||||
}
|
||||
{
|
||||
IRow row = new IRow("settings.author-name", "settings.author-name.subtitle");
|
||||
listBox.append(row);
|
||||
row.setEntry(InceptumConfig.authorName, s -> {
|
||||
InceptumConfig.authorName = s;
|
||||
InceptumConfig.saveConfig();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ 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.gtk.util.ListIndexModel;
|
||||
import io.gitlab.jfronny.inceptum.gtk.window.settings.launcher.LauncherSettingsWindow;
|
||||
import io.gitlab.jfronny.inceptum.launcher.system.instance.*;
|
||||
import io.gitlab.jfronny.inceptum.launcher.system.launch.LaunchType;
|
||||
import org.gnome.adw.Clamp;
|
||||
|
@ -75,9 +76,9 @@ public class MainWindow extends ApplicationWindow {
|
|||
|
||||
instanceList = new ArrayList<>();
|
||||
instanceListIndex = new ListIndexModel(instanceList.size());
|
||||
var singleSelection = new NoSelection(instanceListIndex);
|
||||
var selection = new NoSelection(instanceListIndex);
|
||||
|
||||
ListView listView = new ListView(singleSelection, new InstanceListEntryFactory(app, instanceList));
|
||||
ListView listView = new ListView(selection, new InstanceListEntryFactory(app, instanceList));
|
||||
listView.addCssClass("rich-list");
|
||||
listView.showSeparators = true;
|
||||
listView.onActivate(position -> {
|
||||
|
@ -92,7 +93,7 @@ public class MainWindow extends ApplicationWindow {
|
|||
listContainer = new Clamp();
|
||||
listContainer.maximumSize = 900;
|
||||
listContainer.child = frame;
|
||||
gridView = new GridView(singleSelection, new InstanceGridEntryFactory(instanceList));
|
||||
gridView = new GridView(selection, new InstanceGridEntryFactory(instanceList));
|
||||
empty = new StatusPage();
|
||||
empty.title = I18n.get("main.empty.title");
|
||||
empty.description = I18n.get("main.empty.description");
|
||||
|
@ -106,7 +107,7 @@ public class MainWindow extends ApplicationWindow {
|
|||
scroll.setPolicy(PolicyType.NEVER, PolicyType.AUTOMATIC);
|
||||
scroll.child = stack;
|
||||
|
||||
setDefaultSize(360, 720);
|
||||
setDefaultSize(720, 360);
|
||||
title = "Inceptum";
|
||||
titlebar = header;
|
||||
showMenubar = false;
|
||||
|
|
|
@ -16,7 +16,7 @@ public class MicrosoftLoginDialog extends MessageDialog {
|
|||
return flags;
|
||||
}
|
||||
|
||||
public MicrosoftLoginDialog(@Nullable Window parent, MicrosoftAccount account) {
|
||||
public MicrosoftLoginDialog(@Nullable Window parent, @Nullable MicrosoftAccount account) {
|
||||
super(
|
||||
parent,
|
||||
flags(parent != null),
|
||||
|
@ -25,6 +25,13 @@ public class MicrosoftLoginDialog extends MessageDialog {
|
|||
I18n.get("auth.description")
|
||||
);
|
||||
title = I18n.get("auth.title");
|
||||
onResponse(responseId -> {
|
||||
switch (ResponseType.of(responseId)) {
|
||||
case CLOSE, CANCEL -> this.close();
|
||||
case DELETE_EVENT -> this.destroy();
|
||||
default -> Utils.LOGGER.error("Unexpected response type: " + responseId);
|
||||
}
|
||||
});
|
||||
var btn = Button.newWithLabel(I18n.get("auth.open-browser"));
|
||||
((Box) messageArea).append(btn);
|
||||
btn.onClicked(() -> {
|
||||
|
|
|
@ -28,6 +28,8 @@ public class ProcessStateWatcherDialog extends MessageDialog {
|
|||
}
|
||||
|
||||
public ProcessStateWatcherDialog(Window parent, String title, String errorMessage, ProcessState state, ThrowingConsumer<AtomicBoolean, ?> executor, @Nullable Runnable cancel) {
|
||||
//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
|
||||
super(parent, DialogFlags.MODAL.or(DialogFlags.DESTROY_WITH_PARENT), MessageType.INFO, ButtonsType.NONE, null);
|
||||
this.state = state;
|
||||
this.cancel = cancel;
|
||||
|
@ -61,7 +63,7 @@ public class ProcessStateWatcherDialog extends MessageDialog {
|
|||
if (!nc.equals(cachedState)) {
|
||||
cachedState = nc;
|
||||
setMarkup(cachedState.msg);
|
||||
progress.fraction = cachedState.progress;
|
||||
progress.fraction = Math.min(cachedState.progress, 1);
|
||||
widget.queueDraw();
|
||||
}
|
||||
return GLib.SOURCE_CONTINUE;
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package io.gitlab.jfronny.inceptum.gtk.window.edit;
|
||||
package io.gitlab.jfronny.inceptum.gtk.window.settings.instance;
|
||||
|
||||
import io.gitlab.jfronny.commons.ref.R;
|
||||
import io.gitlab.jfronny.inceptum.common.Utils;
|
||||
import io.gitlab.jfronny.inceptum.gtk.GtkEnvBackend;
|
||||
import io.gitlab.jfronny.inceptum.gtk.GtkMain;
|
||||
import io.gitlab.jfronny.inceptum.gtk.control.settings.SettingsTab;
|
||||
import io.gitlab.jfronny.inceptum.gtk.util.I18n;
|
||||
import io.gitlab.jfronny.inceptum.gtk.window.InstanceSettingsWindow;
|
||||
import io.gitlab.jfronny.inceptum.gtk.window.dialog.ProcessStateWatcherDialog;
|
||||
import io.gitlab.jfronny.inceptum.launcher.system.exporter.Exporter;
|
||||
import io.gitlab.jfronny.inceptum.launcher.system.exporter.Exporters;
|
||||
|
@ -16,8 +16,11 @@ import org.gtk.gtk.*;
|
|||
import java.nio.file.Path;
|
||||
|
||||
public class ExportTab extends SettingsTab {
|
||||
private final Instance instance;
|
||||
|
||||
public ExportTab(Instance instance, InstanceSettingsWindow window) {
|
||||
super(instance, window);
|
||||
super(window);
|
||||
this.instance = instance;
|
||||
section(null, section -> {
|
||||
{
|
||||
var row = section.row("instance.settings.export.version", "instance.settings.export.version.subtitle");
|
|
@ -1,13 +1,13 @@
|
|||
package io.gitlab.jfronny.inceptum.gtk.window.edit;
|
||||
package io.gitlab.jfronny.inceptum.gtk.window.settings.instance;
|
||||
|
||||
import io.github.jwharm.javagi.GErrorException;
|
||||
import io.gitlab.jfronny.commons.ArgumentsTokenizer;
|
||||
import io.gitlab.jfronny.commons.io.JFiles;
|
||||
import io.gitlab.jfronny.inceptum.common.*;
|
||||
import io.gitlab.jfronny.inceptum.gtk.control.ILabel;
|
||||
import io.gitlab.jfronny.inceptum.gtk.control.settings.SettingsTab;
|
||||
import io.gitlab.jfronny.inceptum.gtk.util.I18n;
|
||||
import io.gitlab.jfronny.inceptum.gtk.util.Memory;
|
||||
import io.gitlab.jfronny.inceptum.gtk.window.InstanceSettingsWindow;
|
||||
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;
|
||||
|
@ -29,7 +29,7 @@ public class GeneralTab extends SettingsTab {
|
|||
private static final VersionsList VERSIONS = McApi.getVersions();
|
||||
|
||||
public GeneralTab(Instance instance, InstanceSettingsWindow window) {
|
||||
super(instance, window);
|
||||
super(window);
|
||||
section(null, section -> {
|
||||
var row = section.row("instance.settings.general.name", "instance.settings.general.name.placeholder");
|
||||
Button apply = Button.newWithLabel(I18n.get("instance.settings.apply"));
|
||||
|
@ -210,7 +210,7 @@ public class GeneralTab extends SettingsTab {
|
|||
dialog.close();
|
||||
window.close();
|
||||
} catch (IOException e) {
|
||||
showError("Could not delete the instance", e);
|
||||
showError(I18n.get("instance.delete.fail"), e);
|
||||
}
|
||||
dialog.close();
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package io.gitlab.jfronny.inceptum.gtk.window.settings.instance;
|
||||
|
||||
import io.gitlab.jfronny.inceptum.gtk.control.settings.SettingsWindow;
|
||||
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance;
|
||||
import org.gtk.gtk.Application;
|
||||
|
||||
public class InstanceSettingsWindow extends SettingsWindow {
|
||||
public InstanceSettingsWindow(Application app, Instance instance) {
|
||||
super(app);
|
||||
addTab(new GeneralTab(instance, this), "instance.settings.general", "preferences-other-symbolic");
|
||||
addTab(new ModsTab(instance, this), "instance.settings.mods", "package-x-generic-symbolic");
|
||||
addTab(new ExportTab(instance, this), "instance.settings.export", "send-to-symbolic");
|
||||
}
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
package io.gitlab.jfronny.inceptum.gtk.window.edit;
|
||||
package io.gitlab.jfronny.inceptum.gtk.window.settings.instance;
|
||||
|
||||
import io.gitlab.jfronny.inceptum.gtk.control.ILabel;
|
||||
import io.gitlab.jfronny.inceptum.gtk.window.InstanceSettingsWindow;
|
||||
import io.gitlab.jfronny.inceptum.gtk.control.settings.SettingsTab;
|
||||
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance;
|
||||
|
||||
public class ModsTab extends SettingsTab {
|
||||
public ModsTab(Instance instance, InstanceSettingsWindow window) {
|
||||
super(instance, window);
|
||||
super(window);
|
||||
append(new ILabel("instance.settings.mods.unsupported"));
|
||||
//TODO implement this, somehow
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package io.gitlab.jfronny.inceptum.gtk.window.settings.launcher;
|
||||
|
||||
import io.gitlab.jfronny.inceptum.gtk.control.settings.SettingsTab;
|
||||
import org.gtk.gtk.Label;
|
||||
import org.gtk.gtk.Window;
|
||||
|
||||
public class AccountsTab extends SettingsTab {
|
||||
public AccountsTab(Window window) {
|
||||
super(window);
|
||||
append(new Label("Account management is still in development"));
|
||||
//TODO implement
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package io.gitlab.jfronny.inceptum.gtk.window.settings.launcher;
|
||||
|
||||
import io.gitlab.jfronny.inceptum.common.InceptumConfig;
|
||||
import io.gitlab.jfronny.inceptum.common.model.inceptum.UpdateChannel;
|
||||
import io.gitlab.jfronny.inceptum.gtk.control.settings.IRow;
|
||||
import io.gitlab.jfronny.inceptum.gtk.control.settings.SettingsTab;
|
||||
import org.gtk.gtk.Window;
|
||||
|
||||
public class GeneralTab extends SettingsTab {
|
||||
public GeneralTab(Window window) {
|
||||
super(window);
|
||||
section(null, section -> {
|
||||
{
|
||||
IRow row = section.row("settings.general.snapshots", "settings.general.snapshots.subtitle");
|
||||
row.setSwitch(InceptumConfig.snapshots, b -> {
|
||||
InceptumConfig.snapshots = b;
|
||||
InceptumConfig.saveConfig();
|
||||
});
|
||||
}
|
||||
{
|
||||
IRow row = section.row("settings.general.update-channel", "settings.general.update-channel.subtitle");
|
||||
row.setDropdown(new String[] {"Stable", "CI"}, InceptumConfig.channel == UpdateChannel.CI ? 1 : 0, state -> {
|
||||
InceptumConfig.channel = state == 1 ? UpdateChannel.CI : UpdateChannel.Stable;
|
||||
InceptumConfig.saveConfig();
|
||||
});
|
||||
}
|
||||
{
|
||||
IRow row = section.row("settings.general.author-name", "settings.general.author-name.subtitle");
|
||||
row.setEntry(InceptumConfig.authorName, s -> {
|
||||
InceptumConfig.authorName = s;
|
||||
InceptumConfig.saveConfig();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package io.gitlab.jfronny.inceptum.gtk.window.settings.launcher;
|
||||
|
||||
import io.gitlab.jfronny.inceptum.gtk.control.settings.SettingsWindow;
|
||||
import org.gtk.gtk.Application;
|
||||
|
||||
public class LauncherSettingsWindow extends SettingsWindow {
|
||||
public LauncherSettingsWindow(Application app) {
|
||||
super(app);
|
||||
addTab(new GeneralTab(this), "settings.general", "preferences-other-symbolic");
|
||||
addTab(new AccountsTab(this), "settings.accounts", "system-users-symbolic");
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ cancel=Cancel
|
|||
show=Show
|
||||
save=Save
|
||||
select=Select
|
||||
failed=Failed
|
||||
menu.hamburger.support=Support
|
||||
menu.hamburger.preferences=Preferences
|
||||
menu.hamburger.about=About
|
||||
|
@ -79,12 +80,13 @@ instance.settings.general.last-launched=Last launched: %1$s
|
|||
instance.settings.general.game=Game
|
||||
instance.settings.general.game.version=Version
|
||||
instance.settings.general.game.version.subtitle=Minecraft version of this instance
|
||||
settings.author-name=Author Name
|
||||
settings.snapshots=Snapshots
|
||||
settings.update-channel=Update Channel
|
||||
settings.snapshots.subtitle=Whether to show snapshots in the version selector for new instances
|
||||
settings.update-channel.subtitle=The update channel. I personnaly recommend the CI channel as it gest the latest features and fixes more quickly, but it might be more unstable
|
||||
settings.author-name.subtitle=The author name to add to packs where the metadata format requires specifying one
|
||||
settings.general=General
|
||||
settings.general.author-name=Author Name
|
||||
settings.general.snapshots=Snapshots
|
||||
settings.general.update-channel=Update Channel
|
||||
settings.general.snapshots.subtitle=Whether to show snapshots in the version selector for new instances
|
||||
settings.general.update-channel.subtitle=The update channel. I personnaly recommend the CI channel as it gest the latest features and fixes more quickly, but it might be more unstable
|
||||
settings.general.author-name.subtitle=The author name to add to packs where the metadata format requires specifying one
|
||||
instance.settings.general.game.fabric.enabled=Fabric
|
||||
instance.settings.general.game.fabric.enabled.subtitle=Whether the Fabric Loader should be used for this instance
|
||||
instance.settings.general.game.fabric.version=Fabric Version
|
||||
|
@ -94,4 +96,11 @@ instance.settings.general.game.java.subtitle=The path of the custom Java binary
|
|||
instance.settings.general.game.memory.min=Minimum Memory
|
||||
instance.settings.general.game.memory.min.subtitle=The minimum amount of Memory Minecraft will allocate (in MiB)
|
||||
instance.settings.general.game.memory.max=Maximum Memory
|
||||
instance.settings.general.game.memory.max.subtitle=The maximum amount of Memory Minecraft will allocate (in MiB)
|
||||
instance.settings.general.game.memory.max.subtitle=The maximum amount of Memory Minecraft will allocate (in MiB)
|
||||
settings.accounts=Accounts
|
||||
instance.delete.fail=Could not delete the instance
|
||||
instance.copy.fail=Could not copy instance
|
||||
instance.kill.fail=Could not kill the Instance
|
||||
instance.kill.prompt=Are you sure?
|
||||
instance.kill.details=Killing this Instance may cause data corruption and should be avoided if possible
|
||||
instance.kill=Kill
|
|
@ -20,6 +20,7 @@ cancel=Abbrechen
|
|||
show=Anzeigen
|
||||
save=Speichern
|
||||
select=Auswählen
|
||||
failed=Erfolglos
|
||||
menu.hamburger.support=Unterstützung
|
||||
menu.hamburger.preferences=Einstellungen
|
||||
menu.hamburger.about=Über
|
||||
|
@ -42,7 +43,7 @@ instance.copy=Klonen
|
|||
instance.delete=Löschen
|
||||
instance.delete.subtitle=Diese Instanz unwiederruflich löschen
|
||||
instance.delete.confirm=Diese Instanz wird unwiederruflich gelöscht
|
||||
instance.delete.confirm.title=Bist du sicher?
|
||||
instance.delete.confirm.title=Sind Sie sicher?
|
||||
instance.copy.prompt=Neuer Name
|
||||
instance.copy.details=Gib den Namen für die neue Instanz ein
|
||||
instance.settings.general=Allgemein
|
||||
|
@ -79,12 +80,13 @@ instance.settings.general.last-launched=Zuletzt gestartet: %1$s
|
|||
instance.settings.general.game=Game
|
||||
instance.settings.general.game.version=Version
|
||||
instance.settings.general.game.version.subtitle=Minecraft-Version dieser Instanz
|
||||
settings.author-name=Name des Authors
|
||||
settings.snapshots=Vorschauversionen
|
||||
settings.update-channel=Updatekanal
|
||||
settings.snapshots.subtitle=Ob Vorschauversionen im Versions-Auswahlmenü gezeigt werden sollen
|
||||
settings.update-channel.subtitle=Der Update-Kanal. Ich empfehle den etwas instabileren, aber häufiger aktualisierten CI-Kanal
|
||||
settings.author-name.subtitle=Der Name, der bei Modpack-Exporten, deren Metadaten einen Autor angeben, aufgelistet werden soll
|
||||
settings.general=Allgemein
|
||||
settings.general.author-name=Name des Authors
|
||||
settings.general.snapshots=Vorschauversionen
|
||||
settings.general.update-channel=Updatekanal
|
||||
settings.general.snapshots.subtitle=Ob Vorschauversionen im Versions-Auswahlmenü gezeigt werden sollen
|
||||
settings.general.update-channel.subtitle=Der Update-Kanal. Ich empfehle den etwas instabileren, aber häufiger aktualisierten CI-Kanal
|
||||
settings.general.author-name.subtitle=Der Name, der bei Modpack-Exporten, deren Metadaten einen Autor angeben, aufgelistet werden soll
|
||||
instance.settings.general.game.fabric.enabled=Fabric
|
||||
instance.settings.general.game.fabric.enabled.subtitle=Ob Fabric-Loader für diese Instanz aktiviert werden soll
|
||||
instance.settings.general.game.fabric.version=Fabric-Version
|
||||
|
@ -94,4 +96,11 @@ instance.settings.general.game.java.subtitle=Pfad der Java-Bin
|
|||
instance.settings.general.game.memory.min=Minimale Speichernutzung
|
||||
instance.settings.general.game.memory.min.subtitle=Die minimale Speichernutzung dieser Instanz (in MiB)
|
||||
instance.settings.general.game.memory.max=Maximale Speichernutzung
|
||||
instance.settings.general.game.memory.max.subtitle=Die maximale Speichernutzung dieser Instanz (in MiB)
|
||||
instance.settings.general.game.memory.max.subtitle=Die maximale Speichernutzung dieser Instanz (in MiB)
|
||||
settings.accounts=Konten
|
||||
instance.delete.fail=Konnte die Instanz nicht löschen
|
||||
instance.copy.fail=Konnte die Instanz nicht kopieren
|
||||
instance.kill.fail=Ausführung konnte nicht abgebrochen werden
|
||||
instance.kill.prompt=Sind Sie sicher?
|
||||
instance.kill.details=Das vorzeitige Beenden von Instanzen kann zu Datenverlust führen und sollte vermieden werden
|
||||
instance.kill=Beenden
|
|
@ -44,6 +44,7 @@ public class GuiUtil {
|
|||
GuiMain.open(new ProcessStateWatcherWindow("Creating Instance", "Could not create instance", pState, cToken -> {
|
||||
for (Step step : Steps.STEPS) {
|
||||
if (cToken.get()) return;
|
||||
pState.incrementStep(step.name);
|
||||
step.execute(state, cToken);
|
||||
}
|
||||
LauncherEnv.showInfo("The instance was successfully created. You can now launch it using the main menu", "Successfully installed");
|
||||
|
|
|
@ -123,6 +123,27 @@ public record Instance(String id, Path path, InstanceMeta meta, ModsDirScanner m
|
|||
return false;
|
||||
}
|
||||
|
||||
public boolean kill() {
|
||||
if (!isRunningLocked()) {
|
||||
Utils.LOGGER.info("Already killed");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
if (!ProcessUtils.kill(Files.readString(path.resolve(LOCK_NAME)))) {
|
||||
Utils.LOGGER.error("Could not kill instance");
|
||||
return false;
|
||||
}
|
||||
if (isRunningLocked()) {
|
||||
Utils.LOGGER.error("Still running after kill");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
Utils.LOGGER.error("Could not read running lock of " + name, e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setRunningLock(long pid) throws IOException {
|
||||
Files.writeString(path.resolve(LOCK_NAME), Long.toString(pid));
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package io.gitlab.jfronny.inceptum.launcher.system.instance;
|
|||
import gsoncompile.extensions.io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta.GC_InstanceMeta;
|
||||
import io.gitlab.jfronny.commons.io.JFiles;
|
||||
import io.gitlab.jfronny.commons.throwable.ThrowingConsumer;
|
||||
import io.gitlab.jfronny.commons.throwable.ThrowingRunnable;
|
||||
import io.gitlab.jfronny.inceptum.common.MetaHolder;
|
||||
import io.gitlab.jfronny.inceptum.common.Utils;
|
||||
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta;
|
||||
|
@ -13,10 +14,17 @@ import java.io.IOException;
|
|||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class InstanceList {
|
||||
private static final Map<Path, IEntry> metas = new LinkedHashMap<>();
|
||||
|
||||
public static <TEx extends Throwable> void lock(ThrowingRunnable<TEx> task) throws TEx {
|
||||
synchronized (metas) {
|
||||
task.run();
|
||||
}
|
||||
}
|
||||
|
||||
public static void reset() {
|
||||
synchronized (metas) {
|
||||
for (var entry : metas.entrySet()) {
|
||||
|
@ -55,6 +63,13 @@ public class InstanceList {
|
|||
return JFiles.list(MetaHolder.INSTANCE_DIR, InstanceList::isInstance).isEmpty;
|
||||
}
|
||||
|
||||
public static int size() throws IOException {
|
||||
if (!Files.exists(MetaHolder.INSTANCE_DIR)) return 0;
|
||||
try (Stream<Path> list = Files.list(MetaHolder.INSTANCE_DIR)) {
|
||||
return (int) list.filter(InstanceList::isInstance).count();
|
||||
}
|
||||
}
|
||||
|
||||
public static Instance read(Path instancePath) throws IOException {
|
||||
Objects.requireNonNull(instancePath);
|
||||
synchronized (metas) {
|
||||
|
|
|
@ -149,28 +149,41 @@ public class InstanceLauncher {
|
|||
Runnable starterRunner = () -> {
|
||||
try {
|
||||
proc.set(pb.start());
|
||||
instance.setRunningLock(proc.get().pid());
|
||||
instance.runningLock = proc.get().pid();
|
||||
} catch (IOException e) {
|
||||
Utils.LOGGER.error("Could not start " + launchType.name, e);
|
||||
}
|
||||
};
|
||||
starterRunner.run();
|
||||
if (restart) {
|
||||
new Thread(() -> {
|
||||
while (true) {
|
||||
starterRunner.run();
|
||||
if (!proc.get().isAlive) {
|
||||
Utils.LOGGER.error("Could not create server process");
|
||||
instance.isRunningLocked();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
proc.get().waitFor();
|
||||
} catch (InterruptedException e) {
|
||||
Utils.LOGGER.error("Could not wait for server to finish", e);
|
||||
}
|
||||
Utils.LOGGER.info("Restarting server");
|
||||
starterRunner.run();
|
||||
if (!proc.get().isAlive) {
|
||||
Utils.LOGGER.error("Could not restart server");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
} else {
|
||||
var th = new Thread(() -> {
|
||||
starterRunner.run();
|
||||
try {
|
||||
proc.get().waitFor();
|
||||
} catch (InterruptedException e) {
|
||||
Utils.LOGGER.error("Could not wait for thread", e);
|
||||
return;
|
||||
}
|
||||
instance.isRunningLocked();
|
||||
});
|
||||
th.isDaemon = true;
|
||||
th.start();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,4 +5,5 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||
|
||||
public interface Step {
|
||||
void execute(SetupStepInfo info, AtomicBoolean stopThread) throws IOException;
|
||||
String getName();
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ public class Steps {
|
|||
: LoaderInfo.NONE;
|
||||
SetupStepInfo info = new SetupStepInfo(vi, li, instance.name, state);
|
||||
for (Step step : Steps.STEPS) {
|
||||
state.incrementStep("Starting " + step.getClass().getSimpleName());
|
||||
state.incrementStep(step.name);
|
||||
step.execute(info, cancel);
|
||||
if (cancel.get()) return;
|
||||
}
|
||||
|
|
|
@ -31,4 +31,9 @@ public class DownloadAssetsStep implements Step {
|
|||
throw new IOException("Could not download assets", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Downloading Assets";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,4 +52,9 @@ public class DownloadClientStep implements Step {
|
|||
throw new IOException("Could not download client", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Downloading Game";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,4 +41,9 @@ public class DownloadJavaStep implements Step {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Downloading Java";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,11 @@ public class DownloadLibrariesStep implements Step {
|
|||
execute(info.version, stopThread, info.currentState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Downloading Libraries";
|
||||
}
|
||||
|
||||
public static void execute(VersionInfo version, AtomicBoolean stopThread, ProcessState currentState) throws IOException {
|
||||
for (ArtifactInfo artifact : VersionInfoLibraryResolver.getRelevant(version)) {
|
||||
if (stopThread.get()) return;
|
||||
|
|
|
@ -19,4 +19,9 @@ public class RunMdsStep implements Step {
|
|||
ModsDirScanner.get(instance.resolve("mods"), GC_InstanceMeta.read(instance.resolve(Instance.CONFIG_NAME)))
|
||||
.runOnce((path, iwModDescription) -> info.setState("Scanned " + path));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Running mod discovery system";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,4 +35,9 @@ public class SetupDirsStep implements Step {
|
|||
}
|
||||
Files.createDirectories(MetaHolder.NATIVES_DIR.resolve(GameVersionParser.getGameVersion(info.version.id)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Setting up directories";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,4 +43,9 @@ public class WriteMetadataStep implements Step {
|
|||
Files.writeString(instance.resolve(".iceignore"), Instance.CONFIG_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Writing Metadata";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,13 @@ public class ProcessUtils {
|
|||
return isProcessIdRunning(pid, "ps", "-p", pid);
|
||||
}
|
||||
|
||||
public static boolean kill(String pid) {
|
||||
//TODO test on windows
|
||||
if (OSUtils.TYPE == OSUtils.Type.WINDOWS)
|
||||
return kill("taskkill", "/f", "/pid", pid);
|
||||
return kill("kill", "--timeout", "500", "KILL", pid);
|
||||
}
|
||||
|
||||
private static boolean isProcessIdRunning(String pid, String... command) {
|
||||
try {
|
||||
Runtime rt = Runtime.getRuntime();
|
||||
|
@ -33,4 +40,17 @@ public class ProcessUtils {
|
|||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean kill(String... command) {
|
||||
try {
|
||||
Runtime rt = Runtime.getRuntime();
|
||||
Process pr = rt.exec(command);
|
||||
pr.waitFor();
|
||||
Thread.sleep(100); // Ensure the signal is processed by waiting this randomly picked amount of time
|
||||
return pr.exitValue() == 0;
|
||||
} catch (Exception e) {
|
||||
Utils.LOGGER.error("Could not kill process", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue