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
!dist/javafx/**/lib
!dist/javafx/**/bin
dev.properties
xcuserdata/
*.dylib
project.xcworkspace

View file

@ -1,7 +1,5 @@
package io.xpipe.app.beacon;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import io.xpipe.app.core.AppResources;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
@ -10,6 +8,9 @@ import io.xpipe.beacon.BeaconConfig;
import io.xpipe.beacon.BeaconInterface;
import io.xpipe.core.process.OsType;
import io.xpipe.core.util.XPipeInstallation;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import lombok.Getter;
import java.io.IOException;
@ -126,7 +127,8 @@ public class AppBeaconServer {
}
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 -> {
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))
.findFirst()
.orElseThrow();
setMethod.invoke(b, (Object) s);
setMethod.invoke(b, s);
var m = b.getClass().getDeclaredMethod("build");
m.setAccessible(true);

View file

@ -1,23 +1,31 @@
package io.xpipe.app.beacon.impl;
import com.sun.net.httpserver.HttpExchange;
import io.xpipe.app.util.TerminalLauncherManager;
import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.api.SshLaunchExchange;
import io.xpipe.core.process.ProcessControlProvider;
import io.xpipe.core.process.ShellDialects;
import com.sun.net.httpserver.HttpExchange;
public class SshLaunchExchangeImpl extends SshLaunchExchange {
@Override
public Object handle(HttpExchange exchange, Request msg) throws Exception {
var usedDialect = ShellDialects.ALL.stream().filter(dialect -> dialect.getExecutableName().equalsIgnoreCase(msg.getArguments())).findFirst();
if (msg.getArguments() != null && usedDialect.isEmpty() && !msg.getArguments().contains("SSH_ORIGINAL_COMMAND")) {
var usedDialect = ShellDialects.ALL.stream()
.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());
}
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();
}
}

View file

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

View file

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

View file

