mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-21 15:10:23 +00:00
Rework process streams
This commit is contained in:
parent
a26917b500
commit
58ef068842
135 changed files with 1444 additions and 627 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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));
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 -> {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -9,5 +9,6 @@ public enum DataStoreCreationCategory {
|
|||
TUNNEL,
|
||||
SCRIPT,
|
||||
CLUSTER,
|
||||
DESKTOP
|
||||
DESKTOP,
|
||||
SERIAL
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -16,5 +16,7 @@ public enum DataStoreUsageCategory {
|
|||
@JsonProperty("desktop")
|
||||
DESKTOP,
|
||||
@JsonProperty("group")
|
||||
GROUP;
|
||||
GROUP,
|
||||
@JsonProperty("serial")
|
||||
SERIAL;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 -> {
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
26
app/src/main/java/io/xpipe/app/prefs/WorkspacesCategory.java
Normal file
26
app/src/main/java/io/xpipe/app/prefs/WorkspacesCategory.java
Normal 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();
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -18,4 +18,4 @@
|
|||
|
||||
.options-comp .long-description {
|
||||
-fx-padding: 0 6 0 6;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -101,7 +101,7 @@
|
|||
-fx-border-color: -color-neutral-emphasis;
|
||||
}
|
||||
|
||||
.text {
|
||||
* {
|
||||
-fx-font-smoothing-type: gray;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
12
build.gradle
12
build.gradle
|
@ -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()) {
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
package io.xpipe.core.process;
|
||||
|
||||
public interface CommandFeedbackPredicate {
|
||||
|
||||
boolean test(CommandBuilder command);
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
package io.xpipe.core.process;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
@Value
|
||||
public class ShellProperties {
|
||||
|
||||
ShellDialect dialect;
|
||||
boolean ansiEscapes;
|
||||
}
|
25
core/src/main/java/io/xpipe/core/process/ShellTtyState.java
Normal file
25
core/src/main/java/io/xpipe/core/process/ShellTtyState.java
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
67
dist/changelogs/10.3.md
vendored
67
dist/changelogs/10.3.md
vendored
|
@ -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
|
|
@ -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
34
dist/changelogs/11.0_incremental.md
vendored
Normal 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
|
|
@ -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>() {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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') {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -469,3 +469,15 @@ closeOtherTabs=他のタブを閉じる
|
|||
closeAllTabs=すべてのタブを閉じる
|
||||
closeLeftTabs=タブを左に閉じる
|
||||
closeRightTabs=タブを右に閉じる
|
||||
addSerial=シリアル ...
|
||||
connect=接続する
|
||||
workspaces=ワークスペース
|
||||
manageWorkspaces=ワークスペースを管理する
|
||||
addWorkspace=ワークスペースを追加する
|
||||
workspaceAdd=新しいワークスペースを追加する
|
||||
workspaceAddDescription=ワークスペースは、XPipeを実行するための個別の設定である。すべてのワークスペースには、すべてのデータがローカルに保存されるデータ・ディレクトリがある。これには、接続データや設定などが含まれる。\n\n同期機能を使えば、ワークスペースごとに異なるgitリポジトリと同期させることもできる。
|
||||
workspaceName=ワークスペース名
|
||||
workspaceNameDescription=ワークスペースの表示名
|
||||
workspacePath=ワークスペースのパス
|
||||
workspacePathDescription=ワークスペースのデータディレクトリの場所
|
||||
workspaceCreationAlertTitle=ワークスペースの作成
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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=Создание рабочего пространства
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -469,3 +469,15 @@ closeOtherTabs=关闭其他标签页
|
|||
closeAllTabs=关闭所有标签页
|
||||
closeLeftTabs=向左关闭标签
|
||||
closeRightTabs=向右关闭标签页
|
||||
addSerial=串行 ...
|
||||
connect=连接
|
||||
workspaces=工作空间
|
||||
manageWorkspaces=管理工作区
|
||||
addWorkspace=添加工作区 ...
|
||||
workspaceAdd=添加新工作区
|
||||
workspaceAddDescription=工作区是运行 XPipe 的独特配置。每个工作区都有一个数据目录,本地存储所有数据。其中包括连接数据、设置等。\n\n如果使用同步功能,您还可以选择将每个工作区与不同的 git 仓库同步。
|
||||
workspaceName=工作区名称
|
||||
workspaceNameDescription=工作区的显示名称
|
||||
workspacePath=工作区路径
|
||||
workspacePathDescription=工作区数据目录的位置
|
||||
workspaceCreationAlertTitle=创建工作区
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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=シリアル情報
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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=Серийная информация
|
||||
|
|
|
@ -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ı açı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
|
||||
|
|
|
@ -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=序列信息
|
||||
|
|
10
lang/proc/texts/serialImplementation_da.md
Normal file
10
lang/proc/texts/serialImplementation_da.md
Normal 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.
|
||||
|
10
lang/proc/texts/serialImplementation_de.md
Normal file
10
lang/proc/texts/serialImplementation_de.md
Normal 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
Loading…
Reference in a new issue