Compare commits

..

91 commits
15.4 ... master

Author SHA1 Message Date
Aymeric Cucherousset
a49d1baf87
Fix french translations (#538) 2025-04-15 07:16:06 +02:00
Piotr (Peter) Mardziel
fe8d8fe383
translation fixes and disambiguations (#528)
Co-authored-by: Piotr Mardziel <piotrm@Piotrs-Laptop.local>
2025-04-15 07:14:31 +02:00
crschnick
ab71d178f3 Show script icons in browser 2025-03-25 14:59:11 +00:00
crschnick
9318a24120 Fix script hierarchy containing duplicates 2025-03-25 14:56:46 +00:00
crschnick
a69ecdc94c Select icon if it is an exact match 2025-03-25 14:03:23 +00:00
crschnick
937d59a27a Fix potential OOB 2025-03-24 15:06:36 +00:00
crschnick
f648d63f4b [release] 2025-03-23 08:10:15 +00:00
crschnick
881452ccd0 Don't focus window after load 2025-03-22 18:52:36 +00:00
crschnick
c7bde460af [stage] 2025-03-22 18:39:01 +00:00
crschnick
12fe24a695 Make desktop open more robust 2025-03-22 18:37:59 +00:00
crschnick
3c13cf5e2a Improve tab init fail handling 2025-03-22 14:53:26 +00:00
crschnick
bc0e14a332 Don't check nested event loop return value 2025-03-22 13:30:06 +00:00
crschnick
10dab33ed4 Rework list box sync again 2025-03-22 13:24:18 +00:00
crschnick
c67411815e Check for event loop enter issues 2025-03-22 13:00:32 +00:00
crschnick
74bedf630c Fix list box sync 2025-03-22 11:23:59 +00:00
crschnick
93413c3da1 Improve icon choice keyboard workflow 2025-03-22 10:15:30 +00:00
crschnick
b39c74b4ff Improve list box performance by synchronizing less 2025-03-21 16:53:10 +00:00
crschnick
474d201708 Improve download progress display 2025-03-21 16:52:45 +00:00
crschnick
5c6acc50e4 [stage] 2025-03-21 14:32:04 +00:00
crschnick
0c0d91b67d Add ability to launch custom terminal editors 2025-03-21 14:28:33 +00:00
crschnick
894b93e3c2 [stage] 2025-03-21 09:33:10 +00:00
crschnick
8c516c043c Improve ssh key chmod 2025-03-21 09:25:46 +00:00
crschnick
9dc368ae7b Delay list box order updates 2025-03-21 09:09:51 +00:00
crschnick
f5543beb71 [stage] 2025-03-20 19:42:10 +00:00
crschnick
8c51eac399 Guard against nested event loop exceptions 2025-03-20 12:15:12 +00:00
crschnick
d4a713f29b [stage] 2025-03-20 10:19:20 +00:00
crschnick
c738331acd Disable terminal logging when not supported 2025-03-20 10:18:49 +00:00
crschnick
25fc3ca0c1 Fix int field issues 2025-03-20 10:18:39 +00:00
crschnick
e1ebd24c04 Ssh identity fixes [stage] 2025-03-20 09:31:30 +00:00
crschnick
96fb6b579b Synchronize list box to prevent concurrent modifications 2025-03-19 19:47:26 +00:00
crschnick
a01756562d Improve some error messages 2025-03-19 19:46:31 +00:00
crschnick
ad0e628b60 Add missing git message for icons 2025-03-19 19:09:57 +00:00
crschnick
a7f33dd0d2 [stage] 2025-03-19 19:03:28 +00:00
crschnick
5b6dadedfe Handle LXD scan errors 2025-03-19 18:29:13 +00:00
loong
45ef2eda8c
Update translations_zh.properties (#498) 2025-03-19 19:27:29 +01:00
crschnick
b4d1a9e68b Hide download box buttons while downloading 2025-03-19 17:54:18 +00:00
crschnick
086237f965 Make install detection more robust 2025-03-19 17:53:45 +00:00
crschnick
0417ce635e Bump jsvg dependency to fix reported svg rendering issues 2025-03-19 11:57:56 +00:00
crschnick
fa02ee1bc2 Bump vnclib to fix color depth issues 2025-03-19 11:57:30 +00:00
crschnick
7e2663c6ea [release] 2025-03-18 11:13:22 +00:00
crschnick
39e6e66b16 Fix box refresh on scene change [release] 2025-03-18 10:39:27 +00:00
crschnick
7b9afaef08 List box reliability fixes [release] 2025-03-18 10:02:51 +00:00
crschnick
a130bcfa91 Ignore late list changes 2025-03-18 08:47:06 +00:00
crschnick
6084fb8d4d Revert to previous list box implementation 2025-03-18 08:46:43 +00:00
crschnick
72a5c67aab Fix double struc creation 2025-03-18 08:39:32 +00:00
crschnick
e0bdf3f52d Improve terminal order on Linux 2025-03-18 08:38:57 +00:00
crschnick
e4b01ccf0b List box memory usage improvements [stage] 2025-03-18 07:30:36 +00:00
crschnick
516cfced4a Warp terminal adjustments [stage] 2025-03-17 19:28:31 +00:00
crschnick
40e2015780 Fix right side browser being blocked 2025-03-17 19:28:21 +00:00
crschnick
fe632cd474 [stage] 2025-03-17 18:36:27 +00:00
crschnick
8ee9c25f5f GC adjustments 2025-03-17 18:34:22 +00:00
crschnick
e1d0642557 Warp linux fixes 2025-03-17 18:33:30 +00:00
crschnick
6f29cfd637 Add support for warp on Windows and Linux 2025-03-17 18:16:49 +00:00
crschnick
4f15a39280 More translation fixes 2025-03-17 15:12:35 +00:00
Ikko Eltociear Ashimine
10c8ba62e7
docs: update contact_ja.md (#493) 2025-03-17 16:08:03 +01:00
crschnick
28dd386752 [release] 2025-03-17 08:30:59 +00:00
crschnick
ca1559937b Update changelog 2025-03-17 08:15:08 +00:00
crschnick
62ef05f707 [release] 2025-03-17 08:09:54 +00:00
crschnick
e78a791c06 Fix stage theme change not triggering 2025-03-17 08:07:01 +00:00
crschnick
72f2decd75 Use more aggressive gc ratio [stage] 2025-03-17 06:50:28 +00:00
crschnick
d93eacdb9b Tweak some gc settings [stage] 2025-03-17 06:45:17 +00:00
crschnick
795a3dde7c Remove platform pause from list box 2025-03-17 06:43:44 +00:00
crschnick
4ecc55443a Fix stackoverflow for some elevation requests 2025-03-17 06:43:34 +00:00
crschnick
51c8fff9bd Fix potential NPE 2025-03-17 06:42:57 +00:00
crschnick
25e2ddd2a3 Fix various NullPointers 2025-03-16 16:17:50 +00:00
crschnick
5473474334 Fix NPE when doing undo in filter 2025-03-16 16:17:33 +00:00
crschnick
c94e270eee Fix some platform out of bounds errors 2025-03-16 14:01:06 +00:00
crschnick
8850a78a36 Update ListBoxViewComp.java 2025-03-16 13:58:02 +00:00
crschnick
bffe75a540 Add more logging for layout content loading [stage] 2025-03-16 10:13:36 +00:00
crschnick
484028f22f Fix incus/lxd launches for busybox [stage] 2025-03-16 09:15:31 +00:00
crschnick
b2ac3c1fba Only refresh frame on Windows [stage] 2025-03-16 08:39:53 +00:00
crschnick
12c414eecc Add more logging for app initialization [stage] 2025-03-16 06:59:38 +00:00
crschnick
c42b6d8439 Fix user feedback size limit 2025-03-16 06:58:10 +00:00
crschnick
05d93c68ee Fix fish shell session scripts being skipped [stage] 2025-03-15 14:13:58 +00:00
crschnick
ec3b95c11b Fix outdated security links 2025-03-15 14:13:35 +00:00
crschnick
e69da5d5b6 Don't animate transition in performance mode 2025-03-15 08:43:54 +00:00
crschnick
c265b6b87d [release] 2025-03-15 07:10:20 +00:00
crschnick
9844cadbdd Link sync docs instead of showing built-in dialog 2025-03-14 11:28:02 +00:00
crschnick
1c42605650 [stage] 2025-03-13 12:57:36 +00:00
crschnick
5b454cd8cd Fix NPE for FreeRDP 2025-03-13 12:55:25 +00:00
crschnick
d7cb5967c6 Some shell fixes 2025-03-13 12:44:29 +00:00
crschnick
a98425f100 Some shell fixes 2025-03-13 12:36:12 +00:00
crschnick
793ca373aa Properly delegate initialized call [stage] 2025-03-13 08:39:14 +00:00
crschnick
b0a7f9d17e [stage] 2025-03-13 08:33:07 +00:00
crschnick
55e7c65462 Rework connections category on macos to focus on fallback shell 2025-03-13 08:26:29 +00:00
crschnick
c57000acee Rework fallback shell 2025-03-13 08:26:04 +00:00
crschnick
ff5941249d Fix vscode remote detection on macos 2025-03-13 04:28:04 +00:00
crschnick
48450490c3 Fix observable binding being gced 2025-03-13 04:13:27 +00:00
crschnick
6cc46ed08f Support ctrl+backspace for password fields 2025-03-12 11:35:13 +00:00
crschnick
03d222e5f5 Improve os logo mapping with spaces 2025-03-11 09:05:04 +00:00
crschnick
b2efb8ddfa Add clear functionality for choice comps 2025-03-11 04:28:51 +00:00
104 changed files with 1560 additions and 524 deletions

View file

@ -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.

View file

@ -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'

View file

@ -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);
});
}

