diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/DaemonFocusExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/DaemonFocusExchangeImpl.java index 9eda2856a..ff86617e4 100644 --- a/app/src/main/java/io/xpipe/app/beacon/impl/DaemonFocusExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/beacon/impl/DaemonFocusExchangeImpl.java @@ -9,8 +9,11 @@ import com.sun.net.httpserver.HttpExchange; public class DaemonFocusExchangeImpl extends DaemonFocusExchange { @Override - public Object handle(HttpExchange exchange, Request msg) { - AppOperationMode.switchUp(AppOperationMode.GUI); + public Object handle(HttpExchange exchange, Request msg) throws Throwable { + if (AppOperationMode.GUI.isSupported()) { + AppOperationMode.switchToSyncOrThrow(AppOperationMode.GUI); + } + var w = AppMainWindow.get(); if (w != null) { w.focus(); diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserDialogs.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserDialogs.java index 814a7091b..2689c67f2 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserDialogs.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserDialogs.java @@ -10,7 +10,7 @@ import javafx.beans.property.SimpleObjectProperty; public class BrowserDialogs { - public static FileConflictChoice showFileConflictAlert(FilePath file, boolean multiple) { + public static FileConflictChoice showFileConflictDialog(FilePath file, boolean multiple) { var choice = new SimpleObjectProperty(); var key = multiple ? "fileConflictAlertContentMultiple" : "fileConflictAlertContent"; var w = multiple ? 1050 : 400; diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileTransferOperation.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileTransferOperation.java index 124b673c1..83e0a651a 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileTransferOperation.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileTransferOperation.java @@ -109,7 +109,7 @@ public class BrowserFileTransferOperation { return BrowserDialogs.FileConflictChoice.SKIP; } - var choice = BrowserDialogs.showFileConflictAlert(target, multiple); + var choice = BrowserDialogs.showFileConflictDialog(target, multiple); if (choice == BrowserDialogs.FileConflictChoice.CANCEL) { lastConflictChoice = BrowserDialogs.FileConflictChoice.CANCEL; return BrowserDialogs.FileConflictChoice.CANCEL; diff --git a/app/src/main/java/io/xpipe/app/core/App.java b/app/src/main/java/io/xpipe/app/core/App.java index 393b1f75f..3d473332a 100644 --- a/app/src/main/java/io/xpipe/app/core/App.java +++ b/app/src/main/java/io/xpipe/app/core/App.java @@ -21,7 +21,7 @@ public class App extends Application { @Override @SneakyThrows public void start(Stage primaryStage) { - TrackEvent.info("Application launched"); + TrackEvent.info("Platform application started"); APP = this; stage = primaryStage; } diff --git a/app/src/main/java/io/xpipe/app/core/mode/AppBaseMode.java b/app/src/main/java/io/xpipe/app/core/mode/AppBaseMode.java index 568767a09..6b39075c0 100644 --- a/app/src/main/java/io/xpipe/app/core/mode/AppBaseMode.java +++ b/app/src/main/java/io/xpipe/app/core/mode/AppBaseMode.java @@ -28,7 +28,7 @@ import io.xpipe.app.storage.DataStorageSyncHandler; import io.xpipe.app.terminal.TerminalLauncherManager; import io.xpipe.app.terminal.TerminalView; import io.xpipe.app.update.UpdateAvailableDialog; -import io.xpipe.app.update.UpdateChangelogAlert; +import io.xpipe.app.update.UpdateChangelogDialog; import io.xpipe.app.update.UpdateNagDialog; import io.xpipe.app.util.*; import io.xpipe.core.XPipeDaemonMode; @@ -167,7 +167,7 @@ public class AppBaseMode extends AppOperationMode { AppGreetingsDialog.showAndWaitIfNeeded(); TrackEvent.info("Waiting for startup dialogs to close"); AppDialog.waitForAllDialogsClose(); - UpdateChangelogAlert.showIfNeeded(); + UpdateChangelogDialog.showIfNeeded(); ActionProvider.initProviders(); DataStoreProviders.init(); diff --git a/app/src/main/java/io/xpipe/app/core/mode/AppGuiMode.java b/app/src/main/java/io/xpipe/app/core/mode/AppGuiMode.java index 44f74c277..8c1bbcd3d 100644 --- a/app/src/main/java/io/xpipe/app/core/mode/AppGuiMode.java +++ b/app/src/main/java/io/xpipe/app/core/mode/AppGuiMode.java @@ -2,6 +2,7 @@ package io.xpipe.app.core.mode; import io.xpipe.app.core.window.AppMainWindow; import io.xpipe.app.issue.TrackEvent; +import io.xpipe.app.platform.PlatformInit; import io.xpipe.app.platform.PlatformThread; import io.xpipe.app.util.LicenseProvider; import io.xpipe.core.OsType; @@ -9,7 +10,14 @@ import io.xpipe.core.OsType; import javafx.application.Platform; import javafx.stage.Stage; -public class AppGuiMode extends AppPlatformMode { +public class AppGuiMode extends AppOperationMode { + + @Override + public boolean isSupported() { + // We force GUI to be supported and fail with a terminal + // exception if we can't initialize the platform + return true; + } @Override public String getId() { @@ -32,7 +40,8 @@ public class AppGuiMode extends AppPlatformMode { @Override public void onSwitchTo() throws Throwable { - super.onSwitchTo(); + AppOperationMode.BACKGROUND.onSwitchTo(); + PlatformInit.init(true); // Refresh license check // In case our exit behavior is set to continue in background, @@ -43,4 +52,10 @@ public class AppGuiMode extends AppPlatformMode { AppMainWindow.get().show(); }); } + + @Override + public void finalTeardown() throws Throwable { + onSwitchFrom(); + BACKGROUND.finalTeardown(); + } } diff --git a/app/src/main/java/io/xpipe/app/core/mode/AppOperationMode.java b/app/src/main/java/io/xpipe/app/core/mode/AppOperationMode.java index 846ea5b74..637a99011 100644 --- a/app/src/main/java/io/xpipe/app/core/mode/AppOperationMode.java +++ b/app/src/main/java/io/xpipe/app/core/mode/AppOperationMode.java @@ -5,7 +5,7 @@ import io.xpipe.app.core.*; import io.xpipe.app.core.check.AppDebugModeCheck; import io.xpipe.app.core.window.AppMainWindow; import io.xpipe.app.issue.*; -import io.xpipe.app.platform.NodeCallback; +import io.xpipe.app.platform.PlatformThreadWatcher; import io.xpipe.app.platform.PlatformInit; import io.xpipe.app.platform.PlatformState; import io.xpipe.app.prefs.AppPrefs; @@ -111,7 +111,7 @@ public abstract class AppOperationMode { AppMainWindow.loadingText("initializingApp"); GlobalTimer.init(); AppProperties.init(args); - NodeCallback.init(); + PlatformThreadWatcher.init(); AppLogs.init(); AppDebugModeCheck.printIfNeeded(); AppProperties.get().logArguments(); @@ -222,23 +222,6 @@ public abstract class AppOperationMode { return true; } - public static void switchUp(AppOperationMode newMode) { - if (newMode == BACKGROUND) { - return; - } - - TrackEvent.info("Attempting to switch mode up to " + newMode.getId()); - - if (newMode.equals(TRAY) && TRAY.isSupported() && AppOperationMode.get() == BACKGROUND) { - set(TRAY); - return; - } - - if (newMode.equals(GUI) && GUI.isSupported()) { - set(GUI); - } - } - public static void close() { set(null); } diff --git a/app/src/main/java/io/xpipe/app/core/mode/AppPlatformMode.java b/app/src/main/java/io/xpipe/app/core/mode/AppPlatformMode.java deleted file mode 100644 index e46905fbe..000000000 --- a/app/src/main/java/io/xpipe/app/core/mode/AppPlatformMode.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.xpipe.app.core.mode; - -import io.xpipe.app.platform.PlatformInit; - -public abstract class AppPlatformMode extends AppOperationMode { - - @Override - public boolean isSupported() { - return true; - } - - @Override - public void onSwitchTo() throws Throwable { - AppOperationMode.BACKGROUND.onSwitchTo(); - PlatformInit.init(true); - } - - @Override - public void finalTeardown() throws Throwable { - onSwitchFrom(); - BACKGROUND.finalTeardown(); - } -} diff --git a/app/src/main/java/io/xpipe/app/core/mode/AppTrayMode.java b/app/src/main/java/io/xpipe/app/core/mode/AppTrayMode.java index 7ef1cc9f1..657a0c26b 100644 --- a/app/src/main/java/io/xpipe/app/core/mode/AppTrayMode.java +++ b/app/src/main/java/io/xpipe/app/core/mode/AppTrayMode.java @@ -2,24 +2,26 @@ package io.xpipe.app.core.mode; import io.xpipe.app.core.AppTray; import io.xpipe.app.issue.*; +import io.xpipe.app.platform.PlatformInit; import io.xpipe.app.platform.PlatformThread; import io.xpipe.core.OsType; import java.awt.*; -public class AppTrayMode extends AppPlatformMode { +public class AppTrayMode extends AppOperationMode { @Override public boolean isSupported() { return OsType.getLocal() == OsType.WINDOWS - && super.isSupported() && Desktop.isDesktopSupported() && SystemTray.isSupported(); } @Override public void onSwitchTo() throws Throwable { - super.onSwitchTo(); + AppOperationMode.BACKGROUND.onSwitchTo(); + PlatformInit.init(true); + PlatformThread.runLaterIfNeededBlocking(() -> { if (AppTray.get() == null) { TrackEvent.info("Initializing tray"); @@ -56,4 +58,10 @@ public class AppTrayMode extends AppPlatformMode { ErrorAction.ignore().handle(event); }); } + + @Override + public void finalTeardown() throws Throwable { + onSwitchFrom(); + BACKGROUND.finalTeardown(); + } } diff --git a/app/src/main/java/io/xpipe/app/core/window/AppDialog.java b/app/src/main/java/io/xpipe/app/core/window/AppDialog.java index 32c045813..6c4603c4c 100644 --- a/app/src/main/java/io/xpipe/app/core/window/AppDialog.java +++ b/app/src/main/java/io/xpipe/app/core/window/AppDialog.java @@ -27,11 +27,6 @@ public class AppDialog { @Getter private static final ObservableList modalOverlays = FXCollections.observableArrayList(); - private static void showMainWindow() { - PlatformInit.init(true); - AppMainWindow.init(true); - } - public static void closeDialog(ModalOverlay overlay) { PlatformThread.runLaterIfNeeded(() -> { synchronized (modalOverlays) { @@ -67,7 +62,9 @@ public class AppDialog { } public static void show(ModalOverlay o, boolean wait) { - showMainWindow(); + PlatformInit.init(true); + AppMainWindow.init(true); + if (!Platform.isFxApplicationThread()) { PlatformThread.runLaterIfNeededBlocking(() -> { synchronized (modalOverlays) { diff --git a/app/src/main/java/io/xpipe/app/core/window/AppMainWindow.java b/app/src/main/java/io/xpipe/app/core/window/AppMainWindow.java index 06ffd22a9..c408f559f 100644 --- a/app/src/main/java/io/xpipe/app/core/window/AppMainWindow.java +++ b/app/src/main/java/io/xpipe/app/core/window/AppMainWindow.java @@ -105,7 +105,7 @@ public class AppMainWindow { } AppWindowStyle.addIcons(stage); AppWindowStyle.addStylesheets(stage.getScene()); - AppWindowStyle.addNavigationStyleClasses(stage.getScene()); + AppWindowStyle.addNavigationPseudoClasses(stage.getScene()); AppWindowStyle.addClickShield(stage); AppWindowStyle.addMaximizedPseudoClass(stage); AppWindowStyle.addFontSize(stage); diff --git a/app/src/main/java/io/xpipe/app/core/window/AppSideWindow.java b/app/src/main/java/io/xpipe/app/core/window/AppSideWindow.java index a7676cd27..767643e44 100644 --- a/app/src/main/java/io/xpipe/app/core/window/AppSideWindow.java +++ b/app/src/main/java/io/xpipe/app/core/window/AppSideWindow.java @@ -91,7 +91,7 @@ public class AppSideWindow { AppModifiedStage.prepareStage(stage); AppWindowStyle.addIcons(stage); AppWindowStyle.addStylesheets(alert.getDialogPane().getScene()); - AppWindowStyle.addNavigationStyleClasses(alert.getDialogPane().getScene()); + AppWindowStyle.addNavigationPseudoClasses(alert.getDialogPane().getScene()); return alert; } } diff --git a/app/src/main/java/io/xpipe/app/core/window/AppWindowStyle.java b/app/src/main/java/io/xpipe/app/core/window/AppWindowStyle.java index 57968e362..a66664106 100644 --- a/app/src/main/java/io/xpipe/app/core/window/AppWindowStyle.java +++ b/app/src/main/java/io/xpipe/app/core/window/AppWindowStyle.java @@ -33,7 +33,7 @@ public class AppWindowStyle { }); } - public static void addNavigationStyleClasses(Scene scene) { + public static void addNavigationPseudoClasses(Scene scene) { Consumer onInput = kb -> { var r = scene.getRoot(); if (r != null) { diff --git a/app/src/main/java/io/xpipe/app/issue/ErrorAction.java b/app/src/main/java/io/xpipe/app/issue/ErrorAction.java index 187dd5b8f..86378a572 100644 --- a/app/src/main/java/io/xpipe/app/issue/ErrorAction.java +++ b/app/src/main/java/io/xpipe/app/issue/ErrorAction.java @@ -8,23 +8,10 @@ import io.xpipe.core.FailableSupplier; public interface ErrorAction { static ErrorAction openDocumentation(String link) { - return new ErrorAction() { - @Override - public String getName() { - return AppI18n.get("openDocumentation"); - } - - @Override - public String getDescription() { - return AppI18n.get("openDocumentationDescription"); - } - - @Override - public boolean handle(ErrorEvent event) { - Hyperlinks.open(link); - return false; - } - }; + return translated("openDocumentation", () -> { + Hyperlinks.open(link); + return false; + }); } static ErrorAction translated(String key, FailableSupplier r) { diff --git a/app/src/main/java/io/xpipe/app/issue/ErrorDetailsComp.java b/app/src/main/java/io/xpipe/app/issue/ErrorDetailsComp.java deleted file mode 100644 index a536c4bfb..000000000 --- a/app/src/main/java/io/xpipe/app/issue/ErrorDetailsComp.java +++ /dev/null @@ -1,40 +0,0 @@ -package io.xpipe.app.issue; - -import io.xpipe.app.comp.Comp; -import io.xpipe.app.comp.SimpleComp; -import io.xpipe.app.core.AppFontSizes; -import io.xpipe.app.util.Deobfuscator; - -import javafx.geometry.Insets; -import javafx.scene.control.TextArea; -import javafx.scene.layout.Region; - -import lombok.AllArgsConstructor; - -@AllArgsConstructor -public class ErrorDetailsComp extends SimpleComp { - - private ErrorEvent event; - - private Region createStrackTraceContent() { - if (event.getThrowable() != null) { - String stackTrace = Deobfuscator.deobfuscateToString(event.getThrowable()); - stackTrace = stackTrace.replace("\t", ""); - var tf = new TextArea(stackTrace); - AppFontSizes.xs(tf); - tf.setWrapText(true); - tf.setEditable(false); - tf.setPadding(new Insets(10, 0, 10, 0)); - return tf; - } - - return new Region(); - } - - @Override - protected Region createSimple() { - var tb = Comp.of(this::createStrackTraceContent); - tb.apply(r -> AppFontSizes.xs(r.get())); - return tb.createRegion(); - } -} diff --git a/app/src/main/java/io/xpipe/app/issue/ErrorHandlerDialog.java b/app/src/main/java/io/xpipe/app/issue/ErrorHandlerDialog.java index 77830c34c..ed402ee50 100644 --- a/app/src/main/java/io/xpipe/app/issue/ErrorHandlerDialog.java +++ b/app/src/main/java/io/xpipe/app/issue/ErrorHandlerDialog.java @@ -3,12 +3,17 @@ package io.xpipe.app.issue; import io.xpipe.app.comp.Comp; import io.xpipe.app.comp.base.ModalButton; import io.xpipe.app.comp.base.ModalOverlay; +import io.xpipe.app.core.AppFontSizes; import io.xpipe.app.core.mode.AppOperationMode; import io.xpipe.app.core.window.AppDialog; import io.xpipe.app.platform.LabelGraphic; +import io.xpipe.app.util.Deobfuscator; import javafx.application.Platform; +import javafx.geometry.Insets; +import javafx.scene.control.TextArea; +import javafx.scene.layout.Region; import org.kordamp.ikonli.javafx.FontIcon; import java.util.concurrent.atomic.AtomicReference; @@ -39,9 +44,12 @@ public class ErrorHandlerDialog { errorModal.addButton(new ModalButton( "stackTrace", () -> { - var content = - new ErrorDetailsComp(event).prefWidth(650).prefHeight(750); - var detailsModal = ModalOverlay.of("errorDetails", content); + var detailsModal = ModalOverlay.of("errorDetails", Comp.of(() -> { + var content = createStrackTraceContent(event); + content.setPrefWidth(650); + content.setPrefHeight(750); + return content; + })); detailsModal.show(); }, false, @@ -76,4 +84,20 @@ public class ErrorHandlerDialog { ErrorAction.ignore().handle(event); } } + + private static Region createStrackTraceContent(ErrorEvent event) { + if (event.getThrowable() != null) { + String stackTrace = Deobfuscator.deobfuscateToString(event.getThrowable()); + stackTrace = stackTrace.replace("\t", ""); + var tf = new TextArea(stackTrace); + AppFontSizes.xs(tf); + tf.setWrapText(true); + tf.setEditable(false); + tf.setPadding(new Insets(10, 0, 10, 0)); + return tf; + } + + return new Region(); + } + } diff --git a/app/src/main/java/io/xpipe/app/issue/LogErrorHandler.java b/app/src/main/java/io/xpipe/app/issue/LogErrorHandler.java index 2916946a1..62125e907 100644 --- a/app/src/main/java/io/xpipe/app/issue/LogErrorHandler.java +++ b/app/src/main/java/io/xpipe/app/issue/LogErrorHandler.java @@ -23,7 +23,8 @@ public class LogErrorHandler implements ErrorHandler { System.err.println(event.getDescription()); } if (event.getThrowable() != null) { - Deobfuscator.printStackTrace(event.getThrowable()); + var s = Deobfuscator.deobfuscateToString(event.getThrowable()); + System.err.println(s); } } } diff --git a/app/src/main/java/io/xpipe/app/issue/TrackEvent.java b/app/src/main/java/io/xpipe/app/issue/TrackEvent.java index bf74e65ba..4ecb5b98c 100644 --- a/app/src/main/java/io/xpipe/app/issue/TrackEvent.java +++ b/app/src/main/java/io/xpipe/app/issue/TrackEvent.java @@ -13,7 +13,6 @@ import java.util.stream.Collectors; @Getter public class TrackEvent { - private final Thread thread = Thread.currentThread(); private final Instant instant = Instant.now(); private String type; private String message; diff --git a/app/src/main/java/io/xpipe/app/platform/NodeCallback.java b/app/src/main/java/io/xpipe/app/platform/PlatformThreadWatcher.java similarity index 99% rename from app/src/main/java/io/xpipe/app/platform/NodeCallback.java rename to app/src/main/java/io/xpipe/app/platform/PlatformThreadWatcher.java index 2768b90d5..62f5788eb 100644 --- a/app/src/main/java/io/xpipe/app/platform/NodeCallback.java +++ b/app/src/main/java/io/xpipe/app/platform/PlatformThreadWatcher.java @@ -14,7 +14,7 @@ import java.util.HashSet; import java.util.Set; import java.util.function.Consumer; -public class NodeCallback { +public class PlatformThreadWatcher { private static final Set windows = new HashSet<>(); private static final Set nodes = new HashSet<>(); 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 240534e9d..710c7d04b 100644 --- a/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java +++ b/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java @@ -47,6 +47,36 @@ public final class AppPrefs { private static AppPrefs INSTANCE; private final List mapping = new ArrayList<>(); + public static void initLocal() { + INSTANCE = new AppPrefs(); + PrefsProvider.getAll().forEach(prov -> prov.addPrefs(INSTANCE.extensionHandler)); + INSTANCE.loadLocal(); + INSTANCE.vaultStorageHandler = + new AppPrefsStorageHandler(DataStorage.getStorageDirectory().resolve("preferences.json")); + INSTANCE.fixLocalValues(); + } + + public static void initSynced() throws Exception { + INSTANCE.loadSharedRemote(); + INSTANCE.encryptAllVaultData.addListener((observableValue, aBoolean, t1) -> { + if (DataStorage.get() != null) { + DataStorage.get().forceRewrite(); + } + }); + } + + public static void reset() { + INSTANCE.save(); + + // Keep instance as we might need some values on shutdown, e.g. on update with terminals + // INSTANCE = null; + } + + public static AppPrefs get() { + return INSTANCE; + } + + @Getter private final BooleanProperty requiresRestart = new GlobalBooleanProperty(false); @@ -83,22 +113,54 @@ public final class AppPrefs { .documentationLink(DocumentationLink.MCP) .build()); final BooleanProperty enableMcpMutationTools = - mapLocal(new GlobalBooleanProperty(false), "enableMcpMutationTools", Boolean.class, false); + map(Mapping.builder() + .property(new GlobalBooleanProperty(false)) + .key("enableMcpMutationTools") + .valueClass(Boolean.class) + .build()); final BooleanProperty dontAutomaticallyStartVmSshServer = mapVaultShared(new GlobalBooleanProperty(false), "dontAutomaticallyStartVmSshServer", Boolean.class, false); final BooleanProperty dontAcceptNewHostKeys = mapVaultShared(new GlobalBooleanProperty(false), "dontAcceptNewHostKeys", Boolean.class, false); public final BooleanProperty performanceMode = - mapLocal(new GlobalBooleanProperty(), "performanceMode", Boolean.class, false); + map(Mapping.builder() + .property(new GlobalObjectProperty<>()) + .key("performanceMode") + .valueClass(Boolean.class) + .build()); public final ObjectProperty theme = - mapLocal(new GlobalObjectProperty<>(), "theme", AppTheme.Theme.class, false); - final BooleanProperty useSystemFont = mapLocal( - new GlobalBooleanProperty(OsType.getLocal() != OsType.MACOS), "useSystemFont", Boolean.class, false); - final Property uiScale = mapLocal(new GlobalObjectProperty<>(null), "uiScale", Integer.class, true); + map(Mapping.builder() + .property(new GlobalObjectProperty<>()) + .key("theme") + .valueClass(AppTheme.Theme.class) + .build()); + final BooleanProperty useSystemFont = + map(Mapping.builder() + .property(new GlobalBooleanProperty(OsType.getLocal() != OsType.MACOS)) + .key("useSystemFont") + .valueClass(Boolean.class) + .build()); + final Property uiScale = + map(Mapping.builder() + .property(new GlobalObjectProperty<>()) + .key("uiScale") + .valueClass(Integer.class) + .requiresRestart(true) + .build()); final BooleanProperty saveWindowLocation = - mapLocal(new GlobalBooleanProperty(true), "saveWindowLocation", Boolean.class, false); + map(Mapping.builder() + .property(new GlobalBooleanProperty(true)) + .key("saveWindowLocation") + .valueClass(Boolean.class) + .requiresRestart(false) + .build()); final BooleanProperty preferTerminalTabs = - mapLocal(new GlobalBooleanProperty(true), "preferTerminalTabs", Boolean.class, false); + map(Mapping.builder() + .property(new GlobalBooleanProperty(true)) + .key("preferTerminalTabs") + .valueClass(Boolean.class) + .requiresRestart(false) + .build()); final ObjectProperty terminalType = map(Mapping.builder() .property(new GlobalObjectProperty<>()) .key("terminalType") @@ -113,11 +175,27 @@ public final class AppPrefs { .requiresRestart(false) .documentationLink(DocumentationLink.RDP) .build()); - final DoubleProperty windowOpacity = mapLocal(new GlobalDoubleProperty(1.0), "windowOpacity", Double.class, false); + final DoubleProperty windowOpacity = + map(Mapping.builder() + .property(new GlobalDoubleProperty(1.0)) + .key("windowOpacity") + .valueClass(Double.class) + .requiresRestart(false) + .build()); final StringProperty customTerminalCommand = - mapLocal(new GlobalStringProperty(null), "customTerminalCommand", String.class, false); + map(Mapping.builder() + .property(new GlobalStringProperty(null)) + .key("customTerminalCommand") + .valueClass(String.class) + .requiresRestart(false) + .build()); final BooleanProperty clearTerminalOnInit = - mapLocal(new GlobalBooleanProperty(true), "clearTerminalOnInit", Boolean.class, false); + map(Mapping.builder() + .property(new GlobalBooleanProperty(true)) + .key("clearTerminalOnInit") + .valueClass(Boolean.class) + .requiresRestart(false) + .build()); final Property> iconSources = map(Mapping.builder() .property(new GlobalObjectProperty<>(new ArrayList<>())) .key("iconSources") @@ -125,9 +203,18 @@ public final class AppPrefs { .vaultSpecific(true) .build()); public final BooleanProperty disableCertutilUse = - mapLocal(new GlobalBooleanProperty(false), "disableCertutilUse", Boolean.class, false); + map(Mapping.builder() + .property(new GlobalBooleanProperty(false)) + .key("disableCertutilUse") + .valueClass(Boolean.class) + .build()); public final BooleanProperty useLocalFallbackShell = - mapLocal(new GlobalBooleanProperty(false), "useLocalFallbackShell", Boolean.class, true); + map(Mapping.builder() + .property(new GlobalBooleanProperty(false)) + .key("useLocalFallbackShell") + .valueClass(Boolean.class) + .requiresRestart(true) + .build()); final Property localShellDialect = map(Mapping.builder() .property(new GlobalObjectProperty<>( ProcessControlProvider.get().getAvailableLocalDialects().getFirst())) @@ -142,7 +229,11 @@ public final class AppPrefs { public final Property alwaysConfirmElevation = mapVaultShared(new GlobalObjectProperty<>(false), "alwaysConfirmElevation", Boolean.class, false); public final BooleanProperty focusWindowOnNotifications = - mapLocal(new GlobalBooleanProperty(true), "focusWindowOnNotifications", Boolean.class, false); + map(Mapping.builder() + .property(new GlobalBooleanProperty(false)) + .key("focusWindowOnNotifications") + .valueClass(Boolean.class) + .build()); public final BooleanProperty dontCachePasswords = mapVaultShared(new GlobalBooleanProperty(false), "dontCachePasswords", Boolean.class, false); public final Property vncClient = map(Mapping.builder() @@ -178,7 +269,11 @@ public final class AppPrefs { .documentationLink(DocumentationLink.TERMINAL_MULTIPLEXER) .build()); final Property terminalAlwaysPauseOnExit = - mapLocal(new GlobalBooleanProperty(true), "terminalAlwaysPauseOnExit", Boolean.class, false); + map(Mapping.builder() + .property(new GlobalBooleanProperty(false)) + .key("terminalAlwaysPauseOnExit") + .valueClass(Boolean.class) + .build()); final Property terminalPrompt = map(Mapping.builder() .property(new GlobalObjectProperty<>(null)) .key("terminalPrompt") @@ -186,8 +281,13 @@ public final class AppPrefs { .log(false) .documentationLink(DocumentationLink.TERMINAL_PROMPT) .build()); - final ObjectProperty startupBehaviour = mapLocal( - new GlobalObjectProperty<>(StartupBehaviour.GUI), "startupBehaviour", StartupBehaviour.class, true); + final ObjectProperty startupBehaviour = + map(Mapping.builder() + .property(new GlobalObjectProperty<>(StartupBehaviour.GUI)) + .key("startupBehaviour") + .valueClass(StartupBehaviour.class) + .requiresRestart(true) + .build()); public final BooleanProperty enableGitStorage = map(Mapping.builder() .property(new GlobalBooleanProperty(false)) .key("enableGitStorage") @@ -203,7 +303,11 @@ public final class AppPrefs { .documentationLink(DocumentationLink.SYNC) .build()); final ObjectProperty closeBehaviour = - mapLocal(new GlobalObjectProperty<>(CloseBehaviour.QUIT), "closeBehaviour", CloseBehaviour.class, false); + map(Mapping.builder() + .property(new GlobalObjectProperty<>(CloseBehaviour.QUIT)) + .key("closeBehaviour") + .valueClass(CloseBehaviour.class) + .build()); final ObjectProperty externalEditor = mapLocal(new GlobalObjectProperty<>(), "externalEditor", ExternalEditorType.class, false); final StringProperty customEditorCommand = @@ -288,75 +392,42 @@ public final class AppPrefs { mapVaultShared(new GlobalStringProperty(), "workspaceLock", String.class, true); @Getter - private final List categories; + private final List categories = List.of( + new AboutCategory(), + new AppearanceCategory(), + new VaultCategory(), + new SyncCategory(), + new PasswordManagerCategory(), + new TerminalCategory(), + new LoggingCategory(), + new EditorCategory(), + new RdpCategory(), + new VncCategory(), + new SshCategory(), + new ConnectionHubCategory(), + new FileBrowserCategory(), + new IconsCategory(), + new SystemCategory(), + new ApiCategory(), + new McpCategory(), + new UpdatesCategory(), + new SecurityCategory(), + new WorkspacesCategory(), + new DeveloperCategory(), + new TroubleshootCategory(), + new LinksCategory()); private final AppPrefsStorageHandler globalStorageHandler = new AppPrefsStorageHandler( AppProperties.get().getDataDir().resolve("settings").resolve("preferences.json")); private final Map customEntries = new LinkedHashMap<>(); @Getter - private final Property selectedCategory; + private final Property selectedCategory = new GlobalObjectProperty<>(categories.getFirst()); private final PrefsHandler extensionHandler = new PrefsHandlerImpl(); private AppPrefsStorageHandler vaultStorageHandler; - private AppPrefs() { - this.categories = Stream.of( - new AboutCategory(), - new AppearanceCategory(), - new VaultCategory(), - new SyncCategory(), - new PasswordManagerCategory(), - new TerminalCategory(), - new LoggingCategory(), - new EditorCategory(), - new RdpCategory(), - new VncCategory(), - new SshCategory(), - new ConnectionHubCategory(), - new FileBrowserCategory(), - new IconsCategory(), - new SystemCategory(), - new ApiCategory(), - new McpCategory(), - new UpdatesCategory(), - new SecurityCategory(), - new WorkspacesCategory(), - new DeveloperCategory(), - new TroubleshootCategory(), - new LinksCategory()) - .toList(); - this.selectedCategory = new GlobalObjectProperty<>(categories.getFirst()); - } - - public static void initLocal() { - INSTANCE = new AppPrefs(); - PrefsProvider.getAll().forEach(prov -> prov.addPrefs(INSTANCE.extensionHandler)); - INSTANCE.loadLocal(); - INSTANCE.vaultStorageHandler = - new AppPrefsStorageHandler(DataStorage.getStorageDirectory().resolve("preferences.json")); - INSTANCE.fixLocalValues(); - } - - public static void initSynced() throws Exception { - INSTANCE.loadSharedRemote(); - INSTANCE.encryptAllVaultData.addListener((observableValue, aBoolean, t1) -> { - if (DataStorage.get() != null) { - DataStorage.get().forceRewrite(); - } - }); - } - - public static void reset() { - INSTANCE.save(); - - // Keep instance as we might need some values on shutdown, e.g. on update with terminals - // INSTANCE = null; - } - - public static AppPrefs get() { - return INSTANCE; - } + private AppPrefs() {} public ObservableBooleanValue disableHardwareAcceleration() { return disableHardwareAcceleration; @@ -647,8 +718,8 @@ public final class AppPrefs { var writable = (Property) prop; PlatformThread.runLaterIfNeededBlocking(() -> { writable.setValue(newValue); - save(); }); + save(); } private void fixLocalValues() { @@ -751,7 +822,7 @@ public final class AppPrefs { return val; } - public void save() { + public synchronized void save() { for (Mapping m : mapping) { AppPrefsStorageHandler handler = m.isVaultSpecific() ? vaultStorageHandler : globalStorageHandler; // It might be possible that we save while the vault handler is not initialized yet / has no file or diff --git a/app/src/main/java/io/xpipe/app/prefs/AppPrefsSidebarComp.java b/app/src/main/java/io/xpipe/app/prefs/AppPrefsSidebarComp.java index 6c8044933..5e3d31600 100644 --- a/app/src/main/java/io/xpipe/app/prefs/AppPrefsSidebarComp.java +++ b/app/src/main/java/io/xpipe/app/prefs/AppPrefsSidebarComp.java @@ -23,8 +23,6 @@ import java.util.stream.Collectors; public class AppPrefsSidebarComp extends SimpleComp { - private static final PseudoClass SELECTED = PseudoClass.getPseudoClass("selected"); - @Override protected Region createSimple() { var effectiveCategories = AppPrefs.get().getCategories().stream() @@ -43,7 +41,7 @@ public class AppPrefsSidebarComp extends SimpleComp { struc.get().setTextAlignment(TextAlignment.LEFT); struc.get().setAlignment(Pos.CENTER_LEFT); AppPrefs.get().getSelectedCategory().subscribe(val -> { - struc.get().pseudoClassStateChanged(SELECTED, appPrefsCategory.equals(val)); + struc.get().pseudoClassStateChanged(PseudoClass.getPseudoClass("selected"), appPrefsCategory.equals(val)); }); }) .grow(true, false); diff --git a/app/src/main/java/io/xpipe/app/prefs/UpdateCheckComp.java b/app/src/main/java/io/xpipe/app/prefs/UpdateCheckComp.java index ce31f75b5..7e88b6059 100644 --- a/app/src/main/java/io/xpipe/app/prefs/UpdateCheckComp.java +++ b/app/src/main/java/io/xpipe/app/prefs/UpdateCheckComp.java @@ -12,7 +12,7 @@ import javafx.scene.layout.Region; public class UpdateCheckComp extends SimpleComp { - private void showAlert() { + private void showDialog() { ThreadHelper.runFailableAsync(() -> { AppDistributionType.get().getUpdateHandler().refreshUpdateCheckSilent(false, false); UpdateAvailableDialog.showIfNeeded(false); @@ -96,7 +96,7 @@ public class UpdateCheckComp extends SimpleComp { return new TileButtonComp(name, description, graphic, actionEvent -> { actionEvent.consume(); if (uh.getPreparedUpdate().getValue() != null) { - showAlert(); + showDialog(); return; } diff --git a/app/src/main/java/io/xpipe/app/update/UpdateChangelogAlert.java b/app/src/main/java/io/xpipe/app/update/UpdateChangelogDialog.java similarity index 65% rename from app/src/main/java/io/xpipe/app/update/UpdateChangelogAlert.java rename to app/src/main/java/io/xpipe/app/update/UpdateChangelogDialog.java index b9288b9ee..b69d7e404 100644 --- a/app/src/main/java/io/xpipe/app/update/UpdateChangelogAlert.java +++ b/app/src/main/java/io/xpipe/app/update/UpdateChangelogDialog.java @@ -7,12 +7,11 @@ import io.xpipe.app.comp.base.ModalOverlay; import io.xpipe.app.core.AppI18n; import io.xpipe.app.core.window.AppDialog; import io.xpipe.app.issue.ErrorAction; -import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.issue.ErrorEventFactory; import io.xpipe.app.util.DocumentationLink; import io.xpipe.app.util.Hyperlinks; -public class UpdateChangelogAlert { +public class UpdateChangelogDialog { private static boolean shown = false; @@ -21,23 +20,10 @@ public class UpdateChangelogAlert { if (update != null && !AppDistributionType.get().getUpdateHandler().isUpdateSucceeded()) { ErrorEventFactory.fromMessage(AppI18n.get("updateFail")) .documentationLink(DocumentationLink.UPDATE_FAIL) - .customAction(new ErrorAction() { - @Override - public String getName() { - return AppI18n.get("updateFailAction"); - } - - @Override - public String getDescription() { - return AppI18n.get("updateFailActionDescription"); - } - - @Override - public boolean handle(ErrorEvent event) { - Hyperlinks.open(Hyperlinks.GITHUB_LATEST); - return true; - } - }) + .customAction(ErrorAction.translated("updateFailAction", () -> { + Hyperlinks.open(Hyperlinks.GITHUB_LATEST); + return true; + })) .handle(); return; } diff --git a/app/src/main/java/io/xpipe/app/util/Deobfuscator.java b/app/src/main/java/io/xpipe/app/util/Deobfuscator.java index 5ec569d32..d1d506096 100644 --- a/app/src/main/java/io/xpipe/app/util/Deobfuscator.java +++ b/app/src/main/java/io/xpipe/app/util/Deobfuscator.java @@ -45,11 +45,6 @@ public class Deobfuscator { return stackTrace; } - public static void printStackTrace(Throwable t) { - var s = deobfuscateToString(t); - System.err.println(s); - } - private static boolean canDeobfuscate() { if (!System.getenv().containsKey("XPIPE_MAPPING")) { return false;