[stage] [noannounce]
|
@ -1,7 +1,7 @@
|
|||
package io.xpipe.api;
|
||||
|
||||
import io.xpipe.api.impl.DataSourceImpl;
|
||||
import io.xpipe.core.source.DataSourceId;
|
||||
import io.xpipe.core.source.DataStoreId;
|
||||
import io.xpipe.core.source.DataSourceReference;
|
||||
import io.xpipe.core.source.DataSourceType;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
|
@ -96,16 +96,16 @@ public interface DataSource {
|
|||
}
|
||||
|
||||
/**
|
||||
* Wrapper for {@link #create(DataSourceId, String, InputStream)} that creates an anonymous data source.
|
||||
* Wrapper for {@link #create(DataStoreId, String, InputStream)} that creates an anonymous data source.
|
||||
*/
|
||||
static DataSource createAnonymous(String type, Path path) {
|
||||
return create(null, type, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for {@link #create(DataSourceId, String, InputStream)}.
|
||||
* Wrapper for {@link #create(DataStoreId, String, InputStream)}.
|
||||
*/
|
||||
static DataSource create(DataSourceId id, String type, Path path) {
|
||||
static DataSource create(DataStoreId id, String type, Path path) {
|
||||
try (var in = Files.newInputStream(path)) {
|
||||
return create(id, type, in);
|
||||
} catch (IOException e) {
|
||||
|
@ -114,16 +114,16 @@ public interface DataSource {
|
|||
}
|
||||
|
||||
/**
|
||||
* Wrapper for {@link #create(DataSourceId, String, InputStream)} that creates an anonymous data source.
|
||||
* Wrapper for {@link #create(DataStoreId, String, InputStream)} that creates an anonymous data source.
|
||||
*/
|
||||
static DataSource createAnonymous(String type, URL url) {
|
||||
return create(null, type, url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for {@link #create(DataSourceId, String, InputStream)}.
|
||||
* Wrapper for {@link #create(DataStoreId, String, InputStream)}.
|
||||
*/
|
||||
static DataSource create(DataSourceId id, String type, URL url) {
|
||||
static DataSource create(DataStoreId id, String type, URL url) {
|
||||
try (var in = url.openStream()) {
|
||||
return create(id, type, in);
|
||||
} catch (IOException e) {
|
||||
|
@ -132,7 +132,7 @@ public interface DataSource {
|
|||
}
|
||||
|
||||
/**
|
||||
* Wrapper for {@link #create(DataSourceId, String, InputStream)} that creates an anonymous data source.
|
||||
* Wrapper for {@link #create(DataStoreId, String, InputStream)} that creates an anonymous data source.
|
||||
*/
|
||||
static DataSource createAnonymous(String type, InputStream in) {
|
||||
return create(null, type, in);
|
||||
|
@ -146,7 +146,7 @@ public interface DataSource {
|
|||
* @param in the input stream to read
|
||||
* @return a {@link DataSource} instances that can be used to access the underlying data
|
||||
*/
|
||||
static DataSource create(DataSourceId id, String type, InputStream in) {
|
||||
static DataSource create(DataStoreId id, String type, InputStream in) {
|
||||
return DataSourceImpl.create(id, type, in);
|
||||
}
|
||||
|
||||
|
@ -156,7 +156,7 @@ public interface DataSource {
|
|||
* @param id the data source id
|
||||
* @return a {@link DataSource} instances that can be used to access the underlying data
|
||||
*/
|
||||
static DataSource create(DataSourceId id, io.xpipe.core.source.DataSource<?> source) {
|
||||
static DataSource create(DataStoreId id, io.xpipe.core.source.DataSource<?> source) {
|
||||
return DataSourceImpl.create(id, source);
|
||||
}
|
||||
|
||||
|
@ -169,7 +169,7 @@ public interface DataSource {
|
|||
* @param in the data store to add
|
||||
* @return a {@link DataSource} instances that can be used to access the underlying data
|
||||
*/
|
||||
static DataSource create(DataSourceId id, String type, DataStore in) {
|
||||
static DataSource create(DataStoreId id, String type, DataStore in) {
|
||||
return DataSourceImpl.create(id, type, in);
|
||||
}
|
||||
|
||||
|
@ -182,7 +182,7 @@ public interface DataSource {
|
|||
/**
|
||||
* Returns the id of this data source.
|
||||
*/
|
||||
DataSourceId getId();
|
||||
DataStoreId getId();
|
||||
|
||||
/**
|
||||
* Returns the type of this data source.
|
||||
|
|
|
@ -4,14 +4,14 @@ import io.xpipe.api.impl.DataTableAccumulatorImpl;
|
|||
import io.xpipe.core.data.node.DataStructureNode;
|
||||
import io.xpipe.core.data.node.DataStructureNodeAcceptor;
|
||||
import io.xpipe.core.data.type.TupleType;
|
||||
import io.xpipe.core.source.DataSourceId;
|
||||
import io.xpipe.core.source.DataStoreId;
|
||||
|
||||
/**
|
||||
* An accumulator for table data.
|
||||
* <p>
|
||||
* This class can be used to construct new table data sources by
|
||||
* accumulating the rows using {@link #add(DataStructureNode)} or {@link #acceptor()} and then calling
|
||||
* {@link #finish(DataSourceId)} to complete the construction process and create a new data source.
|
||||
* {@link #finish(DataStoreId)} to complete the construction process and create a new data source.
|
||||
*/
|
||||
public interface DataTableAccumulator {
|
||||
|
||||
|
@ -20,10 +20,10 @@ public interface DataTableAccumulator {
|
|||
}
|
||||
|
||||
/**
|
||||
* Wrapper for {@link #finish(DataSourceId)}.
|
||||
* Wrapper for {@link #finish(DataStoreId)}.
|
||||
*/
|
||||
default DataTable finish(String id) {
|
||||
return finish(DataSourceId.fromString(id));
|
||||
return finish(DataStoreId.fromString(id));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -31,7 +31,7 @@ public interface DataTableAccumulator {
|
|||
*
|
||||
* @param id the data source id to assign
|
||||
*/
|
||||
DataTable finish(DataSourceId id);
|
||||
DataTable finish(DataStoreId id);
|
||||
|
||||
/**
|
||||
* Adds a row to the table.
|
||||
|
|
|
@ -2,7 +2,7 @@ package io.xpipe.api.impl;
|
|||
|
||||
import io.xpipe.api.DataRaw;
|
||||
import io.xpipe.api.DataSourceConfig;
|
||||
import io.xpipe.core.source.DataSourceId;
|
||||
import io.xpipe.core.source.DataStoreId;
|
||||
import io.xpipe.core.source.DataSourceType;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
@ -10,7 +10,7 @@ import java.io.InputStream;
|
|||
public class DataRawImpl extends DataSourceImpl implements DataRaw {
|
||||
|
||||
public DataRawImpl(
|
||||
DataSourceId sourceId, DataSourceConfig sourceConfig, io.xpipe.core.source.DataSource<?> internalSource) {
|
||||
DataStoreId sourceId, DataSourceConfig sourceConfig, io.xpipe.core.source.DataSource<?> internalSource) {
|
||||
super(sourceId, sourceConfig, internalSource);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import io.xpipe.api.DataSource;
|
|||
import io.xpipe.api.DataSourceConfig;
|
||||
import io.xpipe.api.connector.XPipeApiConnection;
|
||||
import io.xpipe.beacon.exchange.*;
|
||||
import io.xpipe.core.source.DataSourceId;
|
||||
import io.xpipe.core.source.DataStoreId;
|
||||
import io.xpipe.core.source.DataSourceReference;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.store.StreamDataStore;
|
||||
|
@ -13,12 +13,12 @@ import java.io.InputStream;
|
|||
|
||||
public abstract class DataSourceImpl implements DataSource {
|
||||
|
||||
private final DataSourceId sourceId;
|
||||
private final DataStoreId sourceId;
|
||||
private final DataSourceConfig config;
|
||||
private final io.xpipe.core.source.DataSource<?> internalSource;
|
||||
|
||||
public DataSourceImpl(
|
||||
DataSourceId sourceId, DataSourceConfig config, io.xpipe.core.source.DataSource<?> internalSource) {
|
||||
DataStoreId sourceId, DataSourceConfig config, io.xpipe.core.source.DataSource<?> internalSource) {
|
||||
this.sourceId = sourceId;
|
||||
this.config = config;
|
||||
this.internalSource = internalSource;
|
||||
|
@ -48,7 +48,7 @@ public abstract class DataSourceImpl implements DataSource {
|
|||
});
|
||||
}
|
||||
|
||||
public static DataSource create(DataSourceId id, io.xpipe.core.source.DataSource<?> source) {
|
||||
public static DataSource create(DataStoreId id, io.xpipe.core.source.DataSource<?> source) {
|
||||
var startReq =
|
||||
AddSourceExchange.Request.builder().source(source).target(id).build();
|
||||
var returnedId = XPipeApiConnection.execute(con -> {
|
||||
|
@ -60,7 +60,7 @@ public abstract class DataSourceImpl implements DataSource {
|
|||
return get(ref);
|
||||
}
|
||||
|
||||
public static DataSource create(DataSourceId id, String type, DataStore store) {
|
||||
public static DataSource create(DataStoreId id, String type, DataStore store) {
|
||||
if (store instanceof StreamDataStore s && s.isContentExclusivelyAccessible()) {
|
||||
store = XPipeApiConnection.execute(con -> {
|
||||
var internal = con.createInternalStreamStore();
|
||||
|
@ -94,7 +94,7 @@ public abstract class DataSourceImpl implements DataSource {
|
|||
return get(ref);
|
||||
}
|
||||
|
||||
public static DataSource create(DataSourceId id, String type, InputStream in) {
|
||||
public static DataSource create(DataStoreId id, String type, InputStream in) {
|
||||
var store = XPipeApiConnection.execute(con -> {
|
||||
var internal = con.createInternalStreamStore();
|
||||
var req = WriteStreamExchange.Request.builder()
|
||||
|
@ -152,7 +152,7 @@ public abstract class DataSourceImpl implements DataSource {
|
|||
}
|
||||
|
||||
@Override
|
||||
public DataSourceId getId() {
|
||||
public DataStoreId getId() {
|
||||
return sourceId;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,13 +3,13 @@ package io.xpipe.api.impl;
|
|||
import io.xpipe.api.DataSourceConfig;
|
||||
import io.xpipe.api.DataStructure;
|
||||
import io.xpipe.core.data.node.DataStructureNode;
|
||||
import io.xpipe.core.source.DataSourceId;
|
||||
import io.xpipe.core.source.DataStoreId;
|
||||
import io.xpipe.core.source.DataSourceType;
|
||||
|
||||
public class DataStructureImpl extends DataSourceImpl implements DataStructure {
|
||||
|
||||
DataStructureImpl(
|
||||
DataSourceId sourceId, DataSourceConfig sourceConfig, io.xpipe.core.source.DataSource<?> internalSource) {
|
||||
DataStoreId sourceId, DataSourceConfig sourceConfig, io.xpipe.core.source.DataSource<?> internalSource) {
|
||||
super(sourceId, sourceConfig, internalSource);
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ import io.xpipe.core.data.node.TupleNode;
|
|||
import io.xpipe.core.data.type.TupleType;
|
||||
import io.xpipe.core.data.typed.TypedDataStreamWriter;
|
||||
import io.xpipe.core.impl.InternalStreamStore;
|
||||
import io.xpipe.core.source.DataSourceId;
|
||||
import io.xpipe.core.source.DataStoreId;
|
||||
import io.xpipe.core.source.DataSourceReference;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -51,7 +51,7 @@ public class DataTableAccumulatorImpl implements DataTableAccumulator {
|
|||
}
|
||||
|
||||
@Override
|
||||
public synchronized DataTable finish(DataSourceId id) {
|
||||
public synchronized DataTable finish(DataStoreId id) {
|
||||
try {
|
||||
bodyOutput.close();
|
||||
} catch (IOException e) {
|
||||
|
|
|
@ -5,7 +5,7 @@ import io.xpipe.api.DataTable;
|
|||
import io.xpipe.core.data.node.ArrayNode;
|
||||
import io.xpipe.core.data.node.DataStructureNode;
|
||||
import io.xpipe.core.data.node.TupleNode;
|
||||
import io.xpipe.core.source.DataSourceId;
|
||||
import io.xpipe.core.source.DataStoreId;
|
||||
import io.xpipe.core.source.DataSourceType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -15,7 +15,7 @@ import java.util.stream.Stream;
|
|||
|
||||
public class DataTableImpl extends DataSourceImpl implements DataTable {
|
||||
|
||||
DataTableImpl(DataSourceId id, DataSourceConfig sourceConfig, io.xpipe.core.source.DataSource<?> internalSource) {
|
||||
DataTableImpl(DataStoreId id, DataSourceConfig sourceConfig, io.xpipe.core.source.DataSource<?> internalSource) {
|
||||
super(id, sourceConfig, internalSource);
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ package io.xpipe.api.impl;
|
|||
|
||||
import io.xpipe.api.DataSourceConfig;
|
||||
import io.xpipe.api.DataText;
|
||||
import io.xpipe.core.source.DataSourceId;
|
||||
import io.xpipe.core.source.DataStoreId;
|
||||
import io.xpipe.core.source.DataSourceType;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -12,7 +12,7 @@ import java.util.stream.Stream;
|
|||
public class DataTextImpl extends DataSourceImpl implements DataText {
|
||||
|
||||
DataTextImpl(
|
||||
DataSourceId sourceId, DataSourceConfig sourceConfig, io.xpipe.core.source.DataSource<?> internalSource) {
|
||||
DataStoreId sourceId, DataSourceConfig sourceConfig, io.xpipe.core.source.DataSource<?> internalSource) {
|
||||
super(sourceId, sourceConfig, internalSource);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package io.xpipe.api.test;
|
||||
|
||||
import io.xpipe.api.DataSource;
|
||||
import io.xpipe.core.source.DataSourceId;
|
||||
import io.xpipe.core.source.DataStoreId;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
@ -10,7 +10,7 @@ public class DataTableTest extends ApiTest {
|
|||
@BeforeAll
|
||||
public static void setupStorage() throws Exception {
|
||||
DataSource.create(
|
||||
DataSourceId.fromString(":usernames"), "csv", DataTableTest.class.getResource("username.csv"));
|
||||
DataStoreId.fromString(":usernames"), "csv", DataTableTest.class.getResource("username.csv"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -156,11 +156,12 @@ run {
|
|||
|
||||
// systemProperty "io.xpipe.beacon.localProxy", "true"
|
||||
|
||||
systemProperties System.getProperties()
|
||||
systemProperty 'java.library.path', "./lib"
|
||||
workingDir = rootDir
|
||||
}
|
||||
|
||||
task runAttachedDebugger(type: JavaExec) {
|
||||
workingDir = rootDir
|
||||
classpath = run.classpath
|
||||
mainModule = 'io.xpipe.app'
|
||||
mainClass = 'io.xpipe.app.Main'
|
||||
|
|
|
@ -4,6 +4,7 @@ import atlantafx.base.controls.Breadcrumbs;
|
|||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
|
@ -80,7 +81,9 @@ public class BrowserBreadcrumbBar extends SimpleComp {
|
|||
}
|
||||
|
||||
breadcrumbs.selectedCrumbProperty().addListener((obs, old, val) -> {
|
||||
model.cd(val != null ? val.getValue() : null);
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
model.cdSync(val != null ? val.getValue() : null);
|
||||
});
|
||||
});
|
||||
|
||||
return breadcrumbs;
|
||||
|
|
|
@ -200,7 +200,7 @@ public class BrowserFileListCompEntry {
|
|||
return;
|
||||
}
|
||||
|
||||
model.getFileSystemModel().cd(item.getRawFileEntry().getPath());
|
||||
model.getFileSystemModel().cdSync(item.getRawFileEntry().getPath());
|
||||
}
|
||||
};
|
||||
DROP_TIMER.schedule(activeTask, 1000);
|
||||
|
|
|
@ -128,7 +128,7 @@ public final class BrowserFileListModel {
|
|||
}
|
||||
|
||||
if (entry.getRawFileEntry().resolved().getKind() == FileKind.DIRECTORY) {
|
||||
fileSystemModel.cd(entry.getRawFileEntry().resolved().getPath());
|
||||
fileSystemModel.cdSync(entry.getRawFileEntry().resolved().getPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ public class BrowserFileOverviewComp extends SimpleComp {
|
|||
var icon = BrowserIcons.createIcon(entry);
|
||||
var l = new Button(entry.getPath(), icon.createRegion());
|
||||
l.setOnAction(event -> {
|
||||
model.cd(entry.getPath());
|
||||
model.cdSync(entry.getPath());
|
||||
event.consume();
|
||||
});
|
||||
l.setAlignment(Pos.CENTER_LEFT);
|
||||
|
|
|
@ -11,6 +11,8 @@ import io.xpipe.app.fxcomps.impl.PrettyImageComp;
|
|||
import io.xpipe.app.fxcomps.impl.StackComp;
|
||||
import io.xpipe.app.fxcomps.impl.TextFieldComp;
|
||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.css.PseudoClass;
|
||||
|
@ -42,8 +44,10 @@ public class BrowserNavBar extends SimpleComp {
|
|||
path.set(newValue);
|
||||
});
|
||||
path.addListener((observable, oldValue, newValue) -> {
|
||||
var changed = model.cdOrRetry(newValue, true);
|
||||
changed.ifPresent(path::set);
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
var changed = model.cdSyncOrRetry(newValue, true);
|
||||
changed.ifPresent(s -> Platform.runLater(() -> path.set(s)));
|
||||
});
|
||||
});
|
||||
|
||||
var pathBar = new TextFieldComp(path, true)
|
||||
|
|
|
@ -57,6 +57,10 @@ public class BrowserWelcomeComp extends SimpleComp {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!entry.get().getState().isUsable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var graphic =
|
||||
entry.get().getProvider().getDisplayIconFileName(entry.get().getStore());
|
||||
var view = new PrettyImageComp(new SimpleStringProperty(graphic), 45, 45);
|
||||
|
|
|
@ -43,7 +43,7 @@ public class OpenFileSystemComp extends SimpleComp {
|
|||
|
||||
private Region createContent() {
|
||||
var overview = new Button(null, new FontIcon("mdi2m-monitor"));
|
||||
overview.setOnAction(e -> model.cd(null));
|
||||
overview.setOnAction(e -> model.cdSync(null));
|
||||
overview.disableProperty().bind(model.getInOverview());
|
||||
overview.setAccessibleText("System overview");
|
||||
|
||||
|
|
|
@ -107,11 +107,11 @@ public final class OpenFileSystemModel {
|
|||
return new FileSystem.FileEntry(fileSystem, currentPath.get(), null, false, false, 0, null, FileKind.DIRECTORY);
|
||||
}
|
||||
|
||||
public void cd(String path) {
|
||||
cdOrRetry(path, false).ifPresent(s -> cdOrRetry(s, false));
|
||||
public void cdSync(String path) {
|
||||
cdSyncOrRetry(path, false).ifPresent(s -> cdSyncOrRetry(s, false));
|
||||
}
|
||||
|
||||
public Optional<String> cdOrRetry(String path, boolean allowCommands) {
|
||||
public Optional<String> cdSyncOrRetry(String path, boolean allowCommands) {
|
||||
if (Objects.equals(path, currentPath.get())) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
@ -179,16 +179,12 @@ public final class OpenFileSystemModel {
|
|||
|
||||
try {
|
||||
FileSystemHelper.validateDirectoryPath(this, resolvedPath);
|
||||
cdSyncWithoutCheck(path);
|
||||
} catch (Exception ex) {
|
||||
ErrorEvent.fromThrowable(ex).handle();
|
||||
return Optional.ofNullable(currentPath.get());
|
||||
}
|
||||
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
try (var ignored = new BusyProperty(busy)) {
|
||||
cdSyncWithoutCheck(path);
|
||||
}
|
||||
});
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
|
|
|
@ -11,8 +11,10 @@ import javafx.css.Size;
|
|||
import javafx.css.SizeUnits;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import lombok.Getter;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
@Getter
|
||||
public class ButtonComp extends Comp<CompStructure<Button>> {
|
||||
|
||||
private final ObservableValue<String> name;
|
||||
|
@ -31,10 +33,6 @@ public class ButtonComp extends Comp<CompStructure<Button>> {
|
|||
this.listener = listener;
|
||||
}
|
||||
|
||||
public ObservableValue<String> getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Node getGraphic() {
|
||||
return graphic.get();
|
||||
}
|
||||
|
@ -43,10 +41,6 @@ public class ButtonComp extends Comp<CompStructure<Button>> {
|
|||
return graphic;
|
||||
}
|
||||
|
||||
public Runnable getListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompStructure<Button> createBase() {
|
||||
var button = new Button(null);
|
||||
|
|
|
@ -11,6 +11,7 @@ import io.xpipe.app.fxcomps.SimpleCompStructure;
|
|||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
|
@ -55,8 +56,9 @@ public class MarkdownComp extends Comp<CompStructure<StackPane>> {
|
|||
@SneakyThrows
|
||||
private WebView createWebView() {
|
||||
var wv = new WebView();
|
||||
wv.setPageFill(Color.valueOf("#EEE"));
|
||||
var url = AppResources.getResourceURL(AppResources.XPIPE_MODULE, "web/github-markdown.css")
|
||||
wv.setPageFill(Color.TRANSPARENT);
|
||||
var theme = AppPrefs.get() != null && AppPrefs.get().theme.getValue().getTheme().isDarkMode() ? "web/github-markdown-dark.css" : "web/github-markdown-light.css";
|
||||
var url = AppResources.getResourceURL(AppResources.XPIPE_MODULE, theme)
|
||||
.orElseThrow();
|
||||
wv.getEngine().setUserStyleSheetLocation(url.toString());
|
||||
|
||||
|
|
59
app/src/main/java/io/xpipe/app/comp/base/OsLogoComp.java
Normal file
|
@ -0,0 +1,59 @@
|
|||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.comp.storage.store.StoreEntryWrapper;
|
||||
import io.xpipe.app.core.AppResources;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.impl.PrettyImageComp;
|
||||
import io.xpipe.app.fxcomps.impl.StackComp;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class OsLogoComp extends SimpleComp {
|
||||
|
||||
private final StoreEntryWrapper wrapper;
|
||||
|
||||
public OsLogoComp(StoreEntryWrapper wrapper) {
|
||||
this.wrapper = wrapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
var img = Bindings.createObjectBinding(
|
||||
() -> {
|
||||
return wrapper.getState().getValue() == DataStoreEntry.State.COMPLETE_AND_VALID
|
||||
? getImage(wrapper.getInformation().get()) : null;
|
||||
},
|
||||
wrapper.getState(), wrapper.getInformation());
|
||||
return new StackComp(List.of(new SystemStateComp(wrapper).hide(img.isNotNull()), new PrettyImageComp(img, 24, 24))).createRegion();
|
||||
}
|
||||
|
||||
private static final Map<String, String> ICONS = new HashMap<>();
|
||||
private static final String LINUX_DEFAULT = "linux.svg";
|
||||
|
||||
private String getImage(String name) {
|
||||
if (name == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (ICONS.isEmpty()) {
|
||||
AppResources.withResource(AppResources.XPIPE_MODULE, "img/os", ModuleLayer.boot(), file -> {
|
||||
try (var list = Files.list(file)) {
|
||||
list.filter(path -> !path.toString().endsWith(LINUX_DEFAULT)).map(path -> FileNames.getFileName(path.toString())).forEach(path -> {
|
||||
var base = FileNames.getBaseName(path).replace("-dark", "") + ".svg";
|
||||
ICONS.put(FileNames.getBaseName(base).split("-")[0], "os/" + base);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var found = ICONS.entrySet().stream().filter(e->name.toLowerCase().contains(e.getKey())).findAny().map(e->e.getValue()).orElse("os/" + LINUX_DEFAULT);
|
||||
return found;
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.AppLayoutModel;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
|
@ -39,7 +38,6 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
|
|||
|
||||
var selected = PseudoClass.getPseudoClass("selected");
|
||||
entries.forEach(e -> {
|
||||
var fi = new FontIcon(e.icon());
|
||||
var b = new IconButtonComp(e.icon(), () -> value.setValue(e)).apply(new FancyTooltipAugment<>(e.name()));
|
||||
b.apply(struc -> {
|
||||
AppFont.setSize(struc.get(), 2);
|
||||
|
@ -55,7 +53,7 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
|
|||
|
||||
{
|
||||
var fi = new FontIcon("mdi2u-update");
|
||||
var b = new BigIconButton(AppI18n.observable("update"), fi, () -> UpdateAvailableAlert.showIfNeeded());
|
||||
var b = new IconButtonComp("mdi2u-update", () -> UpdateAvailableAlert.showIfNeeded());
|
||||
b.hide(PlatformThread.sync(Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
return XPipeDistributionType.get()
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
package io.xpipe.app.comp.base;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import io.xpipe.app.comp.storage.store.StoreEntryWrapper;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.geometry.Pos;
|
||||
|
@ -15,8 +16,7 @@ import org.kordamp.ikonli.javafx.StackedFontIcon;
|
|||
public class SystemStateComp extends SimpleComp {
|
||||
|
||||
|
||||
public SystemStateComp(ObservableValue<String> name, ObservableValue<State> state) {
|
||||
this.name = name;
|
||||
public SystemStateComp(ObservableValue<State> state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
|
@ -26,9 +26,21 @@ public class SystemStateComp extends SimpleComp {
|
|||
OTHER
|
||||
}
|
||||
|
||||
private final ObservableValue<String> name;
|
||||
private final ObservableValue<State> state;
|
||||
|
||||
public SystemStateComp(StoreEntryWrapper w) {
|
||||
var state = Bindings.createObjectBinding(
|
||||
() -> {
|
||||
return w.getState().getValue() == DataStoreEntry.State.COMPLETE_BUT_INVALID
|
||||
? SystemStateComp.State.FAILURE
|
||||
: w.getState().getValue() == DataStoreEntry.State.COMPLETE_AND_VALID
|
||||
? SystemStateComp.State.SUCCESS
|
||||
: SystemStateComp.State.OTHER;
|
||||
},
|
||||
w.getState());
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
var icon = PlatformThread.sync(Bindings.createStringBinding(
|
||||
|
@ -69,7 +81,6 @@ public class SystemStateComp extends SimpleComp {
|
|||
pane.getStylesheets().add(val == State.SUCCESS ? success : val == State.FAILURE ? failure: other);
|
||||
});
|
||||
|
||||
new FancyTooltipAugment<>(PlatformThread.sync(name)).augment(pane);
|
||||
return pane;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,9 +20,9 @@ public class StandardStoreEntryComp extends StoreEntryComp {
|
|||
grid.setHgap(10);
|
||||
grid.setVgap(0);
|
||||
|
||||
var storeIcon = createIcon(60, 45);
|
||||
var storeIcon = createIcon(45, 35);
|
||||
grid.add(storeIcon, 0, 0, 1, 2);
|
||||
grid.getColumnConstraints().add(new ColumnConstraints(60));
|
||||
grid.getColumnConstraints().add(new ColumnConstraints(50));
|
||||
|
||||
grid.add(name, 1, 0);
|
||||
grid.add(createSummary(), 1, 1);
|
||||
|
|
|
@ -6,7 +6,6 @@ import io.xpipe.app.fxcomps.util.PlatformThread;
|
|||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.update.XPipeDistributionType;
|
||||
import io.xpipe.app.util.PlatformState;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
|
@ -29,7 +28,6 @@ public class App extends Application {
|
|||
public void start(Stage primaryStage) {
|
||||
TrackEvent.info("Application launched");
|
||||
APP = this;
|
||||
PlatformState.setCurrent(PlatformState.RUNNING);
|
||||
stage = primaryStage;
|
||||
|
||||
// Set dock icon explicitly on mac
|
||||
|
@ -47,7 +45,6 @@ public class App extends Application {
|
|||
}
|
||||
|
||||
AppWindowHelper.addIcons(stage);
|
||||
Platform.setImplicitExit(false);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
|
|
61
app/src/main/java/io/xpipe/app/core/AppAntivirusAlert.java
Normal file
|
@ -0,0 +1,61 @@
|
|||
package io.xpipe.app.core;
|
||||
|
||||
import io.xpipe.app.comp.base.MarkdownComp;
|
||||
import io.xpipe.app.core.mode.OperationMode;
|
||||
import io.xpipe.app.util.PlatformState;
|
||||
import io.xpipe.app.util.WindowsRegistry;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonBar;
|
||||
import javafx.scene.control.ButtonType;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.util.Optional;
|
||||
|
||||
public class AppAntivirusAlert {
|
||||
|
||||
public static Optional<String> detect() {
|
||||
var bitdefender = WindowsRegistry.readString(WindowsRegistry.HKEY_LOCAL_MACHINE,"SOFTWARE\\Bitdefender", "InstallDir");
|
||||
if (bitdefender.isPresent()) {
|
||||
return Optional.of("Bitdefender");
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public static void showIfNeeded() throws Throwable {
|
||||
// Only show this on first launch on windows
|
||||
if (OsType.getLocal() != OsType.WINDOWS || !AppState.get().isInitialLaunch()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var found = detect();
|
||||
if (found.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
PlatformState.initPlatformOrThrow();
|
||||
AppStyle.init();
|
||||
AppImages.init();
|
||||
|
||||
var a = AppWindowHelper.showBlockingAlert(alert -> {
|
||||
alert.setTitle(AppI18n.get("antivirusNoticeTitle"));
|
||||
alert.setAlertType(Alert.AlertType.NONE);
|
||||
|
||||
AppResources.withResource(
|
||||
AppResources.XPIPE_MODULE,
|
||||
"misc/antivirus.md",
|
||||
AppExtensionManager.getInstance().getExtendedLayer(),
|
||||
file -> {
|
||||
var markdown = new MarkdownComp(Files.readString(file), s -> s.formatted(found.get(), found.get(), AppProperties.get().getVersion(), AppProperties.get().getVersion(), found.get())).prefWidth(550).prefHeight(600).createRegion();
|
||||
alert.getDialogPane().setContent(markdown);
|
||||
alert.getDialogPane().setPadding(new Insets(15));
|
||||
});
|
||||
|
||||
alert.getButtonTypes().add(new ButtonType(AppI18n.get("gotIt"), ButtonBar.ButtonData.OK_DONE));
|
||||
});
|
||||
a.filter(b -> b.getButtonData().isDefaultButton())
|
||||
.ifPresentOrElse(buttonType -> {}, () -> OperationMode.halt(1));
|
||||
}
|
||||
}
|
|
@ -39,6 +39,11 @@ public class AppFileWatcher {
|
|||
}
|
||||
|
||||
public void startWatchersInDirectories(List<Path> dirs, BiConsumer<Path, WatchEvent.Kind<Path>> listener) {
|
||||
// Check in case initialization failed
|
||||
if (watchService == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
dirs.forEach(d -> watchedDirectories.add(new WatchedDirectory(d, listener)));
|
||||
}
|
||||
|
||||
|
@ -78,6 +83,11 @@ public class AppFileWatcher {
|
|||
}
|
||||
|
||||
private void stopWatcher() {
|
||||
// Check in case initialization failed
|
||||
if (watchService == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
active = false;
|
||||
watchedDirectories.clear();
|
||||
|
||||
|
|
|
@ -24,6 +24,10 @@ public class AppImages {
|
|||
private static final Map<String, String> svgImages = new HashMap<>();
|
||||
|
||||
public static void init() {
|
||||
if (images.size() > 0 || svgImages.size() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
TrackEvent.info("Loading images ...");
|
||||
for (var module : AppExtensionManager.getInstance().getContentModules()) {
|
||||
loadDirectory(module.getName(), "img");
|
||||
|
|
|
@ -39,6 +39,10 @@ public class AppLayoutModel {
|
|||
selected.setValue(entries.get(0));
|
||||
}
|
||||
|
||||
public void selectSettings() {
|
||||
selected.setValue(entries.get(2));
|
||||
}
|
||||
|
||||
public void selectConnections() {
|
||||
selected.setValue(entries.get(1));
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package io.xpipe.app.core;
|
|||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.core.util.ModuleHelper;
|
||||
import lombok.Getter;
|
||||
import lombok.Value;
|
||||
|
||||
import java.nio.file.InvalidPathException;
|
||||
|
@ -27,6 +28,7 @@ public class AppProperties {
|
|||
String arch;
|
||||
boolean image;
|
||||
boolean staging;
|
||||
@Getter
|
||||
Path dataDir;
|
||||
boolean showcase;
|
||||
|
||||
|
@ -43,10 +45,10 @@ public class AppProperties {
|
|||
.orElse(UUID.randomUUID());
|
||||
sentryUrl = System.getProperty("io.xpipe.app.sentryUrl");
|
||||
arch = System.getProperty("io.xpipe.app.arch");
|
||||
dataDir = parseDataDir();
|
||||
staging = Optional.ofNullable(System.getProperty("io.xpipe.app.staging"))
|
||||
.map(Boolean::parseBoolean)
|
||||
.orElse(false);
|
||||
dataDir = parseDataDir();
|
||||
showcase = Optional.ofNullable(System.getProperty("io.xpipe.app.showcase"))
|
||||
.map(Boolean::parseBoolean)
|
||||
.orElse(false);
|
||||
|
@ -93,7 +95,7 @@ public class AppProperties {
|
|||
return INSTANCE;
|
||||
}
|
||||
|
||||
private static Path parseDataDir() {
|
||||
private Path parseDataDir() {
|
||||
if (System.getProperty(DATA_DIR_PROP) != null) {
|
||||
try {
|
||||
return Path.of(System.getProperty(DATA_DIR_PROP));
|
||||
|
@ -101,11 +103,7 @@ public class AppProperties {
|
|||
}
|
||||
}
|
||||
|
||||
return Path.of(System.getProperty("user.home"), ".xpipe");
|
||||
}
|
||||
|
||||
public Path getDataDir() {
|
||||
return dataDir;
|
||||
return Path.of(System.getProperty("user.home"), isStaging() ? ".xpipe_stage" : ".xpipe");
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
|
|
|
@ -13,11 +13,9 @@ import javafx.animation.KeyFrame;
|
|||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Application;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.stage.Window;
|
||||
import javafx.util.Duration;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
@ -25,13 +23,6 @@ import lombok.Getter;
|
|||
|
||||
public class AppTheme {
|
||||
|
||||
public record AccentColor(Color primaryColor, PseudoClass pseudoClass) {
|
||||
|
||||
public static AccentColor xpipeBlue() {
|
||||
return new AccentColor(Color.web("#11B4B4"), PseudoClass.getPseudoClass("accent-primer-purple"));
|
||||
}
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
if (AppPrefs.get() == null) {
|
||||
return;
|
||||
|
|
|
@ -25,19 +25,17 @@ public class BaseMode extends OperationMode {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onSwitchTo() {}
|
||||
|
||||
@Override
|
||||
public void onSwitchFrom() {}
|
||||
|
||||
@Override
|
||||
public void initialSetup() throws Exception {
|
||||
public void onSwitchTo() throws Throwable {
|
||||
TrackEvent.info("mode", "Initializing base mode components ...");
|
||||
AppExtensionManager.init(true);
|
||||
JacksonMapper.initModularized(AppExtensionManager.getInstance().getExtendedLayer());
|
||||
JacksonMapper.configure(objectMapper -> {
|
||||
objectMapper.registerSubtypes(LockedSecretValue.class, DefaultSecretValue.class);
|
||||
});
|
||||
// Load translations before storage initialization to localize store error messages
|
||||
// Also loaded before antivirus alert to localize that
|
||||
AppI18n.init();
|
||||
AppAntivirusAlert.showIfNeeded();
|
||||
LocalStore.init();
|
||||
AppPrefs.init();
|
||||
AppCharsets.init();
|
||||
|
@ -49,6 +47,9 @@ public class BaseMode extends OperationMode {
|
|||
TrackEvent.info("mode", "Finished base components initialization");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwitchFrom() {}
|
||||
|
||||
@Override
|
||||
public void finalTeardown() {
|
||||
TrackEvent.info("mode", "Background mode shutdown started");
|
||||
|
|
|
@ -18,11 +18,8 @@ public class GuiMode extends PlatformMode {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onSwitchTo() {
|
||||
if (PlatformState.getCurrent() == PlatformState.NOT_INITIALIZED) {
|
||||
super.platformSetup();
|
||||
}
|
||||
|
||||
public void onSwitchTo() throws Throwable {
|
||||
super.platformSetup();
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
|
|
|
@ -215,20 +215,20 @@ public abstract class OperationMode {
|
|||
OperationMode.halt(hasError ? 1 : 0);
|
||||
}
|
||||
|
||||
public static synchronized void reload() {
|
||||
ThreadHelper.create("reloader", false, () -> {
|
||||
try {
|
||||
switchTo(BACKGROUND);
|
||||
CURRENT.finalTeardown();
|
||||
CURRENT.initialSetup();
|
||||
switchTo(GUI);
|
||||
} catch (Throwable t) {
|
||||
ErrorEvent.fromThrowable(t).build().handle();
|
||||
OperationMode.halt(1);
|
||||
}
|
||||
})
|
||||
.start();
|
||||
}
|
||||
// public static synchronized void reload() {
|
||||
// ThreadHelper.create("reloader", false, () -> {
|
||||
// try {
|
||||
// switchTo(BACKGROUND);
|
||||
// CURRENT.finalTeardown();
|
||||
// CURRENT.onSwitchTo();
|
||||
// switchTo(GUI);
|
||||
// } catch (Throwable t) {
|
||||
// ErrorEvent.fromThrowable(t).build().handle();
|
||||
// OperationMode.halt(1);
|
||||
// }
|
||||
// })
|
||||
// .start();
|
||||
// }
|
||||
|
||||
private static synchronized void set(OperationMode newMode) {
|
||||
if (CURRENT == null && newMode == null) {
|
||||
|
@ -240,17 +240,17 @@ public abstract class OperationMode {
|
|||
}
|
||||
|
||||
try {
|
||||
if (CURRENT == null) {
|
||||
CURRENT = newMode;
|
||||
newMode.initialSetup();
|
||||
} else if (newMode == null) {
|
||||
if (newMode == null) {
|
||||
shutdown(false, false);
|
||||
} else {
|
||||
var cur = CURRENT;
|
||||
cur.onSwitchFrom();
|
||||
CURRENT = newMode;
|
||||
newMode.onSwitchTo();
|
||||
return;
|
||||
}
|
||||
|
||||
if (CURRENT != null) {
|
||||
CURRENT.onSwitchFrom();
|
||||
}
|
||||
|
||||
newMode.onSwitchTo();
|
||||
CURRENT = newMode;
|
||||
} catch (Throwable ex) {
|
||||
ErrorEvent.fromThrowable(ex).terminal(true).build().handle();
|
||||
}
|
||||
|
@ -268,12 +268,10 @@ public abstract class OperationMode {
|
|||
|
||||
public abstract String getId();
|
||||
|
||||
public abstract void onSwitchTo();
|
||||
public abstract void onSwitchTo() throws Throwable;
|
||||
|
||||
public abstract void onSwitchFrom();
|
||||
|
||||
public abstract void initialSetup() throws Throwable;
|
||||
|
||||
public abstract void finalTeardown() throws Throwable;
|
||||
|
||||
public abstract ErrorHandler getErrorHandler();
|
||||
|
|
|
@ -10,54 +10,28 @@ import io.xpipe.app.util.ThreadHelper;
|
|||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public abstract class PlatformMode extends OperationMode {
|
||||
|
||||
private static boolean stateInitialized;
|
||||
public static boolean HAS_GRAPHICS;
|
||||
public static boolean PLATFORM_LOADED;
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
PlatformState.initPlatform();
|
||||
return PlatformState.getCurrent() == PlatformState.RUNNING;
|
||||
}
|
||||
|
||||
private static void initState() {
|
||||
if (stateInitialized) {
|
||||
protected void platformSetup() throws Throwable {
|
||||
if (App.getApp() != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
GraphicsDevice[] screenDevices =
|
||||
GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices();
|
||||
HAS_GRAPHICS = screenDevices != null && screenDevices.length > 0;
|
||||
} catch (HeadlessException e) {
|
||||
TrackEvent.warn(e.getMessage());
|
||||
HAS_GRAPHICS = false;
|
||||
}
|
||||
|
||||
try {
|
||||
Platform.startup(() -> {});
|
||||
PLATFORM_LOADED = true;
|
||||
} catch (Throwable t) {
|
||||
TrackEvent.warn(t.getMessage());
|
||||
PLATFORM_LOADED = false;
|
||||
}
|
||||
|
||||
stateInitialized = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
initState();
|
||||
return HAS_GRAPHICS && PLATFORM_LOADED;
|
||||
}
|
||||
|
||||
protected void platformSetup() {
|
||||
if (App.getApp() != null) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
TrackEvent.info("mode", "Platform mode initial setup");
|
||||
AppI18n.init();
|
||||
var r = PlatformState.initPlatform();
|
||||
if (r.isPresent()) {
|
||||
throw r.get();
|
||||
}
|
||||
|
||||
AppFont.loadFonts();
|
||||
AppTheme.init();
|
||||
AppStyle.init();
|
||||
|
@ -107,18 +81,11 @@ public abstract class PlatformMode extends OperationMode {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialSetup() throws Throwable {
|
||||
BACKGROUND.initialSetup();
|
||||
onSwitchTo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finalTeardown() throws Throwable {
|
||||
TrackEvent.info("mode", "Shutting down platform components");
|
||||
onSwitchFrom();
|
||||
Platform.exit();
|
||||
PlatformState.setCurrent(PlatformState.EXITED);
|
||||
PlatformState.teardown();
|
||||
TrackEvent.info("mode", "Platform shutdown finished");
|
||||
BACKGROUND.finalTeardown();
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package io.xpipe.app.core.mode;
|
|||
import com.dustinredmond.fxtrayicon.FXTrayIcon;
|
||||
import io.xpipe.app.core.AppTray;
|
||||
import io.xpipe.app.issue.*;
|
||||
import io.xpipe.app.util.PlatformState;
|
||||
|
||||
public class TrayMode extends PlatformMode {
|
||||
|
||||
|
@ -18,10 +17,8 @@ public class TrayMode extends PlatformMode {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onSwitchTo() {
|
||||
if (PlatformState.getCurrent() == PlatformState.NOT_INITIALIZED) {
|
||||
super.platformSetup();
|
||||
}
|
||||
public void onSwitchTo() throws Throwable {
|
||||
super.platformSetup();
|
||||
|
||||
if (AppTray.get() == null) {
|
||||
TrackEvent.info("mode", "Initializing tray");
|
||||
|
|
|
@ -12,7 +12,7 @@ public class LaunchExchangeImpl extends LaunchExchange
|
|||
|
||||
@Override
|
||||
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
|
||||
var store = getStoreEntryByName(msg.getName(), false);
|
||||
var store = getStoreEntryById(msg.getId(), false);
|
||||
if (store.getStore() instanceof LaunchableStore s) {
|
||||
var command = s.prepareLaunchCommand(store.getName());
|
||||
var split = CommandLine.parse(command);
|
||||
|
|
|
@ -12,6 +12,7 @@ import io.xpipe.beacon.exchange.MessageExchange;
|
|||
import io.xpipe.core.dialog.Dialog;
|
||||
import io.xpipe.core.impl.NamedStore;
|
||||
import io.xpipe.core.source.DataSource;
|
||||
import io.xpipe.core.source.DataStoreId;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import lombok.NonNull;
|
||||
|
||||
|
@ -63,6 +64,18 @@ public interface MessageExchangeImpl<RQ extends RequestMessage, RS extends Respo
|
|||
return store.get();
|
||||
}
|
||||
|
||||
default DataStoreEntry getStoreEntryById(@NonNull DataStoreId id, boolean acceptDisabled) throws ClientException {
|
||||
var store = DataStorage.get().getStoreEntryIfPresent(id);
|
||||
if (store.isEmpty()) {
|
||||
throw new ClientException("No store with id " + id + " was found");
|
||||
}
|
||||
if (store.get().isDisabled() && !acceptDisabled) {
|
||||
throw new ClientException(
|
||||
String.format("Store %s is disabled", store.get().getName()));
|
||||
}
|
||||
return store.get();
|
||||
}
|
||||
|
||||
String getId();
|
||||
|
||||
RS handleRequest(BeaconHandler handler, RQ msg) throws Exception;
|
||||
|
|
|
@ -8,8 +8,6 @@ import io.xpipe.beacon.exchange.ReadExchange;
|
|||
import io.xpipe.core.dialog.Dialog;
|
||||
import io.xpipe.core.dialog.QueryConverter;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class ReadExchangeImpl extends ReadExchange
|
||||
implements MessageExchangeImpl<ReadExchange.Request, ReadExchange.Response> {
|
||||
|
||||
|
@ -46,11 +44,6 @@ public class ReadExchangeImpl extends ReadExchange
|
|||
return toCompleteConfig(defaultDesc, provider, msg.isConfigureAll());
|
||||
});
|
||||
|
||||
var noTarget = msg.getTarget() == null;
|
||||
var colName = noTarget ? null : msg.getTarget().getCollectionName();
|
||||
var entryName =
|
||||
noTarget ? UUID.randomUUID().toString() : msg.getTarget().getEntryName();
|
||||
|
||||
return Response.builder().build();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
package io.xpipe.app.exchange.cli;
|
||||
|
||||
import io.xpipe.app.exchange.MessageExchangeImpl;
|
||||
import io.xpipe.beacon.BeaconHandler;
|
||||
import io.xpipe.beacon.ClientException;
|
||||
import io.xpipe.beacon.exchange.DrainExchange;
|
||||
import io.xpipe.core.store.ShellStore;
|
||||
|
||||
public class DrainExchangeImpl extends DrainExchange
|
||||
implements MessageExchangeImpl<DrainExchange.Request, DrainExchange.Response> {
|
||||
|
||||
@Override
|
||||
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
|
||||
var ds = getStoreEntryById(msg.getSource(), false);
|
||||
|
||||
if (!(ds.getStore() instanceof ShellStore)) {
|
||||
throw new ClientException("Can't open file system for connection");
|
||||
}
|
||||
|
||||
handler.postResponse(() -> {
|
||||
ShellStore store = ds.getStore().asNeeded();
|
||||
try (var fs = store.createFileSystem();
|
||||
var output = handler.sendBody();
|
||||
var inputStream = fs.openInput(msg.getPath())) {
|
||||
inputStream.transferTo(output);
|
||||
}
|
||||
});
|
||||
return Response.builder().build();
|
||||
}
|
||||
}
|
|
@ -15,12 +15,13 @@ public class ListStoresExchangeImpl extends ListStoresExchange
|
|||
public Response handleRequest(BeaconHandler handler, Request msg) {
|
||||
DataStorage s = DataStorage.get();
|
||||
var e = s.getStoreEntries().stream()
|
||||
.filter(entry -> !entry.isDisabled() && entry.getProvider().canManuallyCreate())
|
||||
.sorted(Comparator.comparing(dataStoreEntry -> dataStoreEntry.getLastUsed()))
|
||||
.filter(entry -> !entry.isDisabled())
|
||||
.map(col -> StoreListEntry.builder()
|
||||
.name(col.getName())
|
||||
.id(DataStorage.get().getId(col))
|
||||
.type(col.getProvider().getId())
|
||||
.information(col.getInformation())
|
||||
.build())
|
||||
.sorted(Comparator.comparing(en -> en.getId().toString()))
|
||||
.toList();
|
||||
return Response.builder().entries(e).build();
|
||||
}
|
||||
|
|
|
@ -1,42 +1,14 @@
|
|||
package io.xpipe.app.exchange.cli;
|
||||
|
||||
import io.xpipe.app.exchange.MessageExchangeImpl;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.beacon.BeaconHandler;
|
||||
import io.xpipe.beacon.ServerException;
|
||||
import io.xpipe.beacon.exchange.cli.ReadDrainExchange;
|
||||
import io.xpipe.core.impl.SinkDrainStore;
|
||||
import io.xpipe.core.store.StreamDataStore;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Optional;
|
||||
|
||||
public class ReadDrainExchangeImpl extends ReadDrainExchange
|
||||
implements MessageExchangeImpl<ReadDrainExchange.Request, ReadDrainExchange.Response> {
|
||||
|
||||
@Override
|
||||
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
|
||||
var ds = DataStorage.get().getStoreEntryIfPresent(msg.getName());
|
||||
if (ds.isEmpty()) {
|
||||
ds = Optional.of(DataStorage.get().addStoreEntry(msg.getName(), msg.getStore()));
|
||||
}
|
||||
|
||||
if (!(ds.get().getStore() instanceof SinkDrainStore)) {
|
||||
throw new ServerException("Data store is not a drain");
|
||||
}
|
||||
|
||||
DataStoreEntry finalDs = ds.get();
|
||||
handler.postResponse(() -> {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
StreamDataStore store = finalDs.getStore().asNeeded();
|
||||
try (var output = handler.sendBody();
|
||||
InputStream inputStream = store.openInput()) {
|
||||
inputStream.transferTo(output);
|
||||
}
|
||||
});
|
||||
});
|
||||
return ReadDrainExchange.Response.builder().build();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package io.xpipe.app.exchange.cli;
|
||||
|
||||
import io.xpipe.app.exchange.MessageExchangeImpl;
|
||||
import io.xpipe.beacon.BeaconHandler;
|
||||
import io.xpipe.beacon.ClientException;
|
||||
import io.xpipe.beacon.exchange.SinkExchange;
|
||||
import io.xpipe.core.store.ShellStore;
|
||||
|
||||
public class SinkExchangeImpl extends SinkExchange
|
||||
implements MessageExchangeImpl<SinkExchange.Request, SinkExchange.Response> {
|
||||
|
||||
@Override
|
||||
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
|
||||
var ds = getStoreEntryById(msg.getSource(), false);
|
||||
|
||||
if (!(ds.getStore() instanceof ShellStore)) {
|
||||
throw new ClientException("Can't open file system for connection");
|
||||
}
|
||||
|
||||
ShellStore store = ds.getStore().asNeeded();
|
||||
try (var fs = store.createFileSystem();
|
||||
var inputStream = handler.receiveBody();
|
||||
var output = fs.openOutput(msg.getPath())) {
|
||||
inputStream.transferTo(output);
|
||||
}
|
||||
|
||||
return Response.builder().build();
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@ package io.xpipe.app.ext;
|
|||
|
||||
import io.xpipe.app.util.Validator;
|
||||
import io.xpipe.core.source.DataSource;
|
||||
import io.xpipe.core.source.DataSourceId;
|
||||
import io.xpipe.core.source.DataStoreId;
|
||||
import io.xpipe.core.util.ModuleLayerLoader;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.scene.layout.Region;
|
||||
|
@ -47,11 +47,11 @@ public interface DataSourceTarget {
|
|||
return ALL;
|
||||
}
|
||||
|
||||
default InstructionsDisplay createRetrievalInstructions(DataSource<?> source, ObservableValue<DataSourceId> id) {
|
||||
default InstructionsDisplay createRetrievalInstructions(DataSource<?> source, ObservableValue<DataStoreId> id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
default InstructionsDisplay createUpdateInstructions(DataSource<?> source, ObservableValue<DataSourceId> id) {
|
||||
default InstructionsDisplay createUpdateInstructions(DataSource<?> source, ObservableValue<DataStoreId> id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import io.xpipe.app.comp.storage.store.StoreSectionComp;
|
|||
import io.xpipe.app.comp.storage.store.StoreEntryWrapper;
|
||||
import io.xpipe.app.comp.storage.store.StoreSection;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.AppImages;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.core.dialog.Dialog;
|
||||
|
@ -65,12 +66,7 @@ public interface DataStoreProvider {
|
|||
: SystemStateComp.State.OTHER;
|
||||
},
|
||||
w.getState());
|
||||
var name = Bindings.createStringBinding(
|
||||
() -> {
|
||||
return w.getState().getValue() == DataStoreEntry.State.COMPLETE_BUT_INVALID ? "stop" : "start";
|
||||
},
|
||||
w.getState());
|
||||
return new SystemStateComp(name, state);
|
||||
return new SystemStateComp(state);
|
||||
}
|
||||
|
||||
default Comp<?> createInsightsComp(ObservableValue<DataStore> store) {
|
||||
|
@ -159,7 +155,13 @@ public interface DataStoreProvider {
|
|||
}
|
||||
|
||||
default String getDisplayIconFileName(DataStore store) {
|
||||
return getModuleName() + ":" + getId() + "_icon.png";
|
||||
var png = getModuleName() + ":" + getId() + "_icon.png";
|
||||
if (AppImages.hasNormalImage(png)) {
|
||||
return png;
|
||||
}
|
||||
|
||||
var svg = getModuleName() + ":" + getId() + "_icon.svg";
|
||||
return svg;
|
||||
}
|
||||
|
||||
default Dialog dialogForStore(DataStore store) {
|
||||
|
|
|
@ -17,6 +17,7 @@ public class DataStoreProviders {
|
|||
if (ALL == null) {
|
||||
ALL = ServiceLoader.load(layer, DataStoreProvider.class).stream()
|
||||
.map(ServiceLoader.Provider::get)
|
||||
.sorted(Comparator.comparing(DataStoreProvider::getId))
|
||||
.collect(Collectors.toList());
|
||||
ALL.removeIf(p -> {
|
||||
try {
|
||||
|
@ -35,10 +36,6 @@ public class DataStoreProviders {
|
|||
}
|
||||
|
||||
public static void postInit(ModuleLayer layer) {
|
||||
ALL = ServiceLoader.load(layer, DataStoreProvider.class).stream()
|
||||
.map(ServiceLoader.Provider::get)
|
||||
.sorted(Comparator.comparing(DataStoreProvider::getId))
|
||||
.collect(Collectors.toList());
|
||||
ALL.forEach(p -> {
|
||||
try {
|
||||
p.postInit();
|
||||
|
|
|
@ -33,6 +33,7 @@ public class ChoicePaneComp extends Comp<CompStructure<VBox>> {
|
|||
public CompStructure<VBox> createBase() {
|
||||
var list = FXCollections.observableArrayList(entries);
|
||||
var cb = new ComboBox<>(list);
|
||||
cb.getSelectionModel().select(selected.getValue());
|
||||
cb.setConverter(new StringConverter<>() {
|
||||
@Override
|
||||
public String toString(Entry object) {
|
||||
|
@ -52,7 +53,7 @@ public class ChoicePaneComp extends Comp<CompStructure<VBox>> {
|
|||
var vbox = new VBox(transformer.apply(cb));
|
||||
vbox.setFillWidth(true);
|
||||
cb.prefWidthProperty().bind(vbox.widthProperty());
|
||||
cb.valueProperty().addListener((c, o, n) -> {
|
||||
SimpleChangeListener.apply(cb.valueProperty(), n-> {
|
||||
if (n == null) {
|
||||
vbox.getChildren().remove(1);
|
||||
} else {
|
||||
|
|
|
@ -28,7 +28,7 @@ public class FileStoreChoiceComp extends SimpleComp {
|
|||
|
||||
public FileStoreChoiceComp(boolean hideFileSystem, Property<FileSystemStore> fileSystem, Property<String> filePath) {
|
||||
this.hideFileSystem = hideFileSystem;
|
||||
this.fileSystem = fileSystem;
|
||||
this.fileSystem = fileSystem != null ? fileSystem : new SimpleObjectProperty<>();
|
||||
this.filePath = filePath;
|
||||
}
|
||||
|
||||
|
|
|
@ -152,18 +152,15 @@ public class OptionsComp extends Comp<CompStructure<Pane>> {
|
|||
if (entries.stream().anyMatch(entry -> entry.name() != null && entry.description() == null)) {
|
||||
var nameWidthBinding = Bindings.createDoubleBinding(
|
||||
() -> {
|
||||
if (nameRegions.stream().anyMatch(r -> r.getWidth() == 0)) {
|
||||
return Region.USE_COMPUTED_SIZE;
|
||||
}
|
||||
|
||||
var m = nameRegions.stream()
|
||||
.map(Region::getWidth)
|
||||
.filter(aDouble -> aDouble > 0.0)
|
||||
.max(Double::compareTo)
|
||||
.orElse(0.0);
|
||||
.orElse(Region.USE_COMPUTED_SIZE);
|
||||
return m;
|
||||
},
|
||||
nameRegions.stream().map(Region::widthProperty).toList().toArray(new Observable[0]));
|
||||
nameRegions.forEach(r -> r.prefWidthProperty().bind(nameWidthBinding));
|
||||
nameRegions.forEach(r -> r.minWidthProperty().bind(nameWidthBinding));
|
||||
}
|
||||
|
||||
return new SimpleCompStructure<>(pane);
|
||||
|
|
|
@ -4,6 +4,8 @@ import io.xpipe.app.core.AppImages;
|
|||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
|
@ -13,6 +15,8 @@ import javafx.scene.image.ImageView;
|
|||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class PrettyImageComp extends SimpleComp {
|
||||
|
||||
private final ObservableValue<String> value;
|
||||
|
@ -57,8 +61,17 @@ public class PrettyImageComp extends SimpleComp {
|
|||
var svgImageContent = new SimpleStringProperty();
|
||||
var storeIcon = SvgView.create(svgImageContent);
|
||||
SimpleChangeListener.apply(image, newValue -> {
|
||||
if (newValue == null) {
|
||||
svgImageContent.set(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (AppImages.hasSvgImage(newValue)) {
|
||||
svgImageContent.set(AppImages.svgImage(newValue));
|
||||
} else if (AppImages.hasSvgImage(newValue.replace("-dark", ""))) {
|
||||
svgImageContent.set(AppImages.svgImage(newValue.replace("-dark", "")));
|
||||
} else {
|
||||
svgImageContent.set(null);
|
||||
}
|
||||
});
|
||||
var ar = Bindings.createDoubleBinding(
|
||||
|
@ -86,11 +99,17 @@ public class PrettyImageComp extends SimpleComp {
|
|||
.imageProperty()
|
||||
.bind(Bindings.createObjectBinding(
|
||||
() -> {
|
||||
if (!AppImages.hasNormalImage(image.getValue())) {
|
||||
if (image.get() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return AppImages.image(image.getValue());
|
||||
if (AppImages.hasNormalImage(image.getValue())) {
|
||||
return AppImages.image(image.getValue());
|
||||
} else if (AppImages.hasNormalImage(image.getValue().replace("-dark", ""))) {
|
||||
return AppImages.image(image.getValue().replace("-dark", ""));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
image));
|
||||
var ar = Bindings.createDoubleBinding(
|
||||
|
@ -110,8 +129,9 @@ public class PrettyImageComp extends SimpleComp {
|
|||
stack.getChildren().add(storeIcon);
|
||||
}
|
||||
|
||||
SimpleChangeListener.apply(PlatformThread.sync(value), val -> {
|
||||
image.set(val);
|
||||
Consumer<String> update = val -> {
|
||||
var fixed = val != null ? FileNames.getBaseName(val) + (AppPrefs.get().theme.get().getTheme().isDarkMode() ? "-dark" : "") + "." + FileNames.getExtension(val) : null;
|
||||
image.set(fixed);
|
||||
aspectRatioProperty.unbind();
|
||||
|
||||
if (val == null) {
|
||||
|
@ -126,6 +146,11 @@ public class PrettyImageComp extends SimpleComp {
|
|||
stack.getChildren().get(0).setOpacity(0.0);
|
||||
stack.getChildren().get(1).setOpacity(1.0);
|
||||
}
|
||||
};
|
||||
|
||||
SimpleChangeListener.apply(PlatformThread.sync(value), val -> update.accept(val));
|
||||
AppPrefs.get().theme.addListener((observable, oldValue, newValue) -> {
|
||||
update.accept(value.getValue());
|
||||
});
|
||||
|
||||
stack.setFocusTraversable(false);
|
||||
|
|
|
@ -10,6 +10,8 @@ import javafx.beans.property.Property;
|
|||
import javafx.scene.control.PasswordField;
|
||||
import javafx.scene.control.TextField;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class SecretFieldComp extends Comp<CompStructure<TextField>> {
|
||||
|
||||
private final Property<SecretValue> value;
|
||||
|
@ -32,6 +34,11 @@ public class SecretFieldComp extends Comp<CompStructure<TextField>> {
|
|||
});
|
||||
value.addListener((c, o, n) -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
// Check if control value is the same. Then don't set it as that might cause bugs
|
||||
if ((n == null && text.getText().isEmpty()) || Objects.equals(text.getText(), n != null ? n.getSecretValue() : null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
text.setText(n != null ? n.getSecretValue() : null);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -41,6 +41,12 @@ public class TextAreaComp extends SimpleComp {
|
|||
lastAppliedValue.addListener((c, o, n) -> {
|
||||
currentValue.setValue(n);
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
// Check if control value is the same. Then don't set it as that might cause bugs
|
||||
if (Objects.equals(text.getText(), n)
|
||||
|| (n == null && text.getText().isEmpty())) {
|
||||
return;
|
||||
}
|
||||
|
||||
text.setText(n);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -41,6 +41,7 @@ public class SentryErrorHandler implements ErrorHandler {
|
|||
options.setTag("os", System.getProperty("os.name"));
|
||||
options.setTag("osVersion", System.getProperty("os.version"));
|
||||
options.setTag("arch", System.getProperty("os.arch"));
|
||||
options.setTag("updatesEnabled", AppPrefs.get() != null ? AppPrefs.get().automaticallyUpdate().getValue().toString() : "unknown");
|
||||
options.setDist(XPipeDistributionType.get().getId());
|
||||
if (AppProperties.get().isStaging()) {
|
||||
options.setTag("staging", "true");
|
||||
|
|
|
@ -6,6 +6,7 @@ import io.xpipe.app.core.mode.OperationMode;
|
|||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.issue.LogErrorHandler;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.beacon.BeaconServer;
|
||||
import io.xpipe.beacon.exchange.FocusExchange;
|
||||
|
@ -112,12 +113,14 @@ public class LauncherCommand implements Callable<Integer> {
|
|||
return XPipeDaemonMode.get(opModeName);
|
||||
}
|
||||
|
||||
return XPipeDaemonMode.GUI;
|
||||
return AppPrefs.get() != null ? AppPrefs.get().startupBehaviour().getValue().getMode() : XPipeDaemonMode.GUI;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer call() {
|
||||
checkStart();
|
||||
// Initialize base mode first to have access to the preferences to determine effective mode
|
||||
OperationMode.switchTo(OperationMode.BACKGROUND);
|
||||
OperationMode.switchTo(OperationMode.map(getEffectiveMode()));
|
||||
LauncherInput.handle(inputs);
|
||||
|
||||
|
|
|
@ -13,10 +13,12 @@ import javafx.beans.property.ObjectProperty;
|
|||
import javafx.event.EventHandler;
|
||||
import javafx.event.EventType;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
public class AppPreferencesFx {
|
||||
|
||||
private final PreferencesFxModel preferencesFxModel;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package io.xpipe.app.prefs;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import com.dlsc.formsfx.model.structure.*;
|
||||
import com.dlsc.preferencesfx.formsfx.view.controls.SimpleComboBoxControl;
|
||||
import com.dlsc.preferencesfx.formsfx.view.controls.SimpleControl;
|
||||
|
@ -10,15 +11,21 @@ import com.dlsc.preferencesfx.model.Setting;
|
|||
import com.dlsc.preferencesfx.util.VisibilityProperty;
|
||||
import io.xpipe.app.comp.base.ButtonComp;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.AppLayoutModel;
|
||||
import io.xpipe.app.core.AppProperties;
|
||||
import io.xpipe.app.core.AppTheme;
|
||||
import io.xpipe.app.ext.PrefsChoiceValue;
|
||||
import io.xpipe.app.ext.PrefsHandler;
|
||||
import io.xpipe.app.ext.PrefsProvider;
|
||||
import io.xpipe.app.fxcomps.impl.HorizontalComp;
|
||||
import io.xpipe.app.fxcomps.impl.StackComp;
|
||||
import io.xpipe.app.fxcomps.impl.TextFieldComp;
|
||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.util.LockChangeAlert;
|
||||
import io.xpipe.app.util.LockedSecretValue;
|
||||
import io.xpipe.app.util.*;
|
||||
import io.xpipe.core.impl.LocalStore;
|
||||
import io.xpipe.core.process.CommandControl;
|
||||
import io.xpipe.core.process.ShellDialects;
|
||||
import io.xpipe.core.util.ModuleHelper;
|
||||
import io.xpipe.core.util.SecretValue;
|
||||
import javafx.beans.binding.Bindings;
|
||||
|
@ -26,10 +33,12 @@ import javafx.beans.property.*;
|
|||
import javafx.beans.value.ObservableBooleanValue;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import lombok.SneakyThrows;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
@ -65,7 +74,7 @@ public class AppPrefs {
|
|||
private static final Path DEFAULT_STORAGE_DIR =
|
||||
AppProperties.get().getDataDir().resolve("storage");
|
||||
private static final boolean STORAGE_DIR_FIXED =
|
||||
!AppProperties.get().getDataDir().equals(AppProperties.DEFAULT_DATA_DIR);
|
||||
!AppProperties.get().getDataDir().equals(Path.of(System.getProperty("user.home"), AppProperties.get().isStaging() ? ".xpipe_stage" : ".xpipe"));
|
||||
private static final String LOG_LEVEL_PROP = "io.xpipe.app.logLevel";
|
||||
// Lets keep this at trace for now, at least for the alpha
|
||||
private static final String DEFAULT_LOG_LEVEL = "debug";
|
||||
|
@ -156,6 +165,10 @@ public class AppPrefs {
|
|||
private final BooleanField preferTerminalTabsField =
|
||||
BooleanField.ofBooleanType(preferTerminalTabs).render(() -> new CustomToggleControl());
|
||||
|
||||
// Password manager
|
||||
// ================
|
||||
private final StringProperty passwordManagerCommand = typed(new SimpleStringProperty(""), String.class);
|
||||
|
||||
// Start behaviour
|
||||
// ===============
|
||||
private final SimpleListProperty<StartupBehaviour> startupBehaviourList = new SimpleListProperty<>(
|
||||
|
@ -163,9 +176,9 @@ public class AppPrefs {
|
|||
private final ObjectProperty<StartupBehaviour> startupBehaviour =
|
||||
typed(new SimpleObjectProperty<>(StartupBehaviour.GUI), StartupBehaviour.class);
|
||||
|
||||
private final SingleSelectionField<StartupBehaviour> startupBehaviourControl =
|
||||
Field.ofSingleSelectionType(startupBehaviourList, startupBehaviour)
|
||||
.render(() -> new TranslatableComboBoxControl<>());
|
||||
private final SingleSelectionField<StartupBehaviour> startupBehaviourControl = Field.ofSingleSelectionType(
|
||||
startupBehaviourList, startupBehaviour)
|
||||
.render(() -> new TranslatableComboBoxControl<>());
|
||||
|
||||
// Close behaviour
|
||||
// ===============
|
||||
|
@ -209,10 +222,8 @@ public class AppPrefs {
|
|||
// =======
|
||||
private final ObjectProperty<Path> storageDirectory =
|
||||
typed(new SimpleObjectProperty<>(DEFAULT_STORAGE_DIR), Path.class);
|
||||
private final StringField storageDirectoryControl = PrefFields.ofPath(storageDirectory)
|
||||
.validate(
|
||||
CustomValidators.absolutePath(),
|
||||
CustomValidators.directory());
|
||||
private final StringField storageDirectoryControl =
|
||||
PrefFields.ofPath(storageDirectory).validate(CustomValidators.absolutePath(), CustomValidators.directory());
|
||||
|
||||
// Log level
|
||||
// =========
|
||||
|
@ -240,30 +251,35 @@ public class AppPrefs {
|
|||
typed(new SimpleBooleanProperty(false), Boolean.class);
|
||||
private final BooleanField developerDisableUpdateVersionCheckField =
|
||||
BooleanField.ofBooleanType(developerDisableUpdateVersionCheck).render(() -> new CustomToggleControl());
|
||||
private final ObservableBooleanValue developerDisableUpdateVersionCheckEffective = bindDeveloperTrue(developerDisableUpdateVersionCheck);
|
||||
private final ObservableBooleanValue developerDisableUpdateVersionCheckEffective =
|
||||
bindDeveloperTrue(developerDisableUpdateVersionCheck);
|
||||
|
||||
private final BooleanProperty developerDisableGuiRestrictions =
|
||||
typed(new SimpleBooleanProperty(false), Boolean.class);
|
||||
private final BooleanField developerDisableGuiRestrictionsField =
|
||||
BooleanField.ofBooleanType(developerDisableGuiRestrictions).render(() -> new CustomToggleControl());
|
||||
private final ObservableBooleanValue developerDisableGuiRestrictionsEffective = bindDeveloperTrue(developerDisableGuiRestrictions);
|
||||
private final ObservableBooleanValue developerDisableGuiRestrictionsEffective =
|
||||
bindDeveloperTrue(developerDisableGuiRestrictions);
|
||||
|
||||
private final BooleanProperty developerShowHiddenProviders = typed(new SimpleBooleanProperty(false), Boolean.class);
|
||||
private final BooleanField developerShowHiddenProvidersField =
|
||||
BooleanField.ofBooleanType(developerShowHiddenProviders).render(() -> new CustomToggleControl());
|
||||
private final ObservableBooleanValue developerShowHiddenProvidersEffective = bindDeveloperTrue(developerShowHiddenProviders);
|
||||
private final ObservableBooleanValue developerShowHiddenProvidersEffective =
|
||||
bindDeveloperTrue(developerShowHiddenProviders);
|
||||
|
||||
private final BooleanProperty developerShowHiddenEntries = typed(new SimpleBooleanProperty(false), Boolean.class);
|
||||
private final BooleanField developerShowHiddenEntriesField =
|
||||
BooleanField.ofBooleanType(developerShowHiddenEntries).render(() -> new CustomToggleControl());
|
||||
private final ObservableBooleanValue developerShowHiddenEntriesEffective = bindDeveloperTrue(developerShowHiddenEntries);
|
||||
private final ObservableBooleanValue developerShowHiddenEntriesEffective =
|
||||
bindDeveloperTrue(developerShowHiddenEntries);
|
||||
|
||||
private final BooleanProperty developerDisableConnectorInstallationVersionCheck =
|
||||
typed(new SimpleBooleanProperty(false), Boolean.class);
|
||||
private final BooleanField developerDisableConnectorInstallationVersionCheckField = BooleanField.ofBooleanType(
|
||||
developerDisableConnectorInstallationVersionCheck)
|
||||
.render(() -> new CustomToggleControl());
|
||||
private final ObservableBooleanValue developerDisableConnectorInstallationVersionCheckEffective = bindDeveloperTrue(developerDisableConnectorInstallationVersionCheck);
|
||||
private final ObservableBooleanValue developerDisableConnectorInstallationVersionCheckEffective =
|
||||
bindDeveloperTrue(developerDisableConnectorInstallationVersionCheck);
|
||||
|
||||
public ReadOnlyProperty<CloseBehaviour> closeBehaviour() {
|
||||
return closeBehaviour;
|
||||
|
@ -510,12 +526,102 @@ public class AppPrefs {
|
|||
return null;
|
||||
}
|
||||
|
||||
public void selectCategory(int index) {
|
||||
AppLayoutModel.get().selectSettings();
|
||||
preferencesFx
|
||||
.getNavigationPresenter()
|
||||
.setSelectedCategory(preferencesFx.getCategories().get(index));
|
||||
}
|
||||
|
||||
public String passwordManagerString(String key) {
|
||||
if (passwordManagerCommand.get() == null
|
||||
|| passwordManagerCommand.get().isEmpty()
|
||||
|| key == null
|
||||
|| key.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ApplicationHelper.replaceFileArgument(passwordManagerCommand.get(), "KEY", key);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private AppPreferencesFx createPreferences() {
|
||||
var ctr = Setting.class.getDeclaredConstructor(String.class, Element.class, Property.class);
|
||||
ctr.setAccessible(true);
|
||||
var terminalTest = ctr.newInstance(
|
||||
null,
|
||||
new LazyNodeElement<>(() -> new StackComp(
|
||||
List.of(new ButtonComp(AppI18n.observable("test"), new FontIcon("mdi2p-play"), () -> {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
var term = AppPrefs.get().terminalType().getValue();
|
||||
if (term != null) {
|
||||
TerminalHelper.open(
|
||||
"Test",
|
||||
new LocalStore().control().command("echo Test"));
|
||||
}
|
||||
});
|
||||
})))
|
||||
.padding(new Insets(15, 0, 0, 0))
|
||||
.apply(struc -> struc.get().setAlignment(Pos.CENTER_LEFT))
|
||||
.createRegion()),
|
||||
null);
|
||||
var editorTest = ctr.newInstance(
|
||||
null,
|
||||
new LazyNodeElement<>(() -> new StackComp(
|
||||
List.of(new ButtonComp(AppI18n.observable("test"), new FontIcon("mdi2p-play"), () -> {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
var editor =
|
||||
AppPrefs.get().externalEditor().getValue();
|
||||
if (editor != null) {
|
||||
FileOpener.openReadOnlyString("Test");
|
||||
}
|
||||
});
|
||||
})))
|
||||
.padding(new Insets(15, 0, 0, 0))
|
||||
.apply(struc -> struc.get().setAlignment(Pos.CENTER_LEFT))
|
||||
.createRegion()),
|
||||
null);
|
||||
var about = ctr.newInstance(null, new LazyNodeElement<>(() -> new AboutComp().createRegion()), null);
|
||||
var troubleshoot = ctr.newInstance(null, new LazyNodeElement<>(() -> new TroubleshootComp().createRegion()), null);
|
||||
var troubleshoot =
|
||||
ctr.newInstance(null, new LazyNodeElement<>(() -> new TroubleshootComp().createRegion()), null);
|
||||
|
||||
var testPasswordManagerValue = new SimpleStringProperty();
|
||||
var testPasswordManager = ctr.newInstance(
|
||||
"passwordManagerCommandTest",
|
||||
new LazyNodeElement<>(() -> new HorizontalComp(List.of(
|
||||
new TextFieldComp(testPasswordManagerValue)
|
||||
.apply(struc -> struc.get().setPromptText("Test password key"))
|
||||
.styleClass(Styles.LEFT_PILL)
|
||||
.grow(false, true),
|
||||
new ButtonComp(null, new FontIcon("mdi2p-play"), () -> {
|
||||
var cmd = passwordManagerString(testPasswordManagerValue.get());
|
||||
if (cmd == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
TerminalHelper.open(
|
||||
"Password test",
|
||||
new LocalStore()
|
||||
.control()
|
||||
.command(cmd
|
||||
+ "\n" + ShellDialects.getPlatformDefault()
|
||||
.getEchoCommand(
|
||||
"Is this your password?", false))
|
||||
.terminalExitMode(
|
||||
CommandControl.TerminalExitMode
|
||||
.KEEP_OPEN));
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).handle();
|
||||
}
|
||||
})
|
||||
.styleClass(Styles.RIGHT_PILL)
|
||||
.grow(false, true)))
|
||||
.padding(new Insets(15, 0, 0, 0))
|
||||
.apply(struc -> struc.get().setAlignment(Pos.CENTER_LEFT))
|
||||
.apply(struc -> struc.get().setFillHeight(true))
|
||||
.createRegion()),
|
||||
null);
|
||||
|
||||
var categories = new ArrayList<>(List.of(
|
||||
Category.of("about", Group.of(about)),
|
||||
|
@ -523,11 +629,7 @@ public class AppPrefs {
|
|||
"system",
|
||||
Group.of(
|
||||
"appBehaviour",
|
||||
Setting.of(
|
||||
"startupBehaviour",
|
||||
startupBehaviourControl,
|
||||
startupBehaviour
|
||||
),
|
||||
Setting.of("startupBehaviour", startupBehaviourControl, startupBehaviour),
|
||||
Setting.of("closeBehaviour", closeBehaviourControl, closeBehaviour)),
|
||||
Group.of("security", Setting.of("workspaceLock", lockCryptControl, lockCrypt)),
|
||||
Group.of(
|
||||
|
@ -539,7 +641,9 @@ public class AppPrefs {
|
|||
Setting.of("updateToPrereleases", checkForPrereleasesField, checkForPrereleases)),
|
||||
group(
|
||||
"advanced",
|
||||
STORAGE_DIR_FIXED ? null : Setting.of("storageDirectory", storageDirectoryControl, storageDirectory),
|
||||
STORAGE_DIR_FIXED
|
||||
? null
|
||||
: Setting.of("storageDirectory", storageDirectoryControl, storageDirectory),
|
||||
Setting.of("logLevel", logLevelField, internalLogLevel),
|
||||
Setting.of("developerMode", developerModeField, internalDeveloperMode))),
|
||||
Category.of(
|
||||
|
@ -551,10 +655,14 @@ public class AppPrefs {
|
|||
Setting.of("tooltipDelay", tooltipDelayInternal, tooltipDelayMin, tooltipDelayMax),
|
||||
Setting.of("language", languageControl, languageInternal)),
|
||||
Group.of("windowOptions", Setting.of("saveWindowLocation", saveWindowLocationInternal))),
|
||||
Category.of(
|
||||
"passwordManager",
|
||||
Group.of(Setting.of("passwordManagerCommand", passwordManagerCommand), testPasswordManager)),
|
||||
Category.of(
|
||||
"editor",
|
||||
Group.of(
|
||||
Setting.of("editorProgram", externalEditorControl, externalEditor),
|
||||
editorTest,
|
||||
Setting.of("customEditorCommand", customEditorCommandControl, customEditorCommand)
|
||||
.applyVisibility(VisibilityProperty.of(
|
||||
externalEditor.isEqualTo(ExternalEditorType.CUSTOM))),
|
||||
|
@ -564,13 +672,15 @@ public class AppPrefs {
|
|||
editorReloadTimeoutMin,
|
||||
editorReloadTimeoutMax),
|
||||
Setting.of("preferEditorTabs", preferEditorTabsField, preferEditorTabs))),
|
||||
Category.of("terminal",
|
||||
Group.of(
|
||||
Setting.of("terminalProgram", terminalTypeControl, terminalType),
|
||||
Setting.of("customTerminalCommand", customTerminalCommandControl, customTerminalCommand)
|
||||
.applyVisibility(VisibilityProperty.of(
|
||||
terminalType.isEqualTo(ExternalTerminalType.CUSTOM))),
|
||||
Setting.of("preferTerminalTabs", preferTerminalTabsField, preferTerminalTabs))),
|
||||
Category.of(
|
||||
"terminal",
|
||||
Group.of(
|
||||
Setting.of("terminalProgram", terminalTypeControl, terminalType),
|
||||
terminalTest,
|
||||
Setting.of("customTerminalCommand", customTerminalCommandControl, customTerminalCommand)
|
||||
.applyVisibility(VisibilityProperty.of(
|
||||
terminalType.isEqualTo(ExternalTerminalType.CUSTOM))),
|
||||
Setting.of("preferTerminalTabs", preferTerminalTabsField, preferTerminalTabs))),
|
||||
Category.of(
|
||||
"developer",
|
||||
Setting.of(
|
||||
|
@ -604,8 +714,9 @@ public class AppPrefs {
|
|||
return AppPreferencesFx.of(cats);
|
||||
}
|
||||
|
||||
private Group group(String name, Setting<?,?>... settings) {
|
||||
return Group.of(name, Arrays.stream(settings).filter(setting -> setting != null).toArray(Setting[]::new));
|
||||
private Group group(String name, Setting<?, ?>... settings) {
|
||||
return Group.of(
|
||||
name, Arrays.stream(settings).filter(setting -> setting != null).toArray(Setting[]::new));
|
||||
}
|
||||
|
||||
private class PrefsHandlerImpl implements PrefsHandler {
|
||||
|
|
|
@ -2,6 +2,7 @@ package io.xpipe.app.prefs;
|
|||
|
||||
import com.dlsc.formsfx.model.validators.CustomValidator;
|
||||
import com.dlsc.formsfx.model.validators.Validator;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
@ -18,7 +19,7 @@ public class CustomValidators {
|
|||
return false;
|
||||
}
|
||||
},
|
||||
"notAnAbsolutePath");
|
||||
AppI18n.get("notAnAbsolutePath"));
|
||||
}
|
||||
|
||||
public static Validator<String> directory() {
|
||||
|
@ -27,6 +28,6 @@ public class CustomValidators {
|
|||
var p = Path.of(s);
|
||||
return Files.exists(p) && Files.isDirectory(p);
|
||||
},
|
||||
"notADirectory");
|
||||
AppI18n.get("notADirectory"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import io.xpipe.app.issue.ErrorEvent;
|
|||
import io.xpipe.core.impl.LocalStore;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import io.xpipe.core.process.ShellDialects;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
@ -52,7 +53,23 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue {
|
|||
if (c.getExitCode() != 0 || path.isBlank()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(Path.of(path));
|
||||
|
||||
// Check if returned paths are actually valid
|
||||
var valid = path.lines().filter(s -> {
|
||||
try {
|
||||
return Files.exists(Path.of(s));
|
||||
} catch (Exception ex) {
|
||||
return false;
|
||||
}
|
||||
}).toList();
|
||||
|
||||
// Prefer app in proper applications directory
|
||||
var app = valid.stream().filter(s -> s.startsWith("/Applications")).findFirst();
|
||||
if (app.isPresent()) {
|
||||
return app.map(Path::of);
|
||||
}
|
||||
|
||||
return valid.stream().findFirst().map(Path::of);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).omit().handle();
|
||||
|
@ -90,13 +107,35 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue {
|
|||
}
|
||||
}
|
||||
|
||||
public abstract static class WindowsFullPathType extends ExternalApplicationType {
|
||||
public abstract static class WindowsType extends ExternalApplicationType {
|
||||
|
||||
public WindowsFullPathType(String id) {
|
||||
private final String executable;
|
||||
|
||||
public WindowsType(String id, String executable) {
|
||||
super(id);
|
||||
this.executable = executable;
|
||||
}
|
||||
|
||||
protected abstract Optional<Path> determinePath();
|
||||
protected abstract Optional<Path> determineInstallation();
|
||||
|
||||
private Optional<Path> determineFromPath() {
|
||||
// Try to locate if it is in the Path
|
||||
try (var cc = LocalStore.getShell()
|
||||
.command(ShellDialects.getPlatformDefault().getWhichCommand("code.cmd"))
|
||||
.start()) {
|
||||
var out = cc.readStdoutDiscardErr();
|
||||
var exit = cc.getExitCode();
|
||||
if (exit == 0) {
|
||||
var first = out.lines().findFirst();
|
||||
if (first.isPresent()) {
|
||||
return first.map(Path::of);
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
ErrorEvent.fromThrowable(ex).omit().handle();
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSelectable() {
|
||||
|
@ -105,8 +144,13 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue {
|
|||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
var path = determinePath();
|
||||
return path.isPresent() && Files.exists(path.get());
|
||||
var path = determineFromPath();
|
||||
if (path.isPresent() && Files.exists(path.get())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var installation = determineInstallation();
|
||||
return installation.isPresent() && Files.exists(installation.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,30 +1,28 @@
|
|||
package io.xpipe.app.prefs;
|
||||
|
||||
import io.xpipe.app.ext.PrefsChoiceValue;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.util.ApplicationHelper;
|
||||
import io.xpipe.app.util.WindowsRegistry;
|
||||
import io.xpipe.core.impl.LocalStore;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.process.ShellDialects;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public interface ExternalEditorType extends PrefsChoiceValue {
|
||||
|
||||
ExternalEditorType NOTEPAD = new WindowsFullPathType("app.notepad") {
|
||||
ExternalEditorType NOTEPAD = new WindowsType("app.notepad", "notepad") {
|
||||
@Override
|
||||
protected Optional<Path> determinePath() {
|
||||
protected Optional<Path> determineInstallation() {
|
||||
return Optional.of(Path.of(System.getenv("SystemRoot") + "\\System32\\notepad.exe"));
|
||||
}
|
||||
};
|
||||
|
||||
ExternalEditorType VSCODE_WINDOWS = new WindowsFullPathType("app.vscode") {
|
||||
ExternalEditorType VSCODE_WINDOWS = new WindowsType("app.vscode", "code.cmd") {
|
||||
|
||||
@Override
|
||||
public boolean canOpenDirectory() {
|
||||
|
@ -32,23 +30,7 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Path> determinePath() {
|
||||
// Try to locate if it is in the Path
|
||||
try (var cc = LocalStore.getShell()
|
||||
.command(ShellDialects.getPlatformDefault().getWhichCommand("code.cmd"))
|
||||
.start()) {
|
||||
var out = cc.readStdoutDiscardErr();
|
||||
var exit = cc.getExitCode();
|
||||
if (exit == 0) {
|
||||
var first = out.lines().findFirst();
|
||||
if (first.isPresent()) {
|
||||
return first.map(Path::of);
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
ErrorEvent.fromThrowable(ex).omit().handle();
|
||||
}
|
||||
|
||||
protected Optional<Path> determineInstallation() {
|
||||
return Optional.of(Path.of(System.getenv("LOCALAPPDATA"))
|
||||
.resolve("Programs")
|
||||
.resolve("Microsoft VS Code")
|
||||
|
@ -61,10 +43,10 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
|||
return false;
|
||||
}
|
||||
};
|
||||
ExternalEditorType NOTEPADPLUSPLUS_WINDOWS = new WindowsFullPathType("app.notepad++") {
|
||||
ExternalEditorType NOTEPADPLUSPLUS_WINDOWS = new WindowsType("app.notepad++", "notepad++") {
|
||||
|
||||
@Override
|
||||
protected Optional<Path> determinePath() {
|
||||
protected Optional<Path> determineInstallation() {
|
||||
Optional<String> launcherDir;
|
||||
launcherDir = WindowsRegistry.readString(WindowsRegistry.HKEY_LOCAL_MACHINE, "SOFTWARE\\Notepad++", null)
|
||||
.map(p -> p + "\\notepad++.exe");
|
||||
|
@ -137,8 +119,8 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
|||
throw new IllegalStateException("No custom editor command specified");
|
||||
}
|
||||
|
||||
var format = customCommand.contains("$file") ? customCommand : customCommand + " $file";
|
||||
ApplicationHelper.executeLocalApplication(sc -> ApplicationHelper.replaceFileArgument(format, "file", file.toString()), true);
|
||||
var format = customCommand.toLowerCase(Locale.ROOT).contains("$file") ? customCommand : customCommand + " $FILE";
|
||||
ApplicationHelper.executeLocalApplication(sc -> ApplicationHelper.replaceFileArgument(format, "FILE", file.toString()), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -175,11 +157,14 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
|||
}
|
||||
}
|
||||
|
||||
abstract class WindowsFullPathType extends ExternalApplicationType.WindowsFullPathType
|
||||
abstract class WindowsType extends ExternalApplicationType.WindowsType
|
||||
implements ExternalEditorType {
|
||||
|
||||
public WindowsFullPathType(String id) {
|
||||
super(id);
|
||||
private final String executable;
|
||||
|
||||
public WindowsType(String id, String executable) {
|
||||
super(id, executable);
|
||||
this.executable = executable;
|
||||
}
|
||||
|
||||
public boolean detach() {
|
||||
|
@ -188,7 +173,7 @@ public interface ExternalEditorType extends PrefsChoiceValue {
|
|||
|
||||
@Override
|
||||
public void launch(Path file) throws Exception {
|
||||
var path = determinePath();
|
||||
var path = determineInstallation();
|
||||
if (path.isEmpty()) {
|
||||
throw new IOException("Unable to find installation of " + toTranslatedString());
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import lombok.Getter;
|
|||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
@ -25,8 +26,8 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
ExternalTerminalType CMD = new SimplePathType("app.cmd", "cmd.exe") {
|
||||
|
||||
@Override
|
||||
protected String toCommand(String name, String file) {
|
||||
return "/C \"" + file + "\"";
|
||||
protected CommandBuilder toCommand(String name, String file) {
|
||||
return CommandBuilder.of().add("/C").addFile(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -38,8 +39,8 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
ExternalTerminalType POWERSHELL_WINDOWS = new SimplePathType("app.powershell", "powershell") {
|
||||
|
||||
@Override
|
||||
protected String toCommand(String name, String file) {
|
||||
return "-ExecutionPolicy Bypass -NoProfile -Command cmd /C '" + file + "'";
|
||||
protected CommandBuilder toCommand(String name, String file) {
|
||||
return CommandBuilder.of().add("-ExecutionPolicy", "Bypass", "-NoProfile", "-Command", "cmd", "/C").addFile(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -51,10 +52,12 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
ExternalTerminalType PWSH_WINDOWS = new SimplePathType("app.pwsh", "pwsh") {
|
||||
|
||||
@Override
|
||||
protected String toCommand(String name, String file) {
|
||||
protected CommandBuilder toCommand(String name, String file) {
|
||||
// Fix for https://github.com/PowerShell/PowerShell/issues/18530#issuecomment-1325691850
|
||||
var script = ScriptHelper.createLocalExecScript("set \"PSModulePath=\"\r\n\"" + file + "\"\npause");
|
||||
return "-ExecutionPolicy Bypass -NoProfile -Command cmd /C '" + script + "'";
|
||||
return CommandBuilder.of().add("-ExecutionPolicy", "Bypass", "-NoProfile", "-Command", "cmd", "/C").add(sc -> {
|
||||
var script = ScriptHelper.createLocalExecScript("set \"PSModulePath=\"\r\n\"" + file + "\"\npause");
|
||||
return sc.getShellDialect().fileArgument(script);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -66,12 +69,12 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
ExternalTerminalType WINDOWS_TERMINAL = new SimplePathType("app.windowsTerminal", "wt.exe") {
|
||||
|
||||
@Override
|
||||
protected String toCommand(String name, String file) {
|
||||
protected CommandBuilder toCommand(String name, String file) {
|
||||
// A weird behavior in Windows Terminal causes the trailing
|
||||
// backslash of a filepath to escape the closing quote in the title argument
|
||||
// So just remove that slash
|
||||
var fixedName = FileNames.removeTrailingSlash(name);
|
||||
return "-w 1 nt --title \"" + fixedName + "\" \"" + file + "\"";
|
||||
return CommandBuilder.of().add("-w", "1", "nt", "--title").addQuoted(fixedName).addFile(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -83,15 +86,14 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
ExternalTerminalType ALACRITTY_WINDOWS = new SimplePathType("app.alacrittyWindows", "alacritty") {
|
||||
|
||||
@Override
|
||||
protected String toCommand(String name, String file) {
|
||||
protected CommandBuilder toCommand(String name, String file) {
|
||||
return CommandBuilder.of()
|
||||
.add("-t")
|
||||
.addQuoted(name)
|
||||
.add("-e")
|
||||
.add("cmd")
|
||||
.add("/c")
|
||||
.addQuoted(file.replaceAll(" ", "^$0"))
|
||||
.build();
|
||||
.addQuoted(file.replaceAll(" ", "^$0"));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -99,16 +101,16 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
return OsType.getLocal().equals(OsType.WINDOWS);
|
||||
}
|
||||
};
|
||||
abstract class WindowsFullPathType extends ExternalApplicationType.WindowsFullPathType
|
||||
abstract class WindowsType extends ExternalApplicationType.WindowsType
|
||||
implements ExternalTerminalType {
|
||||
|
||||
public WindowsFullPathType(String id) {
|
||||
super(id);
|
||||
public WindowsType(String id, String executable) {
|
||||
super(id, executable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launch(String name, String file, boolean elevated) throws Exception {
|
||||
var path = determinePath();
|
||||
var path = determineInstallation();
|
||||
if (path.isEmpty()) {
|
||||
throw new IOException("Unable to find installation of " + toTranslatedString());
|
||||
}
|
||||
|
@ -120,7 +122,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
protected abstract String createCommand(ShellControl shellControl, String name, String path, String file);
|
||||
}
|
||||
|
||||
ExternalTerminalType TABBY_WINDOWS = new WindowsFullPathType("app.tabbyWindows") {
|
||||
ExternalTerminalType TABBY_WINDOWS = new WindowsType("app.tabbyWindows", "tabby") {
|
||||
|
||||
@Override
|
||||
protected String createCommand(ShellControl shellControl, String name, String path, String file) {
|
||||
|
@ -129,7 +131,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Path> determinePath() {
|
||||
protected Optional<Path> determineInstallation() {
|
||||
Optional<String> launcherDir;
|
||||
launcherDir = WindowsRegistry.readString(
|
||||
WindowsRegistry.HKEY_CURRENT_USER,
|
||||
|
@ -167,7 +169,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
try (ShellControl pc = LocalStore.getShell()) {
|
||||
ApplicationHelper.checkSupport(pc, executable, toTranslatedString(), null);
|
||||
|
||||
var toExecute = executable + " " + toCommand(name, file);
|
||||
var toExecute = executable + " " + toCommand(name, file).build(pc);
|
||||
// In order to fix this bug which also affects us:
|
||||
// https://askubuntu.com/questions/1148475/launching-gnome-terminal-from-vscode
|
||||
toExecute = "GNOME_TERMINAL_SCREEN=\"\" nohup " + toExecute + " </dev/null &>/dev/null & disown";
|
||||
|
@ -176,8 +178,8 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected String toCommand(String name, String file) {
|
||||
return "-v --title \"" + name + "\" -- \"" + file + "\"";
|
||||
protected CommandBuilder toCommand(String name, String file) {
|
||||
return CommandBuilder.of().add("-v", "--title").addQuoted(name).add("--").addFile(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -189,11 +191,11 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
ExternalTerminalType KONSOLE = new SimplePathType("app.konsole", "konsole") {
|
||||
|
||||
@Override
|
||||
protected String toCommand(String name, String file) {
|
||||
protected CommandBuilder toCommand(String name, String file) {
|
||||
// Note for later: When debugging konsole launches, it will always open as a child process of
|
||||
// IntelliJ/XPipe even though we try to detach it.
|
||||
// This is not the case for production where it works as expected
|
||||
return "--new-tab -e \"" + file + "\"";
|
||||
return CommandBuilder.of().add("--new-tab", "-e").add("--").addFile(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -205,8 +207,8 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
ExternalTerminalType XFCE = new SimplePathType("app.xfce", "xfce4-terminal") {
|
||||
|
||||
@Override
|
||||
protected String toCommand(String name, String file) {
|
||||
return "--tab --title \"" + name + "\" --command \"" + file + "\"";
|
||||
protected CommandBuilder toCommand(String name, String file) {
|
||||
return CommandBuilder.of().add("--tab", "--title").addQuoted(name).add("--command").addFile(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -218,14 +220,13 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
ExternalTerminalType TERMINATOR = new SimplePathType("app.terminator", "terminator") {
|
||||
|
||||
@Override
|
||||
protected String toCommand(String name, String file) {
|
||||
protected CommandBuilder toCommand(String name, String file) {
|
||||
return CommandBuilder.of()
|
||||
.add("-e")
|
||||
.addQuoted(file)
|
||||
.add("-T")
|
||||
.addQuoted(name)
|
||||
.add("--new-tab")
|
||||
.build();
|
||||
.add("--new-tab");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -237,8 +238,8 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
ExternalTerminalType KITTY = new SimplePathType("app.kitty", "kitty") {
|
||||
|
||||
@Override
|
||||
protected String toCommand(String name, String file) {
|
||||
return CommandBuilder.of().add("-T").addQuoted(name).addQuoted(file).build();
|
||||
protected CommandBuilder toCommand(String name, String file) {
|
||||
return CommandBuilder.of().add("-T").addQuoted(name).addQuoted(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -250,14 +251,13 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
ExternalTerminalType TERMINOLOGY = new SimplePathType("app.terminology", "terminology") {
|
||||
|
||||
@Override
|
||||
protected String toCommand(String name, String file) {
|
||||
protected CommandBuilder toCommand(String name, String file) {
|
||||
return CommandBuilder.of()
|
||||
.add("-T")
|
||||
.addQuoted(name)
|
||||
.add("-2")
|
||||
.add("-e")
|
||||
.addQuoted(file)
|
||||
.build();
|
||||
.addQuoted(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -269,13 +269,12 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
ExternalTerminalType COOL_RETRO_TERM = new SimplePathType("app.coolRetroTerm", "cool-retro-term") {
|
||||
|
||||
@Override
|
||||
protected String toCommand(String name, String file) {
|
||||
protected CommandBuilder toCommand(String name, String file) {
|
||||
return CommandBuilder.of()
|
||||
.add("-T")
|
||||
.addQuoted(name)
|
||||
.add("-e")
|
||||
.addQuoted(file)
|
||||
.build();
|
||||
.addQuoted(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -287,13 +286,12 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
ExternalTerminalType GUAKE = new SimplePathType("app.guake", "guake") {
|
||||
|
||||
@Override
|
||||
protected String toCommand(String name, String file) {
|
||||
protected CommandBuilder toCommand(String name, String file) {
|
||||
return CommandBuilder.of()
|
||||
.add("-r")
|
||||
.addQuoted(name)
|
||||
.add("-e")
|
||||
.addQuoted(file)
|
||||
.build();
|
||||
.addQuoted(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -305,13 +303,12 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
ExternalTerminalType ALACRITTY = new SimplePathType("app.alacritty", "alacritty") {
|
||||
|
||||
@Override
|
||||
protected String toCommand(String name, String file) {
|
||||
protected CommandBuilder toCommand(String name, String file) {
|
||||
return CommandBuilder.of()
|
||||
.add("-t")
|
||||
.addQuoted(name)
|
||||
.add("-e")
|
||||
.addQuoted(file)
|
||||
.build();
|
||||
.addQuoted(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -323,8 +320,8 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
ExternalTerminalType TILDA = new SimplePathType("app.tilda", "tilda") {
|
||||
|
||||
@Override
|
||||
protected String toCommand(String name, String file) {
|
||||
return CommandBuilder.of().add("-c").addQuoted(file).build();
|
||||
protected CommandBuilder toCommand(String name, String file) {
|
||||
return CommandBuilder.of().add("-c").addQuoted(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -450,9 +447,9 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
throw new IllegalStateException("No custom terminal command specified");
|
||||
}
|
||||
|
||||
var format = custom.contains("$cmd") ? custom : custom + " $cmd";
|
||||
var format = custom.toLowerCase(Locale.ROOT).contains("$cmd") ? custom : custom + " $CMD";
|
||||
try (var pc = LocalStore.getShell()) {
|
||||
var toExecute = ApplicationHelper.replaceFileArgument(format, "cmd", file);
|
||||
var toExecute = ApplicationHelper.replaceFileArgument(format, "CMD", file);
|
||||
if (pc.getOsType().equals(OsType.WINDOWS)) {
|
||||
toExecute = "start \"" + name + "\" " + toExecute;
|
||||
} else {
|
||||
|
@ -579,7 +576,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
.start()) {
|
||||
ApplicationHelper.checkSupport(pc, executable, toTranslatedString(), null);
|
||||
var toExecute = "Start-Process \"" + executable + "\" -Verb RunAs -ArgumentList \""
|
||||
+ toCommand(name, file).replaceAll("\"", "`\"") + "\"";
|
||||
+ toCommand(name, file).build(pc).replaceAll("\"", "`\"") + "\"";
|
||||
pc.executeSimpleCommand(toExecute);
|
||||
}
|
||||
return;
|
||||
|
@ -589,7 +586,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
try (ShellControl pc = LocalStore.getShell()) {
|
||||
ApplicationHelper.checkSupport(pc, executable, toTranslatedString(), null);
|
||||
|
||||
var toExecute = executable + " " + toCommand(name, file);
|
||||
var toExecute = executable + " " + toCommand(name, file).build(pc);
|
||||
if (pc.getOsType().equals(OsType.WINDOWS)) {
|
||||
toExecute = "start \"" + name + "\" " + toExecute;
|
||||
} else {
|
||||
|
@ -599,7 +596,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
}
|
||||
}
|
||||
|
||||
protected abstract String toCommand(String name, String file);
|
||||
protected abstract CommandBuilder toCommand(String name, String file);
|
||||
|
||||
public boolean isAvailable() {
|
||||
try (ShellControl pc = LocalStore.getShell()) {
|
||||
|
|
|
@ -4,6 +4,7 @@ import io.xpipe.app.ext.DataStoreProviders;
|
|||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.source.DataStoreId;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.store.FixedHierarchyStore;
|
||||
import lombok.Getter;
|
||||
|
@ -111,6 +112,10 @@ public abstract class DataStorage {
|
|||
}
|
||||
|
||||
public synchronized List<DataStoreEntry> getStoreChildren(DataStoreEntry entry, boolean display, boolean deep) {
|
||||
if (!entry.getState().isUsable()) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
var children = new ArrayList<>(getStoreEntries().stream()
|
||||
.filter(other -> {
|
||||
if (!other.getState().isUsable()) {
|
||||
|
@ -180,6 +185,18 @@ public abstract class DataStorage {
|
|||
return entry;
|
||||
}
|
||||
|
||||
public DataStoreId getId(DataStoreEntry entry) {
|
||||
var names = new ArrayList<String>();
|
||||
names.add(entry.getName());
|
||||
|
||||
DataStoreEntry current = entry;
|
||||
while ((current = getParent(current, false).orElse(null)) != null) {
|
||||
names.add(0, current.getName());
|
||||
}
|
||||
|
||||
return DataStoreId.create(names.toArray(String[]::new));
|
||||
}
|
||||
|
||||
public synchronized DataStoreEntry getStoreEntry(@NonNull DataStore store) {
|
||||
var entry = storeEntries.stream()
|
||||
.filter(n -> n.getStore() != null && Objects.equals(store.getClass(), n.getStore().getClass()) && store.equals(n.getStore()))
|
||||
|
@ -188,6 +205,25 @@ public abstract class DataStorage {
|
|||
return entry;
|
||||
}
|
||||
|
||||
public synchronized Optional<DataStoreEntry> getStoreEntryIfPresent(@NonNull DataStoreId id) {
|
||||
var current = getStoreEntryIfPresent(id.getNames().get(0));
|
||||
if (current.isPresent()) {
|
||||
for (int i = 1; i < id.getNames().size(); i++) {
|
||||
var children = getStoreChildren(current.get(), false, false);
|
||||
int finalI = i;
|
||||
current = children.stream().filter(dataStoreEntry -> dataStoreEntry.getName().equalsIgnoreCase(id.getNames().get(finalI))).findFirst();
|
||||
if (current.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (current.isPresent()) {
|
||||
return current;
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public synchronized Optional<DataStoreEntry> getStoreEntryIfPresent(@NonNull DataStore store) {
|
||||
var entry =
|
||||
storeEntries.stream().filter(n -> store.equals(n.getStore())).findFirst();
|
||||
|
|
|
@ -100,11 +100,11 @@ public class StandardStorage extends DataStorage {
|
|||
storeEntries.forEach(dataStoreEntry -> dataStoreEntry.simpleRefresh());
|
||||
|
||||
// Remove even incomplete stores when in production
|
||||
if (!AppPrefs.get().isDevelopmentEnvironment()) {
|
||||
storeEntries.removeIf(entry -> {
|
||||
return !entry.getState().isUsable();
|
||||
});
|
||||
}
|
||||
// if (!AppPrefs.get().isDevelopmentEnvironment()) {
|
||||
// storeEntries.removeIf(entry -> {
|
||||
// return !entry.getState().isUsable();
|
||||
// });
|
||||
// }
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
ErrorEvent.fromThrowable(ex).terminal(true).build().handle();
|
||||
|
|
|
@ -203,6 +203,14 @@ public abstract class UpdateHandler {
|
|||
return;
|
||||
}
|
||||
|
||||
// Check if prepared update is still the latest.
|
||||
// We only do that here to minimize the sent requests by only executing when it's really necessary
|
||||
var available = XPipeDistributionType.get().getUpdateHandler().refreshUpdateCheckSilent();
|
||||
if (available != null && !available.getVersion().equals(preparedUpdate.getValue().getVersion())) {
|
||||
preparedUpdate.setValue(null);
|
||||
return;
|
||||
}
|
||||
|
||||
event("Executing update ...");
|
||||
OperationMode.executeAfterShutdown(() -> {
|
||||
try {
|
||||
|
|
|
@ -5,11 +5,16 @@ import io.xpipe.core.impl.LocalStore;
|
|||
import io.xpipe.core.process.ShellControl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class ApplicationHelper {
|
||||
|
||||
public static String replaceFileArgument(String format, String variable, String file) {
|
||||
// Support for legacy variables that were not upper case
|
||||
variable = variable.toUpperCase(Locale.ROOT);
|
||||
format = format.replace("$" + variable.toLowerCase(Locale.ROOT), "$" + variable.toUpperCase(Locale.ROOT));
|
||||
|
||||
var fileString = file.contains(" ") ? "\"" + file + "\"" : file;
|
||||
// Check if the variable is already quoted
|
||||
var replaced = format.replace("\"$" + variable + "\"", fileString).replace("$" + variable, fileString);
|
||||
|
|
62
app/src/main/java/io/xpipe/app/util/AskpassAlert.java
Normal file
|
@ -0,0 +1,62 @@
|
|||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.AppWindowHelper;
|
||||
import io.xpipe.app.fxcomps.impl.SecretFieldComp;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.util.SecretValue;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class AskpassAlert {
|
||||
|
||||
private static final Map<UUID, UUID> requestToId = new HashMap<>();
|
||||
private static final Map<UUID, SecretValue> passwords = new HashMap<>();
|
||||
|
||||
public static SecretValue query(String prompt, DataStore store) {
|
||||
var rid = UUID.randomUUID();
|
||||
var secretId = UUID.nameUUIDFromBytes(ByteBuffer.allocate(4).putInt(store.hashCode()).array());
|
||||
return query(prompt, rid, secretId);
|
||||
}
|
||||
|
||||
public static SecretValue query(String prompt, UUID requestId, UUID secretId) {
|
||||
if (requestToId.containsKey(requestId)) {
|
||||
var id = requestToId.remove(requestId);
|
||||
passwords.remove(id);
|
||||
}
|
||||
|
||||
if (passwords.containsKey(secretId)) {
|
||||
return passwords.get(secretId);
|
||||
}
|
||||
|
||||
var prop = new SimpleObjectProperty<SecretValue>();
|
||||
var r = AppWindowHelper.showBlockingAlert(alert -> {
|
||||
alert.setTitle(AppI18n.get("askpassAlertTitle"));
|
||||
alert.setHeaderText(prompt);
|
||||
alert.setAlertType(Alert.AlertType.CONFIRMATION);
|
||||
|
||||
var text = new SecretFieldComp(prop).createRegion();
|
||||
alert.getDialogPane().setContent(new StackPane(text));
|
||||
})
|
||||
.filter(b -> b.getButtonData().isDefaultButton() && prop.getValue() != null)
|
||||
.map(t -> {
|
||||
// AppCache.update(msg.getId(), prop.getValue());
|
||||
return prop.getValue();
|
||||
})
|
||||
.orElse(null);
|
||||
|
||||
// If the result is null, assume that the operation was aborted by the user
|
||||
if (r != null) {
|
||||
passwords.put(secretId, r);
|
||||
requestToId.put(requestId, secretId);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
}
|
|
@ -1,12 +1,22 @@
|
|||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.core.impl.LocalStore;
|
||||
import io.xpipe.core.process.OsType;
|
||||
|
||||
import java.awt.*;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class DesktopHelper {
|
||||
|
||||
public static Path getDesktopDirectory() throws Exception {
|
||||
if (OsType.getLocal() == OsType.WINDOWS) {
|
||||
return Path.of(LocalStore.getLocalPowershell().executeSimpleStringCommand("[Environment]::GetFolderPath([Environment+SpecialFolder]::Desktop)"));
|
||||
}
|
||||
|
||||
return Path.of(System.getProperty("user.home") + "/Desktop");
|
||||
}
|
||||
|
||||
public static void browsePath(Path file) {
|
||||
if (!Desktop.getDesktop().isSupported(Desktop.Action.OPEN)) {
|
||||
return;
|
||||
|
|
|
@ -5,22 +5,22 @@ import io.xpipe.core.process.OsType;
|
|||
import io.xpipe.core.util.XPipeInstallation;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class DesktopShortcuts {
|
||||
|
||||
private static void createWindowsShortcut(String target, String name) throws Exception {
|
||||
var icon = XPipeInstallation.getLocalDefaultInstallationIcon();
|
||||
var shortcutTarget = XPipeInstallation.getLocalDefaultCliExecutable();
|
||||
var shortcutPath = DesktopHelper.getDesktopDirectory().resolve(name + ".lnk");
|
||||
var content = String.format(
|
||||
"""
|
||||
set "TARGET=%s"
|
||||
set "SHORTCUT=%%HOMEDRIVE%%%%HOMEPATH%%\\Desktop\\%s.lnk"
|
||||
set "SHORTCUT=%s"
|
||||
set PWS=powershell.exe -ExecutionPolicy Bypass -NoLogo -NonInteractive -NoProfile
|
||||
|
||||
%%PWS%% -Command "$ws = New-Object -ComObject WScript.Shell; $s = $ws.CreateShortcut('%%SHORTCUT%%'); $S.IconLocation='%s'; $S.WindowStyle=7; $S.TargetPath = '%%TARGET%%'; $S.Arguments = 'open %s'; $S.Save()"
|
||||
""",
|
||||
shortcutTarget, name, icon, target);
|
||||
shortcutTarget, shortcutPath, icon, target);
|
||||
LocalStore.getShell().executeSimpleCommand(content);
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,7 @@ public class DesktopShortcuts {
|
|||
Categories=Utility;Development;Office;
|
||||
""",
|
||||
name, exec, target, icon);
|
||||
var file = Path.of(System.getProperty("user.home") + "/Desktop/" + name + ".desktop");
|
||||
var file = DesktopHelper.getDesktopDirectory().resolve(name + ".desktop");
|
||||
Files.writeString(file, content);
|
||||
file.toFile().setExecutable(true);
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ public class DesktopShortcuts {
|
|||
private static void createMacOSShortcut(String target, String name) throws Exception {
|
||||
var exec = XPipeInstallation.getLocalDefaultCliExecutable();
|
||||
var icon = XPipeInstallation.getLocalDefaultInstallationIcon();
|
||||
var base = System.getProperty("user.home") + "/Desktop/" + name + ".app";
|
||||
var base = DesktopHelper.getDesktopDirectory().resolve(name + ".app");
|
||||
var content = String.format(
|
||||
"""
|
||||
#!/bin/bash
|
||||
|
|
|
@ -2,9 +2,11 @@ package io.xpipe.app.util;
|
|||
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.ext.GuiDialog;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.impl.*;
|
||||
import io.xpipe.core.util.SecretValue;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
|
@ -14,13 +16,13 @@ import javafx.scene.control.Label;
|
|||
import javafx.scene.layout.Region;
|
||||
import net.synedra.validatorfx.Check;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class OptionsBuilder {
|
||||
|
||||
private final Validator ownValidator;
|
||||
private final List<Validator> allValidators = new ArrayList<>();
|
||||
private final List<OptionsComp.Entry> entries = new ArrayList<>();
|
||||
private final List<Property<?>> props = new ArrayList<>();
|
||||
|
||||
|
@ -29,6 +31,47 @@ public class OptionsBuilder {
|
|||
private String longDescription;
|
||||
private Comp<?> comp;
|
||||
|
||||
public Validator buildEffectiveValidator() {
|
||||
return new ChainedValidator(allValidators);
|
||||
}
|
||||
|
||||
public OptionsBuilder() {
|
||||
this.ownValidator = new SimpleValidator();
|
||||
this.allValidators.add(ownValidator);
|
||||
}
|
||||
|
||||
public OptionsBuilder(Validator validator) {
|
||||
this.ownValidator = validator;
|
||||
this.allValidators.add(ownValidator);
|
||||
}
|
||||
|
||||
public OptionsBuilder choice(IntegerProperty selectedIndex, Map<String, OptionsBuilder> options) {
|
||||
var list = options.entrySet().stream()
|
||||
.map(e -> new ChoicePaneComp.Entry(
|
||||
AppI18n.observable(e.getKey()), e.getValue().buildComp()))
|
||||
.toList();
|
||||
var validatorList =
|
||||
options.values().stream().map(builder -> builder.buildEffectiveValidator()).toList();
|
||||
var selected = new SimpleObjectProperty<>(list.get(selectedIndex.getValue()));
|
||||
selected.addListener((observable, oldValue, newValue) -> {
|
||||
selectedIndex.setValue(newValue != null ? list.indexOf(newValue) : null);
|
||||
});
|
||||
var pane = new ChoicePaneComp(list, selected);
|
||||
|
||||
var validatorMap = new LinkedHashMap<ChoicePaneComp.Entry, Validator>();
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
validatorMap.put(list.get(i), validatorList.get(i));
|
||||
}
|
||||
validatorMap.put(null, new SimpleValidator());
|
||||
var orVal = new ExclusiveValidator<>(validatorMap, selected);
|
||||
|
||||
options.values().forEach(builder -> props.addAll(builder.props));
|
||||
props.add(selectedIndex);
|
||||
allValidators.add(orVal);
|
||||
pushComp(pane);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void finishCurrent() {
|
||||
if (comp == null) {
|
||||
return;
|
||||
|
@ -44,10 +87,20 @@ public class OptionsBuilder {
|
|||
|
||||
public OptionsBuilder sub(OptionsBuilder builder) {
|
||||
props.addAll(builder.props);
|
||||
allValidators.add(builder.buildEffectiveValidator());
|
||||
pushComp(builder.buildComp());
|
||||
return this;
|
||||
}
|
||||
|
||||
public OptionsBuilder sub(OptionsBuilder builder, Property<?> prop) {
|
||||
props.addAll(builder.props);
|
||||
allValidators.add(builder.buildEffectiveValidator());
|
||||
props.add(prop);
|
||||
pushComp(builder.buildComp());
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public OptionsBuilder addTitle(String titleKey) {
|
||||
finishCurrent();
|
||||
entries.add(new OptionsComp.Entry(
|
||||
|
@ -76,6 +129,12 @@ public class OptionsBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
public OptionsBuilder nonNull() {
|
||||
var e = name;
|
||||
var p = props.get(props.size() - 1);
|
||||
return decorate(Validator.nonNull(ownValidator, e, p));
|
||||
}
|
||||
|
||||
public OptionsBuilder nonNull(Validator v) {
|
||||
var e = name;
|
||||
var p = props.get(props.size() - 1);
|
||||
|
@ -130,7 +189,6 @@ public class OptionsBuilder {
|
|||
return addComp(Comp.separator());
|
||||
}
|
||||
|
||||
|
||||
public OptionsBuilder name(String nameKey) {
|
||||
finishCurrent();
|
||||
name = AppI18n.observable(nameKey);
|
||||
|
@ -143,6 +201,12 @@ public class OptionsBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
public OptionsBuilder description(ObservableValue<String> description) {
|
||||
finishCurrent();
|
||||
this.description = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OptionsBuilder longDescription(String descriptionKey) {
|
||||
finishCurrent();
|
||||
longDescription = AppI18n.getInstance().getMarkdownDocumentation(descriptionKey);
|
||||
|
@ -207,4 +271,8 @@ public class OptionsBuilder {
|
|||
public Region build() {
|
||||
return buildComp().createRegion();
|
||||
}
|
||||
|
||||
public GuiDialog buildDialog() {
|
||||
return new GuiDialog(buildComp(), buildEffectiveValidator());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import javafx.application.Platform;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
public enum PlatformState {
|
||||
NOT_INITIALIZED,
|
||||
RUNNING,
|
||||
|
@ -11,4 +17,74 @@ public enum PlatformState {
|
|||
@Getter
|
||||
@Setter
|
||||
private static PlatformState current = PlatformState.NOT_INITIALIZED;
|
||||
|
||||
public static boolean HAS_GRAPHICS;
|
||||
public static boolean PLATFORM_LOADED;
|
||||
|
||||
public static void teardown() {
|
||||
Platform.exit();
|
||||
setCurrent(PlatformState.EXITED);
|
||||
}
|
||||
|
||||
public static void initPlatformOrThrow() throws Throwable {
|
||||
var r = PlatformState.initPlatform();
|
||||
if (r.isPresent()) {
|
||||
throw r.get();
|
||||
}
|
||||
}
|
||||
|
||||
public static Optional<Throwable> initPlatform() {
|
||||
if (current == EXITED) {
|
||||
return Optional.of(new IllegalStateException("Platform has already exited"));
|
||||
}
|
||||
|
||||
if (current == RUNNING) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
try {
|
||||
GraphicsDevice[] screenDevices =
|
||||
GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices();
|
||||
HAS_GRAPHICS = screenDevices != null && screenDevices.length > 0;
|
||||
} catch (HeadlessException e) {
|
||||
TrackEvent.warn(e.getMessage());
|
||||
HAS_GRAPHICS = false;
|
||||
return Optional.of(e);
|
||||
}
|
||||
|
||||
try {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
Platform.setImplicitExit(false);
|
||||
Platform.startup(latch::countDown);
|
||||
try {
|
||||
latch.await();
|
||||
PLATFORM_LOADED = true;
|
||||
PlatformState.setCurrent(PlatformState.RUNNING);
|
||||
return Optional.empty();
|
||||
} catch (InterruptedException e) {
|
||||
return Optional.of(e);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
// Check if we already exited
|
||||
if ("Platform.exit has been called".equals(t.getMessage())) {
|
||||
PLATFORM_LOADED = true;
|
||||
PlatformState.setCurrent(PlatformState.EXITED);
|
||||
return Optional.of(t);
|
||||
}
|
||||
|
||||
else if ("Toolkit already initialized".equals(t.getMessage())) {
|
||||
PLATFORM_LOADED = true;
|
||||
PlatformState.setCurrent(PlatformState.RUNNING);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
else {
|
||||
// Platform initialization has failed in this case
|
||||
PLATFORM_LOADED = false;
|
||||
PlatformState.setCurrent(PlatformState.EXITED);
|
||||
return Optional.of(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -44,10 +44,13 @@ public class ScriptHelper {
|
|||
}
|
||||
}
|
||||
|
||||
public static String constructInitFile(
|
||||
ShellControl processControl, List<String> init, String toExecuteInShell, boolean login, String displayName)
|
||||
public static String constructInitFile(ShellControl processControl, List<String> init, String toExecuteInShell, boolean login, String displayName)
|
||||
throws Exception {
|
||||
return constructInitFile(processControl.getShellDialect(), processControl, init, toExecuteInShell, login, displayName);
|
||||
}
|
||||
|
||||
public static String constructInitFile(ShellDialect t, ShellControl processControl, List<String> init, String toExecuteInShell, boolean login, String displayName)
|
||||
throws Exception {
|
||||
ShellDialect t = processControl.getShellDialect();
|
||||
String nl = t.getNewLine().getNewLineString();
|
||||
var content = String.join(nl, init.stream().filter(s -> s != null).toList()) + nl;
|
||||
|
||||
|
@ -73,7 +76,7 @@ public class ScriptHelper {
|
|||
content += t.getExitCommand() + nl;
|
||||
}
|
||||
|
||||
var initFile = createExecScript(processControl, t.initFileName(processControl), content);
|
||||
var initFile = createExecScript(t, processControl, t.initFileName(processControl), content);
|
||||
return initFile;
|
||||
}
|
||||
|
||||
|
@ -97,12 +100,11 @@ public class ScriptHelper {
|
|||
ShellDialect type = processControl.getShellDialect();
|
||||
var temp = processControl.getSubTemporaryDirectory();
|
||||
var file = FileNames.join(temp, fileName + "." + type.getScriptFileEnding());
|
||||
return createExecScript(processControl, file, content);
|
||||
return createExecScript(processControl.getShellDialect(), processControl, file, content);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static String createExecScript(ShellControl processControl, String file, String content) {
|
||||
ShellDialect type = processControl.getShellDialect();
|
||||
public static String createExecScript(ShellDialect type, ShellControl processControl, String file, String content) {
|
||||
content = type.prepareScriptContent(content);
|
||||
|
||||
TrackEvent.withTrace("proc", "Writing exec script")
|
||||
|
@ -114,7 +116,7 @@ public class ScriptHelper {
|
|||
.getShellDialect()
|
||||
.createScriptTextFileWriteCommand(processControl, content, file)
|
||||
.execute();
|
||||
var e = type.getScriptPermissionsCommand(file);
|
||||
var e = processControl.getShellDialect().getScriptPermissionsCommand(file);
|
||||
if (e != null) {
|
||||
processControl.executeSimpleCommand(e, "Failed to set script permissions of " + file);
|
||||
}
|
||||
|
@ -152,7 +154,7 @@ public class ScriptHelper {
|
|||
pass.stream()
|
||||
.map(secretValue -> secretValue.getSecretValue())
|
||||
.toList());
|
||||
var exec = createExecScript(sub, file, content);
|
||||
var exec = createExecScript(sub.getShellDialect(), sub, file, content);
|
||||
return exec;
|
||||
}
|
||||
} else {
|
||||
|
@ -163,7 +165,7 @@ public class ScriptHelper {
|
|||
pass.stream()
|
||||
.map(secretValue -> secretValue.getSecretValue())
|
||||
.toList());
|
||||
var exec = createExecScript(parent, file, content);
|
||||
var exec = createExecScript(parent.getShellDialect(), parent, file, content);
|
||||
return exec;
|
||||
}
|
||||
}
|
||||
|
|
130
app/src/main/java/io/xpipe/app/util/SecretRetrievalStrategy.java
Normal file
|
@ -0,0 +1,130 @@
|
|||
package io.xpipe.app.util;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.core.impl.LocalStore;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.util.SecretValue;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Value;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = SecretRetrievalStrategy.None.class),
|
||||
@JsonSubTypes.Type(value = SecretRetrievalStrategy.Unsupported.class),
|
||||
@JsonSubTypes.Type(value = SecretRetrievalStrategy.Reference.class),
|
||||
@JsonSubTypes.Type(value = SecretRetrievalStrategy.InPlace.class),
|
||||
@JsonSubTypes.Type(value = SecretRetrievalStrategy.Prompt.class),
|
||||
@JsonSubTypes.Type(value = SecretRetrievalStrategy.CustomCommand.class),
|
||||
@JsonSubTypes.Type(value = SecretRetrievalStrategy.PasswordManager.class)
|
||||
})
|
||||
public interface SecretRetrievalStrategy {
|
||||
|
||||
SecretValue retrieve(String displayName, DataStore store) throws Exception;
|
||||
|
||||
@JsonTypeName("none")
|
||||
public static class None implements SecretRetrievalStrategy {
|
||||
|
||||
@Override
|
||||
public SecretValue retrieve(String displayName, DataStore store) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@JsonTypeName("unsupported")
|
||||
public static class Unsupported implements SecretRetrievalStrategy {
|
||||
|
||||
@Override
|
||||
public SecretValue retrieve(String displayName, DataStore store) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
@JsonTypeName("reference")
|
||||
public static class Reference implements SecretRetrievalStrategy {
|
||||
|
||||
@JsonIgnore
|
||||
private final Supplier<SecretValue> supplier;
|
||||
|
||||
public Reference(Supplier<SecretValue> supplier) {
|
||||
this.supplier = supplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretValue retrieve(String displayName, DataStore store) {
|
||||
return supplier.get();
|
||||
}
|
||||
}
|
||||
|
||||
@JsonTypeName("inPlace")
|
||||
@Getter
|
||||
@Builder
|
||||
@Value
|
||||
@Jacksonized
|
||||
public static class InPlace implements SecretRetrievalStrategy {
|
||||
|
||||
SecretValue value;
|
||||
|
||||
public InPlace(SecretValue value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretValue retrieve(String displayName, DataStore store) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@JsonTypeName("prompt")
|
||||
public static class Prompt implements SecretRetrievalStrategy {
|
||||
|
||||
@Override
|
||||
public SecretValue retrieve(String displayName, DataStore store) {
|
||||
return AskpassAlert.query(displayName, store);
|
||||
}
|
||||
}
|
||||
|
||||
@JsonTypeName("passwordManager")
|
||||
@Builder
|
||||
@Jacksonized
|
||||
@Value
|
||||
public static class PasswordManager implements SecretRetrievalStrategy {
|
||||
|
||||
String key;
|
||||
|
||||
@Override
|
||||
public SecretValue retrieve(String displayName, DataStore store) throws Exception {
|
||||
var cmd = AppPrefs.get().passwordManagerString(key);
|
||||
if (cmd == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try (var cc = new LocalStore().createBasicControl().command(cmd).start()) {
|
||||
return SecretHelper.encrypt(cc.readStdoutOrThrow());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JsonTypeName("customCommand")
|
||||
@Builder
|
||||
@Jacksonized
|
||||
@Value
|
||||
public static class CustomCommand implements SecretRetrievalStrategy {
|
||||
|
||||
String command;
|
||||
|
||||
@Override
|
||||
public SecretValue retrieve(String displayName, DataStore store) throws Exception {
|
||||
try (var cc = new LocalStore().createBasicControl().command(command).start()) {
|
||||
return SecretHelper.encrypt(cc.readStdoutOrThrow());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.comp.base.ButtonComp;
|
||||
import io.xpipe.app.core.App;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.impl.HorizontalComp;
|
||||
import io.xpipe.app.fxcomps.impl.SecretFieldComp;
|
||||
import io.xpipe.app.fxcomps.impl.TextFieldComp;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class SecretRetrievalStrategyHelper {
|
||||
|
||||
private static OptionsBuilder inPlace(Property<SecretRetrievalStrategy.InPlace> p) {
|
||||
var secretProperty =
|
||||
new SimpleObjectProperty<>(p.getValue() != null ? p.getValue().getValue() : null);
|
||||
return new OptionsBuilder()
|
||||
.name("password")
|
||||
.addComp(new SecretFieldComp(secretProperty), secretProperty)
|
||||
.bind(
|
||||
() -> {
|
||||
return new SecretRetrievalStrategy.InPlace(secretProperty.getValue());
|
||||
},
|
||||
p);
|
||||
}
|
||||
|
||||
private static OptionsBuilder passwordManager(Property<SecretRetrievalStrategy.PasswordManager> p) {
|
||||
var keyProperty =
|
||||
new SimpleObjectProperty<>(p.getValue() != null ? p.getValue().getKey() : null);
|
||||
var content = new HorizontalComp(List.<Comp<?>>of(
|
||||
new TextFieldComp(keyProperty).hgrow(),
|
||||
new ButtonComp(null, new FontIcon("mdomz-settings"), () -> {
|
||||
AppPrefs.get().selectCategory(3);
|
||||
App.getApp().getStage().requestFocus();
|
||||
})
|
||||
.grow(false, true)))
|
||||
.apply(struc -> struc.get().setSpacing(10));
|
||||
return new OptionsBuilder()
|
||||
.name("command")
|
||||
.addComp(content, keyProperty)
|
||||
.bind(
|
||||
() -> {
|
||||
return new SecretRetrievalStrategy.PasswordManager(keyProperty.getValue());
|
||||
},
|
||||
p);
|
||||
}
|
||||
|
||||
private static OptionsBuilder customCommand(Property<SecretRetrievalStrategy.CustomCommand> p) {
|
||||
var cmdProperty =
|
||||
new SimpleObjectProperty<>(p.getValue() != null ? p.getValue().getCommand() : null);
|
||||
var content = new TextFieldComp(cmdProperty).apply(struc -> struc.get().setPromptText("Password key"));
|
||||
return new OptionsBuilder()
|
||||
.name("command")
|
||||
.addComp(content, cmdProperty)
|
||||
.bind(
|
||||
() -> {
|
||||
return new SecretRetrievalStrategy.CustomCommand(cmdProperty.getValue());
|
||||
},
|
||||
p);
|
||||
}
|
||||
|
||||
public static OptionsBuilder comp(Property<SecretRetrievalStrategy> s) {
|
||||
SecretRetrievalStrategy strat = s.getValue();
|
||||
var inPlace = new SimpleObjectProperty<>(strat instanceof SecretRetrievalStrategy.InPlace i ? i : null);
|
||||
var passwordManager =
|
||||
new SimpleObjectProperty<>(strat instanceof SecretRetrievalStrategy.PasswordManager i ? i : null);
|
||||
var customCommand =
|
||||
new SimpleObjectProperty<>(strat instanceof SecretRetrievalStrategy.CustomCommand i ? i : null);
|
||||
var command = new SimpleObjectProperty<>(strat instanceof SecretRetrievalStrategy.CustomCommand c ? c : null);
|
||||
var map = new LinkedHashMap<String, OptionsBuilder>();
|
||||
map.put("none", new OptionsBuilder());
|
||||
map.put("password", inPlace(inPlace));
|
||||
map.put("passwordManager", passwordManager(passwordManager));
|
||||
map.put("customCommand", customCommand(customCommand));
|
||||
map.put("prompt", new OptionsBuilder());
|
||||
var selected = new SimpleIntegerProperty(
|
||||
strat instanceof SecretRetrievalStrategy.None
|
||||
? 0
|
||||
: strat instanceof SecretRetrievalStrategy.InPlace
|
||||
? 1
|
||||
: strat instanceof SecretRetrievalStrategy.PasswordManager
|
||||
? 2
|
||||
: strat instanceof SecretRetrievalStrategy.CustomCommand ? 3 : strat instanceof SecretRetrievalStrategy.Prompt ? 4 : 0);
|
||||
return new OptionsBuilder()
|
||||
.choice(selected, map)
|
||||
.bindChoice(
|
||||
() -> {
|
||||
return switch (selected.get()) {
|
||||
case 0 -> new SimpleObjectProperty<>(new SecretRetrievalStrategy.None());
|
||||
case 1 -> inPlace;
|
||||
case 2 -> passwordManager;
|
||||
case 3 -> customCommand;
|
||||
case 4 -> new SimpleObjectProperty<>(new SecretRetrievalStrategy.Prompt());
|
||||
default -> new SimpleObjectProperty<>();
|
||||
};
|
||||
},
|
||||
s);
|
||||
}
|
||||
}
|
|
@ -6,14 +6,14 @@ import org.apache.commons.lang3.function.FailableRunnable;
|
|||
public class ThreadHelper {
|
||||
|
||||
public static Thread runAsync(Runnable r) {
|
||||
var t = new Thread(r);
|
||||
var t = Thread.ofVirtual().unstarted(r);
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
return t;
|
||||
}
|
||||
|
||||
public static Thread runFailableAsync(FailableRunnable<Throwable> r) {
|
||||
var t = new Thread(() -> {
|
||||
var t = Thread.ofVirtual().unstarted(() -> {
|
||||
try {
|
||||
r.run();
|
||||
} catch (Throwable e) {
|
||||
|
|
|
@ -14,7 +14,7 @@ public interface Validator {
|
|||
static Check nonNull(Validator v, ObservableValue<String> name, ObservableValue<?> s) {
|
||||
return v.createCheck().dependsOn("val", s).withMethod(c -> {
|
||||
if (c.get("val") == null) {
|
||||
c.error(AppI18n.get("app.mustNotBeEmpty", name.getValue()));
|
||||
c.error(AppI18n.get("app.mustNotBeEmpty", name != null ? name.getValue() : "null"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -145,6 +145,8 @@ open module io.xpipe.app {
|
|||
FocusExchangeImpl,
|
||||
ProxyReadConnectionExchangeImpl,
|
||||
StatusExchangeImpl,
|
||||
DrainExchangeImpl,
|
||||
SinkExchangeImpl,
|
||||
StopExchangeImpl,
|
||||
ModeExchangeImpl,
|
||||
DialogExchangeImpl,
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg fill="#0D597F" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Alpine Linux</title><path d="M5.998 1.607L0 12l5.998 10.393h12.004L24 12 18.002 1.607H5.998zM9.965 7.12L12.66 9.9l1.598 1.595.002-.002 2.41 2.363c-.2.14-.386.252-.563.344a3.756 3.756 0 01-.496.217 2.702 2.702 0 01-.425.111c-.131.023-.25.034-.358.034-.13 0-.242-.014-.338-.034a1.317 1.317 0 01-.24-.072.95.95 0 01-.2-.113l-1.062-1.092-3.039-3.041-1.1 1.053-3.07 3.072a.974.974 0 01-.2.111 1.274 1.274 0 01-.237.073c-.096.02-.209.033-.338.033-.108 0-.227-.009-.358-.031a2.7 2.7 0 01-.425-.114 3.748 3.748 0 01-.496-.217 5.228 5.228 0 01-.563-.343l6.803-6.727zm4.72.785l4.579 4.598 1.382 1.353a5.24 5.24 0 01-.564.344 3.73 3.73 0 01-.494.217 2.697 2.697 0 01-.426.111c-.13.023-.251.034-.36.034-.129 0-.241-.014-.337-.034a1.285 1.285 0 01-.385-.146c-.033-.02-.05-.036-.053-.04l-1.232-1.218-2.111-2.111-.334.334L12.79 9.8l1.896-1.897zm-5.966 4.12v2.529a2.128 2.128 0 01-.356-.035 2.765 2.765 0 01-.422-.116 3.708 3.708 0 01-.488-.214 5.217 5.217 0 01-.555-.34l1.82-1.825Z"/></svg>
|
After Width: | Height: | Size: 1 KiB |
|
@ -0,0 +1 @@
|
|||
<svg fill="#FF9900" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Amazon</title><path d="M.045 18.02c.072-.116.187-.124.348-.022 3.636 2.11 7.594 3.166 11.87 3.166 2.852 0 5.668-.533 8.447-1.595l.315-.14c.138-.06.234-.1.293-.13.226-.088.39-.046.525.13.12.174.09.336-.12.48-.256.19-.6.41-1.006.654-1.244.743-2.64 1.316-4.185 1.726a17.617 17.617 0 01-10.951-.577 17.88 17.88 0 01-5.43-3.35c-.1-.074-.151-.15-.151-.22 0-.047.021-.09.051-.13zm6.565-6.218c0-1.005.247-1.863.743-2.577.495-.71 1.17-1.25 2.04-1.615.796-.335 1.756-.575 2.912-.72.39-.046 1.033-.103 1.92-.174v-.37c0-.93-.105-1.558-.3-1.875-.302-.43-.78-.65-1.44-.65h-.182c-.48.046-.896.196-1.246.46-.35.27-.575.63-.675 1.096-.06.3-.206.465-.435.51l-2.52-.315c-.248-.06-.372-.18-.372-.39 0-.046.007-.09.022-.15.247-1.29.855-2.25 1.82-2.88.976-.616 2.1-.975 3.39-1.05h.54c1.65 0 2.957.434 3.888 1.29.135.15.27.3.405.48.12.165.224.314.283.45.075.134.15.33.195.57.06.254.105.42.135.51.03.104.062.3.076.615.01.313.02.493.02.553v5.28c0 .376.06.72.165 1.036.105.313.21.54.315.674l.51.674c.09.136.136.256.136.36 0 .12-.06.226-.18.314-1.2 1.05-1.86 1.62-1.963 1.71-.165.135-.375.15-.63.045a6.062 6.062 0 01-.526-.496l-.31-.347a9.391 9.391 0 01-.317-.42l-.3-.435c-.81.886-1.603 1.44-2.4 1.665-.494.15-1.093.227-1.83.227-1.11 0-2.04-.343-2.76-1.034-.72-.69-1.08-1.665-1.08-2.94l-.05-.076zm3.753-.438c0 .566.14 1.02.425 1.364.285.34.675.512 1.155.512.045 0 .106-.007.195-.02.09-.016.134-.023.166-.023.614-.16 1.08-.553 1.424-1.178.165-.28.285-.58.36-.91.09-.32.12-.59.135-.8.015-.195.015-.54.015-1.005v-.54c-.84 0-1.484.06-1.92.18-1.275.36-1.92 1.17-1.92 2.43l-.035-.02zm9.162 7.027c.03-.06.075-.11.132-.17.362-.243.714-.41 1.05-.5a8.094 8.094 0 011.612-.24c.14-.012.28 0 .41.03.65.06 1.05.168 1.172.33.063.09.099.228.099.39v.15c0 .51-.149 1.11-.424 1.8-.278.69-.664 1.248-1.156 1.68-.073.06-.14.09-.197.09-.03 0-.06 0-.09-.012-.09-.044-.107-.12-.064-.24.54-1.26.806-2.143.806-2.64 0-.15-.03-.27-.087-.344-.145-.166-.55-.257-1.224-.257-.243 0-.533.016-.87.046-.363.045-.7.09-1 .135-.09 0-.148-.014-.18-.044-.03-.03-.036-.047-.02-.077 0-.017.006-.03.02-.063v-.06z"/></svg>
|
After Width: | Height: | Size: 2.1 KiB |
|
@ -0,0 +1 @@
|
|||
<svg fill="#FFFFFF" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Apple</title><path d="M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701"/></svg>
|
After Width: | Height: | Size: 665 B |
|
@ -0,0 +1 @@
|
|||
<svg fill="#000000" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Apple</title><path d="M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701"/></svg>
|
After Width: | Height: | Size: 665 B |
|
@ -0,0 +1 @@
|
|||
<svg fill="#1793D1" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Arch Linux</title><path d="M11.39.605C10.376 3.092 9.764 4.72 8.635 7.132c.693.734 1.543 1.589 2.923 2.554-1.484-.61-2.496-1.224-3.252-1.86C6.86 10.842 4.596 15.138 0 23.395c3.612-2.085 6.412-3.37 9.021-3.862a6.61 6.61 0 01-.171-1.547l.003-.115c.058-2.315 1.261-4.095 2.687-3.973 1.426.12 2.534 2.096 2.478 4.409a6.52 6.52 0 01-.146 1.243c2.58.505 5.352 1.787 8.914 3.844-.702-1.293-1.33-2.459-1.929-3.57-.943-.73-1.926-1.682-3.933-2.713 1.38.359 2.367.772 3.137 1.234-6.09-11.334-6.582-12.84-8.67-17.74zM22.898 21.36v-.623h-.234v-.084h.562v.084h-.234v.623h.331v-.707h.142l.167.5.034.107a2.26 2.26 0 01.038-.114l.17-.493H24v.707h-.091v-.593l-.206.593h-.084l-.205-.602v.602h-.091"/></svg>
|
After Width: | Height: | Size: 780 B |
|
@ -0,0 +1 @@
|
|||
<svg fill="#262577" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>CentOS</title><path d="M12.076.066L8.883 3.28H3.348v5.434L0 12.01l3.349 3.298v5.39h5.374l3.285 3.236 3.285-3.236h5.43v-5.374L24 12.026l-3.232-3.252V3.321H15.31zm0 .749l2.49 2.506h-1.69v6.441l-.8.805-.81-.815V3.28H9.627zm-8.2 2.991h4.483L6.485 5.692l4.253 4.279v.654H9.94L5.674 6.423l-1.798 1.77zm5.227 0h1.635v5.415l-3.509-3.53zm4.302.043h1.687l1.83 1.842-3.517 3.539zm2.431 0h4.404v4.394l-1.83-1.842-4.241 4.267h-.764v-.69l4.261-4.287zm2.574 3.3l1.83 1.843v1.676h-5.327zm-12.735.013l3.515 3.462H3.876v-1.69zM3.348 9.454v1.697h6.377l.871.858-.782.77H3.35v1.786L.753 12.01zm17.42.068l2.488 2.503-2.533 2.55v-1.796h-6.41l-.75-.754.825-.83h6.38zm-9.502.978l.81.815.186-.188.614-.618v.686h.768l-.825.83.75.754h-.719v.808l-.842-.83-.741.73v-.707h-.7l.781-.77-.188-.186-.682-.672h.788zm-7.39 2.807h5.402l-3.603 3.55-1.798-1.772zm6.154 0h.708v.7l-4.404 4.338 1.852 1.824h-4.31v-4.342l1.798 1.77zm3.348 0h.715l4.317 4.343.186-.187 1.599-1.61v4.316h-4.366l1.853-1.825-.188-.185-4.116-4.054zm1.46 0h5.357v1.798l-1.785 1.796zm-2.83.191l.842.829v6.37h1.691l-2.532 2.495-2.533-2.495h1.79V14.23zm-1.27 1.251v5.42H8.939l-1.852-1.823zm2.64.097l3.552 3.499-1.853 1.825h-1.7z"/></svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1 @@
|
|||
<svg fill="#A81D33" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Debian</title><path d="M13.88 12.685c-.4 0 .08.2.601.28.14-.1.27-.22.39-.33a3.001 3.001 0 01-.99.05m2.14-.53c.23-.33.4-.69.47-1.06-.06.27-.2.5-.33.73-.75.47-.07-.27 0-.56-.8 1.01-.11.6-.14.89m.781-2.05c.05-.721-.14-.501-.2-.221.07.04.13.5.2.22M12.38.31c.2.04.45.07.42.12.23-.05.28-.1-.43-.12m.43.12l-.15.03.14-.01V.43m6.633 9.944c.02.64-.2.95-.38 1.5l-.35.181c-.28.54.03.35-.17.78-.44.39-1.34 1.22-1.62 1.301-.201 0 .14-.25.19-.34-.591.4-.481.6-1.371.85l-.03-.06c-2.221 1.04-5.303-1.02-5.253-3.842-.03.17-.07.13-.12.2a3.551 3.552 0 012.001-3.501 3.361 3.362 0 013.732.48 3.341 3.342 0 00-2.721-1.3c-1.18.01-2.281.76-2.651 1.57-.6.38-.67 1.47-.93 1.661-.361 2.601.66 3.722 2.38 5.042.27.19.08.21.12.35a4.702 4.702 0 01-1.53-1.16c.23.33.47.66.8.91-.55-.18-1.27-1.3-1.48-1.35.93 1.66 3.78 2.921 5.261 2.3a6.203 6.203 0 01-2.33-.28c-.33-.16-.77-.51-.7-.57a5.802 5.803 0 005.902-.84c.44-.35.93-.94 1.07-.95-.2.32.04.16-.12.44.44-.72-.2-.3.46-1.24l.24.33c-.09-.6.74-1.321.66-2.262.19-.3.2.3 0 .97.29-.74.08-.85.15-1.46.08.2.18.42.23.63-.18-.7.2-1.2.28-1.6-.09-.05-.28.3-.32-.53 0-.37.1-.2.14-.28-.08-.05-.26-.32-.38-.861.08-.13.22.33.34.34-.08-.42-.2-.75-.2-1.08-.34-.68-.12.1-.4-.3-.34-1.091.3-.25.34-.74.54.77.84 1.96.981 2.46-.1-.6-.28-1.2-.49-1.76.16.07-.26-1.241.21-.37A7.823 7.824 0 0017.702 1.6c.18.17.42.39.33.42-.75-.45-.62-.48-.73-.67-.61-.25-.65.02-1.06 0C15.082.73 14.862.8 13.8.4l.05.23c-.77-.25-.9.1-1.73 0-.05-.04.27-.14.53-.18-.741.1-.701-.14-1.431.03.17-.13.36-.21.55-.32-.6.04-1.44.35-1.18.07C9.6.68 7.847 1.3 6.867 2.22L6.838 2c-.45.54-1.96 1.611-2.08 2.311l-.131.03c-.23.4-.38.85-.57 1.261-.3.52-.45.2-.4.28-.6 1.22-.9 2.251-1.16 3.102.18.27 0 1.65.07 2.76-.3 5.463 3.84 10.776 8.363 12.006.67.23 1.65.23 2.49.25-.99-.28-1.12-.15-2.08-.49-.7-.32-.85-.7-1.34-1.13l.2.35c-.971-.34-.57-.42-1.361-.67l.21-.27c-.31-.03-.83-.53-.97-.81l-.34.01c-.41-.501-.63-.871-.61-1.161l-.111.2c-.13-.21-1.52-1.901-.8-1.511-.13-.12-.31-.2-.5-.55l.14-.17c-.35-.44-.64-1.02-.62-1.2.2.24.32.3.45.33-.88-2.172-.93-.12-1.601-2.202l.15-.02c-.1-.16-.18-.34-.26-.51l.06-.6c-.63-.74-.18-3.102-.09-4.402.07-.54.53-1.1.88-1.981l-.21-.04c.4-.71 2.341-2.872 3.241-2.761.43-.55-.09 0-.18-.14.96-.991 1.26-.7 1.901-.88.7-.401-.6.16-.27-.151 1.2-.3.85-.7 2.421-.85.16.1-.39.14-.52.26 1-.49 3.151-.37 4.562.27 1.63.77 3.461 3.011 3.531 5.132l.08.02c-.04.85.13 1.821-.17 2.711l.2-.42M9.54 13.236l-.05.28c.26.35.47.73.8 1.01-.24-.47-.42-.66-.75-1.3m.62-.02c-.14-.15-.22-.34-.31-.52.08.32.26.6.43.88l-.12-.36m10.945-2.382l-.07.15c-.1.76-.34 1.511-.69 2.212.4-.73.65-1.541.75-2.362M12.45.12c.27-.1.66-.05.95-.12-.37.03-.74.05-1.1.1l.15.02M3.006 5.142c.07.57-.43.8.11.42.3-.66-.11-.18-.1-.42m-.64 2.661c.12-.39.15-.62.2-.84-.35.44-.17.53-.2.83"/></svg>
|
After Width: | Height: | Size: 2.8 KiB |
|
@ -0,0 +1 @@
|
|||
<svg fill="#51A2DA" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Fedora</title><path d="M12.001 0C5.376 0 .008 5.369.004 11.992H.002v9.287h.002A2.726 2.726 0 0 0 2.73 24h9.275c6.626-.004 11.993-5.372 11.993-11.997C23.998 5.375 18.628 0 12 0zm2.431 4.94c2.015 0 3.917 1.543 3.917 3.671 0 .197.001.395-.03.619a1.002 1.002 0 0 1-1.137.893 1.002 1.002 0 0 1-.842-1.175 2.61 2.61 0 0 0 .013-.337c0-1.207-.987-1.672-1.92-1.672-.934 0-1.775.784-1.777 1.672.016 1.027 0 2.046 0 3.07l1.732-.012c1.352-.028 1.368 2.009.016 1.998l-1.748.013c-.004.826.006.677.002 1.093 0 0 .015 1.01-.016 1.776-.209 2.25-2.124 4.046-4.424 4.046-2.438 0-4.448-1.993-4.448-4.437.073-2.515 2.078-4.492 4.603-4.469l1.409-.01v1.996l-1.409.013h-.007c-1.388.04-2.577.984-2.6 2.47a2.438 2.438 0 0 0 2.452 2.439c1.356 0 2.441-.987 2.441-2.437l-.001-7.557c0-.14.005-.252.02-.407.23-1.848 1.883-3.256 3.754-3.256z"/></svg>
|
After Width: | Height: | Size: 911 B |
|
@ -0,0 +1 @@
|
|||
<svg fill="#54487A" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Gentoo</title><path d="M9.94 0a7.31 7.31 0 00-1.26.116c-4.344.795-7.4 4.555-7.661 7.031-.126 1.215.53 2.125.89 2.526.977 1.085 2.924 1.914 4.175 2.601-1.81 1.543-2.64 2.296-3.457 3.154C1.403 16.712.543 18.125.54 19.138c0 .325-.053 1.365.371 2.187.16.309.613 1.338 1.98 2.109.874.494 2.119.675 3.337.501 3.772-.538 8.823-3.737 12.427-6.716 2.297-1.9 3.977-3.739 4.462-4.644.39-.731.434-2.043.207-2.866-.645-2.337-5.887-7.125-10.172-9.051A7.824 7.824 0 009.94 0zm-.008.068a7.4 7.4 0 013.344.755c3.46 1.7 9.308 6.482 9.739 8.886.534 2.972-9.931 11.017-16.297 12.272-2.47.485-4.576.618-5.537-1.99-.832-2.262.783-3.916 3.16-6.09a92.546 92.546 0 012.96-2.576c.065-.069-5.706-2.059-5.89-4.343C1.221 4.634 4.938.3 9.697.076c.08-.004.157-.007.235-.008zm-.112.52a5.647 5.647 0 00-.506.032c-2.337.245-2.785.547-4.903 2.149-.71.537-2.016 1.844-2.35 3.393-.128.59.024 1.1.448 1.458 1.36 1.144 3.639 2.072 5.509 2.97.547.263.185.74-.698 1.505-2.227 1.928-5.24 4.276-5.45 6.066-.099.842.19 1.988 1.213 2.574 1.195.685 3.676.238 5.333-.379 2.422-.902 5.602-2.892 8.127-4.848 2.625-2.034 5.067-4.617 5.188-5.038.148-.517.133-.996-.154-1.546-.448-.862-1.049-1.503-1.694-2.22-1.732-1.825-3.563-3.43-5.754-4.658C12.694 1.242 11.417.564 9.82.588zm1.075 3.623c.546 0 1.176.173 1.853.5 1.688.817 3.422 2.961-.015 4.195-.935.336-3.9-.824-3.81-2.407.09-1.57.854-2.289 1.972-2.288zm.285 1.367c-.317-.002-.575.079-.694.263-.557.861-.303 1.472.212 1.862.192-.457 2.156.043 2.148.472a.32.32 0 00.055-.032c1.704-1.282-.472-2.557-1.72-2.565z"/></svg>
|
After Width: | Height: | Size: 1.6 KiB |
|
@ -0,0 +1 @@
|
|||
<svg fill="#0079C1" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Kubuntu</title><path d="M21.257 14.292a.2065.2065 0 01.1097.2374l-.5765 1.5293a.2063.2063 0 01-.2033.155l-2.3369-.2283c-.0628.001-.1623-.008-.2776.1255-.0178.0207-.7472.9328-.9783 1.1832-.079.0708-.1067.1652-.0878.281l.5521 2.1962a.2064.2064 0 01-.098.2314l-1.513.8906a.2063.2063 0 01-.2556-.0418l-1.5494-1.7055c-.0516-.0555-.1551-.0994-.2271-.0759l-1.645.2391c-.0804.011-.1267.0635-.1603.1164l-.9938 2.0793a.2063.2063 0 01-.2353.089l-1.6687-.3945a.2065.2065 0 01-.1462-.1824.209.209 0 01.0105-.0815l.8812-3.244a.222.222 0 01.2828-.1373l.0033-.001a5.8423 5.8423 0 002.7168.2176c2.3222-.3696 4.1964-2.0953 4.756-4.3791l.011-.0407c.0277-.1194.1768-.1827.2963-.155 0 0 2.8684.9737 3.2936 1.0816a.2089.2089 0 01.0394.0143zM5.539 4.9898l.0001.0001a.2051.2051 0 01.0659.0489l2.392 2.3567a.222.222 0 01-.0186.3138l-.0008.0034A5.8422 5.8422 0 006.4594 9.976c-.8132 2.2063-.2244 4.685 1.494 6.29l.0353.0396c.0906.0827.0678.2335-.0148.3241 0 0-2.2452 2.0243-2.5472 2.3425a.2064.2064 0 01-.2924.007l-1.0521-1.2507a.2063.2063 0 01-.0358-.253l1.335-1.9253c.0297-.0553.0863-.1376.0262-.3035-.0092-.0256-.4482-1.108-.5536-1.432-.0232-.1035-.092-.1738-.2022-.214l-2.1789-.594a.2064.2064 0 01-.154-.1986l-.0368-1.7552a.2065.2065 0 01.1615-.2026l2.2384-.516c.0737-.0177.1625-.0866.1772-.1609l.5958-1.5517c.0298-.0755.0068-.1417-.023-.1968L4.111 6.5396a.2064.2064 0 01.0374-.2488l1.1602-1.2626a.2066.2066 0 01.2305-.0385zm10.4906-1.747a.2139.2139 0 01.0313.0147l1.5385.8455a.2065.2065 0 01.0947.2412l-.6793 2.198c-.0214.0727-.0062.1841.0508.234l1.046 1.2918c.0505.0636.1193.0767.182.0785l2.3-.2029a.2064.2064 0 01.1968.1567l.5134 1.6361a.2064.2064 0 01-.082.2189h-.0001a.205.205 0 01-.0753.0326l-3.244.8946a.222.222 0 01-.2624-.1729.012.012 0 01-.0025-.0024 5.8422 5.8422 0 00-1.201-2.4466c-1.5041-1.8073-3.9452-2.5368-6.1943-1.851l-.0402.0123c-.1169.0371-.248-.0597-.285-.1766 0 0-.6236-2.958-.7481-3.3786a.2063.2063 0 01.14-.2568l1.6093-.2858a.2063.2063 0 01.237.0955l.9929 2.1203c.033.0534.076.1436.2498.1744.0268.0048 1.1835.1658 1.5169.2366.1012.0316.1966.0073.2864-.068l1.6107-1.5916a.2064.2064 0 01.2177-.0486zM16.021.6955A11.9968 11.9968 0 007.794.763C1.5889 3.086-1.5582 9.9993.7647 16.2044c2.323 6.205 9.2362 9.3522 15.4413 7.0293 6.2051-2.3229 9.3522-9.2362 7.0293-15.4413A11.997 11.997 0 0016.021.6955z"/></svg>
|
After Width: | Height: | Size: 2.3 KiB |
3409
app/src/main/resources/io/xpipe/app/resources/img/os/linux.svg
Normal file
After Width: | Height: | Size: 475 KiB |
|
@ -0,0 +1 @@
|
|||
<svg fill="#35BF5C" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Manjaro</title><path d="M0 0v24h6.75V6.75h8.625V0H0zm8.625 8.625V24h6.75V8.625h-6.75zM17.25 0v24H24V0h-6.75z"/></svg>
|
After Width: | Height: | Size: 210 B |
|
@ -0,0 +1 @@
|
|||
<svg fill="#87CF3E" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Linux Mint</title><path d="M0 1.693v4.193h1.828c1.276 0 1.502.865 1.502 2.058l.01 7.412c0 3.84 3.44 6.951 7.68 6.951h10.464c1.342 0 2.516-.83 2.516-2.108V8.706c0-3.84-3.44-6.95-7.683-6.95h-4.405v-.013L0 1.693zm5.723 2.566h2.102V14.82c0 1.413.984 2.51 2.139 2.51l7.17.03c1.496 0 2.661-1.01 2.661-2.206l-.012-5.607a1.2 1.2 0 0 0-.386-.91 1.224 1.224 0 0 0-.917-.384c-.374 0-.65.12-.918.384a1.2 1.2 0 0 0-.386.91v4.798h-2.223V9.548c0-.364-.124-.648-.389-.91a1.208 1.208 0 0 0-.917-.384c-.366 0-.647.12-.914.384-.265.262-.39.546-.39.91v4.798H10.12V9.548c0-.95.36-1.792 1.042-2.466a3.445 3.445 0 0 1 2.485-1.022c.937 0 1.752.345 2.413.97a3.448 3.448 0 0 1 2.42-.97c.954 0 1.803.348 2.485 1.022a3.385 3.385 0 0 1 1.041 2.466l.009 5.991c-.105 1.004-.539 1.894-1.28 2.637h-.002a4.367 4.367 0 0 1-3.174 1.314H9.574v-.038c-.976-.103-1.846-.519-2.57-1.217-.845-.825-1.281-1.846-1.281-3.01V4.26z"/></svg>
|
After Width: | Height: | Size: 985 B |
|
@ -0,0 +1 @@
|
|||
<svg fill="#73BA25" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>openSUSE</title><path d="M10.724 0a12 12 0 0 0-9.448 4.623c1.464.391 2.5.727 2.81.832.005-.19.037-1.893.037-1.893s.004-.04.025-.06c.026-.026.065-.018.065-.018.385.056 8.602 1.274 12.066 3.292.427.25.638.517.902.786.958.99 2.223 5.108 2.359 5.957.005.033-.036.07-.054.083a5.177 5.177 0 0 1-.313.228c-.82.55-2.708 1.872-5.13 1.656-2.176-.193-5.018-1.44-8.445-3.699.336.79.668 1.58 1 2.371.497.258 5.287 2.7 7.651 2.651 1.904-.04 3.941-.968 4.756-1.458 0 0 .179-.108.257-.048.085.066.061.167.041.27-.05.234-.164.66-.242.863l-.065.165c-.093.25-.183.482-.356.625-.48.436-1.246.784-2.446 1.305-1.855.812-4.865 1.328-7.66 1.31-1.001-.022-1.968-.133-2.817-.232-1.743-.197-3.161-.357-4.026.269A12 12 0 0 0 10.724 24a12 12 0 0 0 12-12 12 12 0 0 0-12-12zM13.4 6.963a3.503 3.503 0 0 0-2.521.942 3.498 3.498 0 0 0-1.114 2.449 3.528 3.528 0 0 0 3.39 3.64 3.48 3.48 0 0 0 2.524-.946 3.504 3.504 0 0 0 1.114-2.446 3.527 3.527 0 0 0-3.393-3.64zm-.03 1.035a2.458 2.458 0 0 1 2.368 2.539 2.43 2.43 0 0 1-.774 1.706 2.456 2.456 0 0 1-1.762.659 2.461 2.461 0 0 1-2.364-2.542c.02-.655.3-1.26.777-1.707a2.419 2.419 0 0 1 1.756-.655zm.402 1.23c-.602 0-1.087.325-1.087.727 0 .4.485.725 1.087.725.6 0 1.088-.326 1.088-.725 0-.402-.487-.726-1.088-.726Z"/></svg>
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -0,0 +1 @@
|
|||
<svg fill="#48B9C7" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Pop!_OS</title><path d="M15.724 11.253c-.776 1.366-.932 1.5-1.153 1.411-.261-.104-.171-1.472.067-3.134.067-.464.183-.684.312-.796.186-.158.524-.165.752-.131.263.038.514.16.704.344.168.163.187.33.13.546-.094.344-.256.674-.411.996-.036.079-.358.689-.401.764zm-1.41 2.034a.57.57 0 0 0-.382.411.53.53 0 0 0 .146.52.451.451 0 0 0 .543.055.484.484 0 0 0 .208-.309c.08-.336-.093-.794-.514-.677zm-5.01-1.239c.154.656.138 1.377.006 1.896-.123.49-.458.93-.989 1.076-.466.13-1.009.035-1.341-.285-.548-.525-.868-1.758-.725-2.653.115-.718.503-1.638 1.352-1.678a1.11 1.11 0 0 1 .163.003c.866.078 1.34.808 1.534 1.641zm-1.153.35a.807.807 0 0 0-.205-.333c-.344-.314-.688.013-.748.386-.046.291.021.63.203.865a.588.588 0 0 0 .336.226.32.32 0 0 0 .144-.006c.162-.046.255-.215.298-.378a1.296 1.296 0 0 0-.028-.76zm4.8.132c-.333.508-1.34.876-1.772.844v.066c-.002 1.212.12 2.14-.413 2.25-.432.087-.418-.292-.45-.61-.129-1.235-.246-3.285-.296-4.526.035-.533.392-.86.764-.94.573-.123 1.185-.088 1.692.226 1.003.62 1.083 1.76.475 2.69zm-1.042-1.266c-.077-.24-.315-.584-.602-.541-.062.01-.165.038-.194.099-.069.142-.064 1.047.056 1.425.02.062.062.122.125.144.227.083.432-.195.525-.363a.972.972 0 0 0 .09-.764zM5.52 9.69c.135.61.107 1.234-.081 1.635-.376.795-1.001 1.427-1.865 1.74.248.647.5 1.288.748 1.935.138.35.269.732.149 1.076-.125.352-.612.45-.965.075-.68-.724-2.98-5.308-3.15-5.652-.145-.297-.36-.614-.355-.957.011-.516.808-1.037 1.191-1.305.535-.373 1.153-.628 1.814-.63.516 0 .956.142 1.347.392A2.698 2.698 0 0 1 5.52 9.69zm-1.833.763A2.533 2.533 0 0 0 2.864 9.2a.883.883 0 0 0-.277-.168c-.717-.235-.407.964-.29 1.266.162.424.407.96.735 1.28.072.07.157.134.255.153.149.03.287-.074.362-.195a.845.845 0 0 0 .1-.338 1.999 1.999 0 0 0-.063-.745zm18.128 5.574zm1.046-1.318c-.355-.05-.556-.117-.556-.355 0-.243.31-.379.6-.383.133-.003.308.02.478.101l.027.355h.421c0-.216.003-.344.003-.56-.328-.263-.586-.32-.949-.312-.45 0-1.058.267-1.058.804 0 .602.432.733.97.788.314.032.705.093.716.391 0 .328-.35.413-.687.413a1.21 1.21 0 0 1-.544-.142l-.03-.367h-.435c.003.076.001.51 0 .586.328.286.587.363 1.002.362.544 0 1.178-.178 1.182-.853-.002-.62-.561-.75-1.14-.828zm-1.045 1.318c0 .017 0 .015 0 0zm-.398-1.046c-.003.703-.432 1.406-1.378 1.406s-1.386-.692-1.386-1.398c0-.88.613-1.425 1.406-1.425.918.008 1.366.69 1.358 1.417zm-.478 0c.008-.475-.243-.962-.88-.973-.595 0-.928.413-.93.977 0 .49.273.95.918.95s.892-.499.892-.955zm-3.096.934H15.27a.223.223 0 0 0-.223.223v.02c0 .122.099.222.223.222h2.573a.223.223 0 0 0 .223-.223v-.019a.222.222 0 0 0-.223-.223z"/></svg>
|
After Width: | Height: | Size: 2.6 KiB |
|
@ -0,0 +1 @@
|
|||
<svg fill="#EE0000" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Red Hat</title><path d="M16.009 13.386c1.577 0 3.86-.326 3.86-2.202a1.765 1.765 0 0 0-.04-.431l-.94-4.08c-.216-.898-.406-1.305-1.982-2.093-1.223-.625-3.888-1.658-4.676-1.658-.733 0-.947.946-1.822.946-.842 0-1.467-.706-2.255-.706-.757 0-1.25.515-1.63 1.576 0 0-1.06 2.99-1.197 3.424a.81.81 0 0 0-.028.245c0 1.162 4.577 4.974 10.71 4.974m4.101-1.435c.218 1.032.218 1.14.218 1.277 0 1.765-1.984 2.745-4.593 2.745-5.895.004-11.06-3.451-11.06-5.734a2.326 2.326 0 0 1 .19-.925C2.746 9.415 0 9.794 0 12.217c0 3.969 9.405 8.861 16.851 8.861 5.71 0 7.149-2.582 7.149-4.62 0-1.605-1.387-3.425-3.887-4.512"/></svg>
|
After Width: | Height: | Size: 696 B |
|
@ -0,0 +1 @@
|
|||
<svg fill="#10B981" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Rocky Linux</title><path d="M23.332 15.957c.433-1.239.668-2.57.668-3.957 0-6.627-5.373-12-12-12S0 5.373 0 12c0 3.28 1.315 6.251 3.447 8.417L15.62 8.245l3.005 3.005zm-2.192 3.819l-5.52-5.52L6.975 22.9c1.528.706 3.23 1.1 5.025 1.1 3.661 0 6.94-1.64 9.14-4.224z"/></svg>
|
After Width: | Height: | Size: 360 B |
|
@ -0,0 +1 @@
|
|||
<svg fill="#E95420" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Ubuntu</title><path d="M17.61.455a3.41 3.41 0 0 0-3.41 3.41 3.41 3.41 0 0 0 3.41 3.41 3.41 3.41 0 0 0 3.41-3.41 3.41 3.41 0 0 0-3.41-3.41zM12.92.8C8.923.777 5.137 2.941 3.148 6.451a4.5 4.5 0 0 1 .26-.007 4.92 4.92 0 0 1 2.585.737A8.316 8.316 0 0 1 12.688 3.6 4.944 4.944 0 0 1 13.723.834 11.008 11.008 0 0 0 12.92.8zm9.226 4.994a4.915 4.915 0 0 1-1.918 2.246 8.36 8.36 0 0 1-.273 8.303 4.89 4.89 0 0 1 1.632 2.54 11.156 11.156 0 0 0 .559-13.089zM3.41 7.932A3.41 3.41 0 0 0 0 11.342a3.41 3.41 0 0 0 3.41 3.409 3.41 3.41 0 0 0 3.41-3.41 3.41 3.41 0 0 0-3.41-3.41zm2.027 7.866a4.908 4.908 0 0 1-2.915.358 11.1 11.1 0 0 0 7.991 6.698 11.234 11.234 0 0 0 2.422.249 4.879 4.879 0 0 1-.999-2.85 8.484 8.484 0 0 1-.836-.136 8.304 8.304 0 0 1-5.663-4.32zm11.405.928a3.41 3.41 0 0 0-3.41 3.41 3.41 3.41 0 0 0 3.41 3.41 3.41 3.41 0 0 0 3.41-3.41 3.41 3.41 0 0 0-3.41-3.41z"/></svg>
|
After Width: | Height: | Size: 963 B |
|
@ -0,0 +1 @@
|
|||
<svg fill="#0078D4" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Windows</title><path d="M0,0H11.377V11.372H0ZM12.623,0H24V11.372H12.623ZM0,12.623H11.377V24H0Zm12.623,0H24V24H12.623"/></svg>
|
After Width: | Height: | Size: 218 B |
|
@ -64,10 +64,12 @@ developerMode=Developer mode
|
|||
developerModeDescription=When enabled, you will have access to a variety of additional options that are useful for development.
|
||||
editor=Editor
|
||||
custom=Custom
|
||||
passwordManagerCommand=Password manager command
|
||||
passwordManagerCommandDescription=The command to execute to fetch passwords. The placeholder string $KEY will be replaced by the quoted password key when called. This should call your password manager CLI to print the password to stdout, e.g. mypassmgr get $KEY.\n\nYou can check here whether the output is correct. The command should only output the password itself, no other formatting should be included in the output.
|
||||
preferEditorTabs=Prefer to open new tabs
|
||||
preferEditorTabsDescription=Controls whether XPipe will try to open new tabs in your chosen editor instead of new windows.
|
||||
customEditorCommand=Custom editor command
|
||||
customEditorCommandDescription=The command to execute to open the custom editor. The placeholder string $file will be replaced by the quoted absolute file name when called. Remember to quote your editor executable path if it contains spaces.
|
||||
customEditorCommandDescription=The command to execute to open the custom editor. The placeholder string $FILE will be replaced by the quoted absolute file name when called. Remember to quote your editor executable path if it contains spaces.
|
||||
editorReloadTimeout=Editor reload timeout
|
||||
editorReloadTimeoutDescription=The amount of milliseconds to wait before reading a file after it has been updated. This avoids issues in cases where your editor is slow at writing or releasing file locks.
|
||||
notepad++=Notepad++
|
||||
|
@ -102,7 +104,7 @@ terminalProgram=Default program
|
|||
terminalProgramDescription=The default terminal to use when opening any kind of shell connection. This application is only used for display purposes, the started shell program depends on the shell connection itself.
|
||||
program=Program
|
||||
customTerminalCommand=Custom terminal command
|
||||
customTerminalCommandDescription=The command to execute to open the custom terminal. The placeholder string $cmd will be replaced by the quoted shell script file name when called. Remember to quote your terminal executable path if it contains spaces.
|
||||
customTerminalCommandDescription=The command to execute to open the custom terminal. The placeholder string $CMD will be replaced by the quoted shell script file name when called. Remember to quote your terminal executable path if it contains spaces.
|
||||
preferTerminalTabs=Prefer to open new tabs
|
||||
preferTerminalTabsDescription=Controls whether XPipe will try to open new tabs in your chosen terminal instead of new windows.
|
||||
cmd=cmd.exe
|
||||
|
|
|
@ -4,9 +4,14 @@ crlf=CRLF (Windows)
|
|||
lf=LF (Linux)
|
||||
none=None
|
||||
common=Common
|
||||
key=Key
|
||||
passwordManager=Password manager
|
||||
prompt=Prompt
|
||||
customCommand=Custom command
|
||||
other=Other
|
||||
setLock=Set lock
|
||||
changeLock=Change lock
|
||||
test=Test
|
||||
lockCreationAlertTitle=Create Lock
|
||||
lockCreationAlertHeader=Set your new lock password
|
||||
finish=Finish
|
||||
|
@ -199,6 +204,7 @@ downloadUpdate=Download update
|
|||
legalAccept=I accept the EULA and the privacy policy
|
||||
confirm=Confirm
|
||||
whatsNew=What's new in version $VERSION$?
|
||||
antivirusNoticeTitle=A note on Antivirus programs
|
||||
updateChangelogAlertTitle=Changelog
|
||||
greetingsAlertTitle=Welcome to XPipe
|
||||
gotIt=Got It
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
### Information about antivirus programs
|
||||
|
||||
XPipe detected that you are running %s. As a result, you might run into a few problems due to %s detecting suspicious activity when XPipe is interacting with your shell programs. This can range from just notifications to full isolation of XPipe and your shell programs, effectively making the application unusable.
|
||||
|
||||
### Threat analysis
|
||||
|
||||
For this reason, all artifacts of every release are automatically uploaded and analyzed on [VirusTotal](https://virustotal.com), so uploading the release you downloaded to VirusTotal should instantly show complete analysis results. From there you should be able to get a more accurate overview over the threat level of XPipe to you.
|
||||
You can find the analysis results listed at the bottom of every release on GitHub, i.e. here:
|
||||
|
||||
[https://github.com/xpipe-io/xpipe/releases/tag/%s](https://github.com/xpipe-io/xpipe/releases/tag/%s)
|
||||
|
||||
### What you can do
|
||||
|
||||
If such a false-positive also happens on your end, you might have to explicitly whitelist XPipe in order for it to work correctly. Accessing shells is necessary for XPipe, there is no fallback alternative built in that does not launch shells.
|
||||
|
||||
If you choose to continue from here, XPipe will start calling shell programs to properly initialize, which might provoke %s.
|
|
@ -1,8 +1,8 @@
|
|||
.prefs * {
|
||||
-fx-text-fill: -color-fg-default;
|
||||
-fx-highlight-text-fill: -color-fg-default;
|
||||
-fx-highlight-text-fill: -color-fg-default;
|
||||
-fx-highlight-fill: -color-neutral-muted;
|
||||
-fx-prompt-text-fill: -color-neutral-muted;
|
||||
-fx-prompt-text-fill: -color-fg-subtle;
|
||||
-fx-text-box-border:-color-neutral-muted;
|
||||
-fx-control-inner-background: -color-neutral-muted;
|
||||
-fx-body-color: -color-neutral-muted;
|
||||
|
|