mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-22 07:30:24 +00:00
Terminal rework
This commit is contained in:
parent
7fd07242f5
commit
b350222769
29 changed files with 825 additions and 764 deletions
|
@ -1,6 +1,7 @@
|
||||||
package io.xpipe.app.browser.file;
|
package io.xpipe.app.browser.file;
|
||||||
|
|
||||||
import io.xpipe.app.browser.BrowserAbstractSessionModel;
|
import io.xpipe.app.browser.BrowserAbstractSessionModel;
|
||||||
|
import io.xpipe.app.browser.BrowserFullSessionModel;
|
||||||
import io.xpipe.app.browser.BrowserStoreSessionTab;
|
import io.xpipe.app.browser.BrowserStoreSessionTab;
|
||||||
import io.xpipe.app.browser.action.BrowserAction;
|
import io.xpipe.app.browser.action.BrowserAction;
|
||||||
import io.xpipe.app.comp.Comp;
|
import io.xpipe.app.comp.Comp;
|
||||||
|
@ -8,15 +9,13 @@ import io.xpipe.app.comp.base.ModalOverlayComp;
|
||||||
import io.xpipe.app.ext.ProcessControlProvider;
|
import io.xpipe.app.ext.ProcessControlProvider;
|
||||||
import io.xpipe.app.ext.ShellStore;
|
import io.xpipe.app.ext.ShellStore;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||||
import io.xpipe.app.terminal.TerminalLauncher;
|
import io.xpipe.app.terminal.*;
|
||||||
import io.xpipe.app.util.BooleanScope;
|
import io.xpipe.app.util.BooleanScope;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import io.xpipe.core.process.CommandBuilder;
|
import io.xpipe.core.process.*;
|
||||||
import io.xpipe.core.process.ShellControl;
|
|
||||||
import io.xpipe.core.process.ShellDialects;
|
|
||||||
import io.xpipe.core.process.ShellOpenFunction;
|
|
||||||
import io.xpipe.core.store.*;
|
import io.xpipe.core.store.*;
|
||||||
import io.xpipe.core.util.FailableConsumer;
|
import io.xpipe.core.util.FailableConsumer;
|
||||||
import io.xpipe.core.util.FailableRunnable;
|
import io.xpipe.core.util.FailableRunnable;
|
||||||
|
@ -206,6 +205,33 @@ public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab<File
|
||||||
cdSyncOrRetry(path, false).ifPresent(s -> cdSyncOrRetry(s, false));
|
cdSyncOrRetry(path, false).ifPresent(s -> cdSyncOrRetry(s, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean shouldLaunchSplitTerminal() {
|
||||||
|
if (!AppPrefs.get().enableTerminalDocking().get()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OsType.getLocal() != OsType.WINDOWS) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var term = AppPrefs.get().terminalType().getValue();
|
||||||
|
if (term == null || term.getOpenFormat() == TerminalOpenFormat.TABBED) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(browserModel instanceof BrowserFullSessionModel f)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the right side is already occupied
|
||||||
|
var existingSplit = f.getEffectiveRightTab().getValue();
|
||||||
|
if (existingSplit != null && !(existingSplit instanceof BrowserTerminalDockTabModel)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public Optional<String> cdSyncOrRetry(String path, boolean customInput) {
|
public Optional<String> cdSyncOrRetry(String path, boolean customInput) {
|
||||||
if (Objects.equals(path, currentPath.get())) {
|
if (Objects.equals(path, currentPath.get())) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
|
@ -250,27 +276,15 @@ public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab<File
|
||||||
if (ShellDialects.getStartableDialects().stream().anyMatch(dialect -> adjustedPath
|
if (ShellDialects.getStartableDialects().stream().anyMatch(dialect -> adjustedPath
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.startsWith(dialect.getExecutableName().toLowerCase()))) {
|
.startsWith(dialect.getExecutableName().toLowerCase()))) {
|
||||||
var uuid = UUID.randomUUID();
|
var cc = fileSystem
|
||||||
terminalRequests.add(uuid);
|
.getShell()
|
||||||
TerminalLauncher.open(
|
.get()
|
||||||
entry.getEntry(),
|
.singularSubShell(
|
||||||
name,
|
ShellOpenFunction.of(CommandBuilder.ofString(adjustedPath), false));
|
||||||
directory,
|
openTerminalAsync(name,directory,cc);
|
||||||
fileSystem
|
|
||||||
.getShell()
|
|
||||||
.get()
|
|
||||||
.singularSubShell(
|
|
||||||
ShellOpenFunction.of(CommandBuilder.ofString(adjustedPath), false)),
|
|
||||||
uuid);
|
|
||||||
} else {
|
} else {
|
||||||
var uuid = UUID.randomUUID();
|
var cc = fileSystem.getShell().get().command(adjustedPath);
|
||||||
terminalRequests.add(uuid);
|
openTerminalAsync(name,directory,cc);
|
||||||
TerminalLauncher.open(
|
|
||||||
entry.getEntry(),
|
|
||||||
name,
|
|
||||||
directory,
|
|
||||||
fileSystem.getShell().get().command(adjustedPath),
|
|
||||||
uuid);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return Optional.ofNullable(currentPath.get());
|
return Optional.ofNullable(currentPath.get());
|
||||||
|
@ -525,7 +539,7 @@ public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab<File
|
||||||
history.updateCurrent(null);
|
history.updateCurrent(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void openTerminalAsync(String directory) {
|
public void openTerminalAsync(String name, String directory, ProcessControl processControl) {
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
if (fileSystem == null) {
|
if (fileSystem == null) {
|
||||||
return;
|
return;
|
||||||
|
@ -533,12 +547,14 @@ public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab<File
|
||||||
|
|
||||||
BooleanScope.executeExclusive(busy, () -> {
|
BooleanScope.executeExclusive(busy, () -> {
|
||||||
if (fileSystem.getShell().isPresent()) {
|
if (fileSystem.getShell().isPresent()) {
|
||||||
var connection = fileSystem.getShell().get();
|
var dock = shouldLaunchSplitTerminal();
|
||||||
var name = (directory != null ? directory + " - " : "")
|
|
||||||
+ entry.get().getName();
|
|
||||||
var uuid = UUID.randomUUID();
|
var uuid = UUID.randomUUID();
|
||||||
terminalRequests.add(uuid);
|
terminalRequests.add(uuid);
|
||||||
TerminalLauncher.open(entry.getEntry(), name, directory, connection, uuid);
|
if (dock && browserModel instanceof BrowserFullSessionModel fullSessionModel &&
|
||||||
|
!(fullSessionModel.getSplits().get(this) instanceof BrowserTerminalDockTabModel)) {
|
||||||
|
fullSessionModel.splitTab(this, new BrowserTerminalDockTabModel(browserModel, this, terminalRequests));
|
||||||
|
}
|
||||||
|
TerminalLauncher.open(entry.getEntry(), name, directory, processControl, uuid, !dock);
|
||||||
|
|
||||||
// Restart connection as we will have to start it anyway, so we speed it up by doing it preemptively
|
// Restart connection as we will have to start it anyway, so we speed it up by doing it preemptively
|
||||||
startIfNeeded();
|
startIfNeeded();
|
||||||
|
|
|
@ -49,8 +49,6 @@ public final class BrowserTerminalDockTabModel extends BrowserSessionTab {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init() throws Exception {
|
public void init() throws Exception {
|
||||||
var sessions = new ArrayList<TerminalView.ShellSession>();
|
|
||||||
var terminals = new ArrayList<TerminalView.TerminalSession>();
|
|
||||||
listener = new TerminalView.Listener() {
|
listener = new TerminalView.Listener() {
|
||||||
@Override
|
@Override
|
||||||
public void onSessionOpened(TerminalView.ShellSession session) {
|
public void onSessionOpened(TerminalView.ShellSession session) {
|
||||||
|
@ -58,17 +56,11 @@ public final class BrowserTerminalDockTabModel extends BrowserSessionTab {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
sessions.add(session);
|
var sessions = TerminalView.get().getSessions();
|
||||||
var tv = terminals.stream()
|
var tv = sessions.stream().filter(s -> terminalRequests.contains(s.getRequest()) && s.getTerminal().isRunning())
|
||||||
.filter(instance -> sessions.stream()
|
.map(s -> s.getTerminal().controllable())
|
||||||
.anyMatch(s -> instance.getTerminalProcess().equals(s.getTerminal())))
|
|
||||||
.map(terminalSession -> terminalSession.controllable())
|
|
||||||
.flatMap(Optional::stream)
|
.flatMap(Optional::stream)
|
||||||
.toList();
|
.toList();
|
||||||
if (tv.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < tv.size() - 1; i++) {
|
for (int i = 0; i < tv.size() - 1; i++) {
|
||||||
dockModel.closeTerminal(tv.get(i));
|
dockModel.closeTerminal(tv.get(i));
|
||||||
}
|
}
|
||||||
|
@ -77,20 +69,11 @@ public final class BrowserTerminalDockTabModel extends BrowserSessionTab {
|
||||||
dockModel.trackTerminal(toTrack);
|
dockModel.trackTerminal(toTrack);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSessionClosed(TerminalView.ShellSession session) {
|
|
||||||
sessions.remove(session);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTerminalOpened(TerminalView.TerminalSession instance) {
|
|
||||||
terminals.add(instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTerminalClosed(TerminalView.TerminalSession instance) {
|
public void onTerminalClosed(TerminalView.TerminalSession instance) {
|
||||||
terminals.remove(instance);
|
var sessions = TerminalView.get().getSessions();
|
||||||
if (terminals.isEmpty()) {
|
var remaining = sessions.stream().filter(s -> terminalRequests.contains(s.getRequest()) && s.getTerminal().isRunning()).toList();
|
||||||
|
if (remaining.isEmpty()) {
|
||||||
((BrowserFullSessionModel) browserModel).unsplitTab(BrowserTerminalDockTabModel.this);
|
((BrowserFullSessionModel) browserModel).unsplitTab(BrowserTerminalDockTabModel.this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,18 +12,22 @@ import com.sun.jna.platform.win32.User32;
|
||||||
import com.sun.jna.platform.win32.WinDef;
|
import com.sun.jna.platform.win32.WinDef;
|
||||||
import com.sun.jna.platform.win32.WinNT;
|
import com.sun.jna.platform.win32.WinNT;
|
||||||
import com.sun.jna.ptr.IntByReference;
|
import com.sun.jna.ptr.IntByReference;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
|
@EqualsAndHashCode
|
||||||
public class NativeWinWindowControl {
|
public class NativeWinWindowControl {
|
||||||
|
|
||||||
public static Optional<NativeWinWindowControl> byPid(long pid) {
|
public static List<NativeWinWindowControl> byPid(long pid) {
|
||||||
var ref = new AtomicReference<NativeWinWindowControl>();
|
var refs = new ArrayList<NativeWinWindowControl>();
|
||||||
User32.INSTANCE.EnumWindows(
|
User32.INSTANCE.EnumWindows(
|
||||||
(hWnd, data) -> {
|
(hWnd, data) -> {
|
||||||
var visible = User32.INSTANCE.IsWindowVisible(hWnd);
|
var visible = User32.INSTANCE.IsWindowVisible(hWnd);
|
||||||
|
@ -34,14 +38,12 @@ public class NativeWinWindowControl {
|
||||||
var wpid = new IntByReference();
|
var wpid = new IntByReference();
|
||||||
User32.INSTANCE.GetWindowThreadProcessId(hWnd, wpid);
|
User32.INSTANCE.GetWindowThreadProcessId(hWnd, wpid);
|
||||||
if (wpid.getValue() == pid) {
|
if (wpid.getValue() == pid) {
|
||||||
ref.set(new NativeWinWindowControl(hWnd));
|
refs.add(new NativeWinWindowControl(hWnd));
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
},
|
},
|
||||||
null);
|
null);
|
||||||
return Optional.ofNullable(ref.get());
|
return refs;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static NativeWinWindowControl MAIN_WINDOW;
|
public static NativeWinWindowControl MAIN_WINDOW;
|
||||||
|
|
|
@ -10,13 +10,13 @@ public interface AlacrittyTerminalType extends ExternalTerminalType, TrackableTe
|
||||||
ExternalTerminalType ALACRITTY_MAC_OS = new MacOs();
|
ExternalTerminalType ALACRITTY_MAC_OS = new MacOs();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default boolean supportsTabs() {
|
default String getWebsite() {
|
||||||
return false;
|
return "https://github.com/alacritty/alacritty";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default String getWebsite() {
|
default TerminalOpenFormat getOpenFormat() {
|
||||||
return "https://github.com/alacritty/alacritty";
|
return TerminalOpenFormat.NEW_WINDOW;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -36,7 +36,7 @@ public interface AlacrittyTerminalType extends ExternalTerminalType, TrackableTe
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected CommandBuilder toCommand(LaunchConfiguration configuration) {
|
protected CommandBuilder toCommand(TerminalLaunchConfiguration configuration) {
|
||||||
var b = CommandBuilder.of();
|
var b = CommandBuilder.of();
|
||||||
|
|
||||||
// if (configuration.getColor() != null) {
|
// if (configuration.getColor() != null) {
|
||||||
|
@ -61,7 +61,7 @@ public interface AlacrittyTerminalType extends ExternalTerminalType, TrackableTe
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected CommandBuilder toCommand(LaunchConfiguration configuration) {
|
protected CommandBuilder toCommand(TerminalLaunchConfiguration configuration) {
|
||||||
return CommandBuilder.of()
|
return CommandBuilder.of()
|
||||||
.add("-t")
|
.add("-t")
|
||||||
.addQuoted(configuration.getCleanTitle())
|
.addQuoted(configuration.getCleanTitle())
|
||||||
|
@ -77,7 +77,7 @@ public interface AlacrittyTerminalType extends ExternalTerminalType, TrackableTe
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void launch(LaunchConfiguration configuration) throws Exception {
|
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||||
LocalShell.getShell()
|
LocalShell.getShell()
|
||||||
.executeSimpleCommand(CommandBuilder.of()
|
.executeSimpleCommand(CommandBuilder.of()
|
||||||
.add("open", "-a")
|
.add("open", "-a")
|
||||||
|
|
|
@ -11,6 +11,11 @@ public class CmdTerminalType extends ExternalTerminalType.SimplePathType impleme
|
||||||
super("app.cmd", "cmd.exe", true);
|
super("app.cmd", "cmd.exe", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TerminalOpenFormat getOpenFormat() {
|
||||||
|
return TerminalOpenFormat.NEW_WINDOW;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getProcessHierarchyOffset() {
|
public int getProcessHierarchyOffset() {
|
||||||
var powershell = ShellDialects.isPowershell(ProcessControlProvider.get().getEffectiveLocalDialect())
|
var powershell = ShellDialects.isPowershell(ProcessControlProvider.get().getEffectiveLocalDialect())
|
||||||
|
@ -18,11 +23,6 @@ public class CmdTerminalType extends ExternalTerminalType.SimplePathType impleme
|
||||||
return powershell ? 0 : -1;
|
return powershell ? 0 : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsTabs() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isRecommended() {
|
public boolean isRecommended() {
|
||||||
return false;
|
return false;
|
||||||
|
@ -34,7 +34,7 @@ public class CmdTerminalType extends ExternalTerminalType.SimplePathType impleme
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected CommandBuilder toCommand(LaunchConfiguration configuration) {
|
protected CommandBuilder toCommand(TerminalLaunchConfiguration configuration) {
|
||||||
if (configuration.getScriptDialect().equals(ShellDialects.CMD)) {
|
if (configuration.getScriptDialect().equals(ShellDialects.CMD)) {
|
||||||
return CommandBuilder.of().add("/c").addFile(configuration.getScriptFile());
|
return CommandBuilder.of().add("/c").addFile(configuration.getScriptFile());
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,8 @@ public class CustomTerminalType extends ExternalApplicationType implements Exter
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsTabs() {
|
public TerminalOpenFormat getOpenFormat() {
|
||||||
return true;
|
return TerminalOpenFormat.NEW_WINDOW;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -31,7 +31,7 @@ public class CustomTerminalType extends ExternalApplicationType implements Exter
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void launch(LaunchConfiguration configuration) throws Exception {
|
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||||
var custom = AppPrefs.get().customTerminalCommand().getValue();
|
var custom = AppPrefs.get().customTerminalCommand().getValue();
|
||||||
if (custom == null || custom.isBlank()) {
|
if (custom == null || custom.isBlank()) {
|
||||||
throw ErrorEvent.expected(new IllegalStateException("No custom terminal command specified"));
|
throw ErrorEvent.expected(new IllegalStateException("No custom terminal command specified"));
|
||||||
|
|
|
@ -1,29 +1,15 @@
|
||||||
package io.xpipe.app.terminal;
|
package io.xpipe.app.terminal;
|
||||||
|
|
||||||
import io.xpipe.app.comp.base.MarkdownComp;
|
|
||||||
import io.xpipe.app.core.AppCache;
|
|
||||||
import io.xpipe.app.core.AppI18n;
|
|
||||||
import io.xpipe.app.core.window.AppWindowHelper;
|
|
||||||
import io.xpipe.app.ext.PrefsChoiceValue;
|
import io.xpipe.app.ext.PrefsChoiceValue;
|
||||||
import io.xpipe.app.ext.ProcessControlProvider;
|
import io.xpipe.app.ext.ProcessControlProvider;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
|
||||||
import io.xpipe.app.prefs.ExternalApplicationType;
|
import io.xpipe.app.prefs.ExternalApplicationType;
|
||||||
import io.xpipe.app.storage.DataColor;
|
|
||||||
import io.xpipe.app.util.*;
|
import io.xpipe.app.util.*;
|
||||||
import io.xpipe.core.process.*;
|
import io.xpipe.core.process.*;
|
||||||
import io.xpipe.core.store.FilePath;
|
|
||||||
import io.xpipe.core.util.FailableFunction;
|
import io.xpipe.core.util.FailableFunction;
|
||||||
|
|
||||||
import javafx.scene.control.Alert;
|
|
||||||
import javafx.scene.control.ButtonBar;
|
|
||||||
import javafx.scene.control.ButtonType;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Value;
|
|
||||||
import lombok.With;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
@ -97,305 +83,13 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ExternalTerminalType XSHELL = new WindowsType("app.xShell", "Xshell") {
|
ExternalTerminalType XSHELL = new XShellTerminalType();
|
||||||
|
|
||||||
@Override
|
ExternalTerminalType SECURECRT = new SecureCrtTerminalType();
|
||||||
protected Optional<Path> determineInstallation() {
|
|
||||||
try {
|
|
||||||
var r = WindowsRegistry.local()
|
|
||||||
.readValue(
|
|
||||||
WindowsRegistry.HKEY_LOCAL_MACHINE,
|
|
||||||
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\Xshell.exe");
|
|
||||||
return r.map(Path::of);
|
|
||||||
} catch (Exception e) {
|
|
||||||
ErrorEvent.fromThrowable(e).omit().handle();
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
ExternalTerminalType MOBAXTERM = new MobaXTermTerminalType();
|
||||||
public String getWebsite() {
|
|
||||||
return "https://www.netsarang.com/en/xshell/";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
ExternalTerminalType TERMIUS = new TermiusTerminalType();
|
||||||
public boolean supportsTabs() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isRecommended() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsColoredTitle() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void execute(Path file, LaunchConfiguration configuration) throws Exception {
|
|
||||||
SshLocalBridge.init();
|
|
||||||
if (!showInfo()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try (var sc = LocalShell.getShell()) {
|
|
||||||
var b = SshLocalBridge.get();
|
|
||||||
var keyName = b.getIdentityKey().getFileName().toString();
|
|
||||||
var command = CommandBuilder.of()
|
|
||||||
.addFile(file.toString())
|
|
||||||
.add("-url")
|
|
||||||
.addQuoted("ssh://" + b.getUser() + "@localhost:" + b.getPort())
|
|
||||||
.add("-i", keyName);
|
|
||||||
sc.executeSimpleCommand(command);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean showInfo() {
|
|
||||||
boolean set = AppCache.getBoolean("xshellSetup", false);
|
|
||||||
if (set) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var b = SshLocalBridge.get();
|
|
||||||
var keyName = b.getIdentityKey().getFileName().toString();
|
|
||||||
var r = AppWindowHelper.showBlockingAlert(alert -> {
|
|
||||||
alert.setTitle(AppI18n.get("xshellSetup"));
|
|
||||||
alert.setAlertType(Alert.AlertType.NONE);
|
|
||||||
|
|
||||||
var activated = AppI18n.get()
|
|
||||||
.getMarkdownDocumentation("app:xshellSetup")
|
|
||||||
.formatted(b.getIdentityKey(), keyName);
|
|
||||||
var markdown = new MarkdownComp(activated, s -> s)
|
|
||||||
.prefWidth(450)
|
|
||||||
.prefHeight(400)
|
|
||||||
.createRegion();
|
|
||||||
alert.getDialogPane().setContent(markdown);
|
|
||||||
|
|
||||||
alert.getButtonTypes().add(new ButtonType(AppI18n.get("ok"), ButtonBar.ButtonData.OK_DONE));
|
|
||||||
});
|
|
||||||
r.filter(buttonType -> buttonType.getButtonData().isDefaultButton());
|
|
||||||
r.ifPresent(buttonType -> {
|
|
||||||
AppCache.update("xshellSetup", true);
|
|
||||||
});
|
|
||||||
return r.isPresent();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ExternalTerminalType SECURECRT = new WindowsType("app.secureCrt", "SecureCRT") {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Optional<Path> determineInstallation() {
|
|
||||||
try (var sc = LocalShell.getShell().start()) {
|
|
||||||
var env = sc.executeSimpleStringCommand(
|
|
||||||
sc.getShellDialect().getPrintEnvironmentVariableCommand("ProgramFiles"));
|
|
||||||
var file = Path.of(env, "VanDyke Software\\SecureCRT\\SecureCRT.exe");
|
|
||||||
if (!Files.exists(file)) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Optional.of(file);
|
|
||||||
} catch (Exception e) {
|
|
||||||
ErrorEvent.fromThrowable(e).omit().handle();
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsTabs() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isRecommended() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsColoredTitle() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getWebsite() {
|
|
||||||
return "https://www.vandyke.com/products/securecrt/";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void execute(Path file, LaunchConfiguration configuration) throws Exception {
|
|
||||||
try (var sc = LocalShell.getShell()) {
|
|
||||||
SshLocalBridge.init();
|
|
||||||
var b = SshLocalBridge.get();
|
|
||||||
var command = CommandBuilder.of()
|
|
||||||
.addFile(file.toString())
|
|
||||||
.add("/T")
|
|
||||||
.add("/SSH2", "/ACCEPTHOSTKEYS", "/I")
|
|
||||||
.addFile(b.getIdentityKey().toString())
|
|
||||||
.add("/P", "" + b.getPort())
|
|
||||||
.add("/L")
|
|
||||||
.addQuoted(b.getUser())
|
|
||||||
.add("localhost");
|
|
||||||
sc.executeSimpleCommand(command);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ExternalTerminalType MOBAXTERM = new WindowsType("app.mobaXterm", "MobaXterm") {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Optional<Path> determineInstallation() {
|
|
||||||
try {
|
|
||||||
var r = WindowsRegistry.local()
|
|
||||||
.readValue(WindowsRegistry.HKEY_LOCAL_MACHINE, "SOFTWARE\\Classes\\mobaxterm\\DefaultIcon");
|
|
||||||
return r.map(Path::of);
|
|
||||||
} catch (Exception e) {
|
|
||||||
ErrorEvent.fromThrowable(e).omit().handle();
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsTabs() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isRecommended() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsColoredTitle() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getWebsite() {
|
|
||||||
return "https://mobaxterm.mobatek.net/";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void execute(Path file, LaunchConfiguration configuration) throws Exception {
|
|
||||||
try (var sc = LocalShell.getShell()) {
|
|
||||||
SshLocalBridge.init();
|
|
||||||
var b = SshLocalBridge.get();
|
|
||||||
var command = CommandBuilder.of()
|
|
||||||
.addFile("ssh")
|
|
||||||
.addQuoted(b.getUser() + "@localhost")
|
|
||||||
.add("-i")
|
|
||||||
.add("\"$(cygpath \"" + b.getIdentityKey().toString() + "\")\"")
|
|
||||||
.add("-p")
|
|
||||||
.add("" + b.getPort());
|
|
||||||
// Don't use local shell to build as it uses cygwin
|
|
||||||
var rawCommand = command.buildSimple();
|
|
||||||
var script = ScriptHelper.getExecScriptFile(sc, "sh");
|
|
||||||
Files.writeString(Path.of(script.toString()), rawCommand);
|
|
||||||
var fixedFile = script.toString().replaceAll("\\\\", "/").replaceAll("\\s", "\\$0");
|
|
||||||
sc.command(CommandBuilder.of()
|
|
||||||
.addFile(file.toString())
|
|
||||||
.add("-newtab")
|
|
||||||
.add(fixedFile))
|
|
||||||
.execute();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ExternalTerminalType TERMIUS = new ExternalTerminalType() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getId() {
|
|
||||||
return "app.termius";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isAvailable() {
|
|
||||||
try (var sc = LocalShell.getShell()) {
|
|
||||||
return switch (OsType.getLocal()) {
|
|
||||||
case OsType.Linux linux -> {
|
|
||||||
yield Files.exists(Path.of("/opt/Termius"));
|
|
||||||
}
|
|
||||||
case OsType.MacOs macOs -> {
|
|
||||||
yield Files.exists(Path.of("/Applications/Termius.app"));
|
|
||||||
}
|
|
||||||
case OsType.Windows windows -> {
|
|
||||||
var r = WindowsRegistry.local()
|
|
||||||
.readValue(WindowsRegistry.HKEY_CURRENT_USER, "SOFTWARE\\Classes\\termius");
|
|
||||||
yield r.isPresent();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} catch (Exception e) {
|
|
||||||
ErrorEvent.fromThrowable(e).omit().handle();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getWebsite() {
|
|
||||||
return "https://termius.com/";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsTabs() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isRecommended() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsColoredTitle() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void launch(LaunchConfiguration configuration) throws Exception {
|
|
||||||
SshLocalBridge.init();
|
|
||||||
if (!showInfo()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var host = "localhost";
|
|
||||||
var b = SshLocalBridge.get();
|
|
||||||
var port = b.getPort();
|
|
||||||
var user = b.getUser();
|
|
||||||
var name = b.getIdentityKey().getFileName().toString();
|
|
||||||
Hyperlinks.open("termius://app/host-sharing#label=" + name + "&ip=" + host + "&port=" + port + "&username="
|
|
||||||
+ user + "&os=undefined");
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean showInfo() throws IOException {
|
|
||||||
boolean set = AppCache.getBoolean("termiusSetup", false);
|
|
||||||
if (set) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var b = SshLocalBridge.get();
|
|
||||||
var keyContent = Files.readString(b.getIdentityKey());
|
|
||||||
var r = AppWindowHelper.showBlockingAlert(alert -> {
|
|
||||||
alert.setTitle(AppI18n.get("termiusSetup"));
|
|
||||||
alert.setAlertType(Alert.AlertType.NONE);
|
|
||||||
|
|
||||||
var activated = AppI18n.get()
|
|
||||||
.getMarkdownDocumentation("app:termiusSetup")
|
|
||||||
.formatted(b.getIdentityKey(), keyContent);
|
|
||||||
var markdown = new MarkdownComp(activated, s -> s)
|
|
||||||
.prefWidth(450)
|
|
||||||
.prefHeight(450)
|
|
||||||
.createRegion();
|
|
||||||
alert.getDialogPane().setContent(markdown);
|
|
||||||
|
|
||||||
alert.getButtonTypes().add(new ButtonType(AppI18n.get("ok"), ButtonBar.ButtonData.OK_DONE));
|
|
||||||
});
|
|
||||||
r.filter(buttonType -> buttonType.getButtonData().isDefaultButton());
|
|
||||||
r.ifPresent(buttonType -> {
|
|
||||||
AppCache.update("termiusSetup", true);
|
|
||||||
});
|
|
||||||
return r.isPresent();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ExternalTerminalType CMD = new CmdTerminalType();
|
ExternalTerminalType CMD = new CmdTerminalType();
|
||||||
|
|
||||||
|
@ -413,8 +107,8 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsTabs() {
|
public TerminalOpenFormat getOpenFormat() {
|
||||||
return true;
|
return TerminalOpenFormat.NEW_WINDOW_OR_TABBED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -428,11 +122,11 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected CommandBuilder toCommand(LaunchConfiguration configuration) {
|
protected CommandBuilder toCommand(TerminalLaunchConfiguration configuration) {
|
||||||
// Note for later: When debugging konsole launches, it will always open as a child process of
|
// Note for later: When debugging konsole launches, it will always open as a child process of
|
||||||
// IntelliJ/XPipe even though we try to detach it.
|
// IntelliJ/XPipe even though we try to detach it.
|
||||||
// This is not the case for production where it works as expected
|
// This is not the case for production where it works as expected
|
||||||
return CommandBuilder.of().add("--new-tab", "-e").addFile(configuration.getScriptFile());
|
return CommandBuilder.of().addIf(configuration.isPreferTabs(), "--new-tab").add("-e").addFile(configuration.getScriptFile());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
ExternalTerminalType XFCE = new SimplePathType("app.xfce", "xfce4-terminal", true) {
|
ExternalTerminalType XFCE = new SimplePathType("app.xfce", "xfce4-terminal", true) {
|
||||||
|
@ -442,8 +136,8 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsTabs() {
|
public TerminalOpenFormat getOpenFormat() {
|
||||||
return true;
|
return TerminalOpenFormat.NEW_WINDOW_OR_TABBED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -457,9 +151,10 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected CommandBuilder toCommand(LaunchConfiguration configuration) {
|
protected CommandBuilder toCommand(TerminalLaunchConfiguration configuration) {
|
||||||
return CommandBuilder.of()
|
return CommandBuilder.of()
|
||||||
.add("--tab", "--title")
|
.addIf(configuration.isPreferTabs(),"--tab")
|
||||||
|
.add("--title")
|
||||||
.addQuoted(configuration.getColoredTitle())
|
.addQuoted(configuration.getColoredTitle())
|
||||||
.add("--command")
|
.add("--command")
|
||||||
.addFile(configuration.getScriptFile());
|
.addFile(configuration.getScriptFile());
|
||||||
|
@ -472,8 +167,8 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsTabs() {
|
public TerminalOpenFormat getOpenFormat() {
|
||||||
return false;
|
return TerminalOpenFormat.NEW_WINDOW;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -487,7 +182,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected CommandBuilder toCommand(LaunchConfiguration configuration) {
|
protected CommandBuilder toCommand(TerminalLaunchConfiguration configuration) {
|
||||||
return CommandBuilder.of()
|
return CommandBuilder.of()
|
||||||
.add("--title")
|
.add("--title")
|
||||||
.addQuoted(configuration.getColoredTitle())
|
.addQuoted(configuration.getColoredTitle())
|
||||||
|
@ -502,8 +197,8 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsTabs() {
|
public TerminalOpenFormat getOpenFormat() {
|
||||||
return true;
|
return TerminalOpenFormat.NEW_WINDOW_OR_TABBED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -517,8 +212,8 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected CommandBuilder toCommand(LaunchConfiguration configuration) {
|
protected CommandBuilder toCommand(TerminalLaunchConfiguration configuration) {
|
||||||
return CommandBuilder.of().add("--new-tab").add("-e").addFile(configuration.getScriptFile());
|
return CommandBuilder.of().addIf(configuration.isPreferTabs(), "--new-tab").add("-e").addFile(configuration.getScriptFile());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
ExternalTerminalType TILIX = new SimplePathType("app.tilix", "tilix", true) {
|
ExternalTerminalType TILIX = new SimplePathType("app.tilix", "tilix", true) {
|
||||||
|
@ -528,8 +223,8 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsTabs() {
|
public TerminalOpenFormat getOpenFormat() {
|
||||||
return false;
|
return TerminalOpenFormat.NEW_WINDOW;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -543,7 +238,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected CommandBuilder toCommand(LaunchConfiguration configuration) {
|
protected CommandBuilder toCommand(TerminalLaunchConfiguration configuration) {
|
||||||
return CommandBuilder.of()
|
return CommandBuilder.of()
|
||||||
.add("-t")
|
.add("-t")
|
||||||
.addQuoted(configuration.getColoredTitle())
|
.addQuoted(configuration.getColoredTitle())
|
||||||
|
@ -557,11 +252,6 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
return "https://gnome-terminator.org/";
|
return "https://gnome-terminator.org/";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsTabs() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isRecommended() {
|
public boolean isRecommended() {
|
||||||
return true;
|
return true;
|
||||||
|
@ -573,13 +263,18 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected CommandBuilder toCommand(LaunchConfiguration configuration) {
|
public TerminalOpenFormat getOpenFormat() {
|
||||||
|
return TerminalOpenFormat.NEW_WINDOW_OR_TABBED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CommandBuilder toCommand(TerminalLaunchConfiguration configuration) {
|
||||||
return CommandBuilder.of()
|
return CommandBuilder.of()
|
||||||
.add("-e")
|
.add("-e")
|
||||||
.addFile(configuration.getScriptFile())
|
.addFile(configuration.getScriptFile())
|
||||||
.add("-T")
|
.add("-T")
|
||||||
.addQuoted(configuration.getColoredTitle())
|
.addQuoted(configuration.getColoredTitle())
|
||||||
.add("--new-tab");
|
.addIf(configuration.isPreferTabs(), "--new-tab");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
ExternalTerminalType TERMINOLOGY = new SimplePathType("app.terminology", "terminology", true) {
|
ExternalTerminalType TERMINOLOGY = new SimplePathType("app.terminology", "terminology", true) {
|
||||||
|
@ -588,11 +283,6 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
return "https://github.com/borisfaure/terminology";
|
return "https://github.com/borisfaure/terminology";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsTabs() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isRecommended() {
|
public boolean isRecommended() {
|
||||||
return true;
|
return true;
|
||||||
|
@ -604,8 +294,14 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected CommandBuilder toCommand(LaunchConfiguration configuration) {
|
public TerminalOpenFormat getOpenFormat() {
|
||||||
|
return TerminalOpenFormat.NEW_WINDOW_OR_TABBED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CommandBuilder toCommand(TerminalLaunchConfiguration configuration) {
|
||||||
return CommandBuilder.of()
|
return CommandBuilder.of()
|
||||||
|
.addIf(!configuration.isPreferTabs(), "-s")
|
||||||
.add("-T")
|
.add("-T")
|
||||||
.addQuoted(configuration.getColoredTitle())
|
.addQuoted(configuration.getColoredTitle())
|
||||||
.add("-2")
|
.add("-2")
|
||||||
|
@ -625,11 +321,6 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
return "https://github.com/Guake/guake";
|
return "https://github.com/Guake/guake";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsTabs() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isRecommended() {
|
public boolean isRecommended() {
|
||||||
return true;
|
return true;
|
||||||
|
@ -641,7 +332,12 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected CommandBuilder toCommand(LaunchConfiguration configuration) {
|
public TerminalOpenFormat getOpenFormat() {
|
||||||
|
return TerminalOpenFormat.TABBED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CommandBuilder toCommand(TerminalLaunchConfiguration configuration) {
|
||||||
return CommandBuilder.of()
|
return CommandBuilder.of()
|
||||||
.add("-n", "~")
|
.add("-n", "~")
|
||||||
.add("-r")
|
.add("-r")
|
||||||
|
@ -656,11 +352,6 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
return "https://github.com/lanoxx/tilda";
|
return "https://github.com/lanoxx/tilda";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsTabs() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isRecommended() {
|
public boolean isRecommended() {
|
||||||
return true;
|
return true;
|
||||||
|
@ -672,7 +363,12 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected CommandBuilder toCommand(LaunchConfiguration configuration) {
|
public TerminalOpenFormat getOpenFormat() {
|
||||||
|
return TerminalOpenFormat.TABBED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CommandBuilder toCommand(TerminalLaunchConfiguration configuration) {
|
||||||
return CommandBuilder.of().add("-c").addFile(configuration.getScriptFile());
|
return CommandBuilder.of().add("-c").addFile(configuration.getScriptFile());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -683,8 +379,8 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsTabs() {
|
public TerminalOpenFormat getOpenFormat() {
|
||||||
return false;
|
return TerminalOpenFormat.NEW_WINDOW;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -698,7 +394,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected CommandBuilder toCommand(LaunchConfiguration configuration) {
|
protected CommandBuilder toCommand(TerminalLaunchConfiguration configuration) {
|
||||||
return CommandBuilder.of()
|
return CommandBuilder.of()
|
||||||
.add("-title")
|
.add("-title")
|
||||||
.addQuoted(configuration.getColoredTitle())
|
.addQuoted(configuration.getColoredTitle())
|
||||||
|
@ -719,8 +415,8 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsTabs() {
|
public TerminalOpenFormat getOpenFormat() {
|
||||||
return false;
|
return TerminalOpenFormat.NEW_WINDOW;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -734,7 +430,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected CommandBuilder toCommand(LaunchConfiguration configuration) {
|
protected CommandBuilder toCommand(TerminalLaunchConfiguration configuration) {
|
||||||
return CommandBuilder.of().add("-C").addFile(configuration.getScriptFile());
|
return CommandBuilder.of().add("-C").addFile(configuration.getScriptFile());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -751,8 +447,8 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsTabs() {
|
public TerminalOpenFormat getOpenFormat() {
|
||||||
return false;
|
return TerminalOpenFormat.NEW_WINDOW;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -766,22 +462,22 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected CommandBuilder toCommand(LaunchConfiguration configuration) {
|
protected CommandBuilder toCommand(TerminalLaunchConfiguration configuration) {
|
||||||
return CommandBuilder.of().add("-e").add(configuration.getDialectLaunchCommand());
|
return CommandBuilder.of().add("-e").add(configuration.getDialectLaunchCommand());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
ExternalTerminalType MACOS_TERMINAL = new MacOsType("app.macosTerminal", "Terminal") {
|
ExternalTerminalType MACOS_TERMINAL = new MacOsType("app.macosTerminal", "Terminal") {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TerminalOpenFormat getOpenFormat() {
|
||||||
|
return TerminalOpenFormat.TABBED;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getProcessHierarchyOffset() {
|
public int getProcessHierarchyOffset() {
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsTabs() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isRecommended() {
|
public boolean isRecommended() {
|
||||||
return false;
|
return false;
|
||||||
|
@ -793,7 +489,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void launch(LaunchConfiguration configuration) throws Exception {
|
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||||
LocalShell.getShell()
|
LocalShell.getShell()
|
||||||
.executeSimpleCommand(CommandBuilder.of()
|
.executeSimpleCommand(CommandBuilder.of()
|
||||||
.add("open", "-a")
|
.add("open", "-a")
|
||||||
|
@ -803,6 +499,11 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
};
|
};
|
||||||
ExternalTerminalType ITERM2 = new MacOsType("app.iterm2", "iTerm") {
|
ExternalTerminalType ITERM2 = new MacOsType("app.iterm2", "iTerm") {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TerminalOpenFormat getOpenFormat() {
|
||||||
|
return TerminalOpenFormat.TABBED;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getProcessHierarchyOffset() {
|
public int getProcessHierarchyOffset() {
|
||||||
return 3;
|
return 3;
|
||||||
|
@ -813,11 +514,6 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
return "https://iterm2.com/";
|
return "https://iterm2.com/";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsTabs() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isRecommended() {
|
public boolean isRecommended() {
|
||||||
return true;
|
return true;
|
||||||
|
@ -829,7 +525,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void launch(LaunchConfiguration configuration) throws Exception {
|
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||||
LocalShell.getShell()
|
LocalShell.getShell()
|
||||||
.executeSimpleCommand(CommandBuilder.of()
|
.executeSimpleCommand(CommandBuilder.of()
|
||||||
.add("open", "-a")
|
.add("open", "-a")
|
||||||
|
@ -837,75 +533,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
.addFile(configuration.getScriptFile()));
|
.addFile(configuration.getScriptFile()));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
ExternalTerminalType WARP = new MacOsType("app.warp", "Warp") {
|
ExternalTerminalType WARP = new WarpTerminalType();
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getProcessHierarchyOffset() {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getWebsite() {
|
|
||||||
return "https://www.warp.dev/";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsTabs() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isRecommended() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsColoredTitle() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean shouldClear() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void launch(LaunchConfiguration configuration) throws Exception {
|
|
||||||
LocalShell.getShell()
|
|
||||||
.executeSimpleCommand(CommandBuilder.of()
|
|
||||||
.add("open", "-a")
|
|
||||||
.addQuoted("Warp.app")
|
|
||||||
.addFile(configuration.getScriptFile()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FailableFunction<LaunchConfiguration, String, Exception> remoteLaunchCommand(
|
|
||||||
ShellDialect systemDialect) {
|
|
||||||
return launchConfiguration -> {
|
|
||||||
var toExecute = CommandBuilder.of()
|
|
||||||
.add("open", "-a")
|
|
||||||
.addQuoted("Warp.app")
|
|
||||||
.addFile(launchConfiguration.getScriptFile());
|
|
||||||
return toExecute.buildSimple();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TerminalInitFunction additionalInitCommands() {
|
|
||||||
return TerminalInitFunction.of(sc -> {
|
|
||||||
if (sc.getShellDialect() == ShellDialects.ZSH) {
|
|
||||||
return "printf '\\eP$f{\"hook\": \"SourcedRcFileForWarp\", \"value\": { \"shell\": \"zsh\"}}\\x9c'";
|
|
||||||
}
|
|
||||||
if (sc.getShellDialect() == ShellDialects.BASH) {
|
|
||||||
return "printf '\\eP$f{\"hook\": \"SourcedRcFileForWarp\", \"value\": { \"shell\": \"bash\"}}\\x9c'";
|
|
||||||
}
|
|
||||||
if (sc.getShellDialect() == ShellDialects.FISH) {
|
|
||||||
return "printf '\\eP$f{\"hook\": \"SourcedRcFileForWarp\", \"value\": { \"shell\": \"fish\"}}\\x9c'";
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
ExternalTerminalType CUSTOM = new CustomTerminalType();
|
ExternalTerminalType CUSTOM = new CustomTerminalType();
|
||||||
List<ExternalTerminalType> WINDOWS_TERMINALS = List.of(
|
List<ExternalTerminalType> WINDOWS_TERMINALS = List.of(
|
||||||
WindowsTerminalType.WINDOWS_TERMINAL_CANARY,
|
WindowsTerminalType.WINDOWS_TERMINAL_CANARY,
|
||||||
|
@ -998,7 +626,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
return TerminalInitFunction.none();
|
return TerminalInitFunction.none();
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean supportsTabs();
|
TerminalOpenFormat getOpenFormat();
|
||||||
|
|
||||||
default String getWebsite() {
|
default String getWebsite() {
|
||||||
return null;
|
return null;
|
||||||
|
@ -1012,9 +640,9 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
default void launch(LaunchConfiguration configuration) throws Exception {}
|
default void launch(TerminalLaunchConfiguration configuration) throws Exception {}
|
||||||
|
|
||||||
default FailableFunction<LaunchConfiguration, String, Exception> remoteLaunchCommand(ShellDialect systemDialect) {
|
default FailableFunction<TerminalLaunchConfiguration, String, Exception> remoteLaunchCommand(ShellDialect systemDialect) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1025,7 +653,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void launch(LaunchConfiguration configuration) throws Exception {
|
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||||
var location = determineFromPath();
|
var location = determineFromPath();
|
||||||
if (location.isEmpty()) {
|
if (location.isEmpty()) {
|
||||||
location = determineInstallation();
|
location = determineInstallation();
|
||||||
|
@ -1038,24 +666,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
execute(location.get(), configuration);
|
execute(location.get(), configuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void execute(Path file, LaunchConfiguration configuration) throws Exception;
|
protected abstract void execute(Path file, TerminalLaunchConfiguration configuration) throws Exception;
|
||||||
}
|
|
||||||
|
|
||||||
@Value
|
|
||||||
class LaunchConfiguration {
|
|
||||||
DataColor color;
|
|
||||||
String coloredTitle;
|
|
||||||
String cleanTitle;
|
|
||||||
|
|
||||||
@With
|
|
||||||
FilePath scriptFile;
|
|
||||||
|
|
||||||
ShellDialect scriptDialect;
|
|
||||||
|
|
||||||
public CommandBuilder getDialectLaunchCommand() {
|
|
||||||
var open = scriptDialect.getOpenScriptCommand(scriptFile.toString());
|
|
||||||
return open;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class MacOsType extends ExternalApplicationType.MacApplication
|
abstract class MacOsType extends ExternalApplicationType.MacApplication
|
||||||
|
@ -1082,13 +693,13 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void launch(LaunchConfiguration configuration) throws Exception {
|
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||||
var args = toCommand(configuration);
|
var args = toCommand(configuration);
|
||||||
launch(configuration.getColoredTitle(), args);
|
launch(configuration.getColoredTitle(), args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public FailableFunction<LaunchConfiguration, String, Exception> remoteLaunchCommand(
|
public FailableFunction<TerminalLaunchConfiguration, String, Exception> remoteLaunchCommand(
|
||||||
ShellDialect systemDialect) {
|
ShellDialect systemDialect) {
|
||||||
return launchConfiguration -> {
|
return launchConfiguration -> {
|
||||||
var args = toCommand(launchConfiguration);
|
var args = toCommand(launchConfiguration);
|
||||||
|
@ -1100,6 +711,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract CommandBuilder toCommand(LaunchConfiguration configuration) throws Exception;
|
protected abstract CommandBuilder toCommand(TerminalLaunchConfiguration configuration) throws Exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,13 +14,13 @@ public class GnomeTerminalType extends ExternalTerminalType.PathCheckType implem
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getWebsite() {
|
public TerminalOpenFormat getOpenFormat() {
|
||||||
return "https://help.gnome.org/users/gnome-terminal/stable/";
|
return TerminalOpenFormat.NEW_WINDOW;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsTabs() {
|
public String getWebsite() {
|
||||||
return false;
|
return "https://help.gnome.org/users/gnome-terminal/stable/";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -34,7 +34,7 @@ public class GnomeTerminalType extends ExternalTerminalType.PathCheckType implem
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void launch(LaunchConfiguration configuration) throws Exception {
|
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||||
try (ShellControl pc = LocalShell.getShell()) {
|
try (ShellControl pc = LocalShell.getShell()) {
|
||||||
CommandSupport.isInPathOrThrow(pc, executable, toTranslatedString().getValue(), null);
|
CommandSupport.isInPathOrThrow(pc, executable, toTranslatedString().getValue(), null);
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ public class GnomeTerminalType extends ExternalTerminalType.PathCheckType implem
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public FailableFunction<LaunchConfiguration, String, Exception> remoteLaunchCommand(ShellDialect systemDialect) {
|
public FailableFunction<TerminalLaunchConfiguration, String, Exception> remoteLaunchCommand(ShellDialect systemDialect) {
|
||||||
return launchConfiguration -> {
|
return launchConfiguration -> {
|
||||||
var toExecute = CommandBuilder.of()
|
var toExecute = CommandBuilder.of()
|
||||||
.add(executable, "-v", "--title")
|
.add(executable, "-v", "--title")
|
||||||
|
|
|
@ -27,7 +27,7 @@ public interface KittyTerminalType extends ExternalTerminalType, TrackableTermin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void open(ExternalTerminalType.LaunchConfiguration configuration, CommandBuilder socketWrite)
|
private static void open(TerminalLaunchConfiguration configuration, CommandBuilder socketWrite)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
try (var sc = LocalShell.getShell().start()) {
|
try (var sc = LocalShell.getShell().start()) {
|
||||||
var payload = JsonNodeFactory.instance.objectNode();
|
var payload = JsonNodeFactory.instance.objectNode();
|
||||||
|
@ -73,11 +73,6 @@ public interface KittyTerminalType extends ExternalTerminalType, TrackableTermin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
default boolean supportsTabs() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default String getWebsite() {
|
default String getWebsite() {
|
||||||
return "https://github.com/kovidgoyal/kitty";
|
return "https://github.com/kovidgoyal/kitty";
|
||||||
|
@ -89,6 +84,11 @@ public interface KittyTerminalType extends ExternalTerminalType, TrackableTermin
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default TerminalOpenFormat getOpenFormat() {
|
||||||
|
return TerminalOpenFormat.TABBED;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default boolean supportsColoredTitle() {
|
default boolean supportsColoredTitle() {
|
||||||
return true;
|
return true;
|
||||||
|
@ -116,7 +116,7 @@ public interface KittyTerminalType extends ExternalTerminalType, TrackableTermin
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void launch(LaunchConfiguration configuration) throws Exception {
|
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||||
try (var sc = LocalShell.getShell().start()) {
|
try (var sc = LocalShell.getShell().start()) {
|
||||||
CommandSupport.isInPathOrThrow(sc, "kitty", "Kitty", null);
|
CommandSupport.isInPathOrThrow(sc, "kitty", "Kitty", null);
|
||||||
CommandSupport.isInPathOrThrow(sc, "socat", "socat", null);
|
CommandSupport.isInPathOrThrow(sc, "socat", "socat", null);
|
||||||
|
@ -167,7 +167,7 @@ public interface KittyTerminalType extends ExternalTerminalType, TrackableTermin
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void launch(LaunchConfiguration configuration) throws Exception {
|
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||||
// We use the absolute path to force the usage of macOS netcat
|
// We use the absolute path to force the usage of macOS netcat
|
||||||
// Homebrew versions have different option formats
|
// Homebrew versions have different option formats
|
||||||
try (var sc = LocalShell.getShell().start()) {
|
try (var sc = LocalShell.getShell().start()) {
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
package io.xpipe.app.terminal;
|
||||||
|
|
||||||
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
|
import io.xpipe.app.util.LocalShell;
|
||||||
|
import io.xpipe.app.util.ScriptHelper;
|
||||||
|
import io.xpipe.app.util.SshLocalBridge;
|
||||||
|
import io.xpipe.app.util.WindowsRegistry;
|
||||||
|
import io.xpipe.core.process.CommandBuilder;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class MobaXTermTerminalType extends ExternalTerminalType.WindowsType {
|
||||||
|
|
||||||
|
public MobaXTermTerminalType() {super("app.mobaXterm", "MobaXterm");}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TerminalOpenFormat getOpenFormat() {
|
||||||
|
return TerminalOpenFormat.TABBED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Optional<Path> determineInstallation() {
|
||||||
|
try {
|
||||||
|
var r = WindowsRegistry.local().readValue(WindowsRegistry.HKEY_LOCAL_MACHINE, "SOFTWARE\\Classes\\mobaxterm\\DefaultIcon");
|
||||||
|
return r.map(Path::of);
|
||||||
|
} catch (Exception e) {
|
||||||
|
ErrorEvent.fromThrowable(e).omit().handle();
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRecommended() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsColoredTitle() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getWebsite() {
|
||||||
|
return "https://mobaxterm.mobatek.net/";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void execute(Path file, TerminalLaunchConfiguration configuration) throws Exception {
|
||||||
|
try (var sc = LocalShell.getShell()) {
|
||||||
|
SshLocalBridge.init();
|
||||||
|
var b = SshLocalBridge.get();
|
||||||
|
var command = CommandBuilder.of().addFile("ssh").addQuoted(b.getUser() + "@localhost").add("-i").add(
|
||||||
|
"\"$(cygpath \"" + b.getIdentityKey().toString() + "\")\"").add("-p").add("" + b.getPort());
|
||||||
|
// Don't use local shell to build as it uses cygwin
|
||||||
|
var rawCommand = command.buildSimple();
|
||||||
|
var script = ScriptHelper.getExecScriptFile(sc, "sh");
|
||||||
|
Files.writeString(Path.of(script.toString()), rawCommand);
|
||||||
|
var fixedFile = script.toString().replaceAll("\\\\", "/").replaceAll("\\s", "\\$0");
|
||||||
|
sc.command(CommandBuilder.of().addFile(file.toString()).add("-newtab").add(fixedFile)).execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,15 +15,15 @@ public class PowerShellTerminalType extends ExternalTerminalType.SimplePathType
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getProcessHierarchyOffset() {
|
public TerminalOpenFormat getOpenFormat() {
|
||||||
var powershell = ProcessControlProvider.get().getEffectiveLocalDialect() == POWERSHELL
|
return TerminalOpenFormat.NEW_WINDOW;
|
||||||
|| AppPrefs.get().enableTerminalLogging().get();
|
|
||||||
return powershell ? -1 : 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsTabs() {
|
public int getProcessHierarchyOffset() {
|
||||||
return false;
|
var powershell = ProcessControlProvider.get().getEffectiveLocalDialect() == ShellDialects.POWERSHELL
|
||||||
|
|| AppPrefs.get().enableTerminalLogging().get();
|
||||||
|
return powershell ? -1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -37,7 +37,7 @@ public class PowerShellTerminalType extends ExternalTerminalType.SimplePathType
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected CommandBuilder toCommand(LaunchConfiguration configuration) {
|
protected CommandBuilder toCommand(TerminalLaunchConfiguration configuration) {
|
||||||
if (configuration.getScriptDialect().equals(ShellDialects.POWERSHELL)) {
|
if (configuration.getScriptDialect().equals(ShellDialects.POWERSHELL)) {
|
||||||
return CommandBuilder.of()
|
return CommandBuilder.of()
|
||||||
.add("-ExecutionPolicy", "Bypass")
|
.add("-ExecutionPolicy", "Bypass")
|
||||||
|
|
|
@ -12,13 +12,13 @@ public class PwshTerminalType extends ExternalTerminalType.SimplePathType implem
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getWebsite() {
|
public TerminalOpenFormat getOpenFormat() {
|
||||||
return "https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell?view=powershell-7.4";
|
return TerminalOpenFormat.NEW_WINDOW;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsTabs() {
|
public String getWebsite() {
|
||||||
return false;
|
return "https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell?view=powershell-7.4";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -32,7 +32,7 @@ public class PwshTerminalType extends ExternalTerminalType.SimplePathType implem
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected CommandBuilder toCommand(LaunchConfiguration configuration) {
|
protected CommandBuilder toCommand(TerminalLaunchConfiguration configuration) {
|
||||||
return CommandBuilder.of()
|
return CommandBuilder.of()
|
||||||
.add("-ExecutionPolicy", "Bypass")
|
.add("-ExecutionPolicy", "Bypass")
|
||||||
.add("-EncodedCommand")
|
.add("-EncodedCommand")
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
package io.xpipe.app.terminal;
|
||||||
|
|
||||||
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
|
import io.xpipe.app.util.LocalShell;
|
||||||
|
import io.xpipe.app.util.SshLocalBridge;
|
||||||
|
import io.xpipe.core.process.CommandBuilder;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class SecureCrtTerminalType extends ExternalTerminalType.WindowsType {
|
||||||
|
|
||||||
|
public SecureCrtTerminalType() {super("app.secureCrt", "SecureCRT");}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TerminalOpenFormat getOpenFormat() {
|
||||||
|
return TerminalOpenFormat.TABBED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Optional<Path> determineInstallation() {
|
||||||
|
try (var sc = LocalShell.getShell().start()) {
|
||||||
|
var env = sc.executeSimpleStringCommand(sc.getShellDialect().getPrintEnvironmentVariableCommand("ProgramFiles"));
|
||||||
|
var file = Path.of(env, "VanDyke Software\\SecureCRT\\SecureCRT.exe");
|
||||||
|
if (!Files.exists(file)) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.of(file);
|
||||||
|
} catch (Exception e) {
|
||||||
|
ErrorEvent.fromThrowable(e).omit().handle();
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRecommended() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsColoredTitle() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getWebsite() {
|
||||||
|
return "https://www.vandyke.com/products/securecrt/";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void execute(Path file, TerminalLaunchConfiguration configuration) throws Exception {
|
||||||
|
try (var sc = LocalShell.getShell()) {
|
||||||
|
SshLocalBridge.init();
|
||||||
|
var b = SshLocalBridge.get();
|
||||||
|
var command = CommandBuilder.of().addFile(file.toString()).add("/T").add("/SSH2", "/ACCEPTHOSTKEYS", "/I").addFile(
|
||||||
|
b.getIdentityKey().toString()).add("/P", "" + b.getPort()).add("/L").addQuoted(b.getUser()).add("localhost");
|
||||||
|
sc.executeSimpleCommand(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,8 +15,8 @@ public interface TabbyTerminalType extends ExternalTerminalType, TrackableTermin
|
||||||
ExternalTerminalType TABBY_MAC_OS = new MacOs();
|
ExternalTerminalType TABBY_MAC_OS = new MacOs();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default boolean supportsTabs() {
|
default TerminalOpenFormat getOpenFormat() {
|
||||||
return true;
|
return TerminalOpenFormat.TABBED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -67,7 +67,7 @@ public interface TabbyTerminalType extends ExternalTerminalType, TrackableTermin
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void execute(Path file, LaunchConfiguration configuration) throws Exception {
|
protected void execute(Path file, TerminalLaunchConfiguration configuration) throws Exception {
|
||||||
// Tabby has a very weird handling of output, even detaching with start does not prevent it from printing
|
// Tabby has a very weird handling of output, even detaching with start does not prevent it from printing
|
||||||
if (configuration.getScriptDialect().equals(ShellDialects.CMD)) {
|
if (configuration.getScriptDialect().equals(ShellDialects.CMD)) {
|
||||||
// It also freezes with any other input than .bat files, why?
|
// It also freezes with any other input than .bat files, why?
|
||||||
|
@ -124,7 +124,7 @@ public interface TabbyTerminalType extends ExternalTerminalType, TrackableTermin
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void launch(LaunchConfiguration configuration) throws Exception {
|
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||||
LocalShell.getShell()
|
LocalShell.getShell()
|
||||||
.executeSimpleCommand(CommandBuilder.of()
|
.executeSimpleCommand(CommandBuilder.of()
|
||||||
.add("open", "-a")
|
.add("open", "-a")
|
||||||
|
|
|
@ -0,0 +1,125 @@
|
||||||
|
package io.xpipe.app.terminal;
|
||||||
|
|
||||||
|
import io.xpipe.app.core.AppProperties;
|
||||||
|
import io.xpipe.app.ext.ProcessControlProvider;
|
||||||
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
|
import io.xpipe.app.storage.DataColor;
|
||||||
|
import io.xpipe.app.storage.DataStorage;
|
||||||
|
import io.xpipe.app.storage.DataStoreEntry;
|
||||||
|
import io.xpipe.app.util.LicenseProvider;
|
||||||
|
import io.xpipe.app.util.LicenseRequiredException;
|
||||||
|
import io.xpipe.app.util.LocalShell;
|
||||||
|
import io.xpipe.app.util.ScriptHelper;
|
||||||
|
import io.xpipe.core.process.CommandBuilder;
|
||||||
|
import io.xpipe.core.process.OsType;
|
||||||
|
import io.xpipe.core.process.ShellDialect;
|
||||||
|
import io.xpipe.core.process.ShellDialects;
|
||||||
|
import io.xpipe.core.store.FilePath;
|
||||||
|
import lombok.Value;
|
||||||
|
import lombok.With;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Value
|
||||||
|
public class TerminalLaunchConfiguration {
|
||||||
|
DataColor color;
|
||||||
|
String coloredTitle;
|
||||||
|
String cleanTitle;
|
||||||
|
boolean preferTabs;
|
||||||
|
|
||||||
|
@With
|
||||||
|
FilePath scriptFile;
|
||||||
|
|
||||||
|
ShellDialect scriptDialect;
|
||||||
|
|
||||||
|
private static final DateTimeFormatter DATE_FORMATTER =
|
||||||
|
DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss").withZone(ZoneId.systemDefault());
|
||||||
|
|
||||||
|
public static TerminalLaunchConfiguration create(
|
||||||
|
UUID request, DataStoreEntry entry, String cleanTitle, String adjustedTitle, boolean preferTabs
|
||||||
|
) throws Exception {
|
||||||
|
var color = entry != null ? DataStorage.get().getEffectiveColor(entry) : null;
|
||||||
|
var d = ProcessControlProvider.get().getEffectiveLocalDialect();
|
||||||
|
var launcherScript = d.terminalLauncherScript(request, adjustedTitle);
|
||||||
|
var preparationScript = ScriptHelper.createLocalExecScript(launcherScript);
|
||||||
|
|
||||||
|
if (!AppPrefs.get().enableTerminalLogging().get()) {
|
||||||
|
var config = new TerminalLaunchConfiguration(
|
||||||
|
entry != null ? color : null, adjustedTitle, cleanTitle, preferTabs, preparationScript, d);
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
var feature = LicenseProvider.get().getFeature("logging");
|
||||||
|
var supported = feature.isSupported();
|
||||||
|
if (!supported) {
|
||||||
|
throw new LicenseRequiredException(feature);
|
||||||
|
}
|
||||||
|
|
||||||
|
var logDir = AppProperties.get().getDataDir().resolve("sessions");
|
||||||
|
Files.createDirectories(logDir);
|
||||||
|
var logFile = logDir.resolve(new FilePath(DataStorage.get().getStoreEntryDisplayName(entry) + " ("
|
||||||
|
+ DATE_FORMATTER.format(Instant.now()) + ").log")
|
||||||
|
.fileSystemCompatible(OsType.getLocal())
|
||||||
|
.toString()
|
||||||
|
.replaceAll(" ", "_"));
|
||||||
|
try (var sc = LocalShell.getShell().start()) {
|
||||||
|
if (OsType.getLocal() == OsType.WINDOWS) {
|
||||||
|
var content =
|
||||||
|
"""
|
||||||
|
echo 'Transcript started, output file is "sessions\\%s"'
|
||||||
|
Start-Transcript -Force -LiteralPath "%s" > $Out-Null
|
||||||
|
& %s
|
||||||
|
Stop-Transcript > $Out-Null
|
||||||
|
echo 'Transcript stopped, output file is "sessions\\%s"'
|
||||||
|
"""
|
||||||
|
.formatted(
|
||||||
|
logFile.getFileName().toString(),
|
||||||
|
logFile,
|
||||||
|
preparationScript,
|
||||||
|
logFile.getFileName().toString());
|
||||||
|
var ps = ScriptHelper.createExecScript(ShellDialects.POWERSHELL, sc, content);
|
||||||
|
var config = new TerminalLaunchConfiguration(
|
||||||
|
entry != null ? color : null, adjustedTitle, cleanTitle, preferTabs, ps, ShellDialects.POWERSHELL);
|
||||||
|
return config;
|
||||||
|
} else {
|
||||||
|
var found = sc.command(sc.getShellDialect().getWhichCommand("script"))
|
||||||
|
.executeAndCheck();
|
||||||
|
if (!found) {
|
||||||
|
var suffix = sc.getOsType() == OsType.MACOS
|
||||||
|
? "This command is available in the util-linux package which can be installed via homebrew."
|
||||||
|
: "This command is available in the util-linux package.";
|
||||||
|
throw ErrorEvent.expected(new IllegalStateException(
|
||||||
|
"Logging requires the script command to be installed. " + suffix));
|
||||||
|
}
|
||||||
|
|
||||||
|
var content = sc.getOsType() == OsType.MACOS || sc.getOsType() == OsType.BSD
|
||||||
|
? """
|
||||||
|
echo "Transcript started, output file is sessions/%s"
|
||||||
|
script -e -q "%s" "%s"
|
||||||
|
echo "Transcript stopped, output file is sessions/%s"
|
||||||
|
"""
|
||||||
|
.formatted(logFile.getFileName(), logFile, preparationScript, logFile.getFileName())
|
||||||
|
: """
|
||||||
|
echo "Transcript started, output file is sessions/%s"
|
||||||
|
script --quiet --command "%s" "%s"
|
||||||
|
echo "Transcript stopped, output file is sessions/%s"
|
||||||
|
"""
|
||||||
|
.formatted(logFile.getFileName(), preparationScript, logFile, logFile.getFileName());
|
||||||
|
var ps = ScriptHelper.createExecScript(sc.getShellDialect(), sc, content);
|
||||||
|
var config = new TerminalLaunchConfiguration(
|
||||||
|
entry != null ? color : null, adjustedTitle, cleanTitle, preferTabs, ps, sc.getShellDialect());
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandBuilder getDialectLaunchCommand() {
|
||||||
|
var open = scriptDialect.getOpenScriptCommand(scriptFile.toString());
|
||||||
|
return open;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,25 +1,16 @@
|
||||||
package io.xpipe.app.terminal;
|
package io.xpipe.app.terminal;
|
||||||
|
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.core.AppProperties;
|
|
||||||
import io.xpipe.app.ext.ProcessControlProvider;
|
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.storage.DataStoreEntry;
|
import io.xpipe.app.storage.DataStoreEntry;
|
||||||
import io.xpipe.app.util.LicenseProvider;
|
|
||||||
import io.xpipe.app.util.LicenseRequiredException;
|
|
||||||
import io.xpipe.app.util.LocalShell;
|
import io.xpipe.app.util.LocalShell;
|
||||||
import io.xpipe.app.util.ScriptHelper;
|
import io.xpipe.app.util.ScriptHelper;
|
||||||
import io.xpipe.core.process.*;
|
import io.xpipe.core.process.*;
|
||||||
import io.xpipe.core.store.FilePath;
|
|
||||||
import io.xpipe.core.util.FailableFunction;
|
import io.xpipe.core.util.FailableFunction;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.time.ZoneId;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@ -41,24 +32,24 @@ public class TerminalLauncher {
|
||||||
&& AppPrefs.get().clearTerminalOnInit().get(),
|
&& AppPrefs.get().clearTerminalOnInit().get(),
|
||||||
TerminalInitFunction.none()),
|
TerminalInitFunction.none()),
|
||||||
true);
|
true);
|
||||||
var config = new ExternalTerminalType.LaunchConfiguration(null, title, title, script, sc.getShellDialect());
|
var config = new TerminalLaunchConfiguration(null, title, title, true, script, sc.getShellDialect());
|
||||||
type.launch(config);
|
type.launch(config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void open(String title, ProcessControl cc) throws Exception {
|
public static void open(String title, ProcessControl cc) throws Exception {
|
||||||
open(null, title, null, cc, UUID.randomUUID());
|
open(null, title, null, cc, UUID.randomUUID(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void open(String title, ProcessControl cc, UUID request) throws Exception {
|
public static void open(String title, ProcessControl cc, UUID request) throws Exception {
|
||||||
open(null, title, null, cc, request);
|
open(null, title, null, cc, request, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void open(DataStoreEntry entry, String title, String directory, ProcessControl cc) throws Exception {
|
public static void open(DataStoreEntry entry, String title, String directory, ProcessControl cc) throws Exception {
|
||||||
open(entry, title, directory, cc, UUID.randomUUID());
|
open(entry, title, directory, cc, UUID.randomUUID(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void open(DataStoreEntry entry, String title, String directory, ProcessControl cc, UUID request)
|
public static void open(DataStoreEntry entry, String title, String directory, ProcessControl cc, UUID request, boolean preferTabs)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
var type = AppPrefs.get().terminalType().getValue();
|
var type = AppPrefs.get().terminalType().getValue();
|
||||||
if (type == null) {
|
if (type == null) {
|
||||||
|
@ -76,7 +67,7 @@ public class TerminalLauncher {
|
||||||
&& type.shouldClear()
|
&& type.shouldClear()
|
||||||
&& AppPrefs.get().clearTerminalOnInit().get(),
|
&& AppPrefs.get().clearTerminalOnInit().get(),
|
||||||
cc instanceof ShellControl ? type.additionalInitCommands() : TerminalInitFunction.none());
|
cc instanceof ShellControl ? type.additionalInitCommands() : TerminalInitFunction.none());
|
||||||
var config = createConfig(request, entry, cleanTitle, adjustedTitle);
|
var config = TerminalLaunchConfiguration.create(request, entry, cleanTitle, adjustedTitle, preferTabs);
|
||||||
var latch = TerminalLauncherManager.submitAsync(request, cc, terminalConfig, directory);
|
var latch = TerminalLauncherManager.submitAsync(request, cc, terminalConfig, directory);
|
||||||
try {
|
try {
|
||||||
type.launch(config);
|
type.launch(config);
|
||||||
|
@ -89,84 +80,4 @@ public class TerminalLauncher {
|
||||||
"Unable to launch terminal " + type.toTranslatedString().getValue() + ": " + modMsg, ex));
|
"Unable to launch terminal " + type.toTranslatedString().getValue() + ": " + modMsg, ex));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final DateTimeFormatter DATE_FORMATTER =
|
|
||||||
DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss").withZone(ZoneId.systemDefault());
|
|
||||||
|
|
||||||
private static ExternalTerminalType.LaunchConfiguration createConfig(
|
|
||||||
UUID request, DataStoreEntry entry, String cleanTitle, String adjustedTitle) throws Exception {
|
|
||||||
var color = entry != null ? DataStorage.get().getEffectiveColor(entry) : null;
|
|
||||||
var d = ProcessControlProvider.get().getEffectiveLocalDialect();
|
|
||||||
var launcherScript = d.terminalLauncherScript(request, adjustedTitle);
|
|
||||||
var preparationScript = ScriptHelper.createLocalExecScript(launcherScript);
|
|
||||||
|
|
||||||
if (!AppPrefs.get().enableTerminalLogging().get()) {
|
|
||||||
var config = new ExternalTerminalType.LaunchConfiguration(
|
|
||||||
entry != null ? color : null, adjustedTitle, cleanTitle, preparationScript, d);
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
var feature = LicenseProvider.get().getFeature("logging");
|
|
||||||
var supported = feature.isSupported();
|
|
||||||
if (!supported) {
|
|
||||||
throw new LicenseRequiredException(feature);
|
|
||||||
}
|
|
||||||
|
|
||||||
var logDir = AppProperties.get().getDataDir().resolve("sessions");
|
|
||||||
Files.createDirectories(logDir);
|
|
||||||
var logFile = logDir.resolve(new FilePath(DataStorage.get().getStoreEntryDisplayName(entry) + " ("
|
|
||||||
+ DATE_FORMATTER.format(Instant.now()) + ").log")
|
|
||||||
.fileSystemCompatible(OsType.getLocal())
|
|
||||||
.toString()
|
|
||||||
.replaceAll(" ", "_"));
|
|
||||||
try (var sc = LocalShell.getShell().start()) {
|
|
||||||
if (OsType.getLocal() == OsType.WINDOWS) {
|
|
||||||
var content =
|
|
||||||
"""
|
|
||||||
echo 'Transcript started, output file is "sessions\\%s"'
|
|
||||||
Start-Transcript -Force -LiteralPath "%s" > $Out-Null
|
|
||||||
& %s
|
|
||||||
Stop-Transcript > $Out-Null
|
|
||||||
echo 'Transcript stopped, output file is "sessions\\%s"'
|
|
||||||
"""
|
|
||||||
.formatted(
|
|
||||||
logFile.getFileName().toString(),
|
|
||||||
logFile,
|
|
||||||
preparationScript,
|
|
||||||
logFile.getFileName().toString());
|
|
||||||
var ps = ScriptHelper.createExecScript(ShellDialects.POWERSHELL, sc, content);
|
|
||||||
var config = new ExternalTerminalType.LaunchConfiguration(
|
|
||||||
entry != null ? color : null, adjustedTitle, cleanTitle, ps, ShellDialects.POWERSHELL);
|
|
||||||
return config;
|
|
||||||
} else {
|
|
||||||
var found = sc.command(sc.getShellDialect().getWhichCommand("script"))
|
|
||||||
.executeAndCheck();
|
|
||||||
if (!found) {
|
|
||||||
var suffix = sc.getOsType() == OsType.MACOS
|
|
||||||
? "This command is available in the util-linux package which can be installed via homebrew."
|
|
||||||
: "This command is available in the util-linux package.";
|
|
||||||
throw ErrorEvent.expected(new IllegalStateException(
|
|
||||||
"Logging requires the script command to be installed. " + suffix));
|
|
||||||
}
|
|
||||||
|
|
||||||
var content = sc.getOsType() == OsType.MACOS || sc.getOsType() == OsType.BSD
|
|
||||||
? """
|
|
||||||
echo "Transcript started, output file is sessions/%s"
|
|
||||||
script -e -q "%s" "%s"
|
|
||||||
echo "Transcript stopped, output file is sessions/%s"
|
|
||||||
"""
|
|
||||||
.formatted(logFile.getFileName(), logFile, preparationScript, logFile.getFileName())
|
|
||||||
: """
|
|
||||||
echo "Transcript started, output file is sessions/%s"
|
|
||||||
script --quiet --command "%s" "%s"
|
|
||||||
echo "Transcript stopped, output file is sessions/%s"
|
|
||||||
"""
|
|
||||||
.formatted(logFile.getFileName(), preparationScript, logFile, logFile.getFileName());
|
|
||||||
var ps = ScriptHelper.createExecScript(sc.getShellDialect(), sc, content);
|
|
||||||
var config = new ExternalTerminalType.LaunchConfiguration(
|
|
||||||
entry != null ? color : null, adjustedTitle, cleanTitle, ps, sc.getShellDialect());
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,9 +18,6 @@ public class TerminalLauncherManager {
|
||||||
|
|
||||||
public static void init() {
|
public static void init() {
|
||||||
TerminalView.get().addListener(new TerminalView.Listener() {
|
TerminalView.get().addListener(new TerminalView.Listener() {
|
||||||
@Override
|
|
||||||
public void onSessionOpened(TerminalView.ShellSession session) {}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSessionClosed(TerminalView.ShellSession session) {
|
public void onSessionClosed(TerminalView.ShellSession session) {
|
||||||
var affectedEntry = entries.values().stream()
|
var affectedEntry = entries.values().stream()
|
||||||
|
@ -34,12 +31,6 @@ public class TerminalLauncherManager {
|
||||||
|
|
||||||
affectedEntry.get().abort();
|
affectedEntry.get().abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTerminalOpened(TerminalView.TerminalSession instance) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTerminalClosed(TerminalView.TerminalSession instance) {}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package io.xpipe.app.terminal;
|
||||||
|
|
||||||
|
public enum TerminalOpenFormat {
|
||||||
|
|
||||||
|
NEW_WINDOW,
|
||||||
|
TABBED,
|
||||||
|
NEW_WINDOW_OR_TABBED;
|
||||||
|
}
|
|
@ -26,7 +26,7 @@ public class TerminalView {
|
||||||
public static class ShellSession {
|
public static class ShellSession {
|
||||||
UUID request;
|
UUID request;
|
||||||
ProcessHandle shell;
|
ProcessHandle shell;
|
||||||
ProcessHandle terminal;
|
TerminalSession terminal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
|
@ -38,6 +38,10 @@ public class TerminalView {
|
||||||
this.terminalProcess = terminalProcess;
|
this.terminalProcess = terminalProcess;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isRunning() {
|
||||||
|
return terminalProcess.isAlive();
|
||||||
|
}
|
||||||
|
|
||||||
public Optional<ControllableTerminalSession> controllable() {
|
public Optional<ControllableTerminalSession> controllable() {
|
||||||
return Optional.ofNullable(this instanceof ControllableTerminalSession c ? c : null);
|
return Optional.ofNullable(this instanceof ControllableTerminalSession c ? c : null);
|
||||||
}
|
}
|
||||||
|
@ -45,13 +49,13 @@ public class TerminalView {
|
||||||
|
|
||||||
public static interface Listener {
|
public static interface Listener {
|
||||||
|
|
||||||
void onSessionOpened(ShellSession session);
|
default void onSessionOpened(ShellSession session) {};
|
||||||
|
|
||||||
void onSessionClosed(ShellSession session);
|
default void onSessionClosed(ShellSession session) {};
|
||||||
|
|
||||||
void onTerminalOpened(TerminalSession instance);
|
default void onTerminalOpened(TerminalSession instance) {};
|
||||||
|
|
||||||
void onTerminalClosed(TerminalSession instance);
|
default void onTerminalClosed(TerminalSession instance) {};
|
||||||
}
|
}
|
||||||
|
|
||||||
private final List<ShellSession> sessions = new ArrayList<>();
|
private final List<ShellSession> sessions = new ArrayList<>();
|
||||||
|
@ -97,20 +101,17 @@ public class TerminalView {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var session = new ShellSession(request, shell.get(), terminal.get());
|
var tv = createTerminalSession(terminal.get());
|
||||||
var instance = terminalInstances.stream()
|
if (tv.isEmpty()) {
|
||||||
.filter(i -> i.getTerminalProcess().equals(terminal.get()))
|
return;
|
||||||
.findFirst();
|
}
|
||||||
if (instance.isEmpty()) {
|
|
||||||
var tv = createTerminalSession(terminal.get());
|
|
||||||
if (tv.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (!terminalInstances.contains(tv.get())) {
|
||||||
terminalInstances.add(tv.get());
|
terminalInstances.add(tv.get());
|
||||||
listeners.forEach(listener -> listener.onTerminalOpened(tv.get()));
|
listeners.forEach(listener -> listener.onTerminalOpened(tv.get()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var session = new ShellSession(request, shell.get(), tv.get());
|
||||||
sessions.add(session);
|
sessions.add(session);
|
||||||
listeners.forEach(listener -> listener.onSessionOpened(session));
|
listeners.forEach(listener -> listener.onSessionOpened(session));
|
||||||
|
|
||||||
|
@ -124,12 +125,18 @@ public class TerminalView {
|
||||||
case OsType.Linux linux -> Optional.of(new TerminalSession(terminalProcess));
|
case OsType.Linux linux -> Optional.of(new TerminalSession(terminalProcess));
|
||||||
case OsType.MacOs macOs -> Optional.of(new TerminalSession(terminalProcess));
|
case OsType.MacOs macOs -> Optional.of(new TerminalSession(terminalProcess));
|
||||||
case OsType.Windows windows -> {
|
case OsType.Windows windows -> {
|
||||||
var control = NativeWinWindowControl.byPid(terminalProcess.pid());
|
var controls = NativeWinWindowControl.byPid(terminalProcess.pid());
|
||||||
if (control.isEmpty()) {
|
if (controls.isEmpty()) {
|
||||||
yield Optional.empty();
|
yield Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
yield Optional.of(new WindowsTerminalSession(terminalProcess, control.get()));
|
var existing = terminalInstances.stream().map(terminalSession -> ((WindowsTerminalSession) terminalSession).getControl()).toList();
|
||||||
|
controls.removeAll(existing);
|
||||||
|
if (controls.isEmpty()) {
|
||||||
|
yield Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
yield Optional.of(new WindowsTerminalSession(terminalProcess, controls.getFirst()));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -176,7 +183,7 @@ public class TerminalView {
|
||||||
|
|
||||||
public synchronized void tick() {
|
public synchronized void tick() {
|
||||||
for (ShellSession session : new ArrayList<>(sessions)) {
|
for (ShellSession session : new ArrayList<>(sessions)) {
|
||||||
var alive = session.shell.isAlive() && session.terminal.isAlive();
|
var alive = session.shell.isAlive() && session.getTerminal().isRunning();
|
||||||
if (!alive) {
|
if (!alive) {
|
||||||
sessions.remove(session);
|
sessions.remove(session);
|
||||||
listeners.forEach(listener -> listener.onSessionClosed(session));
|
listeners.forEach(listener -> listener.onSessionClosed(session));
|
||||||
|
@ -184,7 +191,7 @@ public class TerminalView {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (TerminalSession terminalInstance : new ArrayList<>(terminalInstances)) {
|
for (TerminalSession terminalInstance : new ArrayList<>(terminalInstances)) {
|
||||||
var alive = terminalInstance.getTerminalProcess().isAlive();
|
var alive = terminalInstance.isRunning();
|
||||||
if (!alive) {
|
if (!alive) {
|
||||||
terminalInstances.remove(terminalInstance);
|
terminalInstances.remove(terminalInstance);
|
||||||
TrackEvent.withTrace("Terminal session is dead")
|
TrackEvent.withTrace("Terminal session is dead")
|
||||||
|
|
108
app/src/main/java/io/xpipe/app/terminal/TermiusTerminalType.java
Normal file
108
app/src/main/java/io/xpipe/app/terminal/TermiusTerminalType.java
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
package io.xpipe.app.terminal;
|
||||||
|
|
||||||
|
import io.xpipe.app.comp.base.MarkdownComp;
|
||||||
|
import io.xpipe.app.core.AppCache;
|
||||||
|
import io.xpipe.app.core.AppI18n;
|
||||||
|
import io.xpipe.app.core.window.AppWindowHelper;
|
||||||
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
|
import io.xpipe.app.util.Hyperlinks;
|
||||||
|
import io.xpipe.app.util.LocalShell;
|
||||||
|
import io.xpipe.app.util.SshLocalBridge;
|
||||||
|
import io.xpipe.app.util.WindowsRegistry;
|
||||||
|
import io.xpipe.core.process.OsType;
|
||||||
|
import javafx.scene.control.Alert;
|
||||||
|
import javafx.scene.control.ButtonBar;
|
||||||
|
import javafx.scene.control.ButtonType;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
public class TermiusTerminalType implements ExternalTerminalType {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "app.termius";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TerminalOpenFormat getOpenFormat() {
|
||||||
|
return TerminalOpenFormat.TABBED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAvailable() {
|
||||||
|
try (var sc = LocalShell.getShell()) {
|
||||||
|
return switch (OsType.getLocal()) {
|
||||||
|
case OsType.Linux linux -> {
|
||||||
|
yield Files.exists(Path.of("/opt/Termius"));
|
||||||
|
}
|
||||||
|
case OsType.MacOs macOs -> {
|
||||||
|
yield Files.exists(Path.of("/Applications/Termius.app"));
|
||||||
|
}
|
||||||
|
case OsType.Windows windows -> {
|
||||||
|
var r = WindowsRegistry.local().readValue(WindowsRegistry.HKEY_CURRENT_USER, "SOFTWARE\\Classes\\termius");
|
||||||
|
yield r.isPresent();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (Exception e) {
|
||||||
|
ErrorEvent.fromThrowable(e).omit().handle();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getWebsite() {
|
||||||
|
return "https://termius.com/";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRecommended() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsColoredTitle() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||||
|
SshLocalBridge.init();
|
||||||
|
if (!showInfo()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var host = "localhost";
|
||||||
|
var b = SshLocalBridge.get();
|
||||||
|
var port = b.getPort();
|
||||||
|
var user = b.getUser();
|
||||||
|
var name = b.getIdentityKey().getFileName().toString();
|
||||||
|
Hyperlinks.open("termius://app/host-sharing#label=" + name + "&ip=" + host + "&port=" + port + "&username=" + user + "&os=undefined");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean showInfo() throws IOException {
|
||||||
|
boolean set = AppCache.getBoolean("termiusSetup", false);
|
||||||
|
if (set) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var b = SshLocalBridge.get();
|
||||||
|
var keyContent = Files.readString(b.getIdentityKey());
|
||||||
|
var r = AppWindowHelper.showBlockingAlert(alert -> {
|
||||||
|
alert.setTitle(AppI18n.get("termiusSetup"));
|
||||||
|
alert.setAlertType(Alert.AlertType.NONE);
|
||||||
|
|
||||||
|
var activated = AppI18n.get().getMarkdownDocumentation("app:termiusSetup").formatted(b.getIdentityKey(), keyContent);
|
||||||
|
var markdown = new MarkdownComp(activated, s -> s).prefWidth(450).prefHeight(450).createRegion();
|
||||||
|
alert.getDialogPane().setContent(markdown);
|
||||||
|
|
||||||
|
alert.getButtonTypes().add(new ButtonType(AppI18n.get("ok"), ButtonBar.ButtonData.OK_DONE));
|
||||||
|
});
|
||||||
|
r.filter(buttonType -> buttonType.getButtonData().isDefaultButton());
|
||||||
|
r.ifPresent(buttonType -> {
|
||||||
|
AppCache.update("termiusSetup", true);
|
||||||
|
});
|
||||||
|
return r.isPresent();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
package io.xpipe.app.terminal;
|
||||||
|
|
||||||
|
import io.xpipe.app.util.LocalShell;
|
||||||
|
import io.xpipe.core.process.CommandBuilder;
|
||||||
|
import io.xpipe.core.process.ShellDialect;
|
||||||
|
import io.xpipe.core.process.ShellDialects;
|
||||||
|
import io.xpipe.core.process.TerminalInitFunction;
|
||||||
|
import io.xpipe.core.util.FailableFunction;
|
||||||
|
|
||||||
|
public class WarpTerminalType extends ExternalTerminalType.MacOsType {
|
||||||
|
|
||||||
|
public WarpTerminalType() {super("app.warp", "Warp");}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TerminalOpenFormat getOpenFormat() {
|
||||||
|
return TerminalOpenFormat.TABBED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getProcessHierarchyOffset() {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getWebsite() {
|
||||||
|
return "https://www.warp.dev/";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRecommended() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsColoredTitle() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldClear() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||||
|
LocalShell.getShell().executeSimpleCommand(CommandBuilder.of()
|
||||||
|
.add("open", "-a")
|
||||||
|
.addQuoted("Warp.app")
|
||||||
|
.addFile(configuration.getScriptFile()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FailableFunction<TerminalLaunchConfiguration, String, Exception> remoteLaunchCommand(
|
||||||
|
ShellDialect systemDialect
|
||||||
|
) {
|
||||||
|
return launchConfiguration -> {
|
||||||
|
var toExecute = CommandBuilder.of().add("open", "-a").addQuoted("Warp.app").addFile(launchConfiguration.getScriptFile());
|
||||||
|
return toExecute.buildSimple();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TerminalInitFunction additionalInitCommands() {
|
||||||
|
return TerminalInitFunction.of(sc -> {
|
||||||
|
if (sc.getShellDialect() == ShellDialects.ZSH) {
|
||||||
|
return "printf '\\eP$f{\"hook\": \"SourcedRcFileForWarp\", \"value\": { \"shell\": \"zsh\"}}\\x9c'";
|
||||||
|
}
|
||||||
|
if (sc.getShellDialect() == ShellDialects.BASH) {
|
||||||
|
return "printf '\\eP$f{\"hook\": \"SourcedRcFileForWarp\", \"value\": { \"shell\": \"bash\"}}\\x9c'";
|
||||||
|
}
|
||||||
|
if (sc.getShellDialect() == ShellDialects.FISH) {
|
||||||
|
return "printf '\\eP$f{\"hook\": \"SourcedRcFileForWarp\", \"value\": { \"shell\": \"fish\"}}\\x9c'";
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,11 +18,6 @@ public interface WezTerminalType extends ExternalTerminalType, TrackableTerminal
|
||||||
ExternalTerminalType WEZTERM_LINUX = new Linux();
|
ExternalTerminalType WEZTERM_LINUX = new Linux();
|
||||||
ExternalTerminalType WEZTERM_MAC_OS = new MacOs();
|
ExternalTerminalType WEZTERM_MAC_OS = new MacOs();
|
||||||
|
|
||||||
@Override
|
|
||||||
default boolean supportsTabs() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default String getWebsite() {
|
default String getWebsite() {
|
||||||
return "https://wezfurlong.org/wezterm/index.html";
|
return "https://wezfurlong.org/wezterm/index.html";
|
||||||
|
@ -45,7 +40,12 @@ public interface WezTerminalType extends ExternalTerminalType, TrackableTerminal
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void execute(Path file, LaunchConfiguration configuration) throws Exception {
|
public TerminalOpenFormat getOpenFormat() {
|
||||||
|
return TerminalOpenFormat.NEW_WINDOW;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void execute(Path file, TerminalLaunchConfiguration configuration) throws Exception {
|
||||||
LocalShell.getShell()
|
LocalShell.getShell()
|
||||||
.executeSimpleCommand(CommandBuilder.of()
|
.executeSimpleCommand(CommandBuilder.of()
|
||||||
.addFile(file.toString())
|
.addFile(file.toString())
|
||||||
|
@ -90,6 +90,11 @@ public interface WezTerminalType extends ExternalTerminalType, TrackableTerminal
|
||||||
super("app.wezterm");
|
super("app.wezterm");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TerminalOpenFormat getOpenFormat() {
|
||||||
|
return TerminalOpenFormat.TABBED;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isAvailable() {
|
public boolean isAvailable() {
|
||||||
try (ShellControl pc = LocalShell.getShell()) {
|
try (ShellControl pc = LocalShell.getShell()) {
|
||||||
return pc.executeSimpleBooleanCommand(pc.getShellDialect().getWhichCommand("wezterm"))
|
return pc.executeSimpleBooleanCommand(pc.getShellDialect().getWhichCommand("wezterm"))
|
||||||
|
@ -101,7 +106,7 @@ public interface WezTerminalType extends ExternalTerminalType, TrackableTerminal
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void launch(LaunchConfiguration configuration) throws Exception {
|
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||||
var spawn = LocalShell.getShell()
|
var spawn = LocalShell.getShell()
|
||||||
.command(CommandBuilder.of()
|
.command(CommandBuilder.of()
|
||||||
.addFile("wezterm")
|
.addFile("wezterm")
|
||||||
|
@ -122,7 +127,12 @@ public interface WezTerminalType extends ExternalTerminalType, TrackableTerminal
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void launch(LaunchConfiguration configuration) throws Exception {
|
public TerminalOpenFormat getOpenFormat() {
|
||||||
|
return TerminalOpenFormat.TABBED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||||
try (var sc = LocalShell.getShell()) {
|
try (var sc = LocalShell.getShell()) {
|
||||||
var pathOut = sc.command(String.format(
|
var pathOut = sc.command(String.format(
|
||||||
"mdfind -name '%s' -onlyin /Applications -onlyin ~/Applications -onlyin /System/Applications 2>/dev/null",
|
"mdfind -name '%s' -onlyin /Applications -onlyin ~/Applications -onlyin /System/Applications 2>/dev/null",
|
||||||
|
|
|
@ -4,9 +4,11 @@ import io.xpipe.app.core.window.NativeWinWindowControl;
|
||||||
import io.xpipe.app.util.Rect;
|
import io.xpipe.app.util.Rect;
|
||||||
|
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Getter;
|
||||||
import lombok.experimental.FieldDefaults;
|
import lombok.experimental.FieldDefaults;
|
||||||
|
|
||||||
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
|
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
|
||||||
|
@Getter
|
||||||
public final class WindowsTerminalSession extends ControllableTerminalSession {
|
public final class WindowsTerminalSession extends ControllableTerminalSession {
|
||||||
|
|
||||||
NativeWinWindowControl control;
|
NativeWinWindowControl control;
|
||||||
|
@ -16,6 +18,11 @@ public final class WindowsTerminalSession extends ControllableTerminalSession {
|
||||||
this.control = control;
|
this.control = control;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRunning() {
|
||||||
|
return super.isRunning() && control.isVisible();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void show() {
|
public void show() {
|
||||||
this.control.show();
|
this.control.show();
|
||||||
|
|
|
@ -18,8 +18,8 @@ public interface WindowsTerminalType extends ExternalTerminalType, TrackableTerm
|
||||||
ExternalTerminalType WINDOWS_TERMINAL_PREVIEW = new Preview();
|
ExternalTerminalType WINDOWS_TERMINAL_PREVIEW = new Preview();
|
||||||
ExternalTerminalType WINDOWS_TERMINAL_CANARY = new Canary();
|
ExternalTerminalType WINDOWS_TERMINAL_CANARY = new Canary();
|
||||||
|
|
||||||
private static CommandBuilder toCommand(ExternalTerminalType.LaunchConfiguration configuration) throws Exception {
|
private static CommandBuilder toCommand(TerminalLaunchConfiguration configuration) throws Exception {
|
||||||
var cmd = CommandBuilder.of().add("-w", "1", "nt");
|
var cmd = CommandBuilder.of().addIf(configuration.isPreferTabs(), "-w", "1").add("nt");
|
||||||
|
|
||||||
if (configuration.getColor() != null) {
|
if (configuration.getColor() != null) {
|
||||||
cmd.add("--tabColor").addQuoted(configuration.getColor().toHexString());
|
cmd.add("--tabColor").addQuoted(configuration.getColor().toHexString());
|
||||||
|
@ -56,6 +56,11 @@ public interface WindowsTerminalType extends ExternalTerminalType, TrackableTerm
|
||||||
return cmd;
|
return cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default TerminalOpenFormat getOpenFormat() {
|
||||||
|
return TerminalOpenFormat.NEW_WINDOW_OR_TABBED;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default int getProcessHierarchyOffset() {
|
default int getProcessHierarchyOffset() {
|
||||||
var powershell = AppPrefs.get().enableTerminalLogging().get()
|
var powershell = AppPrefs.get().enableTerminalLogging().get()
|
||||||
|
@ -63,11 +68,6 @@ public interface WindowsTerminalType extends ExternalTerminalType, TrackableTerm
|
||||||
return powershell ? 1 : 0;
|
return powershell ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
default boolean supportsTabs() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default boolean isRecommended() {
|
default boolean isRecommended() {
|
||||||
return true;
|
return true;
|
||||||
|
@ -90,7 +90,7 @@ public interface WindowsTerminalType extends ExternalTerminalType, TrackableTerm
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected CommandBuilder toCommand(LaunchConfiguration configuration) throws Exception {
|
protected CommandBuilder toCommand(TerminalLaunchConfiguration configuration) throws Exception {
|
||||||
return WindowsTerminalType.toCommand(configuration);
|
return WindowsTerminalType.toCommand(configuration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,9 +103,9 @@ public interface WindowsTerminalType extends ExternalTerminalType, TrackableTerm
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void launch(LaunchConfiguration configuration) throws Exception {
|
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||||
if (!isAvailable()) {
|
if (!isAvailable()) {
|
||||||
throw ErrorEvent.expected(new IllegalArgumentException("Windows Terminal Preview is not installed"));
|
throw ErrorEvent.expected(new IllegalArgumentException("Windows Terminal Preview is not installed at " + getPath()));
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalShell.getShell()
|
LocalShell.getShell()
|
||||||
|
@ -138,9 +138,9 @@ public interface WindowsTerminalType extends ExternalTerminalType, TrackableTerm
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void launch(LaunchConfiguration configuration) throws Exception {
|
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||||
if (!isAvailable()) {
|
if (!isAvailable()) {
|
||||||
throw ErrorEvent.expected(new IllegalArgumentException("Windows Terminal Canary is not installed"));
|
throw ErrorEvent.expected(new IllegalArgumentException("Windows Terminal Canary is not installed at " + getPath()));
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalShell.getShell()
|
LocalShell.getShell()
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
package io.xpipe.app.terminal;
|
||||||
|
|
||||||
|
import io.xpipe.app.comp.base.MarkdownComp;
|
||||||
|
import io.xpipe.app.core.AppCache;
|
||||||
|
import io.xpipe.app.core.AppI18n;
|
||||||
|
import io.xpipe.app.core.window.AppWindowHelper;
|
||||||
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
|
import io.xpipe.app.util.LocalShell;
|
||||||
|
import io.xpipe.app.util.SshLocalBridge;
|
||||||
|
import io.xpipe.app.util.WindowsRegistry;
|
||||||
|
import io.xpipe.core.process.CommandBuilder;
|
||||||
|
import javafx.scene.control.Alert;
|
||||||
|
import javafx.scene.control.ButtonBar;
|
||||||
|
import javafx.scene.control.ButtonType;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class XShellTerminalType extends ExternalTerminalType.WindowsType {
|
||||||
|
|
||||||
|
public XShellTerminalType() {super("app.xShell", "Xshell");}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TerminalOpenFormat getOpenFormat() {
|
||||||
|
return TerminalOpenFormat.TABBED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Optional<Path> determineInstallation() {
|
||||||
|
try {
|
||||||
|
var r = WindowsRegistry.local().readValue(WindowsRegistry.HKEY_LOCAL_MACHINE,
|
||||||
|
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\Xshell.exe");
|
||||||
|
return r.map(Path::of);
|
||||||
|
} catch (Exception e) {
|
||||||
|
ErrorEvent.fromThrowable(e).omit().handle();
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getWebsite() {
|
||||||
|
return "https://www.netsarang.com/en/xshell/";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRecommended() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsColoredTitle() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void execute(Path file, TerminalLaunchConfiguration configuration) throws Exception {
|
||||||
|
SshLocalBridge.init();
|
||||||
|
if (!showInfo()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (var sc = LocalShell.getShell()) {
|
||||||
|
var b = SshLocalBridge.get();
|
||||||
|
var keyName = b.getIdentityKey().getFileName().toString();
|
||||||
|
var command = CommandBuilder.of()
|
||||||
|
.addFile(file.toString())
|
||||||
|
.add("-url")
|
||||||
|
.addQuoted("ssh://" + b.getUser() + "@localhost:" + b.getPort())
|
||||||
|
.add("-i", keyName);
|
||||||
|
sc.executeSimpleCommand(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean showInfo() {
|
||||||
|
boolean set = AppCache.getBoolean("xshellSetup", false);
|
||||||
|
if (set) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var b = SshLocalBridge.get();
|
||||||
|
var keyName = b.getIdentityKey().getFileName().toString();
|
||||||
|
var r = AppWindowHelper.showBlockingAlert(alert -> {
|
||||||
|
alert.setTitle(AppI18n.get("xshellSetup"));
|
||||||
|
alert.setAlertType(Alert.AlertType.NONE);
|
||||||
|
|
||||||
|
var activated = AppI18n.get().getMarkdownDocumentation("app:xshellSetup").formatted(b.getIdentityKey(), keyName);
|
||||||
|
var markdown = new MarkdownComp(activated, s -> s).prefWidth(450).prefHeight(400).createRegion();
|
||||||
|
alert.getDialogPane().setContent(markdown);
|
||||||
|
|
||||||
|
alert.getButtonTypes().add(new ButtonType(AppI18n.get("ok"), ButtonBar.ButtonData.OK_DONE));
|
||||||
|
});
|
||||||
|
r.filter(buttonType -> buttonType.getButtonData().isDefaultButton());
|
||||||
|
r.ifPresent(buttonType -> {
|
||||||
|
AppCache.update("xshellSetup", true);
|
||||||
|
});
|
||||||
|
return r.isPresent();
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,22 +30,16 @@ public abstract class MultiExecuteAction implements BrowserBranchAction {
|
||||||
model.withShell(
|
model.withShell(
|
||||||
pc -> {
|
pc -> {
|
||||||
for (BrowserEntry entry : entries) {
|
for (BrowserEntry entry : entries) {
|
||||||
var cmd = pc.command(createCommand(pc, model, entry));
|
var c = createCommand(pc, model, entry);
|
||||||
if (cmd == null) {
|
if (c == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var uuid = UUID.randomUUID();
|
var cmd = pc.command(c);
|
||||||
model.getTerminalRequests().add(uuid);
|
model.openTerminalAsync(entry.getRawFileEntry().getName(), model.getCurrentDirectory() != null
|
||||||
TerminalLauncher.open(
|
? model.getCurrentDirectory()
|
||||||
model.getEntry().getEntry(),
|
.getPath()
|
||||||
entry.getRawFileEntry().getName(),
|
: null, cmd);
|
||||||
model.getCurrentDirectory() != null
|
|
||||||
? model.getCurrentDirectory()
|
|
||||||
.getPath()
|
|
||||||
: null,
|
|
||||||
cmd,
|
|
||||||
uuid);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
false);
|
false);
|
||||||
|
|
|
@ -34,18 +34,16 @@ public abstract class MultiExecuteSelectionAction implements BrowserBranchAction
|
||||||
public void execute(BrowserFileSystemTabModel model, List<BrowserEntry> entries) {
|
public void execute(BrowserFileSystemTabModel model, List<BrowserEntry> entries) {
|
||||||
model.withShell(
|
model.withShell(
|
||||||
pc -> {
|
pc -> {
|
||||||
var uuid = UUID.randomUUID();
|
var c = createCommand(pc, model, entries);
|
||||||
model.getTerminalRequests().add(uuid);
|
if (c == null) {
|
||||||
var cmd = pc.command(createCommand(pc, model, entries));
|
return;
|
||||||
TerminalLauncher.open(
|
}
|
||||||
model.getEntry().getEntry(),
|
|
||||||
getTerminalTitle(),
|
var cmd = pc.command(c);
|
||||||
model.getCurrentDirectory() != null
|
model.openTerminalAsync(getTerminalTitle(), model.getCurrentDirectory() != null
|
||||||
? model.getCurrentDirectory()
|
? model.getCurrentDirectory()
|
||||||
.getPath()
|
.getPath()
|
||||||
: null,
|
: null, cmd);
|
||||||
cmd,
|
|
||||||
uuid);
|
|
||||||
},
|
},
|
||||||
false);
|
false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,33 +17,20 @@ import javafx.scene.input.KeyCombination;
|
||||||
|
|
||||||
import org.kordamp.ikonli.javafx.FontIcon;
|
import org.kordamp.ikonli.javafx.FontIcon;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class OpenTerminalAction implements BrowserLeafAction {
|
public class OpenTerminalAction implements BrowserLeafAction {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(BrowserFileSystemTabModel model, List<BrowserEntry> entries) {
|
public void execute(BrowserFileSystemTabModel model, List<BrowserEntry> entries) {
|
||||||
if (entries.size() == 0) {
|
var dirs = entries.size() > 0 ? entries.stream().map(browserEntry -> browserEntry.getRawFileEntry().getPath()).toList() : model.getCurrentDirectory() != null
|
||||||
model.openTerminalAsync(
|
? List.of(model.getCurrentDirectory().getPath())
|
||||||
model.getCurrentDirectory() != null
|
: Collections.singletonList((String) null);
|
||||||
? model.getCurrentDirectory().getPath()
|
for (String dir : dirs) {
|
||||||
: null);
|
var name = (dir != null ? dir + " - " : "") + model.getName();
|
||||||
} else {
|
model.openTerminalAsync(name, dir, model.getFileSystem().getShell().orElseThrow());
|
||||||
for (var entry : entries) {
|
|
||||||
model.openTerminalAsync(entry.getRawFileEntry().getPath());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (AppPrefs.get().enableTerminalDocking().get()
|
|
||||||
&& model.getBrowserModel() instanceof BrowserFullSessionModel sessionModel) {
|
|
||||||
// Check if the right side is already occupied
|
|
||||||
var existingSplit = sessionModel.getSplits().get(model);
|
|
||||||
if (existingSplit != null && !(existingSplit instanceof BrowserTerminalDockTabModel)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sessionModel.splitTab(
|
|
||||||
model, new BrowserTerminalDockTabModel(sessionModel, model, model.getTerminalRequests()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package io.xpipe.ext.base.desktop;
|
||||||
|
|
||||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||||
import io.xpipe.app.terminal.ExternalTerminalType;
|
import io.xpipe.app.terminal.ExternalTerminalType;
|
||||||
|
import io.xpipe.app.terminal.TerminalLaunchConfiguration;
|
||||||
import io.xpipe.app.util.Validators;
|
import io.xpipe.app.util.Validators;
|
||||||
import io.xpipe.core.process.OsType;
|
import io.xpipe.core.process.OsType;
|
||||||
import io.xpipe.core.process.ShellDialect;
|
import io.xpipe.core.process.ShellDialect;
|
||||||
|
@ -99,7 +100,7 @@ public class DesktopEnvironmentStore extends JacksonizedValue
|
||||||
.createScript(
|
.createScript(
|
||||||
dialect,
|
dialect,
|
||||||
dialect.prepareTerminalInitFileOpenCommand(dialect, null, scriptFile.toString(), false));
|
dialect.prepareTerminalInitFileOpenCommand(dialect, null, scriptFile.toString(), false));
|
||||||
var launchConfig = new ExternalTerminalType.LaunchConfiguration(null, name, name, launchScriptFile, dialect);
|
var launchConfig = new TerminalLaunchConfiguration(null, name, name, true, launchScriptFile, dialect);
|
||||||
base.getStore().runDesktopScript(name, launchCommand.apply(launchConfig));
|
base.getStore().runDesktopScript(name, launchCommand.apply(launchConfig));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue