Rework process streams

This commit is contained in:
crschnick 2024-08-08 06:59:39 +00:00
parent a26917b500
commit 58ef068842
135 changed files with 1444 additions and 627 deletions

View file

@ -197,6 +197,7 @@ The distributed XPipe application consists out of two parts:
- The closed-source extensions, mostly for professional edition features, which are not included in this repository
Additional features are available in the professional edition. For more details see https://xpipe.io/pricing.
If your enterprise puts great emphasis on having access to the full source code, there are also full source-available enterprise options available.
## More links

View file

@ -1,5 +1,7 @@
package io.xpipe.app.beacon;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import io.xpipe.app.core.AppResources;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
@ -8,13 +10,10 @@ import io.xpipe.beacon.BeaconConfig;
import io.xpipe.beacon.BeaconInterface;
import io.xpipe.core.process.OsType;
import io.xpipe.core.util.XPipeInstallation;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import lombok.Getter;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Inet4Address;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@ -127,7 +126,7 @@ public class AppBeaconServer {
}
private void start() throws IOException {
server = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), port), 10);
server = HttpServer.create(new InetSocketAddress(Inet4Address.getByAddress(new byte[]{ 0x7f,0x00,0x00,0x01 }), port), 10);
BeaconInterface.getAll().forEach(beaconInterface -> {
server.createContext(beaconInterface.getPath(), new BeaconRequestHandler<>(beaconInterface));
});

View file

@ -50,7 +50,7 @@ public class BlobManager {
public Path newBlobFile() throws IOException {
var file = TEMP.resolve(UUID.randomUUID().toString());
Files.createDirectories(file.getParent());
FileUtils.forceMkdir(file.getParent().toFile());
return file;
}

View file

@ -190,12 +190,11 @@ public class BrowserTransferModel {
}
public ObservableBooleanValue downloadFinished() {
return Bindings.createBooleanBinding(
() -> {
return progress.getValue() != null
&& progress.getValue().done();
},
progress);
synchronized (progress) {
return Bindings.createBooleanBinding(() -> {
return progress.getValue() != null && progress.getValue().done();
}, progress);
}
}
}
}

View file

