From 059b12b654bc380465e6e334be70d1c8223c68d7 Mon Sep 17 00:00:00 2001 From: crschnick Date: Thu, 18 Apr 2024 04:38:56 +0000 Subject: [PATCH] Various fixes --- README.md | 6 + .../app/browser/fs/OpenFileSystemComp.java | 14 ++- .../app/browser/fs/OpenFileSystemModel.java | 2 +- .../browser/session/BrowserChooserComp.java | 105 ++++++++++-------- ...odel.java => BrowserFileChooserModel.java} | 12 +- .../io/xpipe/app/comp/base/DialogComp.java | 17 +-- .../app/comp/store/StoreCreationComp.java | 4 +- .../app/prefs/ExternalRdpClientType.java | 4 + .../java/io/xpipe/app/util/RdpConfig.java | 12 +- .../io/xpipe/core/util/StreamCharset.java | 24 +++- lang/proc/strings/translations_en.properties | 6 +- lang/proc/texts/rdpFileAllowList_en.md | 32 ++++++ 12 files changed, 162 insertions(+), 76 deletions(-) rename app/src/main/java/io/xpipe/app/browser/session/{BrowserChooserModel.java => BrowserFileChooserModel.java} (89%) create mode 100644 lang/proc/texts/rdpFileAllowList_en.md diff --git a/README.md b/README.md index fe564815b..55ae55d81 100644 --- a/README.md +++ b/README.md @@ -167,6 +167,12 @@ If you don't like installers, you can also use a portable version that is packag Alternatively, you can also use [Homebrew](https://github.com/xpipe-io/homebrew-tap) to install XPipe with `brew install --cask xpipe-io/tap/xpipe`. +## Early access releases + +Prior to full releases, there will be several Public Test Build (PTB) releases published at https://github.com/xpipe-io/xpipe-ptb to see whether everything is production ready and contain the latest new features. + +In case you're interested in trying out the PTB versions, you can easily do so without any limitations. The regular releases and PTB releases are designed to not interfere with each other and can therefore be installed and used side by side. + # Further information ## Open source model diff --git a/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemComp.java b/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemComp.java index 70990781f..44759d78f 100644 --- a/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemComp.java +++ b/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemComp.java @@ -30,15 +30,18 @@ import javafx.scene.layout.Region; import javafx.scene.layout.VBox; import org.kordamp.ikonli.javafx.FontIcon; +import java.util.ArrayList; import java.util.List; import java.util.Map; public class OpenFileSystemComp extends SimpleComp { private final OpenFileSystemModel model; + private final boolean showStatusBar; - public OpenFileSystemComp(OpenFileSystemModel model) { + public OpenFileSystemComp(OpenFileSystemModel model, boolean showStatusBar) { this.model = model; + this.showStatusBar = showStatusBar; } @Override @@ -96,8 +99,13 @@ public class OpenFileSystemComp extends SimpleComp { private Region createFileListContent() { var directoryView = new BrowserFileListComp(model.getFileList()) .apply(struc -> VBox.setVgrow(struc.get(), Priority.ALWAYS)); - var statusBar = new BrowserStatusBarComp(model); - var fileList = new VerticalComp(List.of(directoryView, statusBar)); + var fileListElements = new ArrayList>(); + fileListElements.add(directoryView); + if (showStatusBar) { + var statusBar = new BrowserStatusBarComp(model); + fileListElements.add(statusBar); + } + var fileList = new VerticalComp(fileListElements); var home = new BrowserOverviewComp(model); var stack = new MultiContentComp(Map.of( diff --git a/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemModel.java b/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemModel.java index 094e84b01..6dba18830 100644 --- a/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemModel.java +++ b/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemModel.java @@ -64,7 +64,7 @@ public final class OpenFileSystemModel extends BrowserSessionTab comp() { - return new OpenFileSystemComp(this); + return new OpenFileSystemComp(this, true); } @Override diff --git a/app/src/main/java/io/xpipe/app/browser/session/BrowserChooserComp.java b/app/src/main/java/io/xpipe/app/browser/session/BrowserChooserComp.java index 4654cd89a..8a3d03464 100644 --- a/app/src/main/java/io/xpipe/app/browser/session/BrowserChooserComp.java +++ b/app/src/main/java/io/xpipe/app/browser/session/BrowserChooserComp.java @@ -1,10 +1,10 @@ package io.xpipe.app.browser.session; -import atlantafx.base.controls.Spacer; import io.xpipe.app.browser.BrowserBookmarkComp; import io.xpipe.app.browser.file.BrowserEntry; import io.xpipe.app.browser.fs.OpenFileSystemComp; import io.xpipe.app.browser.fs.OpenFileSystemModel; +import io.xpipe.app.comp.base.DialogComp; import io.xpipe.app.comp.base.SideSplitPaneComp; import io.xpipe.app.comp.store.StoreEntryWrapper; import io.xpipe.app.core.AppFont; @@ -22,13 +22,12 @@ import io.xpipe.core.store.FileSystemStore; import io.xpipe.core.store.ShellStore; import javafx.beans.property.BooleanProperty; import javafx.collections.ListChangeListener; -import javafx.geometry.Insets; -import javafx.geometry.Orientation; import javafx.geometry.Pos; -import javafx.scene.control.Button; -import javafx.scene.control.Label; import javafx.scene.control.TextField; -import javafx.scene.layout.*; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -37,21 +36,23 @@ import java.util.function.Supplier; public class BrowserChooserComp extends SimpleComp { - private final BrowserChooserModel model; + private final BrowserFileChooserModel model; - public BrowserChooserComp(BrowserChooserModel model) { + public BrowserChooserComp(BrowserFileChooserModel model) { this.model = model; } public static void openSingleFile( Supplier> store, Consumer file, boolean save) { PlatformThread.runLaterIfNeeded(() -> { - var model = new BrowserChooserModel(OpenFileSystemModel.SelectionMode.SINGLE_FILE); + var model = new BrowserFileChooserModel(OpenFileSystemModel.SelectionMode.SINGLE_FILE); var comp = new BrowserChooserComp(model) .apply(struc -> struc.get().setPrefSize(1200, 700)) .apply(struc -> AppFont.normal(struc.get())); var window = AppWindowHelper.sideWindow( - AppI18n.get(save ? "saveFileTitle" : "openFileTitle"), stage -> comp, false, null); + AppI18n.get(save ? "saveFileTitle" : "openFileTitle"), stage -> { + return comp; + }, false, null); model.setOnFinish(fileStores -> { file.accept(fileStores.size() > 0 ? fileStores.getFirst() : null); window.close(); @@ -93,7 +94,7 @@ public class BrowserChooserComp extends SimpleComp { model.getSelectedEntry().subscribe(selected -> { PlatformThread.runLaterIfNeeded(() -> { if (selected != null) { - s.getChildren().setAll(new OpenFileSystemComp(selected).createRegion()); + s.getChildren().setAll(new OpenFileSystemComp(selected, false).createRegion()); } else { s.getChildren().clear(); } @@ -108,43 +109,53 @@ public class BrowserChooserComp extends SimpleComp { struc.getLeft().setMinWidth(200); struc.getLeft().setMaxWidth(500); }); - var r = addBottomBar(splitPane.createRegion()); + + var dialogPane = new DialogComp() { + + @Override + protected Comp pane(Comp content) { + return content; + } + + @Override + protected void finish() { + model.finishChooser(); + } + + @Override + public Comp content() { + return splitPane; + } + + @Override + public Comp bottom() { + return Comp.of(() -> { + var selected = new HBox(); + selected.setAlignment(Pos.CENTER_LEFT); + model.getFileSelection().addListener((ListChangeListener) c -> { + PlatformThread.runLaterIfNeeded(() -> { + selected.getChildren() + .setAll(c.getList().stream() + .map(s -> { + var field = + new TextField(s.getRawFileEntry().getPath()); + field.setEditable(false); + HBox.setHgrow(field, Priority.ALWAYS); + return field; + }) + .toList()); + }); + }); + var bottomBar = new HBox(selected); + HBox.setHgrow(selected, Priority.ALWAYS); + bottomBar.setAlignment(Pos.CENTER); + return bottomBar; + }); + } + }; + + var r = dialogPane.createRegion(); r.getStyleClass().add("browser"); return r; } - - private Region addBottomBar(Region r) { - var selectedLabel = new Label("Selected: "); - selectedLabel.setAlignment(Pos.CENTER); - var selected = new HBox(); - selected.setAlignment(Pos.CENTER_LEFT); - selected.setSpacing(10); - model.getFileSelection().addListener((ListChangeListener) c -> { - PlatformThread.runLaterIfNeeded(() -> { - selected.getChildren() - .setAll(c.getList().stream() - .map(s -> { - var field = - new TextField(s.getRawFileEntry().getPath()); - field.setEditable(false); - field.setPrefWidth(500); - return field; - }) - .toList()); - }); - }); - var spacer = new Spacer(Orientation.HORIZONTAL); - var button = new Button("Select"); - button.setPadding(new Insets(5, 10, 5, 10)); - button.setOnAction(event -> model.finishChooser()); - button.setDefaultButton(true); - var bottomBar = new HBox(selectedLabel, selected, spacer, button); - HBox.setHgrow(selected, Priority.ALWAYS); - bottomBar.setAlignment(Pos.CENTER); - bottomBar.getStyleClass().add("chooser-bar"); - - var layout = new VBox(r, bottomBar); - VBox.setVgrow(r, Priority.ALWAYS); - return layout; - } } diff --git a/app/src/main/java/io/xpipe/app/browser/session/BrowserChooserModel.java b/app/src/main/java/io/xpipe/app/browser/session/BrowserFileChooserModel.java similarity index 89% rename from app/src/main/java/io/xpipe/app/browser/session/BrowserChooserModel.java rename to app/src/main/java/io/xpipe/app/browser/session/BrowserFileChooserModel.java index c402d8068..291002b56 100644 --- a/app/src/main/java/io/xpipe/app/browser/session/BrowserChooserModel.java +++ b/app/src/main/java/io/xpipe/app/browser/session/BrowserFileChooserModel.java @@ -22,7 +22,7 @@ import java.util.List; import java.util.function.Consumer; @Getter -public class BrowserChooserModel extends BrowserAbstractSessionModel { +public class BrowserFileChooserModel extends BrowserAbstractSessionModel { private final OpenFileSystemModel.SelectionMode selectionMode; private final ObservableList fileSelection = FXCollections.observableArrayList(); @@ -30,7 +30,7 @@ public class BrowserChooserModel extends BrowserAbstractSessionModel> onFinish; - public BrowserChooserModel(OpenFileSystemModel.SelectionMode selectionMode) { + public BrowserFileChooserModel(OpenFileSystemModel.SelectionMode selectionMode) { this.selectionMode = selectionMode; selectedEntry.addListener((observable, oldValue, newValue) -> { if (newValue == null) { @@ -45,7 +45,7 @@ public class BrowserChooserModel extends BrowserAbstractSessionModel(fileSelection); - synchronized (BrowserChooserModel.this) { + synchronized (BrowserFileChooserModel.this) { var open = selectedEntry.getValue(); if (open != null) { ThreadHelper.runAsync(() -> { @@ -54,10 +54,6 @@ public class BrowserChooserModel extends BrowserAbstractSessionModel new FileReference( selectedEntry.getValue().getEntry(), @@ -81,7 +77,7 @@ public class BrowserChooserModel extends BrowserAbstractSessionModel> { }); } - protected Region createStepNavigation() { + protected Region createNavigation() { HBox buttons = new HBox(); buttons.setFillHeight(true); var customButton = bottom(); if (customButton != null) { - buttons.getChildren().add(customButton.createRegion()); + var c = customButton.createRegion(); + buttons.getChildren().add(c); + HBox.setHgrow(c, Priority.ALWAYS); } - buttons.getChildren().add(new Spacer()); + var spacer = new Region(); + HBox.setHgrow(spacer, Priority.SOMETIMES); + buttons.getChildren().add(spacer); buttons.getStyleClass().add("buttons"); buttons.setSpacing(5); buttons.setAlignment(Pos.CENTER_RIGHT); @@ -69,9 +72,9 @@ public abstract class DialogComp extends Comp> { @Override public CompStructure createBase() { - var sp = scrollPane(content()).createRegion(); + var sp = pane(content()).createRegion(); VBox vbox = new VBox(); - vbox.getChildren().addAll(sp, createStepNavigation()); + vbox.getChildren().addAll(sp, createNavigation()); vbox.getStyleClass().add("dialog-comp"); vbox.setFillWidth(true); VBox.setVgrow(sp, Priority.ALWAYS); @@ -86,7 +89,7 @@ public abstract class DialogComp extends Comp> { public abstract Comp content(); - protected Comp scrollPane(Comp content) { + protected Comp pane(Comp content) { var entry = content.styleClass("dialog-content"); return Comp.of(() -> { var entryR = entry.createRegion(); diff --git a/app/src/main/java/io/xpipe/app/comp/store/StoreCreationComp.java b/app/src/main/java/io/xpipe/app/comp/store/StoreCreationComp.java index 19004d7a0..d92ff13a7 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/StoreCreationComp.java +++ b/app/src/main/java/io/xpipe/app/comp/store/StoreCreationComp.java @@ -309,8 +309,8 @@ public class StoreCreationComp extends DialogComp { } @Override - protected Comp scrollPane(Comp content) { - var back = super.scrollPane(content); + protected Comp pane(Comp content) { + var back = super.pane(content); return new ErrorOverlayComp(back, messageProp); } diff --git a/app/src/main/java/io/xpipe/app/prefs/ExternalRdpClientType.java b/app/src/main/java/io/xpipe/app/prefs/ExternalRdpClientType.java index bfc8f0479..05fd12888 100644 --- a/app/src/main/java/io/xpipe/app/prefs/ExternalRdpClientType.java +++ b/app/src/main/java/io/xpipe/app/prefs/ExternalRdpClientType.java @@ -22,6 +22,10 @@ public interface ExternalRdpClientType extends PrefsChoiceValue { var file = writeConfig(adaptedRdpConfig); LocalShell.getShell() .executeSimpleCommand(CommandBuilder.of().add(executable).addFile(file.toString())); + ThreadHelper.runFailableAsync(() -> { + ThreadHelper.sleep(1000); + Files.delete(file); + }); } private RdpConfig getAdaptedConfig(LaunchConfiguration configuration) throws Exception { diff --git a/app/src/main/java/io/xpipe/app/util/RdpConfig.java b/app/src/main/java/io/xpipe/app/util/RdpConfig.java index a6e1cc656..8165af56a 100644 --- a/app/src/main/java/io/xpipe/app/util/RdpConfig.java +++ b/app/src/main/java/io/xpipe/app/util/RdpConfig.java @@ -1,8 +1,10 @@ package io.xpipe.app.util; +import io.xpipe.core.util.StreamCharset; import lombok.Value; -import java.io.IOException; +import java.io.BufferedInputStream; +import java.io.BufferedReader; import java.nio.file.Files; import java.nio.file.Path; import java.util.LinkedHashMap; @@ -15,9 +17,11 @@ public class RdpConfig { Map content; - public static RdpConfig parseFile(String file) throws IOException { - var content = Files.readString(Path.of(file)); - return parseContent(content); + public static RdpConfig parseFile(String file) throws Exception { + try (var in = new BufferedReader(StreamCharset.detectedReader(new BufferedInputStream(Files.newInputStream(Path.of(file)))))) { + var content = in.lines().collect(Collectors.joining("\n")); + return parseContent(content); + } } public static RdpConfig parseContent(String content) { diff --git a/core/src/main/java/io/xpipe/core/util/StreamCharset.java b/core/src/main/java/io/xpipe/core/util/StreamCharset.java index 7015bb236..2a4b32b28 100644 --- a/core/src/main/java/io/xpipe/core/util/StreamCharset.java +++ b/core/src/main/java/io/xpipe/core/util/StreamCharset.java @@ -4,7 +4,6 @@ import lombok.Value; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.Reader; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Arrays; @@ -138,7 +137,28 @@ public class StreamCharset { return found.get(); } - public Reader reader(InputStream stream) throws Exception { + public static InputStreamReader detectedReader(InputStream inputStream) throws Exception { + StreamCharset detected = null; + for (var charset : StreamCharset.COMMON) { + if (charset.hasByteOrderMark()) { + inputStream.mark(charset.getByteOrderMark().length); + var bom = inputStream.readNBytes(charset.getByteOrderMark().length); + inputStream.reset(); + if (Arrays.equals(bom, charset.getByteOrderMark())) { + detected = charset; + break; + } + } + } + + if (detected == null) { + detected = StreamCharset.UTF8; + } + + return detected.reader(inputStream); + } + + public InputStreamReader reader(InputStream stream) throws Exception { if (hasByteOrderMark()) { var bom = stream.readNBytes(getByteOrderMark().length); if (bom.length != 0 && !Arrays.equals(bom, getByteOrderMark())) { diff --git a/lang/proc/strings/translations_en.properties b/lang/proc/strings/translations_en.properties index 0980cd8e2..23b34381d 100644 --- a/lang/proc/strings/translations_en.properties +++ b/lang/proc/strings/translations_en.properties @@ -282,7 +282,7 @@ sshConfigString.displayName=Customized SSH Connection sshConfigString.displayDescription=Create a fully customized SSH connection sshConfigStringContent=Configuration sshConfigStringContentDescription=SSH options for the connection in OpenSSH config format -vnc.displayName=VNC connection +vnc.displayName=VNC connection over SSH vnc.displayDescription=Open a VNC session via an SSH tunnel binding=Binding vncPortDescription=The port the VNC server is listening on @@ -299,6 +299,8 @@ launch=Launch sshTrustKeyHeader=The host key is not known, and you have enabled manual host key verification. sshTrustKeyTitle=Unknown host key vnc=VNC connections -rdpTunnel.displayName=RDP over SSH +rdpTunnel.displayName=RDP connection over SSH rdpTunnel.displayDescription=Connect via RDP over a tunneled SSH connection +rdpEnableDesktopIntegration=Enable desktop integration +rdpEnableDesktopIntegrationDescription=Run remote applications assuming that the allow list permits that diff --git a/lang/proc/texts/rdpFileAllowList_en.md b/lang/proc/texts/rdpFileAllowList_en.md new file mode 100644 index 000000000..be0d26a5a --- /dev/null +++ b/lang/proc/texts/rdpFileAllowList_en.md @@ -0,0 +1,32 @@ +## RDP desktop integration + +You can use this RDP connection in XPipe to quickly launch applications and scripts. However, due to the nature of RDP, you would have to edit the remote application allow list on your server for this to work. You can also choose not to do this and just use XPipe to launch your RDP client without using any advanced desktop integration features. + +### RDP allow lists + +An RDP server uses the concept of allow lists to handle application launches. This essentially means that unless the allow list is disabled or specific applications have been explicitly added the allow list, launching any remote applications directly will fail. + +You can find the allow list settings in the registry of your server at `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Terminal Server\TSAppAllowList`. + +#### Disabling the allow list + +You can disable the allow list concept to allow all remote applications to be started directly from XPipe. For this, you can run the following command on your server: `Set-ItemProperty -Path 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Terminal Server\TSAppAllowList' -Name "fDisabledAllowList" -Value 1`. + +#### Adding allowed applications + +Alternatively, you can also add individual remote applications to the list. This will then allow you to launch the listed applications directly from XPipe. + +Under the `Applications` key of `TSAppAllowList`, create a new key with some arbitrary name. The only requirement for the name is that it is unique within the children of the “Applications” key. This new key, must have two string values in it: `Name` and `Path`. `Name` is the name by which we will refer to the application later when configuring the client, and `Path` is the path to the application on the server: + +``` +New-Item -Path 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Terminal Server\TSAppAllowList\Applications' -Force +New-Item -Path 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Terminal Server\TSAppAllowList\Applications\' -Force +Set-ItemProperty -Path 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Terminal Server\TSAppAllowList' -Name "Name" -Value "" +Set-ItemProperty -Path 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Terminal Server\TSAppAllowList' -Name "Path" -Value "" +``` + +If you want to allow XPipe to also run scripts and open terminal sessions, you have to add `cmd.exe` to the allow list as well. + +### Security considerations + +This does not make your server insecure in any way, as you can always run the same applications manually when launching an RDP connection. Allow lists are more intended to prevent clients from instantly running any application without user input. At the end of the day, it is up to you whether you trust XPipe to do this. You can launch this connection just fine out of the box, this is only useful if you want to use any of the advanced desktop integration features in XPipe.