Merge branch 1.7.3 into master
4
.gitignore
vendored
|
@ -1,6 +1,8 @@
|
||||||
.gradle/
|
.gradle/
|
||||||
build/
|
build/
|
||||||
.idea
|
.idea/*
|
||||||
|
!.idea/codeStyles
|
||||||
|
!.idea/inspectionProfiles
|
||||||
lib/
|
lib/
|
||||||
dev.properties
|
dev.properties
|
||||||
extensions.txt
|
extensions.txt
|
||||||
|
|
33
README.md
|
@ -1,8 +1,6 @@
|
||||||
<img src="https://github.com/xpipe-io/xpipe/assets/72509152/88d750f3-8469-4c51-bb64-5b264b0e9d47" alt="drawing" width="250"/>
|
<img src="https://github.com/xpipe-io/xpipe/assets/72509152/88d750f3-8469-4c51-bb64-5b264b0e9d47" alt="drawing" width="250"/>
|
||||||
|
|
||||||
### A brand-new shell connection hub and remote file manager
|
XPipe is a new type of shell connection hub and remote file manager that allows you to access your entire server infrastructure from your local machine. It works on top of your installed command-line programs and does not require any setup on your remote systems.
|
||||||
|
|
||||||
XPipe is a new type of shell connection hub and remote file manager that allows you to access your entire server infrastructure from your local machine. It works on top of your installed command-line programs that you normally use to connect and does not require any setup on your remote systems.
|
|
||||||
|
|
||||||
XPipe fully integrates with your tools such as your favourite text/code editors, terminals, shells, command-line tools and more. The platform is designed to be extensible, allowing anyone to add easily support for more tools or to implement custom functionality through a modular extension system.
|
XPipe fully integrates with your tools such as your favourite text/code editors, terminals, shells, command-line tools and more. The platform is designed to be extensible, allowing anyone to add easily support for more tools or to implement custom functionality through a modular extension system.
|
||||||
|
|
||||||
|
@ -17,21 +15,22 @@ It currently supports:
|
||||||
## Connection Hub
|
## Connection Hub
|
||||||
|
|
||||||
- Easily connect to and access all kinds of remote connections in one place
|
- Easily connect to and access all kinds of remote connections in one place
|
||||||
- Securely stores all information exclusively on your computer and encrypts all secret information. See the [security page](/SECURITY.md) for more information
|
- Securely stores all information exclusively on your computer and encrypts all secret information
|
||||||
- Allows you to create specific login environments on any system to instantly jump into a properly set up environment for every use case
|
- Allows you to create specific login environments on any system to instantly jump into a properly set up environment for every use case
|
||||||
- Can create desktop shortcuts that automatically open remote connections in your terminal
|
- Can create desktop shortcuts that automatically open remote connections in your terminal
|
||||||
- Group all your connections into hierarchical categories
|
- Group all your connections into hierarchical categories
|
||||||
|
|
||||||
![connections](https://github.com/xpipe-io/xpipe/assets/72509152/ef19aa85-1b66-45e0-a051-5a4658758626)
|
![connections](https://github.com/xpipe-io/xpipe/assets/72509152/3a690fe3-29b8-43fc-a1d1-1dee9be71d4d)
|
||||||
|
|
||||||
## Remote File Manager
|
## Remote File Manager
|
||||||
|
|
||||||
- Interact with the file system of any remote system using a workflow optimized for professionals
|
- Interact with the file system of any remote system using a workflow optimized for professionals
|
||||||
- Quickly open a terminal into any directory
|
- Quickly open a terminal session into any directory in your favourite terminal emulator
|
||||||
- Utilize your favourite local programs to open and edit remote files
|
- Utilize your favourite local programs to open and edit remote files
|
||||||
- Dynamically elevate sessions with sudo when required
|
- Dynamically elevate sessions with sudo when required without having to restart the session
|
||||||
|
- Integrates with your local desktop environment for a seamless transfer of local files
|
||||||
|
|
||||||
![browser](https://github.com/xpipe-io/xpipe/assets/72509152/5631fe50-58b4-4847-a5f4-ad3898a02a9f)
|
![browser](https://github.com/xpipe-io/xpipe/assets/72509152/60d70293-c513-4f12-b242-30610ce5ab5d)
|
||||||
|
|
||||||
## Terminal Launcher
|
## Terminal Launcher
|
||||||
|
|
||||||
|
@ -44,13 +43,22 @@ It currently supports:
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://github.com/xpipe-io/xpipe/assets/72509152/f3d29909-acd7-4568-a625-0667d936ef2b" alt="Terminal launcher"/>
|
<img src="https://github.com/xpipe-io/xpipe/assets/72509152/02351317-f25d-4af3-8116-bc3b4fb92312" alt="Terminal launcher"/>
|
||||||
</p>
|
</p>
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
## Downloads
|
## Versatile scripting system
|
||||||
|
|
||||||
Note that this is a desktop application that should be run on your local desktop workstation, not on any server or containers. It will be able to connect to your server infrastructure with ease from your local machine.
|
- Create reusable simple shell scripts, templates, and groups to run on connected remote systems
|
||||||
|
- Automatically make your scripts available in the PATH on any remote system without any setup
|
||||||
|
- Setup shell init environments for connections to fully customize your work environment for every purpose
|
||||||
|
- Open custom shells and custom remote connections by providing your own commands
|
||||||
|
|
||||||
|
![scripts](https://github.com/xpipe-io/xpipe/assets/72509152/2d473f7b-ae1d-4dd1-86a3-02658b094da5)
|
||||||
|
|
||||||
|
# Downloads
|
||||||
|
|
||||||
|
Note that this is a desktop application that should be run on your local desktop workstation, not on any server or containers. It will be able to connect to your server infrastructure from there.
|
||||||
|
|
||||||
### Installers
|
### Installers
|
||||||
|
|
||||||
|
@ -94,7 +102,6 @@ The script supports installation via `apt`, `rpm`, and `pacman` on Linux, plus a
|
||||||
bash <(curl -sL https://raw.githubusercontent.com/xpipe-io/xpipe/master/get-xpipe.sh)
|
bash <(curl -sL https://raw.githubusercontent.com/xpipe-io/xpipe/master/get-xpipe.sh)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Package managers
|
### Package managers
|
||||||
|
|
||||||
Alternatively, you can also use your favorite package manager (if supported):
|
Alternatively, you can also use your favorite package manager (if supported):
|
||||||
|
@ -107,7 +114,7 @@ Alternatively, you can also use your favorite package manager (if supported):
|
||||||
|
|
||||||
XPipe utilizes an open core model, which essentially means that the main application is open source while certain other components are not. Select parts are not open source yet, but may be added to this repository in the future.
|
XPipe utilizes an open core model, which essentially means that the main application is open source while certain other components are not. Select parts are not open source yet, but may be added to this repository in the future.
|
||||||
|
|
||||||
This mainly concerns the features only available in the professional tier and the shell handling library implementation and extensions for configuring and handling shell connections. Furthermore, some tests and especially test environments and that run on private servers are also not included in this repository. Finally, scripts and workflows to create and publish installers and packages are also not included to prevent attackers from easily impersonating the XPipe application.
|
This mainly concerns the features only available in the professional tier and the shell handling library implementation. Furthermore, some tests and especially test environments and that run on private servers are also not included in this repository.
|
||||||
|
|
||||||
## Further information
|
## Further information
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
package io.xpipe.app.browser;
|
package io.xpipe.app.browser;
|
||||||
|
|
||||||
import atlantafx.base.theme.Styles;
|
import atlantafx.base.theme.Styles;
|
||||||
import io.xpipe.app.comp.store.*;
|
import io.xpipe.app.comp.store.StoreEntryWrapper;
|
||||||
|
import io.xpipe.app.comp.store.StoreSection;
|
||||||
|
import io.xpipe.app.comp.store.StoreSectionMiniComp;
|
||||||
|
import io.xpipe.app.comp.store.StoreViewState;
|
||||||
import io.xpipe.app.core.AppFont;
|
import io.xpipe.app.core.AppFont;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.impl.FilterComp;
|
import io.xpipe.app.fxcomps.impl.FilterComp;
|
||||||
|
@ -31,14 +34,12 @@ import java.util.function.Predicate;
|
||||||
|
|
||||||
final class BrowserBookmarkList extends SimpleComp {
|
final class BrowserBookmarkList extends SimpleComp {
|
||||||
|
|
||||||
private static final PseudoClass SELECTED = PseudoClass.getPseudoClass("selected");
|
|
||||||
|
|
||||||
public static final Timer DROP_TIMER = new Timer("dnd", true);
|
public static final Timer DROP_TIMER = new Timer("dnd", true);
|
||||||
|
private static final PseudoClass SELECTED = PseudoClass.getPseudoClass("selected");
|
||||||
|
private final BrowserModel model;
|
||||||
private Point2D lastOver = new Point2D(-1, -1);
|
private Point2D lastOver = new Point2D(-1, -1);
|
||||||
private TimerTask activeTask;
|
private TimerTask activeTask;
|
||||||
|
|
||||||
private final BrowserModel model;
|
|
||||||
|
|
||||||
BrowserBookmarkList(BrowserModel model) {
|
BrowserBookmarkList(BrowserModel model) {
|
||||||
this.model = model;
|
this.model = model;
|
||||||
}
|
}
|
||||||
|
@ -48,28 +49,20 @@ final class BrowserBookmarkList extends SimpleComp {
|
||||||
var filterText = new SimpleStringProperty();
|
var filterText = new SimpleStringProperty();
|
||||||
var open = PlatformThread.sync(model.getSelected());
|
var open = PlatformThread.sync(model.getSelected());
|
||||||
Predicate<StoreEntryWrapper> applicable = storeEntryWrapper -> {
|
Predicate<StoreEntryWrapper> applicable = storeEntryWrapper -> {
|
||||||
return (storeEntryWrapper.getEntry().getStore() instanceof ShellStore
|
return (storeEntryWrapper.getEntry().getStore() instanceof ShellStore ||
|
||||||
|| storeEntryWrapper.getEntry().getStore() instanceof FixedHierarchyStore)
|
storeEntryWrapper.getEntry().getStore() instanceof FixedHierarchyStore) && storeEntryWrapper.getEntry().getValidity().isUsable();
|
||||||
&& storeEntryWrapper.getEntry().getValidity().isUsable();
|
|
||||||
};
|
};
|
||||||
var selectedCategory = new SimpleObjectProperty<>(StoreViewState.get().getActiveCategory().getValue());
|
var selectedCategory = new SimpleObjectProperty<>(StoreViewState.get().getActiveCategory().getValue());
|
||||||
var section = StoreSectionMiniComp.createList(
|
var section = StoreSectionMiniComp.createList(
|
||||||
StoreSection.createTopLevel(
|
StoreSection.createTopLevel(StoreViewState.get().getAllEntries(), applicable, filterText, selectedCategory), (s, comp) -> {
|
||||||
StoreViewState.get().getAllEntries(), applicable, filterText, selectedCategory),
|
|
||||||
(s, comp) -> {
|
|
||||||
BooleanProperty busy = new SimpleBooleanProperty(false);
|
BooleanProperty busy = new SimpleBooleanProperty(false);
|
||||||
comp.disable(Bindings.createBooleanBinding(() -> {
|
comp.disable(Bindings.createBooleanBinding(() -> {
|
||||||
return busy.get() || !applicable.test(s.getWrapper());
|
return busy.get() || !applicable.test(s.getWrapper());
|
||||||
}, busy));
|
}, busy));
|
||||||
comp.apply(struc -> {
|
comp.apply(struc -> {
|
||||||
open.addListener((observable, oldValue, newValue) -> {
|
open.addListener((observable, oldValue, newValue) -> {
|
||||||
struc.get()
|
struc.get().pseudoClassStateChanged(SELECTED,
|
||||||
.pseudoClassStateChanged(
|
newValue != null && newValue.getEntry().get().equals(s.getWrapper().getEntry()));
|
||||||
SELECTED,
|
|
||||||
newValue != null
|
|
||||||
&& newValue.getEntry().get()
|
|
||||||
.equals(s.getWrapper()
|
|
||||||
.getEntry()));
|
|
||||||
});
|
});
|
||||||
struc.get().setOnAction(event -> {
|
struc.get().setOnAction(event -> {
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
|
@ -90,19 +83,14 @@ final class BrowserBookmarkList extends SimpleComp {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
var category = new DataStoreCategoryChoiceComp(StoreViewState.get().getAllConnectionsCategory(), StoreViewState.get().getActiveCategory(), selectedCategory)
|
var category = new DataStoreCategoryChoiceComp(StoreViewState.get().getAllConnectionsCategory(), StoreViewState.get().getActiveCategory(),
|
||||||
.styleClass(Styles.LEFT_PILL)
|
selectedCategory).styleClass(Styles.LEFT_PILL).grow(false, true);
|
||||||
.grow(false, true);
|
var filter = new FilterComp(filterText).styleClass(Styles.RIGHT_PILL).hgrow().apply(struc -> {});
|
||||||
var filter =
|
|
||||||
new FilterComp(filterText).styleClass(Styles.RIGHT_PILL).hgrow().apply(struc -> {});
|
|
||||||
|
|
||||||
var top = new HorizontalComp(List.of(category, filter.hgrow()))
|
var top = new HorizontalComp(List.of(category, filter.hgrow())).styleClass("categories").apply(struc -> {
|
||||||
.styleClass("categories")
|
|
||||||
.apply(struc -> {
|
|
||||||
AppFont.medium(struc.get());
|
AppFont.medium(struc.get());
|
||||||
struc.get().setFillHeight(true);
|
struc.get().setFillHeight(true);
|
||||||
})
|
}).createRegion();
|
||||||
.createRegion();
|
|
||||||
var r = section.vgrow().createRegion();
|
var r = section.vgrow().createRegion();
|
||||||
var content = new VBox(top, r);
|
var content = new VBox(top, r);
|
||||||
content.setFillWidth(true);
|
content.setFillWidth(true);
|
||||||
|
|
|
@ -170,30 +170,45 @@ public class BrowserComp extends SimpleComp {
|
||||||
var modifying = new SimpleBooleanProperty();
|
var modifying = new SimpleBooleanProperty();
|
||||||
|
|
||||||
// Handle selection from platform
|
// Handle selection from platform
|
||||||
tabs.getSelectionModel().selectedIndexProperty().addListener((observable, oldValue, newValue) -> {
|
tabs.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
if (modifying.get()) {
|
if (modifying.get()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newValue.intValue() == -1) {
|
if (newValue == null) {
|
||||||
model.getSelected().setValue(null);
|
model.getSelected().setValue(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
model.getSelected().setValue(model.getOpenFileSystems().get(newValue.intValue()));
|
var source = map.entrySet().stream()
|
||||||
|
.filter(openFileSystemModelTabEntry ->
|
||||||
|
openFileSystemModelTabEntry.getValue().equals(newValue))
|
||||||
|
.findAny()
|
||||||
|
.map(Map.Entry::getKey)
|
||||||
|
.orElse(null);
|
||||||
|
model.getSelected().setValue(source);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle selection from model
|
// Handle selection from model
|
||||||
model.getSelected().addListener((observable, oldValue, newValue) -> {
|
model.getSelected().addListener((observable, oldValue, newValue) -> {
|
||||||
PlatformThread.runLaterIfNeeded(() -> {
|
PlatformThread.runLaterIfNeeded(() -> {
|
||||||
var index = model.getOpenFileSystems().indexOf(newValue);
|
if (newValue == null) {
|
||||||
if (index == -1 || index >= tabs.getTabs().size()) {
|
|
||||||
tabs.getSelectionModel().select(null);
|
tabs.getSelectionModel().select(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var tab = tabs.getTabs().get(index);
|
var toSelect = map.entrySet().stream()
|
||||||
tabs.getSelectionModel().select(tab);
|
.filter(openFileSystemModelTabEntry ->
|
||||||
|
openFileSystemModelTabEntry.getKey().equals(newValue))
|
||||||
|
.findAny()
|
||||||
|
.map(Map.Entry::getValue)
|
||||||
|
.orElse(null);
|
||||||
|
if (toSelect == null || !tabs.getTabs().contains(toSelect)) {
|
||||||
|
tabs.getSelectionModel().select(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tabs.getSelectionModel().select(toSelect);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,15 @@ import java.util.function.Consumer;
|
||||||
@Getter
|
@Getter
|
||||||
public class BrowserModel {
|
public class BrowserModel {
|
||||||
|
|
||||||
|
public static final BrowserModel DEFAULT = new BrowserModel(Mode.BROWSER);
|
||||||
|
private final Mode mode;
|
||||||
|
private final ObservableList<OpenFileSystemModel> openFileSystems = FXCollections.observableArrayList();
|
||||||
|
private final Property<OpenFileSystemModel> selected = new SimpleObjectProperty<>();
|
||||||
|
private final BrowserTransferModel localTransfersStage = new BrowserTransferModel(this);
|
||||||
|
private final ObservableList<BrowserEntry> selection = FXCollections.observableArrayList();
|
||||||
|
@Setter
|
||||||
|
private Consumer<List<FileStore>> onFinish;
|
||||||
|
|
||||||
public BrowserModel(Mode mode) {
|
public BrowserModel(Mode mode) {
|
||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
|
|
||||||
|
@ -36,40 +45,6 @@ public class BrowserModel {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Getter
|
|
||||||
public enum Mode {
|
|
||||||
BROWSER(false, true, true, true),
|
|
||||||
SINGLE_FILE_CHOOSER(true, false, true, false),
|
|
||||||
SINGLE_FILE_SAVE(true, false, true, false),
|
|
||||||
MULTIPLE_FILE_CHOOSER(true, true, true, false),
|
|
||||||
SINGLE_DIRECTORY_CHOOSER(true, false, false, true),
|
|
||||||
MULTIPLE_DIRECTORY_CHOOSER(true, true, false, true);
|
|
||||||
|
|
||||||
private final boolean chooser;
|
|
||||||
private final boolean multiple;
|
|
||||||
private final boolean acceptsFiles;
|
|
||||||
private final boolean acceptsDirectories;
|
|
||||||
|
|
||||||
Mode(boolean chooser, boolean multiple, boolean acceptsFiles, boolean acceptsDirectories) {
|
|
||||||
this.chooser = chooser;
|
|
||||||
this.multiple = multiple;
|
|
||||||
this.acceptsFiles = acceptsFiles;
|
|
||||||
this.acceptsDirectories = acceptsDirectories;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final BrowserModel DEFAULT = new BrowserModel(Mode.BROWSER);
|
|
||||||
|
|
||||||
private final Mode mode;
|
|
||||||
|
|
||||||
@Setter
|
|
||||||
private Consumer<List<FileStore>> onFinish;
|
|
||||||
|
|
||||||
private final ObservableList<OpenFileSystemModel> openFileSystems = FXCollections.observableArrayList();
|
|
||||||
private final Property<OpenFileSystemModel> selected = new SimpleObjectProperty<>();
|
|
||||||
private final BrowserTransferModel localTransfersStage = new BrowserTransferModel(this);
|
|
||||||
private final ObservableList<BrowserEntry> selection = FXCollections.observableArrayList();
|
|
||||||
|
|
||||||
public void restoreState(BrowserSavedState state) {
|
public void restoreState(BrowserSavedState state) {
|
||||||
state.getLastSystems().forEach(e -> {
|
state.getLastSystems().forEach(e -> {
|
||||||
restoreState(e, null);
|
restoreState(e, null);
|
||||||
|
@ -87,8 +62,7 @@ public class BrowserModel {
|
||||||
var list = new ArrayList<BrowserSavedState.Entry>();
|
var list = new ArrayList<BrowserSavedState.Entry>();
|
||||||
openFileSystems.forEach(model -> {
|
openFileSystems.forEach(model -> {
|
||||||
if (DataStorage.get().getStoreEntries().contains(model.getEntry().get())) {
|
if (DataStorage.get().getStoreEntries().contains(model.getEntry().get())) {
|
||||||
list.add(new BrowserSavedState.Entry(
|
list.add(new BrowserSavedState.Entry(model.getEntry().get().getUuid(), model.getCurrentPath().get()));
|
||||||
model.getEntry().get().getUuid(), model.getCurrentPath().get()));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -120,11 +94,8 @@ public class BrowserModel {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var stores = chosen.stream()
|
var stores = chosen.stream().map(
|
||||||
.map(entry -> new FileStore(
|
entry -> new FileStore(entry.getRawFileEntry().getFileSystem().getStore(), entry.getRawFileEntry().getPath())).toList();
|
||||||
entry.getRawFileEntry().getFileSystem().getStore(),
|
|
||||||
entry.getRawFileEntry().getPath()))
|
|
||||||
.toList();
|
|
||||||
onFinish.accept(stores);
|
onFinish.accept(stores);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,9 +112,7 @@ public class BrowserModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void openExistingFileSystemIfPresent(DataStoreEntryRef<? extends FileSystemStore> store) {
|
public void openExistingFileSystemIfPresent(DataStoreEntryRef<? extends FileSystemStore> store) {
|
||||||
var found = openFileSystems.stream()
|
var found = openFileSystems.stream().filter(model -> Objects.equals(model.getEntry(), store)).findFirst();
|
||||||
.filter(model -> Objects.equals(model.getEntry(), store))
|
|
||||||
.findFirst();
|
|
||||||
if (found.isPresent()) {
|
if (found.isPresent()) {
|
||||||
selected.setValue(found.get());
|
selected.setValue(found.get());
|
||||||
} else {
|
} else {
|
||||||
|
@ -176,9 +145,9 @@ public class BrowserModel {
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
OpenFileSystemModel model;
|
OpenFileSystemModel model;
|
||||||
|
|
||||||
|
try (var b = new BooleanScope(externalBusy != null ? externalBusy : new SimpleBooleanProperty()).start()) {
|
||||||
// Prevent multiple calls from interfering with each other
|
// Prevent multiple calls from interfering with each other
|
||||||
synchronized (BrowserModel.this) {
|
synchronized (BrowserModel.this) {
|
||||||
try (var b = new BooleanScope(externalBusy != null ? externalBusy : new SimpleBooleanProperty()).start()) {
|
|
||||||
model = new OpenFileSystemModel(this, store);
|
model = new OpenFileSystemModel(this, store);
|
||||||
model.initFileSystem();
|
model.initFileSystem();
|
||||||
model.initSavedState();
|
model.initSavedState();
|
||||||
|
@ -194,4 +163,26 @@ public class BrowserModel {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public enum Mode {
|
||||||
|
BROWSER(false, true, true, true),
|
||||||
|
SINGLE_FILE_CHOOSER(true, false, true, false),
|
||||||
|
SINGLE_FILE_SAVE(true, false, true, false),
|
||||||
|
MULTIPLE_FILE_CHOOSER(true, true, true, false),
|
||||||
|
SINGLE_DIRECTORY_CHOOSER(true, false, false, true),
|
||||||
|
MULTIPLE_DIRECTORY_CHOOSER(true, true, false, true);
|
||||||
|
|
||||||
|
private final boolean chooser;
|
||||||
|
private final boolean multiple;
|
||||||
|
private final boolean acceptsFiles;
|
||||||
|
private final boolean acceptsDirectories;
|
||||||
|
|
||||||
|
Mode(boolean chooser, boolean multiple, boolean acceptsFiles, boolean acceptsDirectories) {
|
||||||
|
this.chooser = chooser;
|
||||||
|
this.multiple = multiple;
|
||||||
|
this.acceptsFiles = acceptsFiles;
|
||||||
|
this.acceptsDirectories = acceptsDirectories;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,25 @@
|
||||||
package io.xpipe.app.browser;
|
package io.xpipe.app.browser;
|
||||||
|
|
||||||
import atlantafx.base.controls.Spacer;
|
import atlantafx.base.controls.Spacer;
|
||||||
import atlantafx.base.controls.Tile;
|
|
||||||
import atlantafx.base.theme.Styles;
|
import atlantafx.base.theme.Styles;
|
||||||
|
import io.xpipe.app.comp.base.ButtonComp;
|
||||||
|
import io.xpipe.app.comp.base.ListBoxViewComp;
|
||||||
import io.xpipe.app.comp.base.TileButtonComp;
|
import io.xpipe.app.comp.base.TileButtonComp;
|
||||||
import io.xpipe.app.core.AppFont;
|
import io.xpipe.app.core.AppFont;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
|
import io.xpipe.app.util.JfxHelper;
|
||||||
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Orientation;
|
import javafx.geometry.Orientation;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.ScrollPane;
|
|
||||||
import javafx.scene.control.Separator;
|
import javafx.scene.control.Separator;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.Priority;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
|
|
||||||
|
@ -53,40 +58,45 @@ public class BrowserWelcomeComp extends SimpleComp {
|
||||||
|
|
||||||
var storeList = new VBox();
|
var storeList = new VBox();
|
||||||
storeList.setSpacing(8);
|
storeList.setSpacing(8);
|
||||||
state.getLastSystems().forEach(e -> {
|
|
||||||
|
var list = FXCollections.observableList(state.getLastSystems().stream().filter(e -> {
|
||||||
var entry = DataStorage.get().getStoreEntryIfPresent(e.getUuid());
|
var entry = DataStorage.get().getStoreEntryIfPresent(e.getUuid());
|
||||||
if (entry.isEmpty()) {
|
if (entry.isEmpty()) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!entry.get().getValidity().isUsable()) {
|
if (!entry.get().getValidity().isUsable()) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var graphic =
|
return true;
|
||||||
entry.get().getProvider().getDisplayIconFileName(entry.get().getStore());
|
}).toList());
|
||||||
|
var box = new ListBoxViewComp<>(list, list, e -> {
|
||||||
|
var entry = DataStorage.get().getStoreEntryIfPresent(e.getUuid());
|
||||||
|
var graphic = entry.get().getProvider().getDisplayIconFileName(entry.get().getStore());
|
||||||
var view = PrettyImageHelper.ofFixedSquare(graphic, 45);
|
var view = PrettyImageHelper.ofFixedSquare(graphic, 45);
|
||||||
view.padding(new Insets(2, 8, 2, 8));
|
view.padding(new Insets(2, 8, 2, 8));
|
||||||
var tile = new Tile(
|
var content =
|
||||||
DataStorage.get().getStoreDisplayName(entry.get()),
|
JfxHelper.createNamedEntry(DataStorage.get().getStoreDisplayName(entry.get()), e.getPath(), graphic);
|
||||||
e.getPath(),
|
var disable = new SimpleBooleanProperty();
|
||||||
view.createRegion());
|
return new ButtonComp(null, content, () -> {
|
||||||
tile.setActionHandler(() -> {
|
ThreadHelper.runAsync(() -> {
|
||||||
model.restoreState(e, tile.disableProperty());
|
model.restoreState(e, disable);
|
||||||
});
|
});
|
||||||
storeList.getChildren().add(tile);
|
}).disable(disable).styleClass("color-box").apply(struc -> struc.get().setMaxWidth(2000)).grow(true, false);
|
||||||
});
|
}).apply(struc -> {
|
||||||
|
VBox vBox = (VBox) struc.get().getContent();
|
||||||
var sp = new ScrollPane(storeList);
|
vBox.setSpacing(10);
|
||||||
sp.setFitToWidth(true);
|
}).createRegion();
|
||||||
|
|
||||||
var layout = new VBox();
|
var layout = new VBox();
|
||||||
layout.setMaxWidth(700);
|
layout.getStyleClass().add("welcome");
|
||||||
layout.setPadding(new Insets(40, 40, 40, 50));
|
layout.setPadding(new Insets(40, 40, 40, 50));
|
||||||
layout.setSpacing(18);
|
layout.setSpacing(18);
|
||||||
layout.getChildren().add(hbox);
|
layout.getChildren().add(hbox);
|
||||||
layout.getChildren().add(new Separator(Orientation.HORIZONTAL));
|
layout.getChildren().add(new Separator(Orientation.HORIZONTAL));
|
||||||
layout.getChildren().add(sp);
|
layout.getChildren().add(box);
|
||||||
|
VBox.setVgrow(layout.getChildren().get(2), Priority.NEVER);
|
||||||
layout.getChildren().add(new Separator(Orientation.HORIZONTAL));
|
layout.getChildren().add(new Separator(Orientation.HORIZONTAL));
|
||||||
|
|
||||||
var tile = new TileButtonComp("restore", "restoreAllSessions", "mdmz-restore", actionEvent -> {
|
var tile = new TileButtonComp("restore", "restoreAllSessions", "mdmz-restore", actionEvent -> {
|
||||||
|
|
|
@ -124,7 +124,7 @@ public class FileSystemHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!model.getFileSystem().directoryExists(path)) {
|
if (!model.getFileSystem().directoryExists(path)) {
|
||||||
throw new IllegalArgumentException(String.format("Directory %s does not exist", path));
|
throw ErrorEvent.unreportable(new IllegalArgumentException(String.format("Directory %s does not exist", path)));
|
||||||
}
|
}
|
||||||
|
|
||||||
model.getFileSystem().directoryAccessible(path);
|
model.getFileSystem().directoryAccessible(path);
|
||||||
|
|
|
@ -7,9 +7,7 @@ import io.xpipe.app.storage.DataStoreEntryRef;
|
||||||
import io.xpipe.app.util.BooleanScope;
|
import io.xpipe.app.util.BooleanScope;
|
||||||
import io.xpipe.app.util.TerminalHelper;
|
import io.xpipe.app.util.TerminalHelper;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import io.xpipe.core.process.ProcessControlProvider;
|
import io.xpipe.core.process.*;
|
||||||
import io.xpipe.core.process.ShellControl;
|
|
||||||
import io.xpipe.core.process.ShellDialects;
|
|
||||||
import io.xpipe.core.store.*;
|
import io.xpipe.core.store.*;
|
||||||
import io.xpipe.core.util.FailableConsumer;
|
import io.xpipe.core.util.FailableConsumer;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
|
@ -155,21 +153,25 @@ public final class OpenFileSystemModel {
|
||||||
var name = adjustedPath + " - " + entry.get().getName();
|
var name = adjustedPath + " - " + entry.get().getName();
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
if (ShellDialects.ALL.stream().anyMatch(dialect -> adjustedPath.startsWith(dialect.getOpenCommand()))) {
|
if (ShellDialects.ALL.stream().anyMatch(dialect -> adjustedPath.startsWith(dialect.getOpenCommand()))) {
|
||||||
TerminalHelper.open(entry.getEntry(), name, fileSystem
|
TerminalHelper.open(
|
||||||
|
entry.getEntry(),
|
||||||
|
name,
|
||||||
|
fileSystem
|
||||||
.getShell()
|
.getShell()
|
||||||
.get()
|
.get()
|
||||||
.subShell(adjustedPath)
|
.subShell(adjustedPath)
|
||||||
.initWith(fileSystem
|
.initWith(new SimpleScriptSnippet(
|
||||||
|
fileSystem
|
||||||
.getShell()
|
.getShell()
|
||||||
.get()
|
.get()
|
||||||
.getShellDialect()
|
.getShellDialect()
|
||||||
.getCdCommand(currentPath.get())));
|
.getCdCommand(currentPath.get()),
|
||||||
|
ScriptSnippet.ExecutionType.BOTH)));
|
||||||
} else {
|
} else {
|
||||||
TerminalHelper.open(entry.getEntry(), name, fileSystem
|
TerminalHelper.open(
|
||||||
.getShell()
|
entry.getEntry(),
|
||||||
.get()
|
name,
|
||||||
.command(adjustedPath)
|
fileSystem.getShell().get().command(adjustedPath).withWorkingDirectory(directory));
|
||||||
.withWorkingDirectory(directory));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return Optional.ofNullable(currentPath.get());
|
return Optional.ofNullable(currentPath.get());
|
||||||
|
@ -363,8 +365,9 @@ public final class OpenFileSystemModel {
|
||||||
var fs = entry.getStore().createFileSystem();
|
var fs = entry.getStore().createFileSystem();
|
||||||
fs.open();
|
fs.open();
|
||||||
this.fileSystem = fs;
|
this.fileSystem = fs;
|
||||||
this.local =
|
this.local = fs.getShell()
|
||||||
fs.getShell().map(shellControl -> shellControl.hasLocalSystemAccess()).orElse(false);
|
.map(shellControl -> shellControl.hasLocalSystemAccess())
|
||||||
|
.orElse(false);
|
||||||
this.cache.init();
|
this.cache.init();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -393,7 +396,10 @@ public final class OpenFileSystemModel {
|
||||||
var connection = ((ConnectionFileSystem) fileSystem).getShellControl();
|
var connection = ((ConnectionFileSystem) fileSystem).getShellControl();
|
||||||
var name = directory + " - " + entry.get().getName();
|
var name = directory + " - " + entry.get().getName();
|
||||||
var toOpen = ProcessControlProvider.get().withDefaultScripts(connection);
|
var toOpen = ProcessControlProvider.get().withDefaultScripts(connection);
|
||||||
TerminalHelper.open(entry.getEntry(), name, toOpen.initWith(connection.getShellDialect().getCdCommand(directory)));
|
TerminalHelper.open(
|
||||||
|
entry.getEntry(),
|
||||||
|
name,
|
||||||
|
toOpen.initWith(new SimpleScriptSnippet(connection.getShellDialect().getCdCommand(directory), ScriptSnippet.ExecutionType.BOTH)));
|
||||||
|
|
||||||
// Restart connection as we will have to start it anyway, so we speed it up by doing it preemptively
|
// Restart connection as we will have to start it anyway, so we speed it up by doing it preemptively
|
||||||
connection.start();
|
connection.start();
|
||||||
|
|
|
@ -80,14 +80,14 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
|
||||||
vbox.getChildren().add(b.createRegion());
|
vbox.getChildren().add(b.createRegion());
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
// {
|
||||||
var b = new IconButtonComp("mdi2c-comment-processing-outline", () -> Hyperlinks.open(Hyperlinks.ROADMAP))
|
// var b = new IconButtonComp("mdi2c-comment-processing-outline", () -> Hyperlinks.open(Hyperlinks.ROADMAP))
|
||||||
.apply(new FancyTooltipAugment<>("roadmap"));
|
// .apply(new FancyTooltipAugment<>("roadmap"));
|
||||||
b.apply(struc -> {
|
// b.apply(struc -> {
|
||||||
AppFont.setSize(struc.get(), 2);
|
// AppFont.setSize(struc.get(), 2);
|
||||||
});
|
// });
|
||||||
vbox.getChildren().add(b.createRegion());
|
// vbox.getChildren().add(b.createRegion());
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -345,7 +345,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||||
|
|
||||||
if (wrapper.getEntry().getProvider() != null && wrapper.getEntry().getProvider().canMoveCategories()) {
|
if (wrapper.getEntry().getProvider() != null && wrapper.getEntry().getProvider().canMoveCategories()) {
|
||||||
var move = new Menu(AppI18n.get("moveTo"), new FontIcon("mdi2f-folder-move-outline"));
|
var move = new Menu(AppI18n.get("moveTo"), new FontIcon("mdi2f-folder-move-outline"));
|
||||||
StoreViewState.get().getSortedCategories(wrapper.getCategory().getValue()).forEach(storeCategoryWrapper -> {
|
StoreViewState.get().getSortedCategories(wrapper.getCategory().getValue().getRoot()).forEach(storeCategoryWrapper -> {
|
||||||
MenuItem m = new MenuItem(storeCategoryWrapper.getName());
|
MenuItem m = new MenuItem(storeCategoryWrapper.getName());
|
||||||
m.setOnAction(event -> {
|
m.setOnAction(event -> {
|
||||||
wrapper.moveTo(storeCategoryWrapper.getCategory());
|
wrapper.moveTo(storeCategoryWrapper.getCategory());
|
||||||
|
|
|
@ -46,7 +46,7 @@ public class StoreSection {
|
||||||
if (wrapper != null) {
|
if (wrapper != null) {
|
||||||
this.showDetails = Bindings.createBooleanBinding(
|
this.showDetails = Bindings.createBooleanBinding(
|
||||||
() -> {
|
() -> {
|
||||||
return wrapper.getExpanded().get() || allChildren.size() == 0;
|
return wrapper.getExpanded().get() || allChildren.isEmpty();
|
||||||
},
|
},
|
||||||
wrapper.getExpanded(),
|
wrapper.getExpanded(),
|
||||||
allChildren);
|
allChildren);
|
||||||
|
@ -63,20 +63,19 @@ public class StoreSection {
|
||||||
|
|
||||||
var c = Comparator.<StoreSection>comparingInt(
|
var c = Comparator.<StoreSection>comparingInt(
|
||||||
value -> value.getWrapper().getEntry().getValidity().isUsable() ? -1 : 1);
|
value -> value.getWrapper().getEntry().getValidity().isUsable() ? -1 : 1);
|
||||||
var mapped = BindingsHelper.mappedBinding(category, storeCategoryWrapper -> storeCategoryWrapper.getSortMode());
|
var mappedSortMode = BindingsHelper.mappedBinding(category, storeCategoryWrapper -> storeCategoryWrapper != null ? storeCategoryWrapper.getSortMode() : null);
|
||||||
return BindingsHelper.orderedContentBinding(
|
return BindingsHelper.orderedContentBinding(
|
||||||
list,
|
list,
|
||||||
(o1, o2) -> {
|
(o1, o2) -> {
|
||||||
var current = category.getValue();
|
var current = mappedSortMode.getValue();
|
||||||
if (current != null) {
|
if (current != null) {
|
||||||
return c.thenComparing(current.getSortMode().getValue().comparator())
|
return c.thenComparing(current.comparator())
|
||||||
.compare(o1, o2);
|
.compare(o1, o2);
|
||||||
} else {
|
} else {
|
||||||
return c.compare(o1, o2);
|
return c.compare(o1, o2);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
category,
|
mappedSortMode);
|
||||||
mapped);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static StoreSection createTopLevel(
|
public static StoreSection createTopLevel(
|
||||||
|
@ -118,10 +117,12 @@ public class StoreSection {
|
||||||
}
|
}
|
||||||
|
|
||||||
var allChildren = BindingsHelper.filteredContentBinding(all, other -> {
|
var allChildren = BindingsHelper.filteredContentBinding(all, other -> {
|
||||||
|
// Legacy implementation that does not use caches. Use for testing
|
||||||
// if (true) return DataStorage.get()
|
// if (true) return DataStorage.get()
|
||||||
// .getDisplayParent(other.getEntry())
|
// .getDisplayParent(other.getEntry())
|
||||||
// .map(found -> found.equals(e.getEntry()))
|
// .map(found -> found.equals(e.getEntry()))
|
||||||
// .orElse(false);
|
// .orElse(false);
|
||||||
|
|
||||||
// This check is fast as the children are cached in the storage
|
// This check is fast as the children are cached in the storage
|
||||||
return DataStorage.get().getStoreChildren(e.getEntry()).contains(other.getEntry());
|
return DataStorage.get().getStoreChildren(e.getEntry()).contains(other.getEntry());
|
||||||
});
|
});
|
||||||
|
@ -131,8 +132,12 @@ public class StoreSection {
|
||||||
var filtered = BindingsHelper.filteredContentBinding(
|
var filtered = BindingsHelper.filteredContentBinding(
|
||||||
ordered,
|
ordered,
|
||||||
section -> {
|
section -> {
|
||||||
return (filterString == null || section.shouldShow(filterString.get()))
|
var showFilter = filterString == null || section.shouldShow(filterString.get());
|
||||||
&& section.anyMatches(entryFilter);
|
var matchesSelector = section.anyMatches(entryFilter);
|
||||||
|
var sameCategory = category == null || category.getValue() == null || category.getValue().contains(section.getWrapper().getEntry());
|
||||||
|
// If this entry is already shown as root due to a different category than parent, don't show it again here
|
||||||
|
var notRoot = !DataStorage.get().isRootEntry(section.getWrapper().getEntry());
|
||||||
|
return showFilter && matchesSelector && sameCategory && notRoot;
|
||||||
},
|
},
|
||||||
category,
|
category,
|
||||||
filterString);
|
filterString);
|
||||||
|
|
|
@ -57,6 +57,7 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
|
||||||
struc.get().setAlignment(Pos.CENTER_LEFT);
|
struc.get().setAlignment(Pos.CENTER_LEFT);
|
||||||
})
|
})
|
||||||
.grow(true, false)
|
.grow(true, false)
|
||||||
|
.apply(struc -> struc.get().setMnemonicParsing(false))
|
||||||
.styleClass("item");
|
.styleClass("item");
|
||||||
augment.accept(section, root);
|
augment.accept(section, root);
|
||||||
|
|
||||||
|
@ -81,7 +82,6 @@ public class StoreSectionMiniComp extends Comp<CompStructure<VBox>> {
|
||||||
List<Comp<?>> topEntryList = List.of(button, root);
|
List<Comp<?>> topEntryList = List.of(button, root);
|
||||||
list.add(new HorizontalComp(topEntryList)
|
list.add(new HorizontalComp(topEntryList)
|
||||||
.apply(struc -> struc.get().setFillHeight(true)));
|
.apply(struc -> struc.get().setFillHeight(true)));
|
||||||
list.add(Comp.separator().visible(expanded));
|
|
||||||
} else {
|
} else {
|
||||||
expanded = new SimpleBooleanProperty(true);
|
expanded = new SimpleBooleanProperty(true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ public class App extends Application {
|
||||||
// This is necessary in case XPipe was started through a script as it will have no icon otherwise
|
// This is necessary in case XPipe was started through a script as it will have no icon otherwise
|
||||||
if (OsType.getLocal().equals(OsType.MACOS) && AppProperties.get().isDeveloperMode() && AppLogs.get().isWriteToSysout()) {
|
if (OsType.getLocal().equals(OsType.MACOS) && AppProperties.get().isDeveloperMode() && AppLogs.get().isWriteToSysout()) {
|
||||||
try {
|
try {
|
||||||
var iconUrl = Main.class.getResourceAsStream("resources/img/logo/logo_128x128.png");
|
var iconUrl = Main.class.getResourceAsStream("resources/img/logo/logo_macos_128x128.png");
|
||||||
if (iconUrl != null) {
|
if (iconUrl != null) {
|
||||||
var awtIcon = ImageIO.read(iconUrl);
|
var awtIcon = ImageIO.read(iconUrl);
|
||||||
Taskbar.getTaskbar().setIconImage(awtIcon);
|
Taskbar.getTaskbar().setIconImage(awtIcon);
|
||||||
|
|
|
@ -174,8 +174,8 @@ public class AppMainWindow {
|
||||||
stage.setWidth(1280);
|
stage.setWidth(1280);
|
||||||
stage.setHeight(720);
|
stage.setHeight(720);
|
||||||
} else {
|
} else {
|
||||||
stage.setX(310);
|
stage.setX(312);
|
||||||
stage.setY(178);
|
stage.setY(149);
|
||||||
stage.setWidth(1296);
|
stage.setWidth(1296);
|
||||||
stage.setHeight(759);
|
stage.setHeight(759);
|
||||||
}
|
}
|
||||||
|
|
|
@ -200,7 +200,7 @@ public class AppTheme {
|
||||||
static Theme getDefaultLightTheme() {
|
static Theme getDefaultLightTheme() {
|
||||||
return switch (OsType.getLocal()) {
|
return switch (OsType.getLocal()) {
|
||||||
case OsType.Windows windows -> PRIMER_LIGHT;
|
case OsType.Windows windows -> PRIMER_LIGHT;
|
||||||
case OsType.Linux linux -> NORD_LIGHT;
|
case OsType.Linux linux -> PRIMER_LIGHT;
|
||||||
case OsType.MacOs macOs -> CUPERTINO_LIGHT;
|
case OsType.MacOs macOs -> CUPERTINO_LIGHT;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -208,7 +208,7 @@ public class AppTheme {
|
||||||
static Theme getDefaultDarkTheme() {
|
static Theme getDefaultDarkTheme() {
|
||||||
return switch (OsType.getLocal()) {
|
return switch (OsType.getLocal()) {
|
||||||
case OsType.Windows windows -> PRIMER_DARK;
|
case OsType.Windows windows -> PRIMER_DARK;
|
||||||
case OsType.Linux linux -> NORD_DARK;
|
case OsType.Linux linux -> PRIMER_DARK;
|
||||||
case OsType.MacOs macOs -> CUPERTINO_DARK;
|
case OsType.MacOs macOs -> CUPERTINO_DARK;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ public class AppTrayIcon {
|
||||||
var image = switch (OsType.getLocal()) {
|
var image = switch (OsType.getLocal()) {
|
||||||
case OsType.Windows windows -> "img/logo/logo_16x16.png";
|
case OsType.Windows windows -> "img/logo/logo_16x16.png";
|
||||||
case OsType.Linux linux -> "img/logo/logo_24x24.png";
|
case OsType.Linux linux -> "img/logo/logo_24x24.png";
|
||||||
case OsType.MacOs macOs -> "img/logo/logo_24x24.png";
|
case OsType.MacOs macOs -> "img/logo/logo_macos_tray_24x24.png";
|
||||||
};
|
};
|
||||||
var url = AppResources.getResourceURL(AppResources.XPIPE_MODULE, image).orElseThrow();
|
var url = AppResources.getResourceURL(AppResources.XPIPE_MODULE, image).orElseThrow();
|
||||||
|
|
||||||
|
|
|
@ -69,6 +69,7 @@ public class BaseMode extends OperationMode {
|
||||||
DataStorage.reset();
|
DataStorage.reset();
|
||||||
AppPrefs.reset();
|
AppPrefs.reset();
|
||||||
AppExtensionManager.reset();
|
AppExtensionManager.reset();
|
||||||
|
AppDataLock.unlock();
|
||||||
// Shut down socket server last to keep a non-daemon thread running
|
// Shut down socket server last to keep a non-daemon thread running
|
||||||
AppSocketServer.reset();
|
AppSocketServer.reset();
|
||||||
TrackEvent.info("mode", "Background mode shutdown finished");
|
TrackEvent.info("mode", "Background mode shutdown finished");
|
||||||
|
|
|
@ -19,4 +19,8 @@ public class ExtensionException extends RuntimeException {
|
||||||
public ExtensionException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
public ExtensionException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||||
super(message, cause, enableSuppression, writableStackTrace);
|
super(message, cause, enableSuppression, writableStackTrace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ExtensionException corrupt(String message) {
|
||||||
|
return new ExtensionException(message + ". Is the installation corrupt?");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import io.xpipe.app.core.AppState;
|
||||||
import io.xpipe.app.core.mode.OperationMode;
|
import io.xpipe.app.core.mode.OperationMode;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.update.XPipeDistributionType;
|
import io.xpipe.app.update.XPipeDistributionType;
|
||||||
|
import io.xpipe.app.util.LicenseProvider;
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
@ -150,6 +151,7 @@ public class SentryErrorHandler implements ErrorHandler {
|
||||||
atts.forEach(attachment -> s.addAttachment(attachment));
|
atts.forEach(attachment -> s.addAttachment(attachment));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.setTag("hasLicense", String.valueOf(LicenseProvider.get().hasLicense()));
|
||||||
s.setTag("updatesEnabled", AppPrefs.get() != null ? AppPrefs.get().automaticallyUpdate().getValue().toString() : "unknown");
|
s.setTag("updatesEnabled", AppPrefs.get() != null ? AppPrefs.get().automaticallyUpdate().getValue().toString() : "unknown");
|
||||||
s.setTag("initError", String.valueOf(OperationMode.isInStartup()));
|
s.setTag("initError", String.valueOf(OperationMode.isInStartup()));
|
||||||
s.setTag(
|
s.setTag(
|
||||||
|
|
|
@ -359,6 +359,9 @@ public class AppPrefs {
|
||||||
private AppPreferencesFx preferencesFx;
|
private AppPreferencesFx preferencesFx;
|
||||||
private boolean controlsSetup;
|
private boolean controlsSetup;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final Set<Field<?>> proRequiredSettings = new HashSet<>();
|
||||||
|
|
||||||
private AppPrefs() {
|
private AppPrefs() {
|
||||||
try {
|
try {
|
||||||
preferencesFx = createPreferences();
|
preferencesFx = createPreferences();
|
||||||
|
|
|
@ -79,7 +79,7 @@ public class CustomFormRenderer extends PreferencesFxFormRenderer {
|
||||||
c.getFieldLabel().setMaxHeight(AppFont.getPixelSize(1));
|
c.getFieldLabel().setMaxHeight(AppFont.getPixelSize(1));
|
||||||
c.getFieldLabel().textProperty().unbind();
|
c.getFieldLabel().textProperty().unbind();
|
||||||
c.getFieldLabel().textProperty().bind(Bindings.createStringBinding(() -> {
|
c.getFieldLabel().textProperty().bind(Bindings.createStringBinding(() -> {
|
||||||
return f.labelProperty().get() + (f.isEditable() ? "" : " (Pro)");
|
return f.labelProperty().get() + (AppPrefs.get().getProRequiredSettings().contains(f) ? " (Pro)" : "");
|
||||||
}, f.labelProperty()));
|
}, f.labelProperty()));
|
||||||
grid.add(c.getFieldLabel(), 0, i + rowAmount);
|
grid.add(c.getFieldLabel(), 0, i + rowAmount);
|
||||||
|
|
||||||
|
|
|
@ -177,13 +177,21 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Optional<Path> determineInstallation() {
|
protected Optional<Path> determineInstallation() {
|
||||||
Optional<String> launcherDir;
|
var perUser = WindowsRegistry.readString(
|
||||||
launcherDir = WindowsRegistry.readString(
|
|
||||||
WindowsRegistry.HKEY_CURRENT_USER,
|
WindowsRegistry.HKEY_CURRENT_USER,
|
||||||
"SOFTWARE\\71445fac-d6ef-5436-9da7-5a323762d7f5",
|
"SOFTWARE\\71445fac-d6ef-5436-9da7-5a323762d7f5",
|
||||||
"InstallLocation")
|
"InstallLocation")
|
||||||
.map(p -> p + "\\Tabby.exe");
|
.map(p -> p + "\\Tabby.exe").map(Path::of);
|
||||||
return launcherDir.map(Path::of);
|
if (perUser.isPresent()) {
|
||||||
|
return perUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
var systemWide = WindowsRegistry.readString(
|
||||||
|
WindowsRegistry.HKEY_LOCAL_MACHINE,
|
||||||
|
"SOFTWARE\\71445fac-d6ef-5436-9da7-5a323762d7f5",
|
||||||
|
"InstallLocation")
|
||||||
|
.map(p -> p + "\\Tabby.exe").map(Path::of);
|
||||||
|
return systemWide;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,8 @@ import javafx.scene.layout.Region;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static io.xpipe.app.prefs.AppPrefs.group;
|
import static io.xpipe.app.prefs.AppPrefs.group;
|
||||||
|
|
||||||
public class VaultCategory extends AppPrefsCategory {
|
public class VaultCategory extends AppPrefsCategory {
|
||||||
|
@ -69,6 +71,9 @@ public class VaultCategory extends AppPrefsCategory {
|
||||||
c.setPrefWidth(1000);
|
c.setPrefWidth(1000);
|
||||||
return c;
|
return c;
|
||||||
});
|
});
|
||||||
|
if (!pro) {
|
||||||
|
prefs.getProRequiredSettings().addAll(List.of(enable, remote));
|
||||||
|
}
|
||||||
return Category.of(
|
return Category.of(
|
||||||
"vault",
|
"vault",
|
||||||
group(
|
group(
|
||||||
|
|
|
@ -7,13 +7,13 @@ import lombok.Getter;
|
||||||
@Getter
|
@Getter
|
||||||
public enum DataStoreColor {
|
public enum DataStoreColor {
|
||||||
@JsonProperty("red")
|
@JsonProperty("red")
|
||||||
RED("red", "\uD83D\uDFE5", Color.BLUE),
|
RED("red", "\uD83D\uDD34", Color.RED),
|
||||||
|
|
||||||
@JsonProperty("green")
|
@JsonProperty("green")
|
||||||
GREEN("green", "\uD83D\uDFE9", Color.BLUE),
|
GREEN("green", "\uD83D\uDFE2", Color.GREEN),
|
||||||
|
|
||||||
@JsonProperty("yellow")
|
@JsonProperty("yellow")
|
||||||
YELLOW("yellow", "\uD83D\uDFE8", Color.BLUE),
|
YELLOW("yellow", "\uD83D\uDFE1", Color.YELLOW),
|
||||||
|
|
||||||
@JsonProperty("blue")
|
@JsonProperty("blue")
|
||||||
BLUE("blue", "\uD83D\uDD35", Color.BLUE);
|
BLUE("blue", "\uD83D\uDD35", Color.BLUE);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package io.xpipe.app.util;
|
package io.xpipe.app.util;
|
||||||
|
|
||||||
|
import io.xpipe.app.ext.ExtensionException;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.storage.GitStorageHandler;
|
import io.xpipe.app.storage.GitStorageHandler;
|
||||||
import io.xpipe.core.process.ShellControl;
|
import io.xpipe.core.process.ShellControl;
|
||||||
|
@ -21,7 +22,7 @@ public abstract class LicenseProvider {
|
||||||
public void init(ModuleLayer layer) {
|
public void init(ModuleLayer layer) {
|
||||||
INSTANCE = ServiceLoader.load(layer, LicenseProvider.class).stream()
|
INSTANCE = ServiceLoader.load(layer, LicenseProvider.class).stream()
|
||||||
.map(ServiceLoader.Provider::get)
|
.map(ServiceLoader.Provider::get)
|
||||||
.findFirst().orElseThrow();
|
.findFirst().orElseThrow(() -> ExtensionException.corrupt("Missing license provider."));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -35,6 +36,8 @@ public abstract class LicenseProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract boolean hasLicense();
|
||||||
|
|
||||||
public abstract LicensedFeature getFeature(String id);
|
public abstract LicensedFeature getFeature(String id);
|
||||||
|
|
||||||
public abstract void handleShellControl(ShellControl sc);
|
public abstract void handleShellControl(ShellControl sc);
|
||||||
|
|
|
@ -41,6 +41,10 @@ public class ScanAlert {
|
||||||
private static void showForShellStore(DataStoreEntry initial) {
|
private static void showForShellStore(DataStoreEntry initial) {
|
||||||
show(initial, (DataStoreEntry entry) -> {
|
show(initial, (DataStoreEntry entry) -> {
|
||||||
try (var sc = ((ShellStore) entry.getStore()).control().start()) {
|
try (var sc = ((ShellStore) entry.getStore()).control().start()) {
|
||||||
|
if (!sc.getShellDialect().isSupportedShell()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var providers = ScanProvider.getAll();
|
var providers = ScanProvider.getAll();
|
||||||
var applicable = new ArrayList<ScanProvider.ScanOperation>();
|
var applicable = new ArrayList<ScanProvider.ScanOperation>();
|
||||||
for (ScanProvider scanProvider : providers) {
|
for (ScanProvider scanProvider : providers) {
|
||||||
|
|
After Width: | Height: | Size: 7.6 KiB |
After Width: | Height: | Size: 468 B |
|
@ -15,10 +15,8 @@
|
||||||
.bookmark-list .store-section-mini-comp .item:selected {
|
.bookmark-list .store-section-mini-comp .item:selected {
|
||||||
-fx-border-width: 1px;
|
-fx-border-width: 1px;
|
||||||
-fx-border-radius: 4px;
|
-fx-border-radius: 4px;
|
||||||
-fx-border-insets: 0px 10px 0 0;
|
-fx-background-color: transparent, -color-accent-emphasis, -color-bg-overlay;
|
||||||
-fx-border-color: -color-accent-muted;
|
|
||||||
-fx-background-color: -color-bg-default;
|
|
||||||
-fx-background-radius: 4px;
|
-fx-background-radius: 4px;
|
||||||
-fx-background-insets: 0px 10px 0 0;
|
-fx-background-insets: 1 11 1 1, 2 12 2 2, 3 13 3 3;
|
||||||
-fx-padding: 0.25em 0.4em 0.25em 0.4em;
|
-fx-padding: 0.25em 0.4em 0.25em 0.4em;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,14 @@
|
||||||
-fx-padding: 1em;
|
-fx-padding: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.browser .welcome .button {
|
||||||
|
-fx-border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.browser .welcome .button:hover {
|
||||||
|
-fx-background-color: -color-neutral-muted;
|
||||||
|
}
|
||||||
|
|
||||||
.browser .tile > * {
|
.browser .tile > * {
|
||||||
-fx-padding: 0.6em 0 0.6em 0;
|
-fx-padding: 0.6em 0 0.6em 0;
|
||||||
}
|
}
|
||||||
|
@ -163,9 +171,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.browser .context-menu {
|
.browser .context-menu {
|
||||||
-fx-padding: 0;
|
-fx-padding: 12 0 12 0;
|
||||||
-fx-background-radius: 1px;
|
-fx-background-radius: 8px;
|
||||||
-fx-border-color: -color-neutral-muted;
|
-fx-border-radius: 8px;
|
||||||
|
-fx-border-color: -color-border-default;
|
||||||
}
|
}
|
||||||
|
|
||||||
.browser .tab-pane {
|
.browser .tab-pane {
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
.context-menu {
|
|
||||||
-fx-background-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-menu-item {
|
.header-menu-item {
|
||||||
-fx-background-color: white;
|
-fx-background-color: white;
|
||||||
|
@ -45,4 +42,6 @@
|
||||||
|
|
||||||
.context-menu * .context-menu {
|
.context-menu * .context-menu {
|
||||||
-fx-padding: 0;
|
-fx-padding: 0;
|
||||||
|
-fx-background-radius: 0;
|
||||||
|
-fx-border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,10 @@
|
||||||
-fx-background-radius: 0;
|
-fx-background-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.store-entry-comp:hover:armed {
|
||||||
|
-fx-background-color: derive(-color-neutral-muted, 25%);
|
||||||
|
}
|
||||||
|
|
||||||
.store-entry-comp:hover {
|
.store-entry-comp:hover {
|
||||||
-fx-background-color: -color-neutral-muted;
|
-fx-background-color: -color-neutral-muted;
|
||||||
}
|
}
|
||||||
|
@ -116,11 +120,17 @@
|
||||||
-fx-effect: dropshadow(three-pass-box, -color-shadow-default, 2px, 0.5, 0, 1);
|
-fx-effect: dropshadow(three-pass-box, -color-shadow-default, 2px, 0.5, 0, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.store-entry-section-comp {
|
.store-entry-section-comp:root {
|
||||||
-fx-border-radius: 4px;
|
-fx-border-radius: 4px;
|
||||||
-fx-background-radius: 4px;
|
-fx-background-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.store-entry-section-comp:sub:expanded {
|
||||||
|
-fx-border-radius: 4 0 0 4;
|
||||||
|
-fx-border-width: 1 0 1 1;
|
||||||
|
-fx-background-radius: 4 0 0 4;
|
||||||
|
}
|
||||||
|
|
||||||
.root.nord .store-entry-section-comp {
|
.root.nord .store-entry-section-comp {
|
||||||
-fx-border-radius: 0;
|
-fx-border-radius: 0;
|
||||||
-fx-background-radius: 0;
|
-fx-background-radius: 0;
|
||||||
|
|
|
@ -45,7 +45,7 @@ project.ext {
|
||||||
kebapProductName = isStage ? 'xpipe-ptb' : 'xpipe'
|
kebapProductName = isStage ? 'xpipe-ptb' : 'xpipe'
|
||||||
publisher = 'XPipe UG (haftungsbeschränkt)'
|
publisher = 'XPipe UG (haftungsbeschränkt)'
|
||||||
shortDescription = 'Your entire server infrastructure at your fingertips'
|
shortDescription = 'Your entire server infrastructure at your fingertips'
|
||||||
longDescription = 'XPipe is a new type of shell connection hub and remote file manager that allows you to access your entire sever infrastructure from your local machine. It works on top of your installed command-line programs that you normally use to connect and does not require any setup on your remote systems.'
|
longDescription = 'XPipe is a new type of shell connection hub and remote file manager that allows you to access your entire server infrastructure from your local machine. It works on top of your installed command-line programs that you normally use to connect and does not require any setup on your remote systems.'
|
||||||
website = 'https://xpipe.io'
|
website = 'https://xpipe.io'
|
||||||
sourceWebsite = 'https://github.com/xpipe-io/xpipe'
|
sourceWebsite = 'https://github.com/xpipe-io/xpipe'
|
||||||
authors = 'Christopher Schnick'
|
authors = 'Christopher Schnick'
|
||||||
|
|
35
core/src/main/java/io/xpipe/core/process/ScriptSnippet.java
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
package io.xpipe.core.process;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
public interface ScriptSnippet {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public static enum ExecutionType {
|
||||||
|
@JsonProperty("dumbOnly")
|
||||||
|
DUMB_ONLY("dumbOnly"),
|
||||||
|
@JsonProperty("terminalOnly")
|
||||||
|
TERMINAL_ONLY("terminalOnly"),
|
||||||
|
@JsonProperty("both")
|
||||||
|
BOTH("both");
|
||||||
|
|
||||||
|
private final String id;
|
||||||
|
|
||||||
|
ExecutionType(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean runInDumb() {
|
||||||
|
return this == DUMB_ONLY || this == BOTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean runInTerminal() {
|
||||||
|
return this == TERMINAL_ONLY || this == BOTH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String content(ShellControl shellControl);
|
||||||
|
|
||||||
|
ExecutionType executionType();
|
||||||
|
}
|
|
@ -15,6 +15,8 @@ import java.util.function.Predicate;
|
||||||
|
|
||||||
public interface ShellControl extends ProcessControl {
|
public interface ShellControl extends ProcessControl {
|
||||||
|
|
||||||
|
List<ScriptSnippet> getInitCommands();
|
||||||
|
|
||||||
ShellControl withTargetTerminalShellDialect(ShellDialect d);
|
ShellControl withTargetTerminalShellDialect(ShellDialect d);
|
||||||
|
|
||||||
ShellDialect getTargetTerminalShellDialect();
|
ShellDialect getTargetTerminalShellDialect();
|
||||||
|
@ -153,11 +155,7 @@ public interface ShellControl extends ProcessControl {
|
||||||
}
|
}
|
||||||
ShellControl elevationPassword(FailableSupplier<SecretValue> value);
|
ShellControl elevationPassword(FailableSupplier<SecretValue> value);
|
||||||
|
|
||||||
ShellControl initWith(String cmds);
|
ShellControl initWith(ScriptSnippet snippet);
|
||||||
|
|
||||||
ShellControl initWithDumb(String cmds);
|
|
||||||
|
|
||||||
ShellControl initWithTerminal(String cmds);
|
|
||||||
|
|
||||||
ShellControl additionalTimeout(int ms);
|
ShellControl additionalTimeout(int ms);
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,10 @@ public interface ShellDialect {
|
||||||
.collect(Collectors.joining(" "));
|
.collect(Collectors.joining(" "));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default boolean isSupportedShell() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
default boolean isSelectable() {
|
default boolean isSelectable() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -40,7 +44,7 @@ public interface ShellDialect {
|
||||||
|
|
||||||
String getCatchAllVariable();
|
String getCatchAllVariable();
|
||||||
|
|
||||||
CommandControl queryVersion(ShellControl shellControl);
|
String queryVersion(ShellControl shellControl) throws Exception;
|
||||||
|
|
||||||
CommandControl prepareUserTempDirectory(ShellControl shellControl, String directory);
|
CommandControl prepareUserTempDirectory(ShellControl shellControl, String directory);
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,8 @@ public class ShellDialects {
|
||||||
public static ShellDialect ZSH;
|
public static ShellDialect ZSH;
|
||||||
public static ShellDialect CSH;
|
public static ShellDialect CSH;
|
||||||
public static ShellDialect FISH;
|
public static ShellDialect FISH;
|
||||||
|
public static ShellDialect UNSUPPORTED;
|
||||||
|
public static ShellDialect CISCO;
|
||||||
|
|
||||||
public static class Loader implements ModuleLayerLoader {
|
public static class Loader implements ModuleLayerLoader {
|
||||||
|
|
||||||
|
@ -40,6 +42,8 @@ public class ShellDialects {
|
||||||
CSH = byName("csh");
|
CSH = byName("csh");
|
||||||
ASH = byName("ash");
|
ASH = byName("ash");
|
||||||
SH = byName("sh");
|
SH = byName("sh");
|
||||||
|
UNSUPPORTED = byName("unsupported");
|
||||||
|
CISCO = byName("cisco");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
package io.xpipe.core.process;
|
||||||
|
|
||||||
|
import lombok.NonNull;
|
||||||
|
|
||||||
|
public class SimpleScriptSnippet implements ScriptSnippet {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final String content;
|
||||||
|
@NonNull
|
||||||
|
private final ExecutionType executionType;
|
||||||
|
|
||||||
|
public SimpleScriptSnippet(@NonNull String content, @NonNull ExecutionType executionType) {
|
||||||
|
this.content = content;
|
||||||
|
this.executionType = executionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String content(ShellControl shellControl) {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExecutionType executionType() {
|
||||||
|
return executionType;
|
||||||
|
}
|
||||||
|
}
|
21
dist/changelogs/1.7.3.md
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
## Changes in 1.7.3
|
||||||
|
|
||||||
|
- Use newly created macOS app icons that better fit in with the general macOS aesthetic
|
||||||
|
- Fix connection freezing when sudo askpass dialog was cancelled by now just executing commands as normal user
|
||||||
|
- Fix Tabby installation not being detected on Windows if it was system-wide instead of per-user
|
||||||
|
- Fix some settings values being incorrectly labelled as professional-only
|
||||||
|
- Fix application restart for license activation not working
|
||||||
|
- Fix open browser tab list being broken when reordering tabs by dragging
|
||||||
|
- Fix browser welcome screen connection list jumping around
|
||||||
|
- Fix connection sorting sometimes not working
|
||||||
|
- Fix connections being duplicated in all connections overview
|
||||||
|
- Fix move to category functionality being broken
|
||||||
|
- Fix bring scripts functionality throwing errors on script setup
|
||||||
|
- Fix scripts possibly being called multiple times when set as default and as dependencies
|
||||||
|
- Improve scripts help documentation
|
||||||
|
- Improve styling in some areas
|
||||||
|
|
||||||
|
## Previous changes in 1.7
|
||||||
|
|
||||||
|
- https://github.com/xpipe-io/xpipe/releases/tag/1.7.2
|
||||||
|
- https://github.com/xpipe-io/xpipe/releases/tag/1.7.1
|
BIN
dist/logo/logo.icns
vendored
BIN
dist/logo/logo.iconset/icon_128x128.png
vendored
Before Width: | Height: | Size: 7 KiB After Width: | Height: | Size: 7.6 KiB |
BIN
dist/logo/logo.iconset/icon_128x128@2.png
vendored
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 20 KiB |
BIN
dist/logo/logo.iconset/icon_16x16.png
vendored
Before Width: | Height: | Size: 647 B After Width: | Height: | Size: 678 B |
BIN
dist/logo/logo.iconset/icon_16x16@2.png
vendored
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.4 KiB |
BIN
dist/logo/logo.iconset/icon_256x256.png
vendored
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 20 KiB |
BIN
dist/logo/logo.iconset/icon_256x256@2.png
vendored
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 48 KiB |
BIN
dist/logo/logo.iconset/icon_32x32.png
vendored
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.4 KiB |
BIN
dist/logo/logo.iconset/icon_32x32@2.png
vendored
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 3.2 KiB |
BIN
dist/logo/logo.iconset/icon_512x512.png
vendored
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 48 KiB |
BIN
dist/logo/logo.iconset/icon_512x512@2.png
vendored
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 117 KiB |
|
@ -1,9 +1,13 @@
|
||||||
package io.xpipe.ext.base.script;
|
package io.xpipe.ext.base.script;
|
||||||
|
|
||||||
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
|
import io.xpipe.app.storage.DataStoreEntry;
|
||||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||||
import io.xpipe.app.util.Validators;
|
import io.xpipe.app.util.Validators;
|
||||||
|
import io.xpipe.core.process.ScriptSnippet;
|
||||||
import io.xpipe.core.process.ShellControl;
|
import io.xpipe.core.process.ShellControl;
|
||||||
|
import io.xpipe.core.process.SimpleScriptSnippet;
|
||||||
import io.xpipe.core.store.DataStore;
|
import io.xpipe.core.store.DataStore;
|
||||||
import io.xpipe.core.store.DataStoreState;
|
import io.xpipe.core.store.DataStoreState;
|
||||||
import io.xpipe.core.store.FileNames;
|
import io.xpipe.core.store.FileNames;
|
||||||
|
@ -18,8 +22,6 @@ import lombok.extern.jackson.Jacksonized;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@SuperBuilder
|
@SuperBuilder
|
||||||
@Getter
|
@Getter
|
||||||
|
@ -31,56 +33,67 @@ public abstract class ScriptStore extends JacksonizedValue implements DataStore,
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ShellControl controlWithScripts(ShellControl pc, List<DataStoreEntryRef<ScriptStore>> initScripts, List<DataStoreEntryRef<ScriptStore>> bringScripts) {
|
public static ShellControl controlWithScripts(ShellControl pc, List<DataStoreEntryRef<ScriptStore>> initScripts, List<DataStoreEntryRef<ScriptStore>> bringScripts) {
|
||||||
pc.onInit(shellControl -> {
|
|
||||||
var initFlattened = flatten(initScripts);
|
var initFlattened = flatten(initScripts);
|
||||||
var scripts = initFlattened.stream()
|
|
||||||
.map(simpleScriptStore -> simpleScriptStore.prepareDumbScript(shellControl))
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.collect(Collectors.joining("\n"));
|
|
||||||
if (!scripts.isBlank()) {
|
|
||||||
shellControl.executeSimpleBooleanCommand(scripts);
|
|
||||||
}
|
|
||||||
|
|
||||||
var terminalCommands = initFlattened.stream()
|
|
||||||
.map(simpleScriptStore -> simpleScriptStore.prepareTerminalScript(shellControl))
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.collect(Collectors.joining("\n"));
|
|
||||||
if (!terminalCommands.isBlank()) {
|
|
||||||
shellControl.initWithTerminal(terminalCommands);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
pc.onInit(shellControl -> {
|
|
||||||
var bringFlattened = flatten(bringScripts);
|
var bringFlattened = flatten(bringScripts);
|
||||||
|
|
||||||
|
pc.onInit(shellControl -> {
|
||||||
|
passInitScripts(pc, initFlattened);
|
||||||
|
|
||||||
var dir = initScriptsDirectory(shellControl, bringFlattened);
|
var dir = initScriptsDirectory(shellControl, bringFlattened);
|
||||||
if (dir != null) {
|
if (dir != null) {
|
||||||
shellControl.initWithTerminal(shellControl.getShellDialect().appendToPathVariableCommand(dir));
|
shellControl.initWith(new SimpleScriptSnippet(shellControl.getShellDialect().appendToPathVariableCommand(dir), ScriptSnippet.ExecutionType.TERMINAL_ONLY));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return pc;
|
return pc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void passInitScripts(ShellControl pc, List<SimpleScriptStore> scriptStores) throws Exception {
|
||||||
|
scriptStores.forEach(simpleScriptStore -> {
|
||||||
|
if (pc.getInitCommands().contains(simpleScriptStore)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!simpleScriptStore.getMinimumDialect().isCompatibleTo(pc.getShellDialect())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pc.initWith(simpleScriptStore);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private static String initScriptsDirectory(ShellControl proc, List<SimpleScriptStore> scriptStores) throws Exception {
|
private static String initScriptsDirectory(ShellControl proc, List<SimpleScriptStore> scriptStores) throws Exception {
|
||||||
if (scriptStores.size() == 0) {
|
if (scriptStores.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var refs = scriptStores.stream().map(scriptStore -> {
|
var applicable = scriptStores.stream().filter(simpleScriptStore -> simpleScriptStore.getMinimumDialect().isCompatibleTo(proc.getShellDialect())).toList();
|
||||||
|
if (applicable.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var refs = applicable.stream().map(scriptStore -> {
|
||||||
return DataStorage.get().getStoreEntries().stream().filter(dataStoreEntry -> dataStoreEntry.getStore() == scriptStore).findFirst().orElseThrow().<SimpleScriptStore>ref();
|
return DataStorage.get().getStoreEntries().stream().filter(dataStoreEntry -> dataStoreEntry.getStore() == scriptStore).findFirst().orElseThrow().<SimpleScriptStore>ref();
|
||||||
}).toList();
|
}).toList();
|
||||||
var hash = refs.stream().mapToInt(value -> value.get().getName().hashCode() + value.getStore().hashCode()).sum();
|
var hash = refs.stream().mapToInt(value -> value.get().getName().hashCode() + value.getStore().hashCode()).sum();
|
||||||
var xpipeHome = XPipeInstallation.getDataDir(proc);
|
var xpipeHome = XPipeInstallation.getDataDir(proc);
|
||||||
var targetDir = FileNames.join(xpipeHome, "scripts");
|
var targetDir = FileNames.join(xpipeHome, "scripts", proc.getShellDialect().getId());
|
||||||
var hashFile = FileNames.join(targetDir, "hash");
|
var hashFile = FileNames.join(targetDir, "hash");
|
||||||
var d = proc.getShellDialect();
|
var d = proc.getShellDialect();
|
||||||
if (d.createFileExistsCommand(proc, hashFile).executeAndCheck()) {
|
if (d.createFileExistsCommand(proc, hashFile).executeAndCheck()) {
|
||||||
var read = d.getFileReadCommand(proc, hashFile).readStdoutOrThrow();
|
var read = d.getFileReadCommand(proc, hashFile).readStdoutOrThrow();
|
||||||
|
try {
|
||||||
var readHash = Integer.parseInt(read);
|
var readHash = Integer.parseInt(read);
|
||||||
if (hash == readHash) {
|
if (hash == readHash) {
|
||||||
return targetDir;
|
return targetDir;
|
||||||
}
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
ErrorEvent.fromThrowable(e).omit().handle();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (d.directoryExists(proc, targetDir).executeAndCheck()) {
|
||||||
d.deleteFileOrDirectory(proc, targetDir).execute();
|
d.deleteFileOrDirectory(proc, targetDir).execute();
|
||||||
|
}
|
||||||
proc.executeSimpleCommand(d.getMkdirsCommand(targetDir));
|
proc.executeSimpleCommand(d.getMkdirsCommand(targetDir));
|
||||||
|
|
||||||
for (DataStoreEntryRef<SimpleScriptStore> scriptStore : refs) {
|
for (DataStoreEntryRef<SimpleScriptStore> scriptStore : refs) {
|
||||||
|
@ -90,8 +103,10 @@ public abstract class ScriptStore extends JacksonizedValue implements DataStore,
|
||||||
d.createScriptTextFileWriteCommand(proc, content, scriptFile).execute();
|
d.createScriptTextFileWriteCommand(proc, content, scriptFile).execute();
|
||||||
|
|
||||||
var chmod = d.getScriptPermissionsCommand(scriptFile);
|
var chmod = d.getScriptPermissionsCommand(scriptFile);
|
||||||
|
if (chmod != null) {
|
||||||
proc.executeSimpleBooleanCommand(chmod);
|
proc.executeSimpleBooleanCommand(chmod);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
d.createTextFileWriteCommand(proc, String.valueOf(hash), hashFile).execute();
|
d.createTextFileWriteCommand(proc, String.valueOf(hash), hashFile).execute();
|
||||||
return targetDir;
|
return targetDir;
|
||||||
|
@ -101,7 +116,7 @@ public abstract class ScriptStore extends JacksonizedValue implements DataStore,
|
||||||
return DataStorage.get().getStoreEntries().stream()
|
return DataStorage.get().getStoreEntries().stream()
|
||||||
.filter(dataStoreEntry -> dataStoreEntry.getStore() instanceof ScriptStore scriptStore
|
.filter(dataStoreEntry -> dataStoreEntry.getStore() instanceof ScriptStore scriptStore
|
||||||
&& scriptStore.getState().isDefault())
|
&& scriptStore.getState().isDefault())
|
||||||
.map(e -> e.<ScriptStore>ref())
|
.map(DataStoreEntry::<ScriptStore>ref)
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +124,7 @@ public abstract class ScriptStore extends JacksonizedValue implements DataStore,
|
||||||
return DataStorage.get().getStoreEntries().stream()
|
return DataStorage.get().getStoreEntries().stream()
|
||||||
.filter(dataStoreEntry -> dataStoreEntry.getStore() instanceof ScriptStore scriptStore
|
.filter(dataStoreEntry -> dataStoreEntry.getStore() instanceof ScriptStore scriptStore
|
||||||
&& scriptStore.getState().isBringToShell())
|
&& scriptStore.getState().isBringToShell())
|
||||||
.map(e -> e.<ScriptStore>ref())
|
.map(DataStoreEntry::<ScriptStore>ref)
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,12 +170,6 @@ public abstract class ScriptStore extends JacksonizedValue implements DataStore,
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
public LinkedHashSet<SimpleScriptStore> getFlattenedScripts() {
|
|
||||||
var set = new LinkedHashSet<SimpleScriptStore>();
|
|
||||||
queryFlattenedScripts(set);
|
|
||||||
return set;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract void queryFlattenedScripts(LinkedHashSet<SimpleScriptStore> all);
|
protected abstract void queryFlattenedScripts(LinkedHashSet<SimpleScriptStore> all);
|
||||||
|
|
||||||
public abstract List<DataStoreEntryRef<ScriptStore>> getEffectiveScripts();
|
public abstract List<DataStoreEntryRef<ScriptStore>> getEffectiveScripts();
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package io.xpipe.ext.base.script;
|
package io.xpipe.ext.base.script;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||||
import io.xpipe.app.util.ScriptHelper;
|
import io.xpipe.app.util.ScriptHelper;
|
||||||
import io.xpipe.app.util.Validators;
|
import io.xpipe.app.util.Validators;
|
||||||
|
import io.xpipe.core.process.ScriptSnippet;
|
||||||
import io.xpipe.core.process.ShellControl;
|
import io.xpipe.core.process.ShellControl;
|
||||||
import io.xpipe.core.process.ShellDialect;
|
import io.xpipe.core.process.ShellDialect;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
@ -13,21 +13,14 @@ import lombok.extern.jackson.Jacksonized;
|
||||||
|
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@SuperBuilder
|
@SuperBuilder
|
||||||
@Getter
|
@Getter
|
||||||
@Jacksonized
|
@Jacksonized
|
||||||
@JsonTypeName("script")
|
@JsonTypeName("script")
|
||||||
public class SimpleScriptStore extends ScriptStore {
|
public class SimpleScriptStore extends ScriptStore implements ScriptSnippet {
|
||||||
|
|
||||||
public String prepareDumbScript(ShellControl shellControl) {
|
|
||||||
return assemble(shellControl, ExecutionType.DUMB_ONLY);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String prepareTerminalScript(ShellControl shellControl) {
|
|
||||||
return assemble(shellControl, ExecutionType.TERMINAL_ONLY);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String assemble(ShellControl shellControl, ExecutionType type) {
|
private String assemble(ShellControl shellControl, ExecutionType type) {
|
||||||
var targetType = type == ExecutionType.TERMINAL_ONLY
|
var targetType = type == ExecutionType.TERMINAL_ONLY
|
||||||
|
@ -51,39 +44,35 @@ public class SimpleScriptStore extends ScriptStore {
|
||||||
@Override
|
@Override
|
||||||
public List<DataStoreEntryRef<ScriptStore>> getEffectiveScripts() {
|
public List<DataStoreEntryRef<ScriptStore>> getEffectiveScripts() {
|
||||||
return scripts != null
|
return scripts != null
|
||||||
? scripts.stream().filter(scriptStore -> scriptStore != null).toList()
|
? scripts.stream().filter(Objects::nonNull).toList()
|
||||||
: List.of();
|
: List.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void queryFlattenedScripts(LinkedHashSet<SimpleScriptStore> all) {
|
public void queryFlattenedScripts(LinkedHashSet<SimpleScriptStore> all) {
|
||||||
|
// Prevent loop
|
||||||
all.add(this);
|
all.add(this);
|
||||||
getEffectiveScripts().stream()
|
getEffectiveScripts().stream()
|
||||||
.filter(scriptStoreDataStoreEntryRef -> !all.contains(scriptStoreDataStoreEntryRef.getStore()))
|
.filter(scriptStoreDataStoreEntryRef -> !all.contains(scriptStoreDataStoreEntryRef.getStore()))
|
||||||
.forEach(scriptStoreDataStoreEntryRef -> {
|
.forEach(scriptStoreDataStoreEntryRef -> {
|
||||||
scriptStoreDataStoreEntryRef.getStore().queryFlattenedScripts(all);
|
scriptStoreDataStoreEntryRef.getStore().queryFlattenedScripts(all);
|
||||||
});
|
});
|
||||||
|
all.remove(this);
|
||||||
|
all.add(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Getter
|
@Override
|
||||||
public enum ExecutionType {
|
public String content(ShellControl shellControl) {
|
||||||
@JsonProperty("dumbOnly")
|
return assemble(shellControl, executionType);
|
||||||
DUMB_ONLY("dumbOnly"),
|
|
||||||
@JsonProperty("terminalOnly")
|
|
||||||
TERMINAL_ONLY("terminalOnly"),
|
|
||||||
@JsonProperty("both")
|
|
||||||
BOTH("both");
|
|
||||||
|
|
||||||
private final String id;
|
|
||||||
|
|
||||||
ExecutionType(String id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScriptSnippet.ExecutionType executionType() {
|
||||||
|
return executionType;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final ShellDialect minimumDialect;
|
private final ShellDialect minimumDialect;
|
||||||
private final String commands;
|
private final String commands;
|
||||||
private final ExecutionType executionType;
|
private final ExecutionType executionType;
|
||||||
private final boolean requiresElevation;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void checkComplete() throws Exception {
|
public void checkComplete() throws Exception {
|
||||||
|
|
|
@ -137,7 +137,6 @@ public class SimpleScriptStoreProvider implements DataStoreProvider {
|
||||||
new SimpleListProperty<>(FXCollections.observableArrayList(new ArrayList<>(st.getEffectiveScripts())));
|
new SimpleListProperty<>(FXCollections.observableArrayList(new ArrayList<>(st.getEffectiveScripts())));
|
||||||
Property<String> commandProp = new SimpleObjectProperty<>(st.getCommands());
|
Property<String> commandProp = new SimpleObjectProperty<>(st.getCommands());
|
||||||
var type = new SimpleObjectProperty<>(st.getExecutionType());
|
var type = new SimpleObjectProperty<>(st.getExecutionType());
|
||||||
var requiresElevationProperty = new SimpleBooleanProperty(st.isRequiresElevation());
|
|
||||||
|
|
||||||
Comp<?> choice = (Comp<?>) Class.forName(
|
Comp<?> choice = (Comp<?>) Class.forName(
|
||||||
AppExtensionManager.getInstance()
|
AppExtensionManager.getInstance()
|
||||||
|
@ -150,6 +149,7 @@ public class SimpleScriptStoreProvider implements DataStoreProvider {
|
||||||
return new OptionsBuilder()
|
return new OptionsBuilder()
|
||||||
.name("snippets")
|
.name("snippets")
|
||||||
.description("snippetsDescription")
|
.description("snippetsDescription")
|
||||||
|
.longDescription("base:scriptDependencies")
|
||||||
.addComp(
|
.addComp(
|
||||||
new DataStoreListChoiceComp<>(
|
new DataStoreListChoiceComp<>(
|
||||||
others,
|
others,
|
||||||
|
@ -159,6 +159,7 @@ public class SimpleScriptStoreProvider implements DataStoreProvider {
|
||||||
others)
|
others)
|
||||||
.name("minimumShellDialect")
|
.name("minimumShellDialect")
|
||||||
.description("minimumShellDialectDescription")
|
.description("minimumShellDialectDescription")
|
||||||
|
.longDescription("base:scriptCompatibility")
|
||||||
.addComp(choice, dialect)
|
.addComp(choice, dialect)
|
||||||
.nonNull()
|
.nonNull()
|
||||||
.name("scriptContents")
|
.name("scriptContents")
|
||||||
|
@ -175,10 +176,6 @@ public class SimpleScriptStoreProvider implements DataStoreProvider {
|
||||||
.description("executionTypeDescription")
|
.description("executionTypeDescription")
|
||||||
.longDescription("base:executionType")
|
.longDescription("base:executionType")
|
||||||
.addComp(new ScriptStoreTypeChoiceComp(type), type)
|
.addComp(new ScriptStoreTypeChoiceComp(type), type)
|
||||||
.name("shouldElevate")
|
|
||||||
.description("shouldElevateDescription")
|
|
||||||
.longDescription("proc:elevation")
|
|
||||||
.addToggle(requiresElevationProperty)
|
|
||||||
.name("scriptGroup")
|
.name("scriptGroup")
|
||||||
.description("scriptGroupDescription")
|
.description("scriptGroupDescription")
|
||||||
.addComp(
|
.addComp(
|
||||||
|
@ -195,7 +192,6 @@ public class SimpleScriptStoreProvider implements DataStoreProvider {
|
||||||
.description(st.getDescription())
|
.description(st.getDescription())
|
||||||
.commands(commandProp.getValue())
|
.commands(commandProp.getValue())
|
||||||
.executionType(type.get())
|
.executionType(type.get())
|
||||||
.requiresElevation(requiresElevationProperty.get())
|
|
||||||
.build();
|
.build();
|
||||||
},
|
},
|
||||||
store)
|
store)
|
||||||
|
@ -210,8 +206,7 @@ public class SimpleScriptStoreProvider implements DataStoreProvider {
|
||||||
@Override
|
@Override
|
||||||
public ObservableValue<String> informationString(StoreEntryWrapper wrapper) {
|
public ObservableValue<String> informationString(StoreEntryWrapper wrapper) {
|
||||||
SimpleScriptStore scriptStore = wrapper.getEntry().getStore().asNeeded();
|
SimpleScriptStore scriptStore = wrapper.getEntry().getStore().asNeeded();
|
||||||
return new SimpleStringProperty((scriptStore.isRequiresElevation() ? "Elevated " : "")
|
return new SimpleStringProperty((scriptStore.getMinimumDialect() != null
|
||||||
+ (scriptStore.getMinimumDialect() != null
|
|
||||||
? scriptStore.getMinimumDialect().getDisplayName() + " "
|
? scriptStore.getMinimumDialect().getDisplayName() + " "
|
||||||
: "")
|
: "")
|
||||||
+ (scriptStore.getExecutionType() == SimpleScriptStore.ExecutionType.TERMINAL_ONLY
|
+ (scriptStore.getExecutionType() == SimpleScriptStore.ExecutionType.TERMINAL_ONLY
|
||||||
|
@ -273,7 +268,6 @@ public class SimpleScriptStoreProvider implements DataStoreProvider {
|
||||||
return SimpleScriptStore.builder()
|
return SimpleScriptStore.builder()
|
||||||
.scripts(List.of())
|
.scripts(List.of())
|
||||||
.executionType(SimpleScriptStore.ExecutionType.TERMINAL_ONLY)
|
.executionType(SimpleScriptStore.ExecutionType.TERMINAL_ONLY)
|
||||||
.requiresElevation(false)
|
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
## Execution types
|
## Execution types
|
||||||
|
|
||||||
There are two distinct execution phases when XPipe connects to a system.
|
There are two distinct execution types when XPipe connects to a system.
|
||||||
The first connection is made in the background in a dumb terminal.
|
|
||||||
Only afterward will a separate connection be made in the actual terminal.
|
|
||||||
|
|
||||||
The file browser for example entirely uses the dumb background mode to handle its operations, so if you want your script environment to apply to the file browser session, it should run in the dumb mode.
|
### Dumb terminals
|
||||||
|
|
||||||
If you want the script to be run when you open the connection in a terminal, then choose the terminal mode.
|
The first connection to a system is made in the background in a dumb terminal.
|
||||||
|
|
||||||
### Blocking commands
|
|
||||||
|
|
||||||
Blocking commands that require user input can freeze the shell process when XPipe starts it up internally first in the background.
|
Blocking commands that require user input can freeze the shell process when XPipe starts it up internally first in the background.
|
||||||
To avoid this, you should only call these blocking commands in the terminal mode.
|
To avoid this, you should only call these blocking commands in the terminal mode.
|
||||||
|
|
||||||
|
The file browser for example entirely uses the dumb background mode to handle its operations, so if you want your script environment to apply to the file browser session, it should run in the dumb mode.
|
||||||
|
|
||||||
|
### Proper terminals
|
||||||
|
|
||||||
|
After a dumb terminal connection has succeeded, XPipe will open a separate connection in the actual terminal.
|
||||||
|
If you want the script to be run when you open the connection in a terminal, then choose the terminal mode.
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
## Script compatibility
|
||||||
|
|
||||||
|
The shell type controls where this script can be run.
|
||||||
|
Aside from an exact match, i.e. running a `zsh` script in `zsh`, XPipe will also include wider compatibility checking.
|
||||||
|
|
||||||
|
### Posix Shells
|
||||||
|
|
||||||
|
Any script declared as a `sh` script is able to run in any posix-related shell environment such as `bash` or `zsh`.
|
||||||
|
If you intend to run a basic script on many different systems, then using only `sh` syntax scripts is the best solution for that.
|
||||||
|
|
||||||
|
### PowerShell
|
||||||
|
|
||||||
|
Scripts declared as normal PowerShell scripts are also able to run in PowerShell Core environments.
|
|
@ -0,0 +1,5 @@
|
||||||
|
## Script dependencies
|
||||||
|
|
||||||
|
The scripts and script groups to run first. If an entire group is made a dependency, all scripts in this group will be considered as dependencies.
|
||||||
|
|
||||||
|
The resolved dependency graph of scripts is flattened, filtered, and made unique, i.e. only compatible scripts will be run and if script would be executed multiple times, it will only be run the first time.
|
|
@ -1,3 +1,5 @@
|
||||||
The contents of the script to run. You can choose to either edit this in place or use the external edit button in the top right corner to launch an external text editor.
|
## Script contents
|
||||||
|
|
||||||
|
The contents of the script to run. You can choose to either edit this in-place or use the external edit button in the top right corner to launch an external text editor.
|
||||||
|
|
||||||
You don't have to specify a shebang line for shells that support it, one is added automatically with the appropriate shell type.
|
You don't have to specify a shebang line for shells that support it, one is added automatically with the appropriate shell type.
|
||||||
|
|
|
@ -72,9 +72,9 @@ scriptContentsDescription=The script commands to execute
|
||||||
snippets=Script dependencies
|
snippets=Script dependencies
|
||||||
snippetsDescription=Other scripts to run first
|
snippetsDescription=Other scripts to run first
|
||||||
snippetsDependenciesDescription=All possible scripts that should be run if applicable
|
snippetsDependenciesDescription=All possible scripts that should be run if applicable
|
||||||
isDefault=Enable in all compatible shells
|
isDefault=Run on init in all compatible shells
|
||||||
bringToShells=Bring to all compatible shells
|
bringToShells=Bring to all compatible shells
|
||||||
isDefaultGroup=Enable all group scripts
|
isDefaultGroup=Run all group scripts on shell init
|
||||||
executionType=Execution type
|
executionType=Execution type
|
||||||
executionTypeDescription=When to run this snippet
|
executionTypeDescription=When to run this snippet
|
||||||
minimumShellDialect=Shell type
|
minimumShellDialect=Shell type
|
||||||
|
|
2
version
|
@ -1 +1 @@
|
||||||
1.7.2
|
1.7.3-3
|