Rework terminal integrations

This commit is contained in:
crschnick 2024-04-02 03:41:08 +00:00
parent 3293f27e6f
commit 91e1a37cdb
9 changed files with 707 additions and 347 deletions

View file

@ -137,10 +137,10 @@ public class AppPrefs {
new AboutCategory(),
new SystemCategory(),
new AppearanceCategory(),
new SyncCategory(),
new VaultCategory(),
new TerminalCategory(),
new EditorCategory(),
new SyncCategory(),
new VaultCategory(),
new LocalShellCategory(),
new SecurityCategory(),
new PasswordManagerCategory(),

View file

@ -5,9 +5,12 @@ import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.PrefsChoiceValue;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.impl.ChoiceComp;
import io.xpipe.app.fxcomps.impl.HorizontalComp;
import io.xpipe.app.fxcomps.impl.StackComp;
import io.xpipe.app.fxcomps.impl.TextFieldComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.terminal.ExternalTerminalType;
import io.xpipe.app.util.Hyperlinks;
import io.xpipe.app.util.OptionsBuilder;
import io.xpipe.app.util.TerminalLauncher;
import io.xpipe.app.util.ThreadHelper;
@ -15,6 +18,8 @@ import io.xpipe.core.store.LocalStore;
import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.ListCell;
import javafx.scene.paint.Color;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.List;
@ -26,6 +31,57 @@ public class TerminalCategory extends AppPrefsCategory {
return "terminal";
}
private Comp<?> terminalChoice() {
var prefs = AppPrefs.get();
var c = ChoiceComp.ofTranslatable(
prefs.terminalType, PrefsChoiceValue.getSupported(ExternalTerminalType.class), false);
c.apply(struc -> {
struc.get().setCellFactory(param -> {
return new ListCell<>() {
@Override
protected void updateItem(ExternalTerminalType item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
return;
}
setText(item.toTranslatedString().getValue());
if (item != ExternalTerminalType.CUSTOM) {
var graphic = new FontIcon(item.isRecommended() ? "mdi2c-check-decagram" : "mdi2a-alert-circle-check");
graphic.setFill(item.isRecommended() ? Color.GREEN : Color.ORANGE);
setGraphic(graphic);
} else {
setGraphic(new FontIcon("mdi2m-minus-circle"));
}
}
};
});
});
var visit = new ButtonComp(AppI18n.observable("website"), new FontIcon("mdi2w-web"), () -> {
var t = prefs.terminalType().getValue();
if (t == null || t.getWebsite() == null) {
return;
}
Hyperlinks.open(t.getWebsite());
});
var visitVisible = BindingsHelper.persist(Bindings.createBooleanBinding(() -> {
var t = prefs.terminalType().getValue();
if (t == null || t.getWebsite() == null) {
return false;
}
return true;
}, prefs.terminalType()));
visit.visible(visitVisible);
return new HorizontalComp(List.of(c, visit)).apply(struc -> {
struc.get().setAlignment(Pos.CENTER_LEFT);
struc.get().setSpacing(10);
});
}
@Override
protected Comp<?> create() {
var prefs = AppPrefs.get();
@ -46,8 +102,7 @@ public class TerminalCategory extends AppPrefsCategory {
.addTitle("terminalConfiguration")
.sub(new OptionsBuilder()
.nameAndDescription("terminalEmulator")
.addComp(ChoiceComp.ofTranslatable(
prefs.terminalType, PrefsChoiceValue.getSupported(ExternalTerminalType.class), false))
.addComp(terminalChoice(), prefs.terminalType)
.nameAndDescription("customTerminalCommand")
.addComp(new TextFieldComp(prefs.customTerminalCommand, true)
.apply(struc -> struc.get().setPromptText("myterminal -e $CMD"))

View file

@ -0,0 +1,93 @@
package io.xpipe.app.terminal;
import io.xpipe.app.util.LocalShell;
import io.xpipe.core.process.CommandBuilder;
public interface AlacrittyTerminalType extends ExternalTerminalType {
static class Windows extends SimplePathType implements AlacrittyTerminalType {
public Windows() {
super("app.alacritty", "alacritty", false);
}
@Override
protected CommandBuilder toCommand(LaunchConfiguration configuration) {
var b = CommandBuilder.of();
// if (configuration.getColor() != null) {
// b.add("-o")
// .addQuoted("colors.primary.background='%s'"
// .formatted(configuration.getColor().toHexString()));
// }
// Alacritty is bugged and will not accept arguments with spaces even if they are correctly passed/escaped
// So this will not work when the script file has spaces
return b.add("-t")
.addQuoted(configuration.getCleanTitle())
.add("-e")
.add(configuration.getDialectLaunchCommand());
}
}
static class Linux extends SimplePathType implements AlacrittyTerminalType {
public Linux() {
super("app.alacritty", "alacritty", true);
}
@Override
protected CommandBuilder toCommand(LaunchConfiguration configuration) {
return CommandBuilder.of()
.add("-t")
.addQuoted(configuration.getCleanTitle())
.add("-e")
.addFile(configuration.getScriptFile());
}
}
ExternalTerminalType ALACRITTY_WINDOWS = new Windows();
ExternalTerminalType ALACRITTY_LINUX = new Linux();
ExternalTerminalType ALACRITTY_MAC_OS = new MacOs();
@Override
default String getWebsite() {
return "https://github.com/alacritty/alacritty";
}
@Override
default boolean isRecommended() {
return false;
}
@Override
default boolean supportsTabs() {
return false;
}
@Override
default boolean supportsColoredTitle() {
return false;
}
class MacOs extends MacOsType implements AlacrittyTerminalType {
public MacOs() {
super("app.alacritty", "Alacritty");
}
@Override
public void launch(LaunchConfiguration configuration) throws Exception {
LocalShell.getShell()
.executeSimpleCommand(CommandBuilder.of()
.add("open", "-a")
.addQuoted("Alacritty.app")
.add("-n", "--args", "-t")
.addQuoted(configuration.getCleanTitle())
.add("-e")
.addFile(configuration.getScriptFile()));
}
}
}

View file

@ -0,0 +1,57 @@
package io.xpipe.app.terminal;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.prefs.ExternalApplicationHelper;
import io.xpipe.app.prefs.ExternalApplicationType;
import io.xpipe.app.util.LocalShell;
import io.xpipe.core.process.OsType;
import java.util.Locale;
public class CustomTerminalType extends ExternalApplicationType implements ExternalTerminalType {
public CustomTerminalType() {
super("app.custom");
}
@Override
public boolean supportsTabs() {
return true;
}
@Override
public boolean isRecommended() {
return true;
}
@Override
public boolean supportsColoredTitle() {
return true;
}
@Override
public void launch(LaunchConfiguration configuration) throws Exception {
var custom = AppPrefs.get().customTerminalCommand().getValue();
if (custom == null || custom.isBlank()) {
throw ErrorEvent.expected(new IllegalStateException("No custom terminal command specified"));
}
var format = custom.toLowerCase(Locale.ROOT).contains("$cmd") ? custom : custom + " $CMD";
try (var pc = LocalShell.getShell()) {
var toExecute = ExternalApplicationHelper.replaceFileArgument(format, "CMD", configuration.getScriptFile().toString());
// We can't be sure whether the command is blocking or not, so always make it not blocking
if (pc.getOsType().equals(OsType.WINDOWS)) {
toExecute = "start \"" + configuration.getCleanTitle() + "\" " + toExecute;
} else {
toExecute = "nohup " + toExecute + " </dev/null &>/dev/null & disown";
}
pc.executeSimpleCommand(toExecute);
}
}
@Override
public boolean isAvailable() {
return true;
}
}

View file

@ -1,9 +1,6 @@
package io.xpipe.app.terminal;
import io.xpipe.app.ext.PrefsChoiceValue;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.prefs.ExternalApplicationHelper;
import io.xpipe.app.prefs.ExternalApplicationType;
import io.xpipe.app.storage.DataStoreColor;
import io.xpipe.app.util.*;
@ -23,6 +20,11 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
ExternalTerminalType CMD = new SimplePathType("app.cmd", "cmd.exe", true) {
@Override
public boolean isRecommended() {
return false;
}
@Override
public boolean supportsTabs() {
return false;
@ -45,6 +47,11 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
ExternalTerminalType POWERSHELL = new SimplePathType("app.powershell", "powershell", true) {
@Override
public boolean isRecommended() {
return false;
}
@Override
public boolean supportsTabs() {
return false;
@ -80,6 +87,11 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
ExternalTerminalType PWSH = new SimplePathType("app.pwsh", "pwsh", true) {
@Override
public boolean isRecommended() {
return false;
}
@Override
public boolean supportsTabs() {
return false;
@ -104,121 +116,17 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
});
}
};
ExternalTerminalType ALACRITTY_WINDOWS = new SimplePathType("app.alacritty", "alacritty", false) {
@Override
public boolean supportsTabs() {
return false;
}
ExternalTerminalType GNOME_TERMINAL = new PathCheckType("app.gnomeTerminal", "gnome-terminal", true) {
@Override
public boolean supportsColoredTitle() {
return false;
}
@Override
protected CommandBuilder toCommand(LaunchConfiguration configuration) {
var b = CommandBuilder.of();
if (configuration.getColor() != null) {
b.add("-o")
.addQuoted("colors.primary.background='%s'"
.formatted(configuration.getColor().toHexString()));
}
// Alacritty is bugged and will not accept arguments with spaces even if they are correctly passed/escaped
// So this will not work when the script file has spaces
return b.add("-t")
.addQuoted(configuration.getCleanTitle())
.add("-e")
.add(configuration.getDialectLaunchCommand());
}
};
ExternalTerminalType TABBY_WINDOWS = new WindowsType("app.tabby", "Tabby") {
@Override
public boolean supportsTabs() {
return true;
}
@Override
protected void execute(Path file, LaunchConfiguration configuration) throws Exception {
// Tabby has a very weird handling of output, even detaching with start does not prevent it from printing
if (configuration.getScriptDialect().equals(ShellDialects.CMD)) {
// It also freezes with any other input than .bat files, why?
LocalShell.getShell().executeSimpleCommand(CommandBuilder.of()
.addFile(file.toString())
.add("run")
.addFile(configuration.getScriptFile())
.discardOutput());
}
LocalShell.getShell()
.executeSimpleCommand(CommandBuilder.of()
.addFile(file.toString())
.add("run")
.add(configuration.getDialectLaunchCommand())
.discardOutput());
}
@Override
protected Optional<Path> determineInstallation() {
var perUser = WindowsRegistry.readString(
WindowsRegistry.HKEY_CURRENT_USER,
"SOFTWARE\\71445fac-d6ef-5436-9da7-5a323762d7f5",
"InstallLocation")
.map(p -> p + "\\Tabby.exe")
.map(Path::of);
if (perUser.isPresent()) {
return perUser;
}
var systemWide = WindowsRegistry.readString(
WindowsRegistry.HKEY_LOCAL_MACHINE,
"SOFTWARE\\71445fac-d6ef-5436-9da7-5a323762d7f5",
"InstallLocation")
.map(p -> p + "\\Tabby.exe")
.map(Path::of);
return systemWide;
}
};
ExternalTerminalType WEZ_WINDOWS = new WindowsType("app.wezterm", "wezterm-gui") {
@Override
public boolean supportsTabs() {
public boolean isRecommended() {
return false;
}
@Override
protected void execute(Path file, LaunchConfiguration configuration) throws Exception {
LocalShell.getShell().executeSimpleCommand(CommandBuilder.of().addFile(file.toString()).add("start").add(configuration.getDialectLaunchCommand()));
}
@Override
protected Optional<Path> determineInstallation() {
Optional<String> launcherDir;
launcherDir = WindowsRegistry.readString(
WindowsRegistry.HKEY_LOCAL_MACHINE,
"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{BCF6F0DA-5B9A-408D-8562-F680AE6E1EAF}_is1",
"InstallLocation")
.map(p -> p + "\\wezterm-gui.exe");
return launcherDir.map(Path::of);
}
};
ExternalTerminalType WEZ_LINUX = new SimplePathType("app.wezterm", "wezterm-gui", true) {
@Override
public boolean supportsTabs() {
return false;
}
@Override
protected CommandBuilder toCommand(LaunchConfiguration configuration) {
return CommandBuilder.of().add("start").addFile(configuration.getScriptFile());
}
};
ExternalTerminalType GNOME_TERMINAL = new PathCheckType("app.gnomeTerminal", "gnome-terminal", true) {
@Override
public boolean supportsTabs() {
return false;
@ -243,6 +151,11 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
};
ExternalTerminalType KONSOLE = new SimplePathType("app.konsole", "konsole", true) {
@Override
public boolean isRecommended() {
return true;
}
@Override
public boolean supportsTabs() {
return true;
@ -262,6 +175,16 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
}
};
ExternalTerminalType XFCE = new SimplePathType("app.xfce", "xfce4-terminal", true) {
@Override
public boolean supportsColoredTitle() {
return true;
}
@Override
public boolean isRecommended() {
return true;
}
@Override
public boolean supportsTabs() {
@ -278,6 +201,16 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
}
};
ExternalTerminalType ELEMENTARY = new SimplePathType("app.elementaryTerminal", "io.elementary.terminal", true) {
@Override
public boolean supportsColoredTitle() {
return true;
}
@Override
public boolean isRecommended() {
return true;
}
@Override
public boolean supportsTabs() {
@ -290,6 +223,16 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
}
};
ExternalTerminalType TILIX = new SimplePathType("app.tilix", "tilix", true) {
@Override
public boolean supportsColoredTitle() {
return true;
}
@Override
public boolean isRecommended() {
return false;
}
@Override
public boolean supportsTabs() {
@ -306,6 +249,16 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
}
};
ExternalTerminalType TERMINATOR = new SimplePathType("app.terminator", "terminator", true) {
@Override
public boolean supportsColoredTitle() {
return true;
}
@Override
public boolean isRecommended() {
return true;
}
@Override
public boolean supportsTabs() {
@ -323,6 +276,16 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
}
};
ExternalTerminalType TERMINOLOGY = new SimplePathType("app.terminology", "terminology", true) {
@Override
public boolean supportsColoredTitle() {
return true;
}
@Override
public boolean isRecommended() {
return true;
}
@Override
public boolean supportsTabs() {
@ -340,6 +303,16 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
}
};
ExternalTerminalType COOL_RETRO_TERM = new SimplePathType("app.coolRetroTerm", "cool-retro-term", true) {
@Override
public boolean supportsColoredTitle() {
return true;
}
@Override
public boolean isRecommended() {
return false;
}
@Override
public boolean supportsTabs() {
@ -356,6 +329,16 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
}
};
ExternalTerminalType GUAKE = new SimplePathType("app.guake", "guake", true) {
@Override
public boolean supportsColoredTitle() {
return true;
}
@Override
public boolean isRecommended() {
return true;
}
@Override
public boolean supportsTabs() {
@ -372,28 +355,17 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
.addFile(configuration.getScriptFile());
}
};
ExternalTerminalType ALACRITTY_LINUX = new SimplePathType("app.alacritty", "alacritty", true) {
@Override
public boolean supportsTabs() {
return false;
}
ExternalTerminalType TILDA = new SimplePathType("app.tilda", "tilda", true) {
@Override
public boolean supportsColoredTitle() {
return false;
return true;
}
@Override
protected CommandBuilder toCommand(LaunchConfiguration configuration) {
return CommandBuilder.of()
.add("-t")
.addQuoted(configuration.getCleanTitle())
.add("-e")
.addFile(configuration.getScriptFile());
public boolean isRecommended() {
return true;
}
};
ExternalTerminalType TILDA = new SimplePathType("app.tilda", "tilda", true) {
@Override
public boolean supportsTabs() {
@ -406,6 +378,16 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
}
};
ExternalTerminalType XTERM = new SimplePathType("app.xterm", "xterm", true) {
@Override
public boolean supportsColoredTitle() {
return true;
}
@Override
public boolean isRecommended() {
return false;
}
@Override
public boolean supportsTabs() {
@ -422,6 +404,16 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
}
};
ExternalTerminalType DEEPIN_TERMINAL = new SimplePathType("app.deepinTerminal", "deepin-terminal", true) {
@Override
public boolean supportsColoredTitle() {
return true;
}
@Override
public boolean isRecommended() {
return false;
}
@Override
public boolean supportsTabs() {
@ -434,6 +426,16 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
}
};
ExternalTerminalType Q_TERMINAL = new SimplePathType("app.qTerminal", "qterminal", true) {
@Override
public boolean supportsColoredTitle() {
return true;
}
@Override
public boolean isRecommended() {
return false;
}
@Override
public boolean supportsTabs() {
@ -446,6 +448,16 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
}
};
ExternalTerminalType MACOS_TERMINAL = new MacOsType("app.macosTerminal", "Terminal") {
@Override
public boolean supportsColoredTitle() {
return true;
}
@Override
public boolean isRecommended() {
return false;
}
@Override
public boolean supportsTabs() {
return false;
@ -467,6 +479,16 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
}
};
ExternalTerminalType ITERM2 = new MacOsType("app.iterm2", "iTerm") {
@Override
public boolean supportsColoredTitle() {
return true;
}
@Override
public boolean isRecommended() {
return true;
}
@Override
public boolean supportsTabs() {
return true;
@ -504,6 +526,16 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
};
ExternalTerminalType WARP = new MacOsType("app.warp", "Warp") {
@Override
public boolean supportsColoredTitle() {
return true;
}
@Override
public boolean isRecommended() {
return true;
}
@Override
public boolean supportsTabs() {
return true;
@ -523,79 +555,18 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
.addFile(configuration.getScriptFile()));
}
};
ExternalTerminalType TABBY_MAC_OS = new MacOsType("app.tabby", "Tabby") {
@Override
public boolean supportsTabs() {
return true;
}
@Override
public void launch(LaunchConfiguration configuration) throws Exception {
LocalShell.getShell()
.executeSimpleCommand(CommandBuilder.of()
.add("open", "-a")
.addQuoted("Tabby.app")
.add("-n", "--args", "run")
.addFile(configuration.getScriptFile()));
}
};
ExternalTerminalType ALACRITTY_MACOS = new MacOsType("app.alacritty", "Alacritty") {
@Override
public boolean supportsTabs() {
return false;
}
@Override
public boolean supportsColoredTitle() {
return false;
}
@Override
public void launch(LaunchConfiguration configuration) throws Exception {
LocalShell.getShell()
.executeSimpleCommand(CommandBuilder.of()
.add("open", "-a")
.addQuoted("Alacritty.app")
.add("-n", "--args", "-t")
.addQuoted(configuration.getCleanTitle())
.add("-e")
.addFile(configuration.getScriptFile()));
}
};
ExternalTerminalType WEZ_MACOS = new MacOsType("app.wezterm", "WezTerm") {
@Override
public boolean supportsTabs() {
return false;
}
@Override
public void launch(LaunchConfiguration configuration) throws Exception {
var c = CommandBuilder.of()
.addFile(getApplicationPath()
.orElseThrow()
.resolve("Contents")
.resolve("MacOS")
.resolve("wezterm-gui")
.toString())
.add("start")
.add(configuration.getDialectLaunchCommand());
ExternalApplicationHelper.startAsync(c);
}
};
ExternalTerminalType CUSTOM = new CustomType();
ExternalTerminalType CUSTOM = new CustomTerminalType();
List<ExternalTerminalType> WINDOWS_TERMINALS = List.of(
TABBY_WINDOWS,
ALACRITTY_WINDOWS,
WEZ_WINDOWS,
TabbyTerminalType.TABBY_WINDOWS,
AlacrittyTerminalType.ALACRITTY_WINDOWS,
WezTerminalType.WEZTERM_WINDOWS,
WindowsTerminalType.WINDOWS_TERMINAL_PREVIEW,
WindowsTerminalType.WINDOWS_TERMINAL,
CMD,
PWSH,
POWERSHELL);
List<ExternalTerminalType> LINUX_TERMINALS = List.of(
WEZ_LINUX,
WezTerminalType.WEZTERM_LINUX,
KONSOLE,
XFCE,
ELEMENTARY,
@ -606,13 +577,13 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
TERMINOLOGY,
COOL_RETRO_TERM,
GUAKE,
ALACRITTY_LINUX,
AlacrittyTerminalType.ALACRITTY_LINUX,
TILDA,
XTERM,
DEEPIN_TERMINAL,
Q_TERMINAL);
List<ExternalTerminalType> MACOS_TERMINALS =
List.of(ITERM2, TABBY_MAC_OS, ALACRITTY_MACOS, KittyTerminalType.KITTY_MACOS, WARP, WEZ_MACOS, MACOS_TERMINAL);
List.of(ITERM2, TabbyTerminalType.TABBY_MAC_OS, AlacrittyTerminalType.ALACRITTY_MAC_OS, KittyTerminalType.KITTY_MACOS, WARP, WezTerminalType.WEZTERM_MAC_OS, MACOS_TERMINAL);
@SuppressWarnings("TrivialFunctionalExpressionUsage")
List<ExternalTerminalType> ALL = ((Supplier<List<ExternalTerminalType>>) () -> {
@ -653,10 +624,14 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
boolean supportsTabs();
default boolean supportsColoredTitle() {
return true;
default String getWebsite() {
return null;
}
boolean isRecommended();
boolean supportsColoredTitle();
default boolean shouldClear() {
return true;
}
@ -709,43 +684,6 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
}
}
class CustomType extends ExternalApplicationType implements ExternalTerminalType {
public CustomType() {
super("app.custom");
}
@Override
public boolean supportsTabs() {
return true;
}
@Override
public void launch(LaunchConfiguration configuration) throws Exception {
var custom = AppPrefs.get().customTerminalCommand().getValue();
if (custom == null || custom.isBlank()) {
throw ErrorEvent.expected(new IllegalStateException("No custom terminal command specified"));
}
var format = custom.toLowerCase(Locale.ROOT).contains("$cmd") ? custom : custom + " $CMD";
try (var pc = LocalShell.getShell()) {
var toExecute = ExternalApplicationHelper.replaceFileArgument(format, "CMD", configuration.getScriptFile().toString());
// We can't be sure whether the command is blocking or not, so always make it not blocking
if (pc.getOsType().equals(OsType.WINDOWS)) {
toExecute = "start \"" + configuration.getCleanTitle() + "\" " + toExecute;
} else {
toExecute = "nohup " + toExecute + " </dev/null &>/dev/null & disown";
}
pc.executeSimpleCommand(toExecute);
}
}
@Override
public boolean isAvailable() {
return true;
}
}
abstract class MacOsType extends ExternalApplicationType.MacApplication implements ExternalTerminalType {
public MacOsType(String id, String applicationName) {
@ -776,4 +714,5 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
protected abstract CommandBuilder toCommand(LaunchConfiguration configuration) throws Exception;
}
}

View file

@ -9,83 +9,32 @@ import io.xpipe.core.process.CommandBuilder;
import io.xpipe.core.store.FilePath;
import io.xpipe.core.util.XPipeInstallation;
public class KittyTerminalType {
public interface KittyTerminalType extends ExternalTerminalType {
public static final ExternalTerminalType KITTY_LINUX = new ExternalTerminalType() {
@Override
default boolean supportsColoredTitle() {
return true;
}
@Override
public String getId() {
return "app.kitty";
}
@Override
default boolean isRecommended() {
return true;
}
@Override
public boolean supportsTabs() {
return true;
}
@Override
default boolean supportsTabs() {
return true;
}
@Override
public void launch(LaunchConfiguration configuration) throws Exception {
try (var sc = LocalShell.getShell().start()) {
CommandSupport.isInPathOrThrow(sc, "kitty", "Kitty", null);
CommandSupport.isInPathOrThrow(sc, "socat", "socat", null);
}
@Override
default String getWebsite() {
return "https://github.com/kovidgoyal/kitty";
}
var toClose = prepare();
var socketWrite = CommandBuilder.of().add("socat", "-");
open(configuration, socketWrite);
if (toClose) {
closeInitial(socketWrite);
}
}
private boolean prepare() throws Exception {
var socket = getSocket();
try (var sc = LocalShell.getShell().start()) {
if (sc.executeSimpleBooleanCommand("test -w " + sc.getShellDialect().fileArgument(socket))) {
return false;
}
public static final ExternalTerminalType KITTY_LINUX = new Linux();
sc.executeSimpleCommand(CommandBuilder.of().add("kitty").add("-o", "allow_remote_control=socket-only", "--listen-on", "unix:" + getSocket(), "--detach"));
ThreadHelper.sleep(1500);
return true;
}
}
};
public static final ExternalTerminalType KITTY_MACOS = new ExternalTerminalType.MacOsType("app.kitty", "kitty") {
@Override
public boolean supportsTabs() {
return true;
}
@Override
public void launch(LaunchConfiguration configuration) throws Exception {
try (var sc = LocalShell.getShell().start()) {
CommandSupport.isInPathOrThrow(sc, "nc", "Netcat", null);
}
var toClose = prepare();
var socketWrite = CommandBuilder.of().add("nc", "-U");
open(configuration, socketWrite);
if (toClose) {
closeInitial(socketWrite);
}
}
private boolean prepare() throws Exception {
var socket = getSocket();
try (var sc = LocalShell.getShell().start()) {
if (sc.executeSimpleBooleanCommand("test -w " + sc.getShellDialect().fileArgument(socket))) {
return false;
}
sc.executeSimpleCommand(CommandBuilder.of().add("open", "-a", "kitty.app", "--args").add("-o", "allow_remote_control=socket-only", "--listen-on", "unix:" + getSocket()));
ThreadHelper.sleep(1000);
return true;
}
}
};
public static final ExternalTerminalType KITTY_MACOS = new MacOs();
private static FilePath getSocket() throws Exception {
try (var sc = LocalShell.getShell().start()) {
@ -132,4 +81,72 @@ public class KittyTerminalType {
sc.executeSimpleCommand(CommandBuilder.of().add("echo", "-en", echoString, "|").add(socketWrite).addFile(getSocket()));
}
}
class Linux implements KittyTerminalType {
@Override
public String getId() {
return "app.kitty";
}
@Override
public void launch(LaunchConfiguration configuration) throws Exception {
try (var sc = LocalShell.getShell().start()) {
CommandSupport.isInPathOrThrow(sc, "kitty", "Kitty", null);
CommandSupport.isInPathOrThrow(sc, "socat", "socat", null);
}
var toClose = prepare();
var socketWrite = CommandBuilder.of().add("socat", "-");
open(configuration, socketWrite);
if (toClose) {
closeInitial(socketWrite);
}
}
private boolean prepare() throws Exception {
var socket = getSocket();
try (var sc = LocalShell.getShell().start()) {
if (sc.executeSimpleBooleanCommand("test -w " + sc.getShellDialect().fileArgument(socket))) {
return false;
}
sc.executeSimpleCommand(CommandBuilder.of().add("kitty").add("-o", "allow_remote_control=socket-only", "--listen-on", "unix:" + getSocket(), "--detach"));
ThreadHelper.sleep(1500);
return true;
}
}
}
class MacOs extends MacOsType implements KittyTerminalType {
public MacOs() {super("app.kitty", "kitty");}
@Override
public void launch(LaunchConfiguration configuration) throws Exception {
try (var sc = LocalShell.getShell().start()) {
CommandSupport.isInPathOrThrow(sc, "nc", "Netcat", null);
}
var toClose = prepare();
var socketWrite = CommandBuilder.of().add("nc", "-U");
open(configuration, socketWrite);
if (toClose) {
closeInitial(socketWrite);
}
}
private boolean prepare() throws Exception {
var socket = getSocket();
try (var sc = LocalShell.getShell().start()) {
if (sc.executeSimpleBooleanCommand("test -w " + sc.getShellDialect().fileArgument(socket))) {
return false;
}
sc.executeSimpleCommand(CommandBuilder.of().add("open", "-a", "kitty.app", "--args").add("-o", "allow_remote_control=socket-only", "--listen-on", "unix:" + getSocket()));
ThreadHelper.sleep(1000);
return true;
}
}
}
}

View file

@ -0,0 +1,95 @@
package io.xpipe.app.terminal;
import io.xpipe.app.util.LocalShell;
import io.xpipe.app.util.WindowsRegistry;
import io.xpipe.core.process.CommandBuilder;
import io.xpipe.core.process.ShellDialects;
import java.nio.file.Path;
import java.util.Optional;
public interface TabbyTerminalType extends ExternalTerminalType {
static class Windows extends ExternalTerminalType.WindowsType implements TabbyTerminalType {
public Windows() {
super("app.tabby", "Tabby.exe");
}
@Override
protected void execute(Path file, LaunchConfiguration configuration) throws Exception {
// Tabby has a very weird handling of output, even detaching with start does not prevent it from printing
if (configuration.getScriptDialect().equals(ShellDialects.CMD)) {
// It also freezes with any other input than .bat files, why?
LocalShell.getShell().executeSimpleCommand(CommandBuilder.of()
.addFile(file.toString())
.add("run")
.addFile(configuration.getScriptFile())
.discardOutput());
}
// This is probably not going to work as it does not launch a bat file
LocalShell.getShell().executeSimpleCommand(CommandBuilder.of()
.addFile(file.toString())
.add("run")
.add(configuration.getDialectLaunchCommand())
.discardOutput());
}
@Override
protected Optional<Path> determineInstallation() {
var perUser = WindowsRegistry.readString(WindowsRegistry.HKEY_CURRENT_USER, "SOFTWARE\\71445fac-d6ef-5436-9da7-5a323762d7f5",
"InstallLocation").map(p -> p + "\\Tabby.exe").map(Path::of);
if (perUser.isPresent()) {
return perUser;
}
var systemWide = WindowsRegistry.readString(WindowsRegistry.HKEY_LOCAL_MACHINE, "SOFTWARE\\71445fac-d6ef-5436-9da7-5a323762d7f5",
"InstallLocation").map(p -> p + "\\Tabby.exe").map(Path::of);
return systemWide;
}
}
ExternalTerminalType TABBY_WINDOWS = new Windows();
ExternalTerminalType TABBY_MAC_OS = new MacOs();
@Override
default String getWebsite() {
return "https://tabby.sh";
}
@Override
default boolean isRecommended() {
return true;
}
@Override
default boolean supportsTabs() {
return true;
}
@Override
default boolean supportsColoredTitle() {
return true;
}
class MacOs extends MacOsType implements TabbyTerminalType {
public MacOs() {
super("app.tabby", "Tabby");
}
@Override
public void launch(LaunchConfiguration configuration) throws Exception {
LocalShell.getShell()
.executeSimpleCommand(CommandBuilder.of()
.add("open", "-a")
.addQuoted("Tabby.app")
.add("-n", "--args", "run")
.addFile(configuration.getScriptFile()));
}
}
}

View file

@ -0,0 +1,94 @@
package io.xpipe.app.terminal;
import io.xpipe.app.prefs.ExternalApplicationHelper;
import io.xpipe.app.util.LocalShell;
import io.xpipe.app.util.WindowsRegistry;
import io.xpipe.core.process.CommandBuilder;
import java.nio.file.Path;
import java.util.Optional;
public interface WezTerminalType extends ExternalTerminalType {
static class Windows extends WindowsType implements WezTerminalType {
public Windows() {
super("app.wezterm", "wezterm-gui");
}
@Override
protected void execute(Path file, LaunchConfiguration configuration) throws Exception {
LocalShell.getShell().executeSimpleCommand(CommandBuilder.of().addFile(file.toString()).add("start").add(configuration.getDialectLaunchCommand()));
}
@Override
protected Optional<Path> determineInstallation() {
Optional<String> launcherDir;
launcherDir = WindowsRegistry.readString(
WindowsRegistry.HKEY_LOCAL_MACHINE,
"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{BCF6F0DA-5B9A-408D-8562-F680AE6E1EAF}_is1",
"InstallLocation")
.map(p -> p + "\\wezterm-gui.exe");
return launcherDir.map(Path::of);
}
}
static class Linux extends SimplePathType implements WezTerminalType {
public Linux() {
super("app.wezterm", "wezterm-gui", true);
}
@Override
protected CommandBuilder toCommand(LaunchConfiguration configuration) {
return CommandBuilder.of().add("start").addFile(configuration.getScriptFile());
}
}
ExternalTerminalType WEZTERM_WINDOWS = new Windows();
ExternalTerminalType WEZTERM_LINUX = new Linux();
ExternalTerminalType WEZTERM_MAC_OS = new MacOs();
@Override
default String getWebsite() {
return "https://wezfurlong.org/wezterm/index.html";
}
@Override
default boolean isRecommended() {
return false;
}
@Override
default boolean supportsTabs() {
return false;
}
@Override
default boolean supportsColoredTitle() {
return true;
}
class MacOs extends MacOsType implements WezTerminalType {
public MacOs() {
super("app.wezterm", "WezTerm");
}
@Override
public void launch(LaunchConfiguration configuration) throws Exception {
var c = CommandBuilder.of()
.addFile(getApplicationPath()
.orElseThrow()
.resolve("Contents")
.resolve("MacOS")
.resolve("wezterm-gui")
.toString())
.add("start")
.add(configuration.getDialectLaunchCommand());
ExternalApplicationHelper.startAsync(c);
}
}
}

View file

@ -8,41 +8,70 @@ import io.xpipe.core.store.FileNames;
import java.nio.file.Files;
import java.nio.file.Path;
public class WindowsTerminalType {
public interface WindowsTerminalType extends ExternalTerminalType {
public static final ExternalTerminalType WINDOWS_TERMINAL =
new ExternalTerminalType.SimplePathType("app.windowsTerminal", "wt.exe", false) {
@Override
default boolean isRecommended() {
return true;
}
@Override
protected CommandBuilder toCommand(LaunchConfiguration configuration) throws Exception {
return WindowsTerminalType.toCommand(configuration);
}
@Override
default boolean supportsTabs() {
return true;
}
@Override
public boolean supportsTabs() {
return true;
}
@Override
default boolean supportsColoredTitle() {
return false;
}
@Override
public boolean supportsColoredTitle() {
return false;
}
};
public static final ExternalTerminalType WINDOWS_TERMINAL = new Standard();
public static final ExternalTerminalType WINDOWS_TERMINAL_PREVIEW = new ExternalTerminalType() {
public static final ExternalTerminalType WINDOWS_TERMINAL_PREVIEW = new Preview();
private static CommandBuilder toCommand(ExternalTerminalType.LaunchConfiguration configuration) throws Exception {
// A weird behavior in Windows Terminal causes the trailing
// backslash of a filepath to escape the closing quote in the title argument
// So just remove that slash
var fixedName = FileNames.removeTrailingSlash(configuration.getColoredTitle());
var toExec = !ShellDialects.isPowershell(LocalShell.getShell())
? CommandBuilder.of().addFile(configuration.getScriptFile())
: CommandBuilder.of()
.add("powershell", "-ExecutionPolicy", "Bypass", "-File")
.addFile(configuration.getScriptFile());
var cmd = CommandBuilder.of().add("-w", "1", "nt");
if (configuration.getColor() != null) {
cmd.add("--tabColor").addQuoted(configuration.getColor().toHexString());
}
return cmd.add("--title").addQuoted(fixedName).add(toExec);
}
class Standard extends SimplePathType implements WindowsTerminalType {
public Standard() {super("app.windowsTerminal", "wt.exe", false);}
@Override
public boolean supportsTabs() {
return true;
public String getWebsite() {
return "https://aka.ms/terminal-preview";
}
@Override
public boolean supportsColoredTitle() {
return false;
protected CommandBuilder toCommand(LaunchConfiguration configuration) throws Exception {
return WindowsTerminalType.toCommand(configuration);
}
}
class Preview implements WindowsTerminalType {
@Override
public String getWebsite() {
return "https://aka.ms/terminal";
}
@Override
public void launch(ExternalTerminalType.LaunchConfiguration configuration) throws Exception {
public void launch(LaunchConfiguration configuration) throws Exception {
LocalShell.getShell()
.executeSimpleCommand(
CommandBuilder.of().addFile(getPath().toString()).add(toCommand(configuration)));
@ -63,24 +92,5 @@ public class WindowsTerminalType {
public String getId() {
return "app.windowsTerminalPreview";
}
};
private static CommandBuilder toCommand(ExternalTerminalType.LaunchConfiguration configuration) throws Exception {
// A weird behavior in Windows Terminal causes the trailing
// backslash of a filepath to escape the closing quote in the title argument
// So just remove that slash
var fixedName = FileNames.removeTrailingSlash(configuration.getColoredTitle());
var toExec = !ShellDialects.isPowershell(LocalShell.getShell())
? CommandBuilder.of().addFile(configuration.getScriptFile())
: CommandBuilder.of()
.add("powershell", "-ExecutionPolicy", "Bypass", "-File")
.addFile(configuration.getScriptFile());
var cmd = CommandBuilder.of().add("-w", "1", "nt");
if (configuration.getColor() != null) {
cmd.add("--tabColor").addQuoted(configuration.getColor().toHexString());
}
return cmd.add("--title").addQuoted(fixedName).add(toExec);
}
}