mirror of
https://github.com/xpipe-io/xpipe.git
synced 2025-04-19 18:53:40 +00:00
Compare commits
91 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a49d1baf87 | ||
![]() |
fe8d8fe383 | ||
![]() |
ab71d178f3 | ||
![]() |
9318a24120 | ||
![]() |
a69ecdc94c | ||
![]() |
937d59a27a | ||
![]() |
f648d63f4b | ||
![]() |
881452ccd0 | ||
![]() |
c7bde460af | ||
![]() |
12fe24a695 | ||
![]() |
3c13cf5e2a | ||
![]() |
bc0e14a332 | ||
![]() |
10dab33ed4 | ||
![]() |
c67411815e | ||
![]() |
74bedf630c | ||
![]() |
93413c3da1 | ||
![]() |
b39c74b4ff | ||
![]() |
474d201708 | ||
![]() |
5c6acc50e4 | ||
![]() |
0c0d91b67d | ||
![]() |
894b93e3c2 | ||
![]() |
8c516c043c | ||
![]() |
9dc368ae7b | ||
![]() |
f5543beb71 | ||
![]() |
8c51eac399 | ||
![]() |
d4a713f29b | ||
![]() |
c738331acd | ||
![]() |
25fc3ca0c1 | ||
![]() |
e1ebd24c04 | ||
![]() |
96fb6b579b | ||
![]() |
a01756562d | ||
![]() |
ad0e628b60 | ||
![]() |
a7f33dd0d2 | ||
![]() |
5b6dadedfe | ||
![]() |
45ef2eda8c | ||
![]() |
b4d1a9e68b | ||
![]() |
086237f965 | ||
![]() |
0417ce635e | ||
![]() |
fa02ee1bc2 | ||
![]() |
7e2663c6ea | ||
![]() |
39e6e66b16 | ||
![]() |
7b9afaef08 | ||
![]() |
a130bcfa91 | ||
![]() |
6084fb8d4d | ||
![]() |
72a5c67aab | ||
![]() |
e0bdf3f52d | ||
![]() |
e4b01ccf0b | ||
![]() |
516cfced4a | ||
![]() |
40e2015780 | ||
![]() |
fe632cd474 | ||
![]() |
8ee9c25f5f | ||
![]() |
e1d0642557 | ||
![]() |
6f29cfd637 | ||
![]() |
4f15a39280 | ||
![]() |
10c8ba62e7 | ||
![]() |
28dd386752 | ||
![]() |
ca1559937b | ||
![]() |
62ef05f707 | ||
![]() |
e78a791c06 | ||
![]() |
72f2decd75 | ||
![]() |
d93eacdb9b | ||
![]() |
795a3dde7c | ||
![]() |
4ecc55443a | ||
![]() |
51c8fff9bd | ||
![]() |
25e2ddd2a3 | ||
![]() |
5473474334 | ||
![]() |
c94e270eee | ||
![]() |
8850a78a36 | ||
![]() |
bffe75a540 | ||
![]() |
484028f22f | ||
![]() |
b2ac3c1fba | ||
![]() |
12c414eecc | ||
![]() |
c42b6d8439 | ||
![]() |
05d93c68ee | ||
![]() |
ec3b95c11b | ||
![]() |
e69da5d5b6 | ||
![]() |
c265b6b87d | ||
![]() |
9844cadbdd | ||
![]() |
1c42605650 | ||
![]() |
5b454cd8cd | ||
![]() |
d7cb5967c6 | ||
![]() |
a98425f100 | ||
![]() |
793ca373aa | ||
![]() |
b0a7f9d17e | ||
![]() |
55e7c65462 | ||
![]() |
c57000acee | ||
![]() |
ff5941249d | ||
![]() |
48450490c3 | ||
![]() |
6cc46ed08f | ||
![]() |
03d222e5f5 | ||
![]() |
b2efb8ddfa |
104 changed files with 1560 additions and 524 deletions
|
@ -2,6 +2,6 @@
|
|||
|
||||
Due to its nature, XPipe has to handle a lot of sensitive information. Therefore, the security, integrity, and privacy of your data has topmost priority.
|
||||
|
||||
General information about the security approach of the XPipe application can be found on the website at https://xpipe.io/features#security. If you're interested in security implementation details, you can find them at https://docs.xpipe.io/security.
|
||||
More information about the security approach of the XPipe application can be found on the documentation website at https://docs.xpipe.io/reference/security.
|
||||
|
||||
You can report security vulnerabilities in this GitHub repository in a confidential manner. We will get back to you as soon as possible if you do.
|
||||
|
|
|
@ -48,7 +48,7 @@ dependencies {
|
|||
api 'com.vladsch.flexmark:flexmark-ext-yaml-front-matter:0.64.8'
|
||||
api 'com.vladsch.flexmark:flexmark-ext-toc:0.64.8'
|
||||
|
||||
api("com.github.weisj:jsvg:1.7.0")
|
||||
api("com.github.weisj:jsvg:1.7.1")
|
||||
api files("$rootDir/gradle/gradle_scripts/markdowngenerator-1.3.1.1.jar")
|
||||
api files("$rootDir/gradle/gradle_scripts/vernacular-1.16.jar")
|
||||
api 'org.bouncycastle:bcprov-jdk18on:1.80'
|
||||
|
|
|
@ -24,6 +24,9 @@ public class BrowserAbstractSessionModel<T extends BrowserSessionTab> {
|
|||
|
||||
public void closeAsync(BrowserSessionTab e) {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
// This is a bit ugly
|
||||
// If we die on tab init, wait a bit with closing to avoid removal while it is still being inited/added
|
||||
ThreadHelper.sleep(100);
|
||||
closeSync(e);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -174,6 +174,7 @@ public class BrowserFullSessionComp extends SimpleComp {
|
|||
private StackComp createSplitStack(SimpleDoubleProperty rightSplit, BrowserSessionTabsComp tabs) {
|
||||
var cache = new HashMap<BrowserSessionTab, Region>();
|
||||
var splitStack = new StackComp(List.of());
|
||||
splitStack.apply(struc -> struc.get().setPickOnBounds(false));
|
||||
splitStack.apply(struc -> {
|
||||
model.getEffectiveRightTab().subscribe((newValue) -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
|
|
|
@ -156,8 +156,8 @@ public final class BrowserFileListComp extends SimpleComp {
|
|||
var os = fileList.getFileSystemModel()
|
||||
.getFileSystem()
|
||||
.getShell()
|
||||
.orElseThrow()
|
||||
.getOsType();
|
||||
.map(shellControl -> shellControl.getOsType())
|
||||
.orElse(null);
|
||||
table.widthProperty().subscribe((newValue) -> {
|
||||
if (os != OsType.WINDOWS && os != OsType.MACOS) {
|
||||
ownerCol.setVisible(newValue.doubleValue() > 1000);
|
||||
|
|
|
@ -53,7 +53,7 @@ public class BrowserFileSystemSavedState {
|
|||
|
||||
public BrowserFileSystemSavedState() {
|
||||
lastDirectory = null;
|
||||
recentDirectories = FXCollections.observableList(new ArrayList<>(STORED));
|
||||
recentDirectories = FXCollections.synchronizedObservableList(FXCollections.observableList(new ArrayList<>(STORED)));
|
||||
}
|
||||
|
||||
static BrowserFileSystemSavedState loadForStore(BrowserFileSystemTabModel model) {
|
||||
|
@ -164,7 +164,7 @@ public class BrowserFileSystemSavedState {
|
|||
.map(recentEntry -> new RecentEntry(FileNames.toDirectory(recentEntry.directory), recentEntry.time))
|
||||
.filter(distinctBy(recentEntry -> recentEntry.getDirectory()))
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
return new BrowserFileSystemSavedState(null, FXCollections.observableList(cleaned));
|
||||
return new BrowserFileSystemSavedState(null, FXCollections.synchronizedObservableList(FXCollections.observableList(cleaned)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -207,7 +207,7 @@ public class BrowserFileSystemTabComp extends SimpleComp {
|
|||
home,
|
||||
model.getCurrentPath().isNull(),
|
||||
fileList,
|
||||
model.getCurrentPath().isNull().not()));
|
||||
model.getCurrentPath().isNull().not()), false);
|
||||
var r = stack.styleClass("browser-content-container").createRegion();
|
||||
r.focusedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue) {
|
||||
|
|
|
@ -26,7 +26,7 @@ public class BrowserHistorySavedStateImpl implements BrowserHistorySavedState {
|
|||
ObservableList<Entry> lastSystems;
|
||||
|
||||
public BrowserHistorySavedStateImpl(List<Entry> lastSystems) {
|
||||
this.lastSystems = FXCollections.observableArrayList(lastSystems);
|
||||
this.lastSystems = FXCollections.synchronizedObservableList(FXCollections.observableArrayList(lastSystems));
|
||||
}
|
||||
|
||||
private static BrowserHistorySavedStateImpl INSTANCE;
|
||||
|
|
|
@ -60,7 +60,7 @@ public class BrowserHistoryTabComp extends SimpleComp {
|
|||
var map = new LinkedHashMap<Comp<?>, ObservableValue<Boolean>>();
|
||||
map.put(emptyDisplay, empty);
|
||||
map.put(contentDisplay, empty.not());
|
||||
var stack = new MultiContentComp(map);
|
||||
var stack = new MultiContentComp(map, false);
|
||||
return stack.createRegion();
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ public class BrowserOverviewComp extends SimpleComp {
|
|||
|
||||
ShellControl sc = model.getFileSystem().getShell().orElseThrow();
|
||||
|
||||
var commonPlatform = FXCollections.<FileEntry>observableArrayList();
|
||||
var commonPlatform = FXCollections.<FileEntry>synchronizedObservableList(FXCollections.observableArrayList());
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
var common = sc.getOsType().determineInterestingPaths(sc).stream()
|
||||
.filter(s -> !s.isBlank())
|
||||
|
|
|
@ -65,13 +65,14 @@ public class BrowserTransferComp extends SimpleComp {
|
|||
return Bindings.createStringBinding(
|
||||
() -> {
|
||||
var p = sourceItem.get().getProgress().getValue();
|
||||
var progressSuffix = p == null
|
||||
|| sourceItem
|
||||
.get()
|
||||
.downloadFinished()
|
||||
.get()
|
||||
var hideProgress = sourceItem
|
||||
.get()
|
||||
.downloadFinished()
|
||||
.get();
|
||||
var share = p != null ? (p.getTransferred() * 100 / p.getTotal()) : 0;
|
||||
var progressSuffix = hideProgress
|
||||
? ""
|
||||
: " " + (p.getTransferred() * 100 / p.getTotal()) + "%";
|
||||
: " " + share + "%";
|
||||
return entry.getFileName() + progressSuffix;
|
||||
},
|
||||
sourceItem.get().getProgress());
|
||||
|
@ -81,14 +82,14 @@ public class BrowserTransferComp extends SimpleComp {
|
|||
var dragNotice = new LabelComp(AppI18n.observable("dragLocalFiles"))
|
||||
.apply(struc -> struc.get().setGraphic(new FontIcon("mdi2h-hand-left")))
|
||||
.apply(struc -> struc.get().setWrapText(true))
|
||||
.hide(model.getEmpty());
|
||||
.hide(Bindings.or(model.getEmpty(), model.getTransferring()));
|
||||
|
||||
var clearButton = new IconButtonComp("mdi2c-close", () -> {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
model.clear(true);
|
||||
});
|
||||
})
|
||||
.hide(model.getEmpty())
|
||||
.hide(Bindings.or(model.getEmpty(), model.getTransferring()))
|
||||
.tooltipKey("clearTransferDescription");
|
||||
|
||||
var downloadButton = new IconButtonComp("mdi2f-folder-move-outline", () -> {
|
||||
|
@ -96,7 +97,7 @@ public class BrowserTransferComp extends SimpleComp {
|
|||
model.transferToDownloads();
|
||||
});
|
||||
})
|
||||
.hide(model.getEmpty())
|
||||
.hide(Bindings.or(model.getEmpty(), model.getTransferring()))
|
||||
.tooltipKey("downloadStageDescription");
|
||||
|
||||
var bottom = new HorizontalComp(
|
||||
|
|
|
@ -8,7 +8,9 @@ import io.xpipe.app.util.ShellTemp;
|
|||
import io.xpipe.app.util.ThreadHelper;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableBooleanValue;
|
||||
import javafx.collections.FXCollections;
|
||||
|
@ -34,6 +36,7 @@ public class BrowserTransferModel {
|
|||
BrowserFullSessionModel browserSessionModel;
|
||||
ObservableList<Item> items = FXCollections.observableArrayList();
|
||||
ObservableBooleanValue empty = Bindings.createBooleanBinding(() -> items.isEmpty(), items);
|
||||
BooleanProperty transferring = new SimpleBooleanProperty();
|
||||
|
||||
public BrowserTransferModel(BrowserFullSessionModel browserSessionModel) {
|
||||
this.browserSessionModel = browserSessionModel;
|
||||
|
@ -47,8 +50,9 @@ public class BrowserTransferModel {
|
|||
}
|
||||
if (toDownload.isPresent()) {
|
||||
downloadSingle(toDownload.get());
|
||||
} else {
|
||||
ThreadHelper.sleep(20);
|
||||
}
|
||||
ThreadHelper.sleep(20);
|
||||
}
|
||||
});
|
||||
thread.start();
|
||||
|
@ -126,6 +130,7 @@ public class BrowserTransferModel {
|
|||
}
|
||||
|
||||
try {
|
||||
transferring.setValue(true);
|
||||
var op = new BrowserFileTransferOperation(
|
||||
BrowserLocalFileSystem.getLocalFileEntry(TEMP),
|
||||
List.of(item.getBrowserEntry().getRawFileEntry()),
|
||||
|
@ -150,6 +155,8 @@ public class BrowserTransferModel {
|
|||
synchronized (items) {
|
||||
items.remove(item);
|
||||
}
|
||||
} finally {
|
||||
transferring.setValue(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -154,7 +154,15 @@ public abstract class Comp<S extends CompStructure<?>> {
|
|||
}
|
||||
|
||||
public Comp<S> disable(ObservableValue<Boolean> o) {
|
||||
return apply(struc -> struc.get().disableProperty().bind(o));
|
||||
return apply(struc -> {
|
||||
var region = struc.get();
|
||||
BindingsHelper.preserve(region, o);
|
||||
o.subscribe(n -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
region.setDisable(n);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public Comp<S> padding(Insets insets) {
|
||||
|
|
|
@ -39,7 +39,7 @@ public class AppLayoutComp extends Comp<AppLayoutComp.Structure> {
|
|||
return model.getSelected().getValue().equals(entry);
|
||||
},
|
||||
model.getSelected())));
|
||||
var multi = new MultiContentComp(map);
|
||||
var multi = new MultiContentComp(map, true);
|
||||
multi.styleClass("background");
|
||||
|
||||
var pane = new BorderPane();
|
||||
|
|
|
@ -6,6 +6,7 @@ import io.xpipe.app.core.AppFontSizes;
|
|||
import io.xpipe.app.core.AppProperties;
|
||||
import io.xpipe.app.core.window.AppDialog;
|
||||
import io.xpipe.app.core.window.AppMainWindow;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.resources.AppImages;
|
||||
import io.xpipe.app.resources.AppResources;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
@ -82,6 +83,7 @@ public class AppMainWindowContentComp extends SimpleComp {
|
|||
|
||||
loaded.subscribe(struc -> {
|
||||
if (struc != null) {
|
||||
TrackEvent.info("Window content node set");
|
||||
PlatformThread.runNestedLoopIteration();
|
||||
anim.stop();
|
||||
struc.prepareAddition();
|
||||
|
@ -90,6 +92,7 @@ public class AppMainWindowContentComp extends SimpleComp {
|
|||
pane.getStyleClass().remove("background");
|
||||
pane.getChildren().remove(vbox);
|
||||
struc.show();
|
||||
TrackEvent.info("Window content node shown");
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -107,14 +110,6 @@ public class AppMainWindowContentComp extends SimpleComp {
|
|||
}
|
||||
});
|
||||
|
||||
loaded.addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue != null) {
|
||||
Platform.runLater(() -> {
|
||||
stage.requestFocus();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return pane;
|
||||
});
|
||||
var modal = new ModalOverlayStackComp(bg, overlay);
|
||||
|
|
|
@ -67,7 +67,7 @@ public class FilterComp extends Comp<CompStructure<CustomTextField>> {
|
|||
filterText.subscribe(val -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
clear.setVisible(val != null);
|
||||
if (!Objects.equals(filter.getText(), val)) {
|
||||
if (!Objects.equals(filter.getText(), val) && !(val == null && "".equals(filter.getText()))) {
|
||||
filter.setText(val);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -5,6 +5,7 @@ import io.xpipe.app.comp.CompStructure;
|
|||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.scene.control.TextField;
|
||||
|
@ -13,6 +14,8 @@ import javafx.scene.input.KeyEvent;
|
|||
import lombok.AccessLevel;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
|
||||
public class IntFieldComp extends Comp<CompStructure<TextField>> {
|
||||
|
||||
|
@ -34,29 +37,38 @@ public class IntFieldComp extends Comp<CompStructure<TextField>> {
|
|||
|
||||
@Override
|
||||
public CompStructure<TextField> createBase() {
|
||||
var text = new TextField(value.getValue() != null ? value.getValue().toString() : null);
|
||||
var field = new TextField(value.getValue() != null ? value.getValue().toString() : null);
|
||||
|
||||
value.addListener((ChangeListener<Number>) (observableValue, oldValue, newValue) -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
if (newValue == null) {
|
||||
text.setText("");
|
||||
} else {
|
||||
if (newValue.intValue() < minValue) {
|
||||
value.setValue(minValue);
|
||||
return;
|
||||
}
|
||||
|
||||
if (newValue.intValue() > maxValue) {
|
||||
value.setValue(maxValue);
|
||||
return;
|
||||
}
|
||||
|
||||
text.setText(newValue.toString());
|
||||
// Check if control value is the same. Then don't set it as that might cause bugs
|
||||
if ((newValue == null && field.getText().isEmpty())
|
||||
|| Objects.equals(field.getText(), newValue != null ? newValue.toString() : null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (newValue == null) {
|
||||
Platform.runLater(() -> {
|
||||
field.setText(null);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (newValue.intValue() < minValue) {
|
||||
value.setValue(minValue);
|
||||
return;
|
||||
}
|
||||
|
||||
if (newValue.intValue() > maxValue) {
|
||||
value.setValue(maxValue);
|
||||
return;
|
||||
}
|
||||
|
||||
field.setText(newValue.toString());
|
||||
});
|
||||
});
|
||||
|
||||
text.addEventFilter(KeyEvent.KEY_TYPED, keyEvent -> {
|
||||
field.addEventFilter(KeyEvent.KEY_TYPED, keyEvent -> {
|
||||
if (minValue < 0) {
|
||||
if (!"-0123456789".contains(keyEvent.getCharacter())) {
|
||||
keyEvent.consume();
|
||||
|
@ -68,7 +80,7 @@ public class IntFieldComp extends Comp<CompStructure<TextField>> {
|
|||
}
|
||||
});
|
||||
|
||||
text.textProperty().addListener((observableValue, oldValue, newValue) -> {
|
||||
field.textProperty().addListener((observableValue, oldValue, newValue) -> {
|
||||
if (newValue == null
|
||||
|| newValue.isEmpty()
|
||||
|| (minValue < 0 && "-".equals(newValue))
|
||||
|
@ -79,12 +91,12 @@ public class IntFieldComp extends Comp<CompStructure<TextField>> {
|
|||
|
||||
int intValue = Integer.parseInt(newValue);
|
||||
if (minValue > intValue || intValue > maxValue) {
|
||||
text.textProperty().setValue(oldValue);
|
||||
field.textProperty().setValue(oldValue);
|
||||
}
|
||||
|
||||
value.setValue(Integer.parseInt(text.textProperty().get()));
|
||||
value.setValue(Integer.parseInt(field.textProperty().get()));
|
||||
});
|
||||
|
||||
return new SimpleCompStructure<>(text);
|
||||
return new SimpleCompStructure<>(field);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import javafx.animation.AnimationTimer;
|
|||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.css.PseudoClass;
|
||||
|
@ -26,6 +27,7 @@ import javafx.scene.layout.VBox;
|
|||
import lombok.Setter;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
||||
|
@ -43,9 +45,6 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
|||
@Setter
|
||||
private boolean visibilityControl = false;
|
||||
|
||||
@Setter
|
||||
private int platformPauseInterval = -1;
|
||||
|
||||
public ListBoxViewComp(
|
||||
ObservableList<T> shown, ObservableList<T> all, Function<T, Comp<?>> compFunction, boolean scrollBar) {
|
||||
this.shown = shown;
|
||||
|
@ -63,10 +62,24 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
|||
vbox.setFocusTraversable(false);
|
||||
var scroll = new ScrollPane(vbox);
|
||||
|
||||
refresh(scroll, vbox, shown, all, cache, false, false);
|
||||
refresh(scroll, vbox, shown, all, cache, false);
|
||||
|
||||
var hadScene = new AtomicBoolean(false);
|
||||
scroll.sceneProperty().subscribe(scene -> {
|
||||
if (scene != null) {
|
||||
hadScene.set(true);
|
||||
refresh(scroll, vbox, shown, all, cache, true);
|
||||
}
|
||||
});
|
||||
|
||||
shown.addListener((ListChangeListener<? super T>) (c) -> {
|
||||
refresh(scroll, vbox, c.getList(), all, cache, true, true);
|
||||
Platform.runLater(() -> {
|
||||
if (scroll.getScene() == null && hadScene.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
refresh(scroll, vbox, c.getList(), all, cache, true);
|
||||
});
|
||||
});
|
||||
|
||||
if (scrollBar) {
|
||||
|
@ -137,8 +150,13 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
|||
});
|
||||
if (StoreViewState.get() != null) {
|
||||
StoreViewState.get().getSortMode().addListener((observable, oldValue, newValue) -> {
|
||||
// This is very ugly, but it just takes multiple iterations for the order to apply
|
||||
Platform.runLater(() -> {
|
||||
dirty.set(true);
|
||||
Platform.runLater(() -> {
|
||||
Platform.runLater(() -> {
|
||||
dirty.set(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -154,10 +172,11 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
|||
|
||||
Node c = vbox;
|
||||
do {
|
||||
c.boundsInParentProperty().addListener((observable1, oldValue1, newValue1) -> {
|
||||
c.boundsInParentProperty().addListener((change, oldBounds,newBounds) -> {
|
||||
dirty.set(true);
|
||||
});
|
||||
} while ((c = c.getParent()) != null);
|
||||
// Don't listen to root node changes, that seemingly can cause exceptions
|
||||
} while ((c = c.getParent()) != null && c.getParent() != null);
|
||||
|
||||
if (newValue != null) {
|
||||
newValue.heightProperty().addListener((observable1, oldValue1, newValue1) -> {
|
||||
|
@ -230,46 +249,42 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
|||
List<? extends T> shown,
|
||||
List<? extends T> all,
|
||||
Map<T, Region> cache,
|
||||
boolean asynchronous,
|
||||
boolean refreshVisibilities) {
|
||||
Runnable update = () -> {
|
||||
synchronized (cache) {
|
||||
var set = new HashSet<T>();
|
||||
// These lists might diverge on updates
|
||||
set.addAll(shown);
|
||||
set.addAll(all);
|
||||
// These lists might diverge on updates, so add both
|
||||
synchronized (shown) {
|
||||
set.addAll(shown);
|
||||
}
|
||||
synchronized (all) {
|
||||
set.addAll(all);
|
||||
}
|
||||
// Clear cache of unused values
|
||||
cache.keySet().removeIf(t -> !set.contains(t));
|
||||
}
|
||||
|
||||
final long[] lastPause = {System.currentTimeMillis()};
|
||||
// Create copy to reduce chances of concurrent modification
|
||||
var shownCopy = new ArrayList<>(shown);
|
||||
var newShown = shownCopy.stream()
|
||||
.map(v -> {
|
||||
var elapsed = System.currentTimeMillis() - lastPause[0];
|
||||
if (platformPauseInterval != -1 && elapsed > platformPauseInterval) {
|
||||
PlatformThread.runNestedLoopIteration();
|
||||
lastPause[0] = System.currentTimeMillis();
|
||||
// Use copy to prevent concurrent modifications and to not synchronize to long
|
||||
List<T> shownCopy;
|
||||
synchronized (shown) {
|
||||
shownCopy = new ArrayList<>(shown);
|
||||
}
|
||||
List<Region> newShown = shownCopy.stream().map(v -> {
|
||||
if (!cache.containsKey(v)) {
|
||||
var comp = compFunction.apply(v);
|
||||
if (comp != null) {
|
||||
var r = comp.createRegion();
|
||||
if (visibilityControl) {
|
||||
r.setVisible(false);
|
||||
}
|
||||
cache.put(v, r);
|
||||
} else {
|
||||
cache.put(v, null);
|
||||
}
|
||||
}
|
||||
|
||||
if (!cache.containsKey(v)) {
|
||||
var comp = compFunction.apply(v);
|
||||
if (comp != null) {
|
||||
var r = comp.createRegion();
|
||||
if (visibilityControl) {
|
||||
r.setVisible(false);
|
||||
}
|
||||
cache.put(v, r);
|
||||
} else {
|
||||
cache.put(v, null);
|
||||
}
|
||||
}
|
||||
|
||||
return cache.get(v);
|
||||
})
|
||||
.filter(region -> region != null)
|
||||
.toList();
|
||||
return cache.get(v);
|
||||
}).filter(region -> region != null).toList();
|
||||
|
||||
if (listView.getChildren().equals(newShown)) {
|
||||
return;
|
||||
|
@ -289,11 +304,6 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
|||
updateVisibilities(scroll, listView);
|
||||
}
|
||||
};
|
||||
|
||||
if (asynchronous) {
|
||||
Platform.runLater(update);
|
||||
} else {
|
||||
PlatformThread.runLaterIfNeeded(update);
|
||||
}
|
||||
update.run();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package io.xpipe.app.comp.base;
|
|||
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
||||
import javafx.beans.value.ObservableValue;
|
||||
|
@ -15,9 +16,11 @@ import java.util.Map;
|
|||
|
||||
public class MultiContentComp extends SimpleComp {
|
||||
|
||||
private final boolean log;
|
||||
private final Map<Comp<?>, ObservableValue<Boolean>> content;
|
||||
|
||||
public MultiContentComp(Map<Comp<?>, ObservableValue<Boolean>> content) {
|
||||
public MultiContentComp(Map<Comp<?>, ObservableValue<Boolean>> content, boolean log) {
|
||||
this.log = log;
|
||||
this.content = FXCollections.observableMap(content);
|
||||
}
|
||||
|
||||
|
@ -34,7 +37,14 @@ public class MultiContentComp extends SimpleComp {
|
|||
});
|
||||
|
||||
for (Map.Entry<Comp<?>, ObservableValue<Boolean>> e : content.entrySet()) {
|
||||
var name = e.getKey().getClass().getSimpleName();
|
||||
if (log) {
|
||||
TrackEvent.trace("Creating content tab region for element " + name);
|
||||
}
|
||||
var r = e.getKey().createRegion();
|
||||
if (log) {
|
||||
TrackEvent.trace("Created content tab region for element " + name);
|
||||
}
|
||||
e.getValue().subscribe(val -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
r.setManaged(val);
|
||||
|
@ -42,6 +52,9 @@ public class MultiContentComp extends SimpleComp {
|
|||
});
|
||||
});
|
||||
m.put(e.getKey(), r);
|
||||
if (log) {
|
||||
TrackEvent.trace("Added content tab region for element " + name);
|
||||
}
|
||||
}
|
||||
|
||||
return stack;
|
||||
|
|
|
@ -10,6 +10,8 @@ import javafx.beans.property.Property;
|
|||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.control.PasswordField;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
|
||||
|
@ -68,23 +70,32 @@ public class SecretFieldComp extends Comp<SecretFieldComp.Structure> {
|
|||
|
||||
@Override
|
||||
public Structure createBase() {
|
||||
var text = new PasswordField();
|
||||
text.setText(value.getValue() != null ? value.getValue().getSecretValue() : null);
|
||||
text.textProperty().addListener((c, o, n) -> {
|
||||
var field = new PasswordField();
|
||||
field.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
|
||||
if (e.isControlDown() && e.getCode() == KeyCode.BACK_SPACE) {
|
||||
var sel = field.getSelection();
|
||||
if (sel.getEnd() > 0) {
|
||||
field.setText(field.getText().substring(sel.getEnd()));
|
||||
e.consume();
|
||||
}
|
||||
}
|
||||
});
|
||||
field.setText(value.getValue() != null ? value.getValue().getSecretValue() : null);
|
||||
field.textProperty().addListener((c, o, n) -> {
|
||||
value.setValue(n != null && n.length() > 0 ? encrypt(n.toCharArray()) : null);
|
||||
});
|
||||
value.addListener((c, o, n) -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
// Check if control value is the same. Then don't set it as that might cause bugs
|
||||
if ((n == null && text.getText().isEmpty())
|
||||
|| Objects.equals(text.getText(), n != null ? n.getSecretValue() : null)) {
|
||||
if ((n == null && field.getText().isEmpty())
|
||||
|| Objects.equals(field.getText(), n != null ? n.getSecretValue() : null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
text.setText(n != null ? n.getSecretValue() : null);
|
||||
field.setText(n != null ? n.getSecretValue() : null);
|
||||
});
|
||||
});
|
||||
HBox.setHgrow(text, Priority.ALWAYS);
|
||||
HBox.setHgrow(field, Priority.ALWAYS);
|
||||
|
||||
var copyButton = new ButtonComp(null, new FontIcon("mdi2c-clipboard-multiple-outline"), () -> {
|
||||
ClipboardHelper.copyPassword(value.getValue());
|
||||
|
@ -93,7 +104,7 @@ public class SecretFieldComp extends Comp<SecretFieldComp.Structure> {
|
|||
.tooltipKey("copyPassword")
|
||||
.createRegion();
|
||||
|
||||
var ig = new InputGroup(text);
|
||||
var ig = new InputGroup(field);
|
||||
ig.setFillHeight(true);
|
||||
ig.getStyleClass().add("secret-field-comp");
|
||||
if (allowCopy) {
|
||||
|
@ -103,10 +114,10 @@ public class SecretFieldComp extends Comp<SecretFieldComp.Structure> {
|
|||
|
||||
ig.focusedProperty().addListener((c, o, n) -> {
|
||||
if (n) {
|
||||
text.requestFocus();
|
||||
field.requestFocus();
|
||||
}
|
||||
});
|
||||
|
||||
return new Structure(ig, text);
|
||||
return new Structure(ig, field);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,7 +82,8 @@ public class OsLogoComp extends SimpleComp {
|
|||
}
|
||||
|
||||
return ICONS.entrySet().stream()
|
||||
.filter(e -> name.toLowerCase().contains(e.getKey()))
|
||||
.filter(e -> name.toLowerCase().contains(e.getKey()) ||
|
||||
name.toLowerCase().replaceAll("\\s+", "").contains(e.getKey()))
|
||||
.findAny()
|
||||
.map(e -> e.getValue())
|
||||
.orElse("os/linux.svg");
|
||||
|
|
|
@ -24,6 +24,7 @@ import javafx.beans.property.SimpleStringProperty;
|
|||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.MenuButton;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
@ -224,6 +225,14 @@ public class StoreChoiceComp<T extends DataStore> extends SimpleComp {
|
|||
}
|
||||
event.consume();
|
||||
});
|
||||
struc.get().setOnMouseClicked(event -> {
|
||||
if (event.getButton() != MouseButton.SECONDARY) {
|
||||
return;
|
||||
}
|
||||
|
||||
selected.setValue(mode == Mode.PROXY ? DataStorage.get().local().ref() : null);
|
||||
event.consume();
|
||||
});
|
||||
})
|
||||
.styleClass("choice-comp");
|
||||
|
||||
|
|
|
@ -142,6 +142,6 @@ public class StoreEntryListComp extends SimpleComp {
|
|||
map.put(new StoreScriptsIntroComp(scriptsIntroShowing), showScriptsIntro);
|
||||
map.put(new StoreIdentitiesIntroComp(), showIdentitiesIntro);
|
||||
|
||||
return new MultiContentComp(map).createRegion();
|
||||
return new MultiContentComp(map, false).createRegion();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import io.xpipe.app.util.BooleanScope;
|
|||
import io.xpipe.app.util.LabelGraphic;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.*;
|
||||
|
@ -118,9 +119,18 @@ public class StoreIconChoiceComp extends SimpleComp {
|
|||
}
|
||||
var data = partitionList(filtered, columns);
|
||||
table.getItems().setAll(data);
|
||||
|
||||
var selectMatch = filtered.size() == 1 || filtered.stream().anyMatch(systemIcon -> systemIcon.getId().equals(filterString));
|
||||
// Table updates seem to not always be instant, sometimes the column is not there yet
|
||||
if (selectMatch && table.getColumns().size() > 0) {
|
||||
table.getSelectionModel().select(0, table.getColumns().getFirst());
|
||||
selected.setValue(filtered.getFirst());
|
||||
} else {
|
||||
selected.setValue(null);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> Collection<List<T>> partitionList(List<T> list, int size) {
|
||||
private <T> List<List<T>> partitionList(List<T> list, int size) {
|
||||
List<List<T>> partitions = new ArrayList<>();
|
||||
if (list.size() == 0) {
|
||||
return partitions;
|
||||
|
|
|
@ -8,6 +8,7 @@ import io.xpipe.app.comp.base.IconButtonComp;
|
|||
import io.xpipe.app.comp.base.ListBoxViewComp;
|
||||
import io.xpipe.app.comp.base.VerticalComp;
|
||||
import io.xpipe.app.storage.DataColor;
|
||||
import io.xpipe.app.util.BindingsHelper;
|
||||
import io.xpipe.app.util.LabelGraphic;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import javafx.beans.Observable;
|
||||
|
@ -59,7 +60,9 @@ public abstract class StoreSectionBaseComp extends Comp<CompStructure<VBox>> {
|
|||
}
|
||||
|
||||
protected void addPseudoClassListeners(VBox vbox, ObservableBooleanValue expanded) {
|
||||
effectiveExpanded(expanded).subscribe(val -> {
|
||||
var observable = effectiveExpanded(expanded);
|
||||
BindingsHelper.preserve(this, observable);
|
||||
observable.subscribe(val -> {
|
||||
vbox.pseudoClassStateChanged(EXPANDED, val);
|
||||
});
|
||||
|
||||
|
|
|
@ -92,8 +92,7 @@ public class StoreSectionComp extends StoreSectionBaseComp {
|
|||
var hbox = ((HBox) struc.get().getChildren().getFirst());
|
||||
addPseudoClassListeners(struc.get(), section.getWrapper().getExpanded());
|
||||
addVisibilityListeners(struc.get(), hbox);
|
||||
})
|
||||
.createStructure();
|
||||
});
|
||||
return full.createStructure();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -85,8 +85,7 @@ public class StoreSectionMiniComp extends StoreSectionBaseComp {
|
|||
var hbox = ((HBox) struc.get().getChildren().getFirst());
|
||||
addVisibilityListeners(struc.get(), hbox);
|
||||
}
|
||||
})
|
||||
.createStructure();
|
||||
});
|
||||
return full.createStructure();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -210,19 +210,26 @@ public class AppTheme {
|
|||
}
|
||||
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
if (AppMainWindow.getInstance() == null) {
|
||||
var window = AppMainWindow.getInstance();
|
||||
if (window == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var window = AppMainWindow.getInstance().getStage();
|
||||
var scene = window.getScene();
|
||||
TrackEvent.debug("Setting theme " + newTheme.getId() + " for scene");
|
||||
|
||||
// Don't animate transition in performance mode
|
||||
if (AppPrefs.get() == null || AppPrefs.get().performanceMode().get()) {
|
||||
newTheme.apply();
|
||||
return;
|
||||
}
|
||||
|
||||
var stage = window.getStage();
|
||||
var scene = stage.getScene();
|
||||
Pane root = (Pane) scene.getRoot();
|
||||
Image snapshot = scene.snapshot(null);
|
||||
ImageView imageView = new ImageView(snapshot);
|
||||
root.getChildren().add(imageView);
|
||||
|
||||
newTheme.apply();
|
||||
TrackEvent.debug("Set theme " + newTheme.getId() + " for scene");
|
||||
|
||||
Platform.runLater(() -> {
|
||||
// Animate!
|
||||
|
|
|
@ -91,6 +91,7 @@ public class BaseMode extends OperationMode {
|
|||
AppPrefs.setLocalDefaultsIfNeeded();
|
||||
PlatformInit.init(true);
|
||||
AppMainWindow.addUpdateTitleListener();
|
||||
TrackEvent.info("Shell initialization thread completed");
|
||||
},
|
||||
() -> {
|
||||
shellLoaded.await();
|
||||
|
@ -112,6 +113,7 @@ public class BaseMode extends OperationMode {
|
|||
imagesLoaded.await();
|
||||
browserLoaded.await();
|
||||
iconsLoaded.await();
|
||||
TrackEvent.info("Waiting for startup dialogs to close");
|
||||
AppDialog.waitForAllDialogsClose();
|
||||
PlatformThread.runLaterIfNeededBlocking(() -> {
|
||||
try {
|
||||
|
@ -121,6 +123,7 @@ public class BaseMode extends OperationMode {
|
|||
}
|
||||
});
|
||||
UpdateChangelogAlert.showIfNeeded();
|
||||
TrackEvent.info("Connection storage initialization thread completed");
|
||||
},
|
||||
() -> {
|
||||
AppFileWatcher.init();
|
||||
|
@ -128,6 +131,7 @@ public class BaseMode extends OperationMode {
|
|||
BlobManager.init();
|
||||
TerminalView.init();
|
||||
TerminalLauncherManager.init();
|
||||
TrackEvent.info("File/Watcher initialization thread completed");
|
||||
},
|
||||
() -> {
|
||||
PlatformInit.init(true);
|
||||
|
@ -136,13 +140,16 @@ public class BaseMode extends OperationMode {
|
|||
storageLoaded.await();
|
||||
SystemIconManager.init();
|
||||
iconsLoaded.countDown();
|
||||
TrackEvent.info("Platform initialization thread completed");
|
||||
},
|
||||
() -> {
|
||||
BrowserIconManager.loadIfNecessary();
|
||||
shellLoaded.await();
|
||||
BrowserLocalFileSystem.init();
|
||||
storageLoaded.await();
|
||||
BrowserFullSessionModel.init();
|
||||
browserLoaded.countDown();
|
||||
TrackEvent.info("Browser initialization thread completed");
|
||||
});
|
||||
ActionProvider.initProviders();
|
||||
DataStoreProviders.init();
|
||||
|
|
|
@ -180,6 +180,8 @@ public abstract class OperationMode {
|
|||
|
||||
var startupMode = getStartupMode();
|
||||
switchToSyncOrThrow(map(startupMode));
|
||||
// If it doesn't find time, the JVM will not gc the startup workload
|
||||
System.gc();
|
||||
inStartup = false;
|
||||
AppOpenArguments.init();
|
||||
}
|
||||
|
|
|
@ -84,7 +84,7 @@ public class AppDialog {
|
|||
var transition = new PauseTransition(Duration.millis(200));
|
||||
transition.setOnFinished(e -> {
|
||||
if (wait) {
|
||||
Platform.exitNestedEventLoop(key, null);
|
||||
PlatformThread.exitNestedEventLoop(key);
|
||||
}
|
||||
});
|
||||
transition.play();
|
||||
|
@ -95,7 +95,7 @@ public class AppDialog {
|
|||
}
|
||||
});
|
||||
if (wait) {
|
||||
Platform.enterNestedEventLoop(key);
|
||||
PlatformThread.enterNestedEventLoop(key);
|
||||
waitForDialogClose(o);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -138,8 +138,10 @@ public class AppMainWindow {
|
|||
}
|
||||
|
||||
public static synchronized void initContent() {
|
||||
TrackEvent.info("Window content node creation started");
|
||||
var content = new AppLayoutComp();
|
||||
var s = content.createStructure();
|
||||
TrackEvent.info("Window content node structure created");
|
||||
loadedContent.setValue(s);
|
||||
}
|
||||
|
||||
|
|
|
@ -124,10 +124,13 @@ public class ModifiedStage extends Stage {
|
|||
var transition = new PauseTransition(Duration.millis(300));
|
||||
transition.setOnFinished(e -> {
|
||||
applyModes(stage);
|
||||
stage.setWidth(stage.getWidth() - 1);
|
||||
Platform.runLater(() -> {
|
||||
stage.setWidth(stage.getWidth() + 1);
|
||||
});
|
||||
// We only need to update the frame by resizing on Windows
|
||||
if (OsType.getLocal() == OsType.WINDOWS) {
|
||||
stage.setWidth(stage.getWidth() - 1);
|
||||
Platform.runLater(() -> {
|
||||
stage.setWidth(stage.getWidth() + 1);
|
||||
});
|
||||
}
|
||||
});
|
||||
transition.play();
|
||||
});
|
||||
|
|
|
@ -2,6 +2,7 @@ package io.xpipe.app.ext;
|
|||
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.util.FailableConsumer;
|
||||
|
@ -21,6 +22,7 @@ public interface ActionProvider {
|
|||
List<ActionProvider> ALL_STANDALONE = new ArrayList<>();
|
||||
|
||||
static void initProviders() {
|
||||
TrackEvent.trace("Starting action provider initialization");
|
||||
for (ActionProvider actionProvider : ALL) {
|
||||
try {
|
||||
actionProvider.init();
|
||||
|
@ -28,6 +30,7 @@ public interface ActionProvider {
|
|||
ErrorEvent.fromThrowable(t).handle();
|
||||
}
|
||||
}
|
||||
TrackEvent.trace("Finished action provider initialization");
|
||||
}
|
||||
|
||||
default void init() throws Exception {}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
package io.xpipe.app.icon;
|
||||
|
||||
import io.xpipe.app.ext.ProcessControlProvider;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.util.DesktopHelper;
|
||||
import io.xpipe.app.util.Hyperlinks;
|
||||
import io.xpipe.app.util.Validators;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import io.xpipe.core.store.FilePath;
|
||||
import io.xpipe.core.util.ValidationException;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
|
@ -90,7 +92,14 @@ public interface SystemIconSource {
|
|||
@Override
|
||||
public void refresh() throws Exception {
|
||||
try (var sc =
|
||||
ProcessControlProvider.get().createLocalProcessControl(true).start()) {
|
||||
ProcessControlProvider.get().createLocalProcessControl(true).start()) {
|
||||
var present = sc.view().findProgram("git").isPresent();
|
||||
if (!present) {
|
||||
var msg = "Git command-line tools are not available in the PATH but are required to use icons from a git repository. For more details, see https://git-scm.com/downloads.";
|
||||
ErrorEvent.fromMessage(msg).expected().handle();
|
||||
return;
|
||||
}
|
||||
|
||||
var dir = SystemIconManager.getPoolPath().resolve(id);
|
||||
if (!Files.exists(dir)) {
|
||||
sc.command(CommandBuilder.of()
|
||||
|
|
|
@ -39,6 +39,14 @@ public class SentryErrorHandler implements ErrorHandler {
|
|||
return hasEmail || hasText;
|
||||
}
|
||||
|
||||
private static boolean doesExceedCommentSize(String text) {
|
||||
if (text == null || text.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return text.length() > 5000;
|
||||
}
|
||||
|
||||
private static Throwable adjustCopy(Throwable throwable, boolean clear) {
|
||||
if (throwable == null) {
|
||||
return null;
|
||||
|
@ -139,6 +147,16 @@ public class SentryErrorHandler implements ErrorHandler {
|
|||
atts.forEach(attachment -> s.addAttachment(attachment));
|
||||
}
|
||||
|
||||
if (doesExceedCommentSize(ee.getUserReport())) {
|
||||
try {
|
||||
var report = Files.createTempFile("report", ".txt");
|
||||
Files.writeString(report, ee.getUserReport());
|
||||
s.addAttachment(new Attachment(report.toString()));
|
||||
} catch (Exception ex) {
|
||||
AppLogs.get().logException("Unable to create report file", ex);
|
||||
}
|
||||
}
|
||||
|
||||
s.setTag(
|
||||
"hasLicense",
|
||||
String.valueOf(
|
||||
|
@ -176,7 +194,7 @@ public class SentryErrorHandler implements ErrorHandler {
|
|||
AppPrefs.get() != null
|
||||
? String.valueOf(AppPrefs.get().useLocalFallbackShell().get())
|
||||
: "unknown");
|
||||
s.setTag("initial", AppProperties.get() != null ? AppProperties.get().isInitialLaunch() + "" : null);
|
||||
s.setTag("initial", AppProperties.get() != null ? AppProperties.get().isInitialLaunch() + "" : "false");
|
||||
|
||||
var exMessage = ee.getThrowable() != null ? ee.getThrowable().getMessage() : null;
|
||||
if (ee.getDescription() != null
|
||||
|
@ -231,7 +249,11 @@ public class SentryErrorHandler implements ErrorHandler {
|
|||
if (hasEmail) {
|
||||
fb.setEmail(email);
|
||||
}
|
||||
fb.setComments(text);
|
||||
if (doesExceedCommentSize(text)) {
|
||||
fb.setComments("<Attachment>");
|
||||
} else {
|
||||
fb.setComments(text);
|
||||
}
|
||||
Sentry.captureUserFeedback(fb);
|
||||
}
|
||||
Sentry.flush(3000);
|
||||
|
|
|
@ -117,6 +117,8 @@ public class AppPrefs {
|
|||
mapLocal(new SimpleObjectProperty<>(), "externalEditor", ExternalEditorType.class, false);
|
||||
final StringProperty customEditorCommand =
|
||||
mapLocal(new SimpleStringProperty(""), "customEditorCommand", String.class, false);
|
||||
final BooleanProperty customEditorCommandInTerminal =
|
||||
mapLocal(new SimpleBooleanProperty(false), "customEditorCommandInTerminal", Boolean.class, false);
|
||||
final BooleanProperty automaticallyCheckForUpdates =
|
||||
mapLocal(new SimpleBooleanProperty(true), "automaticallyCheckForUpdates", Boolean.class, false);
|
||||
final BooleanProperty encryptAllVaultData =
|
||||
|
@ -405,6 +407,10 @@ public class AppPrefs {
|
|||
return customEditorCommand;
|
||||
}
|
||||
|
||||
public ObservableBooleanValue customEditorCommandInTerminal() {
|
||||
return customEditorCommandInTerminal;
|
||||
}
|
||||
|
||||
public final ReadOnlyIntegerProperty editorReloadTimeout() {
|
||||
return editorReloadTimeout;
|
||||
}
|
||||
|
|
|
@ -14,19 +14,22 @@ public class ConnectionsCategory extends AppPrefsCategory {
|
|||
@Override
|
||||
protected Comp<?> create() {
|
||||
var prefs = AppPrefs.get();
|
||||
var options = new OptionsBuilder()
|
||||
.addTitle("connections")
|
||||
.sub(new OptionsBuilder()
|
||||
.pref(prefs.condenseConnectionDisplay)
|
||||
.addToggle(prefs.condenseConnectionDisplay)
|
||||
.pref(prefs.showChildCategoriesInParentCategory)
|
||||
.addToggle(prefs.showChildCategoriesInParentCategory)
|
||||
.pref(prefs.openConnectionSearchWindowOnConnectionCreation)
|
||||
.addToggle(prefs.openConnectionSearchWindowOnConnectionCreation)
|
||||
.pref(prefs.requireDoubleClickForConnections)
|
||||
.addToggle(prefs.requireDoubleClickForConnections))
|
||||
var connectionsBuilder = new OptionsBuilder().pref(prefs.condenseConnectionDisplay).addToggle(prefs.condenseConnectionDisplay).pref(
|
||||
prefs.showChildCategoriesInParentCategory).addToggle(prefs.showChildCategoriesInParentCategory).pref(
|
||||
prefs.openConnectionSearchWindowOnConnectionCreation).addToggle(prefs.openConnectionSearchWindowOnConnectionCreation).pref(
|
||||
prefs.requireDoubleClickForConnections).addToggle(prefs.requireDoubleClickForConnections);
|
||||
var localShellBuilder = new OptionsBuilder().pref(prefs.useLocalFallbackShell).addToggle(prefs.useLocalFallbackShell);
|
||||
// Change order to prioritize fallback shell on macOS
|
||||
var options = OsType.getLocal() == OsType.MACOS ? new OptionsBuilder()
|
||||
.addTitle("localShell")
|
||||
.sub(new OptionsBuilder().pref(prefs.useLocalFallbackShell).addToggle(prefs.useLocalFallbackShell));
|
||||
.sub(localShellBuilder)
|
||||
.addTitle("connections")
|
||||
.sub(connectionsBuilder) :
|
||||
new OptionsBuilder()
|
||||
.addTitle("connections")
|
||||
.sub(connectionsBuilder)
|
||||
.addTitle("localShell")
|
||||
.sub(localShellBuilder);
|
||||
if (OsType.getLocal() == OsType.WINDOWS) {
|
||||
options.addTitle("sshConfiguration")
|
||||
.sub(new OptionsBuilder()
|
||||
|
|
|
@ -47,9 +47,13 @@ public class EditorCategory extends AppPrefsCategory {
|
|||
prefs.externalEditor, PrefsChoiceValue.getSupported(ExternalEditorType.class), false))
|
||||
.nameAndDescription("customEditorCommand")
|
||||
.addComp(new TextFieldComp(prefs.customEditorCommand, true)
|
||||
.apply(struc -> struc.get().setPromptText("myeditor $FILE"))
|
||||
.hide(prefs.externalEditor.isNotEqualTo(ExternalEditorType.CUSTOM)))
|
||||
.addComp(terminalTest))
|
||||
.apply(struc -> struc.get().setPromptText("myeditor $FILE")))
|
||||
.hide(prefs.externalEditor.isNotEqualTo(ExternalEditorType.CUSTOM))
|
||||
.addComp(terminalTest)
|
||||
.nameAndDescription("customEditorCommandInTerminal")
|
||||
.addToggle(prefs.customEditorCommandInTerminal)
|
||||
.hide(prefs.externalEditor.isNotEqualTo(ExternalEditorType.CUSTOM))
|
||||
)
|
||||
.buildComp();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package io.xpipe.app.prefs;
|
|||
|
||||
import io.xpipe.app.ext.PrefsChoiceValue;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.terminal.TerminalLauncher;
|
||||
import io.xpipe.app.util.LocalShell;
|
||||
import io.xpipe.app.util.WindowsRegistry;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
|
@ -171,10 +172,13 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
|||
throw ErrorEvent.expected(new IllegalStateException("No custom editor command specified"));
|
||||
}
|
||||
|
||||
var format =
|
||||
customCommand.toLowerCase(Locale.ROOT).contains("$file") ? customCommand : customCommand + " $FILE";
|
||||
ExternalApplicationHelper.startAsync(CommandBuilder.of()
|
||||
.add(ExternalApplicationHelper.replaceFileArgument(format, "FILE", file.toString())));
|
||||
var format = customCommand.toLowerCase(Locale.ROOT).contains("$file") ? customCommand : customCommand + " $FILE";
|
||||
var command = CommandBuilder.of().add(ExternalApplicationHelper.replaceFileArgument(format, "FILE", file.toString()));
|
||||
if (AppPrefs.get().customEditorCommandInTerminal().get()) {
|
||||
TerminalLauncher.openDirect(file.toString(), sc -> command.buildFull(sc), AppPrefs.get().terminalType.get());
|
||||
} else {
|
||||
ExternalApplicationHelper.startAsync(command);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -108,7 +108,7 @@ public interface ExternalRdpClientType extends PrefsChoiceValue {
|
|||
ThreadHelper.runFailableAsync(() -> {
|
||||
// Startup is slow
|
||||
ThreadHelper.sleep(10000);
|
||||
Files.delete(config);
|
||||
FileUtils.deleteQuietly(config.toFile());
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -125,8 +125,12 @@ public interface ExternalRdpClientType extends PrefsChoiceValue {
|
|||
@Override
|
||||
public void launch(LaunchConfiguration configuration) throws Exception {
|
||||
var file = writeRdpConfigFile(configuration.getTitle(), configuration.getConfig());
|
||||
var escapedPw = configuration.getPassword().getSecretValue().replaceAll("'", "\\\\'");
|
||||
launch(configuration.getTitle(), CommandBuilder.of().addFile(file.toString()).add("/cert-ignore").add("/p:'" + escapedPw + "'"));
|
||||
var b = CommandBuilder.of().addFile(file.toString()).add("/cert-ignore");
|
||||
if (configuration.getPassword() != null) {
|
||||
var escapedPw = configuration.getPassword().getSecretValue().replaceAll("'", "\\\\'");
|
||||
b.add("/p:'" + escapedPw + "'");
|
||||
}
|
||||
launch(configuration.getTitle(), b);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -15,6 +15,7 @@ import javafx.beans.property.SimpleStringProperty;
|
|||
import javafx.collections.FXCollections;
|
||||
import javafx.scene.control.TextField;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -111,8 +112,13 @@ public class IconsCategory extends AppPrefsCategory {
|
|||
return;
|
||||
}
|
||||
|
||||
var path = Path.of(dir.get());
|
||||
if (Files.isRegularFile(path)) {
|
||||
throw new IllegalArgumentException("A custom icon directory requires to be a directory of .svg files, not a single file");
|
||||
}
|
||||
|
||||
var source = SystemIconSource.Directory.builder()
|
||||
.path(Path.of(dir.get()))
|
||||
.path(path)
|
||||
.id(UUID.randomUUID().toString())
|
||||
.build();
|
||||
if (!sources.contains(source)) {
|
||||
|
|
|
@ -5,6 +5,7 @@ import io.xpipe.app.comp.base.*;
|
|||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.window.AppDialog;
|
||||
import io.xpipe.app.storage.DataStorageSyncHandler;
|
||||
import io.xpipe.app.util.Hyperlinks;
|
||||
import io.xpipe.app.util.OptionsBuilder;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
|
||||
|
@ -27,14 +28,6 @@ public class SyncCategory extends AppPrefsCategory {
|
|||
return "vaultSync";
|
||||
}
|
||||
|
||||
private static void showHelpAlert() {
|
||||
var md = AppI18n.get().getMarkdownDocumentation("vault");
|
||||
var markdown = new MarkdownComp(md, s -> s, true).prefWidth(600);
|
||||
var modal = ModalOverlay.of(markdown);
|
||||
modal.addButton(ModalButton.ok());
|
||||
AppDialog.show(modal);
|
||||
}
|
||||
|
||||
public Comp<?> create() {
|
||||
var prefs = AppPrefs.get();
|
||||
AtomicReference<Region> button = new AtomicReference<>();
|
||||
|
@ -61,7 +54,7 @@ public class SyncCategory extends AppPrefsCategory {
|
|||
|
||||
var remoteRepo = new TextFieldComp(prefs.storageGitRemote).hgrow();
|
||||
var helpButton = new ButtonComp(AppI18n.observable("help"), new FontIcon("mdi2h-help-circle-outline"), () -> {
|
||||
showHelpAlert();
|
||||
Hyperlinks.open(Hyperlinks.DOCS_SYNC);
|
||||
});
|
||||
var remoteRow = new HorizontalComp(List.of(remoteRepo, helpButton)).spacing(10);
|
||||
remoteRow.apply(struc -> struc.get().setAlignment(Pos.CENTER_LEFT));
|
||||
|
|
|
@ -15,6 +15,7 @@ import io.xpipe.app.terminal.ExternalTerminalType;
|
|||
import io.xpipe.app.terminal.TerminalLauncher;
|
||||
import io.xpipe.app.util.*;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
|
@ -58,6 +59,10 @@ public class TerminalCategory extends AppPrefsCategory {
|
|||
var feature = LicenseProvider.get().getFeature("logging");
|
||||
if (newValue && !feature.isSupported()) {
|
||||
try {
|
||||
// Disable it again so people don't forget that they left it on
|
||||
Platform.runLater(() -> {
|
||||
prefs.enableTerminalLogging.set(false);
|
||||
});
|
||||
feature.throwIfUnsupported();
|
||||
} catch (LicenseRequiredException ex) {
|
||||
ErrorEvent.fromThrowable(ex).handle();
|
||||
|
|
|
@ -34,7 +34,10 @@ public class DataStoreEntryRef<T extends DataStore> {
|
|||
}
|
||||
|
||||
public void checkComplete() throws Throwable {
|
||||
getStore().checkComplete();
|
||||
var store = getStore();
|
||||
if (store != null) {
|
||||
getStore().checkComplete();
|
||||
}
|
||||
}
|
||||
|
||||
public DataStoreEntry get() {
|
||||
|
@ -42,7 +45,7 @@ public class DataStoreEntryRef<T extends DataStore> {
|
|||
}
|
||||
|
||||
public T getStore() {
|
||||
return entry.getStore().asNeeded();
|
||||
return entry.getStore() != null ? entry.getStore().asNeeded() : null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
|
|
@ -640,7 +640,6 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
.addFile(configuration.getScriptFile()));
|
||||
}
|
||||
};
|
||||
ExternalTerminalType WARP = new WarpTerminalType();
|
||||
ExternalTerminalType CUSTOM = new CustomTerminalType();
|
||||
List<ExternalTerminalType> WINDOWS_TERMINALS = List.of(
|
||||
WindowsTerminalType.WINDOWS_TERMINAL_CANARY,
|
||||
|
@ -648,6 +647,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
WindowsTerminalType.WINDOWS_TERMINAL,
|
||||
AlacrittyTerminalType.ALACRITTY_WINDOWS,
|
||||
WezTerminalType.WEZTERM_WINDOWS,
|
||||
WarpTerminalType.WINDOWS,
|
||||
CMD,
|
||||
PWSH,
|
||||
POWERSHELL,
|
||||
|
@ -679,10 +679,11 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
DEEPIN_TERMINAL,
|
||||
FOOT,
|
||||
Q_TERMINAL,
|
||||
WarpTerminalType.LINUX,
|
||||
TERMIUS,
|
||||
WaveTerminalType.WAVE_LINUX);
|
||||
List<ExternalTerminalType> MACOS_TERMINALS = List.of(
|
||||
WARP,
|
||||
WarpTerminalType.MACOS,
|
||||
ITERM2,
|
||||
KittyTerminalType.KITTY_MACOS,
|
||||
TabbyTerminalType.TABBY_MAC_OS,
|
||||
|
|
|
@ -1,57 +1,140 @@
|
|||
package io.xpipe.app.terminal;
|
||||
|
||||
import io.xpipe.app.prefs.ExternalApplicationHelper;
|
||||
import io.xpipe.app.util.DesktopHelper;
|
||||
import io.xpipe.app.util.Hyperlinks;
|
||||
import io.xpipe.app.util.LocalShell;
|
||||
import io.xpipe.app.util.WindowsRegistry;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
import io.xpipe.core.process.ShellDialects;
|
||||
import io.xpipe.core.process.TerminalInitFunction;
|
||||
|
||||
public class WarpTerminalType extends ExternalTerminalType.MacOsType {
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public WarpTerminalType() {
|
||||
super("app.warp", "Warp");
|
||||
public interface WarpTerminalType extends ExternalTerminalType, TrackableTerminalType {
|
||||
|
||||
static WarpTerminalType WINDOWS = new Windows();
|
||||
static WarpTerminalType LINUX = new Linux();
|
||||
static WarpTerminalType MACOS = new MacOs();
|
||||
|
||||
class Windows implements WarpTerminalType {
|
||||
|
||||
@Override
|
||||
public int getProcessHierarchyOffset() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||
if (!configuration.isPreferTabs()) {
|
||||
DesktopHelper.openUrl("warp://action/new_window?path=" + configuration.getScriptFile());
|
||||
} else {
|
||||
DesktopHelper.openUrl("warp://action/new_tab?path=" + configuration.getScriptFile());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return WindowsRegistry.local().keyExists(WindowsRegistry.HKEY_CURRENT_USER, "Software\\Classes\\warp");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "app.warp";
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminalOpenFormat getOpenFormat() {
|
||||
// Warp always opens the new separate window, so we don't want to use it in the file browser for docking
|
||||
// Just say that we don't support new windows, that way it doesn't dock
|
||||
return TerminalOpenFormat.TABBED;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Linux implements WarpTerminalType {
|
||||
|
||||
@Override
|
||||
public int getProcessHierarchyOffset() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||
if (!configuration.isPreferTabs()) {
|
||||
DesktopHelper.openUrl("warp://action/new_window?path=" + configuration.getScriptFile());
|
||||
} else {
|
||||
DesktopHelper.openUrl("warp://action/new_tab?path=" + configuration.getScriptFile());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return Files.exists(Path.of("/opt/warpdotdev"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "app.warp";
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminalOpenFormat getOpenFormat() {
|
||||
// Warp always opens the new separate window, so we don't want to use it in the file browser for docking
|
||||
// Just say that we don't support new windows, that way it doesn't dock
|
||||
return TerminalOpenFormat.TABBED;
|
||||
}
|
||||
}
|
||||
|
||||
class MacOs extends MacOsType implements WarpTerminalType {
|
||||
|
||||
public MacOs() {
|
||||
super("app.warp", "Warp");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProcessHierarchyOffset() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||
LocalShell.getShell()
|
||||
.executeSimpleCommand(CommandBuilder.of()
|
||||
.add("open", "-a")
|
||||
.addQuoted("Warp.app")
|
||||
.addFile(configuration.getScriptFile()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminalOpenFormat getOpenFormat() {
|
||||
return TerminalOpenFormat.TABBED;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminalOpenFormat getOpenFormat() {
|
||||
return TerminalOpenFormat.TABBED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProcessHierarchyOffset() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWebsite() {
|
||||
default String getWebsite() {
|
||||
return "https://www.warp.dev/";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRecommended() {
|
||||
default boolean isRecommended() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useColoredTitle() {
|
||||
default boolean useColoredTitle() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldClear() {
|
||||
default boolean shouldClear() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
|
||||
LocalShell.getShell()
|
||||
.executeSimpleCommand(CommandBuilder.of()
|
||||
.add("open", "-a")
|
||||
.addQuoted("Warp.app")
|
||||
.addFile(configuration.getScriptFile()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminalInitFunction additionalInitCommands() {
|
||||
default TerminalInitFunction additionalInitCommands() {
|
||||
return TerminalInitFunction.of(sc -> {
|
||||
if (sc.getShellDialect() == ShellDialects.ZSH) {
|
||||
return "printf '\\eP$f{\"hook\": \"SourcedRcFileForWarp\", \"value\": { \"shell\": \"zsh\"}}\\x9c'";
|
||||
|
|
|
@ -3,7 +3,6 @@ package io.xpipe.app.util;
|
|||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.core.process.CountDown;
|
||||
import io.xpipe.core.process.ElevationHandler;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.util.SecretReference;
|
||||
|
||||
|
@ -21,7 +20,7 @@ public class BaseElevationHandler implements ElevationHandler {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean handleRequest(ShellControl parent, UUID requestId, CountDown countDown, boolean confirmIfNeeded) {
|
||||
public boolean handleRequest(UUID requestId, CountDown countDown, boolean confirmIfNeeded, boolean interactive) {
|
||||
var ref = getSecretRef();
|
||||
if (ref == null) {
|
||||
return false;
|
||||
|
@ -35,7 +34,7 @@ public class BaseElevationHandler implements ElevationHandler {
|
|||
List.of(),
|
||||
List.of(),
|
||||
countDown,
|
||||
parent.isInteractive());
|
||||
interactive);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.core.AppDistributionType;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
|
@ -8,11 +10,48 @@ import io.xpipe.core.store.FileKind;
|
|||
import io.xpipe.core.store.FilePath;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class DesktopHelper {
|
||||
|
||||
private static final String[] browsers = {
|
||||
"xdg-open", "google-chrome", "firefox", "opera", "konqueror", "mozilla", "gnome-open", "open"
|
||||
};
|
||||
|
||||
public static void openUrl(String uri) {
|
||||
try {
|
||||
if (OsType.getLocal() == OsType.WINDOWS) {
|
||||
var pb = new ProcessBuilder("rundll32", "url.dll,FileProtocolHandler", uri);
|
||||
pb.directory(new File(System.getProperty("user.home")));
|
||||
pb.redirectErrorStream(true);
|
||||
pb.redirectOutput(ProcessBuilder.Redirect.DISCARD);
|
||||
pb.start();
|
||||
} else if (OsType.getLocal() == OsType.LINUX) {
|
||||
String browser = null;
|
||||
for (String b : browsers) {
|
||||
if (browser == null
|
||||
&& Runtime.getRuntime()
|
||||
.exec(new String[] {"which", b})
|
||||
.getInputStream()
|
||||
.read()
|
||||
!= -1) {
|
||||
Runtime.getRuntime().exec(new String[] {browser = b, uri});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var pb = new ProcessBuilder("open", uri);
|
||||
pb.directory(new File(System.getProperty("user.home")));
|
||||
pb.redirectErrorStream(true);
|
||||
pb.redirectOutput(ProcessBuilder.Redirect.DISCARD);
|
||||
pb.start();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).handle();
|
||||
}
|
||||
}
|
||||
|
||||
public static Path getDesktopDirectory() throws Exception {
|
||||
if (OsType.getLocal() == OsType.WINDOWS) {
|
||||
return Path.of(LocalShell.getLocalPowershell()
|
||||
|
@ -90,47 +129,46 @@ public class DesktopHelper {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!Desktop.getDesktop().isSupported(Desktop.Action.OPEN)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Files.exists(file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadHelper.runAsync(() -> {
|
||||
try {
|
||||
Desktop.getDesktop().open(file.toFile());
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).expected().handle();
|
||||
var xdg = OsType.getLocal() == OsType.LINUX;
|
||||
if (Desktop.getDesktop().isSupported(Desktop.Action.OPEN) && AppDistributionType.get() != AppDistributionType.WEBTOP) {
|
||||
try {
|
||||
Desktop.getDesktop().open(file.toFile());
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).expected().omitted(xdg).handle();
|
||||
}
|
||||
}
|
||||
|
||||
if (xdg) {
|
||||
LocalExec.readStdoutIfPossible("xdg-open", file.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void browseFileInDirectory(Path file) {
|
||||
if (!Desktop.getDesktop().isSupported(Desktop.Action.BROWSE_FILE_DIR)) {
|
||||
if (!Desktop.getDesktop().isSupported(Desktop.Action.OPEN)) {
|
||||
ErrorEvent.fromMessage("Desktop integration unable to open file " + file)
|
||||
.expected()
|
||||
.handle();
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadHelper.runAsync(() -> {
|
||||
try {
|
||||
Desktop.getDesktop().open(file.getParent().toFile());
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).expected().handle();
|
||||
}
|
||||
});
|
||||
browsePathLocal(file.getParent());
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadHelper.runAsync(() -> {
|
||||
try {
|
||||
Desktop.getDesktop().browseFileDirectory(file.toFile());
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).expected().handle();
|
||||
var xdg = OsType.getLocal() == OsType.LINUX;
|
||||
if (AppDistributionType.get() != AppDistributionType.WEBTOP) {
|
||||
try {
|
||||
Desktop.getDesktop().browseFileDirectory(file.toFile());
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).expected().omitted(xdg).handle();
|
||||
}
|
||||
}
|
||||
|
||||
if (xdg) {
|
||||
LocalExec.readStdoutIfPossible("xdg-open", file.getParent().toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.core.process.OsType;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class Hyperlinks {
|
||||
|
||||
|
@ -13,6 +16,7 @@ public class Hyperlinks {
|
|||
public static final String DOCS_EULA = "https://docs.xpipe.io/legal/end-user-license-agreement";
|
||||
public static final String DOCS_SECURITY = "https://docs.xpipe.io/reference/security";
|
||||
public static final String DOCS_WEBTOP_UPDATE = "https://docs.xpipe.io/guide/webtop#updating";
|
||||
public static final String DOCS_SYNC = "https://docs.xpipe.io/guide/sync";
|
||||
|
||||
public static final String GITHUB = "https://github.com/xpipe-io/xpipe";
|
||||
public static final String GITHUB_PTB = "https://github.com/xpipe-io/xpipe-ptb";
|
||||
|
@ -24,38 +28,7 @@ public class Hyperlinks {
|
|||
public static final String SLACK =
|
||||
"https://join.slack.com/t/XPipe/shared_invite/zt-1awjq0t5j-5i4UjNJfNe1VN4b_auu6Cg";
|
||||
|
||||
static final String[] browsers = {
|
||||
"xdg-open", "google-chrome", "firefox", "opera", "konqueror", "mozilla", "gnome-open", "open"
|
||||
};
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static void open(String uri) {
|
||||
String osName = System.getProperty("os.name");
|
||||
try {
|
||||
if (osName.startsWith("Mac OS")) {
|
||||
Runtime.getRuntime().exec("open " + uri);
|
||||
} else if (osName.startsWith("Windows")) {
|
||||
Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + uri);
|
||||
} else { // assume Unix or Linux
|
||||
String browser = null;
|
||||
for (String b : browsers) {
|
||||
if (browser == null
|
||||
&& Runtime.getRuntime()
|
||||
.exec(new String[] {"which", b})
|
||||
.getInputStream()
|
||||
.read()
|
||||
!= -1) {
|
||||
Runtime.getRuntime().exec(new String[] {browser = b, uri});
|
||||
}
|
||||
}
|
||||
if (browser == null) {
|
||||
throw new Exception("No web browser or URL opener found to open " + uri);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// should not happen
|
||||
// dump stack for debug purpose
|
||||
ErrorEvent.fromThrowable(e).handle();
|
||||
}
|
||||
DesktopHelper.openUrl(uri);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.prefs.ExternalApplicationType;
|
||||
import io.xpipe.app.prefs.ExternalEditorType;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
|
@ -15,7 +14,7 @@ public class LocalShellCache extends ShellControlCache {
|
|||
super(shellControl);
|
||||
}
|
||||
|
||||
public Optional<Path> getVsCodePath() {
|
||||
public Optional<Path> getVsCodeCliPath() {
|
||||
if (!has("codePath")) {
|
||||
try {
|
||||
var app =
|
||||
|
@ -25,8 +24,8 @@ public class LocalShellCache extends ShellControlCache {
|
|||
.map(s -> Path.of(s));
|
||||
}
|
||||
case OsType.MacOs macOs -> {
|
||||
yield new ExternalApplicationType.MacApplication(
|
||||
"app.vscode", "Visual Studio Code") {}.findApp();
|
||||
yield CommandSupport.findProgram(getShellControl(), "code")
|
||||
.map(s -> Path.of(s));
|
||||
}
|
||||
case OsType.Windows windows -> {
|
||||
yield ExternalEditorType.VSCODE_WINDOWS.findExecutable();
|
||||
|
|
|
@ -274,6 +274,29 @@ public class PlatformThread {
|
|||
return true;
|
||||
}
|
||||
|
||||
public static void enterNestedEventLoop(Object key) {
|
||||
if (!Platform.canStartNestedEventLoop()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Platform.enterNestedEventLoop(key);
|
||||
} catch (IllegalStateException ex) {
|
||||
// We might be in an animation or layout call
|
||||
ErrorEvent.fromThrowable(ex).omit().expected().handle();
|
||||
}
|
||||
}
|
||||
|
||||
public static void exitNestedEventLoop(Object key) {
|
||||
try {
|
||||
Platform.exitNestedEventLoop(key, null);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// The event loop might have died somehow
|
||||
// Or we passed an invalid key
|
||||
ErrorEvent.fromThrowable(ex).omit().expected().handle();
|
||||
}
|
||||
}
|
||||
|
||||
public static void runNestedLoopIteration() {
|
||||
if (!Platform.canStartNestedEventLoop()) {
|
||||
return;
|
||||
|
@ -281,9 +304,9 @@ public class PlatformThread {
|
|||
|
||||
var key = new Object();
|
||||
Platform.runLater(() -> {
|
||||
Platform.exitNestedEventLoop(key, null);
|
||||
exitNestedEventLoop(key);
|
||||
});
|
||||
Platform.enterNestedEventLoop(key);
|
||||
enterNestedEventLoop(key);
|
||||
}
|
||||
|
||||
public static void runLaterIfNeeded(Runnable r) {
|
||||
|
|
|
@ -23,7 +23,7 @@ public enum SecretQueryState {
|
|||
yield "Session is not interactive but required user input for authentication";
|
||||
}
|
||||
case FIXED_SECRET_WRONG -> {
|
||||
yield "Authentication failed: Provided authentication secret is wrong";
|
||||
yield "Authentication failed: Provided authentication secret was not accepted by the server, probably because it is incorrect";
|
||||
}
|
||||
case RETRIEVAL_FAILURE -> {
|
||||
yield "Failed to retrieve secret for authentication";
|
||||
|
|
|
@ -204,7 +204,7 @@ public class SshLocalBridge {
|
|||
var exec = CommandSupport.findProgram(sc, "sshd");
|
||||
if (exec.isEmpty()) {
|
||||
throw ErrorEvent.expected(new IllegalStateException(
|
||||
"No sshd executable found in PATH. The SSH terminal bridge requires a local ssh server"));
|
||||
"No sshd executable found in PATH. The SSH terminal bridge for SSH clients requires a local ssh server to be installed"));
|
||||
}
|
||||
return exec.get();
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ public class Validators {
|
|||
|
||||
public static <T extends DataStore> void isType(DataStoreEntryRef<? extends T> ref, Class<T> c)
|
||||
throws ValidationException {
|
||||
if (ref == null || !c.isAssignableFrom(ref.getStore().getClass())) {
|
||||
if (ref == null || ref.getStore() == null || !c.isAssignableFrom(ref.getStore().getClass())) {
|
||||
throw new ValidationException("Value must be an instance of " + c.getSimpleName());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,18 @@
|
|||
-fx-focus-color: transparent;
|
||||
}
|
||||
|
||||
.combo-box-base:hover .arrow-button:hover .arrow {
|
||||
-fx-background-color: -color-accent-fg;
|
||||
}
|
||||
|
||||
.identity-select-comp .clear-button:hover .ikonli-font-icon {
|
||||
-fx-icon-color: -color-accent-fg;
|
||||
}
|
||||
|
||||
.identity-select-comp .clear-button {
|
||||
-fx-background-color: transparent;
|
||||
}
|
||||
|
||||
.combo-box-popup .list-cell:hover, .combo-box-popup .list-cell:focused {
|
||||
-fx-background-color: -color-context-menu;
|
||||
}
|
||||
|
|
|
@ -145,7 +145,7 @@ project.ext {
|
|||
]
|
||||
|
||||
// GC config
|
||||
jvmRunArgs += ['-Xms200m', '-Xmx4G', '-XX:MinHeapFreeRatio=20', '-XX:MaxHeapFreeRatio=30', '-XX:GCTimeRatio=39'];
|
||||
jvmRunArgs += ['-XX:+UseG1GC', '-Xms300m', '-Xmx4G', '-XX:GCTimeRatio=9', '-XX:+UseStringDeduplication'];
|
||||
|
||||
if (org.gradle.internal.os.OperatingSystem.current().isMacOsX()) {
|
||||
jvmRunArgs += ["-Dapple.awt.application.appearance=system"]
|
||||
|
|
|
@ -11,9 +11,9 @@ public interface ElevationHandler {
|
|||
|
||||
@Override
|
||||
public boolean handleRequest(
|
||||
ShellControl parent, UUID requestId, CountDown countDown, boolean confirmIfNeeded) {
|
||||
var r = ElevationHandler.this.handleRequest(parent, requestId, countDown, confirmIfNeeded);
|
||||
return r || other.handleRequest(parent, requestId, countDown, confirmIfNeeded);
|
||||
UUID requestId, CountDown countDown, boolean confirmIfNeeded, boolean interactive) {
|
||||
var r = ElevationHandler.this.handleRequest(requestId, countDown, confirmIfNeeded, interactive);
|
||||
return r || other.handleRequest(requestId, countDown, confirmIfNeeded, interactive);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -24,7 +24,7 @@ public interface ElevationHandler {
|
|||
};
|
||||
}
|
||||
|
||||
boolean handleRequest(ShellControl parent, UUID requestId, CountDown countDown, boolean confirmIfNeeded);
|
||||
boolean handleRequest(UUID requestId, CountDown countDown, boolean confirmIfNeeded, boolean interactive);
|
||||
|
||||
SecretReference getSecretRef();
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@ import java.util.function.Function;
|
|||
|
||||
public interface ShellControl extends ProcessControl {
|
||||
|
||||
boolean isInitializing();
|
||||
|
||||
void setDumbOpen(ShellOpenFunction openFunction);
|
||||
|
||||
void setTerminalOpen(ShellOpenFunction openFunction);
|
||||
|
|
|
@ -215,6 +215,11 @@ public class WrapperShellControl implements ShellControl {
|
|||
return parent.getShellDialect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitializing() {
|
||||
return parent.isInitializing();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDumbOpen(ShellOpenFunction openFunction) {
|
||||
parent.setDumbOpen(openFunction);
|
||||
|
|
|
@ -57,10 +57,17 @@ public class XPipeInstallation {
|
|||
|
||||
@SneakyThrows
|
||||
public static Path getCurrentInstallationBasePath() {
|
||||
var command = ProcessHandle.current().info().command();
|
||||
// We should always have a command associated with the current process, otherwise something went seriously wrong
|
||||
if (command.isEmpty()) {
|
||||
var javaHome = System.getProperty("java.home");
|
||||
var javaExec = toRealPathIfPossible(Path.of(javaHome, "bin", "java"));
|
||||
var path = getLocalInstallationBasePathForJavaExecutable(javaExec);
|
||||
return path;
|
||||
}
|
||||
|
||||
// Resolve any possible links to a real path
|
||||
Path path = toRealPathIfPossible(
|
||||
Path.of(ProcessHandle.current().info().command().orElseThrow()));
|
||||
Path path = toRealPathIfPossible(Path.of(command.get()));
|
||||
// Check if the process was started using a relative path, and adapt it if necessary
|
||||
if (!path.isAbsolute()) {
|
||||
path = toRealPathIfPossible(Path.of(System.getProperty("user.dir")).resolve(path));
|
||||
|
|
66
dist/changelogs/15.5.md
vendored
Normal file
66
dist/changelogs/15.5.md
vendored
Normal file
|
@ -0,0 +1,66 @@
|
|||
XPipe 15 comes with many new features, performance improvements, and many bug fixes. Note that the installation data layout has been changed and executables have been moved around. This will break some shortcuts and the old restart functionality after an update. So if you're updating from within XPipe, it won't automatically restart for this update.
|
||||
|
||||
## Tailscale SSH support
|
||||
|
||||
You can now connect to devices in your tailnet via Tailscale SSH and your locally installed tailscale command-line client. This integration supports multiple accounts as well to switch between different tailnets.
|
||||
|
||||
## Custom icons
|
||||
|
||||
You can now add custom icons to use for your connections. This implementation replaces the old model of shipping the icons from https://github.com/selfhst/icons along XPipe. Instead, you can now dynamically add sources of icons. This can either be a local directory or a remote git repository that can be cloned and pulled by xpipe. XPipe will pick up any .svg files in there, rasterize them to cached .pngs, and display them in XPipe. As default icon sources, it will still come with https://github.com/selfhst/icons, but now it can fetch these icons at runtime. If you are using the git vault, you can also add icons to a synced directory in your git vault to have access to them on all systems.
|
||||
|
||||
Your existing custom icons set for connections are not lost, it just requires you to first update the icons and then restart XPipe.
|
||||
|
||||
## Package manager repositories
|
||||
|
||||
There is now an apt repository available at https://apt.xpipe.io and an rpm repository available at https://rpm.xpipe.io. You can add them as sources to apt or your rpm-based package manager. This allows you to also install and upgrade xpipe via your native package manager instead of using the built-in self-updater.
|
||||
|
||||
## New docs
|
||||
|
||||
There is a new documentation site https://docs.xpipe.io. The goal is to expand this over time to provide proper documentation for many features. If you're looking for documentation for a certain feature, let me know.
|
||||
|
||||
## Other
|
||||
|
||||
- Rework application styling
|
||||
- Improve performance when having many connections and categories
|
||||
- Add new action to run scripts in the file browser and show their output without having to open a terminal
|
||||
- The Homelab/Pro preview for new features is now handled automatically, you don't have to enable it anymore
|
||||
- You can now import saved PuTTY sessions on a system when searching for available connections. This also works for KiTTY
|
||||
- The custom service command opener will now use \$PORT instead of \$ADDRESS to allow for the use of commands that have a separate port argument
|
||||
- Add support for Gnome Console and Ptyxis Terminal
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fix user interface not being responsive for a few seconds after launch
|
||||
- Fix VSCode open actions not showing if code executable was not in PATH
|
||||
- Fix startup failure on Windows systems when vcredist140.dll was missing
|
||||
- Fix various issues with shells to Android systems
|
||||
- Fix issues on Linux systems where language en_US.UTF-8 was not available
|
||||
- Fix docked file browser terminal staying ahead of other windows even if XPipe loses focus
|
||||
- Fix permission denied errors on terminal launch when file system had noexec flag set
|
||||
- Fix git synced vault keys not working on other systems
|
||||
- Fix double sudo prompt when elevating to root in file browser
|
||||
- Fix file browser shift selection not marking selected files
|
||||
- Fix file browser yellow keyboard focus indicator showing after typing path
|
||||
- Fix ssh service tunnel sometimes failing with a timeout on close
|
||||
- Fix modal dialogs flickering a bit
|
||||
- Fix some icons resetting on updates
|
||||
- Fix desktop shortcuts not launching actions properly
|
||||
- Fix teleport integration failing for newer teleport versions
|
||||
- Fix MobaXterm integration not working correctly
|
||||
- Fix git sync SSH key password always prompting, even if it is specified in place
|
||||
- Fix creation dialog for scripts and identities still referring to the name as connection name
|
||||
- Fix password prompts for tunneled VM SSH connections showing wrong hostname as localhost
|
||||
- Fix Windows Terminal start up failing if it was the first time that it was launched on the system
|
||||
- Fix some translations not updating when changing display language
|
||||
- Fix custom service open command not working properly with PowerShell
|
||||
- Fix shortcut actions not running when daemon had to be started first
|
||||
- Fix browser directory list entering endless loop if directory contained symlink to itself
|
||||
- Fix certain actions like RDP failing when XPipe was launched from pwsh
|
||||
- Fix terminal selection defaulting to Wave if no other terminal is found
|
||||
- Fix vault version incompatibility notice only offering to disable the git sync
|
||||
- Fix several cases where adding/deleting vault users would corrupt vault data
|
||||
- Fix vault user encryption settings not updating properly
|
||||
- Fix shell init scripts being run multiple times when background shell session was active
|
||||
- Fix restart button not working for custom workspace locations
|
||||
- Fix git readme list updating each time when using a different vault user
|
||||
- Fix installation type detection being wrong when using installer and portable installation side by side
|
9
dist/changelogs/15.5_incremental.md
vendored
Normal file
9
dist/changelogs/15.5_incremental.md
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
- Fix connection hierarchy backgrounds not updating probably when expanding them
|
||||
- Fix some issues on macOS when zsh failed to start
|
||||
- Fix XPipe not falling back to sh in some cases on macOS when zsh failed to start
|
||||
- Fix vscode remote open functionality using wrong app path on macOS
|
||||
- Fix zsh module zsh/stat breaking some file browser functionality when enabled
|
||||
- Fix tailscale refresh operations failing with an out-of-bounds error in some cases
|
||||
- Fix some OS logos not showing correctly
|
||||
- Fix NullPointer when launching FreeRDP
|
||||
- Fix outdated manpages docs link
|
66
dist/changelogs/15.6.md
vendored
Normal file
66
dist/changelogs/15.6.md
vendored
Normal file
|
@ -0,0 +1,66 @@
|
|||
XPipe 15 comes with many new features, performance improvements, and many bug fixes. Note that the installation data layout has been changed and executables have been moved around. This will break some shortcuts and the old restart functionality after an update. So if you're updating from within XPipe, it won't automatically restart for this update.
|
||||
|
||||
## Tailscale SSH support
|
||||
|
||||
You can now connect to devices in your tailnet via Tailscale SSH and your locally installed tailscale command-line client. This integration supports multiple accounts as well to switch between different tailnets.
|
||||
|
||||
## Custom icons
|
||||
|
||||
You can now add custom icons to use for your connections. This implementation replaces the old model of shipping the icons from https://github.com/selfhst/icons along XPipe. Instead, you can now dynamically add sources of icons. This can either be a local directory or a remote git repository that can be cloned and pulled by xpipe. XPipe will pick up any .svg files in there, rasterize them to cached .pngs, and display them in XPipe. As default icon sources, it will still come with https://github.com/selfhst/icons, but now it can fetch these icons at runtime. If you are using the git vault, you can also add icons to a synced directory in your git vault to have access to them on all systems.
|
||||
|
||||
Your existing custom icons set for connections are not lost, it just requires you to first update the icons and then restart XPipe.
|
||||
|
||||
## Package manager repositories
|
||||
|
||||
There is now an apt repository available at https://apt.xpipe.io and an rpm repository available at https://rpm.xpipe.io. You can add them as sources to apt or your rpm-based package manager. This allows you to also install and upgrade xpipe via your native package manager instead of using the built-in self-updater.
|
||||
|
||||
## New docs
|
||||
|
||||
There is a new documentation site https://docs.xpipe.io. The goal is to expand this over time to provide proper documentation for many features. If you're looking for documentation for a certain feature, let me know.
|
||||
|
||||
## Other
|
||||
|
||||
- Rework application styling
|
||||
- Improve performance when having many connections and categories
|
||||
- Add new action to run scripts in the file browser and show their output without having to open a terminal
|
||||
- The Homelab/Pro preview for new features is now handled automatically, you don't have to enable it anymore
|
||||
- You can now import saved PuTTY sessions on a system when searching for available connections. This also works for KiTTY
|
||||
- The custom service command opener will now use \$PORT instead of \$ADDRESS to allow for the use of commands that have a separate port argument
|
||||
- Add support for Gnome Console and Ptyxis Terminal
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fix user interface not being responsive for a few seconds after launch
|
||||
- Fix VSCode open actions not showing if code executable was not in PATH
|
||||
- Fix startup failure on Windows systems when vcredist140.dll was missing
|
||||
- Fix various issues with shells to Android systems
|
||||
- Fix issues on Linux systems where language en_US.UTF-8 was not available
|
||||
- Fix docked file browser terminal staying ahead of other windows even if XPipe loses focus
|
||||
- Fix permission denied errors on terminal launch when file system had noexec flag set
|
||||
- Fix git synced vault keys not working on other systems
|
||||
- Fix double sudo prompt when elevating to root in file browser
|
||||
- Fix file browser shift selection not marking selected files
|
||||
- Fix file browser yellow keyboard focus indicator showing after typing path
|
||||
- Fix ssh service tunnel sometimes failing with a timeout on close
|
||||
- Fix modal dialogs flickering a bit
|
||||
- Fix some icons resetting on updates
|
||||
- Fix desktop shortcuts not launching actions properly
|
||||
- Fix teleport integration failing for newer teleport versions
|
||||
- Fix MobaXterm integration not working correctly
|
||||
- Fix git sync SSH key password always prompting, even if it is specified in place
|
||||
- Fix creation dialog for scripts and identities still referring to the name as connection name
|
||||
- Fix password prompts for tunneled VM SSH connections showing wrong hostname as localhost
|
||||
- Fix Windows Terminal start up failing if it was the first time that it was launched on the system
|
||||
- Fix some translations not updating when changing display language
|
||||
- Fix custom service open command not working properly with PowerShell
|
||||
- Fix shortcut actions not running when daemon had to be started first
|
||||
- Fix browser directory list entering endless loop if directory contained symlink to itself
|
||||
- Fix certain actions like RDP failing when XPipe was launched from pwsh
|
||||
- Fix terminal selection defaulting to Wave if no other terminal is found
|
||||
- Fix vault version incompatibility notice only offering to disable the git sync
|
||||
- Fix several cases where adding/deleting vault users would corrupt vault data
|
||||
- Fix vault user encryption settings not updating properly
|
||||
- Fix shell init scripts being run multiple times when background shell session was active
|
||||
- Fix restart button not working for custom workspace locations
|
||||
- Fix git readme list updating each time when using a different vault user
|
||||
- Fix installation type detection being wrong when using installer and portable installation side by side
|
8
dist/changelogs/15.6_incremental.md
vendored
Normal file
8
dist/changelogs/15.6_incremental.md
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
- Fix some cases where the application used excessive memory
|
||||
- Fix fish shell session scripts not getting added to PATH
|
||||
- Fix window becoming unusable on Linux when changing appearance settings while window was maximized
|
||||
- Fix various rare OutOfBounds exceptions that would break the GUI layout
|
||||
- Fix StackOverflow for certain sudo elevation requests for shell environments
|
||||
- Fix terminal launches failing for alpine LXD/incus containers
|
||||
- Fix NullPointer when pressing undo in a filter text field
|
||||
- Fix NumberFormatException when launching some xshell connections
|
66
dist/changelogs/15.7.1.md
vendored
Normal file
66
dist/changelogs/15.7.1.md
vendored
Normal file
|
@ -0,0 +1,66 @@
|
|||
XPipe 15 comes with many new features, performance improvements, and many bug fixes. Note that the installation data layout has been changed and executables have been moved around. This will break some shortcuts and the old restart functionality after an update. So if you're updating from within XPipe, it won't automatically restart for this update.
|
||||
|
||||
## Tailscale SSH support
|
||||
|
||||
You can now connect to devices in your tailnet via Tailscale SSH and your locally installed tailscale command-line client. This integration supports multiple accounts as well to switch between different tailnets.
|
||||
|
||||
## Custom icons
|
||||
|
||||
You can now add custom icons to use for your connections. This implementation replaces the old model of shipping the icons from https://github.com/selfhst/icons along XPipe. Instead, you can now dynamically add sources of icons. This can either be a local directory or a remote git repository that can be cloned and pulled by xpipe. XPipe will pick up any .svg files in there, rasterize them to cached .pngs, and display them in XPipe. As default icon sources, it will still come with https://github.com/selfhst/icons, but now it can fetch these icons at runtime. If you are using the git vault, you can also add icons to a synced directory in your git vault to have access to them on all systems.
|
||||
|
||||
Your existing custom icons set for connections are not lost, it just requires you to first update the icons and then restart XPipe.
|
||||
|
||||
## Package manager repositories
|
||||
|
||||
There is now an apt repository available at https://apt.xpipe.io and an rpm repository available at https://rpm.xpipe.io. You can add them as sources to apt or your rpm-based package manager. This allows you to also install and upgrade xpipe via your native package manager instead of using the built-in self-updater.
|
||||
|
||||
## New docs
|
||||
|
||||
There is a new documentation site https://docs.xpipe.io. The goal is to expand this over time to provide proper documentation for many features. If you're looking for documentation for a certain feature, let me know.
|
||||
|
||||
## Other
|
||||
|
||||
- Rework application styling
|
||||
- Improve performance when having many connections and categories
|
||||
- Add new action to run scripts in the file browser and show their output without having to open a terminal
|
||||
- The Homelab/Pro preview for new features is now handled automatically, you don't have to enable it anymore
|
||||
- You can now import saved PuTTY sessions on a system when searching for available connections. This also works for KiTTY
|
||||
- The custom service command opener will now use \$PORT instead of \$ADDRESS to allow for the use of commands that have a separate port argument
|
||||
- Add support for Gnome Console and Ptyxis Terminal
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fix user interface not being responsive for a few seconds after launch
|
||||
- Fix VSCode open actions not showing if code executable was not in PATH
|
||||
- Fix startup failure on Windows systems when vcredist140.dll was missing
|
||||
- Fix various issues with shells to Android systems
|
||||
- Fix issues on Linux systems where language en_US.UTF-8 was not available
|
||||
- Fix docked file browser terminal staying ahead of other windows even if XPipe loses focus
|
||||
- Fix permission denied errors on terminal launch when file system had noexec flag set
|
||||
- Fix git synced vault keys not working on other systems
|
||||
- Fix double sudo prompt when elevating to root in file browser
|
||||
- Fix file browser shift selection not marking selected files
|
||||
- Fix file browser yellow keyboard focus indicator showing after typing path
|
||||
- Fix ssh service tunnel sometimes failing with a timeout on close
|
||||
- Fix modal dialogs flickering a bit
|
||||
- Fix some icons resetting on updates
|
||||
- Fix desktop shortcuts not launching actions properly
|
||||
- Fix teleport integration failing for newer teleport versions
|
||||
- Fix MobaXterm integration not working correctly
|
||||
- Fix git sync SSH key password always prompting, even if it is specified in place
|
||||
- Fix creation dialog for scripts and identities still referring to the name as connection name
|
||||
- Fix password prompts for tunneled VM SSH connections showing wrong hostname as localhost
|
||||
- Fix Windows Terminal start up failing if it was the first time that it was launched on the system
|
||||
- Fix some translations not updating when changing display language
|
||||
- Fix custom service open command not working properly with PowerShell
|
||||
- Fix shortcut actions not running when daemon had to be started first
|
||||
- Fix browser directory list entering endless loop if directory contained symlink to itself
|
||||
- Fix certain actions like RDP failing when XPipe was launched from pwsh
|
||||
- Fix terminal selection defaulting to Wave if no other terminal is found
|
||||
- Fix vault version incompatibility notice only offering to disable the git sync
|
||||
- Fix several cases where adding/deleting vault users would corrupt vault data
|
||||
- Fix vault user encryption settings not updating properly
|
||||
- Fix shell init scripts being run multiple times when background shell session was active
|
||||
- Fix restart button not working for custom workspace locations
|
||||
- Fix git readme list updating each time when using a different vault user
|
||||
- Fix installation type detection being wrong when using installer and portable installation side by side
|
13
dist/changelogs/15.7.1_incremental.md
vendored
Normal file
13
dist/changelogs/15.7.1_incremental.md
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
## Performance
|
||||
|
||||
A severe performance regression was accidentally introduced in the recent 15.4 release. This release fixes this issue, so you will get much better performance in this version. It is recommended that you upgrade to 15.7.
|
||||
|
||||
While investigating, there were also a few other performance issues discovered that will be addressed in one of the next releases.
|
||||
|
||||
## Changes
|
||||
|
||||
- Add support for Warp on Windows and Linux
|
||||
- Fix right part of file browser becoming blocked after a tab is split
|
||||
- Fix tailscale refresh operations failing with an out-of-bounds error in some cases
|
||||
- Fix vmware .vmx failing to load if they had an unknown encoding
|
||||
- Fix some translations
|
66
dist/changelogs/15.7.md
vendored
Normal file
66
dist/changelogs/15.7.md
vendored
Normal file
|
@ -0,0 +1,66 @@
|
|||
XPipe 15 comes with many new features, performance improvements, and many bug fixes. Note that the installation data layout has been changed and executables have been moved around. This will break some shortcuts and the old restart functionality after an update. So if you're updating from within XPipe, it won't automatically restart for this update.
|
||||
|
||||
## Tailscale SSH support
|
||||
|
||||
You can now connect to devices in your tailnet via Tailscale SSH and your locally installed tailscale command-line client. This integration supports multiple accounts as well to switch between different tailnets.
|
||||
|
||||
## Custom icons
|
||||
|
||||
You can now add custom icons to use for your connections. This implementation replaces the old model of shipping the icons from https://github.com/selfhst/icons along XPipe. Instead, you can now dynamically add sources of icons. This can either be a local directory or a remote git repository that can be cloned and pulled by xpipe. XPipe will pick up any .svg files in there, rasterize them to cached .pngs, and display them in XPipe. As default icon sources, it will still come with https://github.com/selfhst/icons, but now it can fetch these icons at runtime. If you are using the git vault, you can also add icons to a synced directory in your git vault to have access to them on all systems.
|
||||
|
||||
Your existing custom icons set for connections are not lost, it just requires you to first update the icons and then restart XPipe.
|
||||
|
||||
## Package manager repositories
|
||||
|
||||
There is now an apt repository available at https://apt.xpipe.io and an rpm repository available at https://rpm.xpipe.io. You can add them as sources to apt or your rpm-based package manager. This allows you to also install and upgrade xpipe via your native package manager instead of using the built-in self-updater.
|
||||
|
||||
## New docs
|
||||
|
||||
There is a new documentation site https://docs.xpipe.io. The goal is to expand this over time to provide proper documentation for many features. If you're looking for documentation for a certain feature, let me know.
|
||||
|
||||
## Other
|
||||
|
||||
- Rework application styling
|
||||
- Improve performance when having many connections and categories
|
||||
- Add new action to run scripts in the file browser and show their output without having to open a terminal
|
||||
- The Homelab/Pro preview for new features is now handled automatically, you don't have to enable it anymore
|
||||
- You can now import saved PuTTY sessions on a system when searching for available connections. This also works for KiTTY
|
||||
- The custom service command opener will now use \$PORT instead of \$ADDRESS to allow for the use of commands that have a separate port argument
|
||||
- Add support for Gnome Console and Ptyxis Terminal
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fix user interface not being responsive for a few seconds after launch
|
||||
- Fix VSCode open actions not showing if code executable was not in PATH
|
||||
- Fix startup failure on Windows systems when vcredist140.dll was missing
|
||||
- Fix various issues with shells to Android systems
|
||||
- Fix issues on Linux systems where language en_US.UTF-8 was not available
|
||||
- Fix docked file browser terminal staying ahead of other windows even if XPipe loses focus
|
||||
- Fix permission denied errors on terminal launch when file system had noexec flag set
|
||||
- Fix git synced vault keys not working on other systems
|
||||
- Fix double sudo prompt when elevating to root in file browser
|
||||
- Fix file browser shift selection not marking selected files
|
||||
- Fix file browser yellow keyboard focus indicator showing after typing path
|
||||
- Fix ssh service tunnel sometimes failing with a timeout on close
|
||||
- Fix modal dialogs flickering a bit
|
||||
- Fix some icons resetting on updates
|
||||
- Fix desktop shortcuts not launching actions properly
|
||||
- Fix teleport integration failing for newer teleport versions
|
||||
- Fix MobaXterm integration not working correctly
|
||||
- Fix git sync SSH key password always prompting, even if it is specified in place
|
||||
- Fix creation dialog for scripts and identities still referring to the name as connection name
|
||||
- Fix password prompts for tunneled VM SSH connections showing wrong hostname as localhost
|
||||
- Fix Windows Terminal start up failing if it was the first time that it was launched on the system
|
||||
- Fix some translations not updating when changing display language
|
||||
- Fix custom service open command not working properly with PowerShell
|
||||
- Fix shortcut actions not running when daemon had to be started first
|
||||
- Fix browser directory list entering endless loop if directory contained symlink to itself
|
||||
- Fix certain actions like RDP failing when XPipe was launched from pwsh
|
||||
- Fix terminal selection defaulting to Wave if no other terminal is found
|
||||
- Fix vault version incompatibility notice only offering to disable the git sync
|
||||
- Fix several cases where adding/deleting vault users would corrupt vault data
|
||||
- Fix vault user encryption settings not updating properly
|
||||
- Fix shell init scripts being run multiple times when background shell session was active
|
||||
- Fix restart button not working for custom workspace locations
|
||||
- Fix git readme list updating each time when using a different vault user
|
||||
- Fix installation type detection being wrong when using installer and portable installation side by side
|
13
dist/changelogs/15.7_incremental.md
vendored
Normal file
13
dist/changelogs/15.7_incremental.md
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
## Performance
|
||||
|
||||
A severe performance regression was accidentally introduced in the recent 15.4 release. This release fixes this issue, so you will get much better performance in this version. It is recommended that you upgrade to 15.7.
|
||||
|
||||
While investigating, there were also a few other performance issues discovered that will be addressed in one of the next releases.
|
||||
|
||||
## Changes
|
||||
|
||||
- Add support for Warp on Windows and Linux
|
||||
- Fix right part of file browser becoming blocked after a tab is split
|
||||
- Fix tailscale refresh operations failing with an out-of-bounds error in some cases
|
||||
- Fix vmware .vmx failing to load if they had an unknown encoding
|
||||
- Fix some translations
|
66
dist/changelogs/15.8.md
vendored
Normal file
66
dist/changelogs/15.8.md
vendored
Normal file
|
@ -0,0 +1,66 @@
|
|||
XPipe 15 comes with many new features, performance improvements, and many bug fixes. Note that the installation data layout has been changed and executables have been moved around. This will break some shortcuts and the old restart functionality after an update. So if you're updating from within XPipe, it won't automatically restart for this update.
|
||||
|
||||
## Tailscale SSH support
|
||||
|
||||
You can now connect to devices in your tailnet via Tailscale SSH and your locally installed tailscale command-line client. This integration supports multiple accounts as well to switch between different tailnets.
|
||||
|
||||
## Custom icons
|
||||
|
||||
You can now add custom icons to use for your connections. This implementation replaces the old model of shipping the icons from https://github.com/selfhst/icons along XPipe. Instead, you can now dynamically add sources of icons. This can either be a local directory or a remote git repository that can be cloned and pulled by xpipe. XPipe will pick up any .svg files in there, rasterize them to cached .pngs, and display them in XPipe. As default icon sources, it will still come with https://github.com/selfhst/icons, but now it can fetch these icons at runtime. If you are using the git vault, you can also add icons to a synced directory in your git vault to have access to them on all systems.
|
||||
|
||||
Your existing custom icons set for connections are not lost, it just requires you to first update the icons and then restart XPipe.
|
||||
|
||||
## Package manager repositories
|
||||
|
||||
There is now an apt repository available at https://apt.xpipe.io and an rpm repository available at https://rpm.xpipe.io. You can add them as sources to apt or your rpm-based package manager. This allows you to also install and upgrade xpipe via your native package manager instead of using the built-in self-updater.
|
||||
|
||||
## New docs
|
||||
|
||||
There is a new documentation site https://docs.xpipe.io. The goal is to expand this over time to provide proper documentation for many features. If you're looking for documentation for a certain feature, let me know.
|
||||
|
||||
## Other
|
||||
|
||||
- Rework application styling
|
||||
- Improve performance when having many connections and categories
|
||||
- Add new action to run scripts in the file browser and show their output without having to open a terminal
|
||||
- The Homelab/Pro preview for new features is now handled automatically, you don't have to enable it anymore
|
||||
- You can now import saved PuTTY sessions on a system when searching for available connections. This also works for KiTTY
|
||||
- The custom service command opener will now use \$PORT instead of \$ADDRESS to allow for the use of commands that have a separate port argument
|
||||
- Add support for Gnome Console and Ptyxis Terminal
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fix user interface not being responsive for a few seconds after launch
|
||||
- Fix VSCode open actions not showing if code executable was not in PATH
|
||||
- Fix startup failure on Windows systems when vcredist140.dll was missing
|
||||
- Fix various issues with shells to Android systems
|
||||
- Fix issues on Linux systems where language en_US.UTF-8 was not available
|
||||
- Fix docked file browser terminal staying ahead of other windows even if XPipe loses focus
|
||||
- Fix permission denied errors on terminal launch when file system had noexec flag set
|
||||
- Fix git synced vault keys not working on other systems
|
||||
- Fix double sudo prompt when elevating to root in file browser
|
||||
- Fix file browser shift selection not marking selected files
|
||||
- Fix file browser yellow keyboard focus indicator showing after typing path
|
||||
- Fix ssh service tunnel sometimes failing with a timeout on close
|
||||
- Fix modal dialogs flickering a bit
|
||||
- Fix some icons resetting on updates
|
||||
- Fix desktop shortcuts not launching actions properly
|
||||
- Fix teleport integration failing for newer teleport versions
|
||||
- Fix MobaXterm integration not working correctly
|
||||
- Fix git sync SSH key password always prompting, even if it is specified in place
|
||||
- Fix creation dialog for scripts and identities still referring to the name as connection name
|
||||
- Fix password prompts for tunneled VM SSH connections showing wrong hostname as localhost
|
||||
- Fix Windows Terminal start up failing if it was the first time that it was launched on the system
|
||||
- Fix some translations not updating when changing display language
|
||||
- Fix custom service open command not working properly with PowerShell
|
||||
- Fix shortcut actions not running when daemon had to be started first
|
||||
- Fix browser directory list entering endless loop if directory contained symlink to itself
|
||||
- Fix certain actions like RDP failing when XPipe was launched from pwsh
|
||||
- Fix terminal selection defaulting to Wave if no other terminal is found
|
||||
- Fix vault version incompatibility notice only offering to disable the git sync
|
||||
- Fix several cases where adding/deleting vault users would corrupt vault data
|
||||
- Fix vault user encryption settings not updating properly
|
||||
- Fix shell init scripts being run multiple times when background shell session was active
|
||||
- Fix restart button not working for custom workspace locations
|
||||
- Fix git readme list updating each time when using a different vault user
|
||||
- Fix installation type detection being wrong when using installer and portable installation side by side
|
20
dist/changelogs/15.8_incremental.md
vendored
Normal file
20
dist/changelogs/15.8_incremental.md
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
- Add ability to launch custom terminal-based editors in the custom editor settings. This makes it much easier to use editors like nano or vim
|
||||
- Fix PowerShell-based shell sessions freezing after some time due to a wrong $ErrorActionPreference. This issue especially broke local machine shell sessions on Windows system with PowerShell being the default shell
|
||||
- Fix VNC connections with 24-bit color depths getting rendered with switched colors
|
||||
- Fix SSH key permissions being changed to 400 even if the key had compatible permissions
|
||||
- Fix SSH askpass failing for portable installations in a directory with non-ASCII characters due to an OpenSSH bug
|
||||
- Fix random ConcurrentModificationExceptions breaking the gui layout
|
||||
- Fix file browser download box buttons being usable and potentially misleading while a download was in progress
|
||||
- Fix startup error when process information access was blocked on Windows
|
||||
- Fix SSH config connections not allowing to specify an inline identity username
|
||||
- Fix SSH config connections not applying username specified for the identity
|
||||
- Fix SSH config parse error when all connections were set to be a tsh ProxyCommand
|
||||
- Fix LXD unsupported flag errors frequently showing up when searching for connections
|
||||
- Fix various rendering issues with svg icons
|
||||
- Fix errors when pressing undo or pasting into a port integer text field
|
||||
- Fix terminal logging staying enabled after showing notice that it is not available
|
||||
- Fix connection icon selection not working well with keyboard
|
||||
- Fix some directories failing to show in file browser on Windows systems
|
||||
- Fix directories failing to open in native file manager in the webtop container
|
||||
- Fix application window always taking focus after starting up
|
||||
- Fix many chinese translations
|
|
@ -22,7 +22,7 @@ public class IdentityChoice {
|
|||
ObjectProperty<IdentityValue> identity,
|
||||
boolean requireUser) {
|
||||
var i = new IdentityChoice(
|
||||
gateway, identity, requireUser, requireUser, true, true, true, "identityChoice", "passwordAuthentication");
|
||||
gateway, identity, true, requireUser, true, true, true, "identityChoice", "passwordAuthentication");
|
||||
return i.build();
|
||||
}
|
||||
|
||||
|
|
|
@ -3,12 +3,11 @@ package io.xpipe.ext.base.identity;
|
|||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
import io.xpipe.app.comp.base.ButtonComp;
|
||||
import io.xpipe.app.comp.base.ComboTextFieldComp;
|
||||
import io.xpipe.app.comp.base.HorizontalComp;
|
||||
import io.xpipe.app.comp.base.*;
|
||||
import io.xpipe.app.comp.store.StoreCreationComp;
|
||||
import io.xpipe.app.comp.store.StoreEntryWrapper;
|
||||
import io.xpipe.app.comp.store.StoreViewState;
|
||||
import io.xpipe.app.core.AppFontSizes;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.ext.DataStoreCreationCategory;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
|
@ -29,12 +28,16 @@ import javafx.collections.ListChangeListener;
|
|||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class IdentitySelectComp extends Comp<CompStructure<HBox>> {
|
||||
|
||||
|
@ -201,6 +204,8 @@ public class IdentitySelectComp extends Comp<CompStructure<HBox>> {
|
|||
var s = formatName(newValue.get());
|
||||
prop.setValue(s);
|
||||
});
|
||||
} else {
|
||||
prop.setValue(null);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -218,7 +223,6 @@ public class IdentitySelectComp extends Comp<CompStructure<HBox>> {
|
|||
};
|
||||
});
|
||||
combo.apply(struc -> struc.get().setEditable(allowUserInput));
|
||||
combo.hgrow();
|
||||
combo.styleClass(Styles.LEFT_PILL);
|
||||
combo.grow(false, true);
|
||||
combo.apply(struc -> {
|
||||
|
@ -258,6 +262,32 @@ public class IdentitySelectComp extends Comp<CompStructure<HBox>> {
|
|||
});
|
||||
});
|
||||
|
||||
return combo;
|
||||
var clearButton = new IconButtonComp("mdi2c-close", () -> {
|
||||
selectedReference.setValue(null);
|
||||
inPlaceUser.setValue(null);
|
||||
});
|
||||
clearButton.styleClass(Styles.FLAT);
|
||||
clearButton.hide(selectedReference.isNull());
|
||||
clearButton.apply(struc -> {
|
||||
struc.get().setOpacity(0.7);
|
||||
struc.get().getStyleClass().add("clear-button");
|
||||
AppFontSizes.xs(struc.get());
|
||||
AnchorPane.setRightAnchor(struc.get(), 30.0);
|
||||
AnchorPane.setTopAnchor(struc.get(), 3.0);
|
||||
AnchorPane.setBottomAnchor(struc.get(), 3.0);
|
||||
});
|
||||
|
||||
var stack = new AnchorComp(List.of(combo, clearButton));
|
||||
stack.styleClass("identity-select-comp");
|
||||
stack.hgrow();
|
||||
stack.apply(struc -> {
|
||||
var comboRegion = (Region) struc.get().getChildren().getFirst();
|
||||
struc.get().prefWidthProperty().bind(comboRegion.prefWidthProperty());
|
||||
struc.get().prefHeightProperty().bind(comboRegion.prefHeightProperty());
|
||||
AnchorPane.setLeftAnchor(comboRegion, 0.0);
|
||||
AnchorPane.setRightAnchor(comboRegion, 0.0);
|
||||
});
|
||||
|
||||
return stack;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -194,7 +194,9 @@ public interface SshIdentityStrategy {
|
|||
}
|
||||
|
||||
if ((parent.getOsType().equals(OsType.LINUX) || parent.getOsType().equals(OsType.MACOS))) {
|
||||
parent.command(CommandBuilder.of().add("chmod", "400").addFile(resolved)).executeAndCheck();
|
||||
// Try to preserve the same permission set
|
||||
parent.command(CommandBuilder.of().add("test", "-w").addFile(resolved).add("&&", "chmod", "600")
|
||||
.addFile(resolved).add("||", "chmod", "400").addFile(resolved)).executeAndCheck();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import io.xpipe.app.browser.action.BrowserBranchAction;
|
|||
import io.xpipe.app.browser.action.BrowserLeafAction;
|
||||
import io.xpipe.app.browser.file.BrowserEntry;
|
||||
import io.xpipe.app.browser.file.BrowserFileSystemTabModel;
|
||||
import io.xpipe.app.comp.base.PrettyImageHelper;
|
||||
import io.xpipe.app.comp.store.StoreViewState;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.AppLayoutModel;
|
||||
|
@ -92,6 +93,11 @@ public class RunScriptAction implements BrowserAction, BrowserBranchAction {
|
|||
.map(c -> createActionForScriptHierarchy(model, c))
|
||||
.toList();
|
||||
return new BrowserBranchAction() {
|
||||
@Override
|
||||
public Node getIcon(BrowserFileSystemTabModel model, List<BrowserEntry> entries) {
|
||||
return PrettyImageHelper.ofFixedSize(hierarchy.getBase().get().getEffectiveIconFile(), 16, 16).createRegion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends BrowserAction> getBranchingActions(
|
||||
BrowserFileSystemTabModel model, List<BrowserEntry> entries) {
|
||||
|
@ -110,6 +116,11 @@ public class RunScriptAction implements BrowserAction, BrowserBranchAction {
|
|||
BrowserFileSystemTabModel model, DataStoreEntryRef<SimpleScriptStore> ref) {
|
||||
return new MultiExecuteSelectionAction() {
|
||||
|
||||
@Override
|
||||
public Node getIcon(BrowserFileSystemTabModel model, List<BrowserEntry> entries) {
|
||||
return PrettyImageHelper.ofFixedSize(ref.get().getEffectiveIconFile(), 16, 16).createRegion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObservableValue<String> getName(BrowserFileSystemTabModel model, List<BrowserEntry> entries) {
|
||||
return new SimpleStringProperty(ref.get().getName());
|
||||
|
|
|
@ -35,6 +35,14 @@ public class ScriptGroupStore extends ScriptStore implements GroupStore<ScriptSt
|
|||
});
|
||||
}
|
||||
|
||||
public List<DataStoreEntryRef<ScriptStore>> getImmediateChildrenScripts() {
|
||||
var self = getSelfEntry();
|
||||
return DataStorage.get().getStoreChildren(self).stream()
|
||||
.filter(entry -> entry.getValidity().isUsable())
|
||||
.map(dataStoreEntry -> dataStoreEntry.<ScriptStore>ref())
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DataStoreEntryRef<ScriptStore>> getEffectiveScripts() {
|
||||
var self = getSelfEntry();
|
||||
|
|
|
@ -69,7 +69,7 @@ public class ScriptHierarchy {
|
|||
private static ScriptHierarchy buildHierarchy(
|
||||
DataStoreEntryRef<ScriptStore> ref, Predicate<DataStoreEntryRef<ScriptStore>> include) {
|
||||
if (ref.getStore() instanceof ScriptGroupStore groupStore) {
|
||||
var children = groupStore.getEffectiveScripts().stream()
|
||||
var children = groupStore.getImmediateChildrenScripts().stream()
|
||||
.filter(include)
|
||||
.map(c -> buildHierarchy(c, include))
|
||||
.filter(hierarchy -> hierarchy.show())
|
||||
|
|
|
@ -73,7 +73,7 @@ public abstract class ScriptStore implements DataStore, StatefulDataStore<Enable
|
|||
}
|
||||
|
||||
return Optional.ofNullable(
|
||||
shellControl.getShellDialect().addToPathVariableCommand(List.of(dir), true));
|
||||
shellControl.getOriginalShellDialect().addToPathVariableCommand(List.of(dir), true));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -104,7 +104,7 @@ public abstract class ScriptStore implements DataStore, StatefulDataStore<Enable
|
|||
|
||||
var applicable = refs.stream()
|
||||
.filter(simpleScriptStore ->
|
||||
simpleScriptStore.getStore().getMinimumDialect().isCompatibleTo(proc.getShellDialect()))
|
||||
simpleScriptStore.getStore().getMinimumDialect().isCompatibleTo(proc.getOriginalShellDialect()))
|
||||
.toList();
|
||||
if (applicable.isEmpty()) {
|
||||
return null;
|
||||
|
@ -115,7 +115,7 @@ public abstract class ScriptStore implements DataStore, StatefulDataStore<Enable
|
|||
value.get().getName().hashCode() + value.getStore().hashCode())
|
||||
.sum();
|
||||
var targetDir = ShellTemp.createUserSpecificTempDataDirectory(proc, "scripts")
|
||||
.join(proc.getShellDialect().getId())
|
||||
.join(proc.getOriginalShellDialect().getId())
|
||||
.toString();
|
||||
var hashFile = FileNames.join(targetDir, "hash");
|
||||
var d = proc.getShellDialect();
|
||||
|
|
|
@ -24,6 +24,8 @@ public abstract class AbstractServiceGroupStore<T extends DataStore> implements
|
|||
@Override
|
||||
public void checkComplete() throws Throwable {
|
||||
Validators.nonNull(parent);
|
||||
// Essentially a null check
|
||||
Validators.isType(parent, DataStore.class);
|
||||
parent.checkComplete();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
package io.xpipe.ext.system.incus;
|
||||
|
||||
import io.xpipe.app.ext.ContainerStoreState;
|
||||
import io.xpipe.app.ext.ShellStore;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.app.util.CommandViewBase;
|
||||
import io.xpipe.core.process.*;
|
||||
|
||||
import io.xpipe.core.store.StatefulDataStore;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class IncusCommandView extends CommandViewBase {
|
||||
|
@ -138,16 +141,16 @@ public class IncusCommandView extends CommandViewBase {
|
|||
}
|
||||
}
|
||||
|
||||
public ShellControl exec(String container, String user) {
|
||||
public ShellControl exec(String container, String user, Supplier<Boolean> busybox) {
|
||||
var sub = shellControl.subShell();
|
||||
sub.setDumbOpen(createOpenFunction(container, user, false));
|
||||
sub.setTerminalOpen(createOpenFunction(container, user, true));
|
||||
sub.setDumbOpen(createOpenFunction(container, user, false, busybox));
|
||||
sub.setTerminalOpen(createOpenFunction(container, user, true, busybox));
|
||||
return sub.withErrorFormatter(IncusCommandView::formatErrorMessage)
|
||||
.withExceptionConverter(IncusCommandView::convertException)
|
||||
.elevated(requiresElevation());
|
||||
}
|
||||
|
||||
private ShellOpenFunction createOpenFunction(String containerName, String user, boolean terminal) {
|
||||
private ShellOpenFunction createOpenFunction(String containerName, String user, boolean terminal, Supplier<Boolean> busybox) {
|
||||
return new ShellOpenFunction() {
|
||||
@Override
|
||||
public CommandBuilder prepareWithoutInitCommand() {
|
||||
|
@ -164,7 +167,14 @@ public class IncusCommandView extends CommandViewBase {
|
|||
if (user != null) {
|
||||
b.addQuoted(user);
|
||||
}
|
||||
return b.add("--session-command").addLiteral(command);
|
||||
return b.add(sc -> {
|
||||
var suType = busybox.get();
|
||||
if (suType) {
|
||||
return "-c";
|
||||
} else {
|
||||
return "--session-command";
|
||||
}
|
||||
}).addLiteral(command);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import io.xpipe.app.ext.ShellStore;
|
|||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.app.util.*;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import io.xpipe.core.process.ShellStoreState;
|
||||
import io.xpipe.core.store.FixedChildStore;
|
||||
import io.xpipe.core.store.StatefulDataStore;
|
||||
import io.xpipe.ext.base.identity.IdentityValue;
|
||||
|
@ -75,7 +76,11 @@ public class IncusContainerStore
|
|||
@Override
|
||||
public ShellControl control(ShellControl parent) {
|
||||
var user = identity != null ? identity.unwrap().getUsername() : null;
|
||||
var sc = new IncusCommandView(parent).exec(containerName, user);
|
||||
var sc = new IncusCommandView(parent).exec(containerName, user, () -> {
|
||||
var state = getState();
|
||||
var alpine = state.getOsName() != null && state.getOsName().toLowerCase().contains("alpine");
|
||||
return alpine;
|
||||
});
|
||||
sc.withSourceStore(IncusContainerStore.this);
|
||||
if (identity != null && identity.unwrap().getPassword() != null) {
|
||||
sc.setElevationHandler(new BaseElevationHandler(
|
||||
|
|
|
@ -11,6 +11,7 @@ import lombok.NonNull;
|
|||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class LxdCommandView extends CommandViewBase {
|
||||
|
@ -150,16 +151,16 @@ public class LxdCommandView extends CommandViewBase {
|
|||
}
|
||||
}
|
||||
|
||||
public ShellControl exec(String container, String user) {
|
||||
public ShellControl exec(String container, String user, Supplier<Boolean> busybox) {
|
||||
var sub = shellControl.subShell();
|
||||
sub.setDumbOpen(createOpenFunction(container, user, false));
|
||||
sub.setTerminalOpen(createOpenFunction(container, user, true));
|
||||
sub.setDumbOpen(createOpenFunction(container, user, false, busybox));
|
||||
sub.setTerminalOpen(createOpenFunction(container, user, true, busybox));
|
||||
return sub.withErrorFormatter(LxdCommandView::formatErrorMessage)
|
||||
.withExceptionConverter(LxdCommandView::convertException)
|
||||
.elevated(requiresElevation());
|
||||
}
|
||||
|
||||
private ShellOpenFunction createOpenFunction(String containerName, String user, boolean terminal) {
|
||||
private ShellOpenFunction createOpenFunction(String containerName, String user, boolean terminal, Supplier<Boolean> busybox) {
|
||||
return new ShellOpenFunction() {
|
||||
@Override
|
||||
public CommandBuilder prepareWithoutInitCommand() {
|
||||
|
@ -176,7 +177,14 @@ public class LxdCommandView extends CommandViewBase {
|
|||
if (user != null) {
|
||||
b.addQuoted(user);
|
||||
}
|
||||
return b.add("--session-command").addLiteral(command);
|
||||
return b.add(sc -> {
|
||||
var suType = busybox.get();
|
||||
if (suType) {
|
||||
return "-c";
|
||||
} else {
|
||||
return "--session-command";
|
||||
}
|
||||
}).addLiteral(command);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -73,7 +73,11 @@ public class LxdContainerStore
|
|||
@Override
|
||||
public ShellControl control(ShellControl parent) {
|
||||
var user = identity != null ? identity.unwrap().getUsername() : null;
|
||||
var base = new LxdCommandView(parent).exec(containerName, user);
|
||||
var base = new LxdCommandView(parent).exec(containerName, user, () -> {
|
||||
var state = getState();
|
||||
var alpine = state.getOsName() != null && state.getOsName().toLowerCase().contains("alpine");
|
||||
return alpine;
|
||||
});
|
||||
if (identity != null && identity.unwrap().getPassword() != null) {
|
||||
base.setElevationHandler(new BaseElevationHandler(
|
||||
LxdContainerStore.this, identity.unwrap().getPassword())
|
||||
|
|
|
@ -4,6 +4,7 @@ import io.xpipe.app.ext.ScanProvider;
|
|||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.process.ProcessOutputException;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
|
||||
public class LxdScanProvider extends ScanProvider {
|
||||
|
@ -18,12 +19,18 @@ public class LxdScanProvider extends ScanProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void scan(DataStoreEntry entry, ShellControl sc) {
|
||||
public void scan(DataStoreEntry entry, ShellControl sc) throws Exception {
|
||||
var e = DataStorage.get()
|
||||
.addStoreIfNotPresent(
|
||||
entry,
|
||||
"LXD containers",
|
||||
LxdCmdStore.builder().host(entry.ref()).build());
|
||||
DataStorage.get().refreshChildren(e);
|
||||
try {
|
||||
DataStorage.get().refreshChildrenOrThrow(e);
|
||||
} catch (ProcessOutputException ex) {
|
||||
if (!ex.getOutput().contains("unknown shorthand flag: 'f' in -f")) {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
2
lang/strings/translations_da.properties
generated
2
lang/strings/translations_da.properties
generated
|
@ -1335,3 +1335,5 @@ externalLaunchTitle=Anmodning om ekstern lancering
|
|||
externalLaunchContent=En ekstern terminal har anmodet om at starte en shell-forbindelse. Vil du tillade, at der oprettes shell-forbindelser uden for XPipe?
|
||||
noScriptStateAvailable=Opdater for at bestemme scriptkompatibilitet ...
|
||||
documentationDescription=Tjek dokumentationen ud
|
||||
customEditorCommandInTerminal=Kør en brugerdefineret kommando i en terminal
|
||||
customEditorCommandInTerminalDescription=Hvis din editor er terminalbaseret, kan du aktivere denne mulighed for automatisk at åbne en terminal og køre kommandoen i terminalsessionen i stedet.\n\nDu kan bruge denne indstilling til editorer som vi, vim, nvim og andre.
|
||||
|
|
2
lang/strings/translations_de.properties
generated
2
lang/strings/translations_de.properties
generated
|
@ -1319,3 +1319,5 @@ externalLaunchTitle=Externe Startanforderung
|
|||
externalLaunchContent=Ein externes Terminal hat angefragt, eine Shell-Verbindung zu starten. Willst du das Starten von Shell-Verbindungen von außerhalb von XPipe erlauben?
|
||||
noScriptStateAvailable=Aktualisieren, um die Skriptkompatibilität zu bestimmen ...
|
||||
documentationDescription=Schau dir die Dokumentation an
|
||||
customEditorCommandInTerminal=Benutzerdefinierten Befehl in einem Terminal ausführen
|
||||
customEditorCommandInTerminalDescription=Wenn dein Editor terminalbasiert ist, kannst du diese Option aktivieren, um automatisch ein Terminal zu öffnen und den Befehl stattdessen in der Terminalsitzung auszuführen.\n\nDu kannst diese Option für Editoren wie vi, vim, nvim und andere verwenden.
|
||||
|
|
5
lang/strings/translations_en.properties
generated
5
lang/strings/translations_en.properties
generated
|
@ -40,6 +40,7 @@ selectTypeDescription=Select connection type
|
|||
selectShellType=Shell Type
|
||||
#context: computer shell program
|
||||
selectShellTypeDescription=Select the Type of the Shell Connection
|
||||
#context: name of an inanimate or abstract object
|
||||
name=Name
|
||||
storeIntroTitle=Connection Hub
|
||||
storeIntroDescription=Here you can manage all your local and remote shell connections in one place. To start off, you can quickly detect available connections automatically and choose which ones to add.
|
||||
|
@ -1193,6 +1194,7 @@ lockCreationAlertHeader=Create new vault user
|
|||
loginAlertTitle=Login required
|
||||
loginAlertHeader=Unlock vault to access your personal connections
|
||||
vaultUser=Vault user
|
||||
#context: dative case
|
||||
me=Me
|
||||
addUser=Add user ...
|
||||
addUserDescription=Create a new user for this vault
|
||||
|
@ -1280,6 +1282,7 @@ serviceProtocolType=Service protocol type
|
|||
serviceProtocolTypeDescription=Control how to open the service
|
||||
serviceCommand=The command to run once the service is active
|
||||
serviceCommandDescription=The placeholder $PORT will be replaced with the actual tunneled local port
|
||||
#context: not the measure of importance or personal ethics
|
||||
value=Value
|
||||
showAdvancedOptions=Show advanced options
|
||||
sshAdditionalConfigOptions=Additional config options
|
||||
|
@ -1347,3 +1350,5 @@ externalLaunchTitle=External launch request
|
|||
externalLaunchContent=An external terminal has requested to launch a shell connection. Do you want to allow launching shell connections from outside XPipe?
|
||||
noScriptStateAvailable=Refresh to determine script compatibility ...
|
||||
documentationDescription=Check out the documentation
|
||||
customEditorCommandInTerminal=Run custom command in a terminal
|
||||
customEditorCommandInTerminalDescription=If your editor is terminal-based, you can enable this option to automatically open a terminal and run the command in the terminal session instead.\n\nYou can use this option for editors like vi, vim, nvim, and others.
|
||||
|
|
2
lang/strings/translations_es.properties
generated
2
lang/strings/translations_es.properties
generated
|
@ -1288,3 +1288,5 @@ externalLaunchTitle=Solicitud de lanzamiento externo
|
|||
externalLaunchContent=Un terminal externo ha solicitado lanzar una conexión shell. ¿Quieres permitir el lanzamiento de conexiones shell desde fuera de XPipe?
|
||||
noScriptStateAvailable=Actualizar para determinar la compatibilidad del script ...
|
||||
documentationDescription=Consulta la documentación
|
||||
customEditorCommandInTerminal=Ejecutar un comando personalizado en un terminal
|
||||
customEditorCommandInTerminalDescription=Si tu editor está basado en un terminal, puedes activar esta opción para abrir automáticamente un terminal y ejecutar el comando en la sesión del terminal en su lugar.\n\nPuedes utilizar esta opción para editores como vi, vim, nvim y otros.
|
||||
|
|
10
lang/strings/translations_fr.properties
generated
10
lang/strings/translations_fr.properties
generated
|
@ -18,7 +18,7 @@ addShellTitle=Ajouter une connexion Shell
|
|||
savedConnections=Connexions sauvegardées
|
||||
save=Sauvegarde
|
||||
clean=Nettoyer
|
||||
moveTo=Aller à ...
|
||||
moveTo=Déplacer vers ...
|
||||
addDatabase=Base de données ...
|
||||
browseInternalStorage=Parcourir le stockage interne
|
||||
addTunnel=Tunnel ...
|
||||
|
@ -260,7 +260,7 @@ editorProgramDescription=L'éditeur de texte par défaut à utiliser lors de l'
|
|||
windowOpacity=Opacité de la fenêtre
|
||||
windowOpacityDescription=Modifie l'opacité de la fenêtre pour suivre ce qui se passe en arrière-plan.
|
||||
useSystemFont=Utiliser la police du système
|
||||
openDataDir=Répertoire de données de voûte
|
||||
openDataDir=Répertoire de données du coffre-fort
|
||||
openDataDirButton=Répertoire de données ouvertes
|
||||
openDataDirDescription=Si tu veux synchroniser des fichiers supplémentaires, comme les clés SSH, entre les systèmes avec ton dépôt git, tu peux les mettre dans le répertoire storage data. Tous les fichiers qui y sont référencés verront leur chemin de fichier automatiquement adapté sur n'importe quel système synchronisé.
|
||||
updates=Mises à jour
|
||||
|
@ -421,7 +421,7 @@ lockVaultOnHibernation=Verrouille le coffre-fort lors de la mise en veille de l'
|
|||
#custom
|
||||
lockVaultOnHibernationDescription=Lorsque cette option est activée, le coffre-fort sera automatiquement verrouillé une fois que ton ordinateur sera mis en veille. Au réveil, tu devras saisir à nouveau la phrase de passe de ton coffre-fort.
|
||||
overview=Vue d'ensemble
|
||||
history=Histoire
|
||||
history=Historique
|
||||
skipAll=Sauter tout
|
||||
notes=Notes
|
||||
addNotes=Ajouter des notes
|
||||
|
@ -543,7 +543,7 @@ userName=Nom d'utilisateur
|
|||
team=L'équipe
|
||||
teamSettings=Paramètres de l'équipe
|
||||
teamVaults=Coffres-forts d'équipe
|
||||
vaultTypeNameDefault=Voûte par défaut
|
||||
vaultTypeNameDefault=Coffre-fort par défaut
|
||||
vaultTypeNameLegacy=Coffre-fort personnel hérité
|
||||
vaultTypeNamePersonal=Coffre-fort personnel
|
||||
vaultTypeNameTeam=Coffre-fort de l'équipe
|
||||
|
@ -1323,3 +1323,5 @@ externalLaunchTitle=Demande de lancement externe
|
|||
externalLaunchContent=Un terminal externe a demandé à lancer une connexion shell. Veux-tu autoriser le lancement de connexions shell depuis l'extérieur de XPipe ?
|
||||
noScriptStateAvailable=Actualise pour déterminer la compatibilité des scripts...
|
||||
documentationDescription=Vérifie la documentation
|
||||
customEditorCommandInTerminal=Exécuter une commande personnalisée dans un terminal
|
||||
customEditorCommandInTerminalDescription=Si ton éditeur est basé sur un terminal, tu peux activer cette option pour ouvrir automatiquement un terminal et exécuter la commande dans la session du terminal à la place.\n\nTu peux utiliser cette option pour des éditeurs comme vi, vim, nvim et d'autres.
|
||||
|
|
2
lang/strings/translations_id.properties
generated
2
lang/strings/translations_id.properties
generated
|
@ -1288,3 +1288,5 @@ externalLaunchTitle=Permintaan peluncuran eksternal
|
|||
externalLaunchContent=Terminal eksternal telah meminta untuk meluncurkan koneksi shell. Apakah Anda ingin mengizinkan peluncuran koneksi shell dari luar XPipe?
|
||||
noScriptStateAvailable=Menyegarkan untuk menentukan kompatibilitas skrip ...
|
||||
documentationDescription=Lihat dokumentasi
|
||||
customEditorCommandInTerminal=Menjalankan perintah khusus di terminal
|
||||
customEditorCommandInTerminalDescription=Jika editor Anda berbasis terminal, Anda dapat mengaktifkan opsi ini untuk secara otomatis membuka terminal dan menjalankan perintah dalam sesi terminal.\n\nAnda dapat menggunakan opsi ini untuk editor seperti vi, vim, nvim, dan lainnya.
|
||||
|
|
2
lang/strings/translations_it.properties
generated
2
lang/strings/translations_it.properties
generated
|
@ -1288,3 +1288,5 @@ externalLaunchTitle=Richiesta di lancio esterno
|
|||
externalLaunchContent=Un terminale esterno ha chiesto di lanciare una connessione shell. Vuoi consentire l'avvio di connessioni shell dall'esterno di XPipe?
|
||||
noScriptStateAvailable=Aggiorna per determinare la compatibilità dello script ...
|
||||
documentationDescription=Controlla la documentazione
|
||||
customEditorCommandInTerminal=Eseguire un comando personalizzato in un terminale
|
||||
customEditorCommandInTerminalDescription=Se il tuo editor è basato su un terminale, puoi attivare questa opzione per aprire automaticamente un terminale ed eseguire il comando nella sessione del terminale.\n\nPuoi utilizzare questa opzione per editor come vi, vim, nvim e altri.
|
||||
|
|
2
lang/strings/translations_ja.properties
generated
2
lang/strings/translations_ja.properties
generated
|
@ -1288,3 +1288,5 @@ externalLaunchTitle=外部起動要求
|
|||
externalLaunchContent=外部端末がシェル接続の起動を要求してきた。XPipeの外部からのシェル接続の起動を許可するか。
|
||||
noScriptStateAvailable=スクリプトの互換性を判断するためにリフレッシュする...
|
||||
documentationDescription=ドキュメントをチェックする
|
||||
customEditorCommandInTerminal=ターミナルでカスタムコマンドを実行する
|
||||
customEditorCommandInTerminalDescription=エディターがターミナルベースの場合、このオプションを有効にすると、自動的にターミナルが開き、代わりにターミナルセッションでコマンドが実行される。\n\nこのオプションは、vi、vim、nvimなどのエディタに使用できる。
|
||||
|
|
2
lang/strings/translations_nl.properties
generated
2
lang/strings/translations_nl.properties
generated
|
@ -1288,3 +1288,5 @@ externalLaunchTitle=Extern lanceringsverzoek
|
|||
externalLaunchContent=Een externe terminal heeft gevraagd om een shellverbinding te starten. Wil je het starten van shellverbindingen van buiten XPipe toestaan?
|
||||
noScriptStateAvailable=Vernieuwen om scriptcompatibiliteit te bepalen ...
|
||||
documentationDescription=Bekijk de documentatie
|
||||
customEditorCommandInTerminal=Een aangepast commando uitvoeren in een terminal
|
||||
customEditorCommandInTerminalDescription=Als je editor terminalgebaseerd is, kun je deze optie inschakelen om automatisch een terminal te openen en in plaats daarvan het commando in de terminalsessie uit te voeren.\n\nJe kunt deze optie gebruiken voor editors zoals vi, vim, nvim en andere.
|
||||
|
|
5
lang/strings/translations_pl.properties
generated
5
lang/strings/translations_pl.properties
generated
|
@ -1146,7 +1146,8 @@ lockCreationAlertHeader=Utwórz nowego użytkownika skarbca
|
|||
loginAlertTitle=Wymagane logowanie
|
||||
loginAlertHeader=Odblokuj skarbiec, aby uzyskać dostęp do połączeń osobistych
|
||||
vaultUser=Użytkownik Vault
|
||||
me=Ja
|
||||
#custom
|
||||
me=Mi
|
||||
addUser=Dodaj użytkownika ...
|
||||
addUserDescription=Utwórz nowego użytkownika dla tego skarbca
|
||||
skip=Pomiń
|
||||
|
@ -1288,3 +1289,5 @@ externalLaunchTitle=Żądanie uruchomienia zewnętrznego
|
|||
externalLaunchContent=Zewnętrzny terminal zażądał uruchomienia połączenia powłoki. Czy chcesz zezwolić na uruchamianie połączeń powłoki spoza XPipe?
|
||||
noScriptStateAvailable=Odśwież, aby określić zgodność skryptu ...
|
||||
documentationDescription=Sprawdź dokumentację
|
||||
customEditorCommandInTerminal=Uruchom niestandardowe polecenie w terminalu
|
||||
customEditorCommandInTerminalDescription=Jeśli twój edytor jest oparty na terminalu, możesz włączyć tę opcję, aby automatycznie otworzyć terminal i zamiast tego uruchomić polecenie w sesji terminala.\n\nMożesz użyć tej opcji dla edytorów takich jak vi, vim, nvim i innych.
|
||||
|
|
2
lang/strings/translations_pt.properties
generated
2
lang/strings/translations_pt.properties
generated
|
@ -1288,3 +1288,5 @@ externalLaunchTitle=Pedido de lançamento externo
|
|||
externalLaunchContent=Um terminal externo solicitou o lançamento de uma ligação shell. Pretendes permitir o lançamento de ligações shell a partir do exterior do XPipe?
|
||||
noScriptStateAvailable=Actualiza para determinar a compatibilidade do script ...
|
||||
documentationDescription=Verifica a documentação
|
||||
customEditorCommandInTerminal=Executa um comando personalizado num terminal
|
||||
customEditorCommandInTerminalDescription=Se o teu editor for baseado em terminal, podes ativar esta opção para abrir automaticamente um terminal e executar o comando na sessão do terminal.\n\nPodes utilizar esta opção para editores como o vi, vim, nvim e outros.
|
||||
|
|
2
lang/strings/translations_ru.properties
generated
2
lang/strings/translations_ru.properties
generated
|
@ -1288,3 +1288,5 @@ externalLaunchTitle=Запрос на внешний запуск
|
|||
externalLaunchContent=Внешний терминал запросил запуск shell-соединения. Хочешь ли ты разрешить запуск shell-соединений извне XPipe?
|
||||
noScriptStateAvailable=Обновите, чтобы определить совместимость скриптов ...
|
||||
documentationDescription=Ознакомьтесь с документацией
|
||||
customEditorCommandInTerminal=Запуск пользовательской команды в терминале
|
||||
customEditorCommandInTerminalDescription=Если твой редактор работает через терминал, ты можешь включить эту опцию, чтобы автоматически открыть терминал и вместо этого выполнить команду в терминальной сессии.\n\nЭту опцию можно использовать для таких редакторов, как vi, vim, nvim и других.
|
||||
|
|
2
lang/strings/translations_sv.properties
generated
2
lang/strings/translations_sv.properties
generated
|
@ -1288,3 +1288,5 @@ externalLaunchTitle=Begäran om extern lansering
|
|||
externalLaunchContent=En extern terminal har begärt att få starta en shell-anslutning. Vill du tillåta att shell-anslutningar startas från utanför XPipe?
|
||||
noScriptStateAvailable=Uppdatera för att fastställa skriptkompatibilitet ...
|
||||
documentationDescription=Kolla in dokumentationen
|
||||
customEditorCommandInTerminal=Kör ett anpassat kommando i en terminal
|
||||
customEditorCommandInTerminalDescription=Om din editor är terminalbaserad kan du aktivera det här alternativet för att automatiskt öppna en terminal och köra kommandot i terminalsessionen istället.\n\nDu kan använda det här alternativet för editorer som vi, vim, nvim och andra.
|
||||
|
|
2
lang/strings/translations_tr.properties
generated
2
lang/strings/translations_tr.properties
generated
|
@ -1288,3 +1288,5 @@ externalLaunchTitle=Harici fırlatma talebi
|
|||
externalLaunchContent=Harici bir terminal bir kabuk bağlantısı başlatmak istedi. XPipe dışından kabuk bağlantılarının başlatılmasına izin vermek istiyor musunuz?
|
||||
noScriptStateAvailable=Komut dosyası uyumluluğunu belirlemek için yenileyin ...
|
||||
documentationDescription=Belgelere göz atın
|
||||
customEditorCommandInTerminal=Terminalde özel komut çalıştırma
|
||||
customEditorCommandInTerminalDescription=Düzenleyiciniz terminal tabanlı ise, otomatik olarak bir terminal açmak ve bunun yerine komutu terminal oturumunda çalıştırmak için bu seçeneği etkinleştirebilirsiniz.\n\nBu seçeneği vi, vim, nvim ve diğerleri gibi editörler için kullanabilirsiniz.
|
||||
|
|
649
lang/strings/translations_zh.properties
generated
649
lang/strings/translations_zh.properties
generated
File diff suppressed because it is too large
Load diff
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue