mirror of
https://github.com/xpipe-io/xpipe.git
synced 2026-08-23 09:46:36 +00:00
Rework
This commit is contained in:
@@ -15,7 +15,7 @@ import io.xpipe.app.ext.DataStoreProviders;
|
||||
import io.xpipe.app.ext.ProcessControlProvider;
|
||||
import io.xpipe.app.icon.SystemIconManager;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.password.KeePassXcPasswordManager;
|
||||
import io.xpipe.app.pwman.KeePassXcPasswordManager;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.resources.*;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package io.xpipe.app.prefs;
|
||||
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.core.*;
|
||||
import io.xpipe.app.core.mode.OperationMode;
|
||||
import io.xpipe.app.ext.PrefsHandler;
|
||||
import io.xpipe.app.ext.PrefsProvider;
|
||||
import io.xpipe.app.icon.SystemIconManager;
|
||||
import io.xpipe.app.icon.SystemIconSource;
|
||||
import io.xpipe.app.password.PasswordManager;
|
||||
import io.xpipe.app.password.PasswordManagerCommand;
|
||||
import io.xpipe.app.pwman.PasswordManager;
|
||||
import io.xpipe.app.pwman.PasswordManagerCommand;
|
||||
import io.xpipe.app.rdp.ExternalRdpClient;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.terminal.ExternalTerminalType;
|
||||
import io.xpipe.app.terminal.TerminalMultiplexer;
|
||||
@@ -17,10 +17,8 @@ import io.xpipe.app.update.AppDistributionType;
|
||||
import io.xpipe.app.util.OptionsBuilder;
|
||||
import io.xpipe.app.util.PlatformState;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
import io.xpipe.app.util.SecretRetrievalStrategy;
|
||||
import io.xpipe.core.process.ShellScript;
|
||||
|
||||
import io.xpipe.core.util.SecretValue;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.beans.value.ObservableBooleanValue;
|
||||
import javafx.beans.value.ObservableDoubleValue;
|
||||
@@ -74,8 +72,8 @@ public class AppPrefs {
|
||||
mapLocal(new SimpleBooleanProperty(true), "saveWindowLocation", Boolean.class, false);
|
||||
final ObjectProperty<ExternalTerminalType> terminalType =
|
||||
mapLocal(new SimpleObjectProperty<>(), "terminalType", ExternalTerminalType.class, false);
|
||||
final ObjectProperty<ExternalRdpClientType> rdpClientType =
|
||||
mapLocal(new SimpleObjectProperty<>(), "rdpClientType", ExternalRdpClientType.class, false);
|
||||
final ObjectProperty<ExternalRdpClient> rdpClientType =
|
||||
mapLocal(new SimpleObjectProperty<>(), "rdpClientType", ExternalRdpClient.class, false);
|
||||
final DoubleProperty windowOpacity = mapLocal(new SimpleDoubleProperty(1.0), "windowOpacity", Double.class, false);
|
||||
final StringProperty customRdpClientCommand =
|
||||
mapLocal(new SimpleStringProperty(null), "customRdpClientCommand", String.class, false);
|
||||
@@ -485,7 +483,7 @@ public class AppPrefs {
|
||||
return terminalType;
|
||||
}
|
||||
|
||||
public ObservableValue<ExternalRdpClientType> rdpClientType() {
|
||||
public ObservableValue<ExternalRdpClient> rdpClientType() {
|
||||
return rdpClientType;
|
||||
}
|
||||
|
||||
@@ -558,7 +556,7 @@ public class AppPrefs {
|
||||
public void initDefaultValues() {
|
||||
externalEditor.setValue(ExternalEditorType.determineDefault(externalEditor.get()));
|
||||
terminalType.set(ExternalTerminalType.determineDefault(terminalType.get()));
|
||||
rdpClientType.setValue(ExternalRdpClientType.determineDefault(rdpClientType.get()));
|
||||
rdpClientType.setValue(ExternalRdpClient.determineDefault(rdpClientType.get()));
|
||||
if (AppProperties.get().isInitialLaunch()) {
|
||||
if (AppDistributionType.get() == AppDistributionType.WEBTOP) {
|
||||
performanceMode.setValue(true);
|
||||
|
||||
@@ -13,37 +13,17 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
public abstract class ExternalApplicationType implements PrefsChoiceValue {
|
||||
|
||||
private final String id;
|
||||
|
||||
public ExternalApplicationType(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
public interface ExternalApplicationType extends PrefsChoiceValue {
|
||||
|
||||
public abstract boolean isAvailable();
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
public String getId();
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getId();
|
||||
}
|
||||
|
||||
public abstract static class MacApplication extends ExternalApplicationType {
|
||||
|
||||
protected final String applicationName;
|
||||
|
||||
public MacApplication(String id, String applicationName) {
|
||||
super(id);
|
||||
this.applicationName = applicationName;
|
||||
}
|
||||
public interface MacApplication extends ExternalApplicationType {
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
default boolean isAvailable() {
|
||||
try {
|
||||
return findApp().isPresent();
|
||||
} catch (Exception e) {
|
||||
@@ -52,17 +32,19 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue {
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<Path> findApp() throws Exception {
|
||||
String getApplicationName();
|
||||
|
||||
default Optional<Path> findApp() throws Exception {
|
||||
// Perform a quick check because mdfind is slow
|
||||
var applicationsDef = Path.of("/Applications/" + applicationName + ".app");
|
||||
var applicationsDef = Path.of("/Applications/" + getApplicationName() + ".app");
|
||||
if (Files.exists(applicationsDef)) {
|
||||
return Optional.of(applicationsDef);
|
||||
}
|
||||
var systemApplicationsDef = Path.of("/System/Applications/" + applicationName + ".app");
|
||||
var systemApplicationsDef = Path.of("/System/Applications/" + getApplicationName() + ".app");
|
||||
if (Files.exists(systemApplicationsDef)) {
|
||||
return Optional.of(systemApplicationsDef);
|
||||
}
|
||||
var userApplicationsDef = Path.of(System.getProperty("user.home") + "/Applications/" + applicationName + ".app");
|
||||
var userApplicationsDef = Path.of(System.getProperty("user.home") + "/Applications/" + getApplicationName() + ".app");
|
||||
if (Files.exists(userApplicationsDef)) {
|
||||
return Optional.of(userApplicationsDef);
|
||||
}
|
||||
@@ -70,59 +52,54 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue {
|
||||
try (ShellControl pc = LocalShell.getShell().start()) {
|
||||
var out = pc.command(String.format(
|
||||
"mdfind -literal 'kMDItemFSName = \"%s.app\"' -onlyin /Applications -onlyin ~/Applications -onlyin /System/Applications",
|
||||
applicationName))
|
||||
getApplicationName()))
|
||||
.readStdoutIfPossible();
|
||||
return out.isPresent() && !out.get().isBlank() && out.get().contains(applicationName + ".app")
|
||||
return out.isPresent() && !out.get().isBlank() && out.get().contains(getApplicationName() + ".app")
|
||||
? out.map(s -> Path.of(s))
|
||||
: Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
public void focus() {
|
||||
default void focus() {
|
||||
try (ShellControl pc = LocalShell.getShell().start()) {
|
||||
pc.command(String.format("open -a \"%s.app\"", applicationName)).execute();
|
||||
pc.command(String.format("open -a \"%s.app\"", getApplicationName())).execute();
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).handle();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSelectable() {
|
||||
default boolean isSelectable() {
|
||||
return OsType.getLocal().equals(OsType.MACOS);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class PathApplication extends ExternalApplicationType {
|
||||
public interface PathApplication extends ExternalApplicationType {
|
||||
|
||||
protected final String executable;
|
||||
protected final boolean explicitlyAsync;
|
||||
String getExecutable();
|
||||
|
||||
public PathApplication(String id, String executable, boolean explicitlyAsync) {
|
||||
super(id);
|
||||
this.executable = executable;
|
||||
this.explicitlyAsync = explicitlyAsync;
|
||||
}
|
||||
boolean isExplicitlyAsync();
|
||||
|
||||
public boolean isAvailable() {
|
||||
default boolean isAvailable() {
|
||||
try (ShellControl pc = LocalShell.getShell()) {
|
||||
return CommandSupport.findProgram(pc, executable).isPresent();
|
||||
return CommandSupport.findProgram(pc, getExecutable()).isPresent();
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).omit().handle();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected void launch(String title, CommandBuilder args) throws Exception {
|
||||
default void launch(CommandBuilder args) throws Exception {
|
||||
try (ShellControl pc = LocalShell.getShell()) {
|
||||
if (!CommandSupport.isInPath(pc, executable)) {
|
||||
if (!CommandSupport.isInPath(pc, getExecutable())) {
|
||||
throw ErrorEvent.expected(
|
||||
new IOException(
|
||||
"Executable " + executable
|
||||
"Executable " + getExecutable()
|
||||
+ " not found in PATH. Either add it to the PATH and refresh the environment by restarting XPipe, or specify an absolute executable path using the custom terminal setting."));
|
||||
}
|
||||
|
||||
args.add(0, executable);
|
||||
if (explicitlyAsync) {
|
||||
args.add(0, getExecutable());
|
||||
if (isExplicitlyAsync()) {
|
||||
ExternalApplicationHelper.startAsync(args);
|
||||
} else {
|
||||
pc.executeSimpleCommand(args);
|
||||
@@ -131,21 +108,16 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue {
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class WindowsType extends ExternalApplicationType {
|
||||
public interface WindowsType extends ExternalApplicationType {
|
||||
|
||||
private final String executable;
|
||||
String getExecutable();
|
||||
|
||||
public WindowsType(String id, String executable) {
|
||||
super(id);
|
||||
this.executable = executable;
|
||||
}
|
||||
public abstract Optional<Path> determineInstallation();
|
||||
|
||||
protected abstract Optional<Path> determineInstallation();
|
||||
|
||||
protected Optional<Path> determineFromPath() {
|
||||
default Optional<Path> determineFromPath() {
|
||||
// Try to locate if it is in the Path
|
||||
try (var sc = LocalShell.getShell().start()) {
|
||||
var out = CommandSupport.findProgram(sc, executable);
|
||||
var out = CommandSupport.findProgram(sc, getExecutable());
|
||||
if (out.isPresent()) {
|
||||
return out.map(filePath -> Path.of(filePath.toString()));
|
||||
}
|
||||
@@ -155,8 +127,20 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
default Path findExecutable() {
|
||||
var location = determineFromPath();
|
||||
if (location.isEmpty()) {
|
||||
location = determineInstallation();
|
||||
if (location.isEmpty()) {
|
||||
throw ErrorEvent.expected(new UnsupportedOperationException("Unable to find installation of "
|
||||
+ toTranslatedString().getValue()));
|
||||
}
|
||||
}
|
||||
return location.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
default boolean isAvailable() {
|
||||
var path = determineFromPath();
|
||||
if (path.isPresent() && Files.exists(path.get())) {
|
||||
return true;
|
||||
@@ -167,7 +151,7 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSelectable() {
|
||||
default boolean isSelectable() {
|
||||
return OsType.getLocal().equals(OsType.WINDOWS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,17 +22,48 @@ import java.util.function.Supplier;
|
||||
|
||||
public interface ExternalEditorType extends PrefsChoiceValue {
|
||||
|
||||
ExternalEditorType NOTEPAD = new WindowsType("app.notepad", "notepad", false) {
|
||||
ExternalEditorType NOTEPAD = new WindowsType() {
|
||||
|
||||
@Override
|
||||
protected Optional<Path> determineInstallation() {
|
||||
public String getId() {
|
||||
return "app.notepad";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean detach() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExecutable() {
|
||||
return "notepad";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Path> determineInstallation() {
|
||||
return Optional.of(Path.of(System.getenv("SystemRoot") + "\\System32\\notepad.exe"));
|
||||
}
|
||||
};
|
||||
|
||||
ExternalEditorType VSCODIUM_WINDOWS = new WindowsType("app.vscodium", "codium.cmd", false) {
|
||||
ExternalEditorType VSCODIUM_WINDOWS = new WindowsType() {
|
||||
|
||||
@Override
|
||||
protected Optional<Path> determineInstallation() {
|
||||
public String getId() {
|
||||
return "app.vscodium";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean detach() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExecutable() {
|
||||
return "codium.cmd";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Path> determineInstallation() {
|
||||
return Optional.of(Path.of(System.getenv("LOCALAPPDATA"))
|
||||
.resolve("Programs")
|
||||
.resolve("VSCodium")
|
||||
@@ -42,10 +73,25 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
||||
}
|
||||
};
|
||||
|
||||
WindowsType CURSOR_WINDOWS = new WindowsType("app.cursor", "Cursor", true) {
|
||||
WindowsType CURSOR_WINDOWS = new WindowsType() {
|
||||
|
||||
@Override
|
||||
protected Optional<Path> determineInstallation() {
|
||||
public String getId() {
|
||||
return "app.cursor";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean detach() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExecutable() {
|
||||
return "Cursor";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Path> determineInstallation() {
|
||||
return Optional.of(Path.of(System.getenv("LOCALAPPDATA"))
|
||||
.resolve("Programs")
|
||||
.resolve("cursor")
|
||||
@@ -54,10 +100,25 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
||||
}
|
||||
};
|
||||
|
||||
WindowsType VOID_WINDOWS = new WindowsType("app.void", "Void", true) {
|
||||
WindowsType VOID_WINDOWS = new WindowsType() {
|
||||
|
||||
@Override
|
||||
protected Optional<Path> determineInstallation() {
|
||||
public String getId() {
|
||||
return "app.void";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean detach() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExecutable() {
|
||||
return "Void";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Path> determineInstallation() {
|
||||
return Optional.of(Path.of(System.getenv("PROGRAMFILES"))
|
||||
.resolve("Void")
|
||||
.resolve("Void.exe"))
|
||||
@@ -65,10 +126,25 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
||||
}
|
||||
};
|
||||
|
||||
WindowsType WINDSURF_WINDOWS = new WindowsType("app.windsurf", "windsurf.cmd", false) {
|
||||
WindowsType WINDSURF_WINDOWS = new WindowsType() {
|
||||
|
||||
@Override
|
||||
protected Optional<Path> determineInstallation() {
|
||||
public String getId() {
|
||||
return "app.windsurf";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean detach() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExecutable() {
|
||||
return "windsurf.cmd";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Path> determineInstallation() {
|
||||
return Optional.of(Path.of(System.getenv("LOCALAPPDATA"))
|
||||
.resolve("Programs")
|
||||
.resolve("Windsurf")
|
||||
@@ -79,10 +155,25 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
||||
};
|
||||
|
||||
// Cli is broken, keep inactive
|
||||
WindowsType THEIAIDE_WINDOWS = new WindowsType("app.theiaide", "Theiaide", true) {
|
||||
WindowsType THEIAIDE_WINDOWS = new WindowsType() {
|
||||
|
||||
@Override
|
||||
protected Optional<Path> determineInstallation() {
|
||||
public String getId() {
|
||||
return "app.theiaide";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean detach() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExecutable() {
|
||||
return "Theiaide";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Path> determineInstallation() {
|
||||
return Optional.of(Path.of(System.getenv("LOCALAPPDATA"))
|
||||
.resolve("Programs")
|
||||
.resolve("TheiaIDE")
|
||||
@@ -91,10 +182,25 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
||||
}
|
||||
};
|
||||
|
||||
WindowsType TRAE_WINDOWS = new WindowsType("app.trae", "trae.cmd", false) {
|
||||
WindowsType TRAE_WINDOWS = new WindowsType() {
|
||||
|
||||
@Override
|
||||
protected Optional<Path> determineInstallation() {
|
||||
public String getId() {
|
||||
return "app.trae";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean detach() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExecutable() {
|
||||
return "trae.cmd";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Path> determineInstallation() {
|
||||
return Optional.of(Path.of(System.getenv("LOCALAPPDATA"))
|
||||
.resolve("Programs")
|
||||
.resolve("Trae")
|
||||
@@ -104,10 +210,25 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
||||
}
|
||||
};
|
||||
|
||||
WindowsType VSCODE_WINDOWS = new WindowsType("app.vscode", "code.cmd", false) {
|
||||
WindowsType VSCODE_WINDOWS = new WindowsType() {
|
||||
|
||||
@Override
|
||||
protected Optional<Path> determineInstallation() {
|
||||
public String getId() {
|
||||
return "app.vscode";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean detach() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExecutable() {
|
||||
return "code.cmd";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Path> determineInstallation() {
|
||||
return Optional.of(Path.of(System.getenv("LOCALAPPDATA"))
|
||||
.resolve("Programs")
|
||||
.resolve("Microsoft VS Code")
|
||||
@@ -117,10 +238,25 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
||||
}
|
||||
};
|
||||
|
||||
ExternalEditorType VSCODE_INSIDERS_WINDOWS = new WindowsType("app.vscodeInsiders", "code-insiders.cmd", false) {
|
||||
ExternalEditorType VSCODE_INSIDERS_WINDOWS = new WindowsType() {
|
||||
|
||||
@Override
|
||||
protected Optional<Path> determineInstallation() {
|
||||
public String getId() {
|
||||
return "app.vscodeInsiders";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean detach() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExecutable() {
|
||||
return "code-insiders.cmd";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Path> determineInstallation() {
|
||||
return Optional.of(Path.of(System.getenv("LOCALAPPDATA"))
|
||||
.resolve("Programs")
|
||||
.resolve("Microsoft VS Code Insiders")
|
||||
@@ -130,10 +266,25 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
||||
}
|
||||
};
|
||||
|
||||
ExternalEditorType NOTEPADPLUSPLUS = new WindowsType("app.notepad++", "notepad++", false) {
|
||||
ExternalEditorType NOTEPADPLUSPLUS = new WindowsType() {
|
||||
|
||||
@Override
|
||||
protected Optional<Path> determineInstallation() {
|
||||
public String getId() {
|
||||
return "app.notepad++";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean detach() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExecutable() {
|
||||
return "notepad++";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Path> determineInstallation() {
|
||||
var found = WindowsRegistry.local()
|
||||
.readStringValueIfPresent(WindowsRegistry.HKEY_LOCAL_MACHINE, "SOFTWARE\\Notepad++", null);
|
||||
|
||||
@@ -152,7 +303,7 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
||||
public void launch(Path file) throws Exception {
|
||||
var builder = CommandBuilder.of()
|
||||
.fixedEnvironment("DONT_PROMPT_WSL_INSTALL", "No_Prompt_please")
|
||||
.addFile(executable)
|
||||
.addFile(getExecutable())
|
||||
.addFile(file.toString());
|
||||
ExternalApplicationHelper.startAsync(builder);
|
||||
}
|
||||
@@ -310,10 +461,14 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
||||
|
||||
void launch(Path file) throws Exception;
|
||||
|
||||
class MacOsEditor extends ExternalApplicationType.MacApplication implements ExternalEditorType {
|
||||
class MacOsEditor implements ExternalApplicationType.MacApplication, ExternalEditorType {
|
||||
|
||||
public MacOsEditor(String id, String applicationName) {
|
||||
super(id, applicationName);
|
||||
private final String id;
|
||||
private final String appName;
|
||||
|
||||
public MacOsEditor(String id, String appName) {
|
||||
this.id = id;
|
||||
this.appName = appName;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -321,33 +476,64 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
||||
try (var sc = LocalShell.getShell().start()) {
|
||||
sc.executeSimpleCommand(CommandBuilder.of()
|
||||
.add("open", "-a")
|
||||
.addQuoted(applicationName)
|
||||
.addQuoted(getApplicationName())
|
||||
.addFile(file.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getApplicationName() {
|
||||
return appName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
class GenericPathType extends ExternalApplicationType.PathApplication implements ExternalEditorType {
|
||||
class GenericPathType implements ExternalApplicationType.PathApplication, ExternalEditorType {
|
||||
|
||||
public GenericPathType(String id, String command, boolean explicityAsync) {
|
||||
super(id, command, explicityAsync);
|
||||
private final String id;
|
||||
private final String executable;
|
||||
private final boolean async;
|
||||
|
||||
public GenericPathType(String id, String executable, boolean async) {
|
||||
this.id = id;
|
||||
this.executable = executable;
|
||||
this.async = async;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launch(Path file) throws Exception {
|
||||
var builder = CommandBuilder.of().addFile(executable).addFile(file.toString());
|
||||
if (explicitlyAsync) {
|
||||
var builder = CommandBuilder.of().addFile(getExecutable()).addFile(file.toString());
|
||||
if (isExplicitlyAsync()) {
|
||||
ExternalApplicationHelper.startAsync(builder);
|
||||
} else {
|
||||
LocalShell.getShell().executeSimpleCommand(builder);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExecutable() {
|
||||
return executable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExplicitlyAsync() {
|
||||
return async;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
class LinuxPathType extends GenericPathType {
|
||||
|
||||
public LinuxPathType(String id, String command) {
|
||||
super(id, command, true);
|
||||
public LinuxPathType(String id, String executable) {
|
||||
super(id, executable, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -356,37 +542,19 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
||||
}
|
||||
}
|
||||
|
||||
abstract class WindowsType extends ExternalApplicationType.WindowsType implements ExternalEditorType {
|
||||
interface WindowsType extends ExternalApplicationType.WindowsType, ExternalEditorType {
|
||||
|
||||
private final boolean detach;
|
||||
|
||||
public WindowsType(String id, String executable, boolean detach) {
|
||||
super(id, executable);
|
||||
this.detach = detach;
|
||||
}
|
||||
boolean detach();
|
||||
|
||||
@Override
|
||||
public void launch(Path file) throws Exception {
|
||||
default void launch(Path file) throws Exception {
|
||||
var location = findExecutable();
|
||||
if (location.isEmpty()) {
|
||||
throw ErrorEvent.expected(new IOException(
|
||||
"Unable to find installation of " + toTranslatedString().getValue()));
|
||||
}
|
||||
|
||||
var builder = CommandBuilder.of().addFile(location.get().toString()).addFile(file.toString());
|
||||
if (detach) {
|
||||
var builder = CommandBuilder.of().addFile(location.toString()).addFile(file.toString());
|
||||
if (detach()) {
|
||||
ExternalApplicationHelper.startAsync(builder);
|
||||
} else {
|
||||
LocalShell.getShell().executeSimpleCommand(builder);
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<Path> findExecutable() {
|
||||
var location = determineFromPath();
|
||||
if (location.isEmpty()) {
|
||||
location = determineInstallation();
|
||||
}
|
||||
return location;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,412 +0,0 @@
|
||||
package io.xpipe.app.prefs;
|
||||
|
||||
import io.xpipe.app.ext.PrefsChoiceValue;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.util.*;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.util.SecretValue;
|
||||
|
||||
import lombok.Value;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public interface ExternalRdpClientType extends PrefsChoiceValue {
|
||||
|
||||
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
|
||||
public void launch(LaunchConfiguration configuration) throws Exception {
|
||||
var adaptedRdpConfig = getAdaptedConfig(configuration);
|
||||
var file = writeRdpConfigFile(configuration.getTitle(), adaptedRdpConfig);
|
||||
LocalShell.getShell()
|
||||
.executeSimpleCommand(CommandBuilder.of().add(executable).addFile(file.toString()));
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
ThreadHelper.sleep(1000);
|
||||
FileUtils.deleteQuietly(file.toFile());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsPasswordPassing() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private RdpConfig getAdaptedConfig(LaunchConfiguration configuration) throws Exception {
|
||||
var input = configuration.getConfig();
|
||||
if (input.get("password 51").isPresent()) {
|
||||
return input;
|
||||
}
|
||||
|
||||
if (input.get("username").isEmpty()) {
|
||||
// return input;
|
||||
}
|
||||
|
||||
var pass = configuration.getPassword();
|
||||
if (pass == null) {
|
||||
return input;
|
||||
}
|
||||
|
||||
var adapted = input.overlay(Map.of(
|
||||
"password 51",
|
||||
new RdpConfig.TypedValue("b", encrypt(pass)),
|
||||
"prompt for credentials",
|
||||
new RdpConfig.TypedValue("i", "0")));
|
||||
return adapted;
|
||||
}
|
||||
|
||||
private String encrypt(SecretValue password) throws Exception {
|
||||
var ps = LocalShell.getLocalPowershell();
|
||||
var cmd = ps.command(CommandBuilder.of().add(sc -> "(" + sc.getShellDialect().literalArgument(password.getSecretValue()) + " | ConvertTo-SecureString -AsPlainText -Force) | ConvertFrom-SecureString"));
|
||||
cmd.sensitive();
|
||||
return cmd.readStdoutOrThrow();
|
||||
}
|
||||
};
|
||||
|
||||
ExternalRdpClientType DEVOLUTIONS = new WindowsType("app.devolutions", "RemoteDesktopManager") {
|
||||
|
||||
@Override
|
||||
protected Optional<Path> determineInstallation() {
|
||||
try {
|
||||
var r = WindowsRegistry.local()
|
||||
.readStringValueIfPresent(
|
||||
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 = writeRdpConfigFile(configuration.getTitle(), configuration.getConfig());
|
||||
LocalShell.getShell()
|
||||
.executeSimpleCommand(CommandBuilder.of()
|
||||
.addFile(file.toString())
|
||||
.addFile(config.toString())
|
||||
.discardAllOutput());
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
// Startup is slow
|
||||
ThreadHelper.sleep(10000);
|
||||
FileUtils.deleteQuietly(config.toFile());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsPasswordPassing() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
ExternalRdpClientType REMMINA = new RemminaRdpType();
|
||||
|
||||
ExternalRdpClientType X_FREE_RDP = new PathCheckType("app.xfreeRdp", "xfreerdp", true) {
|
||||
|
||||
@Override
|
||||
public void launch(LaunchConfiguration configuration) throws Exception {
|
||||
var file = writeRdpConfigFile(configuration.getTitle(), configuration.getConfig());
|
||||
var b = CommandBuilder.of().addFile(file.toString()).add("/cert-ignore");
|
||||
if (configuration.getPassword() != null) {
|
||||
var escapedPw = configuration.getPassword().getSecretValue().replaceAll("'", "\\\\'");
|
||||
b.add("/p:'" + escapedPw + "'");
|
||||
}
|
||||
launch(configuration.getTitle(), b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsPasswordPassing() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
ExternalRdpClientType MICROSOFT_REMOTE_DESKTOP_MACOS_APP =
|
||||
new MacOsType("app.microsoftRemoteDesktopApp", "Microsoft Remote Desktop") {
|
||||
|
||||
@Override
|
||||
public void launch(LaunchConfiguration configuration) throws Exception {
|
||||
var file = writeRdpConfigFile(configuration.getTitle(), configuration.getConfig());
|
||||
LocalShell.getShell()
|
||||
.executeSimpleCommand(CommandBuilder.of()
|
||||
.add("open", "-a")
|
||||
.addQuoted("Microsoft Remote Desktop.app")
|
||||
.addFile(file.toString()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsPasswordPassing() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
ExternalRdpClientType WINDOWS_APP_MACOS = new MacOsType("app.windowsApp", "Windows App") {
|
||||
|
||||
@Override
|
||||
public void launch(LaunchConfiguration configuration) throws Exception {
|
||||
var file = writeRdpConfigFile(configuration.getTitle(), configuration.getConfig());
|
||||
LocalShell.getShell()
|
||||
.executeSimpleCommand(CommandBuilder.of()
|
||||
.add("open", "-a")
|
||||
.addQuoted("Windows App.app")
|
||||
.addFile(file.toString()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsPasswordPassing() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
ExternalRdpClientType CUSTOM = new CustomType();
|
||||
List<ExternalRdpClientType> WINDOWS_CLIENTS = List.of(MSTSC, DEVOLUTIONS);
|
||||
List<ExternalRdpClientType> LINUX_CLIENTS = List.of(REMMINA, X_FREE_RDP);
|
||||
List<ExternalRdpClientType> MACOS_CLIENTS = List.of(MICROSOFT_REMOTE_DESKTOP_MACOS_APP, WINDOWS_APP_MACOS);
|
||||
|
||||
@SuppressWarnings("TrivialFunctionalExpressionUsage")
|
||||
List<ExternalRdpClientType> ALL = ((Supplier<List<ExternalRdpClientType>>) () -> {
|
||||
var all = new ArrayList<ExternalRdpClientType>();
|
||||
if (OsType.getLocal().equals(OsType.WINDOWS)) {
|
||||
all.addAll(WINDOWS_CLIENTS);
|
||||
}
|
||||
if (OsType.getLocal().equals(OsType.LINUX)) {
|
||||
all.addAll(LINUX_CLIENTS);
|
||||
}
|
||||
if (OsType.getLocal().equals(OsType.MACOS)) {
|
||||
all.addAll(MACOS_CLIENTS);
|
||||
}
|
||||
all.add(CUSTOM);
|
||||
return all;
|
||||
})
|
||||
.get();
|
||||
|
||||
static ExternalRdpClientType determineDefault(ExternalRdpClientType existing) {
|
||||
// Verify that our selection is still valid
|
||||
if (existing != null && existing.isAvailable()) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
var r = ALL.stream()
|
||||
.filter(t -> !t.equals(CUSTOM))
|
||||
.filter(t -> t.isAvailable())
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
// Check if detection failed for some reason
|
||||
if (r == null) {
|
||||
var def = OsType.getLocal() == OsType.WINDOWS
|
||||
? MSTSC
|
||||
: OsType.getLocal() == OsType.MACOS ? WINDOWS_APP_MACOS : REMMINA;
|
||||
r = def;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void launch(LaunchConfiguration configuration) throws Exception;
|
||||
|
||||
boolean supportsPasswordPassing();
|
||||
|
||||
default Path writeRdpConfigFile(String title, RdpConfig input) throws Exception {
|
||||
var name = OsType.getLocal().makeFileSystemCompatible(title);
|
||||
var file = ShellTemp.getLocalTempDataDirectory("rdp").resolve(name + ".rdp");
|
||||
var string = input.toString();
|
||||
Files.createDirectories(file.getParent());
|
||||
Files.writeString(file, string);
|
||||
return file;
|
||||
}
|
||||
|
||||
@Value
|
||||
class LaunchConfiguration {
|
||||
String title;
|
||||
RdpConfig config;
|
||||
UUID storeId;
|
||||
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 ErrorEvent.expected(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) {
|
||||
super(id, executable, explicityAsync);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class MacOsType extends ExternalApplicationType.MacApplication implements ExternalRdpClientType {
|
||||
|
||||
public MacOsType(String id, String applicationName) {
|
||||
super(id, applicationName);
|
||||
}
|
||||
}
|
||||
|
||||
class CustomType extends ExternalApplicationType implements ExternalRdpClientType {
|
||||
|
||||
public CustomType() {
|
||||
super("app.custom");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launch(LaunchConfiguration configuration) throws Exception {
|
||||
var customCommand = AppPrefs.get().customRdpClientCommand().getValue();
|
||||
if (customCommand == null || customCommand.isBlank()) {
|
||||
throw ErrorEvent.expected(new IllegalStateException("No custom RDP command specified"));
|
||||
}
|
||||
|
||||
var format =
|
||||
customCommand.toLowerCase(Locale.ROOT).contains("$file") ? customCommand : customCommand + " $FILE";
|
||||
ExternalApplicationHelper.startAsync(CommandBuilder.of()
|
||||
.add(ExternalApplicationHelper.replaceVariableArgument(
|
||||
format,
|
||||
"FILE",
|
||||
writeRdpConfigFile(configuration.getTitle(), configuration.getConfig())
|
||||
.toString())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsPasswordPassing() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class RemminaRdpType extends ExternalApplicationType.PathApplication implements ExternalRdpClientType {
|
||||
|
||||
public RemminaRdpType() {
|
||||
super("app.remmina", "remmina", true);
|
||||
}
|
||||
|
||||
private List<String> toStrip() {
|
||||
return List.of("auto connect", "password 51", "prompt for credentials", "smart sizing");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launch(LaunchConfiguration configuration) throws Exception {
|
||||
RdpConfig c = configuration.getConfig();
|
||||
var l = new HashSet<>(c.getContent().keySet());
|
||||
toStrip().forEach(l::remove);
|
||||
if (l.size() == 2 && l.contains("username") && l.contains("full address")) {
|
||||
var encrypted = encryptPassword(configuration.getPassword());
|
||||
if (encrypted.isPresent()) {
|
||||
var file = writeRemminaConfigFile(configuration, encrypted.get());
|
||||
launch(
|
||||
configuration.getTitle(),
|
||||
CommandBuilder.of().add("-c").addFile(file.toString()));
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
ThreadHelper.sleep(5000);
|
||||
FileUtils.deleteQuietly(file.toFile());
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var file = writeRdpConfigFile(configuration.getTitle(), c);
|
||||
launch(configuration.getTitle(), CommandBuilder.of().add("-c").addFile(file.toString()));
|
||||
}
|
||||
|
||||
private Optional<String> encryptPassword(SecretValue password) throws Exception {
|
||||
if (password == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
try (var sc = LocalShell.getShell().start()) {
|
||||
var prefSecretBase64 = sc.command("sed -n 's/^secret=//p' ~/.config/remmina/remmina.pref")
|
||||
.readStdoutIfPossible();
|
||||
if (prefSecretBase64.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
var paddedPassword = password.getSecretValue();
|
||||
paddedPassword = paddedPassword + "\0".repeat(8 - paddedPassword.length() % 8);
|
||||
var prefSecret = Base64.getDecoder().decode(prefSecretBase64.get());
|
||||
var key = Arrays.copyOfRange(prefSecret, 0, 24);
|
||||
var iv = Arrays.copyOfRange(prefSecret, 24, prefSecret.length);
|
||||
|
||||
var cipher = Cipher.getInstance("DESede/CBC/Nopadding");
|
||||
var keySpec = new SecretKeySpec(key, "DESede");
|
||||
var ivspec = new IvParameterSpec(iv);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivspec);
|
||||
byte[] encryptedText = cipher.doFinal(paddedPassword.getBytes(StandardCharsets.UTF_8));
|
||||
var base64Encrypted = Base64.getEncoder().encodeToString(encryptedText);
|
||||
return Optional.ofNullable(base64Encrypted);
|
||||
}
|
||||
}
|
||||
|
||||
private Path writeRemminaConfigFile(LaunchConfiguration configuration, String password) throws Exception {
|
||||
var name = OsType.getLocal().makeFileSystemCompatible(configuration.getTitle());
|
||||
var file = ShellTemp.getLocalTempDataDirectory("rdp").resolve(name + ".remmina");
|
||||
var string =
|
||||
"""
|
||||
[remmina]
|
||||
protocol=RDP
|
||||
name=%s
|
||||
username=%s
|
||||
server=%s
|
||||
password=%s
|
||||
cert_ignore=1
|
||||
"""
|
||||
.formatted(
|
||||
configuration.getTitle(),
|
||||
configuration
|
||||
.getConfig()
|
||||
.get("username")
|
||||
.orElseThrow()
|
||||
.getValue(),
|
||||
configuration
|
||||
.getConfig()
|
||||
.get("full address")
|
||||
.orElseThrow()
|
||||
.getValue(),
|
||||
password);
|
||||
Files.createDirectories(file.getParent());
|
||||
Files.writeString(file, string);
|
||||
return file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsPasswordPassing() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import io.xpipe.app.comp.base.HorizontalComp;
|
||||
import io.xpipe.app.comp.base.LabelComp;
|
||||
import io.xpipe.app.comp.base.TextFieldComp;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.password.PasswordManager;
|
||||
import io.xpipe.app.pwman.PasswordManager;
|
||||
import io.xpipe.app.util.*;
|
||||
|
||||
import javafx.application.Platform;
|
||||
|
||||
@@ -4,6 +4,7 @@ import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.base.ChoiceComp;
|
||||
import io.xpipe.app.comp.base.TextFieldComp;
|
||||
import io.xpipe.app.ext.PrefsChoiceValue;
|
||||
import io.xpipe.app.rdp.ExternalRdpClient;
|
||||
import io.xpipe.app.util.OptionsBuilder;
|
||||
|
||||
public class RdpCategory extends AppPrefsCategory {
|
||||
@@ -21,11 +22,11 @@ public class RdpCategory extends AppPrefsCategory {
|
||||
.sub(new OptionsBuilder()
|
||||
.nameAndDescription("rdpClient")
|
||||
.addComp(ChoiceComp.ofTranslatable(
|
||||
prefs.rdpClientType, PrefsChoiceValue.getSupported(ExternalRdpClientType.class), false))
|
||||
prefs.rdpClientType, PrefsChoiceValue.getSupported(ExternalRdpClient.class), false))
|
||||
.nameAndDescription("customRdpClientCommand")
|
||||
.addComp(new TextFieldComp(prefs.customRdpClientCommand, true)
|
||||
.apply(struc -> struc.get().setPromptText("myrdpclient -c $FILE"))
|
||||
.hide(prefs.rdpClientType.isNotEqualTo(ExternalRdpClientType.CUSTOM))))
|
||||
.hide(prefs.rdpClientType.isNotEqualTo(ExternalRdpClient.CUSTOM))))
|
||||
.buildComp();
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package io.xpipe.app.password;
|
||||
package io.xpipe.app.pwman;
|
||||
|
||||
import io.xpipe.app.ext.ProcessControlProvider;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package io.xpipe.app.password;
|
||||
package io.xpipe.app.pwman;
|
||||
|
||||
import io.xpipe.app.ext.ProcessControlProvider;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
+1
-5
@@ -1,17 +1,13 @@
|
||||
package io.xpipe.app.password;
|
||||
package io.xpipe.app.pwman;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import io.xpipe.app.comp.base.ButtonComp;
|
||||
import io.xpipe.app.comp.base.ContextualFileReferenceChoiceComp;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.ext.ProcessControlProvider;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.terminal.TerminalLauncher;
|
||||
import io.xpipe.app.util.*;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import io.xpipe.core.process.ShellScript;
|
||||
import io.xpipe.core.store.FilePath;
|
||||
import io.xpipe.core.util.InPlaceSecretValue;
|
||||
import io.xpipe.core.util.JacksonMapper;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package io.xpipe.app.password;
|
||||
package io.xpipe.app.pwman;
|
||||
|
||||
import io.xpipe.core.util.InPlaceSecretValue;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package io.xpipe.app.password;
|
||||
package io.xpipe.app.pwman;
|
||||
|
||||
import io.xpipe.app.comp.base.ButtonComp;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package io.xpipe.app.password;
|
||||
package io.xpipe.app.pwman;
|
||||
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.util.DocumentationLink;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package io.xpipe.app.password;
|
||||
package io.xpipe.app.pwman;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import io.xpipe.app.ext.ProcessControlProvider;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package io.xpipe.app.password;
|
||||
package io.xpipe.app.pwman;
|
||||
|
||||
import io.xpipe.app.ext.ProcessControlProvider;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package io.xpipe.app.password;
|
||||
package io.xpipe.app.pwman;
|
||||
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.ext.ProcessControlProvider;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package io.xpipe.app.password;
|
||||
package io.xpipe.app.pwman;
|
||||
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.util.SecretValue;
|
||||
+1
-2
@@ -1,4 +1,4 @@
|
||||
package io.xpipe.app.password;
|
||||
package io.xpipe.app.pwman;
|
||||
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.base.IntegratedTextAreaComp;
|
||||
@@ -13,7 +13,6 @@ import io.xpipe.core.process.ShellControl;
|
||||
import io.xpipe.core.process.ShellScript;
|
||||
import io.xpipe.core.util.InPlaceSecretValue;
|
||||
import io.xpipe.core.util.SecretValue;
|
||||
import io.xpipe.core.util.ValidationException;
|
||||
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package io.xpipe.app.password;
|
||||
package io.xpipe.app.pwman;
|
||||
|
||||
import io.xpipe.app.ext.PrefsChoiceValue;
|
||||
import io.xpipe.core.process.OsType;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package io.xpipe.app.password;
|
||||
package io.xpipe.app.pwman;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import io.xpipe.app.comp.base.SecretFieldComp;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package io.xpipe.app.password;
|
||||
package io.xpipe.app.pwman;
|
||||
|
||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.bouncycastle.crypto.KeyGenerationParameters;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package io.xpipe.app.password;
|
||||
package io.xpipe.app.pwman;
|
||||
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.util.LocalShell;
|
||||
@@ -0,0 +1,40 @@
|
||||
package io.xpipe.app.rdp;
|
||||
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.prefs.ExternalApplicationHelper;
|
||||
import io.xpipe.app.prefs.ExternalApplicationType;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class CustomRdpClient implements ExternalApplicationType, ExternalRdpClient {
|
||||
|
||||
@Override
|
||||
public void launch(RdpLaunchConfig configuration) throws Exception {
|
||||
var customCommand = AppPrefs.get().customRdpClientCommand().getValue();
|
||||
if (customCommand == null || customCommand.isBlank()) {
|
||||
throw ErrorEvent.expected(new IllegalStateException("No custom RDP command specified"));
|
||||
}
|
||||
|
||||
var format = customCommand.toLowerCase(Locale.ROOT).contains("$file") ? customCommand : customCommand + " $FILE";
|
||||
ExternalApplicationHelper.startAsync(CommandBuilder.of()
|
||||
.add(ExternalApplicationHelper.replaceVariableArgument(format, "FILE",
|
||||
writeRdpConfigFile(configuration.getTitle(), configuration.getConfig()).toString())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsPasswordPassing() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "app.custom";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package io.xpipe.app.rdp;
|
||||
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.prefs.ExternalApplicationType;
|
||||
import io.xpipe.app.util.LocalShell;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.app.util.WindowsRegistry;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
public class DevolutionsRdpClient implements ExternalApplicationType.WindowsType, ExternalRdpClient {
|
||||
|
||||
@Override
|
||||
public String getExecutable() {
|
||||
return "RemoteDesktopManager";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Path> determineInstallation() {
|
||||
try {
|
||||
var r = WindowsRegistry.local().readStringValueIfPresent(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
|
||||
public void launch(RdpLaunchConfig configuration) throws Exception {
|
||||
var location = findExecutable();
|
||||
var config = writeRdpConfigFile(configuration.getTitle(), configuration.getConfig());
|
||||
LocalShell.getShell().executeSimpleCommand(CommandBuilder.of().addFile(location).addFile(config).discardAllOutput());
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
// Startup is slow
|
||||
ThreadHelper.sleep(10000);
|
||||
FileUtils.deleteQuietly(config.toFile());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsPasswordPassing() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "app.devolutions";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package io.xpipe.app.rdp;
|
||||
|
||||
import io.xpipe.app.ext.PrefsChoiceValue;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.util.*;
|
||||
import io.xpipe.core.process.OsType;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public interface ExternalRdpClient extends PrefsChoiceValue {
|
||||
|
||||
static ExternalRdpClient getApplicationLauncher() {
|
||||
if (OsType.getLocal() == OsType.WINDOWS) {
|
||||
return MSTSC;
|
||||
} else {
|
||||
return AppPrefs.get().rdpClientType().getValue();
|
||||
}
|
||||
}
|
||||
|
||||
ExternalRdpClient MSTSC = new MstscRdpClient();
|
||||
|
||||
ExternalRdpClient DEVOLUTIONS = new DevolutionsRdpClient();
|
||||
|
||||
ExternalRdpClient REMMINA = new RemminaRdpClient();
|
||||
|
||||
ExternalRdpClient X_FREE_RDP = new FreeRdpClient();
|
||||
|
||||
ExternalRdpClient MICROSOFT_REMOTE_DESKTOP_MACOS_APP = new RemoteDesktopAppRdpClient();
|
||||
|
||||
ExternalRdpClient WINDOWS_APP_MACOS = new WindowsAppRdpClient();
|
||||
|
||||
ExternalRdpClient CUSTOM = new CustomRdpClient();
|
||||
List<ExternalRdpClient> WINDOWS_CLIENTS = List.of(MSTSC, DEVOLUTIONS);
|
||||
List<ExternalRdpClient> LINUX_CLIENTS = List.of(REMMINA, X_FREE_RDP);
|
||||
List<ExternalRdpClient> MACOS_CLIENTS = List.of(MICROSOFT_REMOTE_DESKTOP_MACOS_APP, WINDOWS_APP_MACOS);
|
||||
|
||||
@SuppressWarnings("TrivialFunctionalExpressionUsage")
|
||||
List<ExternalRdpClient> ALL = ((Supplier<List<ExternalRdpClient>>) () -> {
|
||||
var all = new ArrayList<ExternalRdpClient>();
|
||||
if (OsType.getLocal().equals(OsType.WINDOWS)) {
|
||||
all.addAll(WINDOWS_CLIENTS);
|
||||
}
|
||||
if (OsType.getLocal().equals(OsType.LINUX)) {
|
||||
all.addAll(LINUX_CLIENTS);
|
||||
}
|
||||
if (OsType.getLocal().equals(OsType.MACOS)) {
|
||||
all.addAll(MACOS_CLIENTS);
|
||||
}
|
||||
all.add(CUSTOM);
|
||||
return all;
|
||||
})
|
||||
.get();
|
||||
|
||||
static ExternalRdpClient determineDefault(ExternalRdpClient existing) {
|
||||
// Verify that our selection is still valid
|
||||
if (existing != null && existing.isAvailable()) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
var r = ALL.stream()
|
||||
.filter(t -> !t.equals(CUSTOM))
|
||||
.filter(t -> t.isAvailable())
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
// Check if detection failed for some reason
|
||||
if (r == null) {
|
||||
var def = OsType.getLocal() == OsType.WINDOWS
|
||||
? MSTSC
|
||||
: OsType.getLocal() == OsType.MACOS ? WINDOWS_APP_MACOS : REMMINA;
|
||||
r = def;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void launch(RdpLaunchConfig configuration) throws Exception;
|
||||
|
||||
boolean supportsPasswordPassing();
|
||||
|
||||
default Path writeRdpConfigFile(String title, RdpConfig input) throws Exception {
|
||||
var name = OsType.getLocal().makeFileSystemCompatible(title);
|
||||
var file = ShellTemp.getLocalTempDataDirectory("rdp").resolve(name + ".rdp");
|
||||
var string = input.toString();
|
||||
Files.createDirectories(file.getParent());
|
||||
Files.writeString(file, string);
|
||||
return file;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package io.xpipe.app.rdp;
|
||||
|
||||
import io.xpipe.app.prefs.ExternalApplicationType;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
|
||||
public class FreeRdpClient implements ExternalApplicationType.PathApplication, ExternalRdpClient {
|
||||
|
||||
@Override
|
||||
public void launch(RdpLaunchConfig configuration) throws Exception {
|
||||
var file = writeRdpConfigFile(configuration.getTitle(), configuration.getConfig());
|
||||
var b = CommandBuilder.of().addFile(file.toString()).add("/cert-ignore");
|
||||
if (configuration.getPassword() != null) {
|
||||
var escapedPw = configuration.getPassword().getSecretValue().replaceAll("'", "\\\\'");
|
||||
b.add("/p:'" + escapedPw + "'");
|
||||
}
|
||||
launch(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsPasswordPassing() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExecutable() {
|
||||
return "xfreerdp";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExplicitlyAsync() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "app.xfreeRdp";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package io.xpipe.app.rdp;
|
||||
|
||||
import io.xpipe.app.prefs.ExternalApplicationType;
|
||||
import io.xpipe.app.util.LocalShell;
|
||||
import io.xpipe.app.util.RdpConfig;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
import io.xpipe.core.util.SecretValue;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class MstscRdpClient implements ExternalApplicationType.PathApplication, ExternalRdpClient{
|
||||
|
||||
@Override
|
||||
public void launch(RdpLaunchConfig configuration) throws Exception {
|
||||
var adaptedRdpConfig = getAdaptedConfig(configuration);
|
||||
var file = writeRdpConfigFile(configuration.getTitle(), adaptedRdpConfig);
|
||||
LocalShell.getShell().executeSimpleCommand(CommandBuilder.of().add(getExecutable()).addFile(file.toString()));
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
ThreadHelper.sleep(1000);
|
||||
FileUtils.deleteQuietly(file.toFile());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsPasswordPassing() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private RdpConfig getAdaptedConfig(RdpLaunchConfig configuration) throws Exception {
|
||||
var input = configuration.getConfig();
|
||||
if (input.get("password 51").isPresent()) {
|
||||
return input;
|
||||
}
|
||||
|
||||
if (input.get("username").isEmpty()) {
|
||||
// return input;
|
||||
}
|
||||
|
||||
var pass = configuration.getPassword();
|
||||
if (pass == null) {
|
||||
return input;
|
||||
}
|
||||
|
||||
var adapted = input.overlay(
|
||||
Map.of("password 51", new RdpConfig.TypedValue("b", encrypt(pass)), "prompt for credentials", new RdpConfig.TypedValue("i", "0")));
|
||||
return adapted;
|
||||
}
|
||||
|
||||
private String encrypt(SecretValue password) throws Exception {
|
||||
var ps = LocalShell.getLocalPowershell();
|
||||
var cmd = ps.command(CommandBuilder.of()
|
||||
.add(sc -> "(" +
|
||||
sc.getShellDialect().literalArgument(password.getSecretValue()) +
|
||||
" | ConvertTo-SecureString -AsPlainText -Force) | ConvertFrom-SecureString"));
|
||||
cmd.sensitive();
|
||||
return cmd.readStdoutOrThrow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExecutable() {
|
||||
return "mstsc.exe";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExplicitlyAsync() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "app.mstsc";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package io.xpipe.app.rdp;
|
||||
|
||||
import io.xpipe.app.util.RdpConfig;
|
||||
import io.xpipe.core.util.SecretValue;
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Value
|
||||
public class RdpLaunchConfig {
|
||||
String title;
|
||||
RdpConfig config;
|
||||
UUID storeId;
|
||||
SecretValue password;
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package io.xpipe.app.rdp;
|
||||
|
||||
import io.xpipe.app.prefs.ExternalApplicationType;
|
||||
import io.xpipe.app.util.LocalShell;
|
||||
import io.xpipe.app.util.RdpConfig;
|
||||
import io.xpipe.app.util.ShellTemp;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.util.SecretValue;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
public class RemminaRdpClient implements ExternalApplicationType.PathApplication, ExternalRdpClient {
|
||||
|
||||
private List<String> toStrip() {
|
||||
return List.of("auto connect", "password 51", "prompt for credentials", "smart sizing");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launch(RdpLaunchConfig configuration) throws Exception {
|
||||
RdpConfig c = configuration.getConfig();
|
||||
var l = new HashSet<>(c.getContent().keySet());
|
||||
toStrip().forEach(l::remove);
|
||||
if (l.size() == 2 && l.contains("username") && l.contains("full address")) {
|
||||
var encrypted = encryptPassword(configuration.getPassword());
|
||||
if (encrypted.isPresent()) {
|
||||
var file = writeRemminaConfigFile(configuration, encrypted.get());
|
||||
launch(CommandBuilder.of().add("-c").addFile(file.toString()));
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
ThreadHelper.sleep(5000);
|
||||
FileUtils.deleteQuietly(file.toFile());
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var file = writeRdpConfigFile(configuration.getTitle(), c);
|
||||
launch(CommandBuilder.of().add("-c").addFile(file.toString()));
|
||||
}
|
||||
|
||||
private Optional<String> encryptPassword(SecretValue password) throws Exception {
|
||||
if (password == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
try (var sc = LocalShell.getShell().start()) {
|
||||
var prefSecretBase64 = sc.command("sed -n 's/^secret=//p' ~/.config/remmina/remmina.pref").readStdoutIfPossible();
|
||||
if (prefSecretBase64.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
var paddedPassword = password.getSecretValue();
|
||||
paddedPassword = paddedPassword + "\0".repeat(8 - paddedPassword.length() % 8);
|
||||
var prefSecret = Base64.getDecoder().decode(prefSecretBase64.get());
|
||||
var key = Arrays.copyOfRange(prefSecret, 0, 24);
|
||||
var iv = Arrays.copyOfRange(prefSecret, 24, prefSecret.length);
|
||||
|
||||
var cipher = Cipher.getInstance("DESede/CBC/Nopadding");
|
||||
var keySpec = new SecretKeySpec(key, "DESede");
|
||||
var ivspec = new IvParameterSpec(iv);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivspec);
|
||||
byte[] encryptedText = cipher.doFinal(paddedPassword.getBytes(StandardCharsets.UTF_8));
|
||||
var base64Encrypted = Base64.getEncoder().encodeToString(encryptedText);
|
||||
return Optional.ofNullable(base64Encrypted);
|
||||
}
|
||||
}
|
||||
|
||||
private Path writeRemminaConfigFile(RdpLaunchConfig configuration, String password) throws Exception {
|
||||
var name = OsType.getLocal().makeFileSystemCompatible(configuration.getTitle());
|
||||
var file = ShellTemp.getLocalTempDataDirectory("rdp").resolve(name + ".remmina");
|
||||
var string = """
|
||||
[remmina]
|
||||
protocol=RDP
|
||||
name=%s
|
||||
username=%s
|
||||
server=%s
|
||||
password=%s
|
||||
cert_ignore=1
|
||||
""".formatted(configuration.getTitle(), configuration.getConfig().get("username").orElseThrow().getValue(),
|
||||
configuration.getConfig().get("full address").orElseThrow().getValue(), password);
|
||||
Files.createDirectories(file.getParent());
|
||||
Files.writeString(file, string);
|
||||
return file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsPasswordPassing() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExecutable() {
|
||||
return "remmina";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExplicitlyAsync() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "app.remmina";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package io.xpipe.app.rdp;
|
||||
|
||||
import io.xpipe.app.prefs.ExternalApplicationType;
|
||||
import io.xpipe.app.util.LocalShell;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
|
||||
public class RemoteDesktopAppRdpClient implements ExternalApplicationType.MacApplication, ExternalRdpClient {
|
||||
|
||||
@Override
|
||||
public void launch(RdpLaunchConfig configuration) throws Exception {
|
||||
var file = writeRdpConfigFile(configuration.getTitle(), configuration.getConfig());
|
||||
LocalShell.getShell().executeSimpleCommand(CommandBuilder.of()
|
||||
.add("open", "-a")
|
||||
.addQuoted("Microsoft Remote Desktop.app")
|
||||
.addFile(file.toString()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsPasswordPassing() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getApplicationName() {
|
||||
return "Microsoft Remote Desktop";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "app.microsoftRemoteDesktopApp";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package io.xpipe.app.rdp;
|
||||
|
||||
import io.xpipe.app.prefs.ExternalApplicationType;
|
||||
import io.xpipe.app.util.LocalShell;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
|
||||
public class WindowsAppRdpClient implements ExternalApplicationType.MacApplication, ExternalRdpClient {
|
||||
|
||||
@Override
|
||||
public void launch(RdpLaunchConfig configuration) throws Exception {
|
||||
var file = writeRdpConfigFile(configuration.getTitle(), configuration.getConfig());
|
||||
LocalShell.getShell().executeSimpleCommand(CommandBuilder.of().add("open", "-a").addQuoted("Windows App.app").addFile(file.toString()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsPasswordPassing() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getApplicationName() {
|
||||
return "Windows App";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "app.windowsApp";
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.xpipe.app.terminal;
|
||||
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.prefs.ExternalApplicationType;
|
||||
import io.xpipe.app.util.LocalShell;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
|
||||
@@ -30,14 +31,10 @@ public interface AlacrittyTerminalType extends ExternalTerminalType, TrackableTe
|
||||
return false;
|
||||
}
|
||||
|
||||
class Windows extends SimplePathType implements AlacrittyTerminalType {
|
||||
|
||||
public Windows() {
|
||||
super("app.alacritty", "alacritty", true);
|
||||
}
|
||||
class Windows implements ExternalApplicationType.PathApplication, ExternalTerminalType, AlacrittyTerminalType {
|
||||
|
||||
@Override
|
||||
protected CommandBuilder toCommand(TerminalLaunchConfiguration configuration) {
|
||||
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||
var b = CommandBuilder.of();
|
||||
|
||||
// if (configuration.getColor() != null) {
|
||||
@@ -48,33 +45,72 @@ public interface AlacrittyTerminalType extends ExternalTerminalType, TrackableTe
|
||||
|
||||
// Alacritty is bugged and will not accept arguments with spaces even if they are correctly passed/escaped
|
||||
// So this will not work when the script file has spaces
|
||||
return b.add("-t")
|
||||
b.add("-t")
|
||||
.addQuoted(configuration.getCleanTitle())
|
||||
.add("-e")
|
||||
.add(configuration.getDialectLaunchCommand());
|
||||
}
|
||||
}
|
||||
|
||||
class Linux extends SimplePathType implements AlacrittyTerminalType {
|
||||
|
||||
public Linux() {
|
||||
super("app.alacritty", "alacritty", true);
|
||||
launch(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CommandBuilder toCommand(TerminalLaunchConfiguration configuration) {
|
||||
return CommandBuilder.of()
|
||||
.add("-t")
|
||||
.addQuoted(configuration.getCleanTitle())
|
||||
.add("-e")
|
||||
.addFile(configuration.getScriptFile());
|
||||
public String getExecutable() {
|
||||
return "alacritty";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExplicitlyAsync() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "app.alacritty";
|
||||
}
|
||||
}
|
||||
|
||||
class MacOs extends MacOsType implements AlacrittyTerminalType {
|
||||
class Linux implements ExternalApplicationType.PathApplication, AlacrittyTerminalType {
|
||||
|
||||
public MacOs() {
|
||||
super("app.alacritty", "Alacritty");
|
||||
@Override
|
||||
public String getExecutable() {
|
||||
return "alacritty";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExplicitlyAsync() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "app.alacritty";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||
var b = CommandBuilder.of()
|
||||
.add("-t")
|
||||
.addQuoted(configuration.getCleanTitle())
|
||||
.add("-e")
|
||||
.addFile(configuration.getScriptFile());;
|
||||
launch(b);
|
||||
}
|
||||
}
|
||||
|
||||
class MacOs implements ExternalApplicationType.MacApplication, ExternalTerminalType, TrackableTerminalType {
|
||||
|
||||
@Override
|
||||
public TerminalOpenFormat getOpenFormat() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRecommended() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useColoredTitle() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -88,5 +124,15 @@ public interface AlacrittyTerminalType extends ExternalTerminalType, TrackableTe
|
||||
.add("-e")
|
||||
.addFile(configuration.getScriptFile()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "app.alacritty";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getApplicationName() {
|
||||
return "Alacritty";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package io.xpipe.app.terminal;
|
||||
|
||||
import io.xpipe.app.ext.ProcessControlProvider;
|
||||
import io.xpipe.app.prefs.ExternalApplicationType;
|
||||
import io.xpipe.app.prefs.ExternalEditorType;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
import io.xpipe.core.process.ShellDialects;
|
||||
|
||||
public class CmdTerminalType extends ExternalTerminalType.SimplePathType implements TrackableTerminalType {
|
||||
import java.nio.file.Path;
|
||||
|
||||
public CmdTerminalType() {
|
||||
super("app.cmd", "cmd.exe", true);
|
||||
}
|
||||
public class CmdTerminalType implements ExternalApplicationType.PathApplication, ExternalTerminalType, TrackableTerminalType {
|
||||
|
||||
@Override
|
||||
public boolean supportsEscapes() {
|
||||
@@ -36,12 +36,32 @@ public class CmdTerminalType extends ExternalTerminalType.SimplePathType impleme
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CommandBuilder toCommand(TerminalLaunchConfiguration configuration) {
|
||||
private CommandBuilder toCommand(TerminalLaunchConfiguration configuration) {
|
||||
if (configuration.getScriptDialect().equals(ShellDialects.CMD)) {
|
||||
return CommandBuilder.of().add("/c").addFile(configuration.getScriptFile());
|
||||
}
|
||||
|
||||
return CommandBuilder.of().add("/c").add(configuration.getDialectLaunchCommand());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExecutable() {
|
||||
return "cmd.exe";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExplicitlyAsync() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "app.cmd";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||
var args = toCommand(configuration);
|
||||
launch(args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,7 @@ import io.xpipe.core.process.OsType;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class CustomTerminalType extends ExternalApplicationType implements ExternalTerminalType {
|
||||
|
||||
public CustomTerminalType() {
|
||||
super("app.custom");
|
||||
}
|
||||
public class CustomTerminalType implements ExternalApplicationType, ExternalTerminalType {
|
||||
|
||||
@Override
|
||||
public TerminalOpenFormat getOpenFormat() {
|
||||
@@ -55,4 +51,9 @@ public class CustomTerminalType extends ExternalApplicationType implements Exter
|
||||
public boolean isAvailable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "app.custom";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,10 @@ import io.xpipe.app.ext.PrefsChoiceValue;
|
||||
import io.xpipe.app.ext.ProcessControlProvider;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.prefs.ExternalApplicationType;
|
||||
import io.xpipe.app.util.*;
|
||||
import io.xpipe.core.process.*;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||
@@ -19,7 +16,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||
// ExternalTerminalType PUTTY = new WindowsType("app.putty","putty") {
|
||||
//
|
||||
// @Override
|
||||
// protected Optional<Path> determineInstallation() {
|
||||
// public Optional<Path> determineInstallation() {
|
||||
// try {
|
||||
// var r = WindowsRegistry.local().readValue(WindowsRegistry.HKEY_LOCAL_MACHINE,
|
||||
// "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\Xshell.exe");
|
||||
@@ -577,73 +574,8 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||
return CommandBuilder.of().add("-e").add(configuration.getDialectLaunchCommand());
|
||||
}
|
||||
};
|
||||
ExternalTerminalType MACOS_TERMINAL = new MacOsType("app.macosTerminal", "Terminal") {
|
||||
|
||||
@Override
|
||||
public TerminalOpenFormat getOpenFormat() {
|
||||
return TerminalOpenFormat.TABBED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProcessHierarchyOffset() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRecommended() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useColoredTitle() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||
LocalShell.getShell()
|
||||
.executeSimpleCommand(CommandBuilder.of()
|
||||
.add("open", "-a")
|
||||
.addQuoted("Terminal.app")
|
||||
.addFile(configuration.getScriptFile()));
|
||||
}
|
||||
};
|
||||
ExternalTerminalType ITERM2 = new MacOsType("app.iterm2", "iTerm") {
|
||||
|
||||
@Override
|
||||
public TerminalOpenFormat getOpenFormat() {
|
||||
return TerminalOpenFormat.TABBED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProcessHierarchyOffset() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWebsite() {
|
||||
return "https://iterm2.com/";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRecommended() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useColoredTitle() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||
LocalShell.getShell()
|
||||
.executeSimpleCommand(CommandBuilder.of()
|
||||
.add("open", "-a")
|
||||
.addQuoted("iTerm.app")
|
||||
.addFile(configuration.getScriptFile()));
|
||||
}
|
||||
};
|
||||
ExternalTerminalType MACOS_TERMINAL = new MacOsTerminalType();
|
||||
ExternalTerminalType ITERM2 = new ITerm2TerminalType();
|
||||
ExternalTerminalType CUSTOM = new CustomTerminalType();
|
||||
List<ExternalTerminalType> WINDOWS_TERMINALS = List.of(
|
||||
WindowsTerminalType.WINDOWS_TERMINAL_CANARY,
|
||||
@@ -781,60 +713,34 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||
return true;
|
||||
}
|
||||
|
||||
default void launch(TerminalLaunchConfiguration configuration) throws Exception {}
|
||||
void launch(TerminalLaunchConfiguration configuration) throws Exception;
|
||||
|
||||
abstract class WindowsType extends ExternalApplicationType.WindowsType implements ExternalTerminalType {
|
||||
abstract class SimplePathType implements ExternalApplicationType.PathApplication, TrackableTerminalType {
|
||||
|
||||
public WindowsType(String id, String executable) {
|
||||
super(id, executable);
|
||||
@Getter
|
||||
private final String id;
|
||||
@Getter
|
||||
private final String executable;
|
||||
private final boolean async;
|
||||
|
||||
public SimplePathType(String id, String executable, boolean async) {
|
||||
this.id = id;
|
||||
this.executable = executable;
|
||||
this.async = async;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launch(TerminalLaunchConfiguration 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, TerminalLaunchConfiguration configuration) throws Exception;
|
||||
}
|
||||
|
||||
abstract class MacOsType extends ExternalApplicationType.MacApplication
|
||||
implements ExternalTerminalType, TrackableTerminalType {
|
||||
|
||||
public MacOsType(String id, String applicationName) {
|
||||
super(id, applicationName);
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
abstract class PathCheckType extends ExternalApplicationType.PathApplication implements ExternalTerminalType {
|
||||
|
||||
public PathCheckType(String id, String executable, boolean explicitAsync) {
|
||||
super(id, executable, explicitAsync);
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
abstract class SimplePathType extends PathCheckType implements TrackableTerminalType {
|
||||
|
||||
public SimplePathType(String id, String executable, boolean explicitAsync) {
|
||||
super(id, executable, explicitAsync);
|
||||
public boolean isExplicitlyAsync() {
|
||||
return async;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||
var args = toCommand(configuration);
|
||||
launch(configuration.getColoredTitle(), args);
|
||||
launch(args);
|
||||
}
|
||||
|
||||
protected abstract CommandBuilder toCommand(TerminalLaunchConfiguration configuration);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
package io.xpipe.app.terminal;
|
||||
|
||||
import io.xpipe.app.prefs.ExternalApplicationType;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
|
||||
public class GnomeConsoleType extends ExternalTerminalType.SimplePathType implements TrackableTerminalType {
|
||||
|
||||
public GnomeConsoleType() {
|
||||
super("app.gnomeConsole", "kgx", true);
|
||||
}
|
||||
public class GnomeConsoleType implements ExternalApplicationType.PathApplication, TrackableTerminalType {
|
||||
|
||||
@Override
|
||||
public TerminalOpenFormat getOpenFormat() {
|
||||
@@ -29,11 +26,26 @@ public class GnomeConsoleType extends ExternalTerminalType.SimplePathType implem
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CommandBuilder toCommand(TerminalLaunchConfiguration configuration) {
|
||||
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||
var toExecute = CommandBuilder.of()
|
||||
.addIf(configuration.isPreferTabs(), "--tab")
|
||||
.add("--")
|
||||
.add(configuration.getDialectLaunchCommand());
|
||||
return toExecute;
|
||||
launch(toExecute);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExecutable() {
|
||||
return "kgx";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExplicitlyAsync() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "app.gnomeConsole";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
package io.xpipe.app.terminal;
|
||||
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.prefs.ExternalApplicationType;
|
||||
import io.xpipe.app.util.CommandSupport;
|
||||
import io.xpipe.app.util.LocalShell;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
|
||||
public class GnomeTerminalType extends ExternalTerminalType.PathCheckType implements TrackableTerminalType {
|
||||
|
||||
public GnomeTerminalType() {
|
||||
super("app.gnomeTerminal", "gnome-terminal", true);
|
||||
}
|
||||
public class GnomeTerminalType implements ExternalApplicationType.PathApplication, TrackableTerminalType {
|
||||
|
||||
@Override
|
||||
public TerminalOpenFormat getOpenFormat() {
|
||||
@@ -35,10 +32,10 @@ public class GnomeTerminalType extends ExternalTerminalType.PathCheckType implem
|
||||
@Override
|
||||
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||
try (ShellControl pc = LocalShell.getShell()) {
|
||||
CommandSupport.isInPathOrThrow(pc, executable, toTranslatedString().getValue(), null);
|
||||
CommandSupport.isInPathOrThrow(pc, getExecutable(), toTranslatedString().getValue(), null);
|
||||
|
||||
var toExecute = CommandBuilder.of()
|
||||
.add(executable, "-v", "--title")
|
||||
.add(getExecutable(), "-v", "--title")
|
||||
.addQuoted(configuration.getColoredTitle())
|
||||
.add("--")
|
||||
.addFile(configuration.getScriptFile())
|
||||
@@ -48,4 +45,19 @@ public class GnomeTerminalType extends ExternalTerminalType.PathCheckType implem
|
||||
pc.executeSimpleCommand(toExecute);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExecutable() {
|
||||
return "gnome-terminal";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExplicitlyAsync() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "app.gnomeTerminal";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package io.xpipe.app.terminal;
|
||||
|
||||
import io.xpipe.app.prefs.ExternalApplicationType;
|
||||
import io.xpipe.app.util.LocalShell;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
|
||||
public class ITerm2TerminalType implements ExternalApplicationType.MacApplication, TrackableTerminalType {
|
||||
|
||||
@Override
|
||||
public TerminalOpenFormat getOpenFormat() {
|
||||
return TerminalOpenFormat.TABBED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProcessHierarchyOffset() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWebsite() {
|
||||
return "https://iterm2.com/";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRecommended() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useColoredTitle() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||
LocalShell.getShell().executeSimpleCommand(CommandBuilder.of()
|
||||
.add("open", "-a")
|
||||
.addQuoted("iTerm.app")
|
||||
.addFile(configuration.getScriptFile()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getApplicationName() {
|
||||
return "iTerm";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "app.iterm2";
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package io.xpipe.app.terminal;
|
||||
|
||||
import io.xpipe.app.ext.ProcessControlProvider;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.prefs.ExternalApplicationType;
|
||||
import io.xpipe.app.util.CommandSupport;
|
||||
import io.xpipe.app.util.LocalShell;
|
||||
import io.xpipe.app.util.ShellTemp;
|
||||
@@ -154,11 +155,7 @@ public interface KittyTerminalType extends ExternalTerminalType, TrackableTermin
|
||||
}
|
||||
}
|
||||
|
||||
class MacOs extends MacOsType implements KittyTerminalType {
|
||||
|
||||
public MacOs() {
|
||||
super("app.kitty", "kitty");
|
||||
}
|
||||
class MacOs implements ExternalApplicationType.MacApplication, KittyTerminalType {
|
||||
|
||||
@Override
|
||||
public int getProcessHierarchyOffset() {
|
||||
@@ -199,5 +196,15 @@ public interface KittyTerminalType extends ExternalTerminalType, TrackableTermin
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getApplicationName() {
|
||||
return "kitty";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "app.kitty";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package io.xpipe.app.terminal;
|
||||
|
||||
import io.xpipe.app.prefs.ExternalApplicationType;
|
||||
import io.xpipe.app.util.LocalShell;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
|
||||
public class MacOsTerminalType implements ExternalApplicationType.MacApplication, TrackableTerminalType {
|
||||
|
||||
@Override
|
||||
public TerminalOpenFormat getOpenFormat() {
|
||||
return TerminalOpenFormat.TABBED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProcessHierarchyOffset() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRecommended() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useColoredTitle() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||
LocalShell.getShell().executeSimpleCommand(CommandBuilder.of()
|
||||
.add("open", "-a")
|
||||
.addQuoted("Terminal.app")
|
||||
.addFile(configuration.getScriptFile()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getApplicationName() {
|
||||
return "Terminal";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "app.macosTerminal";
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.xpipe.app.terminal;
|
||||
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.prefs.ExternalApplicationType;
|
||||
import io.xpipe.app.util.*;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
|
||||
@@ -8,11 +9,7 @@ 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");
|
||||
}
|
||||
public class MobaXTermTerminalType implements ExternalApplicationType.WindowsType, ExternalTerminalType {
|
||||
|
||||
@Override
|
||||
public TerminalOpenFormat getOpenFormat() {
|
||||
@@ -20,7 +17,12 @@ public class MobaXTermTerminalType extends ExternalTerminalType.WindowsType {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Path> determineInstallation() {
|
||||
public String getExecutable() {
|
||||
return "MobaXterm";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Path> determineInstallation() {
|
||||
try {
|
||||
var r = WindowsRegistry.local()
|
||||
.readStringValueIfPresent(
|
||||
@@ -43,12 +45,7 @@ public class MobaXTermTerminalType extends ExternalTerminalType.WindowsType {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWebsite() {
|
||||
return "https://mobaxterm.mobatek.net/";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(Path file, TerminalLaunchConfiguration configuration) throws Exception {
|
||||
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||
try (var sc = LocalShell.getShell()) {
|
||||
SshLocalBridge.init();
|
||||
var b = SshLocalBridge.get();
|
||||
@@ -70,10 +67,20 @@ public class MobaXTermTerminalType extends ExternalTerminalType.WindowsType {
|
||||
Files.writeString(Path.of(script.toString()), "#!/usr/bin/env bash\n" + rawCommand);
|
||||
var fixedFile = script.toString().replaceAll("\\\\", "/").replaceAll("\\s", "\\$0");
|
||||
sc.command(CommandBuilder.of()
|
||||
.addFile(file.toString())
|
||||
.addFile(findExecutable())
|
||||
.add("-newtab")
|
||||
.add(fixedFile))
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWebsite() {
|
||||
return "https://mobaxterm.mobatek.net/";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "app.mobaXterm";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
package io.xpipe.app.terminal;
|
||||
|
||||
import io.xpipe.app.ext.ProcessControlProvider;
|
||||
import io.xpipe.app.prefs.ExternalApplicationType;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
import io.xpipe.core.process.ShellDialects;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
|
||||
public class PowerShellTerminalType extends ExternalTerminalType.SimplePathType implements TrackableTerminalType {
|
||||
public class PowerShellTerminalType implements ExternalApplicationType.PathApplication, TrackableTerminalType {
|
||||
|
||||
@Override
|
||||
public boolean supportsEscapes() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public PowerShellTerminalType() {
|
||||
super("app.powershell", "powershell", true);
|
||||
@Override
|
||||
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||
launch(toCommand(configuration));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -39,7 +41,6 @@ public class PowerShellTerminalType extends ExternalTerminalType.SimplePathType
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CommandBuilder toCommand(TerminalLaunchConfiguration configuration) {
|
||||
if (configuration.getScriptDialect().equals(ShellDialects.POWERSHELL)) {
|
||||
return CommandBuilder.of()
|
||||
@@ -60,4 +61,19 @@ public class PowerShellTerminalType extends ExternalTerminalType.SimplePathType
|
||||
return "\"" + base64 + "\"";
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExecutable() {
|
||||
return "powershell.exe";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExplicitlyAsync() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "app.powershell";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
package io.xpipe.app.terminal;
|
||||
|
||||
import io.xpipe.app.prefs.ExternalApplicationType;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
|
||||
public class PtyxisTerminalType extends ExternalTerminalType.SimplePathType implements TrackableTerminalType {
|
||||
|
||||
public PtyxisTerminalType() {
|
||||
super("app.ptyxis", "ptyxis", true);
|
||||
}
|
||||
public class PtyxisTerminalType implements ExternalApplicationType.PathApplication, TrackableTerminalType {
|
||||
|
||||
@Override
|
||||
public TerminalOpenFormat getOpenFormat() {
|
||||
@@ -29,12 +26,26 @@ public class PtyxisTerminalType extends ExternalTerminalType.SimplePathType impl
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CommandBuilder toCommand(TerminalLaunchConfiguration configuration) {
|
||||
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||
var toExecute = CommandBuilder.of()
|
||||
.addIf(configuration.isPreferTabs(), "--tab")
|
||||
.addIf(!configuration.isPreferTabs(), "--new-window")
|
||||
.add("--")
|
||||
.add(configuration.getDialectLaunchCommand());
|
||||
return toExecute;
|
||||
launch(toExecute);
|
||||
}
|
||||
@Override
|
||||
public String getExecutable() {
|
||||
return "ptyxis";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExplicitlyAsync() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "app.ptyxis";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,33 @@
|
||||
package io.xpipe.app.terminal;
|
||||
|
||||
import io.xpipe.app.prefs.ExternalApplicationType;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
|
||||
public class PwshTerminalType extends ExternalTerminalType.SimplePathType implements TrackableTerminalType {
|
||||
|
||||
public PwshTerminalType() {
|
||||
super("app.pwsh", "pwsh", true);
|
||||
}
|
||||
public class PwshTerminalType implements ExternalApplicationType.PathApplication, TrackableTerminalType {
|
||||
|
||||
@Override
|
||||
public boolean supportsEscapes() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||
var b = CommandBuilder.of()
|
||||
.add("-ExecutionPolicy", "Bypass")
|
||||
.add("-EncodedCommand")
|
||||
.add(sc -> {
|
||||
// Fix for https://github.com/PowerShell/PowerShell/issues/18530#issuecomment-1325691850
|
||||
var c = "$env:PSModulePath=\"\";"
|
||||
+ configuration.getDialectLaunchCommand().buildBase(sc);
|
||||
var base64 = Base64.getEncoder().encodeToString(c.getBytes(StandardCharsets.UTF_16LE));
|
||||
return "\"" + base64 + "\"";
|
||||
});
|
||||
launch(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminalOpenFormat getOpenFormat() {
|
||||
return TerminalOpenFormat.NEW_WINDOW;
|
||||
@@ -37,16 +49,17 @@ public class PwshTerminalType extends ExternalTerminalType.SimplePathType implem
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CommandBuilder toCommand(TerminalLaunchConfiguration configuration) {
|
||||
return CommandBuilder.of()
|
||||
.add("-ExecutionPolicy", "Bypass")
|
||||
.add("-EncodedCommand")
|
||||
.add(sc -> {
|
||||
// Fix for https://github.com/PowerShell/PowerShell/issues/18530#issuecomment-1325691850
|
||||
var c = "$env:PSModulePath=\"\";"
|
||||
+ configuration.getDialectLaunchCommand().buildBase(sc);
|
||||
var base64 = Base64.getEncoder().encodeToString(c.getBytes(StandardCharsets.UTF_16LE));
|
||||
return "\"" + base64 + "\"";
|
||||
});
|
||||
public String getExecutable() {
|
||||
return "pwsh.exe";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExplicitlyAsync() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "app.pwsh";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.xpipe.app.terminal;
|
||||
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.prefs.ExternalApplicationType;
|
||||
import io.xpipe.app.util.LocalShell;
|
||||
import io.xpipe.app.util.SshLocalBridge;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
@@ -9,11 +10,7 @@ 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");
|
||||
}
|
||||
public class SecureCrtTerminalType implements ExternalApplicationType.WindowsType, ExternalTerminalType {
|
||||
|
||||
@Override
|
||||
public TerminalOpenFormat getOpenFormat() {
|
||||
@@ -21,7 +18,12 @@ public class SecureCrtTerminalType extends ExternalTerminalType.WindowsType {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Path> determineInstallation() {
|
||||
public String getExecutable() {
|
||||
return "SecureCRT";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Path> determineInstallation() {
|
||||
try (var sc = LocalShell.getShell().start()) {
|
||||
var env = sc.executeSimpleStringCommand(
|
||||
sc.getShellDialect().getPrintEnvironmentVariableCommand("ProgramFiles"));
|
||||
@@ -48,17 +50,12 @@ public class SecureCrtTerminalType extends ExternalTerminalType.WindowsType {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWebsite() {
|
||||
return "https://www.vandyke.com/products/securecrt/";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(Path file, TerminalLaunchConfiguration configuration) throws Exception {
|
||||
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||
try (var sc = LocalShell.getShell()) {
|
||||
SshLocalBridge.init();
|
||||
var b = SshLocalBridge.get();
|
||||
var command = CommandBuilder.of()
|
||||
.addFile(file.toString())
|
||||
.addFile(findExecutable())
|
||||
.add("/T")
|
||||
.add("/SSH2", "/ACCEPTHOSTKEYS", "/I")
|
||||
.addFile(b.getIdentityKey().toString())
|
||||
@@ -69,4 +66,14 @@ public class SecureCrtTerminalType extends ExternalTerminalType.WindowsType {
|
||||
sc.executeSimpleCommand(command);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWebsite() {
|
||||
return "https://www.vandyke.com/products/securecrt/";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "app.secureCrt";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.xpipe.app.terminal;
|
||||
|
||||
import io.xpipe.app.prefs.ExternalApplicationType;
|
||||
import io.xpipe.app.util.LocalShell;
|
||||
import io.xpipe.app.util.WindowsRegistry;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
@@ -50,11 +51,7 @@ public interface TabbyTerminalType extends ExternalTerminalType, TrackableTermin
|
||||
return TerminalInitFunction.none();
|
||||
}
|
||||
|
||||
class Windows extends ExternalTerminalType.WindowsType implements TabbyTerminalType {
|
||||
|
||||
public Windows() {
|
||||
super("app.tabby", "Tabby.exe");
|
||||
}
|
||||
class Windows implements ExternalApplicationType.WindowsType, TabbyTerminalType {
|
||||
|
||||
@Override
|
||||
public int getProcessHierarchyOffset() {
|
||||
@@ -67,13 +64,13 @@ public interface TabbyTerminalType extends ExternalTerminalType, TrackableTermin
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(Path file, TerminalLaunchConfiguration configuration) throws Exception {
|
||||
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||
// Tabby has a very weird handling of output, even detaching with start does not prevent it from printing
|
||||
if (configuration.getScriptDialect().equals(ShellDialects.CMD)) {
|
||||
// It also freezes with any other input than .bat files, why?
|
||||
LocalShell.getShell()
|
||||
.executeSimpleCommand(CommandBuilder.of()
|
||||
.addFile(file.toString())
|
||||
.addFile(findExecutable())
|
||||
.add("run")
|
||||
.addFile(configuration.getScriptFile())
|
||||
.discardAllOutput());
|
||||
@@ -81,7 +78,7 @@ public interface TabbyTerminalType extends ExternalTerminalType, TrackableTermin
|
||||
// This is probably not going to work as it does not launch a bat file
|
||||
LocalShell.getShell()
|
||||
.executeSimpleCommand(CommandBuilder.of()
|
||||
.addFile(file.toString())
|
||||
.addFile(findExecutable())
|
||||
.add("run")
|
||||
.add(sc -> configuration
|
||||
.getDialectLaunchCommand()
|
||||
@@ -92,7 +89,12 @@ public interface TabbyTerminalType extends ExternalTerminalType, TrackableTermin
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Path> determineInstallation() {
|
||||
public String getExecutable() {
|
||||
return "Tabby.exe";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Path> determineInstallation() {
|
||||
var perUser = WindowsRegistry.local()
|
||||
.readStringValueIfPresent(
|
||||
WindowsRegistry.HKEY_CURRENT_USER,
|
||||
@@ -113,19 +115,20 @@ public interface TabbyTerminalType extends ExternalTerminalType, TrackableTermin
|
||||
.map(Path::of);
|
||||
return systemWide;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "app.tabby";
|
||||
}
|
||||
}
|
||||
|
||||
class MacOs extends MacOsType implements TabbyTerminalType {
|
||||
class MacOs implements ExternalApplicationType.MacApplication, TabbyTerminalType {
|
||||
|
||||
@Override
|
||||
public boolean isRecommended() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public MacOs() {
|
||||
super("app.tabby", "Tabby");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||
LocalShell.getShell()
|
||||
@@ -135,5 +138,15 @@ public interface TabbyTerminalType extends ExternalTerminalType, TrackableTermin
|
||||
.add("-n", "--args", "run")
|
||||
.addFile(configuration.getScriptFile()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getApplicationName() {
|
||||
return "Tabby";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "app.tabby";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package io.xpipe.app.terminal;
|
||||
|
||||
public interface TrackableTerminalType {
|
||||
public interface TrackableTerminalType extends ExternalTerminalType {
|
||||
|
||||
default int getProcessHierarchyOffset() {
|
||||
return 0;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.xpipe.app.terminal;
|
||||
|
||||
import io.xpipe.app.prefs.ExternalApplicationType;
|
||||
import io.xpipe.app.util.*;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
import io.xpipe.core.process.ShellDialects;
|
||||
@@ -91,11 +92,7 @@ public interface WarpTerminalType extends ExternalTerminalType, TrackableTermina
|
||||
}
|
||||
}
|
||||
|
||||
class MacOs extends MacOsType implements WarpTerminalType {
|
||||
|
||||
public MacOs() {
|
||||
super("app.warp", "Warp");
|
||||
}
|
||||
class MacOs implements ExternalApplicationType.MacApplication, WarpTerminalType {
|
||||
|
||||
@Override
|
||||
public int getProcessHierarchyOffset() {
|
||||
@@ -115,6 +112,16 @@ public interface WarpTerminalType extends ExternalTerminalType, TrackableTermina
|
||||
public TerminalOpenFormat getOpenFormat() {
|
||||
return TerminalOpenFormat.TABBED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getApplicationName() {
|
||||
return "Warp";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "app.warp";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -33,11 +33,7 @@ public interface WezTerminalType extends ExternalTerminalType, TrackableTerminal
|
||||
return true;
|
||||
}
|
||||
|
||||
class Windows extends WindowsType implements WezTerminalType {
|
||||
|
||||
public Windows() {
|
||||
super("app.wezterm", "wezterm-gui");
|
||||
}
|
||||
class Windows implements ExternalApplicationType.WindowsType, ExternalTerminalType, WezTerminalType {
|
||||
|
||||
@Override
|
||||
public TerminalOpenFormat getOpenFormat() {
|
||||
@@ -45,16 +41,21 @@ public interface WezTerminalType extends ExternalTerminalType, TrackableTerminal
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(Path file, TerminalLaunchConfiguration configuration) throws Exception {
|
||||
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||
LocalShell.getShell()
|
||||
.executeSimpleCommand(CommandBuilder.of()
|
||||
.addFile(file.toString())
|
||||
.addFile(findExecutable())
|
||||
.add("start")
|
||||
.add(configuration.getDialectLaunchCommand()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Path> determineInstallation() {
|
||||
public String getExecutable() {
|
||||
return "wezterm-gui";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Path> determineInstallation() {
|
||||
try {
|
||||
var foundKey = WindowsRegistry.local()
|
||||
.findKeyForEqualValueMatchRecursive(
|
||||
@@ -83,13 +84,14 @@ public interface WezTerminalType extends ExternalTerminalType, TrackableTerminal
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "app.wezterm";
|
||||
}
|
||||
}
|
||||
|
||||
class Linux extends ExternalApplicationType implements WezTerminalType {
|
||||
|
||||
public Linux() {
|
||||
super("app.wezterm");
|
||||
}
|
||||
class Linux implements ExternalApplicationType, WezTerminalType {
|
||||
|
||||
@Override
|
||||
public TerminalOpenFormat getOpenFormat() {
|
||||
@@ -106,6 +108,11 @@ public interface WezTerminalType extends ExternalTerminalType, TrackableTerminal
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "app.wezterm";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||
var spawn = LocalShell.getShell()
|
||||
@@ -121,11 +128,7 @@ public interface WezTerminalType extends ExternalTerminalType, TrackableTerminal
|
||||
}
|
||||
}
|
||||
|
||||
class MacOs extends MacOsType implements WezTerminalType {
|
||||
|
||||
public MacOs() {
|
||||
super("app.wezterm", "WezTerm");
|
||||
}
|
||||
class MacOs implements ExternalApplicationType.MacApplication, WezTerminalType {
|
||||
|
||||
@Override
|
||||
public TerminalOpenFormat getOpenFormat() {
|
||||
@@ -137,7 +140,7 @@ public interface WezTerminalType extends ExternalTerminalType, TrackableTerminal
|
||||
try (var sc = LocalShell.getShell()) {
|
||||
var pathOut = sc.command(String.format(
|
||||
"mdfind -name '%s' -onlyin /Applications -onlyin ~/Applications -onlyin /System/Applications 2>/dev/null",
|
||||
applicationName))
|
||||
getApplicationName()))
|
||||
.readStdoutOrThrow();
|
||||
var path = Path.of(pathOut);
|
||||
var spawn = sc.command(CommandBuilder.of()
|
||||
@@ -159,5 +162,15 @@ public interface WezTerminalType extends ExternalTerminalType, TrackableTerminal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getApplicationName() {
|
||||
return "WezTerm";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "app.wezterm";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import io.xpipe.app.comp.base.ModalOverlay;
|
||||
import io.xpipe.app.core.AppCache;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.prefs.ExternalApplicationType;
|
||||
import io.xpipe.app.util.LocalShell;
|
||||
import io.xpipe.app.util.SshLocalBridge;
|
||||
import io.xpipe.app.util.WindowsRegistry;
|
||||
@@ -14,11 +15,7 @@ import io.xpipe.core.process.CommandBuilder;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
public class XShellTerminalType extends ExternalTerminalType.WindowsType {
|
||||
|
||||
public XShellTerminalType() {
|
||||
super("app.xShell", "Xshell");
|
||||
}
|
||||
public class XShellTerminalType implements ExternalApplicationType.WindowsType, ExternalTerminalType {
|
||||
|
||||
@Override
|
||||
public TerminalOpenFormat getOpenFormat() {
|
||||
@@ -26,7 +23,12 @@ public class XShellTerminalType extends ExternalTerminalType.WindowsType {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Path> determineInstallation() {
|
||||
public String getExecutable() {
|
||||
return "Xshell";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Path> determineInstallation() {
|
||||
try {
|
||||
var r = WindowsRegistry.local()
|
||||
.readStringValueIfPresent(
|
||||
@@ -55,7 +57,7 @@ public class XShellTerminalType extends ExternalTerminalType.WindowsType {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(Path file, TerminalLaunchConfiguration configuration) throws Exception {
|
||||
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||
SshLocalBridge.init();
|
||||
if (!showInfo()) {
|
||||
return;
|
||||
@@ -65,7 +67,7 @@ public class XShellTerminalType extends ExternalTerminalType.WindowsType {
|
||||
var b = SshLocalBridge.get();
|
||||
var keyName = b.getIdentityKey().getFileName().toString();
|
||||
var command = CommandBuilder.of()
|
||||
.addFile(file.toString())
|
||||
.addFile(findExecutable())
|
||||
.add("-url")
|
||||
.addQuoted("ssh://" + b.getUser() + "@localhost:" + b.getPort())
|
||||
.add("-i", keyName);
|
||||
@@ -90,4 +92,9 @@ public class XShellTerminalType extends ExternalTerminalType.WindowsType {
|
||||
modal.showAndWait();
|
||||
return AppCache.getBoolean("xshellSetup", false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "app.xShell";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.ext.LocalStore;
|
||||
import io.xpipe.app.password.PasswordManager;
|
||||
import io.xpipe.app.pwman.PasswordManager;
|
||||
import io.xpipe.app.storage.*;
|
||||
import io.xpipe.app.terminal.ExternalTerminalType;
|
||||
import io.xpipe.app.terminal.TerminalMultiplexer;
|
||||
|
||||
@@ -28,7 +28,7 @@ public class LocalShellCache extends ShellControlCache {
|
||||
.map(s -> s.asLocalPath());
|
||||
}
|
||||
case OsType.Windows windows -> {
|
||||
yield ExternalEditorType.VSCODE_WINDOWS.findExecutable();
|
||||
yield ExternalEditorType.VSCODE_WINDOWS.determineFromPath().or(() -> ExternalEditorType.VSCODE_WINDOWS.determineInstallation());
|
||||
}
|
||||
};
|
||||
set("codePath", app.orElse(null));
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package io.xpipe.app.vnc;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import io.xpipe.app.pwman.*;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.util.SecretValue;
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
|
||||
public interface ExternalVncClient {
|
||||
|
||||
static List<Class<?>> getClasses() {
|
||||
var l = new ArrayList<Class<?>>();
|
||||
l.add(OnePasswordManager.class);
|
||||
l.add(KeePassXcPasswordManager.class);
|
||||
l.add(BitwardenPasswordManager.class);
|
||||
l.add(DashlanePasswordManager.class);
|
||||
if (OsType.getLocal() != OsType.WINDOWS) {
|
||||
l.add(LastpassPasswordManager.class);
|
||||
l.add(EnpassPasswordManager.class);
|
||||
}
|
||||
l.add(KeeperPasswordManager.class);
|
||||
l.add(PsonoPasswordManager.class);
|
||||
if (OsType.getLocal() == OsType.WINDOWS) {
|
||||
l.add(WindowsCredentialManager.class);
|
||||
}
|
||||
l.add(PasswordManagerCommand.class);
|
||||
return l;
|
||||
}
|
||||
|
||||
@Value
|
||||
class LaunchConfiguration {
|
||||
String title;
|
||||
String host;
|
||||
int port;
|
||||
SecretValue password;
|
||||
}
|
||||
|
||||
boolean isAvailable();
|
||||
|
||||
void launch(LaunchConfiguration configuration) throws Exception;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package io.xpipe.app.vnc;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import io.xpipe.app.prefs.ExternalApplicationType;
|
||||
import io.xpipe.app.util.LocalShell;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
import lombok.Builder;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
@Builder
|
||||
@Jacksonized
|
||||
@JsonTypeName("tightVnc")
|
||||
public class TightVncClient implements ExternalApplicationType.WindowsType, ExternalVncClient {
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExecutable() {
|
||||
return "tvnviewer.exe";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Path> determineInstallation() {
|
||||
return Optional.of(Path.of(System.getenv("PROGRAMFILES"))
|
||||
.resolve("TightVNC")
|
||||
.resolve("tvnviewer.exe"))
|
||||
.filter(path -> Files.exists(path));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launch(LaunchConfiguration configuration) throws Exception {
|
||||
var command = CommandBuilder.of().addFile(findExecutable()).add("-host").addLiteral(configuration.getHost()).add("-port").add("" + configuration.getPort());
|
||||
LocalShell.getShell().command(command).execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,8 @@ open module io.xpipe.app {
|
||||
exports io.xpipe.app.resources;
|
||||
exports io.xpipe.app.comp;
|
||||
exports io.xpipe.app.icon;
|
||||
exports io.xpipe.app.password;
|
||||
exports io.xpipe.app.pwman;
|
||||
exports io.xpipe.app.rdp;
|
||||
|
||||
requires com.sun.jna;
|
||||
requires com.sun.jna.platform;
|
||||
|
||||
Reference in New Issue
Block a user