This commit is contained in:
crschnick 2024-08-16 10:57:17 +00:00
parent 6a946dbc5a
commit 1ebc60cb71
78 changed files with 561 additions and 387 deletions

1
.gitignore vendored
View file

@ -18,7 +18,6 @@ bin
ComponentsGenerated.wxs ComponentsGenerated.wxs
!dist/javafx/**/lib !dist/javafx/**/lib
!dist/javafx/**/bin !dist/javafx/**/bin
dev.properties
xcuserdata/ xcuserdata/
*.dylib *.dylib
project.xcworkspace project.xcworkspace

View file

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

View file

@ -186,7 +186,7 @@ public class BeaconRequestHandler<T> implements HttpHandler {
&& method.getParameters()[0].getType().equals(byte[].class)) && method.getParameters()[0].getType().equals(byte[].class))
.findFirst() .findFirst()
.orElseThrow(); .orElseThrow();
setMethod.invoke(b, (Object) s); setMethod.invoke(b, s);
var m = b.getClass().getDeclaredMethod("build"); var m = b.getClass().getDeclaredMethod("build");
m.setAccessible(true); m.setAccessible(true);

View file

@ -1,23 +1,31 @@
package io.xpipe.app.beacon.impl; package io.xpipe.app.beacon.impl;
import com.sun.net.httpserver.HttpExchange;
import io.xpipe.app.util.TerminalLauncherManager; import io.xpipe.app.util.TerminalLauncherManager;
import io.xpipe.beacon.BeaconClientException; import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.api.SshLaunchExchange; import io.xpipe.beacon.api.SshLaunchExchange;
import io.xpipe.core.process.ProcessControlProvider; import io.xpipe.core.process.ProcessControlProvider;
import io.xpipe.core.process.ShellDialects; import io.xpipe.core.process.ShellDialects;
import com.sun.net.httpserver.HttpExchange;
public class SshLaunchExchangeImpl extends SshLaunchExchange { public class SshLaunchExchangeImpl extends SshLaunchExchange {
@Override @Override
public Object handle(HttpExchange exchange, Request msg) throws Exception { public Object handle(HttpExchange exchange, Request msg) throws Exception {
var usedDialect = ShellDialects.ALL.stream().filter(dialect -> dialect.getExecutableName().equalsIgnoreCase(msg.getArguments())).findFirst(); var usedDialect = ShellDialects.ALL.stream()
if (msg.getArguments() != null && usedDialect.isEmpty() && !msg.getArguments().contains("SSH_ORIGINAL_COMMAND")) { .filter(dialect -> dialect.getExecutableName().equalsIgnoreCase(msg.getArguments()))
.findFirst();
if (msg.getArguments() != null
&& usedDialect.isEmpty()
&& !msg.getArguments().contains("SSH_ORIGINAL_COMMAND")) {
throw new BeaconClientException("Unexpected argument: " + msg.getArguments()); throw new BeaconClientException("Unexpected argument: " + msg.getArguments());
} }
var r = TerminalLauncherManager.waitForNextLaunch(); var r = TerminalLauncherManager.waitForNextLaunch();
var c = ProcessControlProvider.get().getEffectiveLocalDialect().getOpenScriptCommand(r.toString()).buildBaseParts(null); var c = ProcessControlProvider.get()
.getEffectiveLocalDialect()
.getOpenScriptCommand(r.toString())
.buildBaseParts(null);
return Response.builder().command(c).build(); return Response.builder().command(c).build();
} }
} }

View file

@ -5,8 +5,8 @@ import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppLayoutModel; import io.xpipe.app.core.AppLayoutModel;
import io.xpipe.app.fxcomps.SimpleComp; import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.util.PlatformThread; import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.core.process.OsType; import io.xpipe.core.process.OsType;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;

View file

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

View file

@ -10,7 +10,7 @@ public interface ApplicationPathAction extends BrowserAction {
String getExecutable(); String getExecutable();
@Override @Override
default void init(OpenFileSystemModel model) throws Exception { default void init(OpenFileSystemModel model) {
// Cache result for later calls // Cache result for later calls
model.getCache().isApplicationInPath(getExecutable()); model.getCache().isApplicationInPath(getExecutable());
} }

View file

@ -3,8 +3,10 @@ package io.xpipe.app.browser.action;
import io.xpipe.app.browser.file.BrowserEntry; import io.xpipe.app.browser.file.BrowserEntry;
import io.xpipe.app.browser.fs.OpenFileSystemModel; import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.util.LicenseProvider; import io.xpipe.app.util.LicenseProvider;
import javafx.scene.control.Menu; import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem; import javafx.scene.control.MenuItem;
import org.kordamp.ikonli.javafx.FontIcon; import org.kordamp.ikonli.javafx.FontIcon;
import java.util.List; import java.util.List;
@ -27,9 +29,7 @@ public interface BranchAction extends BrowserAction {
m.setDisable(!isActive(model, selected)); m.setDisable(!isActive(model, selected));
if (getProFeatureId() != null if (getProFeatureId() != null
&& !LicenseProvider.get() && !LicenseProvider.get().getFeature(getProFeatureId()).isSupported()) {
.getFeature(getProFeatureId())
.isSupported()) {
m.setDisable(true); m.setDisable(true);
m.setGraphic(new FontIcon("mdi2p-professional-hexagon")); m.setGraphic(new FontIcon("mdi2p-professional-hexagon"));
} }

View file

@ -25,10 +25,15 @@ public interface BrowserAction {
.toList(); .toList();
} }
static List<LeafAction> getFlattened(BrowserAction browserAction, OpenFileSystemModel model, List<BrowserEntry> entries) { static List<LeafAction> getFlattened(
BrowserAction browserAction, OpenFileSystemModel model, List<BrowserEntry> entries) {
return browserAction instanceof LeafAction return browserAction instanceof LeafAction
? List.of((LeafAction) browserAction) ? List.of((LeafAction) browserAction)
: ((BranchAction) browserAction).getBranchingActions(model, entries).stream().map(action -> getFlattened(action, model, entries)).flatMap(List::stream).toList(); : ((BranchAction) browserAction)
.getBranchingActions(model, entries).stream()
.map(action -> getFlattened(action, model, entries))
.flatMap(List::stream)
.toList();
} }
static LeafAction byId(String id, OpenFileSystemModel model, List<BrowserEntry> entries) { static LeafAction byId(String id, OpenFileSystemModel model, List<BrowserEntry> entries) {
@ -41,9 +46,9 @@ public interface BrowserAction {
default List<BrowserEntry> resolveFilesIfNeeded(List<BrowserEntry> selected) { default List<BrowserEntry> resolveFilesIfNeeded(List<BrowserEntry> selected) {
return automaticallyResolveLinks() return automaticallyResolveLinks()
? selected.stream() ? selected.stream()
.map(browserEntry -> .map(browserEntry ->
new BrowserEntry(browserEntry.getRawFileEntry().resolved(), browserEntry.getModel())) new BrowserEntry(browserEntry.getRawFileEntry().resolved(), browserEntry.getModel()))
.toList() .toList()
: selected; : selected;
} }

View file

@ -4,6 +4,7 @@ import io.xpipe.app.browser.action.BrowserAction;
import io.xpipe.app.browser.fs.OpenFileSystemModel; import io.xpipe.app.browser.fs.OpenFileSystemModel;
import io.xpipe.app.core.AppFont; import io.xpipe.app.core.AppFont;
import io.xpipe.app.util.InputHelper; import io.xpipe.app.util.InputHelper;
import javafx.scene.control.ContextMenu; import javafx.scene.control.ContextMenu;
import javafx.scene.control.SeparatorMenuItem; import javafx.scene.control.SeparatorMenuItem;

View file

@ -157,7 +157,8 @@ public final class BrowserFileListComp extends SimpleComp {
}); });
} }
private void updateTypedSelection(TableView<BrowserEntry> table, AtomicReference<Instant> lastType, KeyEvent event, boolean recursive) { private void updateTypedSelection(
TableView<BrowserEntry> table, AtomicReference<Instant> lastType, KeyEvent event, boolean recursive) {
var typed = event.getText(); var typed = event.getText();
if (typed.isEmpty()) { if (typed.isEmpty()) {
return; return;
@ -165,15 +166,15 @@ public final class BrowserFileListComp extends SimpleComp {
var updated = typedSelection.get() + typed; var updated = typedSelection.get() + typed;
var found = fileList.getShown().getValue().stream() var found = fileList.getShown().getValue().stream()
.filter(browserEntry -> .filter(browserEntry -> browserEntry.getFileName().toLowerCase().startsWith(updated.toLowerCase()))
browserEntry.getFileName().toLowerCase().startsWith(updated.toLowerCase()))
.findFirst(); .findFirst();
if (found.isEmpty()) { if (found.isEmpty()) {
if (typedSelection.get().isEmpty()) { if (typedSelection.get().isEmpty()) {
return; return;
} }
var inCooldown = lastType.get() != null && Duration.between(lastType.get(), Instant.now()).toMillis() < 1000; var inCooldown = lastType.get() != null
&& Duration.between(lastType.get(), Instant.now()).toMillis() < 1000;
if (inCooldown) { if (inCooldown) {
lastType.set(Instant.now()); lastType.set(Instant.now());
event.consume(); event.consume();
@ -599,7 +600,8 @@ public final class BrowserFileListComp extends SimpleComp {
browserEntry.getFileName().toLowerCase().startsWith(selection)) browserEntry.getFileName().toLowerCase().startsWith(selection))
.findFirst(); .findFirst();
// Ugly fix to prevent space from showing the menu when there is a file matching // Ugly fix to prevent space from showing the menu when there is a file matching
// Due to the table view input map, these events always get sent and consumed, not allowing us to differentiate between these cases // Due to the table view input map, these events always get sent and consumed, not allowing us to
// differentiate between these cases
if (found.isPresent()) { if (found.isPresent()) {
return; return;
} }

View file

@ -99,7 +99,9 @@ public final class BrowserFileListModel {
} }
public BrowserEntry rename(BrowserEntry old, String newName) { public BrowserEntry rename(BrowserEntry old, String newName) {
if (fileSystemModel == null || fileSystemModel.isClosed() || fileSystemModel.getCurrentPath().get() == null) { if (fileSystemModel == null
|| fileSystemModel.isClosed()
|| fileSystemModel.getCurrentPath().get() == null) {
return old; return old;
} }

View file

@ -142,14 +142,13 @@ public class OpenFileSystemComp extends SimpleComp {
} }
keyEvent.consume(); keyEvent.consume();
}); });
InputHelper.onKeyCombination( InputHelper.onKeyCombination(root, new KeyCodeCombination(KeyCode.BACK_SPACE), true, keyEvent -> {
root, new KeyCodeCombination(KeyCode.BACK_SPACE), true, keyEvent -> { var p = model.getCurrentParentDirectory();
var p = model.getCurrentParentDirectory(); if (p != null) {
if (p != null) { model.cdAsync(p.getPath());
model.cdAsync(p.getPath()); }
} keyEvent.consume();
keyEvent.consume(); });
});
return root; return root;
} }

View file

@ -1,7 +1,5 @@
package io.xpipe.app.browser.session; package io.xpipe.app.browser.session;
import atlantafx.base.controls.RingProgressIndicator;
import atlantafx.base.theme.Styles;
import io.xpipe.app.browser.BrowserWelcomeComp; import io.xpipe.app.browser.BrowserWelcomeComp;
import io.xpipe.app.comp.base.MultiContentComp; import io.xpipe.app.comp.base.MultiContentComp;
import io.xpipe.app.core.AppI18n; import io.xpipe.app.core.AppI18n;
@ -14,6 +12,7 @@ import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.BooleanScope; import io.xpipe.app.util.BooleanScope;
import io.xpipe.app.util.ContextMenuHelper; import io.xpipe.app.util.ContextMenuHelper;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
@ -28,6 +27,9 @@ import javafx.scene.input.*;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
import atlantafx.base.controls.RingProgressIndicator;
import atlantafx.base.theme.Styles;
import java.util.*; import java.util.*;
import static atlantafx.base.theme.Styles.DENSE; import static atlantafx.base.theme.Styles.DENSE;
@ -205,7 +207,8 @@ public class BrowserSessionTabsComp extends SimpleComp {
return; return;
} }
if (new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN, KeyCombination.SHIFT_DOWN).match(keyEvent)) { if (new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN, KeyCombination.SHIFT_DOWN)
.match(keyEvent)) {
tabs.getTabs().clear(); tabs.getTabs().clear();
keyEvent.consume(); keyEvent.consume();
} }

View file

