Various fixes

This commit is contained in:
crschnick 2024-04-18 04:38:56 +00:00
parent ba83a9c23a
commit 059b12b654
12 changed files with 162 additions and 76 deletions

View file

@ -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

View file

@ -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<Comp<?>>();
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(

View file

@ -64,7 +64,7 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
@Override
public Comp<?> comp() {
return new OpenFileSystemComp(this);
return new OpenFileSystemComp(this, true);
}
@Override

View file

@ -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<DataStoreEntryRef<? extends FileSystemStore>> store, Consumer<FileReference> 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<? super BrowserEntry>) 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<? super BrowserEntry>) 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;
}
}

View file

@ -22,7 +22,7 @@ import java.util.List;
import java.util.function.Consumer;
@Getter
public class BrowserChooserModel extends BrowserAbstractSessionModel<OpenFileSystemModel> {
public class BrowserFileChooserModel extends BrowserAbstractSessionModel<OpenFileSystemModel> {
private final OpenFileSystemModel.SelectionMode selectionMode;
private final ObservableList<BrowserEntry> fileSelection = FXCollections.observableArrayList();
@ -30,7 +30,7 @@ public class BrowserChooserModel extends BrowserAbstractSessionModel<OpenFileSys
@Setter
private Consumer<List<FileReference>> 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<OpenFileSys
public void finishChooser() {
var chosen = new ArrayList<>(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<OpenFileSys
}
}
if (chosen.size() == 0) {
return;
}
var stores = chosen.stream()
.map(entry -> new FileReference(
selectedEntry.getValue().getEntry(),
@ -81,7 +77,7 @@ public class BrowserChooserModel extends BrowserAbstractSessionModel<OpenFileSys
model = new OpenFileSystemModel(this, store, selectionMode);
model.init();
// Prevent multiple calls from interfering with each other
synchronized (BrowserChooserModel.this) {
synchronized (BrowserFileChooserModel.this) {
selectedEntry.setValue(model);
sessionEntries.add(model);
}

View file

@ -1,6 +1,5 @@
package io.xpipe.app.comp.base;
import atlantafx.base.controls.Spacer;
import atlantafx.base.theme.Styles;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppWindowHelper;
@ -39,14 +38,18 @@ public abstract class DialogComp extends Comp<CompStructure<Region>> {
});
}
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<CompStructure<Region>> {
@Override
public CompStructure<Region> 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<CompStructure<Region>> {
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();

View file

@ -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);
}

View file

@ -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 {

View file

@ -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<String, TypedValue> 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) {

View file

@ -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())) {

View file

@ -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

View file

@ -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\<MyApplication>' -Force
Set-ItemProperty -Path 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Terminal Server\TSAppAllowList' -Name "Name" -Value "<MyApplication>"
Set-ItemProperty -Path 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Terminal Server\TSAppAllowList' -Name "Path" -Value "<absolute path of executable>"
```
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.