Skip to content

Commit

Permalink
More terminal launcher rework
Browse files Browse the repository at this point in the history
  • Loading branch information
crschnick committed Aug 13, 2024
1 parent 20206b6 commit da42eb5
Show file tree
Hide file tree
Showing 45 changed files with 443 additions and 20 deletions.
4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ dependencies {
api group: 'org.kordamp.ikonli', name: 'ikonli-javafx', version: "12.2.0"
api group: 'org.kordamp.ikonli', name: 'ikonli-material-pack', version: "12.2.0"
api group: 'org.kordamp.ikonli', name: 'ikonli-feather-pack', version: "12.2.0"
api group: 'org.slf4j', name: 'slf4j-api', version: '2.0.15'
api group: 'org.slf4j', name: 'slf4j-jdk-platform-logging', version: '2.0.15'
api group: 'org.slf4j', name: 'slf4j-api', version: '2.0.16'
api group: 'org.slf4j', name: 'slf4j-jdk-platform-logging', version: '2.0.16'
api 'io.xpipe:modulefs:0.1.5'
api 'net.synedra:validatorfx:0.4.2'
api files("$rootDir/gradle/gradle_scripts/atlantafx-base-2.0.2.jar")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public Object handle(HttpExchange exchange, Request msg) throws Exception {
}
TerminalLauncherManager.submitAsync(UUID.randomUUID(), ((ShellStore) DataStorage.get().local().getStore()).control(),
TerminalInitScriptConfig.ofName("abc"),null);
var r = TerminalLauncherManager.waitForFirstLaunch();
var r = TerminalLauncherManager.waitForNextLaunch();
var c = ProcessControlProvider.get().getEffectiveLocalDialect().getOpenScriptCommand(r.toString()).buildBaseParts(null);
return Response.builder().command(c).build();
}
Expand Down
66 changes: 64 additions & 2 deletions app/src/main/java/io/xpipe/app/prefs/ExternalRdpClientType.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,24 @@
import io.xpipe.core.process.CommandBuilder;
import io.xpipe.core.process.OsType;
import io.xpipe.core.util.SecretValue;

import lombok.Value;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Supplier;