View file

@ -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(() -> {

View file

@ -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);

View file

@ -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)));
}
}

View file

@ -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) {

View file

@ -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;

View file

@ -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();
}

View file

@ -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())

View file

@ -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(

View file

@ -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);
}
}

View file

@ -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) {

View file

@ -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();

View file

@ -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);

View file

@ -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);
}
});

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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");

View file

@ -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");

View file

@ -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();
}
}

View file

@ -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;

View file

@ -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);
});

View file

@ -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();
}
}

View file

@ -85,8 +85,7 @@ public class StoreSectionMiniComp extends StoreSectionBaseComp {
var hbox = ((HBox) struc.get().getChildren().getFirst());
addVisibilityListeners(struc.get(), hbox);
}
})
.createStructure();
});
return full.createStructure();
}
}

View file

@ -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!

View file

@ -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();

View file

@ -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();
}

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -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();
});

View file

@ -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 {}

View file

@ -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()

View file

@ -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);

View file

@ -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;
}

View file

@ -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()

View file

@ -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();
}
}

View file

@ -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

View file

@ -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

View file

@ -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)) {

View file

@ -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));

View file

@ -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();

View file

@ -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")

View file

@ -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,

View file

@ -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'";

View file

@ -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;
}

View file

@ -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());
}
});
}