@ -79,7 +79,7 @@ public class BrowserQuickAccessContextMenu extends ContextMenu {
getItems().addAll(r.getItems());
// Prevent NPE in show()
if (getScene() == null || anchor == null) {
if (getScene() == null) {
return;
}
show(anchor, Side.RIGHT, 0, 0);

View file

@ -70,7 +70,9 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
.bind(Bindings.createDoubleBinding(
() -> {
var v = bar.getVisibleAmount();
return v < 1.0 ? 1.0 : 0.0;
// Check for rounding and accuracy issues
// It might not be exactly equal to 1.0
return v < 0.99 ? 1.0 : 0.0;
},
bar.visibleAmountProperty()));
}

View file

@ -22,6 +22,7 @@ import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import lombok.SneakyThrows;
import org.apache.commons.io.FileUtils;
import java.io.IOException;
import java.nio.file.Files;
@ -63,7 +64,7 @@ public class MarkdownComp extends Comp<CompStructure<StackPane>> {
var html = MarkdownHelper.toHtml(markdown, s -> s, htmlTransformation, null);
try {
// Workaround for https://bugs.openjdk.org/browse/JDK-8199014
Files.createDirectories(file.getParent());
FileUtils.forceMkdir(file.getParent().toFile());
Files.writeString(file, html);
return file;
} catch (IOException e) {

View file

@ -3,7 +3,6 @@ package io.xpipe.app.comp.store;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.augment.GrowAugment;
import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.geometry.HPos;
@ -41,9 +40,11 @@ public class DenseStoreEntryComp extends StoreEntryComp {
.bind(PlatformThread.sync(Bindings.createStringBinding(
() -> {
var val = summary.getValue();
if (val != null
&& grid.isHover()
&& getWrapper().getEntry().getProvider().alwaysShowSummary()) {
var p = getWrapper().getEntry().getProvider();
if (val != null && grid.isHover()
&& p.alwaysShowSummary()) {
return val;
} else if (info.getValue() == null && p.alwaysShowSummary()){
return val;
} else {
return info.getValue();

View file

@ -1,5 +1,6 @@
package io.xpipe.app.comp.store;
import atlantafx.base.controls.Spacer;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.comp.base.DialogComp;
import io.xpipe.app.comp.base.ErrorOverlayComp;
@ -21,7 +22,6 @@ import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.*;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.util.ValidationException;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
@ -33,8 +33,6 @@ import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import atlantafx.base.controls.Spacer;
import lombok.AccessLevel;
import lombok.experimental.FieldDefaults;
import net.synedra.validatorfx.GraphicDecorationStackPane;
@ -51,7 +49,7 @@ public class StoreCreationComp extends DialogComp {
Stage window;
BiConsumer<DataStoreEntry, Boolean> consumer;
Property<DataStoreProvider> provider;
Property<DataStore> store;
ObjectProperty<DataStore> store;
Predicate<DataStoreProvider> filter;
BooleanProperty busy = new SimpleBooleanProperty();
Property<Validator> validator = new SimpleObjectProperty<>(new SimpleValidator());
@ -60,6 +58,7 @@ public class StoreCreationComp extends DialogComp {
ObservableValue<DataStoreEntry> entry;
BooleanProperty changedSinceError = new SimpleBooleanProperty();
BooleanProperty skippable = new SimpleBooleanProperty();
BooleanProperty connectable = new SimpleBooleanProperty();
StringProperty name;
DataStoreEntry existingEntry;
boolean staticDisplay;
@ -68,7 +67,7 @@ public class StoreCreationComp extends DialogComp {
Stage window,
BiConsumer<DataStoreEntry, Boolean> consumer,
Property<DataStoreProvider> provider,
Property<DataStore> store,
ObjectProperty<DataStore> store,
Predicate<DataStoreProvider> filter,
String initialName,
DataStoreEntry existingEntry,
@ -96,6 +95,12 @@ public class StoreCreationComp extends DialogComp {
}
});
this.provider.subscribe((n) -> {
if (n != null) {
connectable.setValue(n.canConnectDuringCreation());
}
});
this.apply(r -> {
r.get().setPrefWidth(650);
r.get().setPrefHeight(750);
@ -163,7 +168,12 @@ public class StoreCreationComp extends DialogComp {
if (!DataStorage.get().getStoreEntries().contains(e)) {
DataStorage.get().addStoreEntryIfNotPresent(newE);
} else {
DataStorage.get().updateEntry(e, newE);
// We didn't change anything
if (e.getStore().equals(newE.getStore())) {
e.setName(newE.getName());
} else {
DataStorage.get().updateEntry(e, newE);
}
}
});
},
@ -239,7 +249,16 @@ public class StoreCreationComp extends DialogComp {
finish();
}
})
.visible(skippable));
.visible(skippable),
new ButtonComp(AppI18n.observable("connect"), null, () -> {
var temp = DataStoreEntry.createTempWrapper(store.getValue());
var action = provider.getValue().launchAction(temp);
ThreadHelper.runFailableAsync(() -> {
action.execute();
});
}).hide(connectable.not().or(Bindings.createBooleanBinding(() -> {
return store.getValue() == null || !store.getValue().isComplete();
}, store))));
}
@Override
@ -393,11 +412,9 @@ public class StoreCreationComp extends DialogComp {
private Region createLayout() {
var layout = new BorderPane();
layout.getStyleClass().add("store-creator");
layout.setPadding(new Insets(20));
var providerChoice = new StoreProviderChoiceComp(filter, provider, staticDisplay);
if (staticDisplay) {
providerChoice.apply(struc -> struc.get().setDisable(true));
} else {
var showProviders = !staticDisplay && providerChoice.getProviders().size() > 1;
if (showProviders) {
providerChoice.onSceneAssign(struc -> struc.get().requestFocus());
}
providerChoice.apply(GrowAugment.create(true, false));
@ -422,9 +439,14 @@ public class StoreCreationComp extends DialogComp {
var sep = new Separator();
sep.getStyleClass().add("spacer");
var top = new VBox(providerChoice.createRegion(), new Spacer(7, Orientation.VERTICAL), sep);
var top = new VBox(providerChoice.createRegion(), new Spacer(5, Orientation.VERTICAL), sep);
top.getStyleClass().add("top");
layout.setTop(top);
if (showProviders) {
layout.setTop(top);
layout.setPadding(new Insets(15, 20, 20, 20));
} else {
layout.setPadding(new Insets(5, 20, 20, 20));
}
var valSp = new GraphicDecorationStackPane();
valSp.getChildren().add(layout);

View file

@ -42,6 +42,8 @@ public class StoreCreationMenu {
// menu.getItems().add(category("addCommand", "mdi2c-code-greater-than", DataStoreCreationCategory.COMMAND, "cmd"));
menu.getItems().add(category("addSerial", "mdi2s-serial-port", DataStoreCreationCategory.SERIAL, "serial"));
menu.getItems().add(category("addDatabase", "mdi2d-database-plus", DataStoreCreationCategory.DATABASE, null));
}

View file

@ -482,7 +482,7 @@ public abstract class StoreEntryComp extends SimpleComp {
sc.textProperty().bind(AppI18n.observable("base.createShortcut"));
sc.setOnAction(event -> {
ThreadHelper.runFailableAsync(() -> {
DesktopShortcuts.create(
DesktopShortcuts.createCliOpen(
url,
DataStorage.get()
.getStoreEntryDisplayName(

View file

@ -29,7 +29,7 @@ public class StoreProviderChoiceComp extends Comp<CompStructure<ComboBox<DataSto
Property<DataStoreProvider> provider;
boolean staticDisplay;
private List<DataStoreProvider> getProviders() {
public List<DataStoreProvider> getProviders() {
return DataStoreProviders.getAll().stream()
.filter(val -> filter == null || filter.test(val))
.toList();

View file

@ -163,10 +163,10 @@ public class StoreSection {
var allChildren = all.filtered(
other -> {
// Legacy implementation that does not use children caches. Use for testing
// if (true) return DataStorage.get()
// .getDisplayParent(other.getEntry())
// .map(found -> found.equals(e.getEntry()))
// .orElse(false);
// if (true) return DataStorage.get()
// .getDefaultDisplayParent(other.getEntry())
// .map(found -> found.equals(e.getEntry()))
// .orElse(false);
// is children. This check is fast as the children are cached in the storage
return DataStorage.get().getStoreChildren(e.getEntry()).contains(other.getEntry())

View file

@ -1,6 +1,7 @@
package io.xpipe.app.core;
import io.xpipe.app.issue.ErrorEvent;
import org.apache.commons.io.FileUtils;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
@ -21,7 +22,7 @@ public class AppDataLock {
public static boolean lock() {
try {
var file = getLockFile().toFile();
Files.createDirectories(file.toPath().getParent());
FileUtils.forceMkdir(file.getParentFile());
if (!Files.exists(file.toPath())) {
try {
// It is possible that another instance creates the lock at almost the same time

View file

@ -55,6 +55,12 @@ public class AppGreetings {
if (set || AppProperties.get().isDevelopmentEnvironment()) {
return;
}
if (AppProperties.get().isAutoAcceptEula()) {
AppCache.update("legalAccepted", true);
return;
}
var read = new SimpleBooleanProperty();
var accepted = new SimpleBooleanProperty();
AppWindowHelper.showBlockingAlert(alert -> {

View file

@ -138,7 +138,7 @@ public class AppLogs {
var shouldLogToFile = shouldWriteLogs();
if (shouldLogToFile) {
try {
Files.createDirectories(usedLogsDir);
FileUtils.forceMkdir(usedLogsDir.toFile());
var file = usedLogsDir.resolve("xpipe.log");
var fos = new FileOutputStream(file.toFile(), true);
var buf = new BufferedOutputStream(fos);

View file

@ -37,11 +37,13 @@ public class AppProperties {
boolean useVirtualThreads;
boolean debugThreads;
Path dataDir;
Path defaultDataDir;
boolean showcase;
AppVersion canonicalVersion;
boolean locatePtb;
boolean locatorVersionCheck;
boolean isTest;
boolean autoAcceptEula;
public AppProperties() {
var appDir = Path.of(System.getProperty("user.dir")).resolve("app");
@ -86,6 +88,7 @@ public class AppProperties {
debugThreads = Optional.ofNullable(System.getProperty("io.xpipe.app.debugThreads"))
.map(Boolean::parseBoolean)
.orElse(false);
defaultDataDir = Path.of(System.getProperty("user.home"), isStaging() ? ".xpipe-ptb" : ".xpipe");
dataDir = Optional.ofNullable(System.getProperty("io.xpipe.app.dataDir"))
.map(s -> {
var p = Path.of(s);
@ -94,7 +97,7 @@ public class AppProperties {
}
return p;
})
.orElse(Path.of(System.getProperty("user.home"), isStaging() ? ".xpipe-ptb" : ".xpipe"));
.orElse(defaultDataDir);
showcase = Optional.ofNullable(System.getProperty("io.xpipe.app.showcase"))
.map(Boolean::parseBoolean)
.orElse(false);
@ -107,6 +110,9 @@ public class AppProperties {
.map(s -> !Boolean.parseBoolean(s))
.orElse(true);
isTest = isJUnitTest();
autoAcceptEula = Optional.ofNullable(System.getProperty("io.xpipe.app.acceptEula"))
.map(Boolean::parseBoolean)
.orElse(false);
}
private static boolean isJUnitTest() {

View file

@ -113,9 +113,6 @@ public class AppTheme {
}
});
});
} catch (UnsupportedOperationException ex) {
// The platform preferences are sometimes not initialized yet
ErrorEvent.fromThrowable(ex).expected().omit().handle();
} catch (Throwable t) {
ErrorEvent.fromThrowable(t).omit().handle();
}
@ -139,9 +136,6 @@ public class AppTheme {
} else {
AppPrefs.get().theme.setValue(Theme.getDefaultLightTheme());
}
} catch (UnsupportedOperationException ex) {
// The platform preferences are sometimes not initialized yet
ErrorEvent.fromThrowable(ex).expected().omit().handle();
} catch (Exception ex) {
// The color scheme query can fail if the toolkit is not initialized properly
AppPrefs.get().theme.setValue(Theme.getDefaultLightTheme());

View file

@ -2,6 +2,7 @@ package io.xpipe.app.core.check;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.issue.ErrorEvent;
import org.apache.commons.io.FileUtils;
import java.io.IOException;
import java.nio.file.Files;
@ -12,18 +13,17 @@ public class AppUserDirectoryCheck {
var dataDirectory = AppProperties.get().getDataDir();
try {
Files.createDirectories(dataDirectory);
FileUtils.forceMkdir(dataDirectory.toFile());
var testDirectory = dataDirectory.resolve("permissions_check");
Files.createDirectories(testDirectory);
if (!Files.exists(testDirectory)) {
throw new IOException("Directory creation in user home directory failed silently");
}
FileUtils.forceMkdir(testDirectory.toFile());
Files.delete(testDirectory);
// if (true) throw new IOException();
} catch (IOException e) {
ErrorEvent.fromThrowable("Unable to access directory " + dataDirectory
+ ". Please make sure that you have the appropriate permissions and no Antivirus program is blocking the access. "
+ "In case you use cloud storage, verify that your cloud storage is working and you are logged in.", e)
ErrorEvent.fromThrowable(
new IOException(
"Unable to access directory " + dataDirectory
+ ". Please make sure that you have the appropriate permissions and no Antivirus program is blocking the access. "
+ "In case you use cloud storage, verify that your cloud storage is working and you are logged in."))
.term()
.expected()
.handle();

View file

@ -9,5 +9,6 @@ public enum DataStoreCreationCategory {
TUNNEL,
SCRIPT,
CLUSTER,
DESKTOP
DESKTOP,
SERIAL
}

View file

@ -100,6 +100,10 @@ public interface DataStoreProvider {
return Comp.empty();
}
default boolean canConnectDuringCreation() {
return false;
}
default Comp<?> createInsightsComp(ObservableValue<DataStore> store) {
var content = Bindings.createStringBinding(
() -> {
@ -148,6 +152,10 @@ public interface DataStoreProvider {
return DataStoreUsageCategory.DATABASE;
}
if (cc == DataStoreCreationCategory.SERIAL) {
return DataStoreUsageCategory.SERIAL;
}
return null;
}

View file

@ -16,5 +16,7 @@ public enum DataStoreUsageCategory {
@JsonProperty("desktop")
DESKTOP,
@JsonProperty("group")
GROUP;
GROUP,
@JsonProperty("serial")
SERIAL;
}

View file

@ -11,7 +11,7 @@ public interface EnabledStoreProvider extends DataStoreProvider {
@Override
default StoreEntryComp customEntryComp(StoreSection sec, boolean preferLarge) {
if (sec.getWrapper().getValidity().getValue() != DataStoreEntry.Validity.COMPLETE) {
if (sec.getWrapper().getValidity().getValue() == DataStoreEntry.Validity.LOAD_FAILED) {
return StoreEntryComp.create(sec, null, preferLarge);
}

View file

@ -2,10 +2,8 @@ package io.xpipe.app.ext;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.util.FailableRunnable;
import io.xpipe.core.util.ModuleLayerLoader;
import lombok.AllArgsConstructor;
import lombok.Value;
@ -22,10 +20,6 @@ public abstract class ScanProvider {
return ALL;
}
public ScanOperation create(DataStore store) {
return null;
}
public ScanOperation create(DataStoreEntry entry, ShellControl sc) throws Exception {
return null;
}

View file

@ -1,5 +1,6 @@
package io.xpipe.app.fxcomps.impl;
import atlantafx.base.theme.Styles;
import io.xpipe.app.browser.session.BrowserChooserComp;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.core.AppI18n;
@ -15,39 +16,29 @@ import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.core.store.FileNames;
import io.xpipe.core.store.FileSystemStore;
import javafx.application.Platform;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.Alert;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import atlantafx.base.theme.Styles;
import org.kordamp.ikonli.javafx.FontIcon;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.List;
import java.util.ArrayList;
public class ContextualFileReferenceChoiceComp extends Comp<CompStructure<HBox>> {
private final Property<DataStoreEntryRef<? extends FileSystemStore>> fileSystem;
private final Property<String> filePath;
private final boolean allowSync;
public <T extends FileSystemStore> ContextualFileReferenceChoiceComp(
ObservableValue<DataStoreEntryRef<T>> fileSystem, Property<String> filePath) {
this.fileSystem = new SimpleObjectProperty<>();
fileSystem.subscribe(val -> {
this.fileSystem.setValue(val);
});
this.filePath = filePath;
}
public <T extends FileSystemStore> ContextualFileReferenceChoiceComp(
Property<DataStoreEntryRef<T>> fileSystem, Property<String> filePath) {
Property<DataStoreEntryRef<T>> fileSystem, Property<String> filePath, boolean allowSync
) {
this.allowSync = allowSync;
this.fileSystem = new SimpleObjectProperty<>();
fileSystem.subscribe(val -> {
this.fileSystem.setValue(val);
@ -79,7 +70,7 @@ public class ContextualFileReferenceChoiceComp extends Comp<CompStructure<HBox>>
},
false);
})
.styleClass(Styles.CENTER_PILL)
.styleClass(allowSync ? Styles.CENTER_PILL : Styles.RIGHT_PILL)
.grow(false, true);
var gitShareButton = new ButtonComp(null, new FontIcon("mdi2g-git"), () -> {
@ -126,7 +117,13 @@ public class ContextualFileReferenceChoiceComp extends Comp<CompStructure<HBox>>
gitShareButton.tooltipKey("gitShareFileTooltip");
gitShareButton.styleClass(Styles.RIGHT_PILL).grow(false, true);
var layout = new HorizontalComp(List.of(fileNameComp, fileBrowseButton, gitShareButton))
var nodes = new ArrayList<Comp<?>>();
nodes.add(fileNameComp);
nodes.add(fileBrowseButton);
if (allowSync) {
nodes.add(gitShareButton);
}
var layout = new HorizontalComp(nodes)
.apply(struc -> struc.get().setFillHeight(true));
layout.apply(struc -> {

View file

@ -53,7 +53,7 @@ public class DataStoreListChoiceComp<T extends DataStore> extends SimpleComp {
});
return new HorizontalComp(List.of(label, Comp.hspacer(), delete)).styleClass("entry");
},
true)
false)
.padding(new Insets(0))
.apply(struc -> struc.get().setMinHeight(0))
.apply(struc -> ((VBox) struc.get().getContent()).setSpacing(5));

View file

@ -10,6 +10,9 @@ import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.beans.binding.Bindings;
import javafx.beans.property.Property;
import javafx.scene.Cursor;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseButton;
import atlantafx.base.controls.CustomTextField;
@ -53,6 +56,13 @@ public class FilterComp extends Comp<CompStructure<CustomTextField>> {
filter.focusedProperty()));
filter.setAccessibleText("Filter");
filter.addEventFilter(KeyEvent.KEY_PRESSED,event -> {
if (new KeyCodeCombination(KeyCode.ESCAPE).match(event)) {
filter.getScene().getRoot().requestFocus();
event.consume();
}
});
filterText.subscribe(val -> {
PlatformThread.runLaterIfNeeded(() -> {
clear.setVisible(val != null);

View file

@ -0,0 +1,79 @@
package io.xpipe.app.fxcomps.impl;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.beans.property.Property;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.scene.control.ComboBox;
import javafx.scene.control.skin.ComboBoxListViewSkin;
import javafx.scene.input.KeyEvent;
import lombok.AccessLevel;
import lombok.experimental.FieldDefaults;
import java.util.List;
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class IntComboFieldComp extends Comp<CompStructure<ComboBox<String>>> {
Property<Integer> value;
List<Integer> predefined;
boolean allowNegative;
public IntComboFieldComp(Property<Integer> value, List<Integer> predefined, boolean allowNegative) {
this.value = value;
this.predefined = predefined;
this.allowNegative = allowNegative;
}
@Override
public CompStructure<ComboBox<String>> createBase() {
var text = new ComboBox<String>();
text.setEditable(true);
text.setValue(value.getValue() != null ? value.getValue().toString() : null);
text.setItems(FXCollections.observableList(predefined.stream().map(integer -> "" + integer).toList()));
text.setMaxWidth(2000);
text.getStyleClass().add("int-combo-field-comp");
text.setSkin(new ComboBoxListViewSkin<>(text));
text.setVisibleRowCount(Math.min(10, predefined.size()));
value.addListener((ChangeListener<Number>) (observableValue, oldValue, newValue) -> {
PlatformThread.runLaterIfNeeded(() -> {
if (newValue == null) {
text.setValue("");
} else {
text.setValue(newValue.toString());
}
});
});
text.addEventFilter(KeyEvent.KEY_TYPED, keyEvent -> {
if (allowNegative) {
if (!"-0123456789".contains(keyEvent.getCharacter())) {
keyEvent.consume();
}
} else {
if (!"0123456789".contains(keyEvent.getCharacter())) {
keyEvent.consume();
}
}
});
text.valueProperty().addListener((observableValue, oldValue, newValue) -> {
if (newValue == null
|| newValue.isEmpty()
|| (allowNegative && "-".equals(newValue))
|| !newValue.matches("-?\\d+")) {
value.setValue(null);
return;
}
int intValue = Integer.parseInt(newValue);
value.setValue(intValue);
});
return new SimpleCompStructure<>(text);
}
}

View file

@ -4,12 +4,10 @@ import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.beans.property.Property;
import javafx.beans.value.ChangeListener;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import lombok.AccessLevel;
import lombok.experimental.FieldDefaults;

View file

@ -1,63 +0,0 @@
package io.xpipe.app.fxcomps.impl;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.util.StringSource;
import io.xpipe.core.store.ShellStore;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
public class StringSourceComp extends SimpleComp {
private final Property<DataStoreEntryRef<ShellStore>> fileSystem;
private final Property<StringSource> stringSource;
public <T extends ShellStore> StringSourceComp(
ObservableValue<DataStoreEntryRef<T>> fileSystem, Property<StringSource> stringSource) {
this.stringSource = stringSource;
this.fileSystem = new SimpleObjectProperty<>();
fileSystem.subscribe(val -> {
this.fileSystem.setValue(val.get().ref());
});
}
@Override
protected Region createSimple() {
var inPlace =
new SimpleObjectProperty<>(stringSource.getValue() instanceof StringSource.InPlace i ? i.get() : null);
var fs = stringSource.getValue() instanceof StringSource.File f ? f.getFile() : null;
var file = new SimpleObjectProperty<>(
stringSource.getValue() instanceof StringSource.File f
? f.getFile().serialize()
: null);
var showText = new SimpleBooleanProperty(inPlace.get() != null);
var stringField = new TextAreaComp(inPlace);
stringField.hide(showText.not());
var fileComp = new ContextualFileReferenceChoiceComp(fileSystem, file);
fileComp.hide(showText);
var tr = stringField.createRegion();
var button = new IconButtonComp("mdi2c-checkbox-marked-outline", () -> {
showText.set(!showText.getValue());
})
.createRegion();
AnchorPane.setBottomAnchor(button, 10.0);
AnchorPane.setRightAnchor(button, 10.0);
var anchorPane = new AnchorPane(tr, button);
AnchorPane.setBottomAnchor(tr, 0.0);
AnchorPane.setTopAnchor(tr, 0.0);
AnchorPane.setLeftAnchor(tr, 0.0);
AnchorPane.setRightAnchor(tr, 0.0);
var fr = fileComp.createRegion();
return new StackPane(tr, fr);
}
}

View file

@ -14,18 +14,16 @@ import io.xpipe.app.terminal.ExternalTerminalType;
import io.xpipe.app.util.PasswordLockSecretValue;
import io.xpipe.core.util.InPlaceSecretValue;
import io.xpipe.core.util.ModuleHelper;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.beans.value.ObservableBooleanValue;
import javafx.beans.value.ObservableDoubleValue;
import javafx.beans.value.ObservableStringValue;
import javafx.beans.value.ObservableValue;
import lombok.Getter;
import lombok.Value;
import org.apache.commons.io.FileUtils;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Stream;
@ -175,6 +173,7 @@ public class AppPrefs {
new SecurityCategory(),
new HttpApiCategory(),
new WorkflowCategory(),
new WorkspacesCategory(),
new TroubleshootCategory(),
new DeveloperCategory())
.filter(appPrefsCategory -> appPrefsCategory.show())
@ -489,7 +488,7 @@ public class AppPrefs {
}
try {
Files.createDirectories(storageDirectory.get());
FileUtils.forceMkdir(storageDirectory.getValue().toFile());
} catch (Exception e) {
ErrorEvent.fromThrowable(e).expected().build().handle();
storageDirectory.setValue(DEFAULT_STORAGE_DIR);

View file

@ -114,14 +114,12 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue {
protected Optional<Path> determineFromPath() {
// Try to locate if it is in the Path
try (var cc = LocalShell.getShell()
.command(CommandBuilder.ofFunction(
var1 -> var1.getShellDialect().getWhichCommand(executable)))
try (var sc = LocalShell.getShell()
.start()) {
var out = cc.readStdoutDiscardErr();
var exit = cc.getExitCode();
if (exit == 0) {
var first = out.lines().findFirst();
var out = sc.command(CommandBuilder.ofFunction(
var1 -> var1.getShellDialect().getWhichCommand(executable))).readStdoutIfPossible();
if (out.isPresent()) {
var first = out.get().lines().findFirst();
if (first.isPresent()) {
return first.map(String::trim).map(Path::of);
}

View file

@ -21,11 +21,7 @@ public class SyncCategory extends AppPrefsCategory {
builder.addTitle("sync")
.sub(new OptionsBuilder()
.name("enableGitStorage")
.description(
AppProperties.get().isStaging()
&& !prefs.developerMode().getValue()
? "enableGitStoragePtbDisabled"
: "enableGitStorageDescription")
.description("enableGitStorageDescription")
.addToggle(prefs.enableGitStorage)
.disable(AppProperties.get().isStaging()
&& !prefs.developerMode().getValue())

View file

@ -0,0 +1,75 @@
package io.xpipe.app.prefs;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.core.mode.OperationMode;
import io.xpipe.app.core.window.AppWindowHelper;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.util.DesktopHelper;
import io.xpipe.app.util.DesktopShortcuts;
import io.xpipe.app.util.OptionsBuilder;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.process.OsType;
import io.xpipe.core.util.XPipeInstallation;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Insets;
import javafx.scene.control.ButtonType;
import org.apache.commons.io.FileUtils;
import java.nio.file.Files;
public class WorkspaceCreationAlert {
public static void showAsync() {
ThreadHelper.runFailableAsync(() -> {
show();
});
}
private static void show() throws Exception {
var name = new SimpleObjectProperty<>("New workspace");
var path = new SimpleObjectProperty<>(AppProperties.get().getDataDir());
var show = AppWindowHelper.showBlockingAlert(alert -> {
alert.setTitle(AppI18n.get("workspaceCreationAlertTitle"));
var content = new OptionsBuilder()
.nameAndDescription("workspaceName")
.addString(name)
.nameAndDescription("workspacePath")
.addPath(path)
.buildComp()
.minWidth(500)
.padding(new Insets(5, 20, 20, 20))
.apply(struc -> AppFont.small(struc.get()))
.createRegion();
alert.getButtonTypes().add(ButtonType.CANCEL);
alert.getButtonTypes().add(ButtonType.OK);
alert.getDialogPane().setContent(content);
})
.map(b -> b.getButtonData().isDefaultButton())
.orElse(false);
if (!show || name.get() == null || path.get() == null) {
return;
}
if (Files.exists(path.get()) && !FileUtils.isEmptyDirectory(path.get().toFile())) {
ErrorEvent.fromMessage("New workspace directory is not empty").expected().handle();
return;
}
var shortcutName = (AppProperties.get().isStaging() ? "XPipe PTB" : "XPipe") + " (" + name.get() + ")";
var file = switch (OsType.getLocal()) {
case OsType.Windows w -> {
var exec = XPipeInstallation.getCurrentInstallationBasePath().resolve(XPipeInstallation.getDaemonExecutablePath(w)).toString();
yield DesktopShortcuts.create(exec, "-Dio.xpipe.app.dataDir=\"" + path.get().toString() + "\" -Dio.xpipe.app.acceptEula=true", shortcutName);
}
default -> {
var exec = XPipeInstallation.getCurrentInstallationBasePath().resolve(XPipeInstallation.getRelativeCliExecutablePath(OsType.getLocal())).toString();
yield DesktopShortcuts.create(exec, "-d \"" + path.get().toString() + "\" --accept-eula", shortcutName);
}
};
DesktopHelper.browseFileInDirectory(file);
OperationMode.close();
}
}

View file

@ -0,0 +1,26 @@
package io.xpipe.app.prefs;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.util.OptionsBuilder;
public class WorkspacesCategory extends AppPrefsCategory {
@Override
protected String getId() {
return "workspaces";
}
@Override
protected Comp<?> create() {
return new OptionsBuilder()
.addTitle("manageWorkspaces")
.sub(new OptionsBuilder()
.nameAndDescription("workspaceAdd")
.addComp(
new ButtonComp(AppI18n.observable("addWorkspace"),
WorkspaceCreationAlert::showAsync)))
.buildComp();
}
}

View file

@ -23,7 +23,7 @@ public class ContextualFileReference {
private static String getDataDir() {
if (DataStorage.get() == null) {
return lastDataDir != null ? lastDataDir : normalized(AppPrefs.DEFAULT_STORAGE_DIR);
return lastDataDir != null ? lastDataDir : normalized(AppPrefs.DEFAULT_STORAGE_DIR.resolve("data"));
}
return lastDataDir = normalized(DataStorage.get().getDataDir());

View file

@ -273,6 +273,10 @@ public abstract class DataStorage {
}
public void updateEntry(DataStoreEntry entry, DataStoreEntry newEntry) {
var state = entry.getStorePersistentState();
var newState = state.mergeCopy(newEntry.getStorePersistentState());
newEntry.setStorePersistentState(newState);
var oldParent = DataStorage.get().getDefaultDisplayParent(entry);
var newParent = DataStorage.get().getDefaultDisplayParent(newEntry);
var sameParent = Objects.equals(oldParent, newParent);

View file

@ -157,7 +157,7 @@ public class DataStoreEntry extends StorageElement {
null,
uuid,
categoryUuid,
name,
name.trim(),
Instant.now(),
Instant.now(),
storeFromNode,
@ -194,7 +194,7 @@ public class DataStoreEntry extends StorageElement {
var categoryUuid = Optional.ofNullable(json.get("categoryUuid"))
.map(jsonNode -> UUID.fromString(jsonNode.textValue()))
.orElse(DataStorage.DEFAULT_CATEGORY_UUID);
var name = json.required("name").textValue();
var name = json.required("name").textValue().trim();
var persistentState = stateJson.get("persistentState");
var lastUsed = Optional.ofNullable(stateJson.get("lastUsed"))

View file

@ -437,7 +437,7 @@ public class StandardStorage extends DataStorage {
var s = Files.readString(file);
vaultKey = new String(Base64.getDecoder().decode(s), StandardCharsets.UTF_8);
} else {
Files.createDirectories(dir);
FileUtils.forceMkdir(dir.toFile());
vaultKey = UUID.randomUUID().toString();
Files.writeString(file, Base64.getEncoder().encodeToString(vaultKey.getBytes(StandardCharsets.UTF_8)));
}
@ -458,7 +458,7 @@ public class StandardStorage extends DataStorage {
Files.writeString(file, s);
}
} else {
Files.createDirectories(dir);
FileUtils.forceMkdir(dir.toFile());
var s = OsType.getLocal().getName();
Files.writeString(file, s);
}

View file

@ -99,16 +99,13 @@ public enum XPipeDistributionType {
// In theory, we can also add && !AppProperties.get().isStaging() here, but we want to replicate the
// production behavior
if (OsType.getLocal().equals(OsType.WINDOWS)) {
try (var chocoOut =
sc.command("choco search --local-only -r xpipe").start()) {
var out = chocoOut.readStdoutDiscardErr();
if (chocoOut.getExitCode() == 0) {
var split = out.split("\\|");
if (split.length == 2) {
var version = split[1];
if (AppProperties.get().getVersion().equals(version)) {
return CHOCO;
}
var out = sc.command("choco search --local-only -r xpipe").readStdoutIfPossible();
if (out.isPresent()) {
var split = out.get().split("\\|");
if (split.length == 2) {
var version = split[1];
if (AppProperties.get().getVersion().equals(version)) {
return CHOCO;
}
}
}
@ -117,17 +114,15 @@ public enum XPipeDistributionType {
// In theory, we can also add && !AppProperties.get().isStaging() here, but we want to replicate the
// production behavior
if (OsType.getLocal().equals(OsType.MACOS)) {
try (var brewOut = sc.command("brew list --casks --versions").start()) {
var out = brewOut.readStdoutDiscardErr();
if (brewOut.getExitCode() == 0) {
if (out.lines().anyMatch(s -> {
var split = s.split(" ");
return split.length == 2
&& split[0].equals("xpipe")
&& split[1].equals(AppProperties.get().getVersion());
})) {
return HOMEBREW;
}
var out = sc.command("brew list --casks --versions").readStdoutIfPossible();
if (out.isPresent()) {
if (out.get().lines().anyMatch(s -> {
var split = s.split(" ");
return split.length == 2
&& split[0].equals("xpipe")
&& split[1].equals(AppProperties.get().getVersion());
})) {
return HOMEBREW;
}
}
}

View file

@ -16,11 +16,10 @@ public class DesktopHelper {
return Path.of(LocalShell.getLocalPowershell()
.executeSimpleStringCommand("[Environment]::GetFolderPath([Environment+SpecialFolder]::Desktop)"));
} else if (OsType.getLocal() == OsType.LINUX) {
try (var cmd = LocalShell.getShell().command("xdg-user-dir DESKTOP").start()) {
var read = cmd.readStdoutDiscardErr();
var exit = cmd.getExitCode();
if (exit == 0) {
return Path.of(read);
try (var sc = LocalShell.getShell().start()) {
var out = sc.command("xdg-user-dir DESKTOP").readStdoutIfPossible();
if (out.isPresent()) {
return Path.of(out.get());
}
}
}
@ -34,12 +33,10 @@ public class DesktopHelper {
.executeSimpleStringCommand(
"(New-Object -ComObject Shell.Application).NameSpace('shell:Downloads').Self.Path"));
} else if (OsType.getLocal() == OsType.LINUX) {
try (var cmd =
LocalShell.getShell().command("xdg-user-dir DOWNLOAD").start()) {
var read = cmd.readStdoutDiscardErr();
var exit = cmd.getExitCode();
if (exit == 0) {
return Path.of(read);
try (var sc = LocalShell.getShell().start()) {
var out = sc.command("xdg-user-dir DOWNLOAD").readStdoutIfPossible();
if (out.isPresent()) {
return Path.of(out.get());
}
}
}

View file

@ -4,27 +4,31 @@ import io.xpipe.core.process.OsType;
import io.xpipe.core.util.XPipeInstallation;
import java.nio.file.Files;
import java.nio.file.Path;
public class DesktopShortcuts {
private static void createWindowsShortcut(String target, String name) throws Exception {
private static Path createWindowsShortcut(String executable, String args, String name) throws Exception {
var icon = XPipeInstallation.getLocalDefaultInstallationIcon();
var shortcutTarget = XPipeInstallation.getLocalDefaultCliExecutable();
var shortcutPath = DesktopHelper.getDesktopDirectory().resolve(name + ".lnk");
var content = String.format(
"""
set "TARGET=%s"
set "SHORTCUT=%s"
set PWS=powershell.exe -ExecutionPolicy Restricted -NoLogo -NonInteractive -NoProfile
%%PWS%% -Command "$ws = New-Object -ComObject WScript.Shell; $s = $ws.CreateShortcut('%%SHORTCUT%%'); $S.IconLocation='%s'; $S.WindowStyle=7; $S.TargetPath = '%%TARGET%%'; $S.Arguments = 'open %s'; $S.Save()"
$TARGET="%s"
$SHORTCUT="%s"
$ws = New-Object -ComObject WScript.Shell
$s = $ws.CreateShortcut("$SHORTCUT")
$S.IconLocation='%s'
$S.WindowStyle=7
$S.TargetPath = "$TARGET"
$S.Arguments = '%s'
$S.Save()
""",
shortcutTarget, shortcutPath, icon, target);
LocalShell.getShell().executeSimpleCommand(content);
executable, shortcutPath, icon, args);
LocalShell.getLocalPowershell().executeSimpleCommand(content);
return shortcutPath;
}
private static void createLinuxShortcut(String target, String name) throws Exception {
var exec = XPipeInstallation.getLocalDefaultCliExecutable();
private static Path createLinuxShortcut(String executable, String args, String name) throws Exception {
var icon = XPipeInstallation.getLocalDefaultInstallationIcon();
var content = String.format(
"""
@ -32,19 +36,19 @@ public class DesktopShortcuts {
Type=Application
Name=%s
Comment=Open with XPipe
Exec="%s" open %s
Exec="%s" %s
Icon=%s
Terminal=false
Categories=Utility;Development;
""",
name, exec, target, icon);
name, executable, args, icon);
var file = DesktopHelper.getDesktopDirectory().resolve(name + ".desktop");
Files.writeString(file, content);
file.toFile().setExecutable(true);
return file;
}
private static void createMacOSShortcut(String target, String name) throws Exception {
var exec = XPipeInstallation.getLocalDefaultCliExecutable();
private static Path createMacOSShortcut(String executable, String args, String name) throws Exception {
var icon = XPipeInstallation.getLocalDefaultInstallationIcon();
var base = DesktopHelper.getDesktopDirectory().resolve(name + ".app");
var content = String.format(
@ -52,18 +56,18 @@ public class DesktopShortcuts {
#!/usr/bin/env sh
"%s" open %s
""",
exec, target);
executable, args);
try (var pc = LocalShell.getShell()) {
pc.getShellDialect().deleteFileOrDirectory(pc, base.toString()).executeAndCheck();
pc.executeSimpleCommand(pc.getShellDialect().getMkdirsCommand(base + "/Contents/MacOS"));
pc.executeSimpleCommand(pc.getShellDialect().getMkdirsCommand(base + "/Contents/Resources"));
var executable = base + "/Contents/MacOS/" + name;
var macExec = base + "/Contents/MacOS/" + name;
pc.getShellDialect()
.createScriptTextFileWriteCommand(pc, content, executable)
.createScriptTextFileWriteCommand(pc, content, macExec)
.execute();
pc.executeSimpleCommand("chmod ugo+x \"" + executable + "\"");
pc.executeSimpleCommand("chmod ugo+x \"" + macExec + "\"");
pc.getShellDialect()
.createTextFileWriteCommand(pc, "APPL????", base + "/Contents/PkgInfo")
@ -85,15 +89,21 @@ public class DesktopShortcuts {
.execute();
pc.executeSimpleCommand("cp \"" + icon + "\" \"" + base + "/Contents/Resources/icon.icns\"");
}
return base;
}
public static void create(String target, String name) throws Exception {
public static Path createCliOpen(String action, String name) throws Exception {
var exec = XPipeInstallation.getLocalDefaultCliExecutable();
return create(exec, "open " + action, name);
}
public static Path create(String executable, String args, String name) throws Exception {
if (OsType.getLocal().equals(OsType.WINDOWS)) {
createWindowsShortcut(target, name);
return createWindowsShortcut(executable, args, name);
} else if (OsType.getLocal().equals(OsType.LINUX)) {
createLinuxShortcut(target, name);
return createLinuxShortcut(executable, args, name);
} else {
createMacOSShortcut(target, name);
return createMacOSShortcut(executable, args, name);
}
}
}

View file

@ -40,7 +40,7 @@ public class ScanAlert {
});
}
private static void showForShellStore(DataStoreEntry initial) {
public static void showForShellStore(DataStoreEntry initial) {
show(initial, (DataStoreEntry entry, ShellControl sc) -> {
if (!sc.getShellDialect().getDumbMode().supportsAnyPossibleInteraction()) {
return null;

View file

@ -26,7 +26,7 @@ public class ShellTemp {
temp = temp.resolve(user != null ? user : "user");
try {
Files.createDirectories(temp);
FileUtils.forceMkdir(temp.toFile());
// We did not set this in earlier versions. If we are running as a different user, it might fail
Files.setPosixFilePermissions(temp, PosixFilePermissions.fromString("rwxrwxrwx"));
} catch (Exception e) {

View file

@ -1,51 +0,0 @@
package io.xpipe.app.util;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.storage.ContextualFileReference;
import io.xpipe.core.store.ShellStore;
import lombok.EqualsAndHashCode;
import lombok.Value;
public abstract class StringSource {
public abstract String get() throws Exception;
@Value
@EqualsAndHashCode(callSuper = true)
public static class InPlace extends StringSource {
String value;
@Override
public String get() {
return value;
}
}
@Value
@EqualsAndHashCode(callSuper = true)
public static class File extends StringSource {
ShellStore host;
ContextualFileReference file;
@Override
public String get() throws Exception {
if (host == null || file == null) {
return "";
}
try (var sc = host.control().start()) {
var path = file.toAbsoluteFilePath(sc);
if (!sc.getShellDialect().createFileExistsCommand(sc, path).executeAndCheck()) {
throw ErrorEvent.expected(new IllegalArgumentException("File " + path + " does not exist"));
}
var abs = file.toAbsoluteFilePath(sc);
var content = sc.getShellDialect().getFileReadCommand(sc, abs).readStdoutOrThrow();
return content;
}
}
}
}

View file

@ -145,23 +145,20 @@ public abstract class WindowsRegistry {
.add("/v")
.addQuoted(valueName);
String output;
try (var c = shellControl.command(command).start()) {
output = c.readStdoutDiscardErr();
if (c.getExitCode() != 0) {
return Optional.empty();
}
var output = shellControl.command(command).readStdoutIfPossible();
if (output.isEmpty()) {
return Optional.empty();
}
// Output has the following format:
// \n<Version information>\n\n<key>\t<registry type>\t<value>
if (output.contains("\t")) {
String[] parsed = output.split("\t");
if (output.get().contains("\t")) {
String[] parsed = output.get().split("\t");
return Optional.of(parsed[parsed.length - 1]);
}
if (output.contains(" ")) {
String[] parsed = output.split(" ");
if (output.get().contains(" ")) {
String[] parsed = output.get().split(" ");
return Optional.of(parsed[parsed.length - 1]);
}
@ -176,14 +173,7 @@ public abstract class WindowsRegistry {
.add("/v")
.addQuoted(valueName)
.add("/s");
try (var c = shellControl.command(command).start()) {
var output = c.readStdoutDiscardErr();
if (c.getExitCode() != 0) {
return Optional.empty();
} else {
return Optional.of(output);
}
}
return shellControl.command(command).readStdoutIfPossible();
}
@Override
@ -196,22 +186,17 @@ public abstract class WindowsRegistry {
.add("/s")
.add("/e")
.add("/d");
try (var c = shellControl.command(command).start()) {
var output = c.readStdoutDiscardErr();
if (c.getExitCode() != 0) {
return shellControl.command(command).readStdoutIfPossible().flatMap(output -> {
return output.lines().findFirst().flatMap(s -> {
if (s.startsWith("HKEY_CURRENT_USER\\")) {
return Optional.of(new Key(HKEY_CURRENT_USER, s.replace("HKEY_CURRENT_USER\\", "")));
}
if (s.startsWith("HKEY_LOCAL_MACHINE\\")) {
return Optional.of(new Key(HKEY_LOCAL_MACHINE, s.replace("HKEY_LOCAL_MACHINE\\", "")));
}
return Optional.empty();
} else {
return output.lines().findFirst().flatMap(s -> {
if (s.startsWith("HKEY_CURRENT_USER\\")) {
return Optional.of(new Key(HKEY_CURRENT_USER, s.replace("HKEY_CURRENT_USER\\", "")));
}
if (s.startsWith("HKEY_LOCAL_MACHINE\\")) {
return Optional.of(new Key(HKEY_LOCAL_MACHINE, s.replace("HKEY_LOCAL_MACHINE\\", "")));
}
return Optional.empty();
});
}
}
});
});
}
}
}

View file

@ -1,11 +1,10 @@
## Welcome
Thank you for using XPipe!
Welcome to XPipe!
You can view the development status, report issues, and more at the following places:
- [GitHub Repository](https://github.com/xpipe-io/xpipe/)
- [Discord Server](https://discord.gg/8y89vS8cRb)
- [Slack Server](https://join.slack.com/t/XPipe/shared_invite/zt-1awjq0t5j-5i4UjNJfNe1VN4b_auu6Cg)
- [Email me](mailto://crschnick@xpipe.io)
Note that the XPipe project currently is a one-man show, but I still try to respond to everything in time.
- [Email us](mailto://hello@xpipe.io)

View file

@ -18,4 +18,4 @@
.options-comp .long-description {
-fx-padding: 0 6 0 6;
}
}

View file

@ -25,14 +25,6 @@
-fx-text-fill: #ee4829;
}
.store-entry-grid:incomplete .summary {
-fx-text-fill: #ee4829;
}
.store-entry-grid:incomplete .information {
-fx-text-fill: #ee4829;
}
.store-entry-grid:incomplete .icon {
-fx-opacity: 0.5;
}

View file

@ -101,7 +101,7 @@
-fx-border-color: -color-neutral-emphasis;
}
.text {
* {
-fx-font-smoothing-type: gray;
}

View file

@ -64,7 +64,8 @@ public class BeaconClient {
var client = HttpClient.newHttpClient();
HttpResponse<String> response;
try {
var uri = URI.create("http://localhost:" + port + prov.getPath());
// Use direct IP to prevent DNS lookups and potential blocks (e.g. portmaster)
var uri = URI.create("http://127.0.0.1:" + port + prov.getPath());
var builder = HttpRequest.newBuilder();
if (token != null) {
builder.header("Authorization", "Bearer " + token);

View file

@ -8,7 +8,7 @@ import io.xpipe.core.util.XPipeInstallation;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.Inet4Address;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.List;
@ -20,7 +20,7 @@ public class BeaconServer {
public static boolean isReachable(int port) {
try (var socket = new Socket()) {
socket.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), port), 5000);
socket.connect(new InetSocketAddress(Inet4Address.getByAddress(new byte[]{ 0x7f,0x00,0x00,0x01 }), port), 5000);
return true;
} catch (Exception e) {
return false;

View file

@ -85,13 +85,13 @@ def getArchName() {
def getPlatformName() {
def currentOS = DefaultNativePlatform.currentOperatingSystem;
def platform
def platform = null
if (currentOS.isWindows()) {
platform = 'windows'
} else if (currentOS.isMacOsX()) {
platform = 'osx'
} else {
} else if (currentOS.isLinux()) {
platform = 'linux'
} else if (currentOS.isMacOsX()) {
platform = 'osx'
}
return platform;
}
@ -145,8 +145,8 @@ project.ext {
"-Dvisualvm.display.name=XPipe",
"-Djavafx.preloader=io.xpipe.app.core.AppPreloader"
]
// Disable this on Windows for now as it requires Windows 10+
if (org.gradle.internal.os.OperatingSystem.current().isLinux() || org.gradle.internal.os.OperatingSystem.current().isMacOsX()) {
// Disable this for now as it requires Windows 10+
if (!org.gradle.internal.os.OperatingSystem.current().isWindows()) {
jvmRunArgs += ['-XX:+UseZGC']
}
if (org.gradle.internal.os.OperatingSystem.current().isMacOsX()) {

View file

@ -255,6 +255,10 @@ public class CommandBuilder {
}
public String buildFull(ShellControl sc) throws Exception {
if (sc == null) {
return buildSimple();
}
var s = buildBase(sc);
LinkedHashMap<String, String> map = new LinkedHashMap<>();
for (var e : environmentVariables.entrySet()) {

View file

@ -1,16 +1,10 @@
package io.xpipe.core.process;
import io.xpipe.core.util.FailableConsumer;
import com.fasterxml.jackson.databind.JsonNode;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.time.Duration;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
public interface CommandControl extends ProcessControl {
@ -70,32 +64,14 @@ public interface CommandControl extends ProcessControl {
CommandControl elevated(ElevationFunction function);
void withStdoutOrThrow(FailableConsumer<InputStreamReader, Exception> c);
String[] readStdoutAndStderr() throws Exception;
String readStdoutDiscardErr() throws Exception;
String readStderrDiscardStdout() throws Exception;
void discardOrThrow() throws Exception;
void accumulateStdout(Consumer<String> con);
void accumulateStderr(Consumer<String> con);
byte[] readRawBytesOrThrow() throws Exception;
String readStdoutOrThrow() throws Exception;
JsonNode readStdoutJsonOrThrow() throws Exception;
String readStderrOrThrow() throws Exception;
String readStdoutAndWait() throws Exception;
String readStderrAndWait() throws Exception;
Optional<String> readStdoutIfPossible() throws Exception;
default boolean discardAndCheckExit() throws ProcessOutputException {
@ -113,10 +89,6 @@ public interface CommandControl extends ProcessControl {
}
}
void discardOut();
void discardErr();
enum TerminalExitMode {
KEEP_OPEN,
CLOSE

View file

@ -1,6 +0,0 @@
package io.xpipe.core.process;
public interface CommandFeedbackPredicate {
boolean test(CommandBuilder command);
}

View file

@ -21,8 +21,10 @@ public interface OsType {
return MACOS;
} else if (osName.contains("win")) {
return WINDOWS;
} else {
} else if (osName.contains("nux")) {
return LINUX;
} else {
throw new UnsupportedOperationException("Unknown operating system");
}
}
@ -181,19 +183,15 @@ public interface OsType {
@Override
public String determineOperatingSystemName(ShellControl pc) throws Exception {
String type = "Unknown";
try (CommandControl c = pc.command("uname -o").start()) {
var text = c.readStdoutDiscardErr();
if (c.getExitCode() == 0) {
type = text.strip();
}
var uname = pc.command("uname -o").readStdoutIfPossible();
if (uname.isPresent()) {
type = uname.get();
}
String version = "?";
try (CommandControl c = pc.command("uname -r").start()) {
var text = c.readStdoutDiscardErr();
if (c.getExitCode() == 0) {
version = text.strip();
}
var unameR = pc.command("uname -r").readStdoutIfPossible();
if (unameR.isPresent()) {
version = unameR.get();
}
return type + " " + version;
@ -209,18 +207,14 @@ public interface OsType {
@Override
public String determineOperatingSystemName(ShellControl pc) throws Exception {
try (CommandControl c = pc.command("lsb_release -a").start()) {
var text = c.readStdoutDiscardErr();
if (c.getExitCode() == 0) {
return PropertiesFormatsParser.parse(text, ":").getOrDefault("Description", "Unknown");
}
var rel = pc.command("lsb_release -a").readStdoutIfPossible();
if (rel.isPresent()) {
return PropertiesFormatsParser.parse(rel.get(), ":").getOrDefault("Description", "Unknown");
}
try (CommandControl c = pc.command("cat /etc/*release").start()) {
var text = c.readStdoutDiscardErr();
if (c.getExitCode() == 0) {
return PropertiesFormatsParser.parse(text, "=").getOrDefault("PRETTY_NAME", "Unknown");
}
var cat = pc.command("cat /etc/*release").readStdoutIfPossible();
if (cat.isPresent()) {
return PropertiesFormatsParser.parse(cat.get(), "=").getOrDefault("PRETTY_NAME", "Unknown");
}
return super.determineOperatingSystemName(pc);

View file

@ -18,6 +18,8 @@ import java.util.function.Function;
public interface ShellControl extends ProcessControl {
ShellTtyState getTtyState();
void setNonInteractive();
boolean isInteractive();
@ -113,25 +115,12 @@ public interface ShellControl extends ProcessControl {
script));
}
default byte[] executeSimpleRawBytesCommand(String command) throws Exception {
try (CommandControl c = command(command).start()) {
return c.readRawBytesOrThrow();
}
}
default String executeSimpleStringCommand(String command) throws Exception {
try (CommandControl c = command(command).start()) {
return c.readStdoutOrThrow();
}
}
default Optional<String> executeSimpleStringCommandAndCheck(String command) throws Exception {
try (CommandControl c = command(command).start()) {
var out = c.readStdoutDiscardErr();
return c.getExitCode() == 0 ? Optional.of(out) : Optional.empty();
}
}
default boolean executeSimpleBooleanCommand(String command) throws Exception {
try (CommandControl c = command(command).start()) {
return c.discardAndCheckExit();
@ -150,20 +139,6 @@ public interface ShellControl extends ProcessControl {
}
}
default void executeSimpleCommand(String command, String failMessage) throws Exception {
try (CommandControl c = command(command).start()) {
c.discardOrThrow();
} catch (ProcessOutputException out) {
throw ProcessOutputException.withPrefix(failMessage, out);
}
}
default String executeSimpleStringCommand(ShellDialect type, String command) throws Exception {
try (var sub = subShell(type).start()) {
return sub.executeSimpleStringCommand(command);
}
}
ShellControl withSecurityPolicy(ShellSecurityPolicy policy);
ShellSecurityPolicy getEffectiveSecurityPolicy();

View file

@ -130,7 +130,7 @@ public interface ShellDialect {
default void prepareCommandForShell(CommandBuilder b) {}
String prepareTerminalInitFileOpenCommand(ShellDialect parentDialect, ShellControl sc, String file);
String prepareTerminalInitFileOpenCommand(ShellDialect parentDialect, ShellControl sc, String file, boolean exit);
String runScriptCommand(ShellControl parent, String file);
@ -184,5 +184,5 @@ public interface ShellDialect {
String getDisplayName();
boolean doesEchoInput();
boolean doesEchoInputByDefault();
}

View file

@ -8,6 +8,8 @@ public interface ShellDumbMode {
return true;
}
default void throwIfUnsupported() {}
default ShellDialect getSwitchDialect() {
return null;
}
@ -25,6 +27,14 @@ public interface ShellDumbMode {
class Unsupported implements ShellDumbMode {
private final String message;
public Unsupported(String message) {this.message = message;}
public void throwIfUnsupported() {
throw new UnsupportedOperationException(message);
}
@Override
public boolean supportsAnyPossibleInteraction() {
return false;

View file

@ -1,10 +0,0 @@
package io.xpipe.core.process;
import lombok.Value;
@Value
public class ShellProperties {
ShellDialect dialect;
boolean ansiEscapes;
}

View file

@ -0,0 +1,25 @@
package io.xpipe.core.process;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
@Getter
public enum ShellTtyState {
@JsonProperty("none")
NONE(true, false, false),
@JsonProperty("merged")
MERGED_STDERR(false, false, false),
@JsonProperty("pty")
PTY_ALLOCATED(false, true, true);
private final boolean hasSeparateStreams;
private final boolean hasAnsiEscapes;
private final boolean echoesAllInput;
ShellTtyState(boolean hasSeparateStreams, boolean hasAnsiEscapes, boolean echoesAllInput) {
this.hasSeparateStreams = hasSeparateStreams;
this.hasAnsiEscapes = hasAnsiEscapes;
this.echoesAllInput = echoesAllInput;
}
}

View file

@ -1,9 +1,8 @@
package io.xpipe.core.store;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.xpipe.core.process.CommandBuilder;
import io.xpipe.core.process.ShellControl;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import java.io.InputStream;
@ -37,9 +36,10 @@ public class ConnectionFileSystem implements FileSystem {
@Override
public FileSystem open() throws Exception {
shellControl.start();
if (!shellControl.getShellDialect().getDumbMode().supportsAnyPossibleInteraction()) {
var d = shellControl.getShellDialect().getDumbMode();
if (!d.supportsAnyPossibleInteraction()) {
shellControl.close();
throw new UnsupportedOperationException("System shell does not support file system interaction");
d.throwIfUnsupported();
}
return this;
}

View file

@ -19,6 +19,6 @@ public class EnabledStoreState extends DataStoreState {
@Override
public DataStoreState mergeCopy(DataStoreState newer) {
var n = (EnabledStoreState) newer;
return EnabledStoreState.builder().enabled(n.enabled).build();
return EnabledStoreState.builder().enabled(enabled || n.enabled).build();
}
}

View file

@ -1,67 +0,0 @@
## A new HTTP API
There is now a new HTTP API for the XPipe daemon, which allows you to programmatically manage remote systems. You can find details and an OpenAPI specification at the new API button in the sidebar. The API page contains everything you need to get started, including code samples for various different programming languages.
To start off, you can query connections based on various filters. With the matched connections, you can start remote shell sessions for each one and run arbitrary commands in them. You get the command exit code and output as a response, allowing you to adapt your control flow based on command outputs. Any kind of passwords and other secrets are automatically provided by XPipe when establishing a shell connection. You can also access the file systems via these shell connections to read and write remote files.
There already exists a community made [XPipe API library for python](https://github.com/coandco/python_xpipe_client) and a [python CLI client](https://github.com/coandco/xpipe_cli). These tools allow you to interact with the API more ergonomically and can also serve as an inspiration of what you can do with the new API. If you also built a tool to interact with the XPipe API, you can let me know and I can compile a list of community development projects.
## Service integration
Many systems run a variety of different services such as web services and others. There is now support to detect, forward, and open the services. For example, if you are running a web service on a remote container, you can automatically forward the service port via SSH tunnels, allowing you to access these services from your local machine, e.g. in a web browser. These service tunnels can be toggled at any time. The port forwarding supports specifying a custom local target port and also works for connections with multiple intermediate systems through chained tunnels. For containers, services are automatically detected via their exposed mapped ports. For other systems, you can manually add services via their port.
You can use an unlimited amount of local services and one active tunneled service in the community edition.
## Script rework
The scripting system has been reworked. There have been several issues with it being clunky and not fun to use. The new system allows you to assign each script one of multiple execution types. Based on these execution types, you can make scripts active or inactive with a toggle. If they are active, the scripts will apply in the selected use cases. There currently are these types:
- Init scripts: When enabled, they will automatically run on init in all compatible shells. This is useful for setting things like aliases consistently
- Shell scripts: When enabled, they will be copied over to the target system and put into the PATH. You can then call them in a normal shell session by their name, e.g. `myscript.sh`, also with arguments.
- File scripts: When enabled, you can call them in the file browser with the selected files as arguments. Useful to perform common actions with files
If you have existing scripts, they will have to be manually adjusted by setting their execution types.
## Docker improvements
The docker integration has been updated to support docker contexts. You can use the default context in the community edition, essentially being the same as before as XPipe previously only used the default context. Support for using multiple contexts is included in the professional edition.
There's now support for Windows docker containers running on HyperV.
Note that old docker container connections will be removed as they are incompatible with the new version.
## Proxmox improvements
You can now automatically open the Proxmox dashboard website through the new service integration. This will also work with the service tunneling feature for remote servers.
You can now open VNC sessions to Proxmox VMs.
The Proxmox professional license requirement has been reworked to support one non-enterprise PVE node in the community edition.
## Better connection organization
The toggle to show only running connections will now no longer actually remove the connections internally and instead just not display them. This will reduce git vault updates and is faster in general.
You can now order connections relative to other sibling connections. This ordering will also persist when changing the global order in the top left.
The UI has also been streamlined to make common actions and toggles more easily accessible.
## Other
- The title bar on Windows will now follow the appearance theme
- Several more actions have been added for podman containers
- Support VMs for tunneling
- Searching for connections has been improved to show children as well
- There is now an AppImage portable release
- The welcome screen will now also contain the option to straight up jump to the synchronization settings
- You can now launch xpipe in another data directory with `xpipe open -d "<dir>"`
- Add option to use double clicks to open connections instead of single clicks
- Add support for foot terminal
- Fix rare null pointers and freezes in file browser
- Fix PowerShell remote session file editing not transferring file correctly
- Fix elementary terminal not launching correctly
- Fix windows jumping around when created
- Fix kubernetes not elevating correctly for non-default contexts
- Fix ohmyzsh update notification freezing shell
- Fix file browser icons being broken for links
- The Linux installers now contain application icons from multiple sizes which should increase the icon display quality
- The Linux builds now list socat as a dependency such that the kitty terminal integration will work without issues

View file

@ -1,3 +1,13 @@
## Profiles
You can now create multiple user profiles in the settings menu.
This will create desktop shortcuts that you can use to start XPipe with different profiles active.
## Serial connection support
There is now support to add serial connections.
## Scripting improvements
The scripting system has been reworked in order to make it more intuitive and powerful.
@ -12,6 +22,13 @@ When multiple files are selected, a script is now called only once with all the
## Other
- Rework state information display for proxmox VMs
- Fix terminal exit not working properly in fish
- Fix renaming a connection clearing all state information
- Fix script enabled status being wrong after editing an enabled script
- Fix download move operation failing when moving a directory that already existed in the downloads folder
- Fix some scrollbars are necessarily showing
- Improve error messages when system interaction was disabled for a system
- Don't show git all compatibility warnings on minor version updates
- Enable ZGC on Linux and macOS
- Some small appearance fixes

34
dist/changelogs/11.0_incremental.md vendored Normal file
View file

@ -0,0 +1,34 @@
## Profiles
You can now create multiple user profiles in the settings menu.
This will create desktop shortcuts that you can use to start XPipe with different profiles active.
## Serial connection support
There is now support to add serial connections.
## Scripting improvements
The scripting system has been reworked in order to make it more intuitive and powerful.
The script execution types have been renamed, the documentation has been improved, and a new execution type has been added.
The new runnable execution type will allow you to call a script from the connection hub directly in a dropdown for each connection when the script is active.
This will also replace the current terminal command functionality, which has been removed.
Any file browser scripts are now grouped by the scripts groups they are in, improving the overview when having many file browser scripts.
Furthermore, you can now launch these scripts in the file browser either in the background if they are quiet or in a terminal if they are intended to be interactive.
When multiple files are selected, a script is now called only once with all the selected files as arguments.
## Other
- Rework state information display for proxmox VMs
- Fix terminal exit not working properly in fish
- Fix renaming a connection clearing all state information
- Fix script enabled status being wrong after editing an enabled script
- Fix download move operation failing when moving a directory that already existed in the downloads folder
- Fix some scrollbars are necessarily showing
- Improve error messages when system interaction was disabled for a system
- Don't show git all compatibility warnings on minor version updates
- Enable ZGC on Linux and macOS
- Some small appearance fixes

View file

@ -3,6 +3,7 @@ package io.xpipe.ext.base.action;
import io.xpipe.app.comp.store.StoreViewState;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.ActionProvider;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.util.TerminalLauncher;
import io.xpipe.core.process.ShellStoreState;
@ -17,7 +18,7 @@ import java.util.List;
public class RunScriptActionMenu implements ActionProvider {
@Value
private static class ScriptActionProvider implements ActionProvider {
private static class TerminalRunActionProvider implements ActionProvider {
ScriptHierarchy hierarchy;
@ -41,10 +42,6 @@ public class RunScriptActionMenu implements ActionProvider {
@Override
public LeafDataStoreCallSite<?> getLeafDataStoreCallSite() {
if (!hierarchy.isLeaf()) {
return null;
}
return new LeafDataStoreCallSite<ShellStore>() {
@Override
public Action createAction(DataStoreEntryRef<ShellStore> store) {
@ -53,12 +50,15 @@ public class RunScriptActionMenu implements ActionProvider {
@Override
public ObservableValue<String> getName(DataStoreEntryRef<ShellStore> store) {
return new SimpleStringProperty(hierarchy.getBase().get().getName());
var t = AppPrefs.get().terminalType().getValue();
return AppI18n.observable(
"executeInTerminal",
t != null ? t.toTranslatedString().getValue() : "?");
}
@Override
public String getIcon(DataStoreEntryRef<ShellStore> store) {
return "mdi2p-play-box-multiple-outline";
return "mdi2d-desktop-mac";
}
@Override
@ -67,10 +67,91 @@ public class RunScriptActionMenu implements ActionProvider {
}
};
}
}
@Value
private static class BackgroundRunActionProvider implements ActionProvider {
ScriptHierarchy hierarchy;
@Value
private class Action implements ActionProvider.Action {
DataStoreEntryRef<ShellStore> shellStore;
@Override
public void execute() throws Exception {
try (var sc = shellStore.getStore().control().start()) {
var script = hierarchy.getLeafBase().getStore().assembleScriptChain(sc);
sc.command(script).execute();
}
}
}
@Override
public LeafDataStoreCallSite<?> getLeafDataStoreCallSite() {
return new LeafDataStoreCallSite<ShellStore>() {
@Override
public Action createAction(DataStoreEntryRef<ShellStore> store) {
return new Action(store);
}
@Override
public ObservableValue<String> getName(DataStoreEntryRef<ShellStore> store) {
return AppI18n.observable("executeInBackground");
}
@Override
public String getIcon(DataStoreEntryRef<ShellStore> store) {
return "mdi2f-flip-to-back";
}
@Override
public Class<?> getApplicableClass() {
return ShellStore.class;
}
};
}
}
@Value
private static class ScriptActionProvider implements ActionProvider {
ScriptHierarchy hierarchy;
private BranchDataStoreCallSite<?> getLeafSite() {
return new BranchDataStoreCallSite<ShellStore>() {
@Override
public Class<ShellStore> getApplicableClass() {
return ShellStore.class;
}
@Override
public ObservableValue<String> getName(DataStoreEntryRef<ShellStore> store) {
return new SimpleStringProperty(hierarchy.getBase().get().getName());
}
@Override
public boolean isDynamicallyGenerated() {
return true;
}
@Override
public String getIcon(DataStoreEntryRef<ShellStore> store) {
return "mdi2p-play-box-multiple-outline";
}
@Override
public List<? extends ActionProvider> getChildren(DataStoreEntryRef<ShellStore> store) {
return List.of(new TerminalRunActionProvider(hierarchy), new BackgroundRunActionProvider(hierarchy));
}
};
}
public BranchDataStoreCallSite<?> getBranchDataStoreCallSite() {
if (hierarchy.isLeaf()) {
return null;
return getLeafSite();
}
return new BranchDataStoreCallSite<ShellStore>() {

View file

@ -10,13 +10,11 @@ import io.xpipe.core.process.ShellControl;
import io.xpipe.core.process.ShellDialects;
import io.xpipe.core.store.LocalStore;
import io.xpipe.core.store.ShellStore;
import javafx.beans.value.ObservableValue;
import lombok.Value;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.StringReader;
public class SampleStoreAction implements ActionProvider {
@ -83,23 +81,14 @@ public class SampleStoreAction implements ActionProvider {
sc.executeSimpleStringCommand(sc.getShellDialect().getEchoCommand("hello!", false));
// You can also implement custom handling for more complex commands
try (CommandControl cc = sc.command("ls").start()) {
// Discard stderr
cc.discardErr();
// Read the stdout lines as a stream
BufferedReader reader = new BufferedReader(new InputStreamReader(cc.getStdout(), cc.getCharset()));
// We don't have to close this stream here, that will be automatically done by the command control
// after the try-with block
reader.lines().filter(s -> !s.isBlank()).forEach(s -> {
System.out.println(s);
});
// Waits for command completion and returns exit code
if (cc.getExitCode() != 0) {
// Handle failure
}
}
var lsOut = sc.command("ls").readStdoutOrThrow();
// Read the stdout lines as a stream
BufferedReader reader = new BufferedReader(new StringReader(lsOut));
// We don't have to close this stream here, that will be automatically done by the command control
// after the try-with block
reader.lines().filter(s -> !s.isBlank()).forEach(s -> {
System.out.println(s);
});
// Commands can also be more complex and span multiple lines.
// In this case, XPipe will internally write a command to a script file and then execute the script

View file

@ -7,9 +7,7 @@ import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.util.ScanAlert;
import io.xpipe.core.process.ShellStoreState;
import io.xpipe.core.store.ShellStore;
import javafx.beans.value.ObservableValue;
import lombok.Value;
public class ScanStoreAction implements ActionProvider {
@ -67,7 +65,9 @@ public class ScanStoreAction implements ActionProvider {
@Override
public void execute() {
ScanAlert.showAsync(entry);
if (entry == null || entry.getStore() instanceof ShellStore) {
ScanAlert.showForShellStore(entry);
}
}
}
}

View file

@ -16,12 +16,10 @@ public abstract class ToFileCommandAction implements LeafAction, ApplicationPath
ShellControl sc = model.getFileSystem().getShell().orElseThrow();
for (BrowserEntry entry : entries) {
var command = createCommand(model, entry);
try (var cc = sc.command(command)
var out = sc.command(command)
.withWorkingDirectory(model.getCurrentDirectory().getPath())
.start()) {
cc.discardErr();
FileOpener.openCommandOutput(entry.getFileName(), entry, cc);
}
.readStdoutOrThrow();
FileOpener.openReadOnlyString(out);
}
}

View file

@ -97,7 +97,7 @@ public class DesktopEnvironmentStore extends JacksonizedValue
var scriptFile = base.getStore().createScript(dialect, toExecute);
var launchScriptFile = base.getStore()
.createScript(
dialect, dialect.prepareTerminalInitFileOpenCommand(dialect, null, scriptFile.toString()));
dialect, dialect.prepareTerminalInitFileOpenCommand(dialect, null, scriptFile.toString(), false));
var launchConfig = new ExternalTerminalType.LaunchConfiguration(null, name, name, launchScriptFile, dialect);
base.getStore().runDesktopScript(name, launchCommand.apply(launchConfig));
}

View file

@ -4,10 +4,10 @@ def currentOS = DefaultNativePlatform.currentOperatingSystem;
def platform = null
if (currentOS.isWindows()) {
platform = 'win'
} else if (currentOS.isLinux()) {
platform = 'linux'
} else if (currentOS.isMacOsX()) {
platform = 'mac'
} else {
platform = 'linux'
}
if (System.getProperty ("os.arch") == 'aarch64') {

View file

@ -488,3 +488,15 @@ closeOtherTabs=Luk andre faner
closeAllTabs=Luk alle faner
closeLeftTabs=Luk faner til venstre
closeRightTabs=Luk faner til højre
addSerial=Seriel ...
connect=Forbind
workspaces=Arbejdsområder
manageWorkspaces=Administrer arbejdsområder
addWorkspace=Tilføj arbejdsområde ...
workspaceAdd=Tilføj et nyt arbejdsområde
workspaceAddDescription=Arbejdsområder er forskellige konfigurationer til at køre XPipe. Hvert arbejdsområde har et datakatalog, hvor alle data gemmes lokalt. Det omfatter forbindelsesdata, indstillinger og meget mere.\n\nHvis du bruger synkroniseringsfunktionen, kan du også vælge at synkronisere hvert arbejdsområde med et forskelligt git-repository.
workspaceName=Navn på arbejdsområde
workspaceNameDescription=Visningsnavnet på arbejdsområdet
workspacePath=Sti til arbejdsområde
workspacePathDescription=Placeringen af arbejdsområdets datakatalog
workspaceCreationAlertTitle=Oprettelse af arbejdsområde

View file

@ -482,3 +482,15 @@ closeOtherTabs=Andere Tabs schließen
closeAllTabs=Alle Registerkarten schließen
closeLeftTabs=Tabs nach links schließen
closeRightTabs=Tabs nach rechts schließen
addSerial=Serielle ...
connect=Verbinden
workspaces=Arbeitsbereiche
manageWorkspaces=Arbeitsbereiche verwalten
addWorkspace=Arbeitsbereich hinzufügen ...
workspaceAdd=Einen neuen Arbeitsbereich hinzufügen
workspaceAddDescription=Arbeitsbereiche sind unterschiedliche Konfigurationen für die Ausführung von XPipe. Jeder Arbeitsbereich hat ein Datenverzeichnis, in dem alle Daten lokal gespeichert werden. Dazu gehören Verbindungsdaten, Einstellungen und mehr.\n\nWenn du die Synchronisierungsfunktion verwendest, kannst du auch wählen, ob du jeden Arbeitsbereich mit einem anderen Git-Repository synchronisieren möchtest.
workspaceName=Name des Arbeitsbereichs
workspaceNameDescription=Der Anzeigename des Arbeitsbereichs
workspacePath=Pfad zum Arbeitsbereich
workspacePathDescription=Der Ort des Datenverzeichnisses des Arbeitsbereichs
workspaceCreationAlertTitle=Arbeitsbereich erstellen

View file

@ -486,3 +486,15 @@ closeOtherTabs=Close other tabs
closeAllTabs=Close all tabs
closeLeftTabs=Close tabs to the left
closeRightTabs=Close tabs to the right
addSerial=Serial ...
connect=Connect
workspaces=Workspaces
manageWorkspaces=Manage workspaces
addWorkspace=Add workspace ...
workspaceAdd=Add a new workspace
workspaceAddDescription=Workspaces are distinct configurations for running XPipe. Every workspace has a data directory where all data is stored locally. This includes connection data, settings, and more.\n\nIf you use the synchronization feature, you can also choose to synchronize each workspace with a different git repository.
workspaceName=Workspace name
workspaceNameDescription=The display name of the workspace
workspacePath=Workspace path
workspacePathDescription=The location of the workspace data directory
workspaceCreationAlertTitle=Workspace creation

View file

@ -469,3 +469,15 @@ closeOtherTabs=Cerrar otras pestañas
closeAllTabs=Cerrar todas las pestañas
closeLeftTabs=Cerrar pestañas a la izquierda
closeRightTabs=Cerrar pestañas a la derecha
addSerial=Serie ...
connect=Conecta
workspaces=Espacios de trabajo
manageWorkspaces=Gestionar espacios de trabajo
addWorkspace=Añadir espacio de trabajo ...
workspaceAdd=Añadir un nuevo espacio de trabajo
workspaceAddDescription=Los espacios de trabajo son configuraciones distintas para ejecutar XPipe. Cada espacio de trabajo tiene un directorio de datos donde se almacenan localmente todos los datos. Esto incluye datos de conexión, configuraciones y más.\n\nSi utilizas la función de sincronización, también puedes elegir sincronizar cada espacio de trabajo con un repositorio git diferente.
workspaceName=Nombre del espacio de trabajo
workspaceNameDescription=El nombre para mostrar del espacio de trabajo
workspacePath=Ruta del espacio de trabajo
workspacePathDescription=La ubicación del directorio de datos del espacio de trabajo
workspaceCreationAlertTitle=Creación de espacios de trabajo

View file

@ -469,3 +469,15 @@ closeOtherTabs=Fermer d'autres onglets
closeAllTabs=Fermer tous les onglets
closeLeftTabs=Ferme les onglets à gauche
closeRightTabs=Ferme les onglets à droite
addSerial=Série ...
connect=Connecter
workspaces=Espaces de travail
manageWorkspaces=Gérer les espaces de travail
addWorkspace=Ajouter un espace de travail ...
workspaceAdd=Ajouter un nouvel espace de travail
workspaceAddDescription=Les espaces de travail sont des configurations distinctes pour l'exécution de XPipe. Chaque espace de travail possède un répertoire de données où toutes les données sont stockées localement. Cela comprend les données de connexion, les paramètres, et plus encore.\n\nSi tu utilises la fonctionnalité de synchronisation, tu peux aussi choisir de synchroniser chaque espace de travail avec un dépôt git différent.
workspaceName=Nom de l'espace de travail
workspaceNameDescription=Le nom d'affichage de l'espace de travail
workspacePath=Chemin d'accès à l'espace de travail
workspacePathDescription=L'emplacement du répertoire de données de l'espace de travail
workspaceCreationAlertTitle=Création d'un espace de travail

View file

@ -469,3 +469,15 @@ closeOtherTabs=Chiudere altre schede
closeAllTabs=Chiudi tutte le schede
closeLeftTabs=Chiudere le schede a sinistra
closeRightTabs=Chiudere le schede a destra
addSerial=Seriale ...
connect=Collegare
workspaces=Spazi di lavoro
manageWorkspaces=Gestire gli spazi di lavoro
addWorkspace=Aggiungi spazio di lavoro ...
workspaceAdd=Aggiungere un nuovo spazio di lavoro
workspaceAddDescription=Gli spazi di lavoro sono configurazioni distinte per l'esecuzione di XPipe. Ogni workspace ha una directory di dati in cui vengono memorizzati tutti i dati a livello locale. Questi includono i dati di connessione, le impostazioni e altro ancora.\n\nSe utilizzi la funzione di sincronizzazione, puoi anche scegliere di sincronizzare ogni workspace con un repository git diverso.
workspaceName=Nome dello spazio di lavoro
workspaceNameDescription=Il nome di visualizzazione dell'area di lavoro
workspacePath=Percorso dello spazio di lavoro
workspacePathDescription=La posizione della directory dei dati dell'area di lavoro
workspaceCreationAlertTitle=Creazione di uno spazio di lavoro

View file

@ -469,3 +469,15 @@ closeOtherTabs=他のタブを閉じる
closeAllTabs=すべてのタブを閉じる
closeLeftTabs=タブを左に閉じる
closeRightTabs=タブを右に閉じる
addSerial=シリアル ...
connect=接続する
workspaces=ワークスペース
manageWorkspaces=ワークスペースを管理する
addWorkspace=ワークスペースを追加する
workspaceAdd=新しいワークスペースを追加する
workspaceAddDescription=ワークスペースは、XPipeを実行するための個別の設定である。すべてのワークスペースには、すべてのデータがローカルに保存されるデータ・ディレクトリがある。これには、接続データや設定などが含まれる。\n\n同期機能を使えば、ワークスペースごとに異なるgitリポジトリと同期させることもできる。
workspaceName=ワークスペース名
workspaceNameDescription=ワークスペースの表示名
workspacePath=ワークスペースのパス
workspacePathDescription=ワークスペースのデータディレクトリの場所
workspaceCreationAlertTitle=ワークスペースの作成

View file

@ -469,3 +469,15 @@ closeOtherTabs=Andere tabbladen sluiten
closeAllTabs=Alle tabbladen sluiten
closeLeftTabs=Tabbladen naar links sluiten
closeRightTabs=Tabbladen naar rechts sluiten
addSerial=Serieel ...
connect=Maak verbinding met
workspaces=Werkruimten
manageWorkspaces=Werkruimten beheren
addWorkspace=Werkruimte toevoegen ...
workspaceAdd=Een nieuwe werkruimte toevoegen
workspaceAddDescription=Workspaces zijn verschillende configuraties voor het uitvoeren van XPipe. Elke workspace heeft een datamap waar alle gegevens lokaal worden opgeslagen. Dit omvat verbindingsgegevens, instellingen en meer.\n\nAls je de synchronisatiefunctie gebruikt, kun je er ook voor kiezen om elke workspace met een andere git repository te synchroniseren.
workspaceName=Naam werkruimte
workspaceNameDescription=De weergavenaam van de werkruimte
workspacePath=Werkruimte pad
workspacePathDescription=De locatie van de gegevensmap van de werkruimte
workspaceCreationAlertTitle=Werkruimte maken

View file

@ -469,3 +469,15 @@ closeOtherTabs=Fecha outros separadores
closeAllTabs=Fecha todos os separadores
closeLeftTabs=Fecha os separadores à esquerda
closeRightTabs=Fecha os separadores à direita
addSerial=Série ...
connect=Liga-te
workspaces=Espaços de trabalho
manageWorkspaces=Gere espaços de trabalho
addWorkspace=Adiciona um espaço de trabalho ...
workspaceAdd=Adiciona um novo espaço de trabalho
workspaceAddDescription=Os espaços de trabalho são configurações distintas para executar o XPipe. Cada espaço de trabalho tem um diretório de dados onde todos os dados são armazenados localmente. Isto inclui dados de ligação, definições e muito mais.\n\nSe utilizares a funcionalidade de sincronização, também podes optar por sincronizar cada espaço de trabalho com um repositório git diferente.
workspaceName=Nome do espaço de trabalho
workspaceNameDescription=O nome de apresentação do espaço de trabalho
workspacePath=Caminho do espaço de trabalho
workspacePathDescription=A localização do diretório de dados do espaço de trabalho
workspaceCreationAlertTitle=Criação de espaço de trabalho

View file

@ -469,3 +469,15 @@ closeOtherTabs=Закрыть другие вкладки
closeAllTabs=Закрыть все вкладки
closeLeftTabs=Закрыть вкладки слева
closeRightTabs=Закрывать вкладки справа
addSerial=Серийный ...
connect=Connect
workspaces=Рабочие пространства
manageWorkspaces=Управляй рабочими пространствами
addWorkspace=Добавь рабочее пространство ...
workspaceAdd=Добавьте новое рабочее пространство
workspaceAddDescription=Рабочие пространства - это отдельные конфигурации для запуска XPipe. В каждом рабочем пространстве есть каталог данных, где все данные хранятся локально. Сюда входят данные о соединениях, настройки и многое другое.\n\nЕсли ты используешь функцию синхронизации, ты также можешь выбрать синхронизацию каждого рабочего пространства с отдельным git-репозиторием.
workspaceName=Имя рабочей области
workspaceNameDescription=Отображаемое имя рабочей области
workspacePath=Путь к рабочему пространству
workspacePathDescription=Расположение каталога данных рабочей области
workspaceCreationAlertTitle=Создание рабочего пространства

View file

@ -470,3 +470,15 @@ closeOtherTabs=Diğer sekmeleri kapatın
closeAllTabs=Tüm sekmeleri kapat
closeLeftTabs=Sekmeleri sola doğru kapatın
closeRightTabs=Sekmeleri sağa doğru kapatın
addSerial=Seri ...
connect=Bağlan
workspaces=Çalışma Alanları
manageWorkspaces=Çalışma alanlarını yönetme
addWorkspace=Çalışma alanı ekle ...
workspaceAdd=Yeni bir çalışma alanı ekleme
workspaceAddDescription=Çalışma alanları XPipe'ı çalıştırmak için farklı konfigürasyonlardır. Her çalışma alanı, tüm verilerin yerel olarak depolandığı bir veri dizinine sahiptir. Buna bağlantı verileri, ayarlar ve daha fazlası dahildir.\n\nSenkronizasyon özelliğini kullanırsanız, her çalışma alanını farklı bir git deposu ile senkronize etmeyi de seçebilirsiniz.
workspaceName=Çalışma alanı adı
workspaceNameDescription=Çalışma alanının görünen adı
workspacePath=Çalışma alanı yolu
workspacePathDescription=Çalışma alanı veri dizininin konumu
workspaceCreationAlertTitle=Çalışma alanı oluşturma

View file

@ -469,3 +469,15 @@ closeOtherTabs=关闭其他标签页
closeAllTabs=关闭所有标签页
closeLeftTabs=向左关闭标签
closeRightTabs=向右关闭标签页
addSerial=串行 ...
connect=连接
workspaces=工作空间
manageWorkspaces=管理工作区
addWorkspace=添加工作区 ...
workspaceAdd=添加新工作区
workspaceAddDescription=工作区是运行 XPipe 的独特配置。每个工作区都有一个数据目录,本地存储所有数据。其中包括连接数据、设置等。\n\n如果使用同步功能您还可以选择将每个工作区与不同的 git 仓库同步。
workspaceName=工作区名称
workspaceNameDescription=工作区的显示名称
workspacePath=工作区路径
workspacePathDescription=工作区数据目录的位置
workspaceCreationAlertTitle=创建工作区

View file

@ -1,3 +1,13 @@
wsl=Windows Subsystem for Linux
docker=Docker
proxmox=Proxmox PVE
proxmox=Proxmox PVE
xonXoff=XON/XOFF
rtsCts=RTS/CTS
dsrDtr=DSR/DTR
putty=PuTTY
screen=Screen
minicom=Minicom
odd=Odd
even=Even
mark=Mark
space=Space

View file

@ -359,3 +359,19 @@ k8sPodActions=Pod-handlinger
openVnc=Sæt VNC op
commandGroup.displayName=Kommandogruppe
commandGroup.displayDescription=Grupper tilgængelige kommandoer for et system
serial.displayName=Seriel forbindelse
serial.displayDescription=Åbn en seriel forbindelse i en terminal
serialPort=Seriel port
serialPortDescription=Den serielle port / enhed, der skal forbindes til
baudRate=Baud-hastighed
dataBits=Data-bits
stopBits=Stop-bits
parity=Paritet
flowControlWindow=Flow-kontrol
serialImplementation=Seriel implementering
serialImplementationDescription=Det værktøj, der skal bruges til at oprette forbindelse til den serielle port
serialHost=Vært
serialHostDescription=Systemet til at få adgang til den serielle port på
serialPortConfiguration=Konfiguration af seriel port
serialPortConfigurationDescription=Konfigurationsparametre for den tilsluttede serielle enhed
serialInformation=Seriel information

View file

@ -337,3 +337,19 @@ k8sPodActions=Pod-Aktionen
openVnc=VNC einrichten
commandGroup.displayName=Befehlsgruppe
commandGroup.displayDescription=Verfügbare Befehle für ein System gruppieren
serial.displayName=Serielle Verbindung
serial.displayDescription=Eine serielle Verbindung in einem Terminal öffnen
serialPort=Serieller Anschluss
serialPortDescription=Der serielle Anschluss/das Gerät, mit dem eine Verbindung hergestellt werden soll
baudRate=Baudrate
dataBits=Datenbits
stopBits=Stoppbits
parity=Parität
flowControlWindow=Flusskontrolle
serialImplementation=Serielle Implementierung
serialImplementationDescription=Das Tool für die Verbindung mit der seriellen Schnittstelle
serialHost=Host
serialHostDescription=Das System für den Zugriff auf die serielle Schnittstelle auf
serialPortConfiguration=Konfiguration der seriellen Schnittstelle
serialPortConfigurationDescription=Konfigurationsparameter des angeschlossenen seriellen Geräts
serialInformation=Serielle Informationen

View file

@ -334,4 +334,20 @@ dockerContextActions=Context actions
k8sPodActions=Pod actions
openVnc=Set up VNC
commandGroup.displayName=Command group
commandGroup.displayDescription=Group available commands for a system
commandGroup.displayDescription=Group available commands for a system
serial.displayName=Serial connection
serial.displayDescription=Open a serial connection in a terminal
serialPort=Serial port
serialPortDescription=The serial port / device to connect to
baudRate=Baud rate
dataBits=Data bits
stopBits=Stop bits
parity=Parity
flowControlWindow=Flow control
serialImplementation=Serial implementation
serialImplementationDescription=The tool to use to connect to the serial port
serialHost=Host
serialHostDescription=The system to access the serial port on
serialPortConfiguration=Serial port configuration
serialPortConfigurationDescription=Configuration parameters of the connected serial device
serialInformation=Serial information

View file

@ -333,3 +333,19 @@ k8sPodActions=Acciones del pod
openVnc=Configurar VNC
commandGroup.displayName=Grupo de comandos
commandGroup.displayDescription=Agrupa los comandos disponibles para un sistema
serial.displayName=Conexión en serie
serial.displayDescription=Abrir una conexión serie en un terminal
serialPort=Puerto serie
serialPortDescription=El puerto serie / dispositivo al que conectarse
baudRate=Velocidad en baudios
dataBits=Bits de datos
stopBits=Bits de parada
parity=Paridad
flowControlWindow=Control de flujo
serialImplementation=Aplicación en serie
serialImplementationDescription=La herramienta que hay que utilizar para conectarse al puerto serie
serialHost=Anfitrión
serialHostDescription=El sistema para acceder al puerto serie en
serialPortConfiguration=Configuración del puerto serie
serialPortConfigurationDescription=Parámetros de configuración del dispositivo serie conectado
serialInformation=Información en serie

View file

@ -333,3 +333,19 @@ k8sPodActions=Actions de pods
openVnc=Configurer VNC
commandGroup.displayName=Groupe de commande
commandGroup.displayDescription=Groupe de commandes disponibles pour un système
serial.displayName=Connexion série
serial.displayDescription=Ouvrir une connexion série dans un terminal
serialPort=Port série
serialPortDescription=Le port série / le périphérique à connecter
baudRate=Taux de bauds
dataBits=Bits de données
stopBits=Bits d'arrêt
parity=Parité
flowControlWindow=Contrôle de flux
serialImplementation=Implémentation en série
serialImplementationDescription=L'outil à utiliser pour se connecter au port série
serialHost=Hôte
serialHostDescription=Le système pour accéder au port série sur
serialPortConfiguration=Configuration du port série
serialPortConfigurationDescription=Paramètres de configuration de l'appareil en série connecté
serialInformation=Informations en série

View file

@ -333,3 +333,19 @@ k8sPodActions=Azioni del pod
openVnc=Configurare VNC
commandGroup.displayName=Gruppo di comando
commandGroup.displayDescription=Gruppo di comandi disponibili per un sistema
serial.displayName=Connessione seriale
serial.displayDescription=Aprire una connessione seriale in un terminale
serialPort=Porta seriale
serialPortDescription=La porta seriale/dispositivo a cui connettersi
baudRate=Velocità di trasmissione
dataBits=Bit di dati
stopBits=Bit di stop
parity=Parità
flowControlWindow=Controllo del flusso
serialImplementation=Implementazione seriale
serialImplementationDescription=Lo strumento da utilizzare per collegarsi alla porta seriale
serialHost=Ospite
serialHostDescription=Il sistema per accedere alla porta seriale su
serialPortConfiguration=Configurazione della porta seriale
serialPortConfigurationDescription=Parametri di configurazione del dispositivo seriale collegato
serialInformation=Informazioni di serie

View file

@ -333,3 +333,19 @@ k8sPodActions=ポッドアクション
openVnc=VNCを設定する
commandGroup.displayName=コマンドグループ
commandGroup.displayDescription=システムで使用可能なコマンドをグループ化する
serial.displayName=シリアル接続
serial.displayDescription=ターミナルでシリアル接続を開く
serialPort=シリアルポート
serialPortDescription=接続するシリアルポート/デバイス
baudRate=ボーレート
dataBits=データビット
stopBits=ストップビット
parity=パリティ
flowControlWindow=フロー制御
serialImplementation=シリアルの実装
serialImplementationDescription=シリアルポートに接続するために使用するツール
serialHost=ホスト
serialHostDescription=のシリアルポートにアクセスするシステム
serialPortConfiguration=シリアルポートの設定
serialPortConfigurationDescription=接続されたシリアル・デバイスの設定パラメーター
serialInformation=シリアル情報

View file

@ -333,3 +333,19 @@ k8sPodActions=Pod acties
openVnc=VNC instellen
commandGroup.displayName=Opdrachtgroep
commandGroup.displayDescription=Groep beschikbare commando's voor een systeem
serial.displayName=Seriële verbinding
serial.displayDescription=Een seriële verbinding in een terminal openen
serialPort=Seriële poort
serialPortDescription=De seriële poort / het apparaat waarmee verbinding moet worden gemaakt
baudRate=Baudrate
dataBits=Gegevensbits
stopBits=Stopbits
parity=Pariteit
flowControlWindow=Debietregeling
serialImplementation=Seriële implementatie
serialImplementationDescription=Het gereedschap om verbinding te maken met de seriële poort
serialHost=Host
serialHostDescription=Het systeem om toegang te krijgen tot de seriële poort op
serialPortConfiguration=Seriële poort configuratie
serialPortConfigurationDescription=Configuratieparameters van het aangesloten seriële apparaat
serialInformation=Seriële informatie

View file

@ -333,3 +333,19 @@ k8sPodActions=Acções de pod
openVnc=Configura o VNC
commandGroup.displayName=Grupo de comandos
commandGroup.displayDescription=Agrupa os comandos disponíveis para um sistema
serial.displayName=Ligação em série
serial.displayDescription=Abre uma ligação de série num terminal
serialPort=Porta de série
serialPortDescription=A porta de série / dispositivo a ligar
baudRate=Taxa de transmissão
dataBits=Bits de dados
stopBits=Bits de paragem
parity=Paridade
flowControlWindow=Controlo de fluxo
serialImplementation=Implementação em série
serialImplementationDescription=A ferramenta a utilizar para ligar à porta série
serialHost=Apresenta
serialHostDescription=O sistema para aceder à porta série em
serialPortConfiguration=Configuração da porta de série
serialPortConfigurationDescription=Parâmetros de configuração do dispositivo de série ligado
serialInformation=Informação de série

View file

@ -333,3 +333,19 @@ k8sPodActions=Действия в капсуле
openVnc=Настройте VNC
commandGroup.displayName=Группа команд
commandGroup.displayDescription=Группа доступных команд для системы
serial.displayName=Последовательное соединение
serial.displayDescription=Открыть последовательное соединение в терминале
serialPort=Последовательный порт
serialPortDescription=Последовательный порт/устройство, к которому нужно подключиться
baudRate=Скорость передачи данных
dataBits=Биты данных
stopBits=Стоп-биты
parity=Четность
flowControlWindow=Контроль потока
serialImplementation=Последовательная реализация
serialImplementationDescription=Инструмент, который нужно использовать для подключения к последовательному порту
serialHost=Хост
serialHostDescription=Система для доступа к последовательному порту на
serialPortConfiguration=Конфигурация последовательного порта
serialPortConfigurationDescription=Параметры конфигурации подключенного последовательного устройства
serialInformation=Серийная информация

View file

@ -333,3 +333,19 @@ k8sPodActions=Pod eylemleri
openVnc=VNC'yi ayarlama
commandGroup.displayName=Komuta grubu
commandGroup.displayDescription=Bir sistem için mevcut komutları gruplama
serial.displayName=Seri bağlantı
serial.displayDescription=Terminalde bir seri bağlantıın
serialPort=Seri bağlantı noktası
serialPortDescription=Bağlanılacak seri port / cihaz
baudRate=Baud hızı
dataBits=Veri bitleri
stopBits=Dur bitleri
parity=Parite
flowControlWindow=Akış kontrolü
serialImplementation=Seri uygulama
serialImplementationDescription=Seri porta bağlanmak için kullanılacak araç
serialHost=Ev sahibi
serialHostDescription=Seri porta erişmek için sistem
serialPortConfiguration=Seri bağlantı noktası yapılandırması
serialPortConfigurationDescription=Bağlı seri cihazın konfigürasyon parametreleri
serialInformation=Seri bilgileri

View file

@ -333,3 +333,19 @@ k8sPodActions=Pod 操作
openVnc=设置 VNC
commandGroup.displayName=命令组
commandGroup.displayDescription=系统可用命令组
serial.displayName=串行连接
serial.displayDescription=在终端中打开串行连接
serialPort=串行端口
serialPortDescription=要连接的串行端口/设备
baudRate=波特率
dataBits=数据位
stopBits=停止位
parity=奇偶校验
flowControlWindow=流量控制
serialImplementation=串行实施
serialImplementationDescription=用于连接串行端口的工具
serialHost=主机
serialHostDescription=访问串行端口的系统
serialPortConfiguration=串行端口配置
serialPortConfigurationDescription=所连接串行设备的配置参数
serialInformation=序列信息

View file

@ -0,0 +1,10 @@
# Implementeringer
XPipe uddelegerer den serielle håndtering til eksterne værktøjer.
Der er flere tilgængelige værktøjer, som XPipe kan uddelegere til, hver med deres egne fordele og ulemper.
For at bruge dem kræves det, at de er tilgængelige på værtssystemet.
De fleste muligheder burde være understøttet af alle værktøjer, men nogle mere eksotiske muligheder er det måske ikke.
Før der oprettes forbindelse, kontrollerer XPipe, at det valgte værktøj er installeret og understøtter alle konfigurerede muligheder.
Hvis denne kontrol er vellykket, starter det valgte værktøj.

View file

@ -0,0 +1,10 @@
# Implementierungen
XPipe delegiert die serielle Verarbeitung an externe Tools.
Es gibt mehrere Tools, an die XPipe delegieren kann, jedes mit seinen eigenen Vor- und Nachteilen.
Um sie zu nutzen, müssen sie auf dem Hostsystem verfügbar sein.
Die meisten Optionen sollten von allen Tools unterstützt werden, aber einige exotischere Optionen sind es vielleicht nicht.
Bevor eine Verbindung hergestellt wird, prüft XPipe, ob das ausgewählte Tool installiert ist und alle konfigurierten Optionen unterstützt.
Wenn diese Prüfung erfolgreich ist, wird das ausgewählte Tool gestartet.

Some files were not shown because too many files have changed in this diff Show more