diff --git a/launcher/src/main/java/com/skcraft/launcher/InstanceSettings.java b/launcher/src/main/java/com/skcraft/launcher/InstanceSettings.java index 08cf92d..f42c6c7 100644 --- a/launcher/src/main/java/com/skcraft/launcher/InstanceSettings.java +++ b/launcher/src/main/java/com/skcraft/launcher/InstanceSettings.java @@ -5,26 +5,10 @@ import com.skcraft.launcher.launch.MemorySettings; import lombok.Data; -import java.util.Optional; - @Data @JsonIgnoreProperties(ignoreUnknown = true) public class InstanceSettings { private JavaRuntime runtime; private MemorySettings memorySettings; private String customJvmArgs; - - /** - * @return Empty optional if there is no custom runtime set, present optional if there is. - */ - public Optional getRuntime() { - return Optional.ofNullable(runtime); - } - - /** - * @return Empty optional if there are no custom memory settings, present optional if there are. - */ - public Optional getMemorySettings() { - return Optional.ofNullable(memorySettings); - } } diff --git a/launcher/src/main/java/com/skcraft/launcher/dialog/InstanceSettingsDialog.java b/launcher/src/main/java/com/skcraft/launcher/dialog/InstanceSettingsDialog.java new file mode 100644 index 0000000..7ef41fe --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/dialog/InstanceSettingsDialog.java @@ -0,0 +1,153 @@ +package com.skcraft.launcher.dialog; + +import com.skcraft.launcher.Instance; +import com.skcraft.launcher.InstanceSettings; +import com.skcraft.launcher.dialog.component.BetterComboBox; +import com.skcraft.launcher.launch.JavaRuntime; +import com.skcraft.launcher.launch.JavaRuntimeFinder; +import com.skcraft.launcher.launch.MemorySettings; +import com.skcraft.launcher.persistence.Persistence; +import com.skcraft.launcher.swing.FormPanel; +import com.skcraft.launcher.swing.LinedBoxPanel; +import com.skcraft.launcher.util.SharedLocale; +import lombok.extern.java.Log; + +import javax.swing.*; +import java.awt.*; + +@Log +public class InstanceSettingsDialog extends JDialog { + private final InstanceSettings settings; + + private final LinedBoxPanel formsPanel = new LinedBoxPanel(false); + private final FormPanel memorySettingsPanel = new FormPanel(); + private final JCheckBox enableMemorySettings = new JCheckBox(SharedLocale.tr("instance.options.customMemory")); + private final JSpinner minMemorySpinner = new JSpinner(); + private final JSpinner maxMemorySpinner = new JSpinner(); + + private final JCheckBox enableCustomRuntime = new JCheckBox(SharedLocale.tr("instance.options.customJava")); + private final FormPanel runtimePanel = new FormPanel(); + private final JComboBox javaRuntimeBox = new BetterComboBox<>(); + private final JTextField javaArgsBox = new JTextField(); + + private final LinedBoxPanel buttonsPanel = new LinedBoxPanel(true); + private final JButton okButton = new JButton(SharedLocale.tr("button.save")); + private final JButton cancelButton = new JButton(SharedLocale.tr("button.cancel")); + + private boolean saved = false; + + public InstanceSettingsDialog(Window owner, InstanceSettings settings) { + super(owner); + this.settings = settings; + + setTitle(SharedLocale.tr("instance.options.title")); + setModalityType(DEFAULT_MODALITY_TYPE); + initComponents(); + setSize(new Dimension(400, 500)); + setLocationRelativeTo(owner); + } + + private void initComponents() { + memorySettingsPanel.addRow(enableMemorySettings); + memorySettingsPanel.addRow(new JLabel(SharedLocale.tr("options.minMemory")), minMemorySpinner); + memorySettingsPanel.addRow(new JLabel(SharedLocale.tr("options.maxMemory")), maxMemorySpinner); + + // TODO: Do we keep this list centrally somewhere? Or is actively refreshing good? + JavaRuntime[] javaRuntimes = JavaRuntimeFinder.getAvailableRuntimes().toArray(new JavaRuntime[0]); + javaRuntimeBox.setModel(new DefaultComboBoxModel<>(javaRuntimes)); + + runtimePanel.addRow(enableCustomRuntime); + runtimePanel.addRow(new JLabel(SharedLocale.tr("options.jvmPath")), javaRuntimeBox); + runtimePanel.addRow(new JLabel(SharedLocale.tr("options.jvmArguments")), javaArgsBox); + + okButton.setMargin(new Insets(0, 10, 0, 10)); + buttonsPanel.addGlue(); + buttonsPanel.addElement(okButton); + buttonsPanel.addElement(cancelButton); + + enableMemorySettings.addActionListener(e -> { + if (enableMemorySettings.isSelected()) { + settings.setMemorySettings(new MemorySettings()); + } else { + settings.setMemorySettings(null); + } + + updateComponents(); + }); + + enableCustomRuntime.addActionListener(e -> { + runtimePanel.setEnabled(enableCustomRuntime.isSelected()); + }); + + okButton.addActionListener(e -> { + save(); + dispose(); + }); + + cancelButton.addActionListener(e -> dispose()); + + formsPanel.addElement(memorySettingsPanel); + formsPanel.addElement(runtimePanel); + + add(formsPanel, BorderLayout.NORTH); + add(buttonsPanel, BorderLayout.SOUTH); + + updateComponents(); + } + + private void updateComponents() { + if (settings.getMemorySettings() != null) { + memorySettingsPanel.setEnabled(true); + enableMemorySettings.setSelected(true); + + minMemorySpinner.setValue(settings.getMemorySettings().getMinMemory()); + maxMemorySpinner.setValue(settings.getMemorySettings().getMaxMemory()); + } else { + memorySettingsPanel.setEnabled(false); + enableMemorySettings.setSelected(false); + } + + if (settings.getRuntime() != null) { + runtimePanel.setEnabled(true); + enableCustomRuntime.setSelected(true); + } else { + runtimePanel.setEnabled(false); + enableCustomRuntime.setSelected(false); + } + + javaRuntimeBox.setSelectedItem(settings.getRuntime()); + javaArgsBox.setText(settings.getCustomJvmArgs()); + } + + private void save() { + if (enableMemorySettings.isSelected()) { + MemorySettings memorySettings = settings.getMemorySettings(); + + memorySettings.setMinMemory((int) minMemorySpinner.getValue()); + memorySettings.setMaxMemory((int) maxMemorySpinner.getValue()); + } else { + settings.setMemorySettings(null); + } + + if (enableCustomRuntime.isSelected()) { + settings.setRuntime((JavaRuntime) javaRuntimeBox.getSelectedItem()); + settings.setCustomJvmArgs(javaArgsBox.getText()); + } else { + settings.setRuntime(null); + settings.setCustomJvmArgs(null); + } + + saved = true; + } + + public static boolean open(Window parent, Instance instance) { + InstanceSettingsDialog dialog = new InstanceSettingsDialog(parent, instance.getSettings()); + dialog.setVisible(true); + + if (dialog.saved) { + Persistence.commitAndForget(instance); + } + + return dialog.saved; + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/dialog/LauncherFrame.java b/launcher/src/main/java/com/skcraft/launcher/dialog/LauncherFrame.java index 6fedd8c..2083da7 100644 --- a/launcher/src/main/java/com/skcraft/launcher/dialog/LauncherFrame.java +++ b/launcher/src/main/java/com/skcraft/launcher/dialog/LauncherFrame.java @@ -241,6 +241,12 @@ }); popup.add(menuItem); + menuItem = new JMenuItem(SharedLocale.tr("instance.openSettings")); + menuItem.addActionListener(e -> { + InstanceSettingsDialog.open(this, selected); + }); + popup.add(menuItem); + popup.addSeparator(); if (!selected.isUpdatePending()) { diff --git a/launcher/src/main/java/com/skcraft/launcher/launch/JavaRuntime.java b/launcher/src/main/java/com/skcraft/launcher/launch/JavaRuntime.java index f651e84..5dc7963 100644 --- a/launcher/src/main/java/com/skcraft/launcher/launch/JavaRuntime.java +++ b/launcher/src/main/java/com/skcraft/launcher/launch/JavaRuntime.java @@ -1,5 +1,9 @@ package com.skcraft.launcher.launch; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonValue; +import com.google.common.base.Objects; import lombok.Data; import java.io.File; @@ -9,11 +13,22 @@ private final File dir; private final String version; private final boolean is64Bit; - private boolean isMinecraftBundled = false; + private boolean isMinecraftBundled = false; // Used only in list sorting & not serialized. + @JsonValue + public File getDir() { + return dir; + } + + @JsonCreator + public static JavaRuntime fromDir(String dir) { + return JavaRuntimeFinder.getRuntimeFromPath(dir); + } + + @JsonIgnore public int getMajorVersion() { if (version == null) { - return 0; // + return 0; // uhh make this an error? } String[] parts = version.split("\\."); @@ -30,6 +45,19 @@ } @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + JavaRuntime that = (JavaRuntime) o; + return Objects.equal(dir, that.dir); + } + + @Override + public int hashCode() { + return Objects.hashCode(dir); + } + + @Override public int compareTo(JavaRuntime o) { if (isMinecraftBundled && !o.isMinecraftBundled) { return -1; diff --git a/launcher/src/main/java/com/skcraft/launcher/launch/JavaRuntimeFinder.java b/launcher/src/main/java/com/skcraft/launcher/launch/JavaRuntimeFinder.java index a81171c..e230595 100644 --- a/launcher/src/main/java/com/skcraft/launcher/launch/JavaRuntimeFinder.java +++ b/launcher/src/main/java/com/skcraft/launcher/launch/JavaRuntimeFinder.java @@ -45,7 +45,6 @@ getEntriesFromRegistry(entries, "SOFTWARE\\JavaSoft\\Java Development Kit"); } catch (Throwable ignored) { } - Collections.sort(entries); } else if (env.getPlatform() == Platform.LINUX) { launcherDir = new File(System.getenv("HOME"), ".minecraft"); @@ -68,40 +67,39 @@ return Collections.emptyList(); } - if (!launcherDir.isDirectory()) { - return entries; - } - File runtimes = new File(launcherDir, "runtime"); File[] runtimeList = runtimes.listFiles(); - if (runtimeList == null) { - return entries; - } + if (runtimeList != null) { + for (File potential : runtimeList) { + if (potential.getName().startsWith("jre-x")) { + boolean is64Bit = potential.getName().equals("jre-x64"); - for (File potential : runtimeList) { - if (potential.getName().startsWith("jre-x")) { - boolean is64Bit = potential.getName().equals("jre-x64"); + JavaRuntime runtime = new JavaRuntime(potential.getAbsoluteFile(), readVersionFromRelease(potential), is64Bit); + runtime.setMinecraftBundled(true); + entries.add(runtime); + } else { + String runtimeName = potential.getName(); - entries.add(new JavaRuntime(potential.getAbsoluteFile(), readVersionFromRelease(potential), is64Bit)); - } else { - String runtimeName = potential.getName(); + String[] children = potential.list(); + if (children == null || children.length == 0) continue; + String platformName = children[0]; - String[] children = potential.list(); - if (children == null || children.length == 0) continue; - String platformName = children[0]; + String[] parts = platformName.split("-"); + if (parts.length < 2) continue; - String[] parts = platformName.split("-"); - if (parts.length < 2) continue; + String arch = parts[1]; + boolean is64Bit = arch.equals("x64"); - String arch = parts[1]; - boolean is64Bit = arch.equals("x64"); + File javaDir = new File(potential, String.format("%s/%s", platformName, runtimeName)); + JavaRuntime runtime = new JavaRuntime(javaDir.getAbsoluteFile(), readVersionFromRelease(javaDir), is64Bit); + runtime.setMinecraftBundled(true); - File javaDir = new File(potential, String.format("%s/%s", platformName, runtimeName)); - - entries.add(new JavaRuntime(javaDir.getAbsoluteFile(), readVersionFromRelease(javaDir), is64Bit)); + entries.add(runtime); + } } } + Collections.sort(entries); return entries; } @@ -127,8 +125,18 @@ .findFirst(); } + public static Optional findAnyJavaRuntime() { + return getAvailableRuntimes().stream().sorted().findFirst(); + } + public static JavaRuntime getRuntimeFromPath(String path) { File target = new File(path); + + if (target.isFile()) { + // Probably referring directly to bin/java, back up two levels + target = target.getParentFile().getParentFile(); + } + { File jre = new File(target, "jre/release"); if (jre.isFile()) { diff --git a/launcher/src/main/java/com/skcraft/launcher/launch/Runner.java b/launcher/src/main/java/com/skcraft/launcher/launch/Runner.java index 7eebe22..241e7b8 100644 --- a/launcher/src/main/java/com/skcraft/launcher/launch/Runner.java +++ b/launcher/src/main/java/com/skcraft/launcher/launch/Runner.java @@ -212,12 +212,14 @@ * * @throws IOException on I/O error */ - private void addJvmArgs() throws IOException { - int minMemory = instance.getSettings().getMemorySettings() + private void addJvmArgs() throws IOException, LauncherException { + Optional memorySettings = Optional.ofNullable(instance.getSettings().getMemorySettings()); + + int minMemory = memorySettings .map(MemorySettings::getMinMemory) .orElse(config.getMinMemory()); - int maxMemory = instance.getSettings().getMemorySettings() + int maxMemory = memorySettings .map(MemorySettings::getMaxMemory) .orElse(config.getMaxMemory()); @@ -247,14 +249,18 @@ builder.setMaxMemory(maxMemory); builder.setPermGen(permGen); - JavaRuntime selectedRuntime = instance.getSettings().getRuntime() + JavaRuntime selectedRuntime = Optional.ofNullable(instance.getSettings().getRuntime()) .orElseGet(() -> Optional.ofNullable(versionManifest.getJavaVersion()) .flatMap(JavaRuntimeFinder::findBestJavaRuntime) .orElse(config.getJavaRuntime()) ); - String rawJvmPath = selectedRuntime.getDir().getAbsolutePath(); - if (!Strings.isNullOrEmpty(rawJvmPath)) { - builder.tryJvmPath(new File(rawJvmPath)); + + // Builder defaults to a found runtime or just the PATH `java` otherwise + if (selectedRuntime != null) { + String rawJvmPath = selectedRuntime.getDir().getAbsolutePath(); + if (!Strings.isNullOrEmpty(rawJvmPath)) { + builder.tryJvmPath(new File(rawJvmPath)); + } } List flags = builder.getFlags(); diff --git a/launcher/src/main/resources/com/skcraft/launcher/lang/Launcher.properties b/launcher/src/main/resources/com/skcraft/launcher/lang/Launcher.properties index 9a09ec1..d8699d4 100644 --- a/launcher/src/main/resources/com/skcraft/launcher/lang/Launcher.properties +++ b/launcher/src/main/resources/com/skcraft/launcher/lang/Launcher.properties @@ -22,6 +22,7 @@ button.cancel=Cancel button.ok=OK +button.save=Save options.title = Options options.useProxyCheck = Use following proxy in Minecraft @@ -52,6 +53,7 @@ instance.openResourcePacks=View resource packs instance.openScreenshots=View screenshots instance.copyAsPath=Copy as path +instance.openSettings=Settings... instance.forceUpdate=Force update instance.hardForceUpdate=Hard force update... instance.deleteFiles=Delete files... @@ -62,6 +64,10 @@ instance.resettingTitle=Resetting instance... instance.resettingStatus=Resetting ''{0}''... +instance.options.title=Instance Settings +instance.options.customJava=Use a custom Java runtime +instance.options.customMemory=Use custom memory settings + launcher.launch=Launch... launcher.checkForUpdates=Check for updates launcher.options=Options...