View file

@ -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);
}
}

View file

@ -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();

View file

@ -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) {

View file

@ -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";

View file

@ -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();
}

View file

@ -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());
}
}

View file

@ -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;
}

View file

@ -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"]

View file

@ -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();
}

View file

@ -18,6 +18,8 @@ import java.util.function.Function;
public interface ShellControl extends ProcessControl {
boolean isInitializing();
void setDumbOpen(ShellOpenFunction openFunction);
void setTerminalOpen(ShellOpenFunction openFunction);

View file

@ -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);

View file

@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

View file

@ -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();
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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());

View file

@ -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();

View file

@ -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())

View file

@ -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();

View file

@ -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();
}
}

View file

@ -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);
}
};
}

View file

@ -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(

View file

@ -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);
}
};
}

View file

@ -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())

View file

@ -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;
}
}
}
}

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -1288,3 +1288,5 @@ externalLaunchTitle=外部起動要求
externalLaunchContent=外部端末がシェル接続の起動を要求してきた。XPipeの外部からのシェル接続の起動を許可するか。
noScriptStateAvailable=スクリプトの互換性を判断するためにリフレッシュする...
documentationDescription=ドキュメントをチェックする
customEditorCommandInTerminal=ターミナルでカスタムコマンドを実行する
customEditorCommandInTerminalDescription=エディターがターミナルベースの場合、このオプションを有効にすると、自動的にターミナルが開き、代わりにターミナルセッションでコマンドが実行される。\n\nこのオプションは、vi、vim、nvimなどのエディタに使用できる。

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -1288,3 +1288,5 @@ externalLaunchTitle=Запрос на внешний запуск
externalLaunchContent=Внешний терминал запросил запуск shell-соединения. Хочешь ли ты разрешить запуск shell-соединений извне XPipe?
noScriptStateAvailable=Обновите, чтобы определить совместимость скриптов ...
documentationDescription=Ознакомьтесь с документацией
customEditorCommandInTerminal=Запуск пользовательской команды в терминале
customEditorCommandInTerminalDescription=Если твой редактор работает через терминал, ты можешь включить эту опцию, чтобы автоматически открыть терминал и вместо этого выполнить команду в терминальной сессии.\n\nЭту опцию можно использовать для таких редакторов, как vi, vim, nvim и других.

View file

@ -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.

View file

@ -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.

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