Merge branch 'xpipe-io:master' into patch-1

This commit is contained in:
Tolga Ulas 2024-04-24 17:52:03 +03:00 committed by GitHub
commit 67059394f6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
248 changed files with 2518 additions and 1187 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -40,7 +40,7 @@ public class BrowserSessionMultiTab extends BrowserSessionTab<DataStore> {
return true;
}
public void init() throws Exception {}
public void init() {}
public void close() {}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,6 +6,7 @@ import io.xpipe.app.util.*;
import io.xpipe.core.process.CommandBuilder;
import io.xpipe.core.process.OsType;
import io.xpipe.core.util.SecretValue;
import lombok.Value;
import java.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;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -27,7 +27,7 @@
}
.prefs {
-fx-background-color: transparent;
-fx-background-color: -color-bg-default;
}
.prefs .sidebar {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -4,6 +4,8 @@ import java.util.List;
public interface ShellLaunchCommand {
String inlineCdCommand(String cd);
List<String> localCommand();
default String loginCommand() {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 154 KiB

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -51,7 +51,7 @@ public class DesktopApplicationStoreProvider implements DataStoreProvider {
@Override
public CreationCategory getCreationCategory() {
return CreationCategory.DESKSTOP;
return CreationCategory.DESKTOP;
}
@Override

View file

@ -52,7 +52,7 @@ public class DesktopCommandStoreProvider implements DataStoreProvider {
@Override
public CreationCategory getCreationCategory() {
return CreationCategory.DESKSTOP;
return CreationCategory.DESKTOP;
}
@Override

View file

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

View file

@ -84,7 +84,7 @@ public class DesktopEnvironmentStoreProvider implements DataStoreProvider {
@Override
public CreationCategory getCreationCategory() {
return CreationCategory.DESKSTOP;
return CreationCategory.DESKTOP;
}
@Override

View file

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

View file

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

View file

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

View file

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

Some files were not shown because too many files have changed in this diff Show more