diff --git a/app/src/main/java/io/xpipe/app/ext/ProcessControlProvider.java b/app/src/main/java/io/xpipe/app/ext/ProcessControlProvider.java index 18bfbebc0..7c8d7d434 100644 --- a/app/src/main/java/io/xpipe/app/ext/ProcessControlProvider.java +++ b/app/src/main/java/io/xpipe/app/ext/ProcessControlProvider.java @@ -1,6 +1,10 @@ package io.xpipe.app.ext; +import io.xpipe.app.browser.BrowserAbstractSessionModel; +import io.xpipe.app.browser.BrowserFullSessionModel; +import io.xpipe.app.browser.BrowserStoreSessionTab; import io.xpipe.app.storage.DataStoreEntryRef; +import io.xpipe.app.vnc.VncBaseStore; import io.xpipe.core.process.*; import io.xpipe.core.store.DataStore; @@ -16,6 +20,7 @@ public abstract class ProcessControlProvider { .findFirst() .orElseThrow(); } + public abstract BrowserStoreSessionTab createVncSession(BrowserFullSessionModel model, DataStoreEntryRef ref); public static ProcessControlProvider get() { return INSTANCE; 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 c7316ee4e..6d355bfbc 100644 --- a/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java +++ b/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java @@ -17,6 +17,9 @@ import io.xpipe.app.update.AppDistributionType; import io.xpipe.app.util.OptionsBuilder; import io.xpipe.app.util.PlatformState; import io.xpipe.app.util.PlatformThread; +import io.xpipe.app.vnc.ExternalVncClient; +import io.xpipe.app.vnc.InternalVncClient; +import io.xpipe.app.vnc.VncCategory; import io.xpipe.core.process.ShellScript; import javafx.beans.property.*; @@ -101,6 +104,11 @@ public class AppPrefs { mapVaultShared(new SimpleObjectProperty<>(false), "alwaysConfirmElevation", Boolean.class, false); public final BooleanProperty dontCachePasswords = mapVaultShared(new SimpleBooleanProperty(false), "dontCachePasswords", Boolean.class, false); + public final Property vncClient = map(Mapping.builder() + .property(new SimpleObjectProperty<>(InternalVncClient.builder().build())) + .key("vncClient") + .valueClass(ExternalVncClient.class) + .build()); final Property passwordManager = map(Mapping.builder() .property(new SimpleObjectProperty<>()) .key("passwordManager") @@ -141,8 +149,6 @@ public class AppPrefs { return terminalAlwaysPauseOnExit; } - public final StringProperty passwordManagerCommand = - mapLocal(new SimpleStringProperty(null), "passwordManagerCommand", String.class, false); final ObjectProperty startupBehaviour = mapLocal( new SimpleObjectProperty<>(StartupBehaviour.GUI), "startupBehaviour", StartupBehaviour.class, true); public final BooleanProperty enableGitStorage = @@ -305,6 +311,7 @@ public class AppPrefs { new LoggingCategory(), new EditorCategory(), new RdpCategory(), + new VncCategory(), new SshCategory(), new ConnectionHubCategory(), new FileBrowserCategory(), @@ -589,15 +596,6 @@ public class AppPrefs { var f = PlatformState.determineDefaultScalingFactor(); uiScale.setValue(f.isPresent() ? f.getAsInt() : null); } - - // Migrate legacy password manager - if (passwordManagerCommand.get() != null - && !passwordManagerCommand.get().isBlank() - && passwordManager.getValue() == null) { - passwordManager.setValue(PasswordManagerCommand.builder() - .script(new ShellScript(passwordManagerCommand.get())) - .build()); - } } private void loadSharedRemote() { diff --git a/app/src/main/java/io/xpipe/app/util/AppJacksonModule.java b/app/src/main/java/io/xpipe/app/util/AppJacksonModule.java index 603ec7b6d..52e700caa 100644 --- a/app/src/main/java/io/xpipe/app/util/AppJacksonModule.java +++ b/app/src/main/java/io/xpipe/app/util/AppJacksonModule.java @@ -6,6 +6,7 @@ import io.xpipe.app.storage.*; import io.xpipe.app.terminal.ExternalTerminalType; import io.xpipe.app.terminal.TerminalMultiplexer; import io.xpipe.app.terminal.TerminalPrompt; +import io.xpipe.app.vnc.ExternalVncClient; import io.xpipe.core.util.InPlaceSecretValue; import io.xpipe.core.util.JacksonMapper; @@ -47,6 +48,7 @@ public class AppJacksonModule extends SimpleModule { context.registerSubtypes(PasswordManager.getClasses()); context.registerSubtypes(TerminalMultiplexer.getClasses()); context.registerSubtypes(TerminalPrompt.getClasses()); + context.registerSubtypes(ExternalVncClient.getClasses()); context.addSerializers(_serializers); context.addDeserializers(_deserializers); diff --git a/app/src/main/java/io/xpipe/app/vnc/ExternalVncClient.java b/app/src/main/java/io/xpipe/app/vnc/ExternalVncClient.java index 8f8252034..4001a3144 100644 --- a/app/src/main/java/io/xpipe/app/vnc/ExternalVncClient.java +++ b/app/src/main/java/io/xpipe/app/vnc/ExternalVncClient.java @@ -2,7 +2,10 @@ package io.xpipe.app.vnc; import com.fasterxml.jackson.annotation.JsonTypeInfo; import io.xpipe.app.pwman.*; +import io.xpipe.app.storage.DataStoreEntry; +import io.xpipe.app.storage.DataStoreEntryRef; import io.xpipe.core.process.OsType; +import io.xpipe.core.process.ShellControl; import io.xpipe.core.util.SecretValue; import lombok.Value; @@ -14,20 +17,8 @@ public interface ExternalVncClient { static List> getClasses() { var l = new ArrayList>(); - l.add(OnePasswordManager.class); - l.add(KeePassXcPasswordManager.class); - l.add(BitwardenPasswordManager.class); - l.add(DashlanePasswordManager.class); - if (OsType.getLocal() != OsType.WINDOWS) { - l.add(LastpassPasswordManager.class); - l.add(EnpassPasswordManager.class); - } - l.add(KeeperPasswordManager.class); - l.add(PsonoPasswordManager.class); - if (OsType.getLocal() == OsType.WINDOWS) { - l.add(WindowsCredentialManager.class); - } - l.add(PasswordManagerCommand.class); + l.add(InternalVncClient.class); + l.add(TightVncClient.class); return l; } @@ -36,11 +27,10 @@ public interface ExternalVncClient { String title; String host; int port; - SecretValue password; + DataStoreEntryRef entry; } boolean isAvailable(); void launch(LaunchConfiguration configuration) throws Exception; - } diff --git a/app/src/main/java/io/xpipe/app/vnc/InternalVncClient.java b/app/src/main/java/io/xpipe/app/vnc/InternalVncClient.java new file mode 100644 index 000000000..9da0713ae --- /dev/null +++ b/app/src/main/java/io/xpipe/app/vnc/InternalVncClient.java @@ -0,0 +1,44 @@ +package io.xpipe.app.vnc; + +import com.fasterxml.jackson.annotation.JsonTypeName; +import io.xpipe.app.browser.BrowserFullSessionModel; +import io.xpipe.app.browser.BrowserStoreSessionTab; +import io.xpipe.app.core.AppLayoutModel; +import io.xpipe.app.ext.ProcessControlProvider; +import io.xpipe.app.prefs.ExternalApplicationType; +import io.xpipe.app.util.LocalShell; +import io.xpipe.core.process.CommandBuilder; +import lombok.Builder; +import lombok.extern.jackson.Jacksonized; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; + +@Builder +@Jacksonized +@JsonTypeName("xpipe") +public class InternalVncClient implements ExternalVncClient { + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public void launch(LaunchConfiguration configuration) throws Exception { + var open = BrowserFullSessionModel.DEFAULT.getSessionEntriesSnapshot().stream() + .filter(browserSessionTab -> browserSessionTab instanceof BrowserStoreSessionTab st + && st.getEntry().get().equals(configuration.getEntry().get())) + .findFirst() + .orElse(null); + if (open != null) { + AppLayoutModel.get().selectBrowser(); + BrowserFullSessionModel.DEFAULT.getSelectedEntry().setValue(open); + return; + } + + BrowserFullSessionModel.DEFAULT.openSync(ProcessControlProvider.get().createVncSession(BrowserFullSessionModel.DEFAULT, configuration.getEntry()), null); + AppLayoutModel.get().selectBrowser(); + } +} diff --git a/app/src/main/java/io/xpipe/app/vnc/TightVncClient.java b/app/src/main/java/io/xpipe/app/vnc/TightVncClient.java index 681640a1a..6269f6013 100644 --- a/app/src/main/java/io/xpipe/app/vnc/TightVncClient.java +++ b/app/src/main/java/io/xpipe/app/vnc/TightVncClient.java @@ -21,6 +21,11 @@ public class TightVncClient implements ExternalApplicationType.WindowsType, Exte return true; } + @Override + public String getId() { + return null; + } + @Override public String getExecutable() { return "tvnviewer.exe"; @@ -36,12 +41,7 @@ public class TightVncClient implements ExternalApplicationType.WindowsType, Exte @Override public void launch(LaunchConfiguration configuration) throws Exception { - var command = CommandBuilder.of().addFile(findExecutable()).add("-host").addLiteral(configuration.getHost()).add("-port").add("" + configuration.getPort()); + var command = CommandBuilder.of().addFile(findExecutable()).addQuoted(configuration.getHost() + "::" + configuration.getPort()); LocalShell.getShell().command(command).execute(); } - - @Override - public String getId() { - return ""; - } } diff --git a/app/src/main/java/io/xpipe/app/vnc/VncBaseStore.java b/app/src/main/java/io/xpipe/app/vnc/VncBaseStore.java new file mode 100644 index 000000000..532be67f4 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/vnc/VncBaseStore.java @@ -0,0 +1,18 @@ +package io.xpipe.app.vnc; + +import io.xpipe.app.util.SecretRetrievalStrategy; +import io.xpipe.core.process.ShellControl; +import io.xpipe.core.store.DataStore; + +import java.util.Optional; + +public interface VncBaseStore extends DataStore { + + String getEffectiveHost(); + + int getEffectivePort(); + + String getUser(); + + SecretRetrievalStrategy getPassword(); +} diff --git a/app/src/main/java/io/xpipe/app/vnc/VncCategory.java b/app/src/main/java/io/xpipe/app/vnc/VncCategory.java new file mode 100644 index 000000000..2ef5b0b22 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/vnc/VncCategory.java @@ -0,0 +1,55 @@ +package io.xpipe.app.vnc; + +import io.xpipe.app.comp.Comp; +import io.xpipe.app.comp.base.ButtonComp; +import io.xpipe.app.core.AppI18n; +import io.xpipe.app.prefs.AppPrefs; +import io.xpipe.app.prefs.AppPrefsCategory; +import io.xpipe.app.pwman.PasswordManager; +import io.xpipe.app.util.*; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import org.kordamp.ikonli.javafx.FontIcon; + +public class VncCategory extends AppPrefsCategory { + + @Override + protected String getId() { + return "vnc"; + } + + @Override + protected Comp create() { + var prefs = AppPrefs.get(); + var choiceBuilder = OptionsChoiceBuilder.builder() + .property(prefs.vncClient) + .subclasses(ExternalVncClient.getClasses()) + .allowNull(false) + .transformer(entryComboBox -> { + var docsLinkButton = new ButtonComp( + AppI18n.observable("docs"), new FontIcon("mdi2h-help-circle-outline"), () -> { + Hyperlinks.open(DocumentationLink.PASSWORD_MANAGER.getLink()); + }); + docsLinkButton.minWidth(Region.USE_PREF_SIZE); + + var hbox = new HBox(entryComboBox, docsLinkButton.createRegion()); + HBox.setHgrow(entryComboBox, Priority.ALWAYS); + hbox.setSpacing(10); + hbox.setMaxWidth(getCompWidth()); + return hbox; + }) + .build(); + prefs.vncClient.addListener((observable, oldValue, newValue) -> { + int a = 0; + }); + var choice = choiceBuilder.build().buildComp(); + return new OptionsBuilder() + .addTitle("vncClient") + .sub(new OptionsBuilder() + .pref(prefs.vncClient) + .addComp(choice) + ) + .buildComp(); + } +} diff --git a/app/src/main/java/module-info.java b/app/src/main/java/module-info.java index bff18d757..4fd94a6ee 100644 --- a/app/src/main/java/module-info.java +++ b/app/src/main/java/module-info.java @@ -42,6 +42,7 @@ open module io.xpipe.app { exports io.xpipe.app.icon; exports io.xpipe.app.pwman; exports io.xpipe.app.rdp; + exports io.xpipe.app.vnc; requires com.sun.jna; requires com.sun.jna.platform;