@ -60,8 +60,7 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
Platform.getPreferences().accentColorProperty()); Platform.getPreferences().accentColorProperty());
var selected = PseudoClass.getPseudoClass("selected"); var selected = PseudoClass.getPseudoClass("selected");
for (int i = 0; i < entries.size(); i++) { for (AppLayoutModel.Entry e : entries) {
var e = entries.get(i);
var b = new IconButtonComp(e.icon(), () -> { var b = new IconButtonComp(e.icon(), () -> {
if (e.action() != null) { if (e.action() != null) {
e.action().run(); e.action().run();
@ -84,30 +83,21 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
b.accessibleText(e.name()); b.accessibleText(e.name());
var indicator = Comp.empty().styleClass("indicator"); var indicator = Comp.empty().styleClass("indicator");
var stack = new StackComp(List.of(indicator, b)) var stack = new StackComp(List.of(indicator, b)).apply(struc -> struc.get().setAlignment(Pos.CENTER_RIGHT));
.apply(struc -> struc.get().setAlignment(Pos.CENTER_RIGHT));
stack.apply(struc -> { stack.apply(struc -> {
var indicatorRegion = (Region) struc.get().getChildren().getFirst(); var indicatorRegion = (Region) struc.get().getChildren().getFirst();
indicatorRegion.setMaxWidth(7); indicatorRegion.setMaxWidth(7);
indicatorRegion indicatorRegion.backgroundProperty().bind(Bindings.createObjectBinding(() -> {
.backgroundProperty() if (value.getValue().equals(e)) {
.bind(Bindings.createObjectBinding( return selectedBorder.get();
() -> { }
if (value.getValue().equals(e)) {
return selectedBorder.get();
}
if (struc.get().isHover()) { if (struc.get().isHover()) {
return hoverBorder.get(); return hoverBorder.get();
} }
return noneBorder.get(); return noneBorder.get();
}, }, struc.get().hoverProperty(), value, hoverBorder, selectedBorder, noneBorder));
struc.get().hoverProperty(),
value,
hoverBorder,
selectedBorder,
noneBorder));
}); });
if (shortcut != null) { if (shortcut != null) {
stack.apply(struc -> struc.get().getProperties().put("shortcut", shortcut)); stack.apply(struc -> struc.get().getProperties().put("shortcut", shortcut));

View file

@ -40,20 +40,15 @@ public class SystemStateComp extends SimpleComp {
var success = Styles.toDataURI( var success = Styles.toDataURI(
""" """
.stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-success-emphasis; } .stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-success-emphasis; }
""");
var failure = Styles.toDataURI(
""" """
);
var failure =
Styles.toDataURI(
"""
.stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-danger-emphasis; } .stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-danger-emphasis; }
""" """);
); var other = Styles.toDataURI(
var other = """
Styles.toDataURI(
"""
.stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-accent-emphasis; } .stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-accent-emphasis; }
""" """);
);
var pane = new StackedFontIcon(); var pane = new StackedFontIcon();
pane.getChildren().addAll(fi, border); pane.getChildren().addAll(fi, border);

View file

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

View file

@ -1,6 +1,5 @@
package io.xpipe.app.comp.store; package io.xpipe.app.comp.store;
import atlantafx.base.controls.Spacer;
import io.xpipe.app.comp.base.ButtonComp; import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.comp.base.DialogComp; import io.xpipe.app.comp.base.DialogComp;
import io.xpipe.app.comp.base.ErrorOverlayComp; import io.xpipe.app.comp.base.ErrorOverlayComp;
@ -22,6 +21,7 @@ import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.*; import io.xpipe.app.util.*;
import io.xpipe.core.store.DataStore; import io.xpipe.core.store.DataStore;
import io.xpipe.core.util.ValidationException; import io.xpipe.core.util.ValidationException;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.property.*; import javafx.beans.property.*;
@ -33,6 +33,8 @@ import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import javafx.stage.Stage; import javafx.stage.Stage;
import atlantafx.base.controls.Spacer;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.experimental.FieldDefaults; import lombok.experimental.FieldDefaults;
import net.synedra.validatorfx.GraphicDecorationStackPane; import net.synedra.validatorfx.GraphicDecorationStackPane;
@ -242,23 +244,30 @@ public class StoreCreationComp extends DialogComp {
@Override @Override
protected List<Comp<?>> customButtons() { protected List<Comp<?>> customButtons() {
return List.of(new ButtonComp(AppI18n.observable("skip"), null, () -> { return List.of(
if (showInvalidConfirmAlert()) { new ButtonComp(AppI18n.observable("skip"), null, () -> {
commit(false); if (showInvalidConfirmAlert()) {
} else { commit(false);
finish(); } else {
} finish();
}) }
.visible(skippable), })
.visible(skippable),
new ButtonComp(AppI18n.observable("connect"), null, () -> { new ButtonComp(AppI18n.observable("connect"), null, () -> {
var temp = DataStoreEntry.createTempWrapper(store.getValue()); var temp = DataStoreEntry.createTempWrapper(store.getValue());
var action = provider.getValue().launchAction(temp); var action = provider.getValue().launchAction(temp);
ThreadHelper.runFailableAsync(() -> { ThreadHelper.runFailableAsync(() -> {
action.execute(); action.execute();
}); });
}).hide(connectable.not().or(Bindings.createBooleanBinding(() -> { })
return store.getValue() == null || !store.getValue().isComplete(); .hide(connectable
}, store)))); .not()
.or(Bindings.createBooleanBinding(
() -> {
return store.getValue() == null
|| !store.getValue().isComplete();
},
store))));
} }
@Override @Override
@ -413,8 +422,10 @@ public class StoreCreationComp extends DialogComp {
var layout = new BorderPane(); var layout = new BorderPane();
layout.getStyleClass().add("store-creator"); layout.getStyleClass().add("store-creator");
var providerChoice = new StoreProviderChoiceComp(filter, provider, staticDisplay); var providerChoice = new StoreProviderChoiceComp(filter, provider, staticDisplay);
var showProviders = (!staticDisplay && (providerChoice.getProviders().size() > 1 || providerChoice.getProviders().getFirst().showProviderChoice())) || var showProviders = (!staticDisplay
(staticDisplay && provider.getValue().showProviderChoice()); && (providerChoice.getProviders().size() > 1
|| providerChoice.getProviders().getFirst().showProviderChoice()))
|| (staticDisplay && provider.getValue().showProviderChoice());
if (showProviders) { if (showProviders) {
providerChoice.onSceneAssign(struc -> struc.get().requestFocus()); providerChoice.onSceneAssign(struc -> struc.get().requestFocus());
} }

View file

@ -7,8 +7,8 @@ import io.xpipe.app.fxcomps.impl.PrettySvgComp;
import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.ScanAlert; import io.xpipe.app.util.ScanAlert;
import io.xpipe.core.process.OsType; import io.xpipe.core.process.OsType;
import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.SimpleStringProperty;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.geometry.Pos; import javafx.geometry.Pos;

View file

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

View file

@ -1,6 +1,7 @@
package io.xpipe.app.core; package io.xpipe.app.core;
import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.issue.ErrorEvent;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import java.io.RandomAccessFile; import java.io.RandomAccessFile;

View file

@ -2,12 +2,9 @@ package io.xpipe.app.core;
import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent; import io.xpipe.app.issue.TrackEvent;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.scene.image.WritableImage; import javafx.scene.image.WritableImage;
import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.FilenameUtils;
import org.slf4j.LoggerFactory;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.IOException; import java.io.IOException;
@ -131,7 +128,7 @@ public class AppImages {
} }
if (!Files.isRegularFile(p)) { if (!Files.isRegularFile(p)) {
LoggerFactory.getLogger(AppImages.class).error("Image file " + p + " not found."); TrackEvent.error("Image file " + p + " not found.");
return DEFAULT_IMAGE; return DEFAULT_IMAGE;
} }

View file

@ -16,7 +16,9 @@ public class AppRosettaCheck {
return; return;
} }
var ret = LocalShell.getShell().command("sysctl -n sysctl.proc_translated").readStdoutIfPossible(); var ret = LocalShell.getShell()
.command("sysctl -n sysctl.proc_translated")
.readStdoutIfPossible();
if (ret.isEmpty()) { if (ret.isEmpty()) {
return; return;
} }

View file

@ -2,6 +2,7 @@ package io.xpipe.app.core.check;
import io.xpipe.app.core.AppProperties; import io.xpipe.app.core.AppProperties;
import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.issue.ErrorEvent;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import java.io.IOException; import java.io.IOException;
@ -22,9 +23,11 @@ public class AppUserDirectoryCheck {
Files.delete(testDirectory); Files.delete(testDirectory);
// if (true) throw new IOException(); // if (true) throw new IOException();
} catch (IOException e) { } catch (IOException e) {
ErrorEvent.fromThrowable("Unable to access directory " + dataDirectory ErrorEvent.fromThrowable(
+ ". Please make sure that you have the appropriate permissions and no Antivirus program is blocking the access. " "Unable to access directory " + dataDirectory
+ "In case you use cloud storage, verify that your cloud storage is working and you are logged in.", e) + ". Please make sure that you have the appropriate permissions and no Antivirus program is blocking the access. "
+ "In case you use cloud storage, verify that your cloud storage is working and you are logged in.",
e)
.term() .term()
.expected() .expected()
.handle(); .handle();

View file

@ -11,6 +11,7 @@ import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.prefs.CloseBehaviourAlert; import io.xpipe.app.prefs.CloseBehaviourAlert;
import io.xpipe.app.util.ThreadHelper; import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.process.OsType; import io.xpipe.core.process.OsType;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
import javafx.geometry.Rectangle2D; import javafx.geometry.Rectangle2D;
@ -23,17 +24,18 @@ import javafx.scene.layout.Region;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.stage.Screen; import javafx.stage.Screen;
import javafx.stage.Stage; import javafx.stage.Stage;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import lombok.Value; import lombok.Value;
import lombok.extern.jackson.Jacksonized; import lombok.extern.jackson.Jacksonized;
import javax.imageio.ImageIO;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import javax.imageio.ImageIO;
public class AppMainWindow { public class AppMainWindow {

View file

@ -2,6 +2,7 @@ package io.xpipe.app.core.window;
import io.xpipe.app.core.App; import io.xpipe.app.core.App;
import io.xpipe.core.process.OsType; import io.xpipe.core.process.OsType;
import javafx.geometry.Rectangle2D; import javafx.geometry.Rectangle2D;
import javafx.stage.Screen; import javafx.stage.Screen;
import javafx.stage.Stage; import javafx.stage.Stage;

View file

@ -121,7 +121,7 @@ public interface ActionProvider {
interface BranchDataStoreCallSite<T extends DataStore> extends DataStoreCallSite<T> { interface BranchDataStoreCallSite<T extends DataStore> extends DataStoreCallSite<T> {
default boolean isDynamicallyGenerated(){ default boolean isDynamicallyGenerated() {
return false; return false;
} }
@ -150,8 +150,10 @@ public interface ActionProvider {
.toList()); .toList());
var menuProviders = ALL.stream() var menuProviders = ALL.stream()
.map(actionProvider -> actionProvider.getBranchDataStoreCallSite() != null && .map(actionProvider -> actionProvider.getBranchDataStoreCallSite() != null
!actionProvider.getBranchDataStoreCallSite().isDynamicallyGenerated() && !actionProvider
.getBranchDataStoreCallSite()
.isDynamicallyGenerated()
? actionProvider.getBranchDataStoreCallSite().getChildren(null) ? actionProvider.getBranchDataStoreCallSite().getChildren(null)
: List.of()) : List.of())
.flatMap(List::stream) .flatMap(List::stream)

View file

@ -18,5 +18,5 @@ public enum DataStoreUsageCategory {
@JsonProperty("group") @JsonProperty("group")
GROUP, GROUP,
@JsonProperty("serial") @JsonProperty("serial")
SERIAL; SERIAL
} }

View file

@ -4,6 +4,7 @@ import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.core.process.ShellControl; import io.xpipe.core.process.ShellControl;
import io.xpipe.core.util.FailableRunnable; import io.xpipe.core.util.FailableRunnable;
import io.xpipe.core.util.ModuleLayerLoader; import io.xpipe.core.util.ModuleLayerLoader;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Value; import lombok.Value;

View file

@ -1,6 +1,5 @@
package io.xpipe.app.fxcomps.impl; package io.xpipe.app.fxcomps.impl;
import atlantafx.base.theme.Styles;
import io.xpipe.app.browser.session.BrowserChooserComp; import io.xpipe.app.browser.session.BrowserChooserComp;
import io.xpipe.app.comp.base.ButtonComp; import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.core.AppLayoutModel; import io.xpipe.app.core.AppLayoutModel;
@ -15,11 +14,14 @@ import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntryRef; import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.core.store.FileNames; import io.xpipe.core.store.FileNames;
import io.xpipe.core.store.FileSystemStore; import io.xpipe.core.store.FileSystemStore;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.property.Property; import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority; import javafx.scene.layout.Priority;
import atlantafx.base.theme.Styles;
import org.kordamp.ikonli.javafx.FontIcon; import org.kordamp.ikonli.javafx.FontIcon;
import java.nio.file.Files; import java.nio.file.Files;
@ -34,8 +36,7 @@ public class ContextualFileReferenceChoiceComp extends Comp<CompStructure<HBox>>
private final boolean allowSync; private final boolean allowSync;
public <T extends FileSystemStore> ContextualFileReferenceChoiceComp( public <T extends FileSystemStore> ContextualFileReferenceChoiceComp(
Property<DataStoreEntryRef<T>> fileSystem, Property<String> filePath, boolean allowSync Property<DataStoreEntryRef<T>> fileSystem, Property<String> filePath, boolean allowSync) {
) {
this.allowSync = allowSync; this.allowSync = allowSync;
this.fileSystem = new SimpleObjectProperty<>(); this.fileSystem = new SimpleObjectProperty<>();
fileSystem.subscribe(val -> { fileSystem.subscribe(val -> {
@ -92,7 +93,8 @@ public class ContextualFileReferenceChoiceComp extends Comp<CompStructure<HBox>>
var f = data.resolve(FileNames.getFileName(currentPath.trim())); var f = data.resolve(FileNames.getFileName(currentPath.trim()));
var source = Path.of(currentPath.trim()); var source = Path.of(currentPath.trim());
if (Files.exists(source)) { if (Files.exists(source)) {
var shouldCopy = AppWindowHelper.showConfirmationAlert("confirmGitShareTitle","confirmGitShareHeader", "confirmGitShareContent"); var shouldCopy = AppWindowHelper.showConfirmationAlert(
"confirmGitShareTitle", "confirmGitShareHeader", "confirmGitShareContent");
if (!shouldCopy) { if (!shouldCopy) {
return; return;
} }
@ -115,8 +117,7 @@ public class ContextualFileReferenceChoiceComp extends Comp<CompStructure<HBox>>
if (allowSync) { if (allowSync) {
nodes.add(gitShareButton); nodes.add(gitShareButton);
} }
var layout = new HorizontalComp(nodes) var layout = new HorizontalComp(nodes).apply(struc -> struc.get().setFillHeight(true));
.apply(struc -> struc.get().setFillHeight(true));
layout.apply(struc -> { layout.apply(struc -> {
struc.get().focusedProperty().addListener((observable, oldValue, newValue) -> { struc.get().focusedProperty().addListener((observable, oldValue, newValue) -> {

View file

@ -114,7 +114,8 @@ public class DataStoreChoiceComp<T extends DataStore> extends SimpleComp {
StoreViewState.get().getActiveCategory(), StoreViewState.get().getActiveCategory(),
selectedCategory) selectedCategory)
.styleClass(Styles.LEFT_PILL); .styleClass(Styles.LEFT_PILL);
var filter = new FilterComp(filterText).styleClass(Styles.CENTER_PILL).hgrow(); var filter =
new FilterComp(filterText).styleClass(Styles.CENTER_PILL).hgrow();
var addButton = Comp.of(() -> { var addButton = Comp.of(() -> {
MenuButton m = new MenuButton(null, new FontIcon("mdi2p-plus-box-outline")); MenuButton m = new MenuButton(null, new FontIcon("mdi2p-plus-box-outline"));

View file

@ -56,7 +56,7 @@ public class FilterComp extends Comp<CompStructure<CustomTextField>> {
filter.focusedProperty())); filter.focusedProperty()));
filter.setAccessibleText("Filter"); filter.setAccessibleText("Filter");
filter.addEventFilter(KeyEvent.KEY_PRESSED,event -> { filter.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
if (new KeyCodeCombination(KeyCode.ESCAPE).match(event)) { if (new KeyCodeCombination(KeyCode.ESCAPE).match(event)) {
filter.getScene().getRoot().requestFocus(); filter.getScene().getRoot().requestFocus();
event.consume(); event.consume();

View file

@ -4,12 +4,14 @@ import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure; import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure; import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.util.PlatformThread; import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.beans.property.Property; import javafx.beans.property.Property;
import javafx.beans.value.ChangeListener; import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.scene.control.ComboBox; import javafx.scene.control.ComboBox;
import javafx.scene.control.skin.ComboBoxListViewSkin; import javafx.scene.control.skin.ComboBoxListViewSkin;
import javafx.scene.input.KeyEvent; import javafx.scene.input.KeyEvent;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.experimental.FieldDefaults; import lombok.experimental.FieldDefaults;
@ -33,7 +35,8 @@ public class IntComboFieldComp extends Comp<CompStructure<ComboBox<String>>> {
var text = new ComboBox<String>(); var text = new ComboBox<String>();
text.setEditable(true); text.setEditable(true);
text.setValue(value.getValue() != null ? value.getValue().toString() : null); text.setValue(value.getValue() != null ? value.getValue().toString() : null);
text.setItems(FXCollections.observableList(predefined.stream().map(integer -> "" + integer).toList())); text.setItems(FXCollections.observableList(
predefined.stream().map(integer -> "" + integer).toList()));
text.setMaxWidth(2000); text.setMaxWidth(2000);
text.getStyleClass().add("int-combo-field-comp"); text.getStyleClass().add("int-combo-field-comp");
text.setSkin(new ComboBoxListViewSkin<>(text)); text.setSkin(new ComboBoxListViewSkin<>(text));

View file

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

View file

@ -24,7 +24,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
public class PlatformThread { public class PlatformThread {
public static <T> ObservableValue<T> syncHighFrequency(ObservableValue<T> observable) { public static <T> ObservableValue<T> syncHighFrequency(ObservableValue<T> observable) {
var prop = new SimpleObjectProperty<T>(observable.getValue()); var prop = new SimpleObjectProperty<>(observable.getValue());
var applied = new AtomicBoolean(true); var applied = new AtomicBoolean(true);
observable.addListener((observable1, oldValue, newValue) -> { observable.addListener((observable1, oldValue, newValue) -> {
if (Platform.isFxApplicationThread()) { if (Platform.isFxApplicationThread()) {

View file

@ -57,7 +57,10 @@ public class ErrorEvent {
return EVENT_BASES.remove(t).description(msg); return EVENT_BASES.remove(t).description(msg);
} }
return builder().throwable(t).description(msg + (t.getMessage() != null ? "\n\n" + t.getMessage().trim() : "")); return builder()
.throwable(t)
.description(
msg + (t.getMessage() != null ? "\n\n" + t.getMessage().trim() : ""));
} }
public static ErrorEventBuilder fromMessage(String msg) { public static ErrorEventBuilder fromMessage(String msg) {

View file

@ -89,8 +89,9 @@ public class LauncherCommand implements Callable<Integer> {
// there might be another instance running, for example // there might be another instance running, for example
// starting up or listening on another port // starting up or listening on another port
if (!AppDataLock.lock()) { if (!AppDataLock.lock()) {
TrackEvent.info("Data directory " + AppProperties.get().getDataDir().toString() TrackEvent.info(
+ " is already locked. Is another instance running?"); "Data directory " + AppProperties.get().getDataDir().toString()
+ " is already locked. Is another instance running?");
OperationMode.halt(1); OperationMode.halt(1);
} }
@ -104,28 +105,43 @@ public class LauncherCommand implements Callable<Integer> {
// If an instance is running as another user, we cannot connect to it as the xpipe_auth file is inaccessible // If an instance is running as another user, we cannot connect to it as the xpipe_auth file is inaccessible
// Therefore the beacon client is not present. // Therefore the beacon client is not present.
// We still should check whether it is somehow occupied, otherwise beacon server startup will fail // We still should check whether it is somehow occupied, otherwise beacon server startup will fail
TrackEvent.info("Another instance is already running on this port as another user or is not reachable. Quitting ..."); TrackEvent.info(
"Another instance is already running on this port as another user or is not reachable. Quitting ...");
OperationMode.halt(1); OperationMode.halt(1);
return; return;
} }
try { try {
client.get().performRequest(DaemonFocusExchange.Request.builder().mode(getEffectiveMode()).build()); client.get()
.performRequest(DaemonFocusExchange.Request.builder()
.mode(getEffectiveMode())
.build());
if (!inputs.isEmpty()) { if (!inputs.isEmpty()) {
client.get().performRequest(DaemonOpenExchange.Request.builder().arguments(inputs).build()); client.get()
.performRequest(DaemonOpenExchange.Request.builder()
.arguments(inputs)
.build());
} }
} catch (Exception ex) { } catch (Exception ex) {
var cli = XPipeInstallation.getLocalDefaultCliExecutable(); var cli = XPipeInstallation.getLocalDefaultCliExecutable();
ErrorEvent.fromThrowable("Unable to connect to existing running daemon instance as it did not respond." + ErrorEvent.fromThrowable(
" Either try to kill the process xpiped manually or use the command \"" + "Unable to connect to existing running daemon instance as it did not respond."
cli + + " Either try to kill the process xpiped manually or use the command \""
"\" daemon stop --force.", ex).term().expected().handle(); + cli
+ "\" daemon stop --force.",
ex)
.term()
.expected()
.handle();
} }
if (OsType.getLocal().equals(OsType.MACOS)) { if (OsType.getLocal().equals(OsType.MACOS)) {
Desktop.getDesktop().setOpenURIHandler(e -> { Desktop.getDesktop().setOpenURIHandler(e -> {
try { try {
client.get().performRequest(DaemonOpenExchange.Request.builder().arguments(List.of(e.getURI().toString())).build()); client.get()
.performRequest(DaemonOpenExchange.Request.builder()
.arguments(List.of(e.getURI().toString()))
.build());
} catch (Exception ex) { } catch (Exception ex) {
ErrorEvent.fromThrowable(ex).expected().omit().handle(); ErrorEvent.fromThrowable(ex).expected().omit().handle();
} }

View file

@ -10,8 +10,8 @@ import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.util.Hyperlinks; import io.xpipe.app.util.Hyperlinks;
import io.xpipe.app.util.JfxHelper; import io.xpipe.app.util.JfxHelper;
import io.xpipe.app.util.OptionsBuilder; import io.xpipe.app.util.OptionsBuilder;
import io.xpipe.core.process.OsType; import io.xpipe.core.process.OsType;
import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.SimpleStringProperty;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.scene.control.ScrollPane; import javafx.scene.control.ScrollPane;
@ -41,9 +41,9 @@ public class AboutCategory extends AppPrefsCategory {
null) null)
.addComp( .addComp(
new TileButtonComp("tryPtb", "tryPtbDescription", "mdi2t-test-tube", e -> { new TileButtonComp("tryPtb", "tryPtbDescription", "mdi2t-test-tube", e -> {
Hyperlinks.open(Hyperlinks.GITHUB_PTB); Hyperlinks.open(Hyperlinks.GITHUB_PTB);
e.consume(); e.consume();
}) })
.grow(true, false), .grow(true, false),
null) null)
.addComp( .addComp(
@ -111,16 +111,15 @@ public class AboutCategory extends AppPrefsCategory {
private Comp<?> createProperties() { private Comp<?> createProperties() {
var title = Comp.of(() -> { var title = Comp.of(() -> {
return JfxHelper.createNamedEntry( return JfxHelper.createNamedEntry(
AppI18n.observable("xPipeClient"), AppI18n.observable("xPipeClient"),
new SimpleStringProperty( new SimpleStringProperty("Version " + AppProperties.get().getVersion() + " ("
"Version " + AppProperties.get().getVersion() + " (" + AppProperties.get().getArch() + ")"),
+ AppProperties.get().getArch() + ")"), "logo.png");
"logo.png"); });
});
if (OsType.getLocal() != OsType.MACOS) { if (OsType.getLocal() != OsType.MACOS) {
title.styleClass(Styles.TEXT_BOLD); title.styleClass(Styles.TEXT_BOLD);
} }
var section = new OptionsBuilder() var section = new OptionsBuilder()

View file

@ -14,12 +14,14 @@ import io.xpipe.app.terminal.ExternalTerminalType;
import io.xpipe.app.util.PasswordLockSecretValue; import io.xpipe.app.util.PasswordLockSecretValue;
import io.xpipe.core.util.InPlaceSecretValue; import io.xpipe.core.util.InPlaceSecretValue;
import io.xpipe.core.util.ModuleHelper; import io.xpipe.core.util.ModuleHelper;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.property.*; import javafx.beans.property.*;
import javafx.beans.value.ObservableBooleanValue; import javafx.beans.value.ObservableBooleanValue;
import javafx.beans.value.ObservableDoubleValue; import javafx.beans.value.ObservableDoubleValue;
import javafx.beans.value.ObservableStringValue; import javafx.beans.value.ObservableStringValue;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import lombok.Getter; import lombok.Getter;
import lombok.Value; import lombok.Value;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;

View file

@ -114,10 +114,10 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue {
protected Optional<Path> determineFromPath() { protected Optional<Path> determineFromPath() {
// Try to locate if it is in the Path // Try to locate if it is in the Path
try (var sc = LocalShell.getShell() try (var sc = LocalShell.getShell().start()) {
.start()) {
var out = sc.command(CommandBuilder.ofFunction( var out = sc.command(CommandBuilder.ofFunction(
var1 -> var1.getShellDialect().getWhichCommand(executable))).readStdoutIfPossible(); var1 -> var1.getShellDialect().getWhichCommand(executable)))
.readStdoutIfPossible();
if (out.isPresent()) { if (out.isPresent()) {
var first = out.get().lines().findFirst(); var first = out.get().lines().findFirst();
if (first.isPresent()) { if (first.isPresent()) {

View file

@ -6,6 +6,7 @@ import io.xpipe.app.util.*;
import io.xpipe.core.process.CommandBuilder; import io.xpipe.core.process.CommandBuilder;
import io.xpipe.core.process.OsType; import io.xpipe.core.process.OsType;
import io.xpipe.core.util.SecretValue; import io.xpipe.core.util.SecretValue;
import lombok.Value; import lombok.Value;
import java.io.IOException; import java.io.IOException;
@ -16,7 +17,7 @@ import java.util.function.Supplier;
public interface ExternalRdpClientType extends PrefsChoiceValue { public interface ExternalRdpClientType extends PrefsChoiceValue {
public static ExternalRdpClientType getApplicationLauncher() { static ExternalRdpClientType getApplicationLauncher() {
if (OsType.getLocal() == OsType.WINDOWS) { if (OsType.getLocal() == OsType.WINDOWS) {
return MSTSC; return MSTSC;
} else { } else {
@ -76,9 +77,10 @@ public interface ExternalRdpClientType extends PrefsChoiceValue {
@Override @Override
protected Optional<Path> determineInstallation() { protected Optional<Path> determineInstallation() {
try { try {
var r = WindowsRegistry.local().readValue(WindowsRegistry.HKEY_LOCAL_MACHINE, "SOFTWARE\\Classes\\rdm\\DefaultIcon"); var r = WindowsRegistry.local()
.readValue(WindowsRegistry.HKEY_LOCAL_MACHINE, "SOFTWARE\\Classes\\rdm\\DefaultIcon");
return r.map(Path::of); return r.map(Path::of);
} catch (Exception e) { } catch (Exception e) {
ErrorEvent.fromThrowable(e).omit().handle(); ErrorEvent.fromThrowable(e).omit().handle();
return Optional.empty(); return Optional.empty();
} }
@ -87,7 +89,11 @@ public interface ExternalRdpClientType extends PrefsChoiceValue {
@Override @Override
protected void execute(Path file, LaunchConfiguration configuration) throws Exception { protected void execute(Path file, LaunchConfiguration configuration) throws Exception {
var config = writeConfig(configuration.getConfig()); var config = writeConfig(configuration.getConfig());
LocalShell.getShell().executeSimpleCommand(CommandBuilder.of().addFile(file.toString()).addFile(config.toString()).discardOutput()); LocalShell.getShell()
.executeSimpleCommand(CommandBuilder.of()
.addFile(file.toString())
.addFile(config.toString())
.discardOutput());
ThreadHelper.runFailableAsync(() -> { ThreadHelper.runFailableAsync(() -> {
// Startup is slow // Startup is slow
ThreadHelper.sleep(10000); ThreadHelper.sleep(10000);

View file

@ -12,9 +12,11 @@ import io.xpipe.app.util.OptionsBuilder;
import io.xpipe.app.util.ThreadHelper; import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.process.OsType; import io.xpipe.core.process.OsType;
import io.xpipe.core.util.XPipeInstallation; import io.xpipe.core.util.XPipeInstallation;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.scene.control.ButtonType; import javafx.scene.control.ButtonType;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import java.nio.file.Files; import java.nio.file.Files;
@ -54,21 +56,33 @@ public class WorkspaceCreationAlert {
} }
if (Files.exists(path.get()) && !FileUtils.isEmptyDirectory(path.get().toFile())) { if (Files.exists(path.get()) && !FileUtils.isEmptyDirectory(path.get().toFile())) {
ErrorEvent.fromMessage("New workspace directory is not empty").expected().handle(); ErrorEvent.fromMessage("New workspace directory is not empty")
.expected()
.handle();
return; return;
} }
var shortcutName = (AppProperties.get().isStaging() ? "XPipe PTB" : "XPipe") + " (" + name.get() + ")"; var shortcutName = (AppProperties.get().isStaging() ? "XPipe PTB" : "XPipe") + " (" + name.get() + ")";
var file = switch (OsType.getLocal()) { var file =
case OsType.Windows w -> { switch (OsType.getLocal()) {
var exec = XPipeInstallation.getCurrentInstallationBasePath().resolve(XPipeInstallation.getDaemonExecutablePath(w)).toString(); case OsType.Windows w -> {
yield DesktopShortcuts.create(exec, "-Dio.xpipe.app.dataDir=\"" + path.get().toString() + "\" -Dio.xpipe.app.acceptEula=true", shortcutName); var exec = XPipeInstallation.getCurrentInstallationBasePath()
} .resolve(XPipeInstallation.getDaemonExecutablePath(w))
default -> { .toString();
var exec = XPipeInstallation.getCurrentInstallationBasePath().resolve(XPipeInstallation.getRelativeCliExecutablePath(OsType.getLocal())).toString(); yield DesktopShortcuts.create(
yield DesktopShortcuts.create(exec, "-d \"" + path.get().toString() + "\" --accept-eula", shortcutName); exec,
} "-Dio.xpipe.app.dataDir=\"" + path.get().toString()
}; + "\" -Dio.xpipe.app.acceptEula=true",
shortcutName);
}
default -> {
var exec = XPipeInstallation.getCurrentInstallationBasePath()
.resolve(XPipeInstallation.getRelativeCliExecutablePath(OsType.getLocal()))
.toString();
yield DesktopShortcuts.create(
exec, "-d \"" + path.get().toString() + "\" --accept-eula", shortcutName);
}
};
DesktopHelper.browseFileInDirectory(file); DesktopHelper.browseFileInDirectory(file);
OperationMode.close(); OperationMode.close();
} }

View file

@ -18,9 +18,7 @@ public class WorkspacesCategory extends AppPrefsCategory {
.addTitle("manageWorkspaces") .addTitle("manageWorkspaces")
.sub(new OptionsBuilder() .sub(new OptionsBuilder()
.nameAndDescription("workspaceAdd") .nameAndDescription("workspaceAdd")
.addComp( .addComp(new ButtonComp(AppI18n.observable("addWorkspace"), WorkspaceCreationAlert::showAsync)))
new ButtonComp(AppI18n.observable("addWorkspace"),
WorkspaceCreationAlert::showAsync)))
.buildComp(); .buildComp();
} }
} }

View file

@ -584,6 +584,6 @@ public class DataStoreEntry extends StorageElement {
@JsonProperty("top") @JsonProperty("top")
TOP, TOP,
@JsonProperty("bottom") @JsonProperty("bottom")
BOTTOM; BOTTOM
} }
} }

View file

@ -12,9 +12,11 @@ import io.xpipe.app.util.*;
import io.xpipe.core.process.*; import io.xpipe.core.process.*;
import io.xpipe.core.store.FilePath; import io.xpipe.core.store.FilePath;
import io.xpipe.core.util.FailableFunction; import io.xpipe.core.util.FailableFunction;
import javafx.scene.control.Alert; import javafx.scene.control.Alert;
import javafx.scene.control.ButtonBar; import javafx.scene.control.ButtonBar;
import javafx.scene.control.ButtonType; import javafx.scene.control.ButtonType;
import lombok.Getter; import lombok.Getter;
import lombok.Value; import lombok.Value;
import lombok.With; import lombok.With;
@ -27,56 +29,60 @@ import java.util.*;
public interface ExternalTerminalType extends PrefsChoiceValue { public interface ExternalTerminalType extends PrefsChoiceValue {
// ExternalTerminalType PUTTY = new WindowsType("app.putty","putty") { // ExternalTerminalType PUTTY = new WindowsType("app.putty","putty") {
// //
// @Override // @Override
// protected Optional<Path> determineInstallation() { // protected Optional<Path> determineInstallation() {
// try { // try {
// var r = WindowsRegistry.local().readValue(WindowsRegistry.HKEY_LOCAL_MACHINE, // var r = WindowsRegistry.local().readValue(WindowsRegistry.HKEY_LOCAL_MACHINE,
// "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\Xshell.exe"); // "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\Xshell.exe");
// return r.map(Path::of); // return r.map(Path::of);
// } catch (Exception e) { // } catch (Exception e) {
// ErrorEvent.fromThrowable(e).omit().handle(); // ErrorEvent.fromThrowable(e).omit().handle();
// return Optional.empty(); // return Optional.empty();
// } // }
// } // }
// //
// @Override // @Override
// public boolean supportsTabs() { // public boolean supportsTabs() {
// return true; // return true;
// } // }
// //
// @Override // @Override
// public boolean isRecommended() { // public boolean isRecommended() {
// return false; // return false;
// } // }
// //
// @Override // @Override
// public boolean supportsColoredTitle() { // public boolean supportsColoredTitle() {
// return false; // return false;
// } // }
// //
// @Override // @Override
// protected void execute(Path file, LaunchConfiguration configuration) throws Exception { // protected void execute(Path file, LaunchConfiguration configuration) throws Exception {
// try (var sc = LocalShell.getShell()) { // try (var sc = LocalShell.getShell()) {
// SshLocalBridge.init(); // SshLocalBridge.init();
// var b = SshLocalBridge.get(); // var b = SshLocalBridge.get();
// var command = CommandBuilder.of().addFile(file.toString()).add("-ssh", "localhost", "-l").addQuoted(b.getUser()) // var command = CommandBuilder.of().addFile(file.toString()).add("-ssh", "localhost",
// .add("-i").addFile(b.getIdentityKey().toString()).add("-P", "" + b.getPort()).add("-hostkey").addFile(b.getPubHostKey().toString()); // "-l").addQuoted(b.getUser())
// sc.executeSimpleCommand(command); // .add("-i").addFile(b.getIdentityKey().toString()).add("-P", "" +
// } // b.getPort()).add("-hostkey").addFile(b.getPubHostKey().toString());
// } // sc.executeSimpleCommand(command);
// }; // }
// }
// };
ExternalTerminalType XSHELL = new WindowsType("app.xShell","Xshell") { ExternalTerminalType XSHELL = new WindowsType("app.xShell", "Xshell") {
@Override @Override
protected Optional<Path> determineInstallation() { protected Optional<Path> determineInstallation() {
try { try {
var r = WindowsRegistry.local().readValue(WindowsRegistry.HKEY_LOCAL_MACHINE, var r = WindowsRegistry.local()
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\Xshell.exe"); .readValue(
WindowsRegistry.HKEY_LOCAL_MACHINE,
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\Xshell.exe");
return r.map(Path::of); return r.map(Path::of);
} catch (Exception e) { } catch (Exception e) {
ErrorEvent.fromThrowable(e).omit().handle(); ErrorEvent.fromThrowable(e).omit().handle();
return Optional.empty(); return Optional.empty();
} }
@ -107,7 +113,10 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
try (var sc = LocalShell.getShell()) { try (var sc = LocalShell.getShell()) {
var b = SshLocalBridge.get(); var b = SshLocalBridge.get();
var keyName = b.getIdentityKey().getFileName().toString(); var keyName = b.getIdentityKey().getFileName().toString();
var command = CommandBuilder.of().addFile(file.toString()).add("-url").addQuoted("ssh://" + b.getUser() + "@localhost:" + b.getPort()) var command = CommandBuilder.of()
.addFile(file.toString())
.add("-url")
.addQuoted("ssh://" + b.getUser() + "@localhost:" + b.getPort())
.add("-i", keyName); .add("-i", keyName);
sc.executeSimpleCommand(command); sc.executeSimpleCommand(command);
} }
@ -121,29 +130,30 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
var b = SshLocalBridge.get(); var b = SshLocalBridge.get();
var keyName = b.getIdentityKey().getFileName().toString(); var keyName = b.getIdentityKey().getFileName().toString();
var r = AppWindowHelper.showBlockingAlert( var r = AppWindowHelper.showBlockingAlert(alert -> {
alert -> { alert.setTitle(AppI18n.get("xshellSetup"));
alert.setTitle(AppI18n.get("xshellSetup")); alert.setAlertType(Alert.AlertType.NONE);
alert.setAlertType(Alert.AlertType.NONE);
var activated = AppI18n.get().getMarkdownDocumentation("app:xshellSetup").formatted(b.getIdentityKey(), keyName); var activated = AppI18n.get()
var markdown = new MarkdownComp(activated, s -> s) .getMarkdownDocumentation("app:xshellSetup")
.prefWidth(450) .formatted(b.getIdentityKey(), keyName);
.prefHeight(400) var markdown = new MarkdownComp(activated, s -> s)
.createRegion(); .prefWidth(450)
alert.getDialogPane().setContent(markdown); .prefHeight(400)
.createRegion();
alert.getDialogPane().setContent(markdown);
alert.getButtonTypes().add(new ButtonType(AppI18n.get("ok"), ButtonBar.ButtonData.OK_DONE)); alert.getButtonTypes().add(new ButtonType(AppI18n.get("ok"), ButtonBar.ButtonData.OK_DONE));
}); });
r.filter(buttonType -> buttonType.getButtonData().isDefaultButton()); r.filter(buttonType -> buttonType.getButtonData().isDefaultButton());
r.ifPresent(buttonType -> { r.ifPresent(buttonType -> {
AppCache.update("xshellSetup", true); AppCache.update("xshellSetup", true);
}); });
return r.isPresent(); return r.isPresent();
} }
}; };
ExternalTerminalType SECURECRT = new WindowsType("app.secureCrt","SecureCRT") { ExternalTerminalType SECURECRT = new WindowsType("app.secureCrt", "SecureCRT") {
@Override @Override
protected Optional<Path> determineInstallation() { protected Optional<Path> determineInstallation() {
@ -156,7 +166,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
} }
return Optional.of(file); return Optional.of(file);
} catch (Exception e) { } catch (Exception e) {
ErrorEvent.fromThrowable(e).omit().handle(); ErrorEvent.fromThrowable(e).omit().handle();
return Optional.empty(); return Optional.empty();
} }
@ -182,21 +192,29 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
try (var sc = LocalShell.getShell()) { try (var sc = LocalShell.getShell()) {
SshLocalBridge.init(); SshLocalBridge.init();
var b = SshLocalBridge.get(); var b = SshLocalBridge.get();
var command = CommandBuilder.of().addFile(file.toString()).add("/T").add("/SSH2", "/ACCEPTHOSTKEYS", "/I").addFile( var command = CommandBuilder.of()
b.getIdentityKey().toString()).add("/P", "" + b.getPort()).add("/L").addQuoted(b.getUser()).add("localhost"); .addFile(file.toString())
.add("/T")
.add("/SSH2", "/ACCEPTHOSTKEYS", "/I")
.addFile(b.getIdentityKey().toString())
.add("/P", "" + b.getPort())
.add("/L")
.addQuoted(b.getUser())
.add("localhost");
sc.executeSimpleCommand(command); sc.executeSimpleCommand(command);
} }
} }
}; };
ExternalTerminalType MOBAXTERM = new WindowsType("app.mobaXterm","MobaXterm") { ExternalTerminalType MOBAXTERM = new WindowsType("app.mobaXterm", "MobaXterm") {
@Override @Override
protected Optional<Path> determineInstallation() { protected Optional<Path> determineInstallation() {
try { try {
var r = WindowsRegistry.local().readValue(WindowsRegistry.HKEY_LOCAL_MACHINE, "SOFTWARE\\Classes\\mobaxterm\\DefaultIcon"); var r = WindowsRegistry.local()
return r.map(Path::of); .readValue(WindowsRegistry.HKEY_LOCAL_MACHINE, "SOFTWARE\\Classes\\mobaxterm\\DefaultIcon");
} catch (Exception e) { return r.map(Path::of);
} catch (Exception e) {
ErrorEvent.fromThrowable(e).omit().handle(); ErrorEvent.fromThrowable(e).omit().handle();
return Optional.empty(); return Optional.empty();
} }
@ -220,13 +238,20 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
@Override @Override
protected void execute(Path file, LaunchConfiguration configuration) throws Exception { protected void execute(Path file, LaunchConfiguration configuration) throws Exception {
try (var sc = LocalShell.getShell()) { try (var sc = LocalShell.getShell()) {
var fixedFile = configuration.getScriptFile().toString() var fixedFile = configuration
.getScriptFile()
.toString()
.replaceAll("\\\\", "/") .replaceAll("\\\\", "/")
.replaceAll("\\s","\\$0"); .replaceAll("\\s", "\\$0");
var command = sc.getShellDialect() == ShellDialects.CMD ? var command = sc.getShellDialect() == ShellDialects.CMD
CommandBuilder.of().addQuoted("cmd /c " + fixedFile) : ? CommandBuilder.of().addQuoted("cmd /c " + fixedFile)
CommandBuilder.of().addQuoted("powershell -NoProfile -ExecutionPolicy Bypass -File " + fixedFile); : CommandBuilder.of()
sc.command(CommandBuilder.of().addFile(file.toString()).add("-newtab").add(command)).execute(); .addQuoted("powershell -NoProfile -ExecutionPolicy Bypass -File " + fixedFile);
sc.command(CommandBuilder.of()
.addFile(file.toString())
.add("-newtab")
.add(command))
.execute();
} }
} }
}; };
@ -249,11 +274,12 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
yield CommandSupport.isInPathSilent(sc, "termius"); yield CommandSupport.isInPathSilent(sc, "termius");
} }
case OsType.Windows windows -> { case OsType.Windows windows -> {
var r = WindowsRegistry.local().readValue(WindowsRegistry.HKEY_CURRENT_USER, "SOFTWARE\\Classes\\termius"); var r = WindowsRegistry.local()
.readValue(WindowsRegistry.HKEY_CURRENT_USER, "SOFTWARE\\Classes\\termius");
yield r.isPresent(); yield r.isPresent();
} }
}; };
} catch (Exception e) { } catch (Exception e) {
ErrorEvent.fromThrowable(e).omit().handle(); ErrorEvent.fromThrowable(e).omit().handle();
return false; return false;
} }
@ -298,20 +324,21 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
var b = SshLocalBridge.get(); var b = SshLocalBridge.get();
var keyName = b.getIdentityKey().getFileName().toString(); var keyName = b.getIdentityKey().getFileName().toString();
var r = AppWindowHelper.showBlockingAlert( var r = AppWindowHelper.showBlockingAlert(alert -> {
alert -> { alert.setTitle(AppI18n.get("termiusSetup"));
alert.setTitle(AppI18n.get("termiusSetup")); alert.setAlertType(Alert.AlertType.NONE);
alert.setAlertType(Alert.AlertType.NONE);
var activated = AppI18n.get().getMarkdownDocumentation("app:termiusSetup").formatted(b.getIdentityKey(), keyName); var activated = AppI18n.get()
var markdown = new MarkdownComp(activated, s -> s) .getMarkdownDocumentation("app:termiusSetup")
.prefWidth(450) .formatted(b.getIdentityKey(), keyName);
.prefHeight(400) var markdown = new MarkdownComp(activated, s -> s)
.createRegion(); .prefWidth(450)
alert.getDialogPane().setContent(markdown); .prefHeight(400)
.createRegion();
alert.getDialogPane().setContent(markdown);
alert.getButtonTypes().add(new ButtonType(AppI18n.get("ok"), ButtonBar.ButtonData.OK_DONE)); alert.getButtonTypes().add(new ButtonType(AppI18n.get("ok"), ButtonBar.ButtonData.OK_DONE));
}); });
r.filter(buttonType -> buttonType.getButtonData().isDefaultButton()); r.filter(buttonType -> buttonType.getButtonData().isDefaultButton());
r.ifPresent(buttonType -> { r.ifPresent(buttonType -> {
AppCache.update("termiusSetup", true); AppCache.update("termiusSetup", true);
@ -320,7 +347,6 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
} }
}; };
ExternalTerminalType CMD = new SimplePathType("app.cmd", "cmd.exe", true) { ExternalTerminalType CMD = new SimplePathType("app.cmd", "cmd.exe", true) {
@Override @Override

View file

@ -139,7 +139,7 @@ public class AppInstaller {
public static final class Debian extends InstallerAssetType { public static final class Debian extends InstallerAssetType {
@Override @Override
public void installLocal(Path file) throws Exception { public void installLocal(Path file) {
var start = AppPrefs.get() != null var start = AppPrefs.get() != null
&& AppPrefs.get().terminalType().getValue() != null && AppPrefs.get().terminalType().getValue() != null
&& AppPrefs.get().terminalType().getValue().isAvailable(); && AppPrefs.get().terminalType().getValue().isAvailable();
@ -177,7 +177,7 @@ public class AppInstaller {
public static final class Rpm extends InstallerAssetType { public static final class Rpm extends InstallerAssetType {
@Override @Override
public void installLocal(Path file) throws Exception { public void installLocal(Path file) {
var start = AppPrefs.get() != null var start = AppPrefs.get() != null
&& AppPrefs.get().terminalType().getValue() != null && AppPrefs.get().terminalType().getValue() != null
&& AppPrefs.get().terminalType().getValue().isAvailable(); && AppPrefs.get().terminalType().getValue().isAvailable();
@ -215,7 +215,7 @@ public class AppInstaller {
public static final class Pkg extends InstallerAssetType { public static final class Pkg extends InstallerAssetType {
@Override @Override
public void installLocal(Path file) throws Exception { public void installLocal(Path file) {
var start = AppPrefs.get() != null var start = AppPrefs.get() != null
&& AppPrefs.get().terminalType().getValue() != null && AppPrefs.get().terminalType().getValue() != null
&& AppPrefs.get().terminalType().getValue().isAvailable(); && AppPrefs.get().terminalType().getValue().isAvailable();

View file

@ -5,8 +5,8 @@ import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.storage.DataStoreEntry; import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.core.process.ShellDialects; import io.xpipe.core.process.ShellDialects;
import io.xpipe.core.process.ShellStoreState; import io.xpipe.core.process.ShellStoreState;
import io.xpipe.core.process.ShellTtyState; import io.xpipe.core.process.ShellTtyState;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
public class DataStoreFormatter { public class DataStoreFormatter {

View file

@ -3,14 +3,10 @@ package io.xpipe.app.util;
import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.core.process.CommandBuilder; import io.xpipe.core.process.CommandBuilder;
import io.xpipe.core.process.CommandControl;
import io.xpipe.core.process.OsType; import io.xpipe.core.process.OsType;
import lombok.SneakyThrows;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.FilterInputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Path; import java.nio.file.Path;
@ -109,21 +105,4 @@ public class FileOpener {
}, },
file -> openInTextEditor(file)); file -> openInTextEditor(file));
} }
public static void openCommandOutput(String keyName, Object key, CommandControl cc) {
FileBridge.get()
.openIO(
keyName,
key,
null,
() -> new FilterInputStream(cc.getStdout()) {
@Override
@SneakyThrows
public void close() {
cc.close();
}
},
null,
file -> openInTextEditor(file));
}
} }

View file

@ -37,7 +37,8 @@ public class InputHelper {
public static void onLeft(EventTarget target, boolean filter, Consumer<KeyEvent> r) { public static void onLeft(EventTarget target, boolean filter, Consumer<KeyEvent> r) {
EventHandler<KeyEvent> e = event -> { EventHandler<KeyEvent> e = event -> {
if (new KeyCodeCombination(KeyCode.LEFT).match(event) || new KeyCodeCombination(KeyCode.NUMPAD4).match(event)) { if (new KeyCodeCombination(KeyCode.LEFT).match(event)
|| new KeyCodeCombination(KeyCode.NUMPAD4).match(event)) {
r.accept(event); r.accept(event);
} }
}; };
@ -50,7 +51,8 @@ public class InputHelper {
public static void onRight(EventTarget target, boolean filter, Consumer<KeyEvent> r) { public static void onRight(EventTarget target, boolean filter, Consumer<KeyEvent> r) {
EventHandler<KeyEvent> e = event -> { EventHandler<KeyEvent> e = event -> {
if (new KeyCodeCombination(KeyCode.RIGHT).match(event) || new KeyCodeCombination(KeyCode.NUMPAD6).match(event)) { if (new KeyCodeCombination(KeyCode.RIGHT).match(event)
|| new KeyCodeCombination(KeyCode.NUMPAD6).match(event)) {
r.accept(event); r.accept(event);
} }
}; };

View file

@ -37,8 +37,8 @@ public class NativeBridge {
return Optional.ofNullable(macOsLibrary); return Optional.ofNullable(macOsLibrary);
} }
public static interface MacOsLibrary extends Library { public interface MacOsLibrary extends Library {
public abstract void setAppearance(NativeLong window, boolean seamlessFrame, boolean dark); void setAppearance(NativeLong window, boolean seamlessFrame, boolean dark);
} }
} }

View file

@ -36,9 +36,11 @@ public class ScanAlert {
public static void showAsync(DataStoreEntry entry) { public static void showAsync(DataStoreEntry entry) {
ThreadHelper.runAsync(() -> { ThreadHelper.runAsync(() -> {
var showForCon = entry == null || (entry.getStore() instanceof ShellStore && ( var showForCon = entry == null
!(entry.getStorePersistentState() instanceof ShellStoreState shellStoreState) || || (entry.getStore() instanceof ShellStore
shellStoreState.getTtyState() == null || shellStoreState.getTtyState() == ShellTtyState.NONE)); && (!(entry.getStorePersistentState() instanceof ShellStoreState shellStoreState)
|| shellStoreState.getTtyState() == null
|| shellStoreState.getTtyState() == ShellTtyState.NONE));
if (showForCon) { if (showForCon) {
showForShellStore(entry); showForShellStore(entry);
} }

View file

@ -8,6 +8,7 @@ import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ProcessControlProvider; import io.xpipe.core.process.ProcessControlProvider;
import io.xpipe.core.process.ShellControl; import io.xpipe.core.process.ShellControl;
import io.xpipe.core.util.XPipeInstallation; import io.xpipe.core.util.XPipeInstallation;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@ -27,6 +28,7 @@ public class SshLocalBridge {
private final Path directory; private final Path directory;
private final int port; private final int port;
private final String user; private final String user;
@Setter @Setter
private ShellControl runningShell; private ShellControl runningShell;
@ -74,22 +76,39 @@ public class SshLocalBridge {
INSTANCE = new SshLocalBridge(bridgeDir, port, user); INSTANCE = new SshLocalBridge(bridgeDir, port, user);
var hostKey = INSTANCE.getHostKey(); var hostKey = INSTANCE.getHostKey();
if (!sc.getShellDialect().createFileExistsCommand(sc, hostKey.toString()).executeAndCheck()) { if (!sc.getShellDialect()
sc.command(CommandBuilder.of().add("ssh-keygen", "-q", "-N") .createFileExistsCommand(sc, hostKey.toString())
.addQuoted("").add("-C").addQuoted("XPipe SSH bridge host key") .executeAndCheck()) {
.add("-t", "ed25519", "-f").addFile(hostKey.toString())).execute(); sc.command(CommandBuilder.of()
.add("ssh-keygen", "-q", "-N")
.addQuoted("")
.add("-C")
.addQuoted("XPipe SSH bridge host key")
.add("-t", "ed25519", "-f")
.addFile(hostKey.toString()))
.execute();
} }
var idKey = INSTANCE.getIdentityKey(); var idKey = INSTANCE.getIdentityKey();
if (!sc.getShellDialect().createFileExistsCommand(sc, idKey.toString()).executeAndCheck()) { if (!sc.getShellDialect()
sc.command(CommandBuilder.of().add("ssh-keygen", "-q", "-N") .createFileExistsCommand(sc, idKey.toString())
.addQuoted("").add("-C").addQuoted("XPipe SSH bridge identity").add("-t", "ed25519", "-f").addFile(idKey.toString())).execute(); .executeAndCheck()) {
sc.command(CommandBuilder.of()
.add("ssh-keygen", "-q", "-N")
.addQuoted("")
.add("-C")
.addQuoted("XPipe SSH bridge identity")
.add("-t", "ed25519", "-f")
.addFile(idKey.toString()))
.execute();
} }
var config = INSTANCE.getConfig(); var config = INSTANCE.getConfig();
var command = "\"" + XPipeInstallation.getLocalDefaultCliExecutable() + "\" ssh-launch " + sc.getShellDialect().environmentVariable("SSH_ORIGINAL_COMMAND"); var command = "\"" + XPipeInstallation.getLocalDefaultCliExecutable() + "\" ssh-launch "
+ sc.getShellDialect().environmentVariable("SSH_ORIGINAL_COMMAND");
var pidFile = bridgeDir.resolve("sshd.pid"); var pidFile = bridgeDir.resolve("sshd.pid");
var content = """ var content =
"""
ForceCommand %s ForceCommand %s
PidFile "%s" PidFile "%s"
StrictModes no StrictModes no
@ -101,14 +120,24 @@ public class SshLocalBridge {
PubkeyAuthentication yes PubkeyAuthentication yes
AuthorizedKeysFile "%s" AuthorizedKeysFile "%s"
""" """
.formatted(command, pidFile.toString(), "" + port, INSTANCE.getHostKey().toString(), INSTANCE.getPubIdentityKey());; .formatted(
command,
pidFile.toString(),
"" + port,
INSTANCE.getHostKey().toString(),
INSTANCE.getPubIdentityKey());
Files.writeString(config, content); Files.writeString(config, content);
// INSTANCE.updateConfig(); // INSTANCE.updateConfig();
var exec = getSshd(sc); var exec = getSshd(sc);
var launchCommand = CommandBuilder.of().addFile(exec).add("-f").addFile(INSTANCE.getConfig().toString()).add("-p", "" + port); var launchCommand = CommandBuilder.of()
var control = ProcessControlProvider.get().createLocalProcessControl(true).start(); .addFile(exec)
.add("-f")
.addFile(INSTANCE.getConfig().toString())
.add("-p", "" + port);
var control =
ProcessControlProvider.get().createLocalProcessControl(true).start();
control.writeLine(launchCommand.buildFull(control)); control.writeLine(launchCommand.buildFull(control));
INSTANCE.setRunningShell(control); INSTANCE.setRunningShell(control);
} }
@ -125,19 +154,24 @@ public class SshLocalBridge {
return; return;
} }
var updated = content + "\n\n" + """ var updated = content + "\n\n"
+ """
Host %s Host %s
HostName localhost HostName localhost
User "%s" User "%s"
Port %s Port %s
IdentityFile "%s" IdentityFile "%s"
""".formatted(getName(), port, user, getIdentityKey()); """
.formatted(getName(), port, user, getIdentityKey());
Files.writeString(file, updated); Files.writeString(file, updated);
} }
private static String getSshd(ShellControl sc) throws Exception { private static String getSshd(ShellControl sc) throws Exception {
if (OsType.getLocal() == OsType.WINDOWS) { if (OsType.getLocal() == OsType.WINDOWS) {
return XPipeInstallation.getLocalBundledToolsDirectory().resolve("openssh").resolve("sshd").toString(); return XPipeInstallation.getLocalBundledToolsDirectory()
.resolve("openssh")
.resolve("sshd")
.toString();
} else { } else {
var exec = sc.executeSimpleStringCommand(sc.getShellDialect().getWhichCommand("sshd")); var exec = sc.executeSimpleStringCommand(sc.getShellDialect().getWhichCommand("sshd"));
return exec; return exec;

View file

@ -7,6 +7,7 @@ import io.xpipe.core.process.ShellControl;
import io.xpipe.core.process.TerminalInitScriptConfig; import io.xpipe.core.process.TerminalInitScriptConfig;
import io.xpipe.core.process.WorkingDirectoryFunction; import io.xpipe.core.process.WorkingDirectoryFunction;
import io.xpipe.core.store.FilePath; import io.xpipe.core.store.FilePath;
import lombok.Setter; import lombok.Setter;
import lombok.Value; import lombok.Value;
import lombok.experimental.NonFinal; import lombok.experimental.NonFinal;
@ -90,7 +91,7 @@ public class TerminalLauncherManager {
return waitForCompletion(e); return waitForCompletion(e);
} }
public static Path waitForCompletion(Entry e) throws BeaconClientException, BeaconServerException { public static Path waitForCompletion(Entry e) throws BeaconServerException {
while (true) { while (true) {
if (e.result == null) { if (e.result == null) {
ThreadHelper.sleep(10); ThreadHelper.sleep(10);

View file

@ -6,7 +6,7 @@ import io.xpipe.app.prefs.AppPrefs;
public class UnlockAlert { public class UnlockAlert {
public static void showIfNeeded() throws Throwable { public static void showIfNeeded() {
if (AppPrefs.get().getLockCrypt().getValue() == null if (AppPrefs.get().getLockCrypt().getValue() == null
|| AppPrefs.get().getLockCrypt().getValue().isEmpty()) { || AppPrefs.get().getLockCrypt().getValue().isEmpty()) {
return; return;

View file

@ -20,7 +20,8 @@ public class BeaconServer {
public static boolean isReachable(int port) { public static boolean isReachable(int port) {
try (var socket = new Socket()) { try (var socket = new Socket()) {
socket.connect(new InetSocketAddress(Inet4Address.getByAddress(new byte[]{ 0x7f,0x00,0x00,0x01 }), port), 5000); socket.connect(
new InetSocketAddress(Inet4Address.getByAddress(new byte[] {0x7f, 0x00, 0x00, 0x01}), port), 5000);
return true; return true;
} catch (Exception e) { } catch (Exception e) {
return false; return false;

View file

@ -1,6 +1,7 @@
package io.xpipe.beacon.api; package io.xpipe.beacon.api;
import io.xpipe.beacon.BeaconInterface; import io.xpipe.beacon.BeaconInterface;
import lombok.Builder; import lombok.Builder;
import lombok.NonNull; import lombok.NonNull;
import lombok.Value; import lombok.Value;

View file

@ -54,7 +54,7 @@ public interface CommandControl extends ProcessControl {
OutputStream startExternalStdin() throws Exception; OutputStream startExternalStdin() throws Exception;
public void setExitTimeout(Duration duration); void setExitTimeout(Duration duration);
boolean waitFor(); boolean waitFor();

View file

@ -97,7 +97,13 @@ public interface OsType {
public List<String> determineInterestingPaths(ShellControl pc) throws Exception { public List<String> determineInterestingPaths(ShellControl pc) throws Exception {
var home = getHomeDirectory(pc); var home = getHomeDirectory(pc);
return List.of( return List.of(
home, "/home", FileNames.join(home, "Downloads"), FileNames.join(home, "Documents"), "/etc", "/tmp", "/var"); home,
"/home",
FileNames.join(home, "Downloads"),
FileNames.join(home, "Documents"),
"/etc",
"/tmp",
"/var");
} }
@Override @Override
@ -114,7 +120,6 @@ public interface OsType {
public String getName() { public String getName() {
return "Linux"; return "Linux";
} }
} }
final class Linux extends Unix implements OsType, Local, Any { final class Linux extends Unix implements OsType, Local, Any {
@ -123,7 +128,6 @@ public interface OsType {
public String getId() { public String getId() {
return "linux"; return "linux";
} }
} }
final class Solaris extends Unix implements Any {} final class Solaris extends Unix implements Any {}
@ -171,6 +175,5 @@ public interface OsType {
public String getName() { public String getName() {
return "Mac"; return "Mac";
} }
} }
} }

View file

@ -29,7 +29,9 @@ public interface ShellDumbMode {
private final String message; private final String message;
public Unsupported(String message) {this.message = message;} public Unsupported(String message) {
this.message = message;
}
public void throwIfUnsupported() { public void throwIfUnsupported() {
throw new UnsupportedOperationException(message); throw new UnsupportedOperationException(message);

View file

@ -5,7 +5,6 @@ import lombok.Getter;
@Getter @Getter
public enum ShellTtyState { public enum ShellTtyState {
@JsonProperty("none") @JsonProperty("none")
NONE(true, false, false, true, true), NONE(true, false, false, true, true),
@JsonProperty("merged") @JsonProperty("merged")
@ -19,7 +18,12 @@ public enum ShellTtyState {
private final boolean supportsInput; private final boolean supportsInput;
private final boolean preservesOutput; private final boolean preservesOutput;
ShellTtyState(boolean hasSeparateStreams, boolean hasAnsiEscapes, boolean echoesAllInput, boolean supportsInput, boolean preservesOutput) { ShellTtyState(
boolean hasSeparateStreams,
boolean hasAnsiEscapes,
boolean echoesAllInput,
boolean supportsInput,
boolean preservesOutput) {
this.hasSeparateStreams = hasSeparateStreams; this.hasSeparateStreams = hasSeparateStreams;
this.hasAnsiEscapes = hasAnsiEscapes; this.hasAnsiEscapes = hasAnsiEscapes;
this.echoesAllInput = echoesAllInput; this.echoesAllInput = echoesAllInput;

View file

@ -1,8 +1,9 @@
package io.xpipe.core.store; package io.xpipe.core.store;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.xpipe.core.process.CommandBuilder; import io.xpipe.core.process.CommandBuilder;
import io.xpipe.core.process.ShellControl; import io.xpipe.core.process.ShellControl;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter; import lombok.Getter;
import java.io.InputStream; import java.io.InputStream;
@ -43,8 +44,10 @@ public class ConnectionFileSystem implements FileSystem {
d.throwIfUnsupported(); d.throwIfUnsupported();
} }
if (!shellControl.getTtyState().isPreservesOutput() || !shellControl.getTtyState().isSupportsInput()) { if (!shellControl.getTtyState().isPreservesOutput()
throw new UnsupportedOperationException("Shell has a PTY allocated and does not support file system operations"); || !shellControl.getTtyState().isSupportsInput()) {
throw new UnsupportedOperationException(
"Shell has a PTY allocated and does not support file system operations");
} }
return this; return this;

View file

@ -3,7 +3,6 @@ package io.xpipe.core.store;
import io.xpipe.core.process.ShellControl; import io.xpipe.core.process.ShellControl;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
public interface NetworkTunnelStore extends DataStore { public interface NetworkTunnelStore extends DataStore {
@ -64,8 +63,6 @@ public interface NetworkTunnelStore extends DataStore {
"Unable to create tunnel chain as one intermediate system does not support tunneling"); "Unable to create tunnel chain as one intermediate system does not support tunneling");
} }
var running = new AtomicBoolean();
var runningCounter = new AtomicInteger();
var counter = new AtomicInteger(); var counter = new AtomicInteger();
var sessions = new ArrayList<NetworkTunnelSession>(); var sessions = new ArrayList<NetworkTunnelSession>();
NetworkTunnelStore current = this; NetworkTunnelStore current = this;
@ -79,14 +76,6 @@ public interface NetworkTunnelStore extends DataStore {
var currentRemotePort = var currentRemotePort =
sessions.isEmpty() ? remotePort : sessions.getLast().getLocalPort(); sessions.isEmpty() ? remotePort : sessions.getLast().getLocalPort();
var t = func.create(currentLocalPort, currentRemotePort); var t = func.create(currentLocalPort, currentRemotePort);
t.addListener(r -> {
if (r) {
runningCounter.incrementAndGet();
} else {
runningCounter.decrementAndGet();
}
running.set(runningCounter.get() == counter.get());
});
t.start(); t.start();
sessions.add(t); sessions.add(t);
counter.incrementAndGet(); counter.incrementAndGet();

View file

@ -4,12 +4,6 @@ import lombok.EqualsAndHashCode;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import lombok.experimental.SuperBuilder; import lombok.experimental.SuperBuilder;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
@ -17,6 +11,12 @@ import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException; import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec; import java.security.spec.KeySpec;
import java.util.Random; import java.util.Random;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
@SuperBuilder @SuperBuilder
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)

View file

@ -5,9 +5,9 @@ import lombok.EqualsAndHashCode;
import lombok.experimental.SuperBuilder; import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized; import lombok.extern.jackson.Jacksonized;
import javax.crypto.SecretKey;
import java.security.spec.InvalidKeySpecException; import java.security.spec.InvalidKeySpecException;
import java.util.Random; import java.util.Random;
import javax.crypto.SecretKey;
@JsonTypeName("default") @JsonTypeName("default")
@SuperBuilder @SuperBuilder

View file

@ -7,8 +7,10 @@ import io.xpipe.app.ext.ActionProvider;
import io.xpipe.app.storage.DataStoreEntry; import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.storage.DataStoreEntryRef; import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.core.store.ShellStore; import io.xpipe.core.store.ShellStore;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import lombok.Value; import lombok.Value;
public class BrowseStoreAction implements ActionProvider { public class BrowseStoreAction implements ActionProvider {

View file

@ -4,6 +4,7 @@ import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.ActionProvider; import io.xpipe.app.ext.ActionProvider;
import io.xpipe.app.storage.DataStoreEntryRef; import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.core.store.DataStore; import io.xpipe.core.store.DataStore;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
public class LaunchStoreAction implements ActionProvider { public class LaunchStoreAction implements ActionProvider {

View file

@ -10,8 +10,10 @@ import io.xpipe.core.process.ShellStoreState;
import io.xpipe.core.process.ShellTtyState; import io.xpipe.core.process.ShellTtyState;
import io.xpipe.core.store.ShellStore; import io.xpipe.core.store.ShellStore;
import io.xpipe.ext.base.script.ScriptHierarchy; import io.xpipe.ext.base.script.ScriptHierarchy;
import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import lombok.Value; import lombok.Value;
import java.util.List; import java.util.List;
@ -34,7 +36,8 @@ public class RunScriptActionMenu implements ActionProvider {
var script = hierarchy.getLeafBase().getStore().assembleScriptChain(sc); var script = hierarchy.getLeafBase().getStore().assembleScriptChain(sc);
TerminalLauncher.open( TerminalLauncher.open(
shellStore.getEntry(), shellStore.getEntry(),
hierarchy.getLeafBase().get().getName() + " - " + shellStore.get().getName(), hierarchy.getLeafBase().get().getName() + " - "
+ shellStore.get().getName(),
null, null,
sc.command(script)); sc.command(script));
} }
@ -145,7 +148,8 @@ public class RunScriptActionMenu implements ActionProvider {
@Override @Override
public List<? extends ActionProvider> getChildren(DataStoreEntryRef<ShellStore> store) { public List<? extends ActionProvider> getChildren(DataStoreEntryRef<ShellStore> store) {
return List.of(new TerminalRunActionProvider(hierarchy), new BackgroundRunActionProvider(hierarchy)); return List.of(
new TerminalRunActionProvider(hierarchy), new BackgroundRunActionProvider(hierarchy));
} }
}; };
} }
@ -179,7 +183,9 @@ public class RunScriptActionMenu implements ActionProvider {
@Override @Override
public List<? extends ActionProvider> getChildren(DataStoreEntryRef<ShellStore> store) { public List<? extends ActionProvider> getChildren(DataStoreEntryRef<ShellStore> store) {
return hierarchy.getChildren().stream().map(c -> new ScriptActionProvider(c)).toList(); return hierarchy.getChildren().stream()
.map(c -> new ScriptActionProvider(c))
.toList();
} }
}; };
} }
@ -190,7 +196,7 @@ public class RunScriptActionMenu implements ActionProvider {
private static class Action implements ActionProvider.Action { private static class Action implements ActionProvider.Action {
@Override @Override
public void execute() throws Exception { public void execute() {
StoreViewState.get().getAllScriptsCategory().select(); StoreViewState.get().getAllScriptsCategory().select();
} }
} }
@ -255,8 +261,12 @@ public class RunScriptActionMenu implements ActionProvider {
var state = o.get().getStorePersistentState(); var state = o.get().getStorePersistentState();
if (state instanceof ShellStoreState shellStoreState) { if (state instanceof ShellStoreState shellStoreState) {
return (shellStoreState.getShellDialect() == null return (shellStoreState.getShellDialect() == null
|| shellStoreState.getShellDialect().getDumbMode().supportsAnyPossibleInteraction()) && || shellStoreState
(shellStoreState.getTtyState() == null || shellStoreState.getTtyState() == ShellTtyState.NONE); .getShellDialect()
.getDumbMode()
.supportsAnyPossibleInteraction())
&& (shellStoreState.getTtyState() == null
|| shellStoreState.getTtyState() == ShellTtyState.NONE);
} else { } else {
return false; return false;
} }
@ -280,7 +290,9 @@ public class RunScriptActionMenu implements ActionProvider {
return true; return true;
}); });
var list = hierarchy.getChildren().stream().map(c -> new ScriptActionProvider(c)).toList(); var list = hierarchy.getChildren().stream()
.map(c -> new ScriptActionProvider(c))
.toList();
if (list.isEmpty()) { if (list.isEmpty()) {
return List.of(new NoScriptsActionProvider()); return List.of(new NoScriptsActionProvider());
} else { } else {

View file

@ -10,7 +10,9 @@ import io.xpipe.core.process.ShellControl;
import io.xpipe.core.process.ShellDialects; import io.xpipe.core.process.ShellDialects;
import io.xpipe.core.store.LocalStore; import io.xpipe.core.store.LocalStore;
import io.xpipe.core.store.ShellStore; import io.xpipe.core.store.ShellStore;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import lombok.Value; import lombok.Value;
import java.io.BufferedReader; import java.io.BufferedReader;

View file

@ -8,7 +8,9 @@ import io.xpipe.app.util.ScanAlert;
import io.xpipe.core.process.ShellStoreState; import io.xpipe.core.process.ShellStoreState;
import io.xpipe.core.process.ShellTtyState; import io.xpipe.core.process.ShellTtyState;
import io.xpipe.core.store.ShellStore; import io.xpipe.core.store.ShellStore;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import lombok.Value; import lombok.Value;
public class ScanStoreAction implements ActionProvider { public class ScanStoreAction implements ActionProvider {
@ -41,8 +43,12 @@ public class ScanStoreAction implements ActionProvider {
var state = o.get().getStorePersistentState(); var state = o.get().getStorePersistentState();
if (state instanceof ShellStoreState shellStoreState) { if (state instanceof ShellStoreState shellStoreState) {
return (shellStoreState.getShellDialect() == null return (shellStoreState.getShellDialect() == null
|| shellStoreState.getShellDialect().getDumbMode().supportsAnyPossibleInteraction()) && || shellStoreState
(shellStoreState.getTtyState() == null || shellStoreState.getTtyState() == ShellTtyState.NONE); .getShellDialect()
.getDumbMode()
.supportsAnyPossibleInteraction())
&& (shellStoreState.getTtyState() == null
|| shellStoreState.getTtyState() == ShellTtyState.NONE);
} else { } else {
return true; return true;
} }

View file

@ -9,6 +9,7 @@ import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.util.TerminalLauncher; import io.xpipe.app.util.TerminalLauncher;
import io.xpipe.core.process.CommandBuilder; import io.xpipe.core.process.CommandBuilder;
import io.xpipe.core.process.ShellControl; import io.xpipe.core.process.ShellControl;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import java.util.List; import java.util.List;
@ -38,7 +39,8 @@ public abstract class MultiExecuteAction implements BranchAction {
model.getCurrentDirectory() != null model.getCurrentDirectory() != null
? model.getCurrentDirectory() ? model.getCurrentDirectory()
.getPath() .getPath()
: null, cmd); : null,
cmd);
} }
}, },
false); false);

View file

@ -9,13 +9,15 @@ import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.util.TerminalLauncher; import io.xpipe.app.util.TerminalLauncher;
import io.xpipe.core.process.CommandBuilder; import io.xpipe.core.process.CommandBuilder;
import io.xpipe.core.process.ShellControl; import io.xpipe.core.process.ShellControl;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import java.util.List; import java.util.List;
public abstract class MultiExecuteSelectionAction implements BranchAction { public abstract class MultiExecuteSelectionAction implements BranchAction {
protected abstract CommandBuilder createCommand(ShellControl sc, OpenFileSystemModel model, List<BrowserEntry> entries); protected abstract CommandBuilder createCommand(
ShellControl sc, OpenFileSystemModel model, List<BrowserEntry> entries);
protected abstract String getTerminalTitle(); protected abstract String getTerminalTitle();
@ -28,14 +30,15 @@ public abstract class MultiExecuteSelectionAction implements BranchAction {
public void execute(OpenFileSystemModel model, List<BrowserEntry> entries) { public void execute(OpenFileSystemModel model, List<BrowserEntry> entries) {
model.withShell( model.withShell(
pc -> { pc -> {
var cmd = pc.command(createCommand(pc, model, entries)); var cmd = pc.command(createCommand(pc, model, entries));
TerminalLauncher.open( TerminalLauncher.open(
model.getEntry().getEntry(), model.getEntry().getEntry(),
getTerminalTitle(), getTerminalTitle(),
model.getCurrentDirectory() != null model.getCurrentDirectory() != null
? model.getCurrentDirectory() ? model.getCurrentDirectory()
.getPath() .getPath()
: null, cmd); : null,
cmd);
}, },
false); false);
} }
@ -61,8 +64,8 @@ public abstract class MultiExecuteSelectionAction implements BranchAction {
pc -> { pc -> {
var cmd = createCommand(pc, model, entries); var cmd = createCommand(pc, model, entries);
pc.command(cmd) pc.command(cmd)
.withWorkingDirectory(model.getCurrentDirectory() .withWorkingDirectory(
.getPath()) model.getCurrentDirectory().getPath())
.execute(); .execute();
}, },
false); false);

View file

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

View file

@ -11,9 +11,11 @@ import io.xpipe.app.util.ScriptHelper;
import io.xpipe.core.process.CommandBuilder; import io.xpipe.core.process.CommandBuilder;
import io.xpipe.core.process.ShellControl; import io.xpipe.core.process.ShellControl;
import io.xpipe.ext.base.browser.MultiExecuteSelectionAction; import io.xpipe.ext.base.browser.MultiExecuteSelectionAction;
import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.scene.Node; import javafx.scene.Node;
import org.kordamp.ikonli.javafx.FontIcon; import org.kordamp.ikonli.javafx.FontIcon;
import java.util.List; import java.util.List;
@ -47,7 +49,8 @@ public class RunScriptAction implements BrowserAction, BranchAction {
return actions; return actions;
} }
private List<? extends BrowserAction> createActionForScriptHierarchy(OpenFileSystemModel model, List<BrowserEntry> selected) { private List<? extends BrowserAction> createActionForScriptHierarchy(
OpenFileSystemModel model, List<BrowserEntry> selected) {
var sc = model.getFileSystem().getShell().orElseThrow(); var sc = model.getFileSystem().getShell().orElseThrow();
var hierarchy = ScriptHierarchy.buildEnabledHierarchy(ref -> { var hierarchy = ScriptHierarchy.buildEnabledHierarchy(ref -> {
if (!ref.getStore().isFileScript()) { if (!ref.getStore().isFileScript()) {
@ -67,10 +70,13 @@ public class RunScriptAction implements BrowserAction, BranchAction {
return createActionForScript(model, hierarchy.getLeafBase()); return createActionForScript(model, hierarchy.getLeafBase());
} }
var list = hierarchy.getChildren().stream().map(c -> createActionForScriptHierarchy(model, c)).toList(); var list = hierarchy.getChildren().stream()
.map(c -> createActionForScriptHierarchy(model, c))
.toList();
return new BranchAction() { return new BranchAction() {
@Override @Override
public List<? extends BrowserAction> getBranchingActions(OpenFileSystemModel model, List<BrowserEntry> entries) { public List<? extends BrowserAction> getBranchingActions(
OpenFileSystemModel model, List<BrowserEntry> entries) {
return list; return list;
} }
@ -91,7 +97,8 @@ public class RunScriptAction implements BrowserAction, BranchAction {
} }
@Override @Override
protected CommandBuilder createCommand(ShellControl sc, OpenFileSystemModel model, List<BrowserEntry> selected) { protected CommandBuilder createCommand(
ShellControl sc, OpenFileSystemModel model, List<BrowserEntry> selected) {
if (!(model.getBrowserModel() instanceof BrowserSessionModel)) { if (!(model.getBrowserModel() instanceof BrowserSessionModel)) {
return null; return null;
} }
@ -100,9 +107,7 @@ public class RunScriptAction implements BrowserAction, BranchAction {
var script = ScriptHelper.createExecScript(sc, content); var script = ScriptHelper.createExecScript(sc, content);
var builder = CommandBuilder.of().add(sc.getShellDialect().runScriptCommand(sc, script.toString())); var builder = CommandBuilder.of().add(sc.getShellDialect().runScriptCommand(sc, script.toString()));
selected.stream() selected.stream()
.map(browserEntry -> browserEntry .map(browserEntry -> browserEntry.getRawFileEntry().getPath())
.getRawFileEntry()
.getPath())
.forEach(s -> { .forEach(s -> {
builder.addFile(s); builder.addFile(s);
}); });

View file

@ -2,6 +2,7 @@ package io.xpipe.ext.base.script;
import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntryRef; import io.xpipe.app.storage.DataStoreEntryRef;
import lombok.Value; import lombok.Value;
import java.util.Comparator; import java.util.Comparator;
@ -37,10 +38,12 @@ public class ScriptHierarchy {
} }
} }
var top = all.stream().filter(ref -> { var top = all.stream()
var parent = DataStorage.get().getDefaultDisplayParent(ref.get()); .filter(ref -> {
return parent.isEmpty(); var parent = DataStorage.get().getDefaultDisplayParent(ref.get());
}).toList(); return parent.isEmpty();
})
.toList();
var mapped = top.stream() var mapped = top.stream()
.map(ref -> buildHierarchy(ref, check -> { .map(ref -> buildHierarchy(ref, check -> {
@ -56,17 +59,21 @@ public class ScriptHierarchy {
})) }))
.map(hierarchy -> condenseHierarchy(hierarchy)) .map(hierarchy -> condenseHierarchy(hierarchy))
.filter(hierarchy -> hierarchy.show()) .filter(hierarchy -> hierarchy.show())
.sorted(Comparator.comparing(scriptHierarchy -> scriptHierarchy.getBase().get().getName().toLowerCase())) .sorted(Comparator.comparing(scriptHierarchy ->
scriptHierarchy.getBase().get().getName().toLowerCase()))
.toList(); .toList();
return condenseHierarchy(new ScriptHierarchy(null, mapped)); return condenseHierarchy(new ScriptHierarchy(null, mapped));
} }
private static ScriptHierarchy buildHierarchy(DataStoreEntryRef<ScriptStore> ref, Predicate<DataStoreEntryRef<ScriptStore>> include) { private static ScriptHierarchy buildHierarchy(
DataStoreEntryRef<ScriptStore> ref, Predicate<DataStoreEntryRef<ScriptStore>> include) {
if (ref.getStore() instanceof ScriptGroupStore groupStore) { if (ref.getStore() instanceof ScriptGroupStore groupStore) {
var children = groupStore.getEffectiveScripts().stream().filter(include) var children = groupStore.getEffectiveScripts().stream()
.filter(include)
.map(c -> buildHierarchy(c, include)) .map(c -> buildHierarchy(c, include))
.filter(hierarchy -> hierarchy.show()) .filter(hierarchy -> hierarchy.show())
.sorted(Comparator.comparing(scriptHierarchy -> scriptHierarchy.getBase().get().getName().toLowerCase())) .sorted(Comparator.comparing(scriptHierarchy ->
scriptHierarchy.getBase().get().getName().toLowerCase()))
.toList(); .toList();
return new ScriptHierarchy(ref, children); return new ScriptHierarchy(ref, children);
} else { } else {
@ -74,11 +81,9 @@ public class ScriptHierarchy {
} }
} }
public static ScriptHierarchy condenseHierarchy(ScriptHierarchy hierarchy) { public static ScriptHierarchy condenseHierarchy(ScriptHierarchy hierarchy) {
var children = hierarchy.getChildren().stream() var children =
.map(c -> condenseHierarchy(c)) hierarchy.getChildren().stream().map(c -> condenseHierarchy(c)).toList();
.toList();
if (children.size() == 1 && !children.getFirst().isLeaf()) { if (children.size() == 1 && !children.getFirst().isLeaf()) {
var nestedChildren = children.getFirst().getChildren(); var nestedChildren = children.getFirst().getChildren();
return new ScriptHierarchy(hierarchy.getBase(), nestedChildren); return new ScriptHierarchy(hierarchy.getBase(), nestedChildren);

View file

@ -149,8 +149,8 @@ public abstract class ScriptStore extends JacksonizedValue implements DataStore,
public static List<DataStoreEntryRef<ScriptStore>> getEnabledScripts() { public static List<DataStoreEntryRef<ScriptStore>> getEnabledScripts() {
return DataStorage.get().getStoreEntries().stream() return DataStorage.get().getStoreEntries().stream()
.filter(dataStoreEntry -> dataStoreEntry.getValidity().isUsable() && .filter(dataStoreEntry -> dataStoreEntry.getValidity().isUsable()
dataStoreEntry.getStore() instanceof ScriptStore scriptStore && dataStoreEntry.getStore() instanceof ScriptStore scriptStore
&& scriptStore.getState().isEnabled()) && scriptStore.getState().isEnabled())
.map(DataStoreEntry::<ScriptStore>ref) .map(DataStoreEntry::<ScriptStore>ref)
.toList(); .toList();
@ -161,7 +161,8 @@ public abstract class ScriptStore extends JacksonizedValue implements DataStore,
scripts.forEach(scriptStoreDataStoreEntryRef -> scripts.forEach(scriptStoreDataStoreEntryRef ->
scriptStoreDataStoreEntryRef.getStore().queryFlattenedScripts(seen)); scriptStoreDataStoreEntryRef.getStore().queryFlattenedScripts(seen));
var dependencies = new HashMap<DataStoreEntryRef<? extends ScriptStore>, Set<DataStoreEntryRef<SimpleScriptStore>>>(); var dependencies =
new HashMap<DataStoreEntryRef<? extends ScriptStore>, Set<DataStoreEntryRef<SimpleScriptStore>>>();
seen.forEach(ref -> { seen.forEach(ref -> {
var f = new HashSet<>(ref.getStore().queryFlattenedScripts()); var f = new HashSet<>(ref.getStore().queryFlattenedScripts());
f.remove(ref); f.remove(ref);

View file

@ -8,9 +8,9 @@ import io.xpipe.core.process.ShellControl;
import io.xpipe.core.process.ShellDialect; import io.xpipe.core.process.ShellDialect;
import io.xpipe.core.process.ShellInitCommand; import io.xpipe.core.process.ShellInitCommand;
import io.xpipe.core.util.ValidationException; import io.xpipe.core.util.ValidationException;
import io.xpipe.ext.base.SelfReferentialStore;
import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonTypeName;
import io.xpipe.ext.base.SelfReferentialStore;
import lombok.Getter; import lombok.Getter;
import lombok.experimental.SuperBuilder; import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized; import lombok.extern.jackson.Jacksonized;
@ -62,7 +62,10 @@ public class SimpleScriptStore extends ScriptStore implements ShellInitCommand.T
public String assembleScriptChain(ShellControl shellControl) { public String assembleScriptChain(ShellControl shellControl) {
var nl = shellControl.getShellDialect().getNewLine().getNewLineString(); var nl = shellControl.getShellDialect().getNewLine().getNewLineString();
var all = queryFlattenedScripts(); var all = queryFlattenedScripts();
var r = all.stream().map(ref -> ref.getStore().assembleScript(shellControl)).filter(s -> s != null).toList(); var r = all.stream()
.map(ref -> ref.getStore().assembleScript(shellControl))
.filter(s -> s != null)
.toList();
if (r.isEmpty()) { if (r.isEmpty()) {
return null; return null;
} }
@ -94,7 +97,12 @@ public class SimpleScriptStore extends ScriptStore implements ShellInitCommand.T
@Override @Override
public List<DataStoreEntryRef<ScriptStore>> getEffectiveScripts() { public List<DataStoreEntryRef<ScriptStore>> getEffectiveScripts() {
return scripts != null ? scripts.stream().filter(Objects::nonNull).filter(ref -> ref.get().getValidity().isUsable()).toList() : List.of(); return scripts != null
? scripts.stream()
.filter(Objects::nonNull)
.filter(ref -> ref.get().getValidity().isUsable())
.toList()
: List.of();
} }
@Override @Override

View file

@ -13,7 +13,7 @@ public class ServiceOpenAction implements ActionProvider {
@Override @Override
public BranchDataStoreCallSite<?> getBranchDataStoreCallSite() { public BranchDataStoreCallSite<?> getBranchDataStoreCallSite() {
return new BranchDataStoreCallSite<DataStore>() { return new BranchDataStoreCallSite<>() {
@Override @Override
public boolean isMajor(DataStoreEntryRef<DataStore> o) { public boolean isMajor(DataStoreEntryRef<DataStore> o) {
return true; return true;

View file

@ -15,6 +15,7 @@ import io.xpipe.app.util.DataStoreFormatter;
import io.xpipe.app.util.TerminalLauncher; import io.xpipe.app.util.TerminalLauncher;
import io.xpipe.core.store.ShellStore; import io.xpipe.core.store.ShellStore;
import io.xpipe.ext.base.script.ScriptStore; import io.xpipe.ext.base.script.ScriptStore;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
@ -26,17 +27,21 @@ public interface ShellStoreProvider extends DataStoreProvider {
@Override @Override
public void execute() throws Exception { public void execute() throws Exception {
ShellStore store = entry.getStore().asNeeded(); ShellStore store = entry.getStore().asNeeded();
TerminalLauncher.open(entry, DataStorage.get().getStoreEntryDisplayName(entry), null, TerminalLauncher.open(
entry,
DataStorage.get().getStoreEntryDisplayName(entry),
null,
ScriptStore.controlWithDefaultScripts(store.control())); ScriptStore.controlWithDefaultScripts(store.control()));
} }
}; };
} }
@Override @Override
default ActionProvider.Action browserAction(BrowserSessionModel sessionModel, DataStoreEntry store, BooleanProperty busy) { default ActionProvider.Action browserAction(
BrowserSessionModel sessionModel, DataStoreEntry store, BooleanProperty busy) {
return new ActionProvider.Action() { return new ActionProvider.Action() {
@Override @Override
public void execute() throws Exception { public void execute() {
sessionModel.openFileSystemAsync(store.ref(), null, busy); sessionModel.openFileSystemAsync(store.ref(), null, busy);
} }
}; };