public interface ExternalRdpClientType extends PrefsChoiceValue {

public static ExternalRdpClientType getApplicationLauncher() {
if (OsType.getLocal() == OsType.WINDOWS) {
return MSTSC;
} else {
return AppPrefs.get().rdpClientType().getValue();
}
}

ExternalRdpClientType MSTSC = new PathCheckType("app.mstsc", "mstsc.exe", false) {

@Override
Expand Down Expand Up @@ -62,6 +70,37 @@ private String encrypt(SecretValue password) throws Exception {
return cmd.readStdoutOrThrow();
}
};

ExternalRdpClientType DEVOLUTIONS = new WindowsType("app.devolutions", "RemoteDesktopManager") {

@Override
protected Optional<Path> determineInstallation() {
try {
var r = WindowsRegistry.local().readValue(WindowsRegistry.HKEY_LOCAL_MACHINE, "SOFTWARE\\Classes\\rdm\\DefaultIcon");
return r.map(Path::of);
} catch (Exception e) {
ErrorEvent.fromThrowable(e).omit().handle();
return Optional.empty();
}
}

@Override
protected void execute(Path file, LaunchConfiguration configuration) throws Exception {
var config = writeConfig(configuration.getConfig());
LocalShell.getShell().executeSimpleCommand(CommandBuilder.of().addFile(file.toString()).addFile(config.toString()).discardOutput());
ThreadHelper.runFailableAsync(() -> {
// Startup is slow
ThreadHelper.sleep(10000);
Files.delete(config);
});
}

@Override
public boolean supportsPasswordPassing() {
return false;
}
};

ExternalRdpClientType REMMINA = new PathCheckType("app.remmina", "remmina", true) {

@Override
Expand Down Expand Up @@ -96,7 +135,7 @@ public boolean supportsPasswordPassing() {
}
};
ExternalRdpClientType CUSTOM = new CustomType();
List<ExternalRdpClientType> WINDOWS_CLIENTS = List.of(MSTSC);
List<ExternalRdpClientType> WINDOWS_CLIENTS = List.of(MSTSC, DEVOLUTIONS);
List<ExternalRdpClientType> LINUX_CLIENTS = List.of(REMMINA);
List<ExternalRdpClientType> MACOS_CLIENTS = List.of(MICROSOFT_REMOTE_DESKTOP_MACOS_APP);

Expand Down Expand Up @@ -145,6 +184,29 @@ class LaunchConfiguration {
SecretValue password;
}

abstract class WindowsType extends ExternalApplicationType.WindowsType implements ExternalRdpClientType {

public WindowsType(String id, String executable) {
super(id, executable);
}

@Override
public void launch(LaunchConfiguration configuration) throws Exception {
var location = determineFromPath();
if (location.isEmpty()) {
location = determineInstallation();
if (location.isEmpty()) {
throw new IOException("Unable to find installation of "
+ toTranslatedString().getValue());
}
}

execute(location.get(), configuration);
}

protected abstract void execute(Path file, LaunchConfiguration configuration) throws Exception;
}

abstract class PathCheckType extends ExternalApplicationType.PathApplication implements ExternalRdpClientType {

public PathCheckType(String id, String executable, boolean explicityAsync) {
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/java/io/xpipe/app/storage/DataStorage.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import io.xpipe.app.comp.store.StoreSortMode;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.util.FixedHierarchyStore;
import io.xpipe.app.util.ThreadHelper;
Expand Down Expand Up @@ -120,9 +121,11 @@ public DataStoreCategory getAllScriptsCategory() {
}

public void forceRewrite() {
TrackEvent.info("Starting forced storage rewrite");
getStoreEntries().forEach(dataStoreEntry -> {
dataStoreEntry.reassignStore();
});
TrackEvent.info("Finished forced storage rewrite");
}

private void dispose() {
Expand Down
211 changes: 206 additions & 5 deletions app/src/main/java/io/xpipe/app/terminal/ExternalTerminalType.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
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.issue.ErrorEvent;
import io.xpipe.app.prefs.ExternalApplicationType;
Expand All @@ -8,18 +12,181 @@
import io.xpipe.core.process.*;
import io.xpipe.core.store.FilePath;
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.Value;
import lombok.With;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;

public interface ExternalTerminalType extends PrefsChoiceValue {

// ExternalTerminalType PUTTY = new WindowsType("app.putty","putty") {
//
// @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 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 {
// try (var sc = LocalShell.getShell()) {
// SshLocalBridge.init();
// var b = SshLocalBridge.get();
// var command = CommandBuilder.of().addFile(file.toString()).add("-ssh", "localhost", "-l").addQuoted(b.getUser())
// .add("-i").addFile(b.getIdentityKey().toString()).add("-P", "" + b.getPort()).add("-hostkey").addFile(b.getPubHostKey().toString());
// sc.executeSimpleCommand(command);
// }
// }
// };

ExternalTerminalType XSHELL = new WindowsType("app.xShell","Xshell") {

@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 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 command = CommandBuilder.of().addFile(file.toString()).add("-url").addQuoted("ssh://" + b.getUser() + "@localhost:" + b.getPort())
.add("-i", "xpipe_bridge");
sc.executeSimpleCommand(command);
}
}

private boolean showInfo() {
boolean set = AppCache.get("xshellSetup", Boolean.class, () -> false);
if (set) {
return true;
}

var b = SshLocalBridge.get();
var r = AppWindowHelper.showBlockingAlert(
alert -> {
alert.setTitle(AppI18n.get("xshellSetup"));
alert.setAlertType(Alert.AlertType.NONE);

var activated = AppI18n.get().getMarkdownDocumentation("app:xshellSetup");
var markdown = new MarkdownComp(activated, s -> s.formatted(b.getIdentityKey(), "xpipe_bridge"))
.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
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
Expand Down Expand Up @@ -108,12 +275,44 @@ public boolean supportsColoredTitle() {
@Override
public void launch(LaunchConfiguration configuration) throws Exception {
SshLocalBridge.init();
if (!showInfo()) {
return;
}

var name = "xpipe_bridge";
var host = "localhost";
var port = 21722;
var user = System.getProperty("user.name");
var port = SshLocalBridge.get().getPort();
var user = SshLocalBridge.get().getUser();
Hyperlinks.open("termius://app/host-sharing#label=" + name + "&ip=" + host + "&port=" + port + "&username="
+ user + "&os=windows");
+ user + "&os=undefined");
}

private boolean showInfo() {
boolean set = AppCache.get("termiusSetup", Boolean.class, () -> false);
if (set) {
return true;
}

var b = SshLocalBridge.get();
var r = AppWindowHelper.showBlockingAlert(
alert -> {
alert.setTitle(AppI18n.get("termiusSetup"));
alert.setAlertType(Alert.AlertType.NONE);

var activated = AppI18n.get().getMarkdownDocumentation("app:termiusSetup");
var markdown = new MarkdownComp(activated, s -> s.formatted(b.getIdentityKey(), "xpipe_bridge"))
.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("termiusSetup", true);
});
return r.isPresent();
}
};

Expand Down Expand Up @@ -747,8 +946,10 @@ public TerminalInitFunction additionalInitCommands() {
TabbyTerminalType.TABBY_WINDOWS,
AlacrittyTerminalType.ALACRITTY_WINDOWS,
WezTerminalType.WEZTERM_WINDOWS,
TERMIUS,
MOBAXTERM,
SECURECRT,
TERMIUS,
XSHELL,
CMD,
PWSH,
POWERSHELL);
Expand Down
Loading

0 comments on commit da42eb5

Please sign in to comment.