diff --git a/README.md b/README.md index 4fbe9a25..16719fcb 100644 --- a/README.md +++ b/README.md @@ -115,9 +115,9 @@ Furthermore, you also need the platform specific toolchains to be installed: You can use the gradle wrapper to build and run the project: - `gradlew app:run` will run the desktop application. You can set various useful properties in `app/build.gradle` -- `gradlew builtCli` will create a native image for the CLI application +- `gradlew buildCli` will create a native image for the CLI application - `gradlew dist` will create a distributable production version in `dist/build/dist/base`. - To include this CLI executable in this build, make sure to run `gradlew builtCli` first + To include this CLI executable in this build, make sure to run `gradlew buildCli` first - You can also run the CLI application in development mode with something like `gradlew :cli:clean :cli:run --args="daemon start"`. Note here that you should always clean the CLI project first, as the native image plugin is a little buggy in that regard. - `gradlew :test` will run the tests of the specified project. diff --git a/app/src/main/java/io/xpipe/app/editor/EditorState.java b/app/src/main/java/io/xpipe/app/editor/EditorState.java index 8fe275eb..26986d0f 100644 --- a/app/src/main/java/io/xpipe/app/editor/EditorState.java +++ b/app/src/main/java/io/xpipe/app/editor/EditorState.java @@ -181,7 +181,7 @@ public class EditorState { public void openInEditor(String file) { var editor = AppPrefs.get().externalEditor().getValue(); - if (editor == null || !editor.isSupported()) { + if (editor == null || !editor.isSelectable()) { return; } diff --git a/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java b/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java index 3ceabf96..586bb4b5 100644 --- a/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java +++ b/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java @@ -61,9 +61,9 @@ public class AppPrefs { private final ObjectProperty languageInternal = typed(new SimpleObjectProperty<>(SupportedLocale.ENGLISH), SupportedLocale.class); public final Property language = new SimpleObjectProperty<>(SupportedLocale.ENGLISH); - private final SingleSelectionField languageControl = - Field.ofSingleSelectionType(languageList, languageInternal).render(() -> new TranslatableComboBoxControl<>()); - + private final SingleSelectionField languageControl = Field.ofSingleSelectionType( + languageList, languageInternal) + .render(() -> new TranslatableComboBoxControl<>()); private final ObjectProperty themeInternal = typed(new SimpleObjectProperty<>(AppStyle.Theme.LIGHT), AppStyle.Theme.class); @@ -85,32 +85,36 @@ public class AppPrefs { private final ObjectProperty closeBehaviour = typed(new SimpleObjectProperty<>(CloseBehaviour.QUIT), CloseBehaviour.class); - private final SingleSelectionField closeBehaviourControl = - Field.ofSingleSelectionType(closeBehaviourList, closeBehaviour).render(() -> new TranslatableComboBoxControl<>()); - private final ObjectProperty externalEditor = - typed(new SimpleObjectProperty<>(ExternalEditorType.getDefault()), ExternalEditorType.class); - private final SingleSelectionField externalEditorControl = - Field.ofSingleSelectionType(externalEditorList, externalEditor).render(() -> new TranslatableComboBoxControl<>()); + private final SingleSelectionField closeBehaviourControl = Field.ofSingleSelectionType( + closeBehaviourList, closeBehaviour) + .render(() -> new TranslatableComboBoxControl<>()); // External editor // =============== - private final StringProperty customEditorCommand = typed(new SimpleStringProperty(""), String.class); + + final ObjectProperty externalEditor = + typed(new SimpleObjectProperty<>(), ExternalEditorType.class); + private final SingleSelectionField externalEditorControl = Field.ofSingleSelectionType( + externalEditorList, externalEditor) + .render(() -> new TranslatableComboBoxControl<>()); + + final StringProperty customEditorCommand = typed(new SimpleStringProperty(""), String.class); private final StringField customEditorCommandControl = editable( StringField.ofStringType(customEditorCommand).render(() -> new SimpleTextControl()), externalEditor.isEqualTo(ExternalEditorType.CUSTOM)); private final IntegerProperty editorReloadTimeout = typed(new SimpleIntegerProperty(1000), Integer.class); private final ObjectProperty externalStartupBehaviour = typed( new SimpleObjectProperty<>( - ExternalStartupBehaviour.TRAY.isSupported() + ExternalStartupBehaviour.TRAY.isSelectable() ? ExternalStartupBehaviour.TRAY : ExternalStartupBehaviour.BACKGROUND), ExternalStartupBehaviour.class); - - private final SingleSelectionField externalStartupBehaviourControl = Field.ofSingleSelectionType(externalStartupBehaviourList, externalStartupBehaviour) .render(() -> new TranslatableComboBoxControl<>()); + // Automatically update + // ==================== private final BooleanProperty automaticallyUpdate = typed(new SimpleBooleanProperty(AppDistributionType.get().supportsUpdate()), Boolean.class); private final BooleanField automaticallyUpdateField = BooleanField.ofBooleanType(automaticallyUpdate) @@ -135,8 +139,8 @@ public class AppPrefs { private final ObjectProperty internalLogLevel = typed(new SimpleObjectProperty<>(DEFAULT_LOG_LEVEL), String.class); - // Automatically update - // ==================== + // Log level + // ========= private final ObjectProperty effectiveLogLevel = LOG_LEVEL_FIXED ? new SimpleObjectProperty<>(System.getProperty(LOG_LEVEL_PROP).toLowerCase()) : internalLogLevel; @@ -144,6 +148,8 @@ public class AppPrefs { logLevelList, effectiveLogLevel) .editable(!LOG_LEVEL_FIXED) .render(() -> new SimpleComboBoxControl<>()); + // Developer mode + // ============== private final BooleanProperty internalDeveloperMode = typed(new SimpleBooleanProperty(false), Boolean.class); private final BooleanProperty effectiveDeveloperMode = System.getProperty(DEVELOPER_MODE_PROP) != null ? new SimpleBooleanProperty(Boolean.parseBoolean(System.getProperty(DEVELOPER_MODE_PROP))) @@ -176,6 +182,70 @@ public class AppPrefs { developerDisableConnectorInstallationVersionCheck) .render(() -> new ToggleControl()); + public ReadOnlyProperty closeBehaviour() { + return closeBehaviour; + } + + public ReadOnlyProperty externalEditor() { + return externalEditor; + } + + public ObservableValue customEditorCommand() { + return customEditorCommand; + } + + public final ReadOnlyIntegerProperty editorReloadTimeout() { + return editorReloadTimeout; + } + + public ReadOnlyProperty externalStartupBehaviour() { + return externalStartupBehaviour; + } + + public ReadOnlyBooleanProperty automaticallyUpdate() { + return automaticallyUpdate; + } + + public ReadOnlyBooleanProperty updateToPrereleases() { + return updateToPrereleases; + } + + public ReadOnlyBooleanProperty confirmDeletions() { + return confirmDeletions; + } + + public ObservableValue storageDirectory() { + return effectiveStorageDirectory; + } + + public ReadOnlyProperty logLevel() { + return effectiveLogLevel; + } + + public ObservableValue developerMode() { + return effectiveDeveloperMode; + } + + public ReadOnlyBooleanProperty developerDisableUpdateVersionCheck() { + return developerDisableUpdateVersionCheck; + } + + public ReadOnlyBooleanProperty developerDisableGuiRestrictions() { + return developerDisableGuiRestrictions; + } + + public ReadOnlyBooleanProperty developerDisableConnectorInstallationVersionCheck() { + return developerDisableConnectorInstallationVersionCheck; + } + + public ReadOnlyBooleanProperty developerShowHiddenProviders() { + return developerShowHiddenProviders; + } + + public ReadOnlyBooleanProperty developerShowHiddenEntries() { + return developerShowHiddenEntries; + } + private AppPreferencesFx preferencesFx; private boolean controlsSetup; @@ -194,6 +264,8 @@ public class AppPrefs { public static void init() { INSTANCE = new AppPrefs(); INSTANCE.preferencesFx.loadSettings(); + INSTANCE.initValues(); + PrefsProvider.getAll().forEach(prov -> prov.init()); } public static AppPrefs get() { @@ -246,8 +318,11 @@ public class AppPrefs { save(); } - // Log level - // ========= + public void initValues() { + if (externalEditor.get() == null) { + ExternalEditorType.detectDefault(); + } + } public void save() { preferencesFx.saveSettings(); @@ -257,76 +332,6 @@ public class AppPrefs { preferencesFx.discardChanges(); } - public ReadOnlyProperty closeBehaviour() { - return closeBehaviour; - } - - public ReadOnlyProperty externalEditor() { - return externalEditor; - } - - public ObservableValue customEditorCommand() { - return customEditorCommand; - } - - public final ReadOnlyIntegerProperty editorReloadTimeout() { - return editorReloadTimeout; - } - - public ReadOnlyProperty externalStartupBehaviour() { - return externalStartupBehaviour; - } - - public ReadOnlyBooleanProperty automaticallyUpdate() { - return automaticallyUpdate; - } - - // Developer mode - // ============== - - public ReadOnlyBooleanProperty updateToPrereleases() { - return updateToPrereleases; - } - - public ReadOnlyBooleanProperty confirmDeletions() { - return confirmDeletions; - } - - public ObservableValue storageDirectory() { - return effectiveStorageDirectory; - } - - // Developer options - // ==================== - - public ReadOnlyProperty logLevel() { - return effectiveLogLevel; - } - - public ObservableValue developerMode() { - return effectiveDeveloperMode; - } - - public ReadOnlyBooleanProperty developerDisableUpdateVersionCheck() { - return developerDisableUpdateVersionCheck; - } - - public ReadOnlyBooleanProperty developerDisableGuiRestrictions() { - return developerDisableGuiRestrictions; - } - - public ReadOnlyBooleanProperty developerDisableConnectorInstallationVersionCheck() { - return developerDisableConnectorInstallationVersionCheck; - } - - public ReadOnlyBooleanProperty developerShowHiddenProviders() { - return developerShowHiddenProviders; - } - - public ReadOnlyBooleanProperty developerShowHiddenEntries() { - return developerShowHiddenEntries; - } - public Class getSettingType(String breadcrumb) { var found = classMap.get(getSetting(breadcrumb).valueProperty()); if (found == null) { @@ -391,15 +396,15 @@ public class AppPrefs { Setting.of("useSystemFont", useSystemFontInternal), Setting.of("tooltipDelay", tooltipDelayInternal, tooltipDelayMin, tooltipDelayMax), Setting.of("fontSize", fontSizeInternal, fontSizeMin, fontSizeMax)), - Group.of( - "windowOptions", - Setting.of("saveWindowLocation", saveWindowLocationInternal))), + Group.of("windowOptions", Setting.of("saveWindowLocation", saveWindowLocationInternal))), Category.of( "integrations", Group.of( "editor", Setting.of("defaultProgram", externalEditorControl, externalEditor), - Setting.of("customEditorCommand", customEditorCommandControl, customEditorCommand).applyVisibility( VisibilityProperty.of(externalEditor.isEqualTo(ExternalEditorType.CUSTOM))), + Setting.of("customEditorCommand", customEditorCommandControl, customEditorCommand) + .applyVisibility(VisibilityProperty.of( + externalEditor.isEqualTo(ExternalEditorType.CUSTOM))), Setting.of( "editorReloadTimeout", editorReloadTimeout, diff --git a/app/src/main/java/io/xpipe/app/prefs/CloseBehaviour.java b/app/src/main/java/io/xpipe/app/prefs/CloseBehaviour.java index 0d18ac8c..1b0d6de2 100644 --- a/app/src/main/java/io/xpipe/app/prefs/CloseBehaviour.java +++ b/app/src/main/java/io/xpipe/app/prefs/CloseBehaviour.java @@ -32,7 +32,7 @@ public enum CloseBehaviour implements PrefsChoiceValue { this.exit = exit; } - public boolean isSupported() { + public boolean isSelectable() { return true; } } diff --git a/app/src/main/java/io/xpipe/app/prefs/ExternalEditorType.java b/app/src/main/java/io/xpipe/app/prefs/ExternalEditorType.java index 88107345..4e672864 100644 --- a/app/src/main/java/io/xpipe/app/prefs/ExternalEditorType.java +++ b/app/src/main/java/io/xpipe/app/prefs/ExternalEditorType.java @@ -2,7 +2,6 @@ package io.xpipe.app.prefs; import io.xpipe.core.process.OsType; import io.xpipe.core.process.ShellTypes; -import io.xpipe.core.store.ShellStore; import io.xpipe.extension.prefs.PrefsChoiceValue; import io.xpipe.extension.util.ApplicationHelper; import io.xpipe.extension.util.WindowsRegistry; @@ -11,7 +10,9 @@ import lombok.Getter; import org.apache.commons.lang3.SystemUtils; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -20,13 +21,24 @@ import java.util.Optional; public abstract class ExternalEditorType implements PrefsChoiceValue { public static final ExternalEditorType NOTEPAD = new WindowsFullPathType("app.notepad") { - @Override protected Optional determinePath() { return Optional.of(Path.of(System.getenv("SystemRoot") + "\\System32\\notepad.exe")); } }; - public static final ExternalEditorType NOTEPADPLUSPLUS_WINDOWS = new WindowsFullPathType("app.notepad++Windows") { + + public static final ExternalEditorType VSCODE = new WindowsFullPathType("app.vscode") { + + @Override + protected Optional determinePath() { + return Optional.of(Path.of(System.getenv("LOCALAPPDATA")) + .resolve("Programs") + .resolve("Microsoft VS Code") + .resolve("bin") + .resolve("code.cmd")); + } + }; + public static final ExternalEditorType NOTEPADPLUSPLUS_WINDOWS = new WindowsFullPathType("app.notepad++") { @Override protected Optional determinePath() { @@ -40,66 +52,80 @@ public abstract class ExternalEditorType implements PrefsChoiceValue { return launcherDir.map(Path::of); } }; - public static final ExternalEditorType NOTEPADPLUSPLUS_LINUX = - new LinuxPathType("app.notepad++Linux", "notepad++") {}; - public static final ExternalEditorType KATE = new LinuxPathType("app.kate", "kate") {}; + public static final PathType NOTEPADPLUSPLUS_LINUX = new PathType("app.notepad++", "notepad++"); + + public static final PathType VSCODE_LINUX = new PathType("app.vscode", "code"); + + public static final PathType KATE = new PathType("app.kate", "kate"); + + public static final PathType GEDIT = new PathType("app.gedit", "gedit"); + + public static final PathType LEAFPAD = new PathType("app.leafpad", "leafpad"); + + public static final PathType MOUSEPAD = new PathType("app.mousepad", "mousepad"); + + public static final PathType PLUMA = new PathType("app.pluma", "pluma"); + + public static final ExternalEditorType TEXT_EDIT = new MacOsFullPathType("app.textEdit") { + @Override + protected Path determinePath() { + return Path.of("/Applications/TextEdit.app"); + } + }; + + public static final ExternalEditorType NOTEPADPP_MACOS = new MacOsFullPathType("app.notepad++") { + @Override + protected Path determinePath() { + return Path.of("/Applications/TextEdit.app"); + } + }; + + public static final ExternalEditorType SUBLIME_MACOS = new MacOsFullPathType("app.sublime") { + @Override + protected Path determinePath() { + return Path.of("/Applications/Sublime.app"); + } + }; + + public static final ExternalEditorType VSCODE_MACOS = new MacOsFullPathType("app.vscode") { + @Override + protected Path determinePath() { + return Path.of("/Applications/VSCode.app"); + } + }; public static final ExternalEditorType CUSTOM = new ExternalEditorType("app.custom") { @Override - public void launch(Path file) throws IOException { - var fileName = SystemUtils.IS_OS_WINDOWS ? " \"" + file + "\"" : file; - var cmd = AppPrefs.get().customEditorCommand().getValue(); - var fullCmd = cmd + " " + fileName; - Runtime.getRuntime() - .exec(ShellTypes.getPlatformDefault() - .executeCommandListWithShell(fullCmd) - .toArray(String[]::new)); + public void launch(Path file) throws Exception { + var customCommand = AppPrefs.get().customEditorCommand().getValue(); + if (customCommand == null || customCommand.trim().isEmpty()) { + return; + } + + var format = customCommand.contains("$file") ? customCommand : customCommand + " $file"; + var fileString = file.toString().contains(" ") ? "\"" + file + "\"" : file.toString(); + ApplicationHelper.executeLocalApplication(format.replace("$file",fileString)); } @Override - public boolean isSupported() { + public boolean isSelectable() { return true; } }; - public static final ExternalEditorType TEXT_EDIT = new ExternalEditorType("app.textEdit") { - - @Override - public void launch(Path file) throws Exception { - var fullCmd = "/Applications/TextEdit.app/Contents/MacOS/TextEdit \"" + file.toString() + "\""; - ShellStore.withLocal(pc -> { - pc.executeSimpleCommand(fullCmd); - }); - } - - @Override - public boolean isSupported() { - return OsType.getLocal().equals(OsType.MAC); - } - }; - public static final List ALL = - List.of(NOTEPAD, NOTEPADPLUSPLUS_WINDOWS, NOTEPADPLUSPLUS_LINUX, KATE, TEXT_EDIT, CUSTOM); private String id; - public static ExternalEditorType getDefault() { - if (OsType.getLocal().equals(OsType.MAC)) { - return TEXT_EDIT; - } - - return OsType.getLocal().equals(OsType.WINDOWS) ? NOTEPAD : KATE; - } - public abstract void launch(Path file) throws Exception; - public abstract boolean isSupported(); + public abstract boolean isSelectable(); - public abstract static class LinuxPathType extends ExternalEditorType { + public static class PathType extends ExternalEditorType { private final String command; - public LinuxPathType(String id, String command) { + public PathType(String id, String command) { super(id); this.command = command; } @@ -111,7 +137,7 @@ public abstract class ExternalEditorType implements PrefsChoiceValue { } @Override - public boolean isSupported() { + public boolean isSelectable() { return OsType.getLocal().equals(OsType.LINUX); } } @@ -131,17 +157,103 @@ public abstract class ExternalEditorType implements PrefsChoiceValue { throw new IOException("Unable to find installation of " + getId()); } - ApplicationHelper.executeLocalApplication(getCommand(path.get(), file)); - } - - protected String getCommand(Path p, Path file) { - var cmd = "\"" + p + "\""; - return "start \"\" " + cmd + " \"" + file + "\""; + ApplicationHelper.executeLocalApplication(List.of(path.get().toString(), file.toString())); } @Override - public boolean isSupported() { + public boolean isSelectable() { return OsType.getLocal().equals(OsType.WINDOWS); } + + @Override + public boolean isAvailable() { + var path = determinePath(); + return path.isPresent() && Files.exists(path.get()); + } + } + + public abstract static class MacOsFullPathType extends ExternalEditorType { + + public MacOsFullPathType(String id) { + super(id); + } + + protected abstract Path determinePath(); + + @Override + public void launch(Path file) throws Exception { + var path = determinePath(); + ApplicationHelper.executeLocalApplication(List.of("open", path.toString(), file.toString())); + } + + @Override + public boolean isSelectable() { + return OsType.getLocal().equals(OsType.MAC); + } + + @Override + public boolean isAvailable() { + var path = determinePath(); + return Files.exists(path); + } + } + + public static final List WINDOWS_EDITORS = List.of(VSCODE, NOTEPADPLUSPLUS_WINDOWS, NOTEPAD); + public static final List LINUX_EDITORS = + List.of(VSCODE_LINUX, NOTEPADPLUSPLUS_LINUX, KATE, GEDIT, PLUMA, LEAFPAD, MOUSEPAD); + public static final List MACOS_EDITORS = + List.of(VSCODE_MACOS, SUBLIME_MACOS, NOTEPADPP_MACOS, TEXT_EDIT); + + public static final List ALL = new ArrayList<>(); + static { + if (OsType.getLocal().equals(OsType.WINDOWS)) { + ALL.addAll(WINDOWS_EDITORS); + } + if (OsType.getLocal().equals(OsType.LINUX)) { + ALL.addAll(LINUX_EDITORS); + } + if (OsType.getLocal().equals(OsType.MAC)) { + ALL.addAll(MACOS_EDITORS); + } + ALL.add(CUSTOM); + } + + public static void detectDefault() { + var typeProperty = AppPrefs.get().externalEditor; + var customProperty = AppPrefs.get().customEditorCommand; + if (OsType.getLocal().equals(OsType.WINDOWS)) { + typeProperty.set(WINDOWS_EDITORS.stream() + .filter(externalEditorType -> externalEditorType.isAvailable()) + .findFirst() + .orElse(null)); + } + + if (OsType.getLocal().equals(OsType.LINUX)) { + var env = System.getenv("VISUAL"); + if (env != null) { + var found = LINUX_EDITORS.stream() + .filter(externalEditorType -> externalEditorType.command.equalsIgnoreCase(env)) + .findFirst() + .orElse(null); + if (found == null) { + typeProperty.set(CUSTOM); + customProperty.set(env); + } else { + typeProperty.set(found); + } + } else { + typeProperty.set(LINUX_EDITORS.stream() + .filter(externalEditorType -> externalEditorType.isAvailable()) + .findFirst() + .orElse(null)); + } + } + + if (OsType.getLocal().equals(OsType.MAC)) { + typeProperty.set(MACOS_EDITORS.stream() + .filter(externalEditorType -> externalEditorType.isAvailable()) + .findFirst() + .orElse(null)); + } } } diff --git a/app/src/main/java/io/xpipe/app/prefs/ExternalStartupBehaviour.java b/app/src/main/java/io/xpipe/app/prefs/ExternalStartupBehaviour.java index b276c3fd..92c150fa 100644 --- a/app/src/main/java/io/xpipe/app/prefs/ExternalStartupBehaviour.java +++ b/app/src/main/java/io/xpipe/app/prefs/ExternalStartupBehaviour.java @@ -15,7 +15,7 @@ public enum ExternalStartupBehaviour implements PrefsChoiceValue { private final String id; private final OperationMode mode; - public boolean isSupported() { + public boolean isSelectable() { return true; } } diff --git a/core/src/main/java/io/xpipe/core/process/ShellType.java b/core/src/main/java/io/xpipe/core/process/ShellType.java index 0a355975..5de37ffc 100644 --- a/core/src/main/java/io/xpipe/core/process/ShellType.java +++ b/core/src/main/java/io/xpipe/core/process/ShellType.java @@ -78,6 +78,8 @@ public interface ShellType { List executeCommandListWithShell(String cmd); + List executeCommandListWithShell(List cmd); + List getMkdirsCommand(String dirs); String getFileReadCommand(String file); diff --git a/core/src/main/java/io/xpipe/core/process/ShellTypes.java b/core/src/main/java/io/xpipe/core/process/ShellTypes.java index 0f7d4fc6..3668f535 100644 --- a/core/src/main/java/io/xpipe/core/process/ShellTypes.java +++ b/core/src/main/java/io/xpipe/core/process/ShellTypes.java @@ -141,6 +141,14 @@ public class ShellTypes { return List.of("cmd", "/C", cmd.replaceAll("[\\^]", "^$0")); } + @Override + public List executeCommandListWithShell(List cmd) { + var list = new ArrayList(); + list.addAll(List.of("cmd", "/C")); + list.addAll(cmd.stream().map(s -> s.replaceAll(" ", "^ ")).toList()); + return list; + } + @Override public List getMkdirsCommand(String dirs) { return List.of("(", "if", "not", "exist", dirs, "mkdir", dirs, ")"); @@ -230,6 +238,11 @@ public class ShellTypes { @Value public static class PowerShell implements ShellType { + @Override + public List executeCommandListWithShell(List cmd) { + return List.of("powershell", "-Command", flatten(cmd)); + } + @Override public void disableHistory(ShellProcessControl pc) throws Exception { pc.executeLine("Set-PSReadLineOption -HistorySaveStyle SaveNothing"); @@ -416,6 +429,11 @@ public class ShellTypes { public abstract static class PosixBase implements ShellType { + @Override + public List executeCommandListWithShell(List cmd) { + return List.of(getExecutable(), "-c", flatten(cmd)); + } + @Override public String getInitFileOpenCommand(String file) { return getName() + " --rcfile \"" + file + "\""; diff --git a/ext/proc/src/main/java/io/xpipe/ext/proc/ProcPrefs.java b/ext/proc/src/main/java/io/xpipe/ext/proc/ProcPrefs.java index aa3024ec..f6ca00b0 100644 --- a/ext/proc/src/main/java/io/xpipe/ext/proc/ProcPrefs.java +++ b/ext/proc/src/main/java/io/xpipe/ext/proc/ProcPrefs.java @@ -25,7 +25,7 @@ public class ProcPrefs extends PrefsProvider { return enableCaching; } - private final ObjectProperty terminalType = new SimpleObjectProperty<>(TerminalType.getDefault()); + private final ObjectProperty terminalType = new SimpleObjectProperty<>(); private final SimpleListProperty terminalTypeList = new SimpleListProperty<>( FXCollections.observableArrayList(PrefsChoiceValue.getSupported(TerminalType.class))); private final SingleSelectionField terminalTypeControl = Field.ofSingleSelectionType( @@ -61,4 +61,11 @@ public class ProcPrefs extends PrefsProvider { .applyVisibility(VisibilityProperty.of(terminalType.isEqualTo(TerminalType.CUSTOM))), String.class); } + + @Override + public void init() { + if (terminalType.get() == null) { + terminalType.set(TerminalType.getDefault()); + } + } } diff --git a/ext/proc/src/main/java/io/xpipe/ext/proc/TerminalType.java b/ext/proc/src/main/java/io/xpipe/ext/proc/TerminalType.java index 65b62787..b131f817 100644 --- a/ext/proc/src/main/java/io/xpipe/ext/proc/TerminalType.java +++ b/ext/proc/src/main/java/io/xpipe/ext/proc/TerminalType.java @@ -10,7 +10,6 @@ import io.xpipe.extension.util.ApplicationHelper; import lombok.AllArgsConstructor; import lombok.Getter; -import java.util.ArrayList; import java.util.List; @Getter @@ -25,7 +24,7 @@ public abstract class TerminalType implements PrefsChoiceValue { } @Override - public boolean isSupported() { + public boolean isSelectable() { return OsType.getLocal().equals(OsType.WINDOWS); } }; @@ -38,8 +37,8 @@ public abstract class TerminalType implements PrefsChoiceValue { } @Override - public boolean isSupported() { - return true; + public boolean isSelectable() { + return OsType.getLocal().equals(OsType.WINDOWS); } }; @@ -52,7 +51,7 @@ public abstract class TerminalType implements PrefsChoiceValue { } @Override - public boolean isSupported() { + public boolean isSelectable() { return OsType.getLocal().equals(OsType.WINDOWS); } }; @@ -66,7 +65,7 @@ public abstract class TerminalType implements PrefsChoiceValue { } @Override - public boolean isSupported() { + public boolean isSelectable() { return OsType.getLocal().equals(OsType.LINUX); } }; @@ -79,7 +78,7 @@ public abstract class TerminalType implements PrefsChoiceValue { } @Override - public boolean isSupported() { + public boolean isSelectable() { return OsType.getLocal().equals(OsType.LINUX); } }; @@ -92,27 +91,31 @@ public abstract class TerminalType implements PrefsChoiceValue { } @Override - public boolean isSupported() { + public boolean isSelectable() { return OsType.getLocal().equals(OsType.LINUX); } }; - public static final TerminalType MAC_TERMINAL = new MacType("proc.mac_terminal", "Terminal") {}; + public static final TerminalType MACOS_TERMINAL = new MacType("proc.macosTerminal", "Terminal"); + + public static final TerminalType ITERM2 = new MacType("proc.iterm2", "iTerm2"); + public static final TerminalType CUSTOM = new TerminalType("app.custom") { @Override public void launch(String name, String command) throws Exception { var custom = PrefsProvider.get(ProcPrefs.class).customTerminalCommand().getValue(); - if (custom == null || custom.isEmpty()) { + if (custom == null || custom.trim().isEmpty()) { return; } - ShellStore.local().create().executeSimpleCommand(custom + " " + command); + var format = custom.contains("$cmd") ? custom : custom + " $cmd"; + ShellStore.local().create().executeSimpleCommand(format.replace("$cmd", command)); } @Override - public boolean isSupported() { + public boolean isSelectable() { return true; } @@ -121,27 +124,26 @@ public abstract class TerminalType implements PrefsChoiceValue { return false; } }; - public static final List ALL = - List.of(CMD, POWERSHELL, WINDOWS_TERMINAL, GNOME_TERMINAL, KONSOLE, XFCE, MAC_TERMINAL, CUSTOM); - private String id; + + public static final List ALL = List.of( + WINDOWS_TERMINAL, POWERSHELL, CMD, + KONSOLE, XFCE, GNOME_TERMINAL, + ITERM2, MACOS_TERMINAL, + CUSTOM) + .stream() + .filter(terminalType -> terminalType.isSelectable()) + .toList(); public static TerminalType getDefault() { - if (OsType.getLocal().equals(OsType.WINDOWS)) { - return CMD; - } else { - var toChooseFrom = new ArrayList<>(ALL); - toChooseFrom.remove(CMD); - toChooseFrom.remove(POWERSHELL); - return toChooseFrom.stream() - .filter(terminalType -> terminalType.isAvailable()) - .findFirst() - .orElse(TerminalType.GNOME_TERMINAL); - } + return ALL.stream() + .filter(terminalType -> terminalType.isAvailable()).findFirst().orElse(null); } + private String id; + public abstract void launch(String name, String command) throws Exception; - public abstract boolean isSupported(); + public abstract boolean isSelectable(); public abstract boolean isAvailable(); @@ -164,7 +166,7 @@ public abstract class TerminalType implements PrefsChoiceValue { } @Override - public boolean isSupported() { + public boolean isSelectable() { return OsType.getLocal().equals(OsType.MAC); } @@ -212,7 +214,7 @@ public abstract class TerminalType implements PrefsChoiceValue { } @Override - public boolean isSupported() { + public boolean isSelectable() { return true; } } diff --git a/extension/src/main/java/io/xpipe/extension/prefs/PrefsChoiceValue.java b/extension/src/main/java/io/xpipe/extension/prefs/PrefsChoiceValue.java index d1625a72..71d38580 100644 --- a/extension/src/main/java/io/xpipe/extension/prefs/PrefsChoiceValue.java +++ b/extension/src/main/java/io/xpipe/extension/prefs/PrefsChoiceValue.java @@ -39,11 +39,15 @@ public interface PrefsChoiceValue extends Translatable { throw new AssertionError(); } - return all.stream().filter(t -> ((PrefsChoiceValue) t).isSupported()).toList(); + return all.stream().filter(t -> ((PrefsChoiceValue) t).isSelectable()).toList(); } } - default boolean isSupported() { + default boolean isAvailable() { + return true; + } + + default boolean isSelectable() { return true; } diff --git a/extension/src/main/java/io/xpipe/extension/prefs/PrefsProvider.java b/extension/src/main/java/io/xpipe/extension/prefs/PrefsProvider.java index 6eb83546..d235e794 100644 --- a/extension/src/main/java/io/xpipe/extension/prefs/PrefsProvider.java +++ b/extension/src/main/java/io/xpipe/extension/prefs/PrefsProvider.java @@ -34,4 +34,6 @@ public abstract class PrefsProvider { } public abstract void addPrefs(PrefsHandler handler); + + public abstract void init(); } diff --git a/extension/src/main/java/io/xpipe/extension/util/ApplicationHelper.java b/extension/src/main/java/io/xpipe/extension/util/ApplicationHelper.java index 35338835..51b5eb0a 100644 --- a/extension/src/main/java/io/xpipe/extension/util/ApplicationHelper.java +++ b/extension/src/main/java/io/xpipe/extension/util/ApplicationHelper.java @@ -2,18 +2,27 @@ package io.xpipe.extension.util; import io.xpipe.core.process.ShellProcessControl; import io.xpipe.core.process.ShellTypes; +import io.xpipe.core.store.ShellStore; +import io.xpipe.extension.event.TrackEvent; import java.io.IOException; -import java.nio.charset.StandardCharsets; +import java.util.List; public class ApplicationHelper { public static void executeLocalApplication(String s) throws Exception { var args = ShellTypes.getPlatformDefault().executeCommandListWithShell(s); - var p = new ProcessBuilder(args).redirectOutput(ProcessBuilder.Redirect.DISCARD).start(); - var error = new String(p.getErrorStream().readAllBytes(), StandardCharsets.UTF_8); - if (p.waitFor() != 0) { - throw new IOException(error); + TrackEvent.withDebug("proc", "Executing local application").elements(args).handle(); + try (var c = ShellStore.local().create().command(s).start()) { + c.discardOrThrow(); + } + } + + public static void executeLocalApplication(List s) throws Exception { + var args = ShellTypes.getPlatformDefault().executeCommandListWithShell(s); + TrackEvent.withDebug("proc", "Executing local application").elements(args).handle(); + try (var c = ShellStore.local().create().command(s).start()) { + c.discardOrThrow(); } }