mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-25 09:00:26 +00:00
Various fixes
This commit is contained in:
parent
ba83a9c23a
commit
059b12b654
12 changed files with 162 additions and 76 deletions
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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())) {
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
32
lang/proc/texts/rdpFileAllowList_en.md
Normal file
32
lang/proc/texts/rdpFileAllowList_en.md
Normal 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.
|
Loading…
Reference in a new issue