From 6d07f5bab5ce6321308db3a77ce7948cb40f1b96 Mon Sep 17 00:00:00 2001 From: crschnick Date: Sun, 5 Feb 2023 15:04:18 +0000 Subject: [PATCH] Implement more robust action system and desktop shortcuts --- app/build.gradle | 2 +- .../xpipe/app/comp/about/UpdateCheckComp.java | 6 +- .../comp/storage/store/StoreEntryComp.java | 10 ++- .../comp/storage/store/StoreEntryWrapper.java | 10 +-- .../java/io/xpipe/app/grid/AppUpdater.java | 10 +-- .../xpipe/app/issue/SentryErrorHandler.java | 4 +- .../io/xpipe/app/launcher/LauncherInput.java | 73 +++++----------- .../java/io/xpipe/app/prefs/AppPrefs.java | 6 +- .../io/xpipe/core/util/XPipeInstallation.java | 25 ++++++ dist/base.gradle | 13 +++ .../ext/base/actions/AddStoreAction.java | 51 ++++++++++++ ext/base/src/main/java/module-info.java | 7 +- .../extension/XPipeServiceProviders.java | 3 +- .../xpipe/extension/util/ActionProvider.java | 79 ++++++++++++++++++ .../extension/util/DesktopShortcuts.java | 83 +++++++++++++++++++ .../extension/util/XPipeDistributionType.java | 34 ++++++-- extension/src/main/java/module-info.java | 2 + 17 files changed, 331 insertions(+), 87 deletions(-) create mode 100644 ext/base/src/main/java/io/xpipe/ext/base/actions/AddStoreAction.java create mode 100644 extension/src/main/java/io/xpipe/extension/util/ActionProvider.java create mode 100644 extension/src/main/java/io/xpipe/extension/util/DesktopShortcuts.java rename app/src/main/java/io/xpipe/app/core/AppDistributionType.java => extension/src/main/java/io/xpipe/extension/util/XPipeDistributionType.java (61%) diff --git a/app/build.gradle b/app/build.gradle index 26e37036..ceb6bb1b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -136,7 +136,7 @@ run { systemProperty 'io.xpipe.app.writeSysOut', "true" systemProperty 'io.xpipe.app.developerMode', "true" systemProperty 'io.xpipe.app.logLevel', "debug" - systemProperty "io.xpipe.beacon.port", "21724" + // systemProperty "io.xpipe.beacon.port", "21724" // systemProperty "io.xpipe.beacon.printMessages", "true" systemProperty "io.xpipe.app.extensions", extensionDirList // systemProperty 'io.xpipe.app.debugPlatform', "true" diff --git a/app/src/main/java/io/xpipe/app/comp/about/UpdateCheckComp.java b/app/src/main/java/io/xpipe/app/comp/about/UpdateCheckComp.java index 3f7a8049..58db0d70 100644 --- a/app/src/main/java/io/xpipe/app/comp/about/UpdateCheckComp.java +++ b/app/src/main/java/io/xpipe/app/comp/about/UpdateCheckComp.java @@ -1,6 +1,6 @@ package io.xpipe.app.comp.about; -import io.xpipe.app.core.AppDistributionType; +import io.xpipe.extension.util.XPipeDistributionType; import io.xpipe.app.core.AppI18n; import io.xpipe.app.grid.AppUpdater; import io.xpipe.app.util.Hyperlinks; @@ -101,7 +101,7 @@ public class UpdateCheckComp extends SimpleComp { } if (updateAvailable.getValue()) { - return AppDistributionType.get().supportsUpdate() + return XPipeDistributionType.get().supportsUpdate() ? I18n.get("downloadUpdate") : I18n.get("checkOutUpdate"); } else { @@ -134,7 +134,7 @@ public class UpdateCheckComp extends SimpleComp { return; } - if (updateAvailable.getValue() && !AppDistributionType.get().supportsUpdate()) { + if (updateAvailable.getValue() && !XPipeDistributionType.get().supportsUpdate()) { Hyperlinks.open( AppUpdater.get().getLastUpdateCheckResult().getValue().getReleaseUrl()); } else if (updateAvailable.getValue()) { diff --git a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryComp.java b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryComp.java index e1424c28..e8149576 100644 --- a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryComp.java +++ b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryComp.java @@ -163,7 +163,7 @@ public class StoreEntryComp extends SimpleComp { private Comp createButtonBar() { var list = new ArrayList>(); for (var p : entry.getActionProviders().entrySet()) { - var actionProvider = p.getKey(); + var actionProvider = p.getKey().getDataStoreCallSite(); if (!actionProvider.isMajor()) { continue; } @@ -171,7 +171,8 @@ public class StoreEntryComp extends SimpleComp { var button = new IconButtonComp( actionProvider.getIcon(entry.getEntry().getStore().asNeeded()), () -> { ThreadHelper.runFailableAsync(() -> { - actionProvider.execute(entry.getEntry().getStore().asNeeded()); + var action = actionProvider.createAction(entry.getEntry().getStore().asNeeded()); + action.execute(); }); }); button.apply(new FancyTooltipAugment<>( @@ -216,7 +217,7 @@ public class StoreEntryComp extends SimpleComp { AppFont.normal(contextMenu.getStyleableNode()); for (var p : entry.getActionProviders().entrySet()) { - var actionProvider = p.getKey(); + var actionProvider = p.getKey().getDataStoreCallSite(); if (actionProvider.isMajor()) { continue; } @@ -226,7 +227,8 @@ public class StoreEntryComp extends SimpleComp { var item = new MenuItem(null, new FontIcon(icon)); item.setOnAction(event -> { ThreadHelper.runFailableAsync(() -> { - actionProvider.execute(entry.getEntry().getStore().asNeeded()); + var action = actionProvider.createAction(entry.getEntry().getStore().asNeeded()); + action.execute(); }); }); item.textProperty().bind(name); diff --git a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryWrapper.java b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryWrapper.java index 0be20f49..2593d83b 100644 --- a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryWrapper.java +++ b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntryWrapper.java @@ -5,9 +5,9 @@ import io.xpipe.app.comp.storage.StorageFilter; import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStoreEntry; -import io.xpipe.extension.DataStoreActionProvider; import io.xpipe.extension.event.ErrorEvent; import io.xpipe.extension.fxcomps.util.PlatformThread; +import io.xpipe.extension.util.ActionProvider; import javafx.beans.binding.Bindings; import javafx.beans.property.*; import javafx.beans.value.ObservableBooleanValue; @@ -29,7 +29,7 @@ public class StoreEntryWrapper implements StorageFilter.Filterable { private final Property state = new SimpleObjectProperty<>(); private final StringProperty information = new SimpleStringProperty(); private final StringProperty summary = new SimpleStringProperty(); - private final Map, ObservableBooleanValue> actionProviders; + private final Map actionProviders; private final BooleanProperty editable = new SimpleBooleanProperty(); private final BooleanProperty renamable = new SimpleBooleanProperty(); private final BooleanProperty refreshable = new SimpleBooleanProperty(); @@ -40,10 +40,10 @@ public class StoreEntryWrapper implements StorageFilter.Filterable { this.name = new SimpleStringProperty(entry.getName()); this.lastAccess = new SimpleObjectProperty<>(entry.getLastAccess().minus(Duration.ofMillis(500))); this.actionProviders = new LinkedHashMap<>(); - DataStoreActionProvider.ALL.stream() + ActionProvider.ALL.stream() .filter(dataStoreActionProvider -> { return !entry.isDisabled() - && dataStoreActionProvider + && dataStoreActionProvider.getDataStoreCallSite() != null && dataStoreActionProvider.getDataStoreCallSite() .getApplicableClass() .isAssignableFrom(entry.getStore().getClass()); }) @@ -54,7 +54,7 @@ public class StoreEntryWrapper implements StorageFilter.Filterable { return false; } - return dataStoreActionProvider.isApplicable( + return dataStoreActionProvider.getDataStoreCallSite().isApplicable( entry.getStore().asNeeded()); }, disabledProperty(), diff --git a/app/src/main/java/io/xpipe/app/grid/AppUpdater.java b/app/src/main/java/io/xpipe/app/grid/AppUpdater.java index 5e4b3677..c908b7b2 100644 --- a/app/src/main/java/io/xpipe/app/grid/AppUpdater.java +++ b/app/src/main/java/io/xpipe/app/grid/AppUpdater.java @@ -1,7 +1,7 @@ package io.xpipe.app.grid; import io.xpipe.app.core.AppCache; -import io.xpipe.app.core.AppDistributionType; +import io.xpipe.extension.util.XPipeDistributionType; import io.xpipe.app.core.AppExtensionManager; import io.xpipe.app.core.AppProperties; import io.xpipe.app.core.mode.OperationMode; @@ -54,7 +54,7 @@ public class AppUpdater { .equals(AppProperties.get().getVersion())) { downloadedUpdate.setValue(null); } - if (!AppDistributionType.get().supportsUpdate()) { + if (!XPipeDistributionType.get().supportsUpdate()) { downloadedUpdate.setValue(null); } @@ -124,8 +124,8 @@ public class AppUpdater { INSTANCE = new AppUpdater(); - if (AppDistributionType.get().supportsUpdate() - && AppDistributionType.get() != AppDistributionType.DEVELOPMENT) { + if (XPipeDistributionType.get().supportsUpdate() + && XPipeDistributionType.get() != XPipeDistributionType.DEVELOPMENT) { ThreadHelper.create("updater", true, () -> { ThreadHelper.sleep(Duration.ofMinutes(10).toMillis()); event("Starting background updater thread"); @@ -182,7 +182,7 @@ public class AppUpdater { return; } - if (!AppDistributionType.get().supportsUpdate()) { + if (!XPipeDistributionType.get().supportsUpdate()) { return; } diff --git a/app/src/main/java/io/xpipe/app/issue/SentryErrorHandler.java b/app/src/main/java/io/xpipe/app/issue/SentryErrorHandler.java index 963ee982..ab1bc3f7 100644 --- a/app/src/main/java/io/xpipe/app/issue/SentryErrorHandler.java +++ b/app/src/main/java/io/xpipe/app/issue/SentryErrorHandler.java @@ -4,7 +4,7 @@ import io.sentry.*; import io.sentry.protocol.SentryId; import io.sentry.protocol.User; import io.xpipe.app.core.AppCache; -import io.xpipe.app.core.AppDistributionType; +import io.xpipe.extension.util.XPipeDistributionType; import io.xpipe.app.core.AppProperties; import io.xpipe.extension.event.ErrorEvent; import io.xpipe.extension.event.TrackEvent; @@ -22,7 +22,7 @@ public class SentryErrorHandler { options.setEnableUncaughtExceptionHandler(false); options.setAttachServerName(false); // options.setDebug(true); - options.setDist(AppDistributionType.get().getName()); + options.setDist(XPipeDistributionType.get().getName()); options.setRelease(AppProperties.get().getVersion()); options.setEnableShutdownHook(false); options.setProguardUuid(AppProperties.get().getBuildUuid().toString()); diff --git a/app/src/main/java/io/xpipe/app/launcher/LauncherInput.java b/app/src/main/java/io/xpipe/app/launcher/LauncherInput.java index 628db1d7..cf85e885 100644 --- a/app/src/main/java/io/xpipe/app/launcher/LauncherInput.java +++ b/app/src/main/java/io/xpipe/app/launcher/LauncherInput.java @@ -1,17 +1,11 @@ package io.xpipe.app.launcher; -import com.fasterxml.jackson.core.JsonProcessingException; import io.xpipe.app.comp.source.GuiDsCreatorMultiStep; -import io.xpipe.app.comp.source.store.GuiDsStoreCreator; import io.xpipe.app.core.mode.OperationMode; -import io.xpipe.app.storage.DataStoreEntry; import io.xpipe.core.impl.FileStore; -import io.xpipe.core.store.DataStore; -import io.xpipe.core.util.JacksonMapper; -import io.xpipe.core.util.SecretValue; import io.xpipe.extension.DataSourceProvider; import io.xpipe.extension.event.ErrorEvent; -import lombok.EqualsAndHashCode; +import io.xpipe.extension.util.ActionProvider; import lombok.Getter; import lombok.Value; @@ -22,12 +16,11 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.UUID; public abstract class LauncherInput { public static void handle(List arguments) { - var all = new ArrayList(); + var all = new ArrayList(); arguments.forEach(s -> { try { all.addAll(of(s)); @@ -55,11 +48,7 @@ public abstract class LauncherInput { }); } - public abstract void execute() throws Exception; - - public abstract boolean requiresPlatform(); - - public static List of(String input) { + public static List of(String input) { if (input.startsWith("\"") && input.endsWith("\"")) { input = input.substring(1, input.length() - 1); } @@ -76,13 +65,22 @@ public abstract class LauncherInput { if (scheme.equalsIgnoreCase("xpipe")) { var action = uri.getAuthority(); var args = Arrays.asList(uri.getPath().split("/")); - - var a = switch (action.toLowerCase()) { - case "add" -> new AddActionInput(args); - default -> throw new IllegalStateException("Unexpected value: " + action); - }; - - return List.of(a); + var found = ActionProvider.ALL.stream() + .filter(actionProvider -> actionProvider.getLauncherCallSite() != null + && actionProvider + .getLauncherCallSite() + .getId() + .equalsIgnoreCase(action)) + .findFirst(); + if (found.isPresent()) { + ActionProvider.Action a = null; + try { + a = found.get().getLauncherCallSite().createAction(args); + } catch (Exception e) { + return List.of(); + } + return List.of(a); + } } } } catch (IllegalArgumentException ignored) { @@ -96,13 +94,11 @@ public abstract class LauncherInput { } catch (InvalidPathException ignored) { } - return List.of(); } @Value - @EqualsAndHashCode(callSuper = true) - public static class LocalFileInput extends LauncherInput { + public static class LocalFileInput implements ActionProvider.Action { Path file; @@ -125,7 +121,7 @@ public abstract class LauncherInput { } } - public static abstract class ActionInput extends LauncherInput { + public abstract static class ActionInput extends LauncherInput { @Getter private final List args; @@ -134,31 +130,4 @@ public abstract class LauncherInput { this.args = args; } } - - @Value - @EqualsAndHashCode(callSuper = true) - public static class AddActionInput extends ActionInput { - - public AddActionInput(List args) { - super(args); - } - - @Override - public void execute() throws JsonProcessingException { - var type = getArgs().get(1); - var storeString = SecretValue.ofSecret(getArgs().get(2)); - var store = JacksonMapper.parse(storeString.getSecretValue(), DataStore.class); - if (store == null) { - return; - } - - var entry = DataStoreEntry.createNew(UUID.randomUUID(),"", store); - GuiDsStoreCreator.showEdit(entry); - } - - @Override - public boolean requiresPlatform() { - return true; - } - } } diff --git a/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java b/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java index 586bb4b5..9f2bac6b 100644 --- a/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java +++ b/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java @@ -8,7 +8,7 @@ import com.dlsc.preferencesfx.model.Category; import com.dlsc.preferencesfx.model.Group; import com.dlsc.preferencesfx.model.Setting; import com.dlsc.preferencesfx.util.VisibilityProperty; -import io.xpipe.app.core.AppDistributionType; +import io.xpipe.extension.util.XPipeDistributionType; import io.xpipe.app.core.AppProperties; import io.xpipe.app.core.AppStyle; import io.xpipe.extension.event.ErrorEvent; @@ -116,9 +116,9 @@ public class AppPrefs { // Automatically update // ==================== private final BooleanProperty automaticallyUpdate = - typed(new SimpleBooleanProperty(AppDistributionType.get().supportsUpdate()), Boolean.class); + typed(new SimpleBooleanProperty(XPipeDistributionType.get().supportsUpdate()), Boolean.class); private final BooleanField automaticallyUpdateField = BooleanField.ofBooleanType(automaticallyUpdate) - .editable(AppDistributionType.get().supportsUpdate()) + .editable(XPipeDistributionType.get().supportsUpdate()) .render(() -> new ToggleControl()); private final BooleanProperty updateToPrereleases = typed(new SimpleBooleanProperty(true), Boolean.class); private final BooleanProperty confirmDeletions = typed(new SimpleBooleanProperty(true), Boolean.class); diff --git a/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java b/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java index 4f963dbf..0442fa7a 100644 --- a/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java +++ b/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java @@ -177,6 +177,31 @@ public class XPipeInstallation { } } + public static Path getLocalDefaultInstallationIcon() { + Path path = getLocalInstallationBasePath(); + + // Check for development environment + if (!ModuleHelper.isImage()) { + if (OsType.getLocal().equals(OsType.WINDOWS)) { + return path.resolve("dist").resolve("logo").resolve("logo.ico"); + } else if (OsType.getLocal().equals(OsType.LINUX)) { + return path.resolve("dist").resolve("logo").resolve("logo.png"); + } else { + return path.resolve("dist").resolve("logo").resolve("logo.icns"); + } + } + + if (OsType.getLocal().equals(OsType.WINDOWS)) { + return path.resolve("app").resolve("logo.ico"); + } else if (OsType.getLocal().equals(OsType.LINUX)) { + return path.resolve("logo.png"); + } else { + return path.resolve("Contents") + .resolve("Resources") + .resolve("logo.icns"); + } + } + public static String getLocalDefaultInstallationBasePath(boolean acceptCustomHome) { var customHome = System.getenv("XPIPE_HOME"); if (customHome != null && !customHome.isEmpty() && acceptCustomHome) { diff --git a/dist/base.gradle b/dist/base.gradle index d2491106..81652d3f 100644 --- a/dist/base.gradle +++ b/dist/base.gradle @@ -22,6 +22,10 @@ if (org.gradle.internal.os.OperatingSystem.current().isWindows()) { from "$distDir/jpackage/xpiped" into "$distDir/base/app" } + copy { + from "$projectDir/logo/logo.ico" + into "$distDir/base/app" + } file("$distDir/base/app/xpiped.exe").writable = true exec { @@ -86,7 +90,12 @@ if (org.gradle.internal.os.OperatingSystem.current().isWindows()) { from "$distDir/jpackage/xpiped" into "$distDir/base/app" } + copy { + from "$projectDir/logo/logo.png" + into "$distDir/base/" + } + // Fixes a JPackage bug copy { from "$distDir/base/app/lib/app/xpiped.cfg" into "$distDir/base/app" @@ -135,6 +144,10 @@ if (org.gradle.internal.os.OperatingSystem.current().isWindows()) { from "$distDir/jpackage/xpiped.app/Contents" into "$distDir/X-Pipe.app/Contents/" } + copy { + from "$projectDir/logo/logo.icns" + into "$distDir/X-Pipe.app/Contents/Resources/" + } copy { from "$distDir/cli/xpipe" into "$distDir/X-Pipe.app/Contents/MacOS/" diff --git a/ext/base/src/main/java/io/xpipe/ext/base/actions/AddStoreAction.java b/ext/base/src/main/java/io/xpipe/ext/base/actions/AddStoreAction.java new file mode 100644 index 00000000..5d54b59b --- /dev/null +++ b/ext/base/src/main/java/io/xpipe/ext/base/actions/AddStoreAction.java @@ -0,0 +1,51 @@ +package io.xpipe.ext.base.actions; + +import io.xpipe.app.comp.source.store.GuiDsStoreCreator; +import io.xpipe.app.storage.DataStoreEntry; +import io.xpipe.core.store.DataStore; +import io.xpipe.core.util.JacksonMapper; +import io.xpipe.core.util.SecretValue; +import io.xpipe.extension.util.ActionProvider; +import lombok.Value; + +import java.util.List; +import java.util.UUID; + +public class AddStoreAction implements ActionProvider { + @Value + static class Action implements ActionProvider.Action { + + DataStore store; + + @Override + public boolean requiresPlatform() { + return true; + } + @Override + public void execute() throws Exception { + if (store == null) { + return; + } + + var entry = DataStoreEntry.createNew(UUID.randomUUID(), "", store); + GuiDsStoreCreator.showEdit(entry); + } + } + + @Override + public LauncherCallSite getLauncherCallSite() { + return new LauncherCallSite() { + @Override + public String getId() { + return "addStore"; + } + + @Override + public Action createAction(List args) throws Exception { + var storeString = SecretValue.ofSecret(args.get(1)); + var store = JacksonMapper.parse(storeString.getSecretValue(), DataStore.class); + return new Action(store); + } + }; + } +} diff --git a/ext/base/src/main/java/module-info.java b/ext/base/src/main/java/module-info.java index f51ebd36..e2b8f050 100644 --- a/ext/base/src/main/java/module-info.java +++ b/ext/base/src/main/java/module-info.java @@ -1,13 +1,11 @@ import io.xpipe.ext.base.*; -import io.xpipe.ext.base.actions.FileBrowseAction; -import io.xpipe.ext.base.actions.FileEditAction; -import io.xpipe.ext.base.actions.ShareStoreAction; -import io.xpipe.ext.base.actions.StreamExportAction; +import io.xpipe.ext.base.actions.*; import io.xpipe.ext.base.apps.*; import io.xpipe.extension.DataSourceProvider; import io.xpipe.extension.DataStoreActionProvider; import io.xpipe.extension.DataStoreProvider; import io.xpipe.extension.DataSourceTarget; +import io.xpipe.extension.util.ActionProvider; open module io.xpipe.ext.base { exports io.xpipe.ext.base; @@ -24,6 +22,7 @@ open module io.xpipe.ext.base { requires static net.synedra.validatorfx; requires static io.xpipe.app; + provides ActionProvider with AddStoreAction; provides DataStoreActionProvider with StreamExportAction, ShareStoreAction, diff --git a/extension/src/main/java/io/xpipe/extension/XPipeServiceProviders.java b/extension/src/main/java/io/xpipe/extension/XPipeServiceProviders.java index 5d014fd5..7148b11a 100644 --- a/extension/src/main/java/io/xpipe/extension/XPipeServiceProviders.java +++ b/extension/src/main/java/io/xpipe/extension/XPipeServiceProviders.java @@ -6,6 +6,7 @@ import io.xpipe.core.util.JacksonMapper; import io.xpipe.core.util.ProxyFunction; import io.xpipe.extension.event.TrackEvent; import io.xpipe.extension.prefs.PrefsProvider; +import io.xpipe.extension.util.ActionProvider; import io.xpipe.extension.util.ModuleLayerLoader; import io.xpipe.extension.util.XPipeDaemon; @@ -37,7 +38,7 @@ public class XPipeServiceProviders { ModuleLayerLoader.loadAll(layer, hasDaemon); if (hasDaemon) { - DataStoreActionProvider.init(layer); + ActionProvider.init(layer); DataSourceActionProvider.init(layer); ProxyFunction.init(layer); PrefsProvider.init(layer); diff --git a/extension/src/main/java/io/xpipe/extension/util/ActionProvider.java b/extension/src/main/java/io/xpipe/extension/util/ActionProvider.java new file mode 100644 index 00000000..cb818923 --- /dev/null +++ b/extension/src/main/java/io/xpipe/extension/util/ActionProvider.java @@ -0,0 +1,79 @@ +package io.xpipe.extension.util; + +import io.xpipe.core.store.DataStore; +import io.xpipe.extension.event.ErrorEvent; +import javafx.beans.value.ObservableValue; + +import java.util.ArrayList; +import java.util.List; +import java.util.ServiceLoader; + +public interface ActionProvider { + + static List ALL = new ArrayList<>(); + + public static void init(ModuleLayer layer) { + if (ALL.size() == 0) { + ALL.addAll(ServiceLoader.load(layer, ActionProvider.class).stream() + .map(p -> (ActionProvider) p.get()) + .filter(provider -> { + try { + return provider.isActive(); + } catch (Throwable e) { + ErrorEvent.fromThrowable(e).handle(); + return false; + } + }) + .toList()); + } + } + + interface Action { + + boolean requiresPlatform(); + + void execute() throws Exception; + } + + default boolean isActive() throws Exception { + return true; + } + + + interface LauncherCallSite { + + String getId(); + + Action createAction(List args) throws Exception; + } + + default LauncherCallSite getLauncherCallSite() { + return null; + } + + default DataStoreCallSite getDataStoreCallSite() { + return null; + } + + public static interface DataStoreCallSite { + + Action createAction(T store); + + Class getApplicableClass(); + + default boolean isMajor() { + return false; + } + default boolean isApplicable(T o) throws Exception { + return true; + } + + ObservableValue getName(T store); + + String getIcon(T store); + + default boolean showIfDisabled() { + return true; + } + } +} diff --git a/extension/src/main/java/io/xpipe/extension/util/DesktopShortcuts.java b/extension/src/main/java/io/xpipe/extension/util/DesktopShortcuts.java new file mode 100644 index 00000000..a471defc --- /dev/null +++ b/extension/src/main/java/io/xpipe/extension/util/DesktopShortcuts.java @@ -0,0 +1,83 @@ +package io.xpipe.extension.util; + +import io.xpipe.core.process.OsType; +import io.xpipe.core.store.ShellStore; +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 content = String.format( + """ + set "TARGET=%s" + set "SHORTCUT=%%HOMEDRIVE%%%%HOMEPATH%%\\Desktop\\%s.lnk" + 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.TargetPath = '%%TARGET%%'; $S.Save()" + """, + target, name, icon.toString()); + ShellStore.local().create().executeSimpleCommand(content); + } + + private static void createLinuxShortcut(String target, String name) throws Exception { + var icon = XPipeInstallation.getLocalDefaultInstallationIcon(); + var content = String.format( + """ + [Desktop Entry] + Type=Application + Name=%s + Comment=Open with X-Pipe + TryExec=/opt/xpipe/app/bin/xpiped + Exec=/opt/xpipe/cli/bin/xpipe open %s + Icon=%s + Terminal=false + Categories=Utility;Development;Office; + """, + name, target, icon.toString()); + var file = Path.of("~/Desktop/" + name + ".desktop").toRealPath(); + Files.writeString(file, content); + file.toFile().setExecutable(true); + } + + private static void createMacOSShortcut(String target, String name) throws Exception { + var icon = XPipeInstallation.getLocalDefaultInstallationIcon(); + var content = String.format( + """ + #!/bin/bash + open %s + """, + target); + var file = Path.of("~/Desktop/" + name + ".command").toRealPath(); + Files.writeString(file, content); + file.toFile().setExecutable(true); + + var iconScriptContent = String.format( + """ + iconSource="%s" + iconDestination="%s" + icon=/tmp/`basename $iconSource` + rsrc=/tmp/icon.rsrc + cp $iconSource $icon + sips -i $icon + DeRez -only icns $icon > $rsrc + SetFile -a C $iconDestination + Rez -append $rsrc -o $iconDestination + """, + icon, target); + ShellStore.local().create().executeSimpleCommand(iconScriptContent); + } + + public static void create(String target, String name) throws Exception { + if (OsType.getLocal().equals(OsType.WINDOWS)) { + createWindowsShortcut(target, name); + } else if (OsType.getLocal().equals(OsType.LINUX)) { + createLinuxShortcut(target, name); + } else { + createMacOSShortcut(target, name); + } + } +} diff --git a/app/src/main/java/io/xpipe/app/core/AppDistributionType.java b/extension/src/main/java/io/xpipe/extension/util/XPipeDistributionType.java similarity index 61% rename from app/src/main/java/io/xpipe/app/core/AppDistributionType.java rename to extension/src/main/java/io/xpipe/extension/util/XPipeDistributionType.java index d122cc07..bcf1abb8 100644 --- a/app/src/main/java/io/xpipe/app/core/AppDistributionType.java +++ b/extension/src/main/java/io/xpipe/extension/util/XPipeDistributionType.java @@ -1,11 +1,13 @@ -package io.xpipe.app.core; +package io.xpipe.extension.util; +import io.xpipe.core.process.OsType; +import io.xpipe.core.util.ModuleHelper; import io.xpipe.core.util.XPipeInstallation; import io.xpipe.extension.event.TrackEvent; -public interface AppDistributionType { +public interface XPipeDistributionType { - AppDistributionType DEVELOPMENT = new AppDistributionType() { + XPipeDistributionType DEVELOPMENT = new XPipeDistributionType() { @Override public boolean supportsUpdate() { @@ -17,12 +19,18 @@ public interface AppDistributionType { TrackEvent.info("Development mode update executed"); } + @Override + public boolean supportsURLs() { + // Enabled for testing + return true; + } + @Override public String getName() { return "development"; } }; - AppDistributionType PORTABLE = new AppDistributionType() { + XPipeDistributionType PORTABLE = new XPipeDistributionType() { @Override public boolean supportsUpdate() { @@ -32,12 +40,17 @@ public interface AppDistributionType { @Override public void performUpdateAction() {} + @Override + public boolean supportsURLs() { + return OsType.getLocal().equals(OsType.MAC); + } + @Override public String getName() { return "portable"; } }; - AppDistributionType INSTALLATION = new AppDistributionType() { + XPipeDistributionType INSTALLATION = new XPipeDistributionType() { @Override public boolean supportsUpdate() { @@ -49,14 +62,19 @@ public interface AppDistributionType { TrackEvent.info("Update action called"); } + @Override + public boolean supportsURLs() { + return true; + } + @Override public String getName() { return "install"; } }; - static AppDistributionType get() { - if (!AppProperties.get().isImage()) { + static XPipeDistributionType get() { + if (!ModuleHelper.isImage()) { return DEVELOPMENT; } @@ -71,5 +89,7 @@ public interface AppDistributionType { void performUpdateAction(); + boolean supportsURLs(); + String getName(); } diff --git a/extension/src/main/java/module-info.java b/extension/src/main/java/module-info.java index 40726bc1..d2a5744d 100644 --- a/extension/src/main/java/module-info.java +++ b/extension/src/main/java/module-info.java @@ -2,6 +2,7 @@ import io.xpipe.core.util.ProxyFunction; import io.xpipe.extension.DataSourceProvider; import io.xpipe.extension.DataStoreActionProvider; import io.xpipe.extension.DataSourceTarget; +import io.xpipe.extension.util.ActionProvider; import io.xpipe.extension.util.ModuleLayerLoader; import io.xpipe.extension.util.XPipeDaemon; @@ -52,6 +53,7 @@ open module io.xpipe.extension { uses io.xpipe.extension.Cache; uses io.xpipe.extension.DataSourceActionProvider; uses ProxyFunction; + uses ActionProvider; uses io.xpipe.extension.util.ModuleLayerLoader; provides ModuleLayerLoader with DataSourceTarget.Loader;