@ -10,7 +10,7 @@ public interface ApplicationPathAction extends BrowserAction {
String getExecutable();
@Override
default void init(OpenFileSystemModel model) throws Exception {
default void init(OpenFileSystemModel model) {
// Cache result for later calls
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.fs.OpenFileSystemModel;
import io.xpipe.app.util.LicenseProvider;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.List;
@ -27,15 +29,13 @@ public interface BranchAction extends BrowserAction {
m.setDisable(!isActive(model, selected));
if (getProFeatureId() != null
&& !LicenseProvider.get()
.getFeature(getProFeatureId())
.isSupported()) {
&& !LicenseProvider.get().getFeature(getProFeatureId()).isSupported()) {
m.setDisable(true);
m.setGraphic(new FontIcon("mdi2p-professional-hexagon"));
}
return m;
}
List<? extends BrowserAction> getBranchingActions(OpenFileSystemModel model, List<BrowserEntry> entries);
}

View file

@ -25,10 +25,15 @@ public interface BrowserAction {
.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
? List.of((LeafAction) browserAction)
: ((BranchAction) browserAction).getBranchingActions(model, entries).stream().map(action -> getFlattened(action, model, entries)).flatMap(List::stream).toList();
? List.of((LeafAction) browserAction)
: ((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) {
@ -41,9 +46,9 @@ public interface BrowserAction {
default List<BrowserEntry> resolveFilesIfNeeded(List<BrowserEntry> selected) {
return automaticallyResolveLinks()
? selected.stream()
.map(browserEntry ->
new BrowserEntry(browserEntry.getRawFileEntry().resolved(), browserEntry.getModel()))
.toList()
.map(browserEntry ->
new BrowserEntry(browserEntry.getRawFileEntry().resolved(), browserEntry.getModel()))
.toList()
: 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.core.AppFont;
import io.xpipe.app.util.InputHelper;
import javafx.scene.control.ContextMenu;
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();
if (typed.isEmpty()) {
return;
@ -165,15 +166,15 @@ public final class BrowserFileListComp extends SimpleComp {
var updated = typedSelection.get() + typed;
var found = fileList.getShown().getValue().stream()
.filter(browserEntry ->
browserEntry.getFileName().toLowerCase().startsWith(updated.toLowerCase()))
.filter(browserEntry -> browserEntry.getFileName().toLowerCase().startsWith(updated.toLowerCase()))
.findFirst();
if (found.isEmpty()) {
if (typedSelection.get().isEmpty()) {
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) {
lastType.set(Instant.now());
event.consume();
@ -599,7 +600,8 @@ public final class BrowserFileListComp extends SimpleComp {
browserEntry.getFileName().toLowerCase().startsWith(selection))
.findFirst();
// 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()) {
return;
}

View file

@ -99,7 +99,9 @@ public final class BrowserFileListModel {
}
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;
}

View file

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

View file

@ -1,7 +1,5 @@
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.comp.base.MultiContentComp;
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.util.BooleanScope;
import io.xpipe.app.util.ContextMenuHelper;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleBooleanProperty;
@ -28,6 +27,9 @@ import javafx.scene.input.*;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import atlantafx.base.controls.RingProgressIndicator;
import atlantafx.base.theme.Styles;
import java.util.*;
import static atlantafx.base.theme.Styles.DENSE;
@ -205,7 +207,8 @@ public class BrowserSessionTabsComp extends SimpleComp {
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();
keyEvent.consume();
}

View file

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

View file

@ -40,20 +40,15 @@ public class SystemStateComp extends SimpleComp {
var success = Styles.toDataURI(
"""
.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; }
"""
);
var other =
Styles.toDataURI(
"""
""");
var other = Styles.toDataURI(
"""
.stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-accent-emphasis; }
"""
);
""");
var pane = new StackedFontIcon();
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.augment.GrowAugment;
import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.geometry.HPos;
@ -41,10 +42,9 @@ public class DenseStoreEntryComp extends StoreEntryComp {
() -> {
var val = summary.getValue();
var p = getWrapper().getEntry().getProvider();
if (val != null && grid.isHover()
&& p.alwaysShowSummary()) {
if (val != null && grid.isHover() && p.alwaysShowSummary()) {
return val;
} else if (info.getValue() == null && p.alwaysShowSummary()){
} else if (info.getValue() == null && p.alwaysShowSummary()) {
return val;
} else {
return info.getValue();

View file

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

View file

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

View file

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

View file

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

View file

@ -16,7 +16,9 @@ public class AppRosettaCheck {
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()) {
return;
}

View file

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

View file

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

View file

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

View file

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

View file

@ -18,5 +18,5 @@ public enum DataStoreUsageCategory {
@JsonProperty("group")
GROUP,
@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.util.FailableRunnable;
import io.xpipe.core.util.ModuleLayerLoader;
import lombok.AllArgsConstructor;
import lombok.Value;

View file

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

View file

@ -114,7 +114,8 @@ public class DataStoreChoiceComp<T extends DataStore> extends SimpleComp {
StoreViewState.get().getActiveCategory(),
selectedCategory)
.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(() -> {
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.setAccessibleText("Filter");
filter.addEventFilter(KeyEvent.KEY_PRESSED,event -> {
filter.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
if (new KeyCodeCombination(KeyCode.ESCAPE).match(event)) {
filter.getScene().getRoot().requestFocus();
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.SimpleCompStructure;
import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.beans.property.Property;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.scene.control.ComboBox;
import javafx.scene.control.skin.ComboBoxListViewSkin;
import javafx.scene.input.KeyEvent;
import lombok.AccessLevel;
import lombok.experimental.FieldDefaults;
@ -33,7 +35,8 @@ public class IntComboFieldComp extends Comp<CompStructure<ComboBox<String>>> {
var text = new ComboBox<String>();
text.setEditable(true);
text.setValue(value.getValue() != null ? value.getValue().toString() : null);
text.setItems(FXCollections.observableList(predefined.stream().map(integer -> "" + integer).toList()));
text.setItems(FXCollections.observableList(
predefined.stream().map(integer -> "" + integer).toList()));
text.setMaxWidth(2000);
text.getStyleClass().add("int-combo-field-comp");
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.SimpleCompStructure;
import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.beans.property.Property;
import javafx.beans.value.ChangeListener;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import lombok.AccessLevel;
import lombok.experimental.FieldDefaults;

View file

@ -24,7 +24,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
public class PlatformThread {
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);
observable.addListener((observable1, oldValue, newValue) -> {
if (Platform.isFxApplicationThread()) {

View file

@ -57,7 +57,10 @@ public class ErrorEvent {
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) {

View file

@ -89,8 +89,9 @@ public class LauncherCommand implements Callable<Integer> {
// there might be another instance running, for example
// starting up or listening on another port
if (!AppDataLock.lock()) {
TrackEvent.info("Data directory " + AppProperties.get().getDataDir().toString()
+ " is already locked. Is another instance running?");
TrackEvent.info(
"Data directory " + AppProperties.get().getDataDir().toString()
+ " is already locked. Is another instance running?");
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
// Therefore the beacon client is not present.
// 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);
return;
}
try {
client.get().performRequest(DaemonFocusExchange.Request.builder().mode(getEffectiveMode()).build());
client.get()
.performRequest(DaemonFocusExchange.Request.builder()
.mode(getEffectiveMode())
.build());
if (!inputs.isEmpty()) {
client.get().performRequest(DaemonOpenExchange.Request.builder().arguments(inputs).build());
client.get()
.performRequest(DaemonOpenExchange.Request.builder()
.arguments(inputs)
.build());
}
} catch (Exception ex) {
var cli = XPipeInstallation.getLocalDefaultCliExecutable();
ErrorEvent.fromThrowable("Unable to connect to existing running daemon instance as it did not respond." +
" Either try to kill the process xpiped manually or use the command \"" +
cli +
"\" daemon stop --force.", ex).term().expected().handle();
ErrorEvent.fromThrowable(
"Unable to connect to existing running daemon instance as it did not respond."
+ " Either try to kill the process xpiped manually or use the command \""
+ cli
+ "\" daemon stop --force.",
ex)
.term()
.expected()
.handle();
}
if (OsType.getLocal().equals(OsType.MACOS)) {
Desktop.getDesktop().setOpenURIHandler(e -> {
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) {
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.JfxHelper;
import io.xpipe.app.util.OptionsBuilder;
import io.xpipe.core.process.OsType;
import javafx.beans.property.SimpleStringProperty;
import javafx.geometry.Insets;
import javafx.scene.control.ScrollPane;
@ -41,9 +41,9 @@ public class AboutCategory extends AppPrefsCategory {
null)
.addComp(
new TileButtonComp("tryPtb", "tryPtbDescription", "mdi2t-test-tube", e -> {
Hyperlinks.open(Hyperlinks.GITHUB_PTB);
e.consume();
})
Hyperlinks.open(Hyperlinks.GITHUB_PTB);
e.consume();
})
.grow(true, false),
null)
.addComp(
@ -111,16 +111,15 @@ public class AboutCategory extends AppPrefsCategory {
private Comp<?> createProperties() {
var title = Comp.of(() -> {
return JfxHelper.createNamedEntry(
AppI18n.observable("xPipeClient"),
new SimpleStringProperty(
"Version " + AppProperties.get().getVersion() + " ("
+ AppProperties.get().getArch() + ")"),
"logo.png");
});
return JfxHelper.createNamedEntry(
AppI18n.observable("xPipeClient"),
new SimpleStringProperty("Version " + AppProperties.get().getVersion() + " ("
+ AppProperties.get().getArch() + ")"),
"logo.png");
});
if (OsType.getLocal() != OsType.MACOS) {
title.styleClass(Styles.TEXT_BOLD);
title.styleClass(Styles.TEXT_BOLD);
}
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.core.util.InPlaceSecretValue;
import io.xpipe.core.util.ModuleHelper;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.beans.value.ObservableBooleanValue;
import javafx.beans.value.ObservableDoubleValue;
import javafx.beans.value.ObservableStringValue;
import javafx.beans.value.ObservableValue;
import lombok.Getter;
import lombok.Value;
import org.apache.commons.io.FileUtils;

View file

@ -114,10 +114,10 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue {
protected Optional<Path> determineFromPath() {
// Try to locate if it is in the Path
try (var sc = LocalShell.getShell()
.start()) {
try (var sc = LocalShell.getShell().start()) {
var out = sc.command(CommandBuilder.ofFunction(
var1 -> var1.getShellDialect().getWhichCommand(executable))).readStdoutIfPossible();
var1 -> var1.getShellDialect().getWhichCommand(executable)))
.readStdoutIfPossible();
if (out.isPresent()) {
var first = out.get().lines().findFirst();
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.OsType;
import io.xpipe.core.util.SecretValue;
import lombok.Value;
import java.io.IOException;
@ -16,7 +17,7 @@ import java.util.function.Supplier;
public interface ExternalRdpClientType extends PrefsChoiceValue {
public static ExternalRdpClientType getApplicationLauncher() {
static ExternalRdpClientType getApplicationLauncher() {
if (OsType.getLocal() == OsType.WINDOWS) {
return MSTSC;
} else {
@ -76,9 +77,10 @@ public interface ExternalRdpClientType extends PrefsChoiceValue {
@Override
protected Optional<Path> determineInstallation() {
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);
} catch (Exception e) {
} catch (Exception e) {
ErrorEvent.fromThrowable(e).omit().handle();
return Optional.empty();
}
@ -87,7 +89,11 @@ public interface ExternalRdpClientType extends PrefsChoiceValue {
@Override
protected void execute(Path file, LaunchConfiguration configuration) throws Exception {
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(() -> {
// Startup is slow
ThreadHelper.sleep(10000);

View file

@ -12,9 +12,11 @@ import io.xpipe.app.util.OptionsBuilder;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.process.OsType;
import io.xpipe.core.util.XPipeInstallation;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Insets;
import javafx.scene.control.ButtonType;
import org.apache.commons.io.FileUtils;
import java.nio.file.Files;
@ -54,21 +56,33 @@ public class WorkspaceCreationAlert {
}
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;
}
var shortcutName = (AppProperties.get().isStaging() ? "XPipe PTB" : "XPipe") + " (" + name.get() + ")";
var file = switch (OsType.getLocal()) {
case OsType.Windows w -> {
var exec = XPipeInstallation.getCurrentInstallationBasePath().resolve(XPipeInstallation.getDaemonExecutablePath(w)).toString();
yield DesktopShortcuts.create(exec, "-Dio.xpipe.app.dataDir=\"" + path.get().toString() + "\" -Dio.xpipe.app.acceptEula=true", shortcutName);
}
default -> {
var exec = XPipeInstallation.getCurrentInstallationBasePath().resolve(XPipeInstallation.getRelativeCliExecutablePath(OsType.getLocal())).toString();
yield DesktopShortcuts.create(exec, "-d \"" + path.get().toString() + "\" --accept-eula", shortcutName);
}
};
var file =
switch (OsType.getLocal()) {
case OsType.Windows w -> {
var exec = XPipeInstallation.getCurrentInstallationBasePath()
.resolve(XPipeInstallation.getDaemonExecutablePath(w))
.toString();
yield DesktopShortcuts.create(
exec,
"-Dio.xpipe.app.dataDir=\"" + path.get().toString()
+ "\" -Dio.xpipe.app.acceptEula=true",
shortcutName);
}
default -> {
var exec = XPipeInstallation.getCurrentInstallationBasePath()
.resolve(XPipeInstallation.getRelativeCliExecutablePath(OsType.getLocal()))
.toString();
yield DesktopShortcuts.create(
exec, "-d \"" + path.get().toString() + "\" --accept-eula", shortcutName);
}
};
DesktopHelper.browseFileInDirectory(file);
OperationMode.close();
}

View file

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

View file

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

View file

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

View file

@ -139,7 +139,7 @@ public class AppInstaller {
public static final class Debian extends InstallerAssetType {
@Override
public void installLocal(Path file) throws Exception {
public void installLocal(Path file) {
var start = AppPrefs.get() != null
&& AppPrefs.get().terminalType().getValue() != null
&& AppPrefs.get().terminalType().getValue().isAvailable();
@ -177,7 +177,7 @@ public class AppInstaller {
public static final class Rpm extends InstallerAssetType {
@Override
public void installLocal(Path file) throws Exception {
public void installLocal(Path file) {
var start = AppPrefs.get() != null
&& AppPrefs.get().terminalType().getValue() != null
&& AppPrefs.get().terminalType().getValue().isAvailable();
@ -215,7 +215,7 @@ public class AppInstaller {
public static final class Pkg extends InstallerAssetType {
@Override
public void installLocal(Path file) throws Exception {
public void installLocal(Path file) {
var start = AppPrefs.get() != null
&& AppPrefs.get().terminalType().getValue() != null
&& 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.core.process.ShellDialects;
import io.xpipe.core.process.ShellStoreState;
import io.xpipe.core.process.ShellTtyState;
import javafx.beans.value.ObservableValue;
public class DataStoreFormatter {

View file

@ -3,14 +3,10 @@ package io.xpipe.app.util;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.core.process.CommandBuilder;
import io.xpipe.core.process.CommandControl;
import io.xpipe.core.process.OsType;
import lombok.SneakyThrows;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
@ -109,21 +105,4 @@ public class FileOpener {
},
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) {
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);
}
};
@ -50,7 +51,8 @@ public class InputHelper {
public static void onRight(EventTarget target, boolean filter, Consumer<KeyEvent> r) {
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);
}
};

View file

@ -37,8 +37,8 @@ public class NativeBridge {
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) {
ThreadHelper.runAsync(() -> {
var showForCon = entry == null || (entry.getStore() instanceof ShellStore && (
!(entry.getStorePersistentState() instanceof ShellStoreState shellStoreState) ||
shellStoreState.getTtyState() == null || shellStoreState.getTtyState() == ShellTtyState.NONE));
var showForCon = entry == null
|| (entry.getStore() instanceof ShellStore
&& (!(entry.getStorePersistentState() instanceof ShellStoreState shellStoreState)
|| shellStoreState.getTtyState() == null
|| shellStoreState.getTtyState() == ShellTtyState.NONE));
if (showForCon) {
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.ShellControl;
import io.xpipe.core.util.XPipeInstallation;
import lombok.Getter;
import lombok.Setter;
@ -27,6 +28,7 @@ public class SshLocalBridge {
private final Path directory;
private final int port;
private final String user;
@Setter
private ShellControl runningShell;
@ -74,22 +76,39 @@ public class SshLocalBridge {
INSTANCE = new SshLocalBridge(bridgeDir, port, user);
var hostKey = INSTANCE.getHostKey();
if (!sc.getShellDialect().createFileExistsCommand(sc, hostKey.toString()).executeAndCheck()) {
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();
if (!sc.getShellDialect()
.createFileExistsCommand(sc, hostKey.toString())
.executeAndCheck()) {
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();
if (!sc.getShellDialect().createFileExistsCommand(sc, idKey.toString()).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();
if (!sc.getShellDialect()
.createFileExistsCommand(sc, idKey.toString())
.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 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 content = """
var content =
"""
ForceCommand %s
PidFile "%s"
StrictModes no
@ -101,14 +120,24 @@ public class SshLocalBridge {
PubkeyAuthentication yes
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);
// INSTANCE.updateConfig();
var exec = getSshd(sc);
var launchCommand = CommandBuilder.of().addFile(exec).add("-f").addFile(INSTANCE.getConfig().toString()).add("-p", "" + port);
var control = ProcessControlProvider.get().createLocalProcessControl(true).start();
var exec = getSshd(sc);
var launchCommand = CommandBuilder.of()
.addFile(exec)
.add("-f")
.addFile(INSTANCE.getConfig().toString())
.add("-p", "" + port);
var control =
ProcessControlProvider.get().createLocalProcessControl(true).start();
control.writeLine(launchCommand.buildFull(control));
INSTANCE.setRunningShell(control);
}
@ -125,19 +154,24 @@ public class SshLocalBridge {
return;
}
var updated = content + "\n\n" + """
var updated = content + "\n\n"
+ """
Host %s
HostName localhost
User "%s"
Port %s
IdentityFile "%s"
""".formatted(getName(), port, user, getIdentityKey());
"""
.formatted(getName(), port, user, getIdentityKey());
Files.writeString(file, updated);
}
private static String getSshd(ShellControl sc) throws Exception {
if (OsType.getLocal() == OsType.WINDOWS) {
return XPipeInstallation.getLocalBundledToolsDirectory().resolve("openssh").resolve("sshd").toString();
return XPipeInstallation.getLocalBundledToolsDirectory()
.resolve("openssh")
.resolve("sshd")
.toString();
} else {
var exec = sc.executeSimpleStringCommand(sc.getShellDialect().getWhichCommand("sshd"));
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.WorkingDirectoryFunction;
import io.xpipe.core.store.FilePath;
import lombok.Setter;
import lombok.Value;
import lombok.experimental.NonFinal;
@ -90,7 +91,7 @@ public class TerminalLauncherManager {
return waitForCompletion(e);
}
public static Path waitForCompletion(Entry e) throws BeaconClientException, BeaconServerException {
public static Path waitForCompletion(Entry e) throws BeaconServerException {
while (true) {
if (e.result == null) {
ThreadHelper.sleep(10);

View file

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

View file

@ -20,7 +20,8 @@ public class BeaconServer {
public static boolean isReachable(int port) {
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;
} catch (Exception e) {
return false;

View file

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

View file

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

View file

@ -97,7 +97,13 @@ public interface OsType {
public List<String> determineInterestingPaths(ShellControl pc) throws Exception {
var home = getHomeDirectory(pc);
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
@ -114,7 +120,6 @@ public interface OsType {
public String getName() {
return "Linux";
}
}
final class Linux extends Unix implements OsType, Local, Any {
@ -123,7 +128,6 @@ public interface OsType {
public String getId() {
return "linux";
}
}
final class Solaris extends Unix implements Any {}
@ -171,6 +175,5 @@ public interface OsType {
public String getName() {
return "Mac";
}
}
}

View file

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

View file

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

View file

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

View file

@ -3,7 +3,6 @@ package io.xpipe.core.store;
import io.xpipe.core.process.ShellControl;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
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");
}
var running = new AtomicBoolean();
var runningCounter = new AtomicInteger();
var counter = new AtomicInteger();
var sessions = new ArrayList<NetworkTunnelSession>();
NetworkTunnelStore current = this;
@ -79,14 +76,6 @@ public interface NetworkTunnelStore extends DataStore {
var currentRemotePort =
sessions.isEmpty() ? remotePort : sessions.getLast().getLocalPort();
var t = func.create(currentLocalPort, currentRemotePort);
t.addListener(r -> {
if (r) {
runningCounter.incrementAndGet();
} else {
runningCounter.decrementAndGet();
}
running.set(runningCounter.get() == counter.get());
});
t.start();
sessions.add(t);
counter.incrementAndGet();

View file

@ -4,12 +4,6 @@ import lombok.EqualsAndHashCode;
import lombok.SneakyThrows;
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.ByteOrder;
import java.security.NoSuchAlgorithmException;
@ -17,6 +11,12 @@ import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
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
@EqualsAndHashCode(callSuper = true)

View file

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

View file

@ -7,8 +7,10 @@ import io.xpipe.app.ext.ActionProvider;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.core.store.ShellStore;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue;
import lombok.Value;
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.storage.DataStoreEntryRef;
import io.xpipe.core.store.DataStore;
import javafx.beans.value.ObservableValue;
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.store.ShellStore;
import io.xpipe.ext.base.script.ScriptHierarchy;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import lombok.Value;
import java.util.List;
@ -34,7 +36,8 @@ public class RunScriptActionMenu implements ActionProvider {
var script = hierarchy.getLeafBase().getStore().assembleScriptChain(sc);
TerminalLauncher.open(
shellStore.getEntry(),
hierarchy.getLeafBase().get().getName() + " - " + shellStore.get().getName(),
hierarchy.getLeafBase().get().getName() + " - "
+ shellStore.get().getName(),
null,
sc.command(script));
}
@ -145,7 +148,8 @@ public class RunScriptActionMenu implements ActionProvider {
@Override
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
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 {
@Override
public void execute() throws Exception {
public void execute() {
StoreViewState.get().getAllScriptsCategory().select();
}
}
@ -255,8 +261,12 @@ public class RunScriptActionMenu implements ActionProvider {
var state = o.get().getStorePersistentState();
if (state instanceof ShellStoreState shellStoreState) {
return (shellStoreState.getShellDialect() == null
|| shellStoreState.getShellDialect().getDumbMode().supportsAnyPossibleInteraction()) &&
(shellStoreState.getTtyState() == null || shellStoreState.getTtyState() == ShellTtyState.NONE);
|| shellStoreState
.getShellDialect()
.getDumbMode()
.supportsAnyPossibleInteraction())
&& (shellStoreState.getTtyState() == null
|| shellStoreState.getTtyState() == ShellTtyState.NONE);
} else {
return false;
}
@ -280,7 +290,9 @@ public class RunScriptActionMenu implements ActionProvider {
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()) {
return List.of(new NoScriptsActionProvider());
} else {

View file

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

View file

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

View file

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

View file

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

View file

@ -97,7 +97,8 @@ public class DesktopEnvironmentStore extends JacksonizedValue
var scriptFile = base.getStore().createScript(dialect, toExecute);
var launchScriptFile = base.getStore()
.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);
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.ShellControl;
import io.xpipe.ext.base.browser.MultiExecuteSelectionAction;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Node;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.List;
@ -47,7 +49,8 @@ public class RunScriptAction implements BrowserAction, BranchAction {
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 hierarchy = ScriptHierarchy.buildEnabledHierarchy(ref -> {
if (!ref.getStore().isFileScript()) {
@ -67,10 +70,13 @@ public class RunScriptAction implements BrowserAction, BranchAction {
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() {
@Override
public List<? extends BrowserAction> getBranchingActions(OpenFileSystemModel model, List<BrowserEntry> entries) {
public List<? extends BrowserAction> getBranchingActions(
OpenFileSystemModel model, List<BrowserEntry> entries) {
return list;
}
@ -91,7 +97,8 @@ public class RunScriptAction implements BrowserAction, BranchAction {
}
@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)) {
return null;
}
@ -100,9 +107,7 @@ public class RunScriptAction implements BrowserAction, BranchAction {
var script = ScriptHelper.createExecScript(sc, content);
var builder = CommandBuilder.of().add(sc.getShellDialect().runScriptCommand(sc, script.toString()));
selected.stream()
.map(browserEntry -> browserEntry
.getRawFileEntry()
.getPath())
.map(browserEntry -> browserEntry.getRawFileEntry().getPath())
.forEach(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.DataStoreEntryRef;
import lombok.Value;
import java.util.Comparator;
@ -37,10 +38,12 @@ public class ScriptHierarchy {
}
}
var top = all.stream().filter(ref -> {
var parent = DataStorage.get().getDefaultDisplayParent(ref.get());
return parent.isEmpty();
}).toList();
var top = all.stream()
.filter(ref -> {
var parent = DataStorage.get().getDefaultDisplayParent(ref.get());
return parent.isEmpty();
})
.toList();
var mapped = top.stream()
.map(ref -> buildHierarchy(ref, check -> {
@ -56,17 +59,21 @@ public class ScriptHierarchy {
}))
.map(hierarchy -> condenseHierarchy(hierarchy))
.filter(hierarchy -> hierarchy.show())
.sorted(Comparator.comparing(scriptHierarchy -> scriptHierarchy.getBase().get().getName().toLowerCase()))
.sorted(Comparator.comparing(scriptHierarchy ->
scriptHierarchy.getBase().get().getName().toLowerCase()))
.toList();
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) {
var children = groupStore.getEffectiveScripts().stream().filter(include)
var children = groupStore.getEffectiveScripts().stream()
.filter(include)
.map(c -> buildHierarchy(c, include))
.filter(hierarchy -> hierarchy.show())
.sorted(Comparator.comparing(scriptHierarchy -> scriptHierarchy.getBase().get().getName().toLowerCase()))
.sorted(Comparator.comparing(scriptHierarchy ->
scriptHierarchy.getBase().get().getName().toLowerCase()))
.toList();
return new ScriptHierarchy(ref, children);
} else {
@ -74,11 +81,9 @@ public class ScriptHierarchy {
}
}
public static ScriptHierarchy condenseHierarchy(ScriptHierarchy hierarchy) {
var children = hierarchy.getChildren().stream()
.map(c -> condenseHierarchy(c))
.toList();
var children =
hierarchy.getChildren().stream().map(c -> condenseHierarchy(c)).toList();
if (children.size() == 1 && !children.getFirst().isLeaf()) {
var nestedChildren = children.getFirst().getChildren();
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() {
return DataStorage.get().getStoreEntries().stream()
.filter(dataStoreEntry -> dataStoreEntry.getValidity().isUsable() &&
dataStoreEntry.getStore() instanceof ScriptStore scriptStore
.filter(dataStoreEntry -> dataStoreEntry.getValidity().isUsable()
&& dataStoreEntry.getStore() instanceof ScriptStore scriptStore
&& scriptStore.getState().isEnabled())
.map(DataStoreEntry::<ScriptStore>ref)
.toList();
@ -161,7 +161,8 @@ public abstract class ScriptStore extends JacksonizedValue implements DataStore,
scripts.forEach(scriptStoreDataStoreEntryRef ->
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 -> {
var f = new HashSet<>(ref.getStore().queryFlattenedScripts());
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.ShellInitCommand;
import io.xpipe.core.util.ValidationException;
import io.xpipe.ext.base.SelfReferentialStore;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.xpipe.ext.base.SelfReferentialStore;
import lombok.Getter;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
@ -62,7 +62,10 @@ public class SimpleScriptStore extends ScriptStore implements ShellInitCommand.T
public String assembleScriptChain(ShellControl shellControl) {
var nl = shellControl.getShellDialect().getNewLine().getNewLineString();
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()) {
return null;
}
@ -94,7 +97,12 @@ public class SimpleScriptStore extends ScriptStore implements ShellInitCommand.T
@Override
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

View file

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

View file

@ -15,6 +15,7 @@ import io.xpipe.app.util.DataStoreFormatter;
import io.xpipe.app.util.TerminalLauncher;
import io.xpipe.core.store.ShellStore;
import io.xpipe.ext.base.script.ScriptStore;
import javafx.beans.property.BooleanProperty;
import javafx.beans.value.ObservableValue;
@ -26,17 +27,21 @@ public interface ShellStoreProvider extends DataStoreProvider {
@Override
public void execute() throws Exception {
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()));
}
};
}
@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() {
@Override
public void execute() throws Exception {
public void execute() {
sessionModel.openFileSystemAsync(store.ref(), null, busy);
}
};