From 9fcfb10a08b699693eb6a8c4a256e15683c70d80 Mon Sep 17 00:00:00 2001 From: crschnick Date: Fri, 26 Jun 2026 08:38:22 +0000 Subject: [PATCH] RDP rework --- .../io/xpipe/app/rdp/CustomRdpClient.java | 2 +- .../xpipe/app/rdp/DevolutionsRdpClient.java | 2 +- .../io/xpipe/app/rdp/ExternalRdpClient.java | 2 +- .../java/io/xpipe/app/rdp/FreeRdpClient.java | 47 +++++++++++++------ .../java/io/xpipe/app/rdp/KrdcRdpClient.java | 2 +- .../java/io/xpipe/app/rdp/MstscRdpClient.java | 11 +---- .../xpipe/app/rdp/RdpLaunchConfigGateway.java | 15 ++++++ .../io/xpipe/app/rdp/RemminaRdpClient.java | 4 +- .../app/rdp/RemoteDesktopAppRdpClient.java | 2 +- .../io/xpipe/app/rdp/WindowsAppRdpClient.java | 2 +- .../java/io/xpipe/app/util/RemminaHelper.java | 21 ++++----- 11 files changed, 64 insertions(+), 46 deletions(-) diff --git a/app/src/main/java/io/xpipe/app/rdp/CustomRdpClient.java b/app/src/main/java/io/xpipe/app/rdp/CustomRdpClient.java index 1cc62e609..b00bb140a 100644 --- a/app/src/main/java/io/xpipe/app/rdp/CustomRdpClient.java +++ b/app/src/main/java/io/xpipe/app/rdp/CustomRdpClient.java @@ -54,7 +54,7 @@ public class CustomRdpClient implements ExternalApplicationType, ExternalRdpClie } @Override - public boolean supportsPasswordPassing(RdpLaunchConfig config) { + public boolean supportsPasswordPassing() { return false; } diff --git a/app/src/main/java/io/xpipe/app/rdp/DevolutionsRdpClient.java b/app/src/main/java/io/xpipe/app/rdp/DevolutionsRdpClient.java index f002319a2..5435f2b60 100644 --- a/app/src/main/java/io/xpipe/app/rdp/DevolutionsRdpClient.java +++ b/app/src/main/java/io/xpipe/app/rdp/DevolutionsRdpClient.java @@ -56,7 +56,7 @@ public class DevolutionsRdpClient implements ExternalApplicationType.WindowsType } @Override - public boolean supportsPasswordPassing(RdpLaunchConfig config) { + public boolean supportsPasswordPassing() { return false; } diff --git a/app/src/main/java/io/xpipe/app/rdp/ExternalRdpClient.java b/app/src/main/java/io/xpipe/app/rdp/ExternalRdpClient.java index 40b257eb2..8a61b7070 100644 --- a/app/src/main/java/io/xpipe/app/rdp/ExternalRdpClient.java +++ b/app/src/main/java/io/xpipe/app/rdp/ExternalRdpClient.java @@ -87,7 +87,7 @@ public interface ExternalRdpClient extends PrefsValue { void launch(RdpLaunchConfig configuration) throws Exception; - boolean supportsPasswordPassing(RdpLaunchConfig config); + boolean supportsPasswordPassing(); String getWebsite(); diff --git a/app/src/main/java/io/xpipe/app/rdp/FreeRdpClient.java b/app/src/main/java/io/xpipe/app/rdp/FreeRdpClient.java index 3dff6925c..b0241e144 100644 --- a/app/src/main/java/io/xpipe/app/rdp/FreeRdpClient.java +++ b/app/src/main/java/io/xpipe/app/rdp/FreeRdpClient.java @@ -13,8 +13,6 @@ import lombok.Builder; import lombok.Value; import lombok.extern.jackson.Jacksonized; -import java.util.Map; - @JsonTypeName("freeRdp") @Value @Jacksonized @@ -33,7 +31,15 @@ public class FreeRdpClient implements ExternalRdpClient { var v3 = CommandSupport.isInLocalPath("xfreerdp3"); if (!v3) { var v2 = CommandSupport.isInLocalPath("xfreerdp"); - if (!v2 && OsType.ofLocal() == OsType.LINUX) { + + // macOS uses xfreerdp3 by default + if (OsType.ofLocal() == OsType.MACOS && v2) { + exec = CommandBuilder.of().add("xfreerdp"); + v3 = true; + } else if (v2) { + exec = CommandBuilder.of().add("xfreerdp"); + v3 = false; + } else if (OsType.ofLocal() == OsType.LINUX) { var flatpak = FlatpakCache.getApp("com.freerdp.FreeRDP"); if (flatpak.isPresent()) { exec = FlatpakCache.getRunCommand("com.freerdp.FreeRDP"); @@ -42,10 +48,7 @@ public class FreeRdpClient implements ExternalRdpClient { return null; } } else { - CommandSupport.isInPathOrThrow(LocalShell.getShell(), "xfreerdp"); - exec = CommandBuilder.of().add("xfreerdp"); - // macOS uses xfreerdp3 by default - v3 = true; + return null; } } else { exec = CommandBuilder.of().add("xfreerdp3"); @@ -55,13 +58,16 @@ public class FreeRdpClient implements ExternalRdpClient { private Executable getWaylandCommandBase() throws Exception { CommandBuilder exec; - var v3 = CommandSupport.isInLocalPath("wlfreerdp3"); + var v3 = CommandSupport.isInLocalPath("sdl-freerdp3") || CommandSupport.isInLocalPath("wlfreerdp3"); if (!v3) { var v2 = CommandSupport.isInLocalPath("wlfreerdp"); - if (!v2 && OsType.ofLocal() == OsType.LINUX) { + if (v2) { + exec = CommandBuilder.of().add("wlfreerdp"); + v3 = false; + } else if (OsType.ofLocal() == OsType.LINUX) { var flatpak = FlatpakCache.getApp("com.freerdp.FreeRDP"); if (flatpak.isPresent()) { - exec = FlatpakCache.getRunCommand("com.freerdp.FreeRDP", "sdl-freerdp"); + exec = FlatpakCache.getRunCommand("com.freerdp.FreeRDP", "sdl-freerdp3"); v3 = true; } else { return null; @@ -71,7 +77,7 @@ public class FreeRdpClient implements ExternalRdpClient { return null; } } else { - exec = CommandBuilder.of().add("wlfreerdp3"); + exec = CommandSupport.isInLocalPath("sdl-freerdp3") ? CommandBuilder.of().add("sdl-freerdp3") : CommandBuilder.of().add("wlfreerdp3"); } return new Executable(exec, v3); } @@ -85,7 +91,7 @@ public class FreeRdpClient implements ExternalRdpClient { } if (exec == null) { - throw ErrorEventFactory.expected(new IllegalStateException("Unable to find a FreeRDP v2 or v3 installation")); + throw ErrorEventFactory.expected(new IllegalStateException("Unable to find a FreeRDP executable for v2 or v3 in the PATH or as a flatpak")); } var file = writeRdpConfigFile(configuration.getTitle(), configuration.getConfig()); @@ -100,6 +106,16 @@ public class FreeRdpClient implements ExternalRdpClient { .add("-themes") .add("/size:100%"); + b.add(argument("/v", configuration.getHost())); + + if (configuration.getUsername() != null) { + b.add(argument("/u", configuration.getUsernameWithoutDomain())); + } + + if (configuration.getDomain().isPresent()) { + b.add(argument("/d", configuration.getDomain().get())); + } + if (configuration.getPassword() != null) { b.add(argument("/p", configuration.getPassword().getSecretValue())); } @@ -110,7 +126,10 @@ public class FreeRdpClient implements ExternalRdpClient { // Horrible quoting rules: https://github.com/FreeRDP/FreeRDP/issues/11396 String s = "g:" + gateway.getHost(); if (gateway.getUsername() != null) { - s += "," + gatewayArgument("u", gateway.getUsername()); + s += "," + gatewayArgument("u", gateway.getUsernameWithoutDomain()); + } + if (gateway.getDomain().isPresent()) { + s += "," + gatewayArgument("d", gateway.getDomain().get()); } if (gateway.getPassword() != null) { s += "," + gatewayArgument("p", gateway.getPassword().getSecretValue()); @@ -146,7 +165,7 @@ public class FreeRdpClient implements ExternalRdpClient { } @Override - public boolean supportsPasswordPassing(RdpLaunchConfig config) { + public boolean supportsPasswordPassing() { return true; } diff --git a/app/src/main/java/io/xpipe/app/rdp/KrdcRdpClient.java b/app/src/main/java/io/xpipe/app/rdp/KrdcRdpClient.java index df33ec41e..0302430f4 100644 --- a/app/src/main/java/io/xpipe/app/rdp/KrdcRdpClient.java +++ b/app/src/main/java/io/xpipe/app/rdp/KrdcRdpClient.java @@ -34,7 +34,7 @@ public class KrdcRdpClient implements ExternalApplicationType.LinuxApplication, } @Override - public boolean supportsPasswordPassing(RdpLaunchConfig config) { + public boolean supportsPasswordPassing() { return false; } diff --git a/app/src/main/java/io/xpipe/app/rdp/MstscRdpClient.java b/app/src/main/java/io/xpipe/app/rdp/MstscRdpClient.java index 2aebd589d..1577f8c64 100644 --- a/app/src/main/java/io/xpipe/app/rdp/MstscRdpClient.java +++ b/app/src/main/java/io/xpipe/app/rdp/MstscRdpClient.java @@ -1,19 +1,13 @@ package io.xpipe.app.rdp; -import com.sun.jna.LastErrorException; -import com.sun.jna.Memory; -import com.sun.jna.Native; -import com.sun.jna.Pointer; import io.xpipe.app.comp.base.ModalButton; import io.xpipe.app.comp.base.ModalOverlay; import io.xpipe.app.core.AppCache; import io.xpipe.app.core.AppDisplayScale; import io.xpipe.app.core.window.AppDialog; -import io.xpipe.app.platform.ClipboardHelper; import io.xpipe.app.platform.OptionsBuilder; import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.prefs.ExternalApplicationType; -import io.xpipe.app.process.CommandBuilder; import io.xpipe.app.process.LocalShell; import io.xpipe.app.storage.DataStorage; import io.xpipe.app.util.*; @@ -27,10 +21,7 @@ import com.fasterxml.jackson.annotation.JsonTypeName; import lombok.Builder; import lombok.Value; import lombok.extern.jackson.Jacksonized; -import org.apache.commons.io.FileUtils; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.Map; import java.util.Optional; @@ -231,7 +222,7 @@ public class MstscRdpClient implements ExternalApplicationType.PathApplication, } @Override - public boolean supportsPasswordPassing(RdpLaunchConfig config) { + public boolean supportsPasswordPassing() { return true; } diff --git a/app/src/main/java/io/xpipe/app/rdp/RdpLaunchConfigGateway.java b/app/src/main/java/io/xpipe/app/rdp/RdpLaunchConfigGateway.java index f3b95fec3..6d093d34d 100644 --- a/app/src/main/java/io/xpipe/app/rdp/RdpLaunchConfigGateway.java +++ b/app/src/main/java/io/xpipe/app/rdp/RdpLaunchConfigGateway.java @@ -6,6 +6,7 @@ import io.xpipe.app.util.SecretValue; import lombok.Value; +import java.util.Optional; import java.util.UUID; @Value @@ -15,4 +16,18 @@ public class RdpLaunchConfigGateway { String username; SecretValue password; boolean reused; + + public Optional getDomain() { + var domain = username.contains("\\") ? username.split("\\\\")[0] : null; + return Optional.ofNullable(domain); + } + + public String getUsernameWithoutDomain() { + if (username.contains("\\")) { + return username.split("\\\\")[1]; + } else { + return username; + } + } + } diff --git a/app/src/main/java/io/xpipe/app/rdp/RemminaRdpClient.java b/app/src/main/java/io/xpipe/app/rdp/RemminaRdpClient.java index f815817e3..ca4f0459f 100644 --- a/app/src/main/java/io/xpipe/app/rdp/RemminaRdpClient.java +++ b/app/src/main/java/io/xpipe/app/rdp/RemminaRdpClient.java @@ -9,8 +9,6 @@ import lombok.Builder; import lombok.Value; import lombok.extern.jackson.Jacksonized; -import java.util.*; - @JsonTypeName("remmina") @Value @Jacksonized @@ -34,7 +32,7 @@ public class RemminaRdpClient implements ExternalApplicationType.LinuxApplicatio } @Override - public boolean supportsPasswordPassing(RdpLaunchConfig config) { + public boolean supportsPasswordPassing() { return true; } diff --git a/app/src/main/java/io/xpipe/app/rdp/RemoteDesktopAppRdpClient.java b/app/src/main/java/io/xpipe/app/rdp/RemoteDesktopAppRdpClient.java index dde4e9496..add8370d6 100644 --- a/app/src/main/java/io/xpipe/app/rdp/RemoteDesktopAppRdpClient.java +++ b/app/src/main/java/io/xpipe/app/rdp/RemoteDesktopAppRdpClient.java @@ -36,7 +36,7 @@ public class RemoteDesktopAppRdpClient implements ExternalApplicationType.MacApp } @Override - public boolean supportsPasswordPassing(RdpLaunchConfig config) { + public boolean supportsPasswordPassing() { return false; } diff --git a/app/src/main/java/io/xpipe/app/rdp/WindowsAppRdpClient.java b/app/src/main/java/io/xpipe/app/rdp/WindowsAppRdpClient.java index 664c9ef05..d965492a0 100644 --- a/app/src/main/java/io/xpipe/app/rdp/WindowsAppRdpClient.java +++ b/app/src/main/java/io/xpipe/app/rdp/WindowsAppRdpClient.java @@ -35,7 +35,7 @@ public class WindowsAppRdpClient implements ExternalApplicationType.MacApplicati } @Override - public boolean supportsPasswordPassing(RdpLaunchConfig config) { + public boolean supportsPasswordPassing() { return false; } diff --git a/app/src/main/java/io/xpipe/app/util/RemminaHelper.java b/app/src/main/java/io/xpipe/app/util/RemminaHelper.java index 00abd9bd9..4a031fa6c 100644 --- a/app/src/main/java/io/xpipe/app/util/RemminaHelper.java +++ b/app/src/main/java/io/xpipe/app/util/RemminaHelper.java @@ -9,6 +9,7 @@ import io.xpipe.app.vnc.VncLaunchConfig; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; import java.util.Optional; @@ -60,23 +61,17 @@ public class RemminaHelper { var name = OsFileSystem.ofLocal().makeFileSystemCompatible(configuration.getTitle()); var file = AppLocalTemp.getLocalTempDataDirectory("remmina").resolve("xpipe-" + name + ".remmina"); - var gateway = ""; + var gateway = new ArrayList(); if (configuration.getGateway() != null) { - gateway += "gateway_server=" + configuration.getGateway().getHost(); + gateway.add("gateway_server=" + configuration.getGateway().getHost()); if (configuration.getGateway().getUsername() != null) { - var gatewayUser = configuration.getGateway().getUsername(); - var gatewayDomain = gatewayUser.contains("\\") ? gatewayUser.split("\\\\")[0] : null; - if (gatewayDomain != null) { - gatewayUser = gatewayUser.split("\\\\")[1]; - } - - gateway += "gateway_username=" + gatewayUser; - if (gatewayDomain != null) { - gateway += "gateway_domain=" + gatewayDomain; + gateway.add("gateway_username=" + configuration.getGateway().getUsernameWithoutDomain()); + if (configuration.getGateway().getDomain().isPresent()) { + gateway.add("gateway_domain=" + configuration.getGateway().getDomain().get()); } } if (configuration.getGateway().getPassword() != null) { - gateway += "gateway_password=" + RemminaHelper.encryptPassword(configuration.getGateway().getPassword()); + gateway.add("gateway_password=" + RemminaHelper.encryptPassword(configuration.getGateway().getPassword()).orElse("")); } } @@ -102,7 +97,7 @@ public class RemminaHelper { w, h, maximize, - !gateway.isEmpty() ? "\n" + gateway : ""); + !gateway.isEmpty() ? "\n" + String.join("\n", gateway) : ""); Files.createDirectories(file.getParent()); Files.writeString(file, string); return file;