mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-06-23 08:30:38 +12:00
Merge branch 'xpipe-io:master' into patch-1
This commit is contained in:
commit
67059394f6
|
@ -23,12 +23,13 @@ XPipe is able to automatically detect your local installation and fetch the requ
|
|||
components from it when it is run in a development environment.
|
||||
|
||||
Note that in case the current master branch is ahead of the latest release, it might happen that there are some incompatibilities when loading data from your local XPipe installation.
|
||||
It is therefore recommended to always check out the matching version tag for your local repository and local XPipe installation.
|
||||
You can find the available version tags at https://github.com/xpipe-io/xpipe/tags
|
||||
You should therefore always check out the matching version tag for your local repository and local XPipe installation.
|
||||
You can find the available version tags at https://github.com/xpipe-io/xpipe/tags.
|
||||
So for example if you currently have XPipe `9.0` installed, you should run `git reset --hard 9.0` first to properly compile against it.
|
||||
|
||||
You need to have JDK for Java 22 installed to compile the project.
|
||||
You need to have JDK for Java 21 installed to compile the project.
|
||||
If you are on Linux or macOS, you can easily accomplish that by running the `setup.sh` script.
|
||||
On Windows, you have to manually install a JDK, e.g. from [Adoptium](https://adoptium.net/temurin/releases/?version=22).
|
||||
On Windows, you have to manually install a JDK, e.g. from [Adoptium](https://adoptium.net/temurin/releases/?version=21).
|
||||
|
||||
## Building and Running
|
||||
|
||||
|
@ -47,7 +48,7 @@ to connect to that debugger through [AttachMe](https://plugins.jetbrains.com/plu
|
|||
|
||||
## Modularity and IDEs
|
||||
|
||||
All XPipe components target [Java 22](https://openjdk.java.net/projects/jdk/22/) and make full use of the Java Module System (JPMS).
|
||||
All XPipe components target [Java 21](https://openjdk.java.net/projects/jdk/21/) and make full use of the Java Module System (JPMS).
|
||||
All components are modularized, including all their dependencies.
|
||||
In case a dependency is (sadly) not modularized yet, module information is manually added using [extra-java-module-info](https://github.com/gradlex-org/extra-java-module-info).
|
||||
Further, note that as this is a pretty complicated Java project that fully utilizes modularity,
|
||||
|
@ -55,7 +56,7 @@ many IDEs still have problems building this project properly.
|
|||
|
||||
For example, you can't build this project in eclipse or vscode as it will complain about missing modules.
|
||||
The tested and recommended IDE is IntelliJ.
|
||||
When setting up the project in IntelliJ, make sure that the correct JDK (Java 22)
|
||||
When setting up the project in IntelliJ, make sure that the correct JDK (Java 21)
|
||||
is selected both for the project and for gradle itself.
|
||||
|
||||
## Contributing guide
|
||||
|
@ -68,7 +69,7 @@ All code for handling external editors can be found [here](https://github.com/xp
|
|||
|
||||
### Implementing support for a new terminal
|
||||
|
||||
All code for handling external terminals can be found [here](https://github.com/xpipe-io/xpipe/blob/master/app/src/main/java/io/xpipe/app/terminal/ExternalTerminalType.java). There you will find plenty of working examples that you can use as a base for your own implementation.
|
||||
All code for handling external terminals can be found [here](https://github.com/xpipe-io/xpipe/blob/master/app/src/main/java/io/xpipe/app/terminal/). There you will find plenty of working examples that you can use as a base for your own implementation.
|
||||
|
||||
### Adding more file icons for specific types
|
||||
|
||||
|
|
|
@ -94,7 +94,7 @@ public class BrowserStatusBarComp extends SimpleComp {
|
|||
model.getFileList().getAll());
|
||||
var selectedComp = new LabelComp(Bindings.createStringBinding(
|
||||
() -> {
|
||||
if (selectedCount.getValue().intValue() == 0) {
|
||||
if (selectedCount.getValue() == 0) {
|
||||
return null;
|
||||
} else {
|
||||
return selectedCount.getValue() + " / " + allCount.getValue() + " selected";
|
||||
|
|
|
@ -3,14 +3,13 @@ package io.xpipe.app.browser.action;
|
|||
import io.xpipe.app.browser.file.BrowserEntry;
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.fxcomps.impl.TooltipAugment;
|
||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||
import io.xpipe.app.fxcomps.util.Shortcuts;
|
||||
import io.xpipe.app.util.BooleanScope;
|
||||
import io.xpipe.app.util.LicenseProvider;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.MenuItem;
|
||||
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -65,7 +64,13 @@ public interface LeafAction extends BrowserAction {
|
|||
default MenuItem toMenuItem(OpenFileSystemModel model, List<BrowserEntry> selected) {
|
||||
var name = getName(model, selected);
|
||||
var mi = new MenuItem();
|
||||
mi.textProperty().bind(name);
|
||||
mi.textProperty().bind(BindingsHelper.map(name, s -> {
|
||||
if (getProFeatureId() != null
|
||||
&& !LicenseProvider.get().getFeature(getProFeatureId()).isSupported()) {
|
||||
return s + " (Pro)";
|
||||
}
|
||||
return s;
|
||||
}));
|
||||
mi.setOnAction(event -> {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
BooleanScope.execute(model.getBusy(), () -> {
|
||||
|
@ -86,10 +91,8 @@ public interface LeafAction extends BrowserAction {
|
|||
mi.setMnemonicParsing(false);
|
||||
mi.setDisable(!isActive(model, selected));
|
||||
|
||||
if (getProFeatureId() != null
|
||||
&& !LicenseProvider.get().getFeature(getProFeatureId()).isSupported()) {
|
||||
if (getProFeatureId() != null && !LicenseProvider.get().getFeature(getProFeatureId()).isSupported()) {
|
||||
mi.setDisable(true);
|
||||
mi.setText(mi.getText() + " (Pro)");
|
||||
}
|
||||
|
||||
return mi;
|
||||
|
|
|
@ -8,6 +8,7 @@ import io.xpipe.app.util.InputHelper;
|
|||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.store.FileKind;
|
||||
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.geometry.Side;
|
||||
|
@ -84,38 +85,39 @@ public class BrowserQuickAccessContextMenu extends ContextMenu {
|
|||
}
|
||||
|
||||
private List<MenuItem> updateMenuItems(Menu m, BrowserEntry entry, boolean updateInstantly) throws Exception {
|
||||
var newFiles = model.getFileSystem()
|
||||
.listFiles(entry.getRawFileEntry().resolved().getPath());
|
||||
try (var s = newFiles) {
|
||||
var list = s.map(fileEntry -> fileEntry.resolved()).toList();
|
||||
// Wait until all files are listed, i.e. do not skip the stream elements
|
||||
list = list.subList(0, Math.min(list.size(), 150));
|
||||
List<FileSystem.FileEntry> list = new ArrayList<>();
|
||||
model.withFiles(entry.getRawFileEntry().resolved().getPath(), newFiles -> {
|
||||
try (var s = newFiles) {
|
||||
var l = s.map(fileEntry -> fileEntry.resolved()).toList();
|
||||
// Wait until all files are listed, i.e. do not skip the stream elements
|
||||
list.addAll(l.subList(0, Math.min(l.size(), 150)));
|
||||
}
|
||||
});
|
||||
|
||||
var newItems = new ArrayList<MenuItem>();
|
||||
if (list.isEmpty()) {
|
||||
var empty = new Menu("<empty>");
|
||||
empty.getStyleClass().add("leaf");
|
||||
newItems.add(empty);
|
||||
} else {
|
||||
var browserEntries = list.stream()
|
||||
.map(fileEntry -> new BrowserEntry(fileEntry, model.getFileList(), false))
|
||||
.toList();
|
||||
var menus = browserEntries.stream()
|
||||
.sorted(model.getFileList().order())
|
||||
.collect(Collectors.toMap(e -> e, e -> createItem(e), (v1, v2) -> v2, LinkedHashMap::new));
|
||||
var dirs = browserEntries.stream()
|
||||
.filter(e -> e.getRawFileEntry().getKind() == FileKind.DIRECTORY)
|
||||
.toList();
|
||||
if (dirs.size() == 1) {
|
||||
updateMenuItems((Menu) menus.get(dirs.getFirst()), dirs.getFirst(), true);
|
||||
}
|
||||
newItems.addAll(menus.values());
|
||||
var newItems = new ArrayList<MenuItem>();
|
||||
if (list.isEmpty()) {
|
||||
var empty = new Menu("<empty>");
|
||||
empty.getStyleClass().add("leaf");
|
||||
newItems.add(empty);
|
||||
} else {
|
||||
var browserEntries = list.stream()
|
||||
.map(fileEntry -> new BrowserEntry(fileEntry, model.getFileList(), false))
|
||||
.toList();
|
||||
var menus = browserEntries.stream()
|
||||
.sorted(model.getFileList().order())
|
||||
.collect(Collectors.toMap(e -> e, e -> createItem(e), (v1, v2) -> v2, LinkedHashMap::new));
|
||||
var dirs = browserEntries.stream()
|
||||
.filter(e -> e.getRawFileEntry().getKind() == FileKind.DIRECTORY)
|
||||
.toList();
|
||||
if (dirs.size() == 1) {
|
||||
updateMenuItems((Menu) menus.get(dirs.getFirst()), dirs.getFirst(), true);
|
||||
}
|
||||
if (updateInstantly) {
|
||||
m.getItems().setAll(newItems);
|
||||
}
|
||||
return newItems;
|
||||
newItems.addAll(menus.values());
|
||||
}
|
||||
if (updateInstantly) {
|
||||
m.getItems().setAll(newItems);
|
||||
}
|
||||
return newItems;
|
||||
}
|
||||
|
||||
@Getter
|
||||
|
|
|
@ -22,10 +22,8 @@ import io.xpipe.core.process.ShellDialects;
|
|||
import io.xpipe.core.process.ShellOpenFunction;
|
||||
import io.xpipe.core.store.*;
|
||||
import io.xpipe.core.util.FailableConsumer;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.*;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
|
@ -299,6 +297,18 @@ public final class OpenFileSystemModel extends BrowserSessionTab<FileSystemStore
|
|||
loadFilesSync(path);
|
||||
}
|
||||
|
||||
public void withFiles(String dir, FailableConsumer<Stream<FileSystem.FileEntry>, Exception> consumer) throws Exception {
|
||||
BooleanScope.executeExclusive(busy, () -> {
|
||||
if (dir != null) {
|
||||
startIfNeeded();
|
||||
var stream = getFileSystem().listFiles(dir);
|
||||
consumer.accept(stream);
|
||||
} else {
|
||||
consumer.accept(Stream.of());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean loadFilesSync(String dir) {
|
||||
try {
|
||||
if (dir != null) {
|
||||
|
|
|
@ -116,6 +116,11 @@ public class BrowserChooserComp extends SimpleComp {
|
|||
|
||||
var dialogPane = new DialogComp() {
|
||||
|
||||
@Override
|
||||
protected String finishKey() {
|
||||
return "select";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Comp<?> pane(Comp<?> content) {
|
||||
return content;
|
||||
|
|
|
@ -40,7 +40,7 @@ public class BrowserSessionMultiTab extends BrowserSessionTab<DataStore> {
|
|||
return true;
|
||||
}
|
||||
|
||||
public void init() throws Exception {}
|
||||
public void init() {}
|
||||
|
||||
public void close() {}
|
||||
}
|
||||
|
|
|
@ -123,7 +123,7 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
|||
});
|
||||
});
|
||||
|
||||
model.getSessionEntries().addListener((ListChangeListener<? super BrowserSessionTab>) c -> {
|
||||
model.getSessionEntries().addListener((ListChangeListener<? super BrowserSessionTab<?>>) c -> {
|
||||
while (c.next()) {
|
||||
for (var r : c.getRemoved()) {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
|
|
|
@ -60,7 +60,7 @@ public abstract class DialogComp extends Comp<CompStructure<Region>> {
|
|||
.addAll(customButtons().stream()
|
||||
.map(buttonComp -> buttonComp.createRegion())
|
||||
.toList());
|
||||
var nextButton = new ButtonComp(AppI18n.observable("finishStep"), null, this::finish)
|
||||
var nextButton = new ButtonComp(AppI18n.observable(finishKey()), null, this::finish)
|
||||
.apply(struc -> struc.get().setDefaultButton(true))
|
||||
.styleClass(Styles.ACCENT)
|
||||
.styleClass("next");
|
||||
|
@ -68,6 +68,10 @@ public abstract class DialogComp extends Comp<CompStructure<Region>> {
|
|||
return buttons;
|
||||
}
|
||||
|
||||
protected String finishKey() {
|
||||
return "finishStep";
|
||||
}
|
||||
|
||||
protected List<Comp<?>> customButtons() {
|
||||
return List.of();
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
|||
private final ObservableList<T> shown;
|
||||
private final ObservableList<T> all;
|
||||
private final Function<T, Comp<?>> compFunction;
|
||||
private int limit = Integer.MAX_VALUE;
|
||||
private final int limit = Integer.MAX_VALUE;
|
||||
|
||||
public ListBoxViewComp(ObservableList<T> shown, ObservableList<T> all, Function<T, Comp<?>> compFunction) {
|
||||
this.shown = PlatformThread.sync(shown);
|
||||
|
|
|
@ -5,17 +5,18 @@ import io.xpipe.app.core.AppI18n;
|
|||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.value.ObservableBooleanValue;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class StoreToggleComp extends SimpleComp {
|
||||
|
@ -28,6 +29,19 @@ public class StoreToggleComp extends SimpleComp {
|
|||
@Setter
|
||||
private ObservableBooleanValue customVisibility = new SimpleBooleanProperty(true);
|
||||
|
||||
public static <T extends DataStore> StoreToggleComp simpleToggle(String nameKey, StoreSection section, Function<T, Boolean> initial, BiConsumer<T, Boolean> setter) {
|
||||
return new StoreToggleComp(nameKey, section,new SimpleBooleanProperty(initial.apply(section.getWrapper().getEntry().getStore().asNeeded())),v -> {
|
||||
setter.accept(section.getWrapper().getEntry().getStore().asNeeded(),v);
|
||||
});
|
||||
}
|
||||
|
||||
public static <T extends DataStore> StoreToggleComp childrenToggle(String nameKey, StoreSection section, Function<T, Boolean> initial, BiConsumer<T, Boolean> setter) {
|
||||
return new StoreToggleComp(nameKey, section,new SimpleBooleanProperty(initial.apply(section.getWrapper().getEntry().getStore().asNeeded())),v -> {
|
||||
setter.accept(section.getWrapper().getEntry().getStore().asNeeded(),v);
|
||||
section.getWrapper().refreshChildren();
|
||||
});
|
||||
}
|
||||
|
||||
public StoreToggleComp(String nameKey, StoreSection section, boolean initial, Consumer<Boolean> onChange) {
|
||||
this.nameKey = nameKey;
|
||||
this.section = section;
|
||||
|
|
|
@ -107,17 +107,7 @@ public class StoreCategoryWrapper {
|
|||
});
|
||||
|
||||
share.addListener((observable, oldValue, newValue) -> {
|
||||
category.setShare(newValue);
|
||||
|
||||
DataStoreCategory p = category;
|
||||
if (newValue) {
|
||||
while ((p = DataStorage.get()
|
||||
.getStoreCategoryIfPresent(p.getParentCategory())
|
||||
.orElse(null))
|
||||
!= null) {
|
||||
p.setShare(true);
|
||||
}
|
||||
}
|
||||
DataStorage.get().shareCategory(category, newValue);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ public class StoreCreationMenu {
|
|||
menu.getItems().add(category("addHost", "mdi2h-home-plus", DataStoreProvider.CreationCategory.HOST, "ssh"));
|
||||
|
||||
menu.getItems()
|
||||
.add(category("addDesktop", "mdi2c-camera-plus", DataStoreProvider.CreationCategory.DESKSTOP, null));
|
||||
.add(category("addDesktop", "mdi2c-camera-plus", DataStoreProvider.CreationCategory.DESKTOP, null));
|
||||
|
||||
menu.getItems()
|
||||
.add(category("addShell", "mdi2t-text-box-multiple", DataStoreProvider.CreationCategory.SHELL, null));
|
||||
|
|
|
@ -244,7 +244,9 @@ public abstract class StoreEntryComp extends SimpleComp {
|
|||
list.getLast().styleClass(Styles.RIGHT_PILL);
|
||||
}
|
||||
list.forEach(comp -> {
|
||||
comp.apply(struc -> struc.get().getStyleClass().remove(Styles.FLAT));
|
||||
comp.apply(struc -> {
|
||||
struc.get().getStyleClass().remove(Styles.FLAT);
|
||||
});
|
||||
});
|
||||
return new HorizontalComp(list)
|
||||
.apply(struc -> {
|
||||
|
@ -379,8 +381,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
|||
contextMenu.getItems().add(browse);
|
||||
}
|
||||
|
||||
if (wrapper.getEntry().getProvider() != null
|
||||
&& wrapper.getEntry().getProvider().canMoveCategories()) {
|
||||
if (wrapper.getEntry().getProvider() != null) {
|
||||
var move = new Menu(AppI18n.get("moveTo"), new FontIcon("mdi2f-folder-move-outline"));
|
||||
StoreViewState.get()
|
||||
.getSortedCategories(wrapper.getCategory().getValue().getRoot())
|
||||
|
|
|
@ -25,7 +25,7 @@ public class StoreEntryListComp extends SimpleComp {
|
|||
return new HorizontalComp(List.of(Comp.hspacer(8), custom, Comp.hspacer(10)))
|
||||
.styleClass("top");
|
||||
})
|
||||
.apply(struc -> ((Region) struc.get().getContent()).setPadding(new Insets(10, 0, 10, 0)));
|
||||
.apply(struc -> ((Region) struc.get().getContent()).setPadding(new Insets(8, 0, 8, 0)));
|
||||
return content.styleClass("store-list-comp");
|
||||
}
|
||||
|
||||
|
|
|
@ -80,12 +80,12 @@ public class StoreQuickAccessButtonComp extends Comp<CompStructure<Button>> {
|
|||
public CompStructure<Button> createBase() {
|
||||
var button = new IconButtonComp("mdi2c-chevron-double-right");
|
||||
button.apply(struc -> {
|
||||
var cm = createMenu();
|
||||
if (cm == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
struc.get().setOnAction(event -> {
|
||||
var cm = createMenu();
|
||||
if (cm == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ContextMenuHelper.toggleShow(cm, struc.get(), Side.RIGHT);
|
||||
event.consume();
|
||||
});
|
||||
|
|
|
@ -107,7 +107,7 @@ public class StoreSection {
|
|||
var matchesSelector = section.anyMatches(entryFilter);
|
||||
var sameCategory = category == null
|
||||
|| category.getValue() == null
|
||||
|| inCategory(category.getValue(), section.getWrapper());
|
||||
|| showInCategory(category.getValue(), section.getWrapper());
|
||||
return showFilter && matchesSelector && sameCategory;
|
||||
},
|
||||
category,
|
||||
|
@ -148,7 +148,7 @@ public class StoreSection {
|
|||
var matchesSelector = section.anyMatches(entryFilter);
|
||||
var sameCategory = category == null
|
||||
|| category.getValue() == null
|
||||
|| inCategory(category.getValue(), section.getWrapper());
|
||||
|| showInCategory(category.getValue(), section.getWrapper());
|
||||
// If this entry is already shown as root due to a different category than parent, don't show it
|
||||
// again here
|
||||
var notRoot =
|
||||
|
@ -160,7 +160,7 @@ public class StoreSection {
|
|||
return new StoreSection(e, cached, filtered, depth);
|
||||
}
|
||||
|
||||
private static boolean inCategory(StoreCategoryWrapper categoryWrapper, StoreEntryWrapper entryWrapper) {
|
||||
private static boolean showInCategory(StoreCategoryWrapper categoryWrapper, StoreEntryWrapper entryWrapper) {
|
||||
var current = entryWrapper.getCategory().getValue();
|
||||
while (current != null) {
|
||||
if (categoryWrapper
|
||||
|
|
|
@ -178,12 +178,12 @@ public class StoreSectionComp extends Comp<CompStructure<VBox>> {
|
|||
newList.removeIf(s -> Arrays.stream(DataStoreColor.values())
|
||||
.anyMatch(
|
||||
dataStoreColor -> dataStoreColor.getId().equals(s)));
|
||||
newList.remove("none");
|
||||
newList.remove("gray");
|
||||
newList.add("color-box");
|
||||
if (val != null) {
|
||||
newList.add(val.getId());
|
||||
} else {
|
||||
newList.add("none");
|
||||
newList.add("gray");
|
||||
}
|
||||
struc.get().getStyleClass().setAll(newList);
|
||||
});
|
||||
|
|
|
@ -110,9 +110,7 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
|
|||
return section.getShownChildren().isEmpty();
|
||||
},
|
||||
section.getShownChildren());
|
||||
Consumer<StoreEntryWrapper> quickAccessAction = w -> {
|
||||
action.accept(w);
|
||||
};
|
||||
Consumer<StoreEntryWrapper> quickAccessAction = action;
|
||||
var quickAccessButton = new StoreQuickAccessButtonComp(section, quickAccessAction)
|
||||
.vgrow()
|
||||
.styleClass("quick-access-button")
|
||||
|
@ -178,12 +176,12 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
|
|||
struc.get().getStyleClass().removeIf(s -> Arrays.stream(DataStoreColor.values())
|
||||
.anyMatch(dataStoreColor ->
|
||||
dataStoreColor.getId().equals(s)));
|
||||
struc.get().getStyleClass().remove("none");
|
||||
struc.get().getStyleClass().remove("gray");
|
||||
struc.get().getStyleClass().add("color-box");
|
||||
if (val != null) {
|
||||
struc.get().getStyleClass().add(val.getId());
|
||||
} else {
|
||||
struc.get().getStyleClass().add("none");
|
||||
struc.get().getStyleClass().add("gray");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -14,17 +14,22 @@ public class StoreSidebarComp extends SimpleComp {
|
|||
@Override
|
||||
protected Region createSimple() {
|
||||
var sideBar = new VerticalComp(List.of(
|
||||
new StoreEntryListStatusComp().styleClass("color-box").styleClass("gray"),
|
||||
new StoreCategoryListComp(StoreViewState.get().getAllConnectionsCategory())
|
||||
.styleClass("color-box")
|
||||
.styleClass("gray"),
|
||||
new StoreCategoryListComp(StoreViewState.get().getAllScriptsCategory())
|
||||
.styleClass("color-box")
|
||||
.styleClass("gray"),
|
||||
Comp.of(() -> new Region())
|
||||
.styleClass("bar")
|
||||
new StoreEntryListStatusComp()
|
||||
.styleClass("color-box")
|
||||
.styleClass("gray")
|
||||
.styleClass("bar"),
|
||||
new StoreCategoryListComp(StoreViewState.get().getAllConnectionsCategory())
|
||||
.styleClass("color-box")
|
||||
.styleClass("gray")
|
||||
.styleClass("bar"),
|
||||
new StoreCategoryListComp(StoreViewState.get().getAllScriptsCategory())
|
||||
.styleClass("color-box")
|
||||
.styleClass("gray")
|
||||
.styleClass("bar"),
|
||||
Comp.of(() -> new Region())
|
||||
.styleClass("color-box")
|
||||
.styleClass("gray")
|
||||
.styleClass("bar")
|
||||
.styleClass("filler-bar")
|
||||
.vgrow()));
|
||||
sideBar.apply(struc -> struc.get().setFillWidth(true));
|
||||
|
|
|
@ -90,10 +90,6 @@ public class AppI18n {
|
|||
private static String getCallerModuleName() {
|
||||
var callers = CallingClass.INSTANCE.getCallingClasses();
|
||||
for (Class<?> caller : callers) {
|
||||
if (caller.isSynthetic()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (caller.equals(CallingClass.class)
|
||||
|| caller.equals(ModuleHelper.class)
|
||||
|| caller.equals(ModalOverlayComp.class)
|
||||
|
|
|
@ -89,9 +89,6 @@ public class StoreAddExchangeImpl extends StoreAddExchange
|
|||
|
||||
DataStore s = creator.getResult();
|
||||
String d = "";
|
||||
try {
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
d = d.indent(2);
|
||||
return "Successfully created data store " + name.get() + ":\n" + d;
|
||||
});
|
||||
|
|
|
@ -36,10 +36,6 @@ public interface DataStoreProvider {
|
|||
return false;
|
||||
}
|
||||
|
||||
default boolean canMoveCategories() {
|
||||
return true;
|
||||
}
|
||||
|
||||
default boolean alwaysShowSummary() {
|
||||
return false;
|
||||
}
|
||||
|
@ -220,6 +216,6 @@ public interface DataStoreProvider {
|
|||
TUNNEL,
|
||||
SCRIPT,
|
||||
CLUSTER,
|
||||
DESKSTOP;
|
||||
DESKTOP
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,31 +16,31 @@ import javafx.beans.value.ObservableBooleanValue;
|
|||
public interface SingletonSessionStoreProvider extends DataStoreProvider {
|
||||
|
||||
@Override
|
||||
public default ObservableBooleanValue busy(StoreEntryWrapper wrapper) {
|
||||
default ObservableBooleanValue busy(StoreEntryWrapper wrapper) {
|
||||
return Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
SingletonSessionStore<?> s = wrapper.getEntry().getStore().asNeeded();
|
||||
return s.isEnabled() != s.isRunning();
|
||||
return s.isSessionEnabled() != s.isSessionRunning();
|
||||
},
|
||||
wrapper.getCache());
|
||||
}
|
||||
|
||||
@Override
|
||||
public default StoreEntryComp customEntryComp(StoreSection sec, boolean preferLarge) {
|
||||
default StoreEntryComp customEntryComp(StoreSection sec, boolean preferLarge) {
|
||||
var t = createToggleComp(sec);
|
||||
return StoreEntryComp.create(sec.getWrapper(), t, preferLarge);
|
||||
}
|
||||
|
||||
default Comp<?> createToggleComp(StoreSection sec) {
|
||||
default StoreToggleComp createToggleComp(StoreSection sec) {
|
||||
var enabled = new SimpleBooleanProperty();
|
||||
sec.getWrapper().getCache().subscribe((newValue) -> {
|
||||
SingletonSessionStore<?> s = sec.getWrapper().getEntry().getStore().asNeeded();
|
||||
enabled.set(s.isEnabled());
|
||||
enabled.set(s.isSessionEnabled());
|
||||
});
|
||||
|
||||
var t = new StoreToggleComp(null, sec, enabled, aBoolean -> {
|
||||
SingletonSessionStore<?> s = sec.getWrapper().getEntry().getStore().asNeeded();
|
||||
if (s.isEnabled() != aBoolean) {
|
||||
if (s.isSessionEnabled() != aBoolean) {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
if (aBoolean) {
|
||||
s.startSessionIfNeeded();
|
||||
|
@ -53,15 +53,15 @@ public interface SingletonSessionStoreProvider extends DataStoreProvider {
|
|||
return t;
|
||||
}
|
||||
|
||||
public default Comp<?> stateDisplay(StoreEntryWrapper w) {
|
||||
default Comp<?> stateDisplay(StoreEntryWrapper w) {
|
||||
return new SystemStateComp(Bindings.createObjectBinding(
|
||||
() -> {
|
||||
SingletonSessionStore<?> s = w.getEntry().getStore().asNeeded();
|
||||
if (!s.isEnabled()) {
|
||||
if (!s.isSessionEnabled()) {
|
||||
return SystemStateComp.State.OTHER;
|
||||
}
|
||||
|
||||
return s.isRunning() ? SystemStateComp.State.SUCCESS : SystemStateComp.State.FAILURE;
|
||||
return s.isSessionRunning() ? SystemStateComp.State.SUCCESS : SystemStateComp.State.FAILURE;
|
||||
},
|
||||
w.getCache()));
|
||||
}
|
||||
|
|
|
@ -106,7 +106,7 @@ public class PrettyImageComp extends SimpleComp {
|
|||
}
|
||||
};
|
||||
|
||||
PlatformThread.sync(value).subscribe(val -> update.accept(val));
|
||||
PlatformThread.sync(value).subscribe(update);
|
||||
AppPrefs.get().theme.addListener((observable, oldValue, newValue) -> {
|
||||
update.accept(value.getValue());
|
||||
});
|
||||
|
|
|
@ -92,7 +92,7 @@ public class PrettySvgComp extends SimpleComp {
|
|||
image.set(fixed);
|
||||
};
|
||||
|
||||
syncValue.subscribe(val -> update.accept(val));
|
||||
syncValue.subscribe(update);
|
||||
AppPrefs.get().theme.addListener((observable, oldValue, newValue) -> {
|
||||
update.accept(syncValue.getValue());
|
||||
});
|
||||
|
|
|
@ -1,25 +1,52 @@
|
|||
package io.xpipe.app.fxcomps.impl;
|
||||
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import atlantafx.base.layout.InputGroup;
|
||||
import io.xpipe.app.comp.base.ButtonComp;
|
||||
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 io.xpipe.app.util.ClipboardHelper;
|
||||
import io.xpipe.core.util.InPlaceSecretValue;
|
||||
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.control.PasswordField;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class SecretFieldComp extends Comp<CompStructure<TextField>> {
|
||||
public class SecretFieldComp extends Comp<SecretFieldComp.Structure> {
|
||||
|
||||
@AllArgsConstructor
|
||||
public static class Structure implements CompStructure<InputGroup> {
|
||||
|
||||
private final InputGroup inputGroup;
|
||||
@Getter
|
||||
private final TextField field;
|
||||
|
||||
@Override
|
||||
public InputGroup get() {
|
||||
return inputGroup;
|
||||
}
|
||||
}
|
||||
|
||||
private final Property<InPlaceSecretValue> value;
|
||||
private final boolean allowCopy;
|
||||
private final List<Comp<?>> additionalButtons = new ArrayList<>();
|
||||
|
||||
public SecretFieldComp(Property<InPlaceSecretValue> value) {
|
||||
public SecretFieldComp(Property<InPlaceSecretValue> value, boolean allowCopy) {
|
||||
this.value = value;
|
||||
this.allowCopy = allowCopy;
|
||||
}
|
||||
|
||||
public void addButton(Comp<?> button) {
|
||||
this.additionalButtons.add(button);
|
||||
}
|
||||
|
||||
public static SecretFieldComp ofString(Property<String> s) {
|
||||
|
@ -30,7 +57,7 @@ public class SecretFieldComp extends Comp<CompStructure<TextField>> {
|
|||
s.addListener((observableValue, s1, t1) -> {
|
||||
prop.set(t1 != null ? InPlaceSecretValue.of(t1) : null);
|
||||
});
|
||||
return new SecretFieldComp(prop);
|
||||
return new SecretFieldComp(prop, false);
|
||||
}
|
||||
|
||||
protected InPlaceSecretValue encrypt(char[] c) {
|
||||
|
@ -38,9 +65,8 @@ public class SecretFieldComp extends Comp<CompStructure<TextField>> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public CompStructure<TextField> createBase() {
|
||||
public Structure createBase() {
|
||||
var text = new PasswordField();
|
||||
text.getStyleClass().add("secret-field-comp");
|
||||
text.setText(value.getValue() != null ? value.getValue().getSecretValue() : null);
|
||||
text.textProperty().addListener((c, o, n) -> {
|
||||
value.setValue(n != null && n.length() > 0 ? encrypt(n.toCharArray()) : null);
|
||||
|
@ -56,7 +82,25 @@ public class SecretFieldComp extends Comp<CompStructure<TextField>> {
|
|||
text.setText(n != null ? n.getSecretValue() : null);
|
||||
});
|
||||
});
|
||||
AppFont.small(text);
|
||||
return new SimpleCompStructure<>(text);
|
||||
HBox.setHgrow(text, Priority.ALWAYS);
|
||||
|
||||
var copyButton = new ButtonComp(null, new FontIcon("mdi2c-clipboard-multiple-outline"), () -> {
|
||||
ClipboardHelper.copyPassword(value.getValue());
|
||||
}).grow(false, true).tooltipKey("copyPassword").createRegion();
|
||||
|
||||
var ig = new InputGroup(text);
|
||||
ig.getStyleClass().add("secret-field-comp");
|
||||
if (allowCopy) {
|
||||
ig.getChildren().add(copyButton);
|
||||
}
|
||||
additionalButtons.forEach(comp -> ig.getChildren().add(comp.createRegion()));
|
||||
|
||||
ig.focusedProperty().addListener((c, o, n) -> {
|
||||
if (n) {
|
||||
text.requestFocus();
|
||||
}
|
||||
});
|
||||
|
||||
return new Structure(ig, text);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,11 +22,7 @@ public class BindingsHelper {
|
|||
static {
|
||||
ThreadHelper.createPlatformThread("referenceGC", true, () -> {
|
||||
while (true) {
|
||||
for (ReferenceEntry reference : REFERENCES) {
|
||||
if (reference.canGc()) {
|
||||
REFERENCES.remove(reference);
|
||||
}
|
||||
}
|
||||
REFERENCES.removeIf(ReferenceEntry::canGc);
|
||||
ThreadHelper.sleep(1000);
|
||||
|
||||
// Use for testing
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package io.xpipe.app.issue;
|
||||
|
||||
import atlantafx.base.controls.Popover;
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import io.xpipe.app.comp.base.ButtonComp;
|
||||
import io.xpipe.app.comp.base.ListSelectorComp;
|
||||
import io.xpipe.app.comp.base.MarkdownComp;
|
||||
|
@ -7,7 +9,6 @@ import io.xpipe.app.comp.base.TitledPaneComp;
|
|||
import io.xpipe.app.core.*;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
|
||||
import javafx.beans.property.ListProperty;
|
||||
import javafx.beans.property.SimpleListProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
|
@ -22,15 +23,9 @@ import javafx.scene.control.TextField;
|
|||
import javafx.scene.layout.*;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import atlantafx.base.controls.Popover;
|
||||
import atlantafx.base.controls.Spacer;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static atlantafx.base.theme.Styles.ACCENT;
|
||||
import static atlantafx.base.theme.Styles.BUTTON_OUTLINED;
|
||||
|
||||
public class UserReportComp extends SimpleComp {
|
||||
|
||||
private final StringProperty email = new SimpleStringProperty();
|
||||
|
@ -130,7 +125,7 @@ public class UserReportComp extends SimpleComp {
|
|||
event1.consume();
|
||||
});
|
||||
var sendButton = new ButtonComp(AppI18n.observable("sendReport"), null, this::send)
|
||||
.apply(struc -> struc.get().getStyleClass().addAll(BUTTON_OUTLINED, ACCENT))
|
||||
.apply(struc -> struc.get().setDefaultButton(true))
|
||||
.createRegion();
|
||||
var spacer = new Region();
|
||||
var agree = new Label("Note the issue reporter ");
|
||||
|
|
|
@ -28,6 +28,7 @@ import lombok.Value;
|
|||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class AppPrefs {
|
||||
|
||||
|
@ -137,21 +138,22 @@ public class AppPrefs {
|
|||
private AppPrefsStorageHandler vaultStorageHandler;
|
||||
|
||||
private AppPrefs() {
|
||||
this.categories = List.of(
|
||||
this.categories = Stream.of(
|
||||
new AboutCategory(),
|
||||
new SystemCategory(),
|
||||
new AppearanceCategory(),
|
||||
new SyncCategory(),
|
||||
new VaultCategory(),
|
||||
new PasswordManagerCategory(),
|
||||
new SecurityCategory(),
|
||||
new TerminalCategory(),
|
||||
new EditorCategory(),
|
||||
new RdpCategory(),
|
||||
new SshCategory(),
|
||||
new LocalShellCategory(),
|
||||
new SecurityCategory(),
|
||||
new TroubleshootCategory(),
|
||||
new DeveloperCategory());
|
||||
new DeveloperCategory())
|
||||
.filter(appPrefsCategory -> appPrefsCategory.show()).toList();
|
||||
var selected = AppCache.get("selectedPrefsCategory", Integer.class, () -> 0);
|
||||
if (selected == null) {
|
||||
selected = 0;
|
||||
|
|
|
@ -4,6 +4,10 @@ import io.xpipe.app.fxcomps.Comp;
|
|||
|
||||
public abstract class AppPrefsCategory {
|
||||
|
||||
protected boolean show() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected abstract String getId();
|
||||
|
||||
protected abstract Comp<?> create();
|
||||
|
|
|
@ -10,7 +10,7 @@ public interface ExternalPasswordManager extends PrefsChoiceValue {
|
|||
|
||||
String getTemplate();
|
||||
|
||||
static ExternalPasswordManager BITWARDEN = new ExternalPasswordManager() {
|
||||
ExternalPasswordManager BITWARDEN = new ExternalPasswordManager() {
|
||||
@Override
|
||||
public String getTemplate() {
|
||||
return "bw get password $KEY --nointeraction --raw";
|
||||
|
@ -22,7 +22,7 @@ public interface ExternalPasswordManager extends PrefsChoiceValue {
|
|||
}
|
||||
};
|
||||
|
||||
static ExternalPasswordManager ONEPASSWORD = new ExternalPasswordManager() {
|
||||
ExternalPasswordManager ONEPASSWORD = new ExternalPasswordManager() {
|
||||
@Override
|
||||
public String getTemplate() {
|
||||
return "op read $KEY --force";
|
||||
|
@ -34,7 +34,7 @@ public interface ExternalPasswordManager extends PrefsChoiceValue {
|
|||
}
|
||||
};
|
||||
|
||||
static ExternalPasswordManager DASHLANE = new ExternalPasswordManager() {
|
||||
ExternalPasswordManager DASHLANE = new ExternalPasswordManager() {
|
||||
@Override
|
||||
public String getTemplate() {
|
||||
return "dcli password --output console $KEY";
|
||||
|
@ -46,7 +46,7 @@ public interface ExternalPasswordManager extends PrefsChoiceValue {
|
|||
}
|
||||
};
|
||||
|
||||
static ExternalPasswordManager LASTPASS = new ExternalPasswordManager() {
|
||||
ExternalPasswordManager LASTPASS = new ExternalPasswordManager() {
|
||||
@Override
|
||||
public String getTemplate() {
|
||||
return "lpass show --password $KEY";
|
||||
|
@ -58,7 +58,7 @@ public interface ExternalPasswordManager extends PrefsChoiceValue {
|
|||
}
|
||||
};
|
||||
|
||||
static ExternalPasswordManager MACOS_KEYCHAIN = new ExternalPasswordManager() {
|
||||
ExternalPasswordManager MACOS_KEYCHAIN = new ExternalPasswordManager() {
|
||||
@Override
|
||||
public String getTemplate() {
|
||||
return "security find-generic-password -w -l $KEY";
|
||||
|
@ -75,7 +75,7 @@ public interface ExternalPasswordManager extends PrefsChoiceValue {
|
|||
}
|
||||
};
|
||||
|
||||
static List<ExternalPasswordManager> ALL = Stream.of(ONEPASSWORD, BITWARDEN, DASHLANE, LASTPASS, MACOS_KEYCHAIN)
|
||||
List<ExternalPasswordManager> ALL = Stream.of(ONEPASSWORD, BITWARDEN, DASHLANE, LASTPASS, MACOS_KEYCHAIN)
|
||||
.filter(externalPasswordManager -> externalPasswordManager.isSelectable())
|
||||
.toList();
|
||||
}
|
||||
|
|
|
@ -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.nio.file.Files;
|
||||
|
@ -29,6 +30,11 @@ public interface ExternalRdpClientType extends PrefsChoiceValue {
|
|||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsPasswordPassing() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private RdpConfig getAdaptedConfig(LaunchConfiguration configuration) throws Exception {
|
||||
var input = configuration.getConfig();
|
||||
if (input.get("password 51").isPresent()) {
|
||||
|
@ -38,24 +44,23 @@ public interface ExternalRdpClientType extends PrefsChoiceValue {
|
|||
var address = input.get("full address")
|
||||
.map(typedValue -> typedValue.getValue())
|
||||
.orElse("?");
|
||||
var pass = SecretManager.retrieve(
|
||||
configuration.getPassword(), "Password for " + address, configuration.getStoreId(), 0);
|
||||
var pass = configuration.getPassword();
|
||||
if (pass == null) {
|
||||
return input;
|
||||
}
|
||||
|
||||
var adapted = input.overlay(Map.of(
|
||||
"password 51",
|
||||
new RdpConfig.TypedValue("b", encrypt(pass.getSecretValue())),
|
||||
new RdpConfig.TypedValue("b", encrypt(pass)),
|
||||
"prompt for credentials",
|
||||
new RdpConfig.TypedValue("i", "0")));
|
||||
return adapted;
|
||||
}
|
||||
|
||||
private String encrypt(String password) throws Exception {
|
||||
private String encrypt(SecretValue password) throws Exception {
|
||||
var ps = LocalShell.getLocalPowershell();
|
||||
var cmd = ps.command(
|
||||
"(\"" + password + "\" | ConvertTo-SecureString -AsPlainText -Force) | ConvertFrom-SecureString;");
|
||||
"(\"" + password.getSecretValue() + "\" | ConvertTo-SecureString -AsPlainText -Force) | ConvertFrom-SecureString;");
|
||||
cmd.setSensitive();
|
||||
return cmd.readStdoutOrThrow();
|
||||
}
|
||||
|
@ -69,6 +74,11 @@ public interface ExternalRdpClientType extends PrefsChoiceValue {
|
|||
.executeSimpleCommand(
|
||||
CommandBuilder.of().add(executable).add("-c").addFile(file.toString()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsPasswordPassing() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
ExternalRdpClientType MICROSOFT_REMOTE_DESKTOP_MACOS_APP =
|
||||
new MacOsType("app.microsoftRemoteDesktopApp", "Microsoft Remote Desktop.app") {
|
||||
|
@ -82,6 +92,11 @@ public interface ExternalRdpClientType extends PrefsChoiceValue {
|
|||
.addQuoted("Microsoft Remote Desktop.app")
|
||||
.addFile(file.toString()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsPasswordPassing() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
ExternalRdpClientType CUSTOM = new CustomType();
|
||||
List<ExternalRdpClientType> WINDOWS_CLIENTS = List.of(MSTSC);
|
||||
|
@ -115,6 +130,8 @@ public interface ExternalRdpClientType extends PrefsChoiceValue {
|
|||
|
||||
void launch(LaunchConfiguration configuration) throws Exception;
|
||||
|
||||
boolean supportsPasswordPassing();
|
||||
|
||||
default Path writeConfig(RdpConfig input) throws Exception {
|
||||
var file =
|
||||
LocalShell.getShell().getSystemTemporaryDirectory().join("exec-" + ScriptHelper.getScriptId() + ".rdp");
|
||||
|
@ -128,7 +145,7 @@ public interface ExternalRdpClientType extends PrefsChoiceValue {
|
|||
String title;
|
||||
RdpConfig config;
|
||||
UUID storeId;
|
||||
SecretRetrievalStrategy password;
|
||||
SecretValue password;
|
||||
}
|
||||
|
||||
abstract class PathCheckType extends ExternalApplicationType.PathApplication implements ExternalRdpClientType {
|
||||
|
@ -167,6 +184,11 @@ public interface ExternalRdpClientType extends PrefsChoiceValue {
|
|||
writeConfig(configuration.getConfig()).toString())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsPasswordPassing() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return true;
|
||||
|
|
|
@ -4,10 +4,13 @@ import io.xpipe.app.fxcomps.Comp;
|
|||
import io.xpipe.app.util.OptionsBuilder;
|
||||
import io.xpipe.core.process.OsType;
|
||||
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
|
||||
public class SshCategory extends AppPrefsCategory {
|
||||
|
||||
@Override
|
||||
protected boolean show() {
|
||||
return OsType.getLocal().equals(OsType.WINDOWS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getId() {
|
||||
return "ssh";
|
||||
|
@ -21,9 +24,7 @@ public class SshCategory extends AppPrefsCategory {
|
|||
.sub(new OptionsBuilder()
|
||||
.nameAndDescription("useBundledTools")
|
||||
.addToggle(prefs.useBundledTools)
|
||||
.hide(new SimpleBooleanProperty(!OsType.getLocal().equals(OsType.WINDOWS)))
|
||||
.addComp(prefs.getCustomComp("x11WslInstance"))
|
||||
.hide(new SimpleBooleanProperty(!OsType.getLocal().equals(OsType.WINDOWS))))
|
||||
.addComp(prefs.getCustomComp("x11WslInstance")))
|
||||
.buildComp();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ public class DataStateProviderImpl extends DataStateProvider {
|
|||
return;
|
||||
}
|
||||
|
||||
var entry = DataStorage.get().getStoreEntryIfPresent(store).or(() -> DataStorage.get()
|
||||
var entry = DataStorage.get().getStoreEntryIfPresent(store, true).or(() -> DataStorage.get()
|
||||
.getStoreEntryInProgressIfPresent(store));
|
||||
if (entry.isEmpty()) {
|
||||
return;
|
||||
|
@ -30,7 +30,7 @@ public class DataStateProviderImpl extends DataStateProvider {
|
|||
return def.get();
|
||||
}
|
||||
|
||||
var entry = DataStorage.get().getStoreEntryIfPresent(store);
|
||||
var entry = DataStorage.get().getStoreEntryIfPresent(store, true);
|
||||
if (entry.isEmpty()) {
|
||||
return def.get();
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ public class DataStateProviderImpl extends DataStateProvider {
|
|||
return;
|
||||
}
|
||||
|
||||
var entry = DataStorage.get().getStoreEntryIfPresent(store);
|
||||
var entry = DataStorage.get().getStoreEntryIfPresent(store, true);
|
||||
if (entry.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ public class DataStateProviderImpl extends DataStateProvider {
|
|||
return def.get();
|
||||
}
|
||||
|
||||
var entry = DataStorage.get().getStoreEntryIfPresent(store);
|
||||
var entry = DataStorage.get().getStoreEntryIfPresent(store, true);
|
||||
if (entry.isEmpty()) {
|
||||
return def.get();
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ public class DataStateProviderImpl extends DataStateProvider {
|
|||
}
|
||||
|
||||
public boolean isInStorage(DataStore store) {
|
||||
var entry = DataStorage.get().getStoreEntryIfPresent(store);
|
||||
var entry = DataStorage.get().getStoreEntryIfPresent(store, true);
|
||||
return entry.isPresent();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,9 +10,7 @@ import io.xpipe.core.store.DataStoreId;
|
|||
import io.xpipe.core.store.FixedChildStore;
|
||||
import io.xpipe.core.store.LocalStore;
|
||||
import io.xpipe.core.util.UuidHelper;
|
||||
|
||||
import javafx.util.Pair;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
|
@ -196,10 +194,6 @@ public abstract class DataStorage {
|
|||
return dir.resolve("data");
|
||||
}
|
||||
|
||||
protected Path getStreamsDir() {
|
||||
return dir.resolve("streams");
|
||||
}
|
||||
|
||||
protected Path getCategoriesDir() {
|
||||
return dir.resolve("categories");
|
||||
}
|
||||
|
@ -314,6 +308,23 @@ public abstract class DataStorage {
|
|||
saveAsync();
|
||||
}
|
||||
|
||||
public void shareCategory(DataStoreCategory category, boolean share) {
|
||||
category.setShare(share);
|
||||
|
||||
DataStoreCategory p = category;
|
||||
if (share) {
|
||||
while ((p = DataStorage.get()
|
||||
.getStoreCategoryIfPresent(p.getParentCategory())
|
||||
.orElse(null))
|
||||
!= null) {
|
||||
p.setShare(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Update git remote if needed
|
||||
DataStorage.get().saveAsync();
|
||||
}
|
||||
|
||||
public void updateCategory(DataStoreEntry entry, DataStoreCategory newCategory) {
|
||||
if (getStoreCategoryIfPresent(entry.getUuid())
|
||||
.map(category -> category.equals(newCategory))
|
||||
|
@ -435,6 +446,10 @@ public abstract class DataStorage {
|
|||
|
||||
public void deleteChildren(DataStoreEntry e) {
|
||||
var c = getDeepStoreChildren(e);
|
||||
if (c.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
c.forEach(entry -> entry.finalizeEntry());
|
||||
this.storeEntriesSet.removeAll(c);
|
||||
this.listeners.forEach(l -> l.onStoreRemove(c.toArray(DataStoreEntry[]::new)));
|
||||
|
@ -450,6 +465,9 @@ public abstract class DataStorage {
|
|||
return c.stream();
|
||||
})
|
||||
.toList();
|
||||
if (toDelete.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
toDelete.forEach(entry -> entry.finalizeEntry());
|
||||
toDelete.forEach(this.storeEntriesSet::remove);
|
||||
|
@ -458,20 +476,6 @@ public abstract class DataStorage {
|
|||
saveAsync();
|
||||
}
|
||||
|
||||
public DataStoreCategory addStoreCategoryIfNotPresent(@NonNull DataStoreCategory cat) {
|
||||
if (storeCategories.contains(cat)) {
|
||||
return cat;
|
||||
}
|
||||
|
||||
var byId = getStoreCategoryIfPresent(cat.getUuid()).orElse(null);
|
||||
if (byId != null) {
|
||||
return byId;
|
||||
}
|
||||
|
||||
addStoreCategory(cat);
|
||||
return cat;
|
||||
}
|
||||
|
||||
public void addStoreCategory(@NonNull DataStoreCategory cat) {
|
||||
cat.setDirectory(getCategoriesDir().resolve(cat.getUuid().toString()));
|
||||
this.storeCategories.add(cat);
|
||||
|
@ -528,9 +532,13 @@ public abstract class DataStorage {
|
|||
}
|
||||
|
||||
public void addStoreEntriesIfNotPresent(@NonNull DataStoreEntry... es) {
|
||||
if (es.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (DataStoreEntry e : es) {
|
||||
if (storeEntriesSet.contains(e)
|
||||
|| getStoreEntryIfPresent(e.getStore()).isPresent()) {
|
||||
|| getStoreEntryIfPresent(e.getStore(), false).isPresent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -564,7 +572,7 @@ public abstract class DataStorage {
|
|||
}
|
||||
|
||||
public DataStoreEntry addStoreIfNotPresent(DataStoreEntry related, @NonNull String name, DataStore store) {
|
||||
var f = getStoreEntryIfPresent(store);
|
||||
var f = getStoreEntryIfPresent(store, false);
|
||||
if (f.isPresent()) {
|
||||
return f.get();
|
||||
}
|
||||
|
@ -719,12 +727,6 @@ public abstract class DataStorage {
|
|||
return children;
|
||||
}
|
||||
|
||||
public List<DataStoreEntry> getUsableEntries() {
|
||||
return new ArrayList<>(getStoreEntries().stream()
|
||||
.filter(entry -> entry.getValidity().isUsable())
|
||||
.toList());
|
||||
}
|
||||
|
||||
private List<DataStoreEntry> getHierarchy(DataStoreEntry entry) {
|
||||
var es = new ArrayList<DataStoreEntry>();
|
||||
es.add(entry);
|
||||
|
@ -771,22 +773,18 @@ public abstract class DataStorage {
|
|||
return Optional.empty();
|
||||
}
|
||||
|
||||
public DataStoreEntry getStoreEntry(@NonNull DataStore store) {
|
||||
return getStoreEntryIfPresent(store).orElseThrow(() -> new IllegalArgumentException("Store not found"));
|
||||
}
|
||||
|
||||
public Optional<DataStoreEntry> getStoreEntryInProgressIfPresent(@NonNull DataStore store) {
|
||||
return storeEntriesInProgress.keySet().stream()
|
||||
.filter(n -> n.getStore() == store)
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
public Optional<DataStoreEntry> getStoreEntryIfPresent(@NonNull DataStore store) {
|
||||
public Optional<DataStoreEntry> getStoreEntryIfPresent(@NonNull DataStore store, boolean identityOnly) {
|
||||
return storeEntriesSet.stream()
|
||||
.filter(n -> n.getStore() == store
|
||||
|| (n.getStore() != null
|
||||
|| (!identityOnly && (n.getStore() != null
|
||||
&& Objects.equals(store.getClass(), n.getStore().getClass())
|
||||
&& store.equals(n.getStore())))
|
||||
&& store.equals(n.getStore()))))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
|
@ -825,7 +823,7 @@ public abstract class DataStorage {
|
|||
return Optional.empty();
|
||||
}
|
||||
|
||||
return getStoreEntryIfPresent(store).map(dataStoreEntry -> dataStoreEntry.getName());
|
||||
return getStoreEntryIfPresent(store, true).map(dataStoreEntry -> dataStoreEntry.getName());
|
||||
}
|
||||
|
||||
public String getStoreDisplayName(DataStoreEntry store) {
|
||||
|
|
|
@ -456,17 +456,23 @@ public class DataStoreEntry extends StorageElement {
|
|||
return false;
|
||||
}
|
||||
|
||||
store = DataStorageParser.storeFromNode(storeNode);
|
||||
if (store == null) {
|
||||
var newStore = DataStorageParser.storeFromNode(storeNode);
|
||||
if (newStore == null) {
|
||||
store = null;
|
||||
validity = Validity.LOAD_FAILED;
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
var newComplete = store.isComplete();
|
||||
var newComplete = newStore.isComplete();
|
||||
if (!newComplete) {
|
||||
validity = Validity.INCOMPLETE;
|
||||
store = newStore;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!newStore.equals(store)) {
|
||||
store = newStore;
|
||||
}
|
||||
validity = Validity.COMPLETE;
|
||||
// Don't count this as modification as this is done always
|
||||
notifyUpdate(false, false);
|
||||
|
@ -482,17 +488,23 @@ public class DataStoreEntry extends StorageElement {
|
|||
return false;
|
||||
}
|
||||
|
||||
store = DataStorageParser.storeFromNode(storeNode);
|
||||
if (store == null) {
|
||||
var newStore = DataStorageParser.storeFromNode(storeNode);
|
||||
if (newStore == null) {
|
||||
store = null;
|
||||
validity = Validity.LOAD_FAILED;
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
var newComplete = store.isComplete();
|
||||
var newComplete = newStore.isComplete();
|
||||
if (newComplete) {
|
||||
validity = Validity.COMPLETE;
|
||||
store = newStore;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!newStore.equals(store)) {
|
||||
store = newStore;
|
||||
}
|
||||
validity = Validity.INCOMPLETE;
|
||||
notifyUpdate(false, false);
|
||||
return true;
|
||||
|
|
|
@ -29,7 +29,7 @@ public interface AlacrittyTerminalType extends ExternalTerminalType {
|
|||
return false;
|
||||
}
|
||||
|
||||
static class Windows extends SimplePathType implements AlacrittyTerminalType {
|
||||
class Windows extends SimplePathType implements AlacrittyTerminalType {
|
||||
|
||||
public Windows() {
|
||||
super("app.alacritty", "alacritty", false);
|
||||
|
@ -54,7 +54,7 @@ public interface AlacrittyTerminalType extends ExternalTerminalType {
|
|||
}
|
||||
}
|
||||
|
||||
static class Linux extends SimplePathType implements AlacrittyTerminalType {
|
||||
class Linux extends SimplePathType implements AlacrittyTerminalType {
|
||||
|
||||
public Linux() {
|
||||
super("app.alacritty", "alacritty", true);
|
||||
|
|
|
@ -92,6 +92,11 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
|
||||
ExternalTerminalType PWSH = new SimplePathType("app.pwsh", "pwsh", true) {
|
||||
|
||||
@Override
|
||||
public String getWebsite() {
|
||||
return "https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell?view=powershell-7.4";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsTabs() {
|
||||
return false;
|
||||
|
@ -122,6 +127,11 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
}
|
||||
};
|
||||
ExternalTerminalType GNOME_TERMINAL = new PathCheckType("app.gnomeTerminal", "gnome-terminal", true) {
|
||||
@Override
|
||||
public String getWebsite() {
|
||||
return "https://help.gnome.org/users/gnome-terminal/stable/";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsTabs() {
|
||||
return false;
|
||||
|
@ -155,7 +165,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
}
|
||||
|
||||
@Override
|
||||
public FailableFunction<LaunchConfiguration, String, Exception> remoteLaunchCommand() {
|
||||
public FailableFunction<LaunchConfiguration, String, Exception> remoteLaunchCommand(ShellDialect systemDialect) {
|
||||
return launchConfiguration -> {
|
||||
var toExecute = CommandBuilder.of()
|
||||
.add(executable, "-v", "--title")
|
||||
|
@ -168,6 +178,11 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
};
|
||||
ExternalTerminalType KONSOLE = new SimplePathType("app.konsole", "konsole", true) {
|
||||
|
||||
@Override
|
||||
public String getWebsite() {
|
||||
return "https://konsole.kde.org/download.html";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsTabs() {
|
||||
return true;
|
||||
|
@ -192,6 +207,11 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
}
|
||||
};
|
||||
ExternalTerminalType XFCE = new SimplePathType("app.xfce", "xfce4-terminal", true) {
|
||||
@Override
|
||||
public String getWebsite() {
|
||||
return "https://docs.xfce.org/apps/terminal/start";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsTabs() {
|
||||
return true;
|
||||
|
@ -217,6 +237,12 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
}
|
||||
};
|
||||
ExternalTerminalType ELEMENTARY = new SimplePathType("app.elementaryTerminal", "io.elementary.terminal", true) {
|
||||
|
||||
@Override
|
||||
public String getWebsite() {
|
||||
return "https://github.com/elementary/terminal";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsTabs() {
|
||||
return true;
|
||||
|
@ -238,6 +264,11 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
}
|
||||
};
|
||||
ExternalTerminalType TILIX = new SimplePathType("app.tilix", "tilix", true) {
|
||||
@Override
|
||||
public String getWebsite() {
|
||||
return "https://gnunn1.github.io/tilix-web/";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsTabs() {
|
||||
return false;
|
||||
|
@ -263,6 +294,11 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
}
|
||||
};
|
||||
ExternalTerminalType TERMINATOR = new SimplePathType("app.terminator", "terminator", true) {
|
||||
@Override
|
||||
public String getWebsite() {
|
||||
return "https://gnome-terminator.org/";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsTabs() {
|
||||
return true;
|
||||
|
@ -289,6 +325,11 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
}
|
||||
};
|
||||
ExternalTerminalType TERMINOLOGY = new SimplePathType("app.terminology", "terminology", true) {
|
||||
@Override
|
||||
public String getWebsite() {
|
||||
return "https://github.com/borisfaure/terminology";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsTabs() {
|
||||
return true;
|
||||
|
@ -314,32 +355,12 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
.addFile(configuration.getScriptFile());
|
||||
}
|
||||
};
|
||||
ExternalTerminalType COOL_RETRO_TERM = new SimplePathType("app.coolRetroTerm", "cool-retro-term", true) {
|
||||
@Override
|
||||
public boolean supportsTabs() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRecommended() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsColoredTitle() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CommandBuilder toCommand(LaunchConfiguration configuration) {
|
||||
return CommandBuilder.of()
|
||||
.add("-T")
|
||||
.addQuoted(configuration.getColoredTitle())
|
||||
.add("-e")
|
||||
.addFile(configuration.getScriptFile());
|
||||
}
|
||||
};
|
||||
ExternalTerminalType GUAKE = new SimplePathType("app.guake", "guake", true) {
|
||||
@Override
|
||||
public String getWebsite() {
|
||||
return "https://github.com/Guake/guake";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsTabs() {
|
||||
return true;
|
||||
|
@ -366,6 +387,11 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
}
|
||||
};
|
||||
ExternalTerminalType TILDA = new SimplePathType("app.tilda", "tilda", true) {
|
||||
@Override
|
||||
public String getWebsite() {
|
||||
return "https://github.com/lanoxx/tilda";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsTabs() {
|
||||
return true;
|
||||
|
@ -387,6 +413,11 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
}
|
||||
};
|
||||
ExternalTerminalType XTERM = new SimplePathType("app.xterm", "xterm", true) {
|
||||
@Override
|
||||
public String getWebsite() {
|
||||
return "https://invisible-island.net/xterm/";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsTabs() {
|
||||
return false;
|
||||
|
@ -412,6 +443,11 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
}
|
||||
};
|
||||
ExternalTerminalType DEEPIN_TERMINAL = new SimplePathType("app.deepinTerminal", "deepin-terminal", true) {
|
||||
@Override
|
||||
public String getWebsite() {
|
||||
return "https://www.deepin.org/en/original/deepin-terminal/";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsTabs() {
|
||||
return false;
|
||||
|
@ -433,6 +469,11 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
}
|
||||
};
|
||||
ExternalTerminalType Q_TERMINAL = new SimplePathType("app.qTerminal", "qterminal", true) {
|
||||
@Override
|
||||
public String getWebsite() {
|
||||
return "https://github.com/lxqt/qterminal";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsTabs() {
|
||||
return false;
|
||||
|
@ -485,6 +526,11 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
}
|
||||
};
|
||||
ExternalTerminalType ITERM2 = new MacOsType("app.iterm2", "iTerm") {
|
||||
@Override
|
||||
public String getWebsite() {
|
||||
return "https://iterm2.com/";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsTabs() {
|
||||
return true;
|
||||
|
@ -536,6 +582,11 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
};
|
||||
ExternalTerminalType WARP = new MacOsType("app.warp", "Warp") {
|
||||
|
||||
@Override
|
||||
public String getWebsite() {
|
||||
return "https://www.warp.dev/";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsTabs() {
|
||||
return true;
|
||||
|
@ -566,7 +617,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
}
|
||||
|
||||
@Override
|
||||
public FailableFunction<LaunchConfiguration, String, Exception> remoteLaunchCommand() {
|
||||
public FailableFunction<LaunchConfiguration, String, Exception> remoteLaunchCommand(ShellDialect systemDialect) {
|
||||
return launchConfiguration -> {
|
||||
var toExecute = CommandBuilder.of()
|
||||
.add("open", "-a")
|
||||
|
@ -575,6 +626,22 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
return toExecute.buildSimple();
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminalInitFunction additionalInitCommands() {
|
||||
return TerminalInitFunction.of(sc -> {
|
||||
if (sc.getShellDialect() == ShellDialects.ZSH) {
|
||||
return "printf '\\eP$f{\"hook\": \"SourcedRcFileForWarp\", \"value\": { \"shell\": \"zsh\"}}\\x9c'";
|
||||
}
|
||||
if (sc.getShellDialect() == ShellDialects.BASH) {
|
||||
return "printf '\\eP$f{\"hook\": \"SourcedRcFileForWarp\", \"value\": { \"shell\": \"bash\"}}\\x9c'";
|
||||
}
|
||||
if (sc.getShellDialect() == ShellDialects.FISH) {
|
||||
return "printf '\\eP$f{\"hook\": \"SourcedRcFileForWarp\", \"value\": { \"shell\": \"fish\"}}\\x9c'";
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
};
|
||||
ExternalTerminalType CUSTOM = new CustomTerminalType();
|
||||
List<ExternalTerminalType> WINDOWS_TERMINALS = List.of(
|
||||
|
@ -596,7 +663,6 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
TERMINATOR,
|
||||
KittyTerminalType.KITTY_LINUX,
|
||||
TERMINOLOGY,
|
||||
COOL_RETRO_TERM,
|
||||
GUAKE,
|
||||
AlacrittyTerminalType.ALACRITTY_LINUX,
|
||||
TILDA,
|
||||
|
@ -612,9 +678,9 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
WezTerminalType.WEZTERM_MAC_OS,
|
||||
MACOS_TERMINAL);
|
||||
|
||||
List<ExternalTerminalType> APPLICABLE = getTypes(OsType.getLocal(), false, true);
|
||||
List<ExternalTerminalType> ALL = getTypes(OsType.getLocal(), false, true);
|
||||
|
||||
List<ExternalTerminalType> ALL = getTypes(null, false, true);
|
||||
List<ExternalTerminalType> ALL_ON_ALL_PLATFORMS = getTypes(null, false, true);
|
||||
|
||||
static List<ExternalTerminalType> getTypes(OsType osType, boolean remote, boolean custom) {
|
||||
var all = new ArrayList<ExternalTerminalType>();
|
||||
|
@ -628,7 +694,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
all.addAll(MACOS_TERMINALS);
|
||||
}
|
||||
if (remote) {
|
||||
all.removeIf(externalTerminalType -> externalTerminalType.remoteLaunchCommand() == null);
|
||||
all.removeIf(externalTerminalType -> externalTerminalType.remoteLaunchCommand(null) == null);
|
||||
}
|
||||
// Prefer recommended
|
||||
all.sort(Comparator.comparingInt(o -> (o.isRecommended() ? -1 : 0)));
|
||||
|
@ -649,13 +715,17 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
return existing;
|
||||
}
|
||||
|
||||
return APPLICABLE.stream()
|
||||
return ALL.stream()
|
||||
.filter(externalTerminalType -> !externalTerminalType.equals(CUSTOM))
|
||||
.filter(terminalType -> terminalType.isAvailable())
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
default TerminalInitFunction additionalInitCommands() {
|
||||
return TerminalInitFunction.none();
|
||||
}
|
||||
|
||||
boolean supportsTabs();
|
||||
|
||||
default String getWebsite() {
|
||||
|
@ -672,7 +742,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
|
||||
default void launch(LaunchConfiguration configuration) throws Exception {}
|
||||
|
||||
default FailableFunction<LaunchConfiguration, String, Exception> remoteLaunchCommand() {
|
||||
default FailableFunction<LaunchConfiguration, String, Exception> remoteLaunchCommand(ShellDialect systemDialect) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -714,12 +784,6 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
var open = scriptDialect.getOpenScriptCommand(scriptFile.toString());
|
||||
return open;
|
||||
}
|
||||
|
||||
public CommandBuilder appendDialectLaunchCommand(CommandBuilder b) {
|
||||
var open = getDialectLaunchCommand();
|
||||
b.add(open);
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class MacOsType extends ExternalApplicationType.MacApplication implements ExternalTerminalType {
|
||||
|
@ -751,12 +815,12 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
}
|
||||
|
||||
@Override
|
||||
public FailableFunction<LaunchConfiguration, String, Exception> remoteLaunchCommand() {
|
||||
public FailableFunction<LaunchConfiguration, String, Exception> remoteLaunchCommand(ShellDialect systemDialect) {
|
||||
return launchConfiguration -> {
|
||||
var args = toCommand(launchConfiguration);
|
||||
args.add(0, executable);
|
||||
if (explicityAsync) {
|
||||
args = launchConfiguration.getScriptDialect().launchAsnyc(args);
|
||||
args = systemDialect.launchAsnyc(args);
|
||||
}
|
||||
return args.buildSimple();
|
||||
};
|
||||
|
|
|
@ -12,8 +12,8 @@ import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
|||
|
||||
public interface KittyTerminalType extends ExternalTerminalType {
|
||||
|
||||
public static final ExternalTerminalType KITTY_LINUX = new Linux();
|
||||
public static final ExternalTerminalType KITTY_MACOS = new MacOs();
|
||||
ExternalTerminalType KITTY_LINUX = new Linux();
|
||||
ExternalTerminalType KITTY_MACOS = new MacOs();
|
||||
|
||||
private static FilePath getSocket() throws Exception {
|
||||
try (var sc = LocalShell.getShell().start()) {
|
||||
|
|
|
@ -4,6 +4,7 @@ import io.xpipe.app.util.LocalShell;
|
|||
import io.xpipe.app.util.WindowsRegistry;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
import io.xpipe.core.process.ShellDialects;
|
||||
import io.xpipe.core.process.TerminalInitFunction;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
@ -33,7 +34,28 @@ public interface TabbyTerminalType extends ExternalTerminalType {
|
|||
return true;
|
||||
}
|
||||
|
||||
static class Windows extends ExternalTerminalType.WindowsType implements TabbyTerminalType {
|
||||
@Override
|
||||
default TerminalInitFunction additionalInitCommands() {
|
||||
// return TerminalInitFunction.of(sc -> {
|
||||
// if (sc.getShellDialect() == ShellDialects.ZSH) {
|
||||
// return "export PS1=\"$PS1\\[\\e]1337;CurrentDir=\"'$(pwd)\\a\\]'";
|
||||
// }
|
||||
// if (sc.getShellDialect() == ShellDialects.BASH) {
|
||||
// return "precmd () { echo -n \"\\x1b]1337;CurrentDir=$(pwd)\\x07\" }";
|
||||
// }
|
||||
// if (sc.getShellDialect() == ShellDialects.FISH) {
|
||||
// return """
|
||||
// function __tabby_working_directory_reporting --on-event fish_prompt
|
||||
// echo -en "\\e]1337;CurrentDir=$PWD\\x7"
|
||||
// end
|
||||
// """;
|
||||
// }
|
||||
// return null;
|
||||
// });
|
||||
return null;
|
||||
}
|
||||
|
||||
class Windows extends ExternalTerminalType.WindowsType implements TabbyTerminalType {
|
||||
|
||||
public Windows() {
|
||||
super("app.tabby", "Tabby.exe");
|
||||
|
|
|
@ -34,7 +34,7 @@ public interface WezTerminalType extends ExternalTerminalType {
|
|||
return true;
|
||||
}
|
||||
|
||||
static class Windows extends WindowsType implements WezTerminalType {
|
||||
class Windows extends WindowsType implements WezTerminalType {
|
||||
|
||||
public Windows() {
|
||||
super("app.wezterm", "wezterm-gui");
|
||||
|
@ -61,7 +61,7 @@ public interface WezTerminalType extends ExternalTerminalType {
|
|||
}
|
||||
}
|
||||
|
||||
static class Linux extends SimplePathType implements WezTerminalType {
|
||||
class Linux extends SimplePathType implements WezTerminalType {
|
||||
|
||||
public Linux() {
|
||||
super("app.wezterm", "wezterm-gui", true);
|
||||
|
|
|
@ -10,8 +10,8 @@ import java.nio.file.Path;
|
|||
|
||||
public interface WindowsTerminalType extends ExternalTerminalType {
|
||||
|
||||
public static final ExternalTerminalType WINDOWS_TERMINAL = new Standard();
|
||||
public static final ExternalTerminalType WINDOWS_TERMINAL_PREVIEW = new Preview();
|
||||
ExternalTerminalType WINDOWS_TERMINAL = new Standard();
|
||||
ExternalTerminalType WINDOWS_TERMINAL_PREVIEW = new Preview();
|
||||
|
||||
private static CommandBuilder toCommand(ExternalTerminalType.LaunchConfiguration configuration) throws Exception {
|
||||
// A weird behavior in Windows Terminal causes the trailing
|
||||
|
|
|
@ -68,7 +68,7 @@ public class AppJacksonModule extends SimpleModule {
|
|||
@Override
|
||||
public ExternalTerminalType deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||
var id = p.getValueAsString();
|
||||
return ExternalTerminalType.ALL.stream()
|
||||
return ExternalTerminalType.ALL_ON_ALL_PLATFORMS.stream()
|
||||
.filter(terminalType -> terminalType.getId().equals(id))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
|
|
@ -44,8 +44,8 @@ public class AskpassAlert {
|
|||
});
|
||||
}
|
||||
|
||||
var text = new SecretFieldComp(prop).createStructure().get();
|
||||
alert.getDialogPane().setContent(new StackPane(text));
|
||||
var text = new SecretFieldComp(prop, false).createStructure();
|
||||
alert.getDialogPane().setContent(new StackPane(text.get()));
|
||||
var stage = (Stage) alert.getDialogPane().getScene().getWindow();
|
||||
stage.setAlwaysOnTop(true);
|
||||
|
||||
|
@ -84,8 +84,8 @@ public class AskpassAlert {
|
|||
anim.start();
|
||||
// Wait 1 pulse before focus so that the scene can be assigned to text
|
||||
Platform.runLater(() -> {
|
||||
text.requestFocus();
|
||||
text.end();
|
||||
text.getField().requestFocus();
|
||||
text.getField().end();
|
||||
});
|
||||
event.consume();
|
||||
});
|
||||
|
|
|
@ -1,14 +1,46 @@
|
|||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
|
||||
import io.xpipe.core.util.SecretValue;
|
||||
import javafx.animation.PauseTransition;
|
||||
import javafx.scene.input.Clipboard;
|
||||
import javafx.scene.input.DataFormat;
|
||||
import javafx.util.Duration;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class ClipboardHelper {
|
||||
|
||||
public static void copyPassword(SecretValue pass) {
|
||||
if (pass == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
Clipboard clipboard = Clipboard.getSystemClipboard();
|
||||
Map<DataFormat, Object> previous = Stream.of(DataFormat.PLAIN_TEXT, DataFormat.URL, DataFormat.RTF, DataFormat.HTML, DataFormat.IMAGE, DataFormat.FILES)
|
||||
.map(dataFormat -> new AbstractMap.SimpleEntry<>(dataFormat, clipboard.getContent(dataFormat))).filter(o -> o.getValue() != null)
|
||||
.collect(HashMap::new, (m,v)->m.put(v.getKey(), v.getValue()), HashMap::putAll);
|
||||
|
||||
var withPassword = new HashMap<>(previous);
|
||||
withPassword.put(DataFormat.PLAIN_TEXT, pass.getSecretValue());
|
||||
clipboard.setContent(withPassword);
|
||||
|
||||
var transition = new PauseTransition(Duration.millis(10000));
|
||||
transition.setOnFinished(e -> {
|
||||
var present = clipboard.getString();
|
||||
if (present != null && present.equals(pass.getSecretValue())) {
|
||||
previous.putIfAbsent(DataFormat.PLAIN_TEXT, "");
|
||||
clipboard.setContent(previous);
|
||||
}
|
||||
});
|
||||
transition.play();
|
||||
});
|
||||
}
|
||||
|
||||
public static void copyText(String s) {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
Clipboard clipboard = Clipboard.getSystemClipboard();
|
||||
|
|
|
@ -37,7 +37,7 @@ public class DataStoreFormatter {
|
|||
return formattedOsName(s.getOsName());
|
||||
}
|
||||
|
||||
if (s.getShellDialect().equals(ShellDialects.UNSUPPORTED)) {
|
||||
if (s.getShellDialect().equals(ShellDialects.NO_INTERACTION)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -28,11 +28,11 @@ public class LockChangeAlert {
|
|||
alert.setAlertType(Alert.AlertType.CONFIRMATION);
|
||||
|
||||
var label1 = new LabelComp(AppI18n.observable("passphrase")).createRegion();
|
||||
var p1 = new SecretFieldComp(prop1).createRegion();
|
||||
var p1 = new SecretFieldComp(prop1, false).createRegion();
|
||||
p1.setStyle("-fx-border-width: 1px");
|
||||
|
||||
var label2 = new LabelComp(AppI18n.observable("repeatPassphrase")).createRegion();
|
||||
var p2 = new SecretFieldComp(prop2).createRegion();
|
||||
var p2 = new SecretFieldComp(prop2, false).createRegion();
|
||||
p1.setStyle("-fx-border-width: 1px");
|
||||
|
||||
var content = new VBox(label1, p1, new Spacer(15), label2, p2);
|
||||
|
|
|
@ -320,7 +320,7 @@ public class OptionsBuilder {
|
|||
}
|
||||
|
||||
public OptionsBuilder addSecret(Property<InPlaceSecretValue> prop) {
|
||||
var comp = new SecretFieldComp(prop);
|
||||
var comp = new SecretFieldComp(prop, true);
|
||||
pushComp(comp);
|
||||
props.add(prop);
|
||||
return this;
|
||||
|
|
|
@ -10,6 +10,7 @@ import lombok.SneakyThrows;
|
|||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ScriptHelper {
|
||||
|
||||
|
@ -31,9 +32,10 @@ public class ScriptHelper {
|
|||
ShellDialect t,
|
||||
ShellControl processControl,
|
||||
WorkingDirectoryFunction workingDirectory,
|
||||
List<String> init,
|
||||
String toExecuteInShell,
|
||||
TerminalInitScriptConfig config)
|
||||
List<String> preInit,
|
||||
List<String> postInit,
|
||||
TerminalInitScriptConfig config,
|
||||
boolean exit)
|
||||
throws Exception {
|
||||
String nl = t.getNewLine().getNewLineString();
|
||||
var content = "";
|
||||
|
@ -43,16 +45,14 @@ public class ScriptHelper {
|
|||
content += clear + nl;
|
||||
}
|
||||
|
||||
var applyRcCommand = t.applyRcFileCommand();
|
||||
if (applyRcCommand != null) {
|
||||
content += nl + applyRcCommand + nl;
|
||||
}
|
||||
// Normalize line endings
|
||||
content += nl + preInit.stream().flatMap(s -> s.lines()).collect(Collectors.joining(nl)) + nl;
|
||||
|
||||
// We just apply the profile files always, as we can't be sure that they definitely have been applied.
|
||||
// Especially if we launch something that is not the system default shell
|
||||
var applyProfilesCommand = t.applyProfileFilesCommand();
|
||||
if (applyProfilesCommand != null) {
|
||||
content += nl + applyProfilesCommand + nl;
|
||||
var applyCommand = t.applyInitFileCommand();
|
||||
if (applyCommand != null) {
|
||||
content += nl + applyCommand + nl;
|
||||
}
|
||||
|
||||
if (config.getDisplayName() != null) {
|
||||
|
@ -66,12 +66,11 @@ public class ScriptHelper {
|
|||
}
|
||||
}
|
||||
|
||||
content += nl + String.join(nl, init.stream().filter(s -> s != null).toList()) + nl;
|
||||
// Normalize line endings
|
||||
content += nl + postInit.stream().flatMap(s -> s.lines()).collect(Collectors.joining(nl)) + nl;
|
||||
|
||||
if (toExecuteInShell != null) {
|
||||
// Normalize line endings
|
||||
content += String.join(nl, toExecuteInShell.lines().toList()) + nl;
|
||||
content += nl + t.getPassthroughExitCommand() + nl;
|
||||
if (exit) {
|
||||
content += nl + t.getPassthroughExitCommand();
|
||||
}
|
||||
|
||||
return createExecScript(t, processControl, new FilePath(t.initFileName(processControl)), content);
|
||||
|
|
|
@ -27,7 +27,7 @@ public class SecretRetrievalStrategyHelper {
|
|||
? p.getValue().getValue().getInternalSecret()
|
||||
: null);
|
||||
return new OptionsBuilder()
|
||||
.addComp(new SecretFieldComp(secretProperty), secretProperty)
|
||||
.addComp(new SecretFieldComp(secretProperty, true), secretProperty)
|
||||
.bind(
|
||||
() -> {
|
||||
var newSecret = secretProperty.get();
|
||||
|
@ -45,16 +45,20 @@ public class SecretRetrievalStrategyHelper {
|
|||
new SimpleObjectProperty<>(p.getValue() != null ? p.getValue().getKey() : null);
|
||||
var content = new HorizontalComp(List.of(
|
||||
new TextFieldComp(keyProperty)
|
||||
.apply(struc -> struc.get().setPromptText("Password key"))
|
||||
.apply(struc -> struc.get().setPromptText("$KEY"))
|
||||
.hgrow(),
|
||||
new ButtonComp(null, new FontIcon("mdomz-settings"), () -> {
|
||||
AppPrefs.get().selectCategory("passwordManager");
|
||||
App.getApp().getStage().requestFocus();
|
||||
})
|
||||
.grow(false, true)))
|
||||
.apply(struc -> struc.get().setSpacing(10));
|
||||
.apply(struc -> struc.get().setSpacing(10))
|
||||
.apply(struc -> struc.get().focusedProperty().addListener((c, o, n) -> {
|
||||
if (n) {
|
||||
struc.get().getChildren().getFirst().requestFocus();
|
||||
}
|
||||
}));
|
||||
return new OptionsBuilder()
|
||||
.name("passwordKey")
|
||||
.addComp(content, keyProperty)
|
||||
.nonNull()
|
||||
.bind(
|
||||
|
|
|
@ -27,11 +27,13 @@ public class TerminalLauncher {
|
|||
sc,
|
||||
WorkingDirectoryFunction.none(),
|
||||
List.of(),
|
||||
command.apply(sc),
|
||||
List.of(command.apply(sc)),
|
||||
new TerminalInitScriptConfig(
|
||||
title,
|
||||
type.shouldClear()
|
||||
&& AppPrefs.get().clearTerminalOnInit().get()));
|
||||
&& AppPrefs.get().clearTerminalOnInit().get(),
|
||||
null),
|
||||
true);
|
||||
var config = new ExternalTerminalType.LaunchConfiguration(null, title, title, script, sc.getShellDialect());
|
||||
type.launch(config);
|
||||
}
|
||||
|
@ -53,7 +55,8 @@ public class TerminalLauncher {
|
|||
var adjustedTitle = prefix + cleanTitle;
|
||||
var terminalConfig = new TerminalInitScriptConfig(
|
||||
adjustedTitle,
|
||||
type.shouldClear() && AppPrefs.get().clearTerminalOnInit().get());
|
||||
type.shouldClear() && AppPrefs.get().clearTerminalOnInit().get(),
|
||||
cc instanceof ShellControl ? type.additionalInitCommands() : TerminalInitFunction.none());
|
||||
|
||||
var request = UUID.randomUUID();
|
||||
var d = ProcessControlProvider.get().getEffectiveLocalDialect();
|
||||
|
|
|
@ -37,7 +37,7 @@ public class TerminalLauncherManager {
|
|||
}
|
||||
|
||||
@Override
|
||||
public FilePath apply(ShellControl shellControl) throws Exception {
|
||||
public FilePath apply(ShellControl shellControl) {
|
||||
if (directory == null) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ public class UnlockAlert {
|
|||
alert.setHeaderText(AppI18n.get("unlockAlertHeader"));
|
||||
alert.setAlertType(Alert.AlertType.CONFIRMATION);
|
||||
|
||||
var text = new SecretFieldComp(pw).createRegion();
|
||||
var text = new SecretFieldComp(pw, false).createRegion();
|
||||
text.setStyle("-fx-border-width: 1px");
|
||||
|
||||
var content = new VBox(text);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
.bookmark-list > .categories {
|
||||
-fx-padding: 1em;
|
||||
-fx-background-color: -color-bg-subtle;
|
||||
-fx-background-color: -color-bg-default;
|
||||
-fx-border-color: -color-border-default;
|
||||
-fx-border-width: 0 0 1 0;
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
-fx-border-color: -color-border-default;
|
||||
-fx-border-width: 1px 0 0 0;
|
||||
-fx-padding: 1em;
|
||||
-fx-background-color: -color-bg-subtle;
|
||||
-fx-background-color: -color-bg-default;
|
||||
}
|
||||
|
||||
.transfer .button {
|
||||
|
@ -47,6 +47,7 @@
|
|||
.browser .overview {
|
||||
-fx-spacing: 1.5em;
|
||||
-fx-padding: 1.5em;
|
||||
-fx-background-color: -color-bg-default;
|
||||
}
|
||||
|
||||
.selected-file-list {
|
||||
|
@ -133,13 +134,14 @@
|
|||
}
|
||||
|
||||
.browser .overview-file-list {
|
||||
-fx-border-width: 1px;
|
||||
-fx-border-width: 0;
|
||||
}
|
||||
|
||||
.browser .overview-file-list .button {
|
||||
-fx-border-width: 0;
|
||||
-fx-background-radius: 0;
|
||||
-fx-background-insets: 0;
|
||||
-fx-effect: none;
|
||||
}
|
||||
|
||||
.browser .tab-pane {
|
||||
|
|
|
@ -18,24 +18,13 @@
|
|||
-fx-border-color: derive(-color-border-default, -10%);
|
||||
}
|
||||
|
||||
|
||||
.root:pretty:dark .color-box.gray {
|
||||
-fx-background-color: linear-gradient(from 100% 0% to 0% 100%, derive(-color-bg-default, 13%) 40%, derive(-color-bg-default, 11%) 50%, derive(-color-bg-default, 14%) 100%);
|
||||
-fx-border-color: -color-border-default;
|
||||
}
|
||||
|
||||
.root:performance:dark .color-box.gray {
|
||||
.root:dark .color-box.gray {
|
||||
-fx-background-color: derive(-color-bg-default, 13%);
|
||||
-fx-border-color: -color-border-default;
|
||||
}
|
||||
|
||||
.root:pretty:light .color-box.gray {
|
||||
-fx-background-color: linear-gradient(from 100% 0% to 0% 100%, derive(-color-bg-default, -5%) 40%, derive(-color-bg-default, -3%) 50%, derive(-color-bg-default, -5%) 100%);
|
||||
-fx-border-color: derive(-color-border-default, -10%);
|
||||
}
|
||||
|
||||
.root:performance:light .color-box.gray {
|
||||
-fx-background-color: derive(-color-bg-default, -5%);
|
||||
.root:light .color-box.gray {
|
||||
-fx-background-color: derive(-color-bg-default, -3%);
|
||||
-fx-border-color: derive(-color-border-default, -10%);
|
||||
}
|
||||
|
||||
|
@ -45,12 +34,12 @@
|
|||
|
||||
|
||||
.root:pretty:light .color-box.blue {
|
||||
-fx-background-color: linear-gradient(from 100% 0% to 0% 100%, rgb(130, 130, 250, 0.2) 40%, rgb(57, 57, 200, 0.2) 50%, rgb(137, 137, 250, 0.2) 100%);
|
||||
-fx-background-color: linear-gradient(from 100% 0% to 0% 100%, rgb(130, 130, 250, 0.15) 40%, rgb(57, 57, 200, 0.15) 50%, rgb(137, 137, 250, 0.15) 100%);
|
||||
-fx-border-color: rgba(80, 100, 150, 0.3);
|
||||
}
|
||||
|
||||
.root:performance:light .color-box.blue {
|
||||
-fx-background-color: rgb(130, 130, 250, 0.2);
|
||||
-fx-background-color: rgb(130, 130, 250, 0.15);
|
||||
-fx-border-color: rgba(80, 100, 150, 0.3);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
|
||||
.bar {
|
||||
-fx-padding: 0.8em 1.0em 0.8em 1.0em;
|
||||
-fx-background-color: -color-bg-subtle;
|
||||
-fx-border-color: -color-border-default;
|
||||
}
|
||||
|
||||
.root:pretty .bar {
|
||||
-fx-effect: dropshadow(three-pass-box, -color-shadow-default, 3px, 0.5, 0, 1);
|
||||
-fx-effect: dropshadow(three-pass-box, -color-shadow-default, 3, 0.5, 1, 1);
|
||||
}
|
||||
|
||||
.store-header-bar {
|
||||
|
@ -88,8 +86,6 @@
|
|||
|
||||
.store-category-bar {
|
||||
-fx-padding: 0.8em 0.5em 0.8em 0.5em;
|
||||
-fx-background-color: -color-bg-subtle;
|
||||
-fx-border-color: -color-border-default;
|
||||
}
|
||||
|
||||
.store-sort-bar {
|
||||
|
@ -110,47 +106,30 @@
|
|||
-fx-background-color: transparent;
|
||||
}
|
||||
|
||||
.bar .button-comp {
|
||||
.store-header-bar .button-comp {
|
||||
-fx-padding: 0.2em 0em 0.2em 0em;
|
||||
}
|
||||
|
||||
.collections-bar {
|
||||
-fx-background-radius: 0 0 4px 0;
|
||||
-fx-border-radius: 0 0 4px 0;
|
||||
-fx-border-width: 0 4px 4px 0;
|
||||
}
|
||||
|
||||
.entry-bar {
|
||||
-fx-background-radius: 0 0 4px 4px;
|
||||
-fx-border-radius: 0 0 4px 4px;
|
||||
-fx-border-width: 0 4px 4px 4px;
|
||||
-fx-background-color: -color-neutral-muted;
|
||||
}
|
||||
|
||||
.entry-bar .horizontal-comp {
|
||||
-fx-spacing: 5px;
|
||||
}
|
||||
|
||||
.bar .icon-button-comp {
|
||||
.store-header-bar .icon-button-comp {
|
||||
-fx-text-fill: -color-fg-default;
|
||||
}
|
||||
|
||||
.bar .name {
|
||||
.store-header-bar .name {
|
||||
-fx-font-weight: BOLD;
|
||||
-fx-text-fill: -color-fg-default;
|
||||
}
|
||||
|
||||
.bar .count-comp {
|
||||
.store-header-bar .count-comp {
|
||||
-fx-font-weight: BOLD;
|
||||
-fx-padding: 0.0em 0em 0.0em 0.4em;
|
||||
-fx-text-fill: -color-fg-muted;
|
||||
}
|
||||
|
||||
.bar .filter-bar .text-field {
|
||||
.store-header-bar .filter-bar .text-field {
|
||||
-fx-text-fill: -color-fg-default;
|
||||
}
|
||||
|
||||
.bar .filter-bar {
|
||||
.store-header-bar .filter-bar {
|
||||
-fx-background-radius: 3px;
|
||||
-fx-background-color: -color-bg-default;
|
||||
-fx-border-color: -color-fg-default;
|
||||
|
@ -159,7 +138,7 @@
|
|||
}
|
||||
|
||||
|
||||
.bar .filter-bar .filter-background {
|
||||
.store-header-bar .filter-bar .filter-background {
|
||||
-fx-opacity: 0.7;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
.menu-button .context-menu > * > * {
|
||||
.store-header-bar .menu-button .context-menu > * > * {
|
||||
-fx-padding: 5px 10px 5px 10px;
|
||||
}
|
||||
|
||||
.menu-button .context-menu > * {
|
||||
.store-header-bar .menu-button .context-menu > * {
|
||||
-fx-padding: 0;
|
||||
}
|
||||
|
||||
.menu-button .context-menu {
|
||||
.store-header-bar .menu-button .context-menu {
|
||||
-fx-padding: 3px;
|
||||
-fx-background-radius: 4px;
|
||||
-fx-border-radius: 4px;
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
}
|
||||
|
||||
.prefs {
|
||||
-fx-background-color: transparent;
|
||||
-fx-background-color: -color-bg-default;
|
||||
}
|
||||
|
||||
.prefs .sidebar {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
}
|
||||
|
||||
.root:dark.background {
|
||||
-fx-background-color: derive(-color-bg-default, 5%);
|
||||
-fx-background-color: derive(-color-bg-default, 1%);
|
||||
}
|
||||
|
||||
.root:light.background {
|
||||
|
@ -59,4 +59,3 @@
|
|||
.root:dark .loading-comp {
|
||||
-fx-background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
.third-party-dependency-list-comp {
|
||||
|
||||
}
|
||||
|
||||
.third-party-dependency-list-comp .titled-pane {
|
||||
.third-party-dependency-list-comp .titled-pane .content * {
|
||||
-fx-background-color: transparent;
|
||||
}
|
||||
|
||||
.titled-pane .content {
|
||||
-fx-padding: 0em;
|
||||
.titled-pane {
|
||||
-fx-background-radius: 5;
|
||||
}
|
||||
|
||||
.titled-pane .content {
|
||||
-fx-padding: 4;
|
||||
}
|
||||
|
|
|
@ -87,10 +87,10 @@ project.ext {
|
|||
website = 'https://xpipe.io'
|
||||
sourceWebsite = 'https://github.com/xpipe-io/xpipe'
|
||||
authors = 'Christopher Schnick'
|
||||
javafxVersion = '22'
|
||||
javafxVersion = '22.0.1'
|
||||
platformName = getPlatformName()
|
||||
artifactChecksums = new HashMap<String, String>()
|
||||
languages = ["en", "nl", "es", "fr", "de", "it", "pt", "ru", "ja", "zh", "tr"]
|
||||
languages = ["en", "nl", "es", "fr", "de", "it", "pt", "ru", "ja", "zh", "tr", "da"]
|
||||
jvmRunArgs = [
|
||||
"--add-opens", "java.base/java.lang=io.xpipe.app",
|
||||
"--add-opens", "java.base/java.lang=io.xpipe.core",
|
||||
|
|
|
@ -94,7 +94,7 @@ public interface ShellControl extends ProcessControl {
|
|||
ShellControl withErrorFormatter(Function<String, String> formatter);
|
||||
|
||||
String prepareIntermediateTerminalOpen(
|
||||
String content, TerminalInitScriptConfig config, WorkingDirectoryFunction workingDirectory)
|
||||
TerminalInitFunction content, TerminalInitScriptConfig config, WorkingDirectoryFunction workingDirectory)
|
||||
throws Exception;
|
||||
|
||||
FilePath getSystemTemporaryDirectory();
|
||||
|
|
|
@ -62,16 +62,12 @@ public interface ShellDialect {
|
|||
|
||||
String addToPathVariableCommand(List<String> entries, boolean append);
|
||||
|
||||
default String applyRcFileCommand() {
|
||||
default String applyInitFileCommand() {
|
||||
return null;
|
||||
}
|
||||
|
||||
String changeTitleCommand(String newTitle);
|
||||
|
||||
default String applyProfileFilesCommand() {
|
||||
return null;
|
||||
}
|
||||
|
||||
CommandControl createStreamFileWriteCommand(ShellControl shellControl, String file, long totalBytes);
|
||||
|
||||
default String getCdCommand(String directory) {
|
||||
|
|
|
@ -23,7 +23,7 @@ public class ShellDialects {
|
|||
public static ShellDialect CSH;
|
||||
public static ShellDialect FISH;
|
||||
|
||||
public static ShellDialect UNSUPPORTED;
|
||||
public static ShellDialect NO_INTERACTION;
|
||||
public static ShellDialect CISCO;
|
||||
public static ShellDialect MIKROTIK;
|
||||
public static ShellDialect RBASH;
|
||||
|
@ -74,7 +74,7 @@ public class ShellDialects {
|
|||
CSH = byId("csh");
|
||||
ASH = byId("ash");
|
||||
SH = byId("sh");
|
||||
UNSUPPORTED = byId("unsupported");
|
||||
NO_INTERACTION = byId("unsupported");
|
||||
CISCO = byId("cisco");
|
||||
MIKROTIK = byId("mikrotik");
|
||||
RBASH = byId("rbash");
|
||||
|
|
|
@ -4,6 +4,8 @@ import java.util.List;
|
|||
|
||||
public interface ShellLaunchCommand {
|
||||
|
||||
String inlineCdCommand(String cd);
|
||||
|
||||
List<String> localCommand();
|
||||
|
||||
default String loginCommand() {
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
package io.xpipe.core.process;
|
||||
|
||||
import io.xpipe.core.util.FailableFunction;
|
||||
|
||||
public interface TerminalInitFunction {
|
||||
|
||||
static TerminalInitFunction of(FailableFunction<ShellControl, String, Exception> f) {
|
||||
return new TerminalInitFunction() {
|
||||
@Override
|
||||
public boolean isFixed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSpecified() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String apply(ShellControl shellControl) throws Exception {
|
||||
return f.apply(shellControl);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static TerminalInitFunction fixed(String s) {
|
||||
return new TerminalInitFunction() {
|
||||
@Override
|
||||
public boolean isFixed() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSpecified() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String apply(ShellControl shellControl) {
|
||||
return s;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static TerminalInitFunction none() {
|
||||
return new TerminalInitFunction() {
|
||||
@Override
|
||||
public boolean isFixed() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSpecified() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String apply(ShellControl shellControl) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
boolean isFixed();
|
||||
|
||||
boolean isSpecified();
|
||||
|
||||
String apply(ShellControl shellControl) throws Exception;
|
||||
}
|
|
@ -7,8 +7,9 @@ public class TerminalInitScriptConfig {
|
|||
|
||||
String displayName;
|
||||
boolean clearScreen;
|
||||
TerminalInitFunction terminalSpecificCommands;
|
||||
|
||||
public static TerminalInitScriptConfig ofName(String name) {
|
||||
return new TerminalInitScriptConfig(name, true);
|
||||
return new TerminalInitScriptConfig(name, true, null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ package io.xpipe.core.store;
|
|||
|
||||
public interface ExpandedLifecycleStore extends DataStore {
|
||||
|
||||
default void initializeValidate() throws Exception {}
|
||||
default void initializeValidate() {}
|
||||
|
||||
default void finalizeValidate() throws Exception {}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package io.xpipe.core.store;
|
|||
public interface SingletonSessionStore<T extends SingletonSessionStore.Session>
|
||||
extends ExpandedLifecycleStore, InternalCacheDataStore {
|
||||
|
||||
abstract static class Session {
|
||||
abstract class Session {
|
||||
|
||||
public abstract boolean isRunning();
|
||||
|
||||
|
@ -13,24 +13,24 @@ public interface SingletonSessionStore<T extends SingletonSessionStore.Session>
|
|||
}
|
||||
|
||||
@Override
|
||||
public default void finalizeValidate() throws Exception {
|
||||
default void finalizeValidate() throws Exception {
|
||||
stopSessionIfNeeded();
|
||||
}
|
||||
|
||||
default void setEnabled(boolean value) {
|
||||
default void setSessionEnabled(boolean value) {
|
||||
setCache("sessionEnabled", value);
|
||||
}
|
||||
|
||||
default boolean isRunning() {
|
||||
default boolean isSessionRunning() {
|
||||
return getCache("sessionRunning", Boolean.class, false);
|
||||
}
|
||||
|
||||
default boolean isEnabled() {
|
||||
default boolean isSessionEnabled() {
|
||||
return getCache("sessionEnabled", Boolean.class, false);
|
||||
}
|
||||
|
||||
default void onSessionUpdate(boolean active) {
|
||||
setEnabled(active);
|
||||
setSessionEnabled(active);
|
||||
setCache("sessionRunning", active);
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,7 @@ public interface SingletonSessionStore<T extends SingletonSessionStore.Session>
|
|||
default void startSessionIfNeeded() throws Exception {
|
||||
synchronized (this) {
|
||||
var s = getSession();
|
||||
setEnabled(true);
|
||||
setSessionEnabled(true);
|
||||
if (s != null) {
|
||||
if (s.isRunning()) {
|
||||
return;
|
||||
|
@ -66,7 +66,7 @@ public interface SingletonSessionStore<T extends SingletonSessionStore.Session>
|
|||
default void stopSessionIfNeeded() throws Exception {
|
||||
synchronized (this) {
|
||||
var ex = getSession();
|
||||
setEnabled(false);
|
||||
setSessionEnabled(false);
|
||||
if (ex != null) {
|
||||
ex.stop();
|
||||
setCache("session", null);
|
||||
|
|
|
@ -21,8 +21,7 @@ public interface StatefulDataStore<T extends DataStoreState> extends DataStore {
|
|||
|
||||
@SuppressWarnings("unchecked")
|
||||
default T getState() {
|
||||
return (T)
|
||||
DataStateProvider.get().getState(this, this::createDefaultState).deepCopy();
|
||||
return (T) DataStateProvider.get().getState(this, this::createDefaultState).deepCopy();
|
||||
}
|
||||
|
||||
default void setState(T val) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package io.xpipe.core.util;
|
||||
|
||||
import com.fasterxml.jackson.databind.*;
|
||||
import io.xpipe.core.dialog.BaseQueryElement;
|
||||
import io.xpipe.core.dialog.BusyElement;
|
||||
import io.xpipe.core.dialog.ChoiceElement;
|
||||
|
@ -13,10 +14,6 @@ import com.fasterxml.jackson.annotation.JsonIdentityInfo;
|
|||
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.jsontype.NamedType;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
|
@ -99,7 +96,16 @@ public class CoreJacksonModule extends SimpleModule {
|
|||
|
||||
@Override
|
||||
public ShellDialect deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||
return ShellDialects.byNameIfPresent(p.getValueAsString()).orElse(null);
|
||||
JsonNode tree = JacksonMapper.getDefault().readTree(p);
|
||||
if (tree.isObject()) {
|
||||
var t = (JsonNode) tree.get("type");
|
||||
if (t == null) {
|
||||
return null;
|
||||
}
|
||||
return ShellDialects.byNameIfPresent(t.asText()).orElse(null);
|
||||
}
|
||||
|
||||
return ShellDialects.byNameIfPresent(tree.asText()).orElse(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -206,15 +206,6 @@ public class XPipeInstallation {
|
|||
return v;
|
||||
}
|
||||
|
||||
public static String queryInstallationVersion(ShellControl p, String exec) throws Exception {
|
||||
try (CommandControl c =
|
||||
p.command(CommandBuilder.of().addFile(exec).add("version")).start()) {
|
||||
return c.readStdoutOrThrow();
|
||||
} catch (ProcessOutputException ex) {
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
|
||||
public static Path getLocalBundledToolsDirectory() {
|
||||
Path path = getCurrentInstallationBasePath();
|
||||
|
||||
|
@ -282,20 +273,6 @@ public class XPipeInstallation {
|
|||
return path;
|
||||
}
|
||||
|
||||
public static String getDefaultInstallationBasePath(ShellControl p) throws Exception {
|
||||
String path;
|
||||
if (p.getOsType().equals(OsType.WINDOWS)) {
|
||||
var base = p.executeSimpleStringCommand(p.getShellDialect().getPrintVariableCommand("LOCALAPPDATA"));
|
||||
path = FileNames.join(base, isStaging() ? "XPipe PTB" : "XPipe");
|
||||
} else if (p.getOsType().equals(OsType.LINUX)) {
|
||||
path = isStaging() ? "/opt/xpipe-ptb" : "/opt/xpipe";
|
||||
} else {
|
||||
path = isStaging() ? "/Applications/XPipe PTB.app" : "/Applications/XPipe.app";
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
public static Path getLangPath() {
|
||||
if (!ModuleHelper.isImage()) {
|
||||
return getCurrentInstallationBasePath().resolve("lang");
|
||||
|
|
2
dist/licenses/vscode-icons.properties
vendored
2
dist/licenses/vscode-icons.properties
vendored
|
@ -1,4 +1,4 @@
|
|||
name=vscode-icons
|
||||
version=1.2.0
|
||||
license=MIT License / Creative Commons - ShareAlike (CC BY-SA)
|
||||
license=MIT License / Creative Commons - ShareAlike (CC BY-SA)
|
||||
link=https://github.com/vscode-icons/vscode-icons
|
BIN
dist/logo/ico/logo_40x40.png
vendored
Normal file
BIN
dist/logo/ico/logo_40x40.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
BIN
dist/logo/ico/logo_90x90.png
vendored
Normal file
BIN
dist/logo/ico/logo_90x90.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
BIN
dist/logo/logo.ico
vendored
BIN
dist/logo/logo.ico
vendored
Binary file not shown.
Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 154 KiB |
|
@ -10,7 +10,7 @@ public interface SelfReferentialStore extends DataStore {
|
|||
|
||||
default DataStoreEntry getSelfEntry() {
|
||||
return DataStorage.get()
|
||||
.getStoreEntryIfPresent(this)
|
||||
.getStoreEntryIfPresent(this, true)
|
||||
.or(() -> {
|
||||
return DataStorage.get().getStoreEntryInProgressIfPresent(this);
|
||||
})
|
||||
|
|
|
@ -6,11 +6,10 @@ import io.xpipe.app.core.AppLayoutModel;
|
|||
import io.xpipe.app.ext.ActionProvider;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.core.process.ShellStoreState;
|
||||
import io.xpipe.core.store.ShellStore;
|
||||
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
public class BrowseStoreAction implements ActionProvider {
|
||||
|
@ -19,6 +18,17 @@ public class BrowseStoreAction implements ActionProvider {
|
|||
public DataStoreCallSite<?> getDataStoreCallSite() {
|
||||
return new DataStoreCallSite<ShellStore>() {
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(DataStoreEntryRef<ShellStore> o) {
|
||||
var state = o.get().getStorePersistentState();
|
||||
if (state instanceof ShellStoreState shellStoreState) {
|
||||
return shellStoreState.getShellDialect() == null ||
|
||||
shellStoreState.getShellDialect().getDumbMode().supportsAnyPossibleInteraction();
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionProvider.Action createAction(DataStoreEntryRef<ShellStore> store) {
|
||||
return new Action(store.get());
|
||||
|
|
|
@ -24,7 +24,7 @@ public class LaunchAction implements ActionProvider {
|
|||
|
||||
@Override
|
||||
public DataStoreCallSite<?> getDataStoreCallSite() {
|
||||
return new DataStoreCallSite<DataStore>() {
|
||||
return new DataStoreCallSite<>() {
|
||||
|
||||
@Override
|
||||
public boolean canLinkTo() {
|
||||
|
@ -43,9 +43,8 @@ public class LaunchAction implements ActionProvider {
|
|||
|
||||
@Override
|
||||
public boolean isApplicable(DataStoreEntryRef<DataStore> o) {
|
||||
return o.get().getValidity().isUsable()
|
||||
&& (o.getStore() instanceof LaunchableStore
|
||||
|| o.get().getProvider().launchAction(o.get()) != null);
|
||||
return o.get().getValidity().isUsable() && (o.getStore() instanceof LaunchableStore || o.get().getProvider().launchAction(o.get()) !=
|
||||
null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -62,7 +61,7 @@ public class LaunchAction implements ActionProvider {
|
|||
|
||||
@Override
|
||||
public DefaultDataStoreCallSite<?> getDefaultDataStoreCallSite() {
|
||||
return new DefaultDataStoreCallSite<DataStore>() {
|
||||
return new DefaultDataStoreCallSite<>() {
|
||||
|
||||
@Override
|
||||
public ActionProvider.Action createAction(DataStoreEntryRef<DataStore> store) {
|
||||
|
@ -76,9 +75,8 @@ public class LaunchAction implements ActionProvider {
|
|||
|
||||
@Override
|
||||
public boolean isApplicable(DataStoreEntryRef<DataStore> o) {
|
||||
return o.get().getValidity().isUsable()
|
||||
&& (o.getStore() instanceof LaunchableStore
|
||||
|| o.get().getProvider().launchAction(o.get()) != null);
|
||||
return o.get().getValidity().isUsable() && (o.getStore() instanceof LaunchableStore || o.get().getProvider().launchAction(o.get()) !=
|
||||
null);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,10 +5,9 @@ import io.xpipe.app.ext.ActionProvider;
|
|||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.app.util.ScanAlert;
|
||||
import io.xpipe.core.process.ShellStoreState;
|
||||
import io.xpipe.core.store.ShellStore;
|
||||
|
||||
import javafx.beans.value.ObservableValue;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
public class ScanAction implements ActionProvider {
|
||||
|
@ -34,7 +33,17 @@ public class ScanAction implements ActionProvider {
|
|||
|
||||
@Override
|
||||
public boolean isApplicable(DataStoreEntryRef<ShellStore> o) {
|
||||
return o.get().getProvider().canHaveSubShells();
|
||||
if (!o.get().getProvider().canHaveSubShells()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var state = o.get().getStorePersistentState();
|
||||
if (state instanceof ShellStoreState shellStoreState) {
|
||||
return shellStoreState.getShellDialect() == null ||
|
||||
shellStoreState.getShellDialect().getDumbMode().supportsAnyPossibleInteraction();
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -4,6 +4,7 @@ import io.xpipe.app.browser.action.LeafAction;
|
|||
import io.xpipe.app.browser.file.BrowserEntry;
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.util.LocalShell;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import io.xpipe.core.process.ShellDialect;
|
||||
|
@ -17,34 +18,34 @@ public class BrowseInNativeManagerAction implements LeafAction {
|
|||
|
||||
@Override
|
||||
public void execute(OpenFileSystemModel model, List<BrowserEntry> entries) throws Exception {
|
||||
ShellControl sc = model.getFileSystem().getShell().get();
|
||||
ShellControl sc = model.getFileSystem().getShell().orElseThrow();
|
||||
ShellDialect d = sc.getShellDialect();
|
||||
for (BrowserEntry entry : entries) {
|
||||
var e = entry.getRawFileEntry().getPath();
|
||||
var localFile = sc.getLocalSystemAccess().translateToLocalSystemPath(e);
|
||||
switch (OsType.getLocal()) {
|
||||
case OsType.Windows windows -> {
|
||||
if (entry.getRawFileEntry().getKind() == FileKind.DIRECTORY) {
|
||||
sc.executeSimpleCommand("explorer " + d.fileArgument(localFile));
|
||||
} else {
|
||||
sc.executeSimpleCommand("explorer /select," + d.fileArgument(localFile));
|
||||
try (var local = LocalShell.getShell().start()) {
|
||||
switch (OsType.getLocal()) {
|
||||
case OsType.Windows windows -> {
|
||||
// Explorer does not support single quotes, so use normal quotes
|
||||
if (entry.getRawFileEntry().getKind() == FileKind.DIRECTORY) {
|
||||
local.executeSimpleCommand("explorer " + d.quoteArgument(localFile));
|
||||
} else {
|
||||
local.executeSimpleCommand("explorer /select," + d.quoteArgument(localFile));
|
||||
}
|
||||
}
|
||||
case OsType.Linux linux -> {
|
||||
var action = entry.getRawFileEntry().getKind() == FileKind.DIRECTORY ?
|
||||
"org.freedesktop.FileManager1.ShowFolders" :
|
||||
"org.freedesktop.FileManager1.ShowItems";
|
||||
var dbus = String.format("""
|
||||
dbus-send --session --print-reply --dest=org.freedesktop.FileManager1 --type=method_call /org/freedesktop/FileManager1 %s array:string:"file://%s" string:""
|
||||
""", action, localFile);
|
||||
local.executeSimpleCommand(dbus);
|
||||
}
|
||||
case OsType.MacOs macOs -> {
|
||||
local.executeSimpleCommand(
|
||||
"open " + (entry.getRawFileEntry().getKind() == FileKind.DIRECTORY ? "" : "-R ") + d.fileArgument(localFile));
|
||||
}
|
||||
}
|
||||
case OsType.Linux linux -> {
|
||||
var action = entry.getRawFileEntry().getKind() == FileKind.DIRECTORY
|
||||
? "org.freedesktop.FileManager1.ShowFolders"
|
||||
: "org.freedesktop.FileManager1.ShowItems";
|
||||
var dbus = String.format(
|
||||
"""
|
||||
dbus-send --session --print-reply --dest=org.freedesktop.FileManager1 --type=method_call /org/freedesktop/FileManager1 %s array:string:"file://%s" string:""
|
||||
""",
|
||||
action, localFile);
|
||||
sc.executeSimpleCommand(dbus);
|
||||
}
|
||||
case OsType.MacOs macOs -> {
|
||||
sc.executeSimpleCommand(
|
||||
"open " + (entry.getRawFileEntry().getKind() == FileKind.DIRECTORY ? "" : "-R ")
|
||||
+ d.fileArgument(localFile));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package io.xpipe.app.browser.action;
|
||||
package io.xpipe.ext.base.browser;
|
||||
|
||||
import io.xpipe.app.browser.action.ApplicationPathAction;
|
||||
import io.xpipe.app.browser.action.LeafAction;
|
||||
import io.xpipe.app.browser.file.BrowserEntry;
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.core.process.ShellControl;
|
|
@ -1,7 +1,6 @@
|
|||
package io.xpipe.ext.base.browser;
|
||||
|
||||
import io.xpipe.app.browser.action.BrowserActionFormatter;
|
||||
import io.xpipe.app.browser.action.MultiExecuteAction;
|
||||
import io.xpipe.app.browser.file.BrowserEntry;
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.browser.icon.BrowserIconFileType;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package io.xpipe.ext.base.browser;
|
||||
|
||||
import io.xpipe.app.browser.action.BrowserActionFormatter;
|
||||
import io.xpipe.app.browser.action.ToFileCommandAction;
|
||||
import io.xpipe.app.browser.file.BrowserEntry;
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.browser.icon.BrowserIconFileType;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package io.xpipe.app.browser.action;
|
||||
package io.xpipe.ext.base.browser;
|
||||
|
||||
import io.xpipe.app.browser.action.BranchAction;
|
||||
import io.xpipe.app.browser.action.LeafAction;
|
||||
import io.xpipe.app.browser.file.BrowserEntry;
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
|
@ -7,11 +9,8 @@ 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 org.apache.commons.io.FilenameUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public abstract class MultiExecuteAction implements BranchAction {
|
||||
|
@ -30,8 +29,7 @@ public abstract class MultiExecuteAction implements BranchAction {
|
|||
for (BrowserEntry entry : entries) {
|
||||
TerminalLauncher.open(
|
||||
model.getEntry().getEntry(),
|
||||
FilenameUtils.getBaseName(
|
||||
entry.getRawFileEntry().getPath()),
|
||||
entry.getRawFileEntry().getName(),
|
||||
model.getCurrentDirectory() != null
|
||||
? model.getCurrentDirectory()
|
||||
.getPath()
|
|
@ -40,7 +40,7 @@ public class RenameAction implements LeafAction {
|
|||
|
||||
@Override
|
||||
public ObservableValue<String> getName(OpenFileSystemModel model, List<BrowserEntry> entries) {
|
||||
return AppI18n.observable("openWithDefaultApplication");
|
||||
return AppI18n.observable("rename");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package io.xpipe.ext.base.browser;
|
||||
|
||||
import io.xpipe.app.browser.action.MultiExecuteAction;
|
||||
import io.xpipe.app.browser.file.BrowserEntry;
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package io.xpipe.app.browser.action;
|
||||
package io.xpipe.ext.base.browser;
|
||||
|
||||
import io.xpipe.app.browser.action.ApplicationPathAction;
|
||||
import io.xpipe.app.browser.action.LeafAction;
|
||||
import io.xpipe.app.browser.file.BrowserEntry;
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.util.FileOpener;
|
|
@ -1,6 +1,5 @@
|
|||
package io.xpipe.ext.base.browser;
|
||||
|
||||
import io.xpipe.app.browser.action.ExecuteApplicationAction;
|
||||
import io.xpipe.app.browser.file.BrowserEntry;
|
||||
import io.xpipe.app.browser.fs.OpenFileSystemModel;
|
||||
import io.xpipe.app.browser.icon.BrowserIconFileType;
|
||||
|
|
|
@ -51,7 +51,7 @@ public class DesktopApplicationStoreProvider implements DataStoreProvider {
|
|||
|
||||
@Override
|
||||
public CreationCategory getCreationCategory() {
|
||||
return CreationCategory.DESKSTOP;
|
||||
return CreationCategory.DESKTOP;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -52,7 +52,7 @@ public class DesktopCommandStoreProvider implements DataStoreProvider {
|
|||
|
||||
@Override
|
||||
public CreationCategory getCreationCategory() {
|
||||
return CreationCategory.DESKSTOP;
|
||||
return CreationCategory.DESKTOP;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -50,8 +50,7 @@ public class DesktopEnvironmentStore extends JacksonizedValue
|
|||
var f = ScriptStore.flatten(scripts);
|
||||
var filtered = f.stream()
|
||||
.filter(simpleScriptStore ->
|
||||
simpleScriptStore.getMinimumDialect().isCompatibleTo(dialect)
|
||||
&& simpleScriptStore.getExecutionType().runInTerminal())
|
||||
simpleScriptStore.getMinimumDialect().isCompatibleTo(dialect))
|
||||
.toList();
|
||||
var initCommands = new ArrayList<>(filtered.stream()
|
||||
.map(simpleScriptStore -> simpleScriptStore.getCommands())
|
||||
|
@ -89,17 +88,16 @@ public class DesktopEnvironmentStore extends JacksonizedValue
|
|||
}
|
||||
|
||||
public void runDesktopTerminal(String name, String script) throws Exception {
|
||||
var launchCommand = terminal.remoteLaunchCommand();
|
||||
var launchCommand = terminal.remoteLaunchCommand(base.getStore().getUsedDialect());
|
||||
var toExecute = (script != null
|
||||
? getMergedInitCommands(
|
||||
script + "\n" + dialect.getPauseCommand() + "\n" + dialect.getNormalExitCommand())
|
||||
: getMergedInitCommands(null));
|
||||
var scriptFile = base.getStore().createScript(dialect, toExecute);
|
||||
var launchScriptFile = base.getStore()
|
||||
.createScript(
|
||||
dialect, dialect.prepareTerminalInitFileOpenCommand(dialect, null, scriptFile.toString()));
|
||||
.createScript(dialect, dialect.prepareTerminalInitFileOpenCommand(dialect, null, scriptFile.toString()));
|
||||
var launchConfig =
|
||||
new ExternalTerminalType.LaunchConfiguration(null, name, name, launchScriptFile, getUsedDialect());
|
||||
new ExternalTerminalType.LaunchConfiguration(null, name, name, launchScriptFile, dialect);
|
||||
base.getStore().runDesktopScript(name, launchCommand.apply(launchConfig));
|
||||
}
|
||||
|
||||
|
|
|
@ -84,7 +84,7 @@ public class DesktopEnvironmentStoreProvider implements DataStoreProvider {
|
|||
|
||||
@Override
|
||||
public CreationCategory getCreationCategory() {
|
||||
return CreationCategory.DESKSTOP;
|
||||
return CreationCategory.DESKTOP;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -19,7 +19,6 @@ public enum PredefinedScriptStore {
|
|||
.group(PredefinedScriptGroup.CLINK.getEntry())
|
||||
.minimumDialect(ShellDialects.CMD)
|
||||
.commands(file("clink.bat"))
|
||||
.executionType(SimpleScriptStore.ExecutionType.TERMINAL_ONLY)
|
||||
.build()),
|
||||
CLINK_INJECT("Clink Inject", () -> SimpleScriptStore.builder()
|
||||
.group(PredefinedScriptGroup.CLINK.getEntry())
|
||||
|
@ -28,38 +27,32 @@ public enum PredefinedScriptStore {
|
|||
.commands("""
|
||||
clink inject --quiet
|
||||
""")
|
||||
.executionType(SimpleScriptStore.ExecutionType.TERMINAL_ONLY)
|
||||
.build()),
|
||||
STARSHIP_BASH("Starship Bash", () -> SimpleScriptStore.builder()
|
||||
.group(PredefinedScriptGroup.STARSHIP.getEntry())
|
||||
.minimumDialect(ShellDialects.BASH)
|
||||
.commands(file("starship_bash.sh"))
|
||||
.executionType(SimpleScriptStore.ExecutionType.TERMINAL_ONLY)
|
||||
.build()),
|
||||
STARSHIP_ZSH("Starship Zsh", () -> SimpleScriptStore.builder()
|
||||
.group(PredefinedScriptGroup.STARSHIP.getEntry())
|
||||
.minimumDialect(ShellDialects.ZSH)
|
||||
.commands(file("starship_zsh.sh"))
|
||||
.executionType(SimpleScriptStore.ExecutionType.TERMINAL_ONLY)
|
||||
.build()),
|
||||
STARSHIP_FISH("Starship Fish", () -> SimpleScriptStore.builder()
|
||||
.group(PredefinedScriptGroup.STARSHIP.getEntry())
|
||||
.minimumDialect(ShellDialects.FISH)
|
||||
.commands(file("starship_fish.fish"))
|
||||
.executionType(SimpleScriptStore.ExecutionType.TERMINAL_ONLY)
|
||||
.build()),
|
||||
STARSHIP_CMD("Starship Cmd", () -> SimpleScriptStore.builder()
|
||||
.group(PredefinedScriptGroup.STARSHIP.getEntry())
|
||||
.minimumDialect(ShellDialects.CMD)
|
||||
.script(CLINK_SETUP.getEntry())
|
||||
.commands(file(("starship_cmd.bat")))
|
||||
.executionType(SimpleScriptStore.ExecutionType.TERMINAL_ONLY)
|
||||
.build()),
|
||||
STARSHIP_POWERSHELL("Starship Powershell", () -> SimpleScriptStore.builder()
|
||||
.group(PredefinedScriptGroup.STARSHIP.getEntry())
|
||||
.minimumDialect(ShellDialects.POWERSHELL)
|
||||
.commands(file("starship_powershell.ps1"))
|
||||
.executionType(SimpleScriptStore.ExecutionType.TERMINAL_ONLY)
|
||||
.build());
|
||||
|
||||
private final String name;
|
||||
|
|
|
@ -25,18 +25,17 @@ public class ScriptGroupStoreProvider implements DataStoreProvider {
|
|||
|
||||
@Override
|
||||
public StoreEntryComp customEntryComp(StoreSection sec, boolean preferLarge) {
|
||||
ScriptGroupStore s = sec.getWrapper().getEntry().getStore().asNeeded();
|
||||
if (sec.getWrapper().getValidity().getValue() != DataStoreEntry.Validity.COMPLETE) {
|
||||
return new DenseStoreEntryComp(sec.getWrapper(), true, null);
|
||||
}
|
||||
|
||||
var def = new StoreToggleComp("base.isDefaultGroup", sec, s.getState().isDefault(), aBoolean -> {
|
||||
var def = StoreToggleComp.<ScriptGroupStore>simpleToggle("base.isDefaultGroup", sec, s -> s.getState().isDefault(), (s, aBoolean) -> {
|
||||
var state = s.getState();
|
||||
state.setDefault(aBoolean);
|
||||
s.setState(state);
|
||||
});
|
||||
|
||||
var bring = new StoreToggleComp("base.bringToShells", sec, s.getState().isBringToShell(), aBoolean -> {
|
||||
var bring = StoreToggleComp.<ScriptGroupStore>simpleToggle("base.bringToShells", sec, s -> s.getState().isBringToShell(), (s, aBoolean) -> {
|
||||
var state = s.getState();
|
||||
state.setBringToShell(aBoolean);
|
||||
s.setState(state);
|
||||
|
|
|
@ -25,14 +25,10 @@ public class SimpleScriptStore extends ScriptStore implements ScriptSnippet {
|
|||
|
||||
private final ShellDialect minimumDialect;
|
||||
private final String commands;
|
||||
private final ExecutionType executionType;
|
||||
|
||||
private String assemble(ShellControl shellControl, ExecutionType type) {
|
||||
var targetType = type == ExecutionType.TERMINAL_ONLY
|
||||
? shellControl.getOriginalShellDialect()
|
||||
: shellControl.getShellDialect();
|
||||
if ((executionType == type || executionType == ExecutionType.BOTH)
|
||||
&& minimumDialect.isCompatibleTo(targetType)) {
|
||||
private String assemble(ShellControl shellControl) {
|
||||
var targetType = shellControl.getOriginalShellDialect();
|
||||
if (minimumDialect.isCompatibleTo(targetType)) {
|
||||
var shebang = commands.startsWith("#");
|
||||
// Fix new lines and shebang
|
||||
var fixedCommands = commands.lines()
|
||||
|
@ -48,19 +44,18 @@ public class SimpleScriptStore extends ScriptStore implements ScriptSnippet {
|
|||
|
||||
@Override
|
||||
public String content(ShellControl shellControl) {
|
||||
return assemble(shellControl, executionType);
|
||||
return assemble(shellControl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScriptSnippet.ExecutionType executionType() {
|
||||
return executionType;
|
||||
return ExecutionType.TERMINAL_ONLY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkComplete() throws Throwable {
|
||||
Validators.nonNull(group);
|
||||
super.checkComplete();
|
||||
Validators.nonNull(executionType);
|
||||
Validators.nonNull(minimumDialect);
|
||||
}
|
||||
|
||||
|
|
|
@ -43,11 +43,6 @@ public class SimpleScriptStoreProvider implements DataStoreProvider {
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canMoveCategories() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEdit() {
|
||||
return true;
|
||||
|
@ -55,25 +50,25 @@ public class SimpleScriptStoreProvider implements DataStoreProvider {
|
|||
|
||||
@Override
|
||||
public StoreEntryComp customEntryComp(StoreSection sec, boolean preferLarge) {
|
||||
SimpleScriptStore s = sec.getWrapper().getEntry().getStore().asNeeded();
|
||||
if (sec.getWrapper().getValidity().getValue() != DataStoreEntry.Validity.COMPLETE) {
|
||||
return new DenseStoreEntryComp(sec.getWrapper(), true, null);
|
||||
}
|
||||
|
||||
var groupWrapper = StoreViewState.get().getEntryWrapper(s.getGroup().getEntry());
|
||||
|
||||
var def = new StoreToggleComp("base.isDefault", sec, s.getState().isDefault(), aBoolean -> {
|
||||
var def = StoreToggleComp.<SimpleScriptStore>simpleToggle("base.isDefaultGroup", sec, s -> s.getState().isDefault(), (s, aBoolean) -> {
|
||||
var state = s.getState();
|
||||
state.setDefault(aBoolean);
|
||||
s.setState(state);
|
||||
});
|
||||
|
||||
var bring = new StoreToggleComp("base.bringToShells", sec, s.getState().isBringToShell(), aBoolean -> {
|
||||
var bring = StoreToggleComp.<SimpleScriptStore>simpleToggle("base.bringToShells", sec, s -> s.getState().isBringToShell(), (s, aBoolean) -> {
|
||||
var state = s.getState();
|
||||
state.setBringToShell(aBoolean);
|
||||
s.setState(state);
|
||||
});
|
||||
|
||||
SimpleScriptStore s = sec.getWrapper().getEntry().getStore().asNeeded();
|
||||
var groupWrapper = StoreViewState.get().getEntryWrapper(s.getGroup().getEntry());
|
||||
|
||||
// Disable selection if parent group is already made default
|
||||
def.disable(BindingsHelper.map(groupWrapper.getPersistentState(), o -> {
|
||||
ScriptStore.State state = (ScriptStore.State) o;
|
||||
|
@ -141,10 +136,8 @@ public class SimpleScriptStoreProvider implements DataStoreProvider {
|
|||
|
||||
var group = new SimpleObjectProperty<>(st.getGroup());
|
||||
Property<ShellDialect> dialect = new SimpleObjectProperty<>(st.getMinimumDialect());
|
||||
var others =
|
||||
new SimpleListProperty<>(FXCollections.observableArrayList(new ArrayList<>(st.getEffectiveScripts())));
|
||||
var others = new SimpleListProperty<>(FXCollections.observableArrayList(new ArrayList<>(st.getEffectiveScripts())));
|
||||
Property<String> commandProp = new SimpleObjectProperty<>(st.getCommands());
|
||||
var type = new SimpleObjectProperty<>(st.getExecutionType());
|
||||
|
||||
Comp<?> choice = (Comp<?>) Class.forName(
|
||||
AppExtensionManager.getInstance()
|
||||
|
@ -181,9 +174,6 @@ public class SimpleScriptStoreProvider implements DataStoreProvider {
|
|||
})),
|
||||
commandProp)
|
||||
.name("executionType")
|
||||
.description("executionTypeDescription")
|
||||
.longDescription("base:executionType")
|
||||
.addComp(new ScriptStoreTypeChoiceComp(type), type)
|
||||
.name("scriptGroup")
|
||||
.description("scriptGroupDescription")
|
||||
.addComp(
|
||||
|
@ -204,7 +194,6 @@ public class SimpleScriptStoreProvider implements DataStoreProvider {
|
|||
.scripts(new ArrayList<>(others.get()))
|
||||
.description(st.getDescription())
|
||||
.commands(commandProp.getValue())
|
||||
.executionType(type.get())
|
||||
.build();
|
||||
},
|
||||
store)
|
||||
|
@ -256,12 +245,7 @@ public class SimpleScriptStoreProvider implements DataStoreProvider {
|
|||
return new SimpleStringProperty((scriptStore.getMinimumDialect() != null
|
||||
? scriptStore.getMinimumDialect().getDisplayName() + " "
|
||||
: "")
|
||||
+ (scriptStore.getExecutionType() == SimpleScriptStore.ExecutionType.TERMINAL_ONLY
|
||||
? "Terminal"
|
||||
: scriptStore.getExecutionType() == SimpleScriptStore.ExecutionType.DUMB_ONLY
|
||||
? "Background"
|
||||
: "")
|
||||
+ " Snippet");
|
||||
+ " snippet");
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
|
@ -286,7 +270,6 @@ public class SimpleScriptStoreProvider implements DataStoreProvider {
|
|||
public DataStore defaultStore() {
|
||||
return SimpleScriptStore.builder()
|
||||
.scripts(List.of())
|
||||
.executionType(SimpleScriptStore.ExecutionType.TERMINAL_ONLY)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
|
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue