GTK: Start working on accounts UI and complete more menu entries
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/woodpecker Pipeline was successful Details

This commit is contained in:
Johannes Frohnmeyer 2023-01-29 15:45:51 +01:00
parent eb9601d6cf
commit 7284193981
Signed by: Johannes
GPG Key ID: E76429612C2929F4
34 changed files with 328 additions and 133 deletions

View File

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

View File

@ -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();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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();
});
}
}
}
}

View File

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

View File

@ -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(() -> {

View File

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

View File

@ -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");

View File

@ -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();
}

View File

@ -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");
}
}

View File

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

View File

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

View File

@ -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();
});
}
});
}
}

View File

@ -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");
}
}

View File

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

View File

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

View File

@ -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");

View File

@ -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));
}

View File

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

View File

@ -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();
}
}

View File

@ -5,4 +5,5 @@ import java.util.concurrent.atomic.AtomicBoolean;
public interface Step {
void execute(SetupStepInfo info, AtomicBoolean stopThread) throws IOException;
String getName();
}

View File

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

View File

@ -31,4 +31,9 @@ public class DownloadAssetsStep implements Step {
throw new IOException("Could not download assets", e);
}
}
@Override
public String getName() {
return "Downloading Assets";
}
}

View File

@ -52,4 +52,9 @@ public class DownloadClientStep implements Step {
throw new IOException("Could not download client", e);
}
}
@Override
public String getName() {
return "Downloading Game";
}
}

View File

@ -41,4 +41,9 @@ public class DownloadJavaStep implements Step {
}
}
}
@Override
public String getName() {
return "Downloading Java";
}
}

View File

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

View File

@ -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";
}
}

View File

@ -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";
}
}

View File

@ -43,4 +43,9 @@ public class WriteMetadataStep implements Step {
Files.writeString(instance.resolve(".iceignore"), Instance.CONFIG_NAME);
}
}
@Override
public String getName() {
return "Writing Metadata";
}
}

View File

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