mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-22 07:30:24 +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`.
|
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
|
# Further information
|
||||||
|
|
||||||
## Open source model
|
## Open source model
|
||||||
|
|
|
@ -30,15 +30,18 @@ import javafx.scene.layout.Region;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import org.kordamp.ikonli.javafx.FontIcon;
|
import org.kordamp.ikonli.javafx.FontIcon;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class OpenFileSystemComp extends SimpleComp {
|
public class OpenFileSystemComp extends SimpleComp {
|
||||||
|
|
||||||
private final OpenFileSystemModel model;
|
private final OpenFileSystemModel model;
|
||||||
|
private final boolean showStatusBar;
|
||||||
|
|
||||||
public OpenFileSystemComp(OpenFileSystemModel model) {
|
public OpenFileSystemComp(OpenFileSystemModel model, boolean showStatusBar) {
|
||||||
this.model = model;
|
this.model = model;
|
||||||
|
this.showStatusBar = showStatusBar;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -96,8 +99,13 @@ public class OpenFileSystemComp extends SimpleComp {
|
||||||
private Region createFileListContent() {
|
private Region createFileListContent() {
|
||||||
var directoryView = new BrowserFileListComp(model.getFileList())
|
var directoryView = new BrowserFileListComp(model.getFileList())
|
||||||
.apply(struc -> VBox.setVgrow(struc.get(), Priority.ALWAYS));
|
.apply(struc -> VBox.setVgrow(struc.get(), Priority.ALWAYS));
|
||||||
|
var fileListElements = new ArrayList<Comp<?>>();
|
||||||
|
fileListElements.add(directoryView);
|
||||||
|
if (showStatusBar) {
|
||||||
var statusBar = new BrowserStatusBarComp(model);
|
var statusBar = new BrowserStatusBarComp(model);
|
||||||
var fileList = new VerticalComp(List.of(directoryView, statusBar));
|
fileListElements.add(statusBar);
|
||||||
|
}
|
||||||
|
var fileList = new VerticalComp(fileListElements);
|
||||||
|
|
||||||
var home = new BrowserOverviewComp(model);
|
var home = new BrowserOverviewComp(model);
|
||||||
var stack = new MultiContentComp(Map.of(
|
var stack = new MultiContentComp(Map.of(
|
||||||
|
|
|
@ -64,7 +64,7 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Comp<?> comp() {
|
public Comp<?> comp() {
|
||||||
return new OpenFileSystemComp(this);
|
return new OpenFileSystemComp(this, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package io.xpipe.app.browser.session;
|
package io.xpipe.app.browser.session;
|
||||||
|
|
||||||
import atlantafx.base.controls.Spacer;
|
|
||||||
import io.xpipe.app.browser.BrowserBookmarkComp;
|
import io.xpipe.app.browser.BrowserBookmarkComp;
|
||||||
import io.xpipe.app.browser.file.BrowserEntry;
|
import io.xpipe.app.browser.file.BrowserEntry;
|
||||||
import io.xpipe.app.browser.fs.OpenFileSystemComp;
|
import io.xpipe.app.browser.fs.OpenFileSystemComp;
|
||||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
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.base.SideSplitPaneComp;
|
||||||
import io.xpipe.app.comp.store.StoreEntryWrapper;
|
import io.xpipe.app.comp.store.StoreEntryWrapper;
|
||||||
import io.xpipe.app.core.AppFont;
|
import io.xpipe.app.core.AppFont;
|
||||||
|
@ -22,13 +22,12 @@ import io.xpipe.core.store.FileSystemStore;
|
||||||
import io.xpipe.core.store.ShellStore;
|
import io.xpipe.core.store.ShellStore;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.collections.ListChangeListener;
|
import javafx.collections.ListChangeListener;
|
||||||
import javafx.geometry.Insets;
|
|
||||||
import javafx.geometry.Orientation;
|
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.control.Button;
|
|
||||||
import javafx.scene.control.Label;
|
|
||||||
import javafx.scene.control.TextField;
|
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.BiConsumer;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
@ -37,21 +36,23 @@ import java.util.function.Supplier;
|
||||||
|
|
||||||
public class BrowserChooserComp extends SimpleComp {
|
public class BrowserChooserComp extends SimpleComp {
|
||||||
|
|
||||||
private final BrowserChooserModel model;
|
private final BrowserFileChooserModel model;
|
||||||
|
|
||||||
public BrowserChooserComp(BrowserChooserModel model) {
|
public BrowserChooserComp(BrowserFileChooserModel model) {
|
||||||
this.model = model;
|
this.model = model;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void openSingleFile(
|
public static void openSingleFile(
|
||||||
Supplier<DataStoreEntryRef<? extends FileSystemStore>> store, Consumer<FileReference> file, boolean save) {
|
Supplier<DataStoreEntryRef<? extends FileSystemStore>> store, Consumer<FileReference> file, boolean save) {
|
||||||
PlatformThread.runLaterIfNeeded(() -> {
|
PlatformThread.runLaterIfNeeded(() -> {
|
||||||
var model = new BrowserChooserModel(OpenFileSystemModel.SelectionMode.SINGLE_FILE);
|
var model = new BrowserFileChooserModel(OpenFileSystemModel.SelectionMode.SINGLE_FILE);
|
||||||
var comp = new BrowserChooserComp(model)
|
var comp = new BrowserChooserComp(model)
|
||||||
.apply(struc -> struc.get().setPrefSize(1200, 700))
|
.apply(struc -> struc.get().setPrefSize(1200, 700))
|
||||||
.apply(struc -> AppFont.normal(struc.get()));
|
.apply(struc -> AppFont.normal(struc.get()));
|
||||||
var window = AppWindowHelper.sideWindow(
|
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 -> {
|
model.setOnFinish(fileStores -> {
|
||||||
file.accept(fileStores.size() > 0 ? fileStores.getFirst() : null);
|
file.accept(fileStores.size() > 0 ? fileStores.getFirst() : null);
|
||||||
window.close();
|
window.close();
|
||||||
|
@ -93,7 +94,7 @@ public class BrowserChooserComp extends SimpleComp {
|
||||||
model.getSelectedEntry().subscribe(selected -> {
|
model.getSelectedEntry().subscribe(selected -> {
|
||||||
PlatformThread.runLaterIfNeeded(() -> {
|
PlatformThread.runLaterIfNeeded(() -> {
|
||||||
if (selected != null) {
|
if (selected != null) {
|
||||||
s.getChildren().setAll(new OpenFileSystemComp(selected).createRegion());
|
s.getChildren().setAll(new OpenFileSystemComp(selected, false).createRegion());
|
||||||
} else {
|
} else {
|
||||||
s.getChildren().clear();
|
s.getChildren().clear();
|
||||||
}
|
}
|
||||||
|
@ -108,17 +109,29 @@ public class BrowserChooserComp extends SimpleComp {
|
||||||
struc.getLeft().setMinWidth(200);
|
struc.getLeft().setMinWidth(200);
|
||||||
struc.getLeft().setMaxWidth(500);
|
struc.getLeft().setMaxWidth(500);
|
||||||
});
|
});
|
||||||
var r = addBottomBar(splitPane.createRegion());
|
|
||||||
r.getStyleClass().add("browser");
|
var dialogPane = new DialogComp() {
|
||||||
return r;
|
|
||||||
|
@Override
|
||||||
|
protected Comp<?> pane(Comp<?> content) {
|
||||||
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Region addBottomBar(Region r) {
|
@Override
|
||||||
var selectedLabel = new Label("Selected: ");
|
protected void finish() {
|
||||||
selectedLabel.setAlignment(Pos.CENTER);
|
model.finishChooser();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Comp<?> content() {
|
||||||
|
return splitPane;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Comp<?> bottom() {
|
||||||
|
return Comp.of(() -> {
|
||||||
var selected = new HBox();
|
var selected = new HBox();
|
||||||
selected.setAlignment(Pos.CENTER_LEFT);
|
selected.setAlignment(Pos.CENTER_LEFT);
|
||||||
selected.setSpacing(10);
|
|
||||||
model.getFileSelection().addListener((ListChangeListener<? super BrowserEntry>) c -> {
|
model.getFileSelection().addListener((ListChangeListener<? super BrowserEntry>) c -> {
|
||||||
PlatformThread.runLaterIfNeeded(() -> {
|
PlatformThread.runLaterIfNeeded(() -> {
|
||||||
selected.getChildren()
|
selected.getChildren()
|
||||||
|
@ -127,24 +140,22 @@ public class BrowserChooserComp extends SimpleComp {
|
||||||
var field =
|
var field =
|
||||||
new TextField(s.getRawFileEntry().getPath());
|
new TextField(s.getRawFileEntry().getPath());
|
||||||
field.setEditable(false);
|
field.setEditable(false);
|
||||||
field.setPrefWidth(500);
|
HBox.setHgrow(field, Priority.ALWAYS);
|
||||||
return field;
|
return field;
|
||||||
})
|
})
|
||||||
.toList());
|
.toList());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
var spacer = new Spacer(Orientation.HORIZONTAL);
|
var bottomBar = new HBox(selected);
|
||||||
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);
|
HBox.setHgrow(selected, Priority.ALWAYS);
|
||||||
bottomBar.setAlignment(Pos.CENTER);
|
bottomBar.setAlignment(Pos.CENTER);
|
||||||
bottomBar.getStyleClass().add("chooser-bar");
|
return bottomBar;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
var layout = new VBox(r, bottomBar);
|
var r = dialogPane.createRegion();
|
||||||
VBox.setVgrow(r, Priority.ALWAYS);
|
r.getStyleClass().add("browser");
|
||||||
return layout;
|
return r;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public class BrowserChooserModel extends BrowserAbstractSessionModel<OpenFileSystemModel> {
|
public class BrowserFileChooserModel extends BrowserAbstractSessionModel<OpenFileSystemModel> {
|
||||||
|
|
||||||
private final OpenFileSystemModel.SelectionMode selectionMode;
|
private final OpenFileSystemModel.SelectionMode selectionMode;
|
||||||
private final ObservableList<BrowserEntry> fileSelection = FXCollections.observableArrayList();
|
private final ObservableList<BrowserEntry> fileSelection = FXCollections.observableArrayList();
|
||||||
|
@ -30,7 +30,7 @@ public class BrowserChooserModel extends BrowserAbstractSessionModel<OpenFileSys
|
||||||
@Setter
|
@Setter
|
||||||
private Consumer<List<FileReference>> onFinish;
|
private Consumer<List<FileReference>> onFinish;
|
||||||
|
|
||||||
public BrowserChooserModel(OpenFileSystemModel.SelectionMode selectionMode) {
|
public BrowserFileChooserModel(OpenFileSystemModel.SelectionMode selectionMode) {
|
||||||
this.selectionMode = selectionMode;
|
this.selectionMode = selectionMode;
|
||||||
selectedEntry.addListener((observable, oldValue, newValue) -> {
|
selectedEntry.addListener((observable, oldValue, newValue) -> {
|
||||||
if (newValue == null) {
|
if (newValue == null) {
|
||||||
|
@ -45,7 +45,7 @@ public class BrowserChooserModel extends BrowserAbstractSessionModel<OpenFileSys
|
||||||
public void finishChooser() {
|
public void finishChooser() {
|
||||||
var chosen = new ArrayList<>(fileSelection);
|
var chosen = new ArrayList<>(fileSelection);
|
||||||
|
|
||||||
synchronized (BrowserChooserModel.this) {
|
synchronized (BrowserFileChooserModel.this) {
|
||||||
var open = selectedEntry.getValue();
|
var open = selectedEntry.getValue();
|
||||||
if (open != null) {
|
if (open != null) {
|
||||||
ThreadHelper.runAsync(() -> {
|
ThreadHelper.runAsync(() -> {
|
||||||
|
@ -54,10 +54,6 @@ public class BrowserChooserModel extends BrowserAbstractSessionModel<OpenFileSys
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chosen.size() == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var stores = chosen.stream()
|
var stores = chosen.stream()
|
||||||
.map(entry -> new FileReference(
|
.map(entry -> new FileReference(
|
||||||
selectedEntry.getValue().getEntry(),
|
selectedEntry.getValue().getEntry(),
|
||||||
|
@ -81,7 +77,7 @@ public class BrowserChooserModel extends BrowserAbstractSessionModel<OpenFileSys
|
||||||
model = new OpenFileSystemModel(this, store, selectionMode);
|
model = new OpenFileSystemModel(this, store, selectionMode);
|
||||||
model.init();
|
model.init();
|
||||||
// Prevent multiple calls from interfering with each other
|
// Prevent multiple calls from interfering with each other
|
||||||
synchronized (BrowserChooserModel.this) {
|
synchronized (BrowserFileChooserModel.this) {
|
||||||
selectedEntry.setValue(model);
|
selectedEntry.setValue(model);
|
||||||
sessionEntries.add(model);
|
sessionEntries.add(model);
|
||||||
}
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
package io.xpipe.app.comp.base;
|
package io.xpipe.app.comp.base;
|
||||||
|
|
||||||
import atlantafx.base.controls.Spacer;
|
|
||||||
import atlantafx.base.theme.Styles;
|
import atlantafx.base.theme.Styles;
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.core.AppWindowHelper;
|
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();
|
HBox buttons = new HBox();
|
||||||
buttons.setFillHeight(true);
|
buttons.setFillHeight(true);
|
||||||
var customButton = bottom();
|
var customButton = bottom();
|
||||||
if (customButton != null) {
|
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.getStyleClass().add("buttons");
|
||||||
buttons.setSpacing(5);
|
buttons.setSpacing(5);
|
||||||
buttons.setAlignment(Pos.CENTER_RIGHT);
|
buttons.setAlignment(Pos.CENTER_RIGHT);
|
||||||
|
@ -69,9 +72,9 @@ public abstract class DialogComp extends Comp<CompStructure<Region>> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompStructure<Region> createBase() {
|
public CompStructure<Region> createBase() {
|
||||||
var sp = scrollPane(content()).createRegion();
|
var sp = pane(content()).createRegion();
|
||||||
VBox vbox = new VBox();
|
VBox vbox = new VBox();
|
||||||
vbox.getChildren().addAll(sp, createStepNavigation());
|
vbox.getChildren().addAll(sp, createNavigation());
|
||||||
vbox.getStyleClass().add("dialog-comp");
|
vbox.getStyleClass().add("dialog-comp");
|
||||||
vbox.setFillWidth(true);
|
vbox.setFillWidth(true);
|
||||||
VBox.setVgrow(sp, Priority.ALWAYS);
|
VBox.setVgrow(sp, Priority.ALWAYS);
|
||||||
|
@ -86,7 +89,7 @@ public abstract class DialogComp extends Comp<CompStructure<Region>> {
|
||||||
|
|
||||||
public abstract Comp<?> content();
|
public abstract Comp<?> content();
|
||||||
|
|
||||||
protected Comp<?> scrollPane(Comp<?> content) {
|
protected Comp<?> pane(Comp<?> content) {
|
||||||
var entry = content.styleClass("dialog-content");
|
var entry = content.styleClass("dialog-content");
|
||||||
return Comp.of(() -> {
|
return Comp.of(() -> {
|
||||||
var entryR = entry.createRegion();
|
var entryR = entry.createRegion();
|
||||||
|
|
|
@ -309,8 +309,8 @@ public class StoreCreationComp extends DialogComp {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Comp<?> scrollPane(Comp<?> content) {
|
protected Comp<?> pane(Comp<?> content) {
|
||||||
var back = super.scrollPane(content);
|
var back = super.pane(content);
|
||||||
return new ErrorOverlayComp(back, messageProp);
|
return new ErrorOverlayComp(back, messageProp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,10 @@ public interface ExternalRdpClientType extends PrefsChoiceValue {
|
||||||
var file = writeConfig(adaptedRdpConfig);
|
var file = writeConfig(adaptedRdpConfig);
|
||||||
LocalShell.getShell()
|
LocalShell.getShell()
|
||||||
.executeSimpleCommand(CommandBuilder.of().add(executable).addFile(file.toString()));
|
.executeSimpleCommand(CommandBuilder.of().add(executable).addFile(file.toString()));
|
||||||
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
|
ThreadHelper.sleep(1000);
|
||||||
|
Files.delete(file);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private RdpConfig getAdaptedConfig(LaunchConfiguration configuration) throws Exception {
|
private RdpConfig getAdaptedConfig(LaunchConfiguration configuration) throws Exception {
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package io.xpipe.app.util;
|
package io.xpipe.app.util;
|
||||||
|
|
||||||
|
import io.xpipe.core.util.StreamCharset;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.BufferedReader;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
@ -15,10 +17,12 @@ public class RdpConfig {
|
||||||
|
|
||||||
Map<String, TypedValue> content;
|
Map<String, TypedValue> content;
|
||||||
|
|
||||||
public static RdpConfig parseFile(String file) throws IOException {
|
public static RdpConfig parseFile(String file) throws Exception {
|
||||||
var content = Files.readString(Path.of(file));
|
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);
|
return parseContent(content);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static RdpConfig parseContent(String content) {
|
public static RdpConfig parseContent(String content) {
|
||||||
var map = new LinkedHashMap<String, TypedValue>();
|
var map = new LinkedHashMap<String, TypedValue>();
|
||||||
|
|
|
@ -4,7 +4,6 @@ import lombok.Value;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.io.Reader;
|
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -138,7 +137,28 @@ public class StreamCharset {
|
||||||
return found.get();
|
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()) {
|
if (hasByteOrderMark()) {
|
||||||
var bom = stream.readNBytes(getByteOrderMark().length);
|
var bom = stream.readNBytes(getByteOrderMark().length);
|
||||||
if (bom.length != 0 && !Arrays.equals(bom, getByteOrderMark())) {
|
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
|
sshConfigString.displayDescription=Create a fully customized SSH connection
|
||||||
sshConfigStringContent=Configuration
|
sshConfigStringContent=Configuration
|
||||||
sshConfigStringContentDescription=SSH options for the connection in OpenSSH config format
|
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
|
vnc.displayDescription=Open a VNC session via an SSH tunnel
|
||||||
binding=Binding
|
binding=Binding
|
||||||
vncPortDescription=The port the VNC server is listening on
|
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.
|
sshTrustKeyHeader=The host key is not known, and you have enabled manual host key verification.
|
||||||
sshTrustKeyTitle=Unknown host key
|
sshTrustKeyTitle=Unknown host key
|
||||||
vnc=VNC connections
|
vnc=VNC connections
|
||||||
rdpTunnel.displayName=RDP over SSH
|
rdpTunnel.displayName=RDP connection over SSH
|
||||||
rdpTunnel.displayDescription=Connect via RDP over a tunneled SSH connection
|
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