diff --git a/app/src/main/java/io/xpipe/app/comp/CompDescriptor.java b/app/src/main/java/io/xpipe/app/comp/CompDescriptor.java index 44707ffd1..f906a908e 100644 --- a/app/src/main/java/io/xpipe/app/comp/CompDescriptor.java +++ b/app/src/main/java/io/xpipe/app/comp/CompDescriptor.java @@ -3,6 +3,7 @@ package io.xpipe.app.comp; import io.xpipe.app.comp.base.TooltipHelper; import io.xpipe.app.core.AppI18n; +import io.xpipe.app.platform.PlatformThread; import javafx.application.Platform; import javafx.beans.binding.Bindings; import javafx.beans.property.ReadOnlyObjectWrapper; @@ -69,15 +70,15 @@ public class CompDescriptor { getName() != null ? getName() : new ReadOnlyObjectWrapper<>(), getDescription() != null ? getDescription() : new ReadOnlyObjectWrapper<>()); - var tt = TooltipHelper.create(tooltipText); + var tt = TooltipHelper.create(PlatformThread.sync(tooltipText)); Tooltip.install(r, tt); } if (accessibleText != null) { - r.accessibleTextProperty().bind(getName()); + r.accessibleTextProperty().bind(PlatformThread.sync(getName())); } if (getDescription() != null) { - r.accessibleHelpProperty().bind(getDescription()); + r.accessibleHelpProperty().bind(PlatformThread.sync(getDescription())); } if (getFocusTraversal() != null) { switch (getFocusTraversal()) { diff --git a/app/src/main/java/io/xpipe/app/comp/base/OptionsComp.java b/app/src/main/java/io/xpipe/app/comp/base/OptionsComp.java index fb6731637..3f960c5ee 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/OptionsComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/OptionsComp.java @@ -96,7 +96,7 @@ public class OptionsComp extends Comp> { if (entry.description() != null) { var description = new Label(); - description.wrapTextProperty().bind(pane.visibleProperty()); + description.setWrapText(true); description.getStyleClass().add("description"); description.textProperty().bind(entry.description()); description.setAlignment(Pos.CENTER_LEFT); 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 2adbde52f..5f084fb1f 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 @@ -4,6 +4,8 @@ import io.xpipe.app.beacon.AppBeaconServer; import io.xpipe.app.core.*; import io.xpipe.app.core.check.AppDebugModeCheck; import io.xpipe.app.core.window.AppMainWindow; +import io.xpipe.app.core.window.AppSideWindow; +import io.xpipe.app.core.window.AppWindowStyle; import io.xpipe.app.issue.*; import io.xpipe.app.platform.PlatformInit; import io.xpipe.app.platform.PlatformState; @@ -177,6 +179,11 @@ public abstract class AppOperationMode { ThreadHelper.runAsync(() -> { DataStorage.get().generateCaches(); }); + // Ugly solution to only start tracking kb input after we are finished starting up + // Otherwise, any typed vault password will always make think that kb input is active + Platform.runLater(() -> { + AppWindowStyle.addNavigationPseudoClasses(AppMainWindow.get().getStage().getScene()); + }); } catch (Throwable ex) { ErrorEventFactory.fromThrowable(ex).term().handle(); } 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 014f39b36..6b46edc3c 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,6 @@ public class AppMainWindow { } AppWindowStyle.addIcons(stage); AppWindowStyle.addStylesheets(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/platform/PlatformState.java b/app/src/main/java/io/xpipe/app/platform/PlatformState.java index 28faafbd2..65034da67 100644 --- a/app/src/main/java/io/xpipe/app/platform/PlatformState.java +++ b/app/src/main/java/io/xpipe/app/platform/PlatformState.java @@ -150,6 +150,7 @@ public enum PlatformState { // Platform initialization has failed in this case var msg = getErrorMessage(t.getMessage()); var ex = new UnsupportedOperationException(msg, t); + ErrorEventFactory.expected(ex); PlatformState.setCurrent(PlatformState.EXITED); lastError = ex; return; diff --git a/app/src/main/java/io/xpipe/app/platform/PlatformThreadWatcher.java b/app/src/main/java/io/xpipe/app/platform/PlatformThreadWatcher.java index 62f5788eb..5388e626f 100644 --- a/app/src/main/java/io/xpipe/app/platform/PlatformThreadWatcher.java +++ b/app/src/main/java/io/xpipe/app/platform/PlatformThreadWatcher.java @@ -8,6 +8,7 @@ import javafx.beans.value.ObservableValue; import javafx.collections.ListChangeListener; import javafx.scene.Node; import javafx.scene.Parent; +import javafx.scene.control.Labeled; import javafx.stage.Window; import java.util.HashSet; @@ -96,6 +97,11 @@ public class PlatformThreadWatcher { c.opacityProperty().addListener(listener); c.accessibleHelpProperty().addListener(listener); c.accessibleTextProperty().addListener(listener); + + if (c instanceof Labeled l) { + l.textProperty().addListener(listener); + l.graphicProperty().addListener(listener); + } } else { c.visibleProperty().removeListener(listener); c.boundsInParentProperty().removeListener(listener); @@ -103,6 +109,11 @@ public class PlatformThreadWatcher { c.opacityProperty().removeListener(listener); c.accessibleHelpProperty().removeListener(listener); c.accessibleTextProperty().removeListener(listener); + + if (c instanceof Labeled l) { + l.textProperty().removeListener(listener); + l.graphicProperty().removeListener(listener); + } } }); }); 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 f010fa663..7cfbbf55a 100644 --- a/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java +++ b/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java @@ -13,6 +13,7 @@ import io.xpipe.app.process.ShellDialect; import io.xpipe.app.process.ShellScript; import io.xpipe.app.pwman.PasswordManager; import io.xpipe.app.rdp.ExternalRdpClient; +import io.xpipe.app.spice.ExternalSpiceClient; import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStorageUserHandler; import io.xpipe.app.terminal.ExternalTerminalType; @@ -243,6 +244,11 @@ public final class AppPrefs { .valueClass(ExternalVncClient.class) .documentationLink(DocumentationLink.VNC) .build()); + public final Property spiceClient = map(Mapping.builder() + .property(new GlobalObjectProperty<>()) + .key("spiceClient") + .valueClass(ExternalSpiceClient.class) + .build()); final Property passwordManager = map(Mapping.builder() .property(new GlobalObjectProperty<>()) .key("passwordManager") @@ -801,6 +807,7 @@ public final class AppPrefs { externalEditor.setValue(ExternalEditorType.determineDefault(externalEditor.get())); terminalType.set(ExternalTerminalType.determineDefault(terminalType.get())); rdpClientType.setValue(ExternalRdpClient.determineDefault(rdpClientType.get())); + spiceClient.setValue(ExternalSpiceClient.determineDefault(spiceClient.getValue())); PrefsProvider.getAll().forEach(prov -> prov.initDefaultValues()); } diff --git a/app/src/main/java/io/xpipe/app/prefs/PasswordManagerTestComp.java b/app/src/main/java/io/xpipe/app/prefs/PasswordManagerTestComp.java index 82b5022ad..ba8a8eb9a 100644 --- a/app/src/main/java/io/xpipe/app/prefs/PasswordManagerTestComp.java +++ b/app/src/main/java/io/xpipe/app/prefs/PasswordManagerTestComp.java @@ -22,11 +22,13 @@ import org.kordamp.ikonli.javafx.FontIcon; import java.time.Duration; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; public class PasswordManagerTestComp extends SimpleComp { private final StringProperty value; private final boolean handleEnter; + private final AtomicInteger counter = new AtomicInteger(0); public PasswordManagerTestComp(StringProperty value, boolean handleEnter) { this.value = value; @@ -85,6 +87,7 @@ public class PasswordManagerTestComp extends SimpleComp { } private void testPasswordManager(String key, StringProperty testPasswordManagerResult) { + var currentIndex = counter.incrementAndGet(); var prefs = AppPrefs.get(); ThreadHelper.runFailableAsync(() -> { if (prefs.passwordManager.getValue() == null || key == null) { @@ -111,7 +114,9 @@ public class PasswordManagerTestComp extends SimpleComp { GlobalTimer.delay( () -> { Platform.runLater(() -> { - testPasswordManagerResult.set(null); + if (counter.get() == currentIndex) { + testPasswordManagerResult.set(null); + } }); }, Duration.ofSeconds(5)); diff --git a/app/src/main/java/io/xpipe/app/pwman/KeeperPasswordManager.java b/app/src/main/java/io/xpipe/app/pwman/KeeperPasswordManager.java index f6002d7f1..69360148a 100644 --- a/app/src/main/java/io/xpipe/app/pwman/KeeperPasswordManager.java +++ b/app/src/main/java/io/xpipe/app/pwman/KeeperPasswordManager.java @@ -1,26 +1,50 @@ package io.xpipe.app.pwman; +import io.xpipe.app.comp.base.ButtonComp; +import io.xpipe.app.comp.base.ListBoxViewComp; +import io.xpipe.app.core.AppI18n; import io.xpipe.app.ext.ProcessControlProvider; import io.xpipe.app.issue.ErrorEventFactory; +import io.xpipe.app.platform.DerivedObservableList; +import io.xpipe.app.platform.OptionsBuilder; import io.xpipe.app.process.*; import io.xpipe.app.secret.SecretManager; import io.xpipe.app.secret.SecretPromptStrategy; +import io.xpipe.app.secret.SecretQueryState; import io.xpipe.app.terminal.TerminalLaunch; -import io.xpipe.core.InPlaceSecretValue; -import io.xpipe.core.JacksonMapper; -import io.xpipe.core.OsType; +import io.xpipe.app.util.AskpassAlert; +import io.xpipe.app.util.ThreadHelper; +import io.xpipe.core.*; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; +import javafx.application.Platform; +import javafx.beans.binding.Bindings; +import javafx.beans.property.Property; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; +import lombok.extern.jackson.Jacksonized; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.List; import java.util.UUID; @JsonTypeName("keeper") +@Getter +@Builder(toBuilder = true) +@ToString +@Jacksonized public class KeeperPasswordManager implements PasswordManager { private static final UUID KEEPER_PASSWORD_ID = UUID.randomUUID(); private static ShellControl SHELL; + private final Boolean mfa; private static synchronized ShellControl getOrStartShell() throws Exception { if (SHELL == null) { @@ -31,18 +55,29 @@ public class KeeperPasswordManager implements PasswordManager { } private String getExecutable(ShellControl sc) { - return sc.getShellDialect() == ShellDialects.CMD - ? "@keeper" - : (OsType.ofLocal() == OsType.WINDOWS ? "keeper-commander" : "keeper"); + return OsType.ofLocal() == OsType.WINDOWS ? "keeper-commander" : "keeper"; + } + + @SuppressWarnings("unused") + public static OptionsBuilder createOptions(Property p) { + var mfa = new SimpleObjectProperty<>(p.getValue().getMfa()); + return new OptionsBuilder() + .nameAndDescription("keeperUseMfa") + .addToggle(mfa) + .bind( + () -> { + return KeeperPasswordManager.builder().mfa(mfa.get()).build(); + }, + p); } @Override public synchronized CredentialResult retrieveCredentials(String key) { // The copy UID button copies the whole URL in the Keeper UI. Why? ... - key = key.replaceFirst("https://keepersecurity\\.\\w+/vault/#detail/", ""); + key = key.replaceFirst("https://\\w+\\.\\w+/vault/#detail/", ""); try { - CommandSupport.isInLocalPathOrThrow("Keeper Commander CLI", "keeper"); + CommandSupport.isInLocalPathOrThrow("Keeper Commander CLI", "keeper-commander"); } catch (Exception e) { ErrorEventFactory.fromThrowable(e) .link("https://docs.keeper.io/en/keeperpam/commander-cli/commander-installation-setup") @@ -52,8 +87,8 @@ public class KeeperPasswordManager implements PasswordManager { try { var sc = getOrStartShell(); - var file = sc.view().userHome().join(".keeper", "config.json"); - if (!sc.view().fileExists(file)) { + var config = sc.view().userHome().join(".keeper", "config.json"); + if (!sc.view().fileExists(config)) { var script = ShellScript.lines( sc.getShellDialect().getEchoCommand("Log in into your Keeper account from the CLI:", false), getExecutable(sc) + " login"); @@ -76,19 +111,97 @@ public class KeeperPasswordManager implements PasswordManager { return null; } - var out = sc.command(CommandBuilder.of() - .add(getExecutable(sc), "get") - .addLiteral(key) - .add("--format", "json", "--unmask") - .add("--password") - .addLiteral(r.getSecretValue())) - .sensitive() - .readStdoutOrThrow(); + if (r.getSecretValue().contains("\"")) { + SecretManager.clearAll(KEEPER_PASSWORD_ID); + throw ErrorEventFactory.expected(new IllegalArgumentException("Keeper password contains double quote \" character, which is not supported by the Keeper Commander application")); + } + + var b = CommandBuilder.of() + .add(getExecutable(sc), "get") + .addLiteral(key) + .add("--format", "json", "--unmask") + .add("--password") + .addLiteral(r.getSecretValue()); + FilePath file = null; + CommandBuilder fullB; + if (mfa != null && mfa) { + var totp = AskpassAlert.queryRaw("Enter Keeper 2FA Code", null, true); + if (totp.getState() != SecretQueryState.NORMAL) { + return null; + } + + var input = """ + + 1 + %s + """.formatted(totp.getSecret().getSecretValue()); + file = sc.getSystemTemporaryDirectory().join("keeper.txt"); + sc.view().writeTextFile(file, input); + fullB = CommandBuilder.of().add(sc.getShellDialect() == ShellDialects.CMD ? "type" : "cat").addFile(file).add("|").add(b); + } else { + fullB = b; + } + + var queryCommand = sc.command(fullB); + queryCommand.sensitive(); + queryCommand.killOnTimeout(CountDown.of().start(15_000)); + + var result = queryCommand.readStdoutAndStderr(); + var exitCode = queryCommand.getExitCode(); + + if (file != null) { + sc.view().deleteFileIfPossible(file); + } + + var out = result[0].replace("\r\n", "\n").replace(""" + Selection: Invalid entry, additional factors of authentication shown may be configured if not currently enabled. + Selection:\s + 2FA Code Duration: Require Every Login. + To change duration: 2fa_duration=login|12_hours|24_hours|30_days|forever + """, "") + .replace(""" + This account requires 2FA Authentication + + 1. TOTP (Google and Microsoft Authenticator) \s + q. Quit login attempt and return to Commander prompt + """, "") + .replace("Selection:", "") + .strip(); + var err = result[1].replace("\r\n", "\n") + .replace(""" + EOF when reading a line + """, "") + .strip(); + + var jsonStart = out.indexOf("{\n"); + var jsonEnd = out.indexOf("\n}"); + if (jsonEnd != -1) { + jsonEnd += 2; + } + + var outPrefix = jsonStart <= 0 ? out : out.substring(0, jsonStart + 1); + var outJson = jsonStart <= 0 ? (jsonEnd != -1 ? out.substring(0, jsonEnd) : out) : + (jsonEnd != -1 ? out.substring(jsonStart, jsonEnd) : out.substring(jsonStart)); + + if (exitCode != 0) { + var wrongPw = outPrefix.contains("Enter password for"); + if (wrongPw) { + SecretManager.clearAll(KEEPER_PASSWORD_ID); + ErrorEventFactory.fromMessage("Master password was not accepted by Keeper. Is it correct?").expected().handle(); + return null; + } + + var message = !err.isEmpty() ? outPrefix + "\n" + err : outPrefix; + ErrorEventFactory.fromMessage(message).expected().handle(); + return null; + } + JsonNode tree; try { - tree = JacksonMapper.getDefault().readTree(out); + tree = JacksonMapper.getDefault().readTree(outJson); } catch (JsonProcessingException e) { - ErrorEventFactory.fromMessage(out).expected().handle(); + var message = !err.isEmpty() ? outPrefix + "\n" + err : outPrefix; + ErrorEventFactory.fromMessage(message).expected().handle(); return null; } diff --git a/app/src/main/java/io/xpipe/app/secret/SecretPasswordManagerStrategy.java b/app/src/main/java/io/xpipe/app/secret/SecretPasswordManagerStrategy.java index 908b5ea86..bd2f13464 100644 --- a/app/src/main/java/io/xpipe/app/secret/SecretPasswordManagerStrategy.java +++ b/app/src/main/java/io/xpipe/app/secret/SecretPasswordManagerStrategy.java @@ -105,7 +105,7 @@ public class SecretPasswordManagerStrategy implements SecretRetrievalStrategy { @Override public Duration cacheDuration() { // To reduce password manager access, cache it for a few seconds - return Duration.ofSeconds(10); + return Duration.ofSeconds(15); } @Override diff --git a/app/src/main/java/io/xpipe/app/spice/CustomSpiceClient.java b/app/src/main/java/io/xpipe/app/spice/CustomSpiceClient.java new file mode 100644 index 000000000..0a0ef5cd3 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/spice/CustomSpiceClient.java @@ -0,0 +1,54 @@ +package io.xpipe.app.spice; + +import com.fasterxml.jackson.annotation.JsonTypeName; +import io.xpipe.app.comp.base.TextFieldComp; +import io.xpipe.app.platform.OptionsBuilder; +import io.xpipe.app.prefs.ExternalApplicationHelper; +import io.xpipe.app.process.CommandBuilder; +import io.xpipe.app.vnc.ExternalVncClient; +import io.xpipe.app.vnc.VncLaunchConfig; +import javafx.beans.property.Property; +import javafx.beans.property.SimpleObjectProperty; +import lombok.Builder; +import lombok.Value; +import lombok.extern.jackson.Jacksonized; + +import java.util.Locale; + +@JsonTypeName("custom") +@Value +@Jacksonized +@Builder +public class CustomSpiceClient implements ExternalSpiceClient { + + String command; + + @SuppressWarnings("unused") + static OptionsBuilder createOptions(Property property) { + var command = new SimpleObjectProperty<>(property.getValue().getCommand()); + return new OptionsBuilder() + .nameAndDescription("customSpiceCommand") + .addComp( + new TextFieldComp(command, false) + .apply(struc -> struc.get().setPromptText("myspiceClient $FILE")) + .maxWidth(600), + command) + .bind(() -> CustomSpiceClient.builder().command(command.get()).build(), property); + } + + @Override + public void launch(SpiceLaunchConfig configuration) throws Exception { + if (command == null) { + return; + } + + var format = command.toLowerCase(Locale.ROOT).contains("$file") ? command : command + " $FILE"; + var toExecute = ExternalApplicationHelper.replaceVariableArgument(format, "ADDRESS", configuration.getFile().toString()); + ExternalApplicationHelper.startAsync(CommandBuilder.of().add(toExecute)); + } + + @Override + public String getWebsite() { + return null; + } +} diff --git a/app/src/main/java/io/xpipe/app/spice/ExternalSpiceClient.java b/app/src/main/java/io/xpipe/app/spice/ExternalSpiceClient.java new file mode 100644 index 000000000..9f269daa4 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/spice/ExternalSpiceClient.java @@ -0,0 +1,65 @@ +package io.xpipe.app.spice; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import io.xpipe.app.ext.PrefsValue; +import io.xpipe.app.platform.ClipboardHelper; +import io.xpipe.app.prefs.AppPrefs; +import io.xpipe.app.rdp.*; +import io.xpipe.app.vnc.*; +import io.xpipe.core.OsType; + +import java.util.ArrayList; +import java.util.List; + +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") +public interface ExternalSpiceClient extends PrefsValue { + + static ExternalSpiceClient determineDefault(ExternalSpiceClient existing) { + // Verify that our selection is still valid + if (existing != null && existing.isAvailable()) { + return existing; + } + + return switch (OsType.ofLocal()) { + case OsType.Linux ignored -> { + yield new VirtViewerSpiceClient.Linux(); + } + case OsType.MacOs ignored -> { + yield new VirtViewerSpiceClient.MacOs(); + } + case OsType.Windows ignored -> { + yield new VirtViewerSpiceClient.Windows(); + } + }; + } + + static void launchClient(SpiceLaunchConfig configuration) throws Exception { + var client = AppPrefs.get().spiceClient.getValue(); + if (client == null) { + return; + } + + client.launch(configuration); + } + + static List> getClasses() { + var l = new ArrayList>(); + switch (OsType.ofLocal()) { + case OsType.Linux ignored -> { + l.add(VirtViewerSpiceClient.Linux.class); + } + case OsType.MacOs ignored -> { + l.add(VirtViewerSpiceClient.MacOs.class); + } + case OsType.Windows ignored -> { + l.add(VirtViewerSpiceClient.Windows.class); + } + } + l.add(CustomSpiceClient.class); + return l; + } + + void launch(SpiceLaunchConfig configuration) throws Exception; + + String getWebsite(); +} diff --git a/app/src/main/java/io/xpipe/app/spice/SpiceLaunchConfig.java b/app/src/main/java/io/xpipe/app/spice/SpiceLaunchConfig.java new file mode 100644 index 000000000..56141bdae --- /dev/null +++ b/app/src/main/java/io/xpipe/app/spice/SpiceLaunchConfig.java @@ -0,0 +1,18 @@ +package io.xpipe.app.spice; + +import io.xpipe.app.process.ShellControl; +import io.xpipe.app.secret.SecretManager; +import io.xpipe.app.storage.DataStoreEntryRef; +import io.xpipe.app.vnc.VncBaseStore; +import io.xpipe.core.SecretValue; +import lombok.Value; + +import java.nio.file.Path; +import java.util.Optional; + +@Value +public class SpiceLaunchConfig { + + DataStoreEntryRef entry; + Path file; +} diff --git a/app/src/main/java/io/xpipe/app/spice/VirtViewerSpiceClient.java b/app/src/main/java/io/xpipe/app/spice/VirtViewerSpiceClient.java new file mode 100644 index 000000000..138b48a31 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/spice/VirtViewerSpiceClient.java @@ -0,0 +1,117 @@ +package io.xpipe.app.spice; + +import com.fasterxml.jackson.annotation.JsonTypeName; +import io.xpipe.app.core.AppSystemInfo; +import io.xpipe.app.issue.ErrorEventFactory; +import io.xpipe.app.prefs.ExternalApplicationType; +import io.xpipe.app.process.CommandBuilder; +import io.xpipe.app.process.LocalShell; +import io.xpipe.app.vnc.ExternalVncClient; +import io.xpipe.app.vnc.VncLaunchConfig; +import lombok.Builder; +import lombok.extern.jackson.Jacksonized; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; + +public abstract class VirtViewerSpiceClient implements ExternalSpiceClient { + + protected CommandBuilder createBuilder(SpiceLaunchConfig configuration) { + var builder = CommandBuilder.of().addFile(configuration.getFile()); + return builder; + } + + @Override + public String getWebsite() { + return "https://virt-manager.org"; + } + + @Builder + @Jacksonized + @JsonTypeName("virtViewer") + public static class Windows extends VirtViewerSpiceClient implements ExternalApplicationType.WindowsType { + + @Override + public boolean detach() { + return false; + } + + @Override + public String getExecutable() { + return "virt-viewer.exe"; + } + + @Override + public Optional determineInstallation() { + try (var stream = Files.list(AppSystemInfo.ofWindows().getProgramFiles())) { + var l = stream.toList(); + var found = l.stream().filter(path -> path.toString().contains("VirtViewer")).findFirst(); + if (found.isEmpty()) { + return Optional.empty(); + } + + return Optional.ofNullable(found.get().resolve("bin", "virt-viewer.exe")); + } catch (IOException e) { + ErrorEventFactory.fromThrowable(e).handle(); + return Optional.empty(); + } + } + + @Override + public void launch(SpiceLaunchConfig configuration) throws Exception { + var builder = createBuilder(configuration); + launch(builder); + } + } + + @Builder + @Jacksonized + @JsonTypeName("virtViewer") + public static class Linux extends VirtViewerSpiceClient implements ExternalApplicationType.LinuxApplication { + + @Override + public void launch(SpiceLaunchConfig configuration) throws Exception { + var builder = createBuilder(configuration); + launch(builder); + } + + @Override + public String getExecutable() { + return "remote-viewer"; + } + + @Override + public boolean detach() { + return true; + } + + @Override + public String getFlatpakId() { + return "org.virt_manager.virt-viewer"; + } + } + + @Builder + @Jacksonized + @JsonTypeName("virtViewer") + public static class MacOs extends VirtViewerSpiceClient implements ExternalApplicationType.PathApplication { + + @Override + public void launch(SpiceLaunchConfig configuration) throws Exception { + var builder = createBuilder(configuration); + launch(builder); + } + + @Override + public String getExecutable() { + return "remote-viewer"; + } + + @Override + public boolean detach() { + return true; + } + } +} 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 74acd722d..238584317 100644 --- a/app/src/main/java/io/xpipe/app/util/AppJacksonModule.java +++ b/app/src/main/java/io/xpipe/app/util/AppJacksonModule.java @@ -9,6 +9,7 @@ import io.xpipe.app.pwman.KeePassXcPasswordManager; import io.xpipe.app.pwman.PasswordManager; import io.xpipe.app.rdp.ExternalRdpClient; import io.xpipe.app.secret.*; +import io.xpipe.app.spice.ExternalSpiceClient; import io.xpipe.app.storage.*; import io.xpipe.app.terminal.ExternalTerminalType; import io.xpipe.app.terminal.TerminalMultiplexer; @@ -83,6 +84,7 @@ public class AppJacksonModule extends SimpleModule { context.registerSubtypes(TerminalPrompt.getClasses()); context.registerSubtypes(ExternalVncClient.getClasses()); context.registerSubtypes(ExternalRdpClient.getClasses()); + context.registerSubtypes(ExternalSpiceClient.getClasses()); context.registerSubtypes(SecretRetrievalStrategy.getClasses()); context.registerSubtypes(DataStorageGroupStrategy.getClasses()); diff --git a/app/src/main/java/io/xpipe/app/util/DocumentationLink.java b/app/src/main/java/io/xpipe/app/util/DocumentationLink.java index d3057e727..054c4035e 100644 --- a/app/src/main/java/io/xpipe/app/util/DocumentationLink.java +++ b/app/src/main/java/io/xpipe/app/util/DocumentationLink.java @@ -29,6 +29,8 @@ public enum DocumentationLink { KUBERNETES("guide/kubernetes"), DOCKER("guide/docker"), PROXMOX("guide/proxmox"), + PROXMOX_GUEST_AGENT("guide/proxmox#guest-agent"), + PROXMOX_NETWORKING("guide/proxmox#networking"), TAILSCALE("guide/tailscale"), TAILSCALE_AUTH("guide/tailscale#tailscale-authentication"), IDENTITY_APPLY("guide/ssh#applying-identities"), @@ -40,8 +42,11 @@ public enum DocumentationLink { PODMAN("guide/podman"), KVM("guide/kvm"), KVM_VNC("guide/kvm#vnc-access"), + KVM_GUEST_AGENT("guide/kvm#guest-agent"), + KVM_NETWORKING("guide/kvm#networking"), HCLOUD("guide/hcloud"), VMWARE("guide/vmware"), + VMWARE_NETWORKING("guide/vmware#networking"), AWS("guide/aws"), AWS_PROFILES("guide/aws#profiles"), AWS_EC2("guide/aws#ec2-instances"), @@ -78,6 +83,7 @@ public enum DocumentationLink { TUNNELS_REMOTE("guide/ssh#remote-tunnels"), TUNNELS_DYNAMIC("guide/ssh#dynamic-tunnels"), HYPERV("guide/hyperv"), + HYPERV_NETWORKING("guide/hyperv#custom-networking"), SSH_MACS("troubleshoot/ssh#no-matching-mac-found"), SSH_FEATURE_NOT_SUPPORTED("troubleshoot/ssh#requested-feature-not-supported"), SSH_JUMP_SERVERS("guide/ssh#gateways-and-jump-servers"), diff --git a/dist/changelog/20.4.md b/dist/changelog/20.4.md new file mode 100644 index 000000000..74923e47d --- /dev/null +++ b/dist/changelog/20.4.md @@ -0,0 +1 @@ +- Fix powershell runnable scripts with named parameters failing to execute diff --git a/ext/base/src/main/java/io/xpipe/ext/base/script/RunBackgroundScriptActionProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/script/RunBackgroundScriptActionProvider.java index 41dd18bec..ec9b96a59 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/script/RunBackgroundScriptActionProvider.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/script/RunBackgroundScriptActionProvider.java @@ -24,7 +24,7 @@ public class RunBackgroundScriptActionProvider implements ActionProvider { @Override public void executeImpl() throws Exception { var sc = ref.getStore().getOrStartSession(); - var script = scriptStore.getStore().assembleScriptChain(sc); + var script = scriptStore.getStore().assembleScriptChain(sc, false); if (script != null) { sc.command(script).execute(); } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/script/RunFileScriptMenuProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/script/RunFileScriptMenuProvider.java index 6d6b30f57..a1c4d82d3 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/script/RunFileScriptMenuProvider.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/script/RunFileScriptMenuProvider.java @@ -139,7 +139,7 @@ public class RunFileScriptMenuProvider implements BrowserMenuBranchProvider { @Override protected List createCommand(BrowserFileSystemTabModel model, List entries) { var sc = model.getFileSystem().getShell().orElseThrow(); - var content = ref.getStore().assembleScriptChain(sc); + var content = ref.getStore().assembleScriptChain(sc, true); var script = ScriptHelper.createExecScript(sc, content); var builder = CommandBuilder.of().add(sc.getShellDialect().runScriptCommand(sc, script.toString())); for (BrowserEntry entry : entries) { diff --git a/ext/base/src/main/java/io/xpipe/ext/base/script/RunHubBatchScriptActionProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/script/RunHubBatchScriptActionProvider.java index 1ec3926ac..76d563f87 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/script/RunHubBatchScriptActionProvider.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/script/RunHubBatchScriptActionProvider.java @@ -30,7 +30,7 @@ public class RunHubBatchScriptActionProvider implements ActionProvider { var map = new LinkedHashMap(); for (DataStoreEntryRef ref : refs) { var sc = ref.getStore().getOrStartSession(); - var script = scriptStore.getStore().assembleScriptChain(sc); + var script = scriptStore.getStore().assembleScriptChain(sc, false); var cmd = sc.command(script); map.put(ref.get().getName(), cmd); } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/script/RunHubScriptActionProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/script/RunHubScriptActionProvider.java index ea9c44559..dba01715c 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/script/RunHubScriptActionProvider.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/script/RunHubScriptActionProvider.java @@ -25,7 +25,7 @@ public class RunHubScriptActionProvider implements ActionProvider { @Override public void executeImpl() throws Exception { var sc = ref.getStore().getOrStartSession(); - var script = scriptStore.getStore().assembleScriptChain(sc); + var script = scriptStore.getStore().assembleScriptChain(sc, false); var cmd = sc.command(script); CommandDialog.runAndShow(cmd); } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/script/RunTerminalScriptActionProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/script/RunTerminalScriptActionProvider.java index b3c388f26..80bd02bf0 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/script/RunTerminalScriptActionProvider.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/script/RunTerminalScriptActionProvider.java @@ -25,7 +25,7 @@ public class RunTerminalScriptActionProvider implements ActionProvider { @Override public void executeImpl() throws Exception { var sc = ref.getStore().getOrStartSession(); - var script = scriptStore.getStore().assembleScriptChain(sc); + var script = scriptStore.getStore().assembleScriptChain(sc, false); TerminalLaunch.builder() .entry(ref.get()) .title(scriptStore.get().getName()) diff --git a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStoreSetup.java b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStoreSetup.java index 266272dc4..c23ecec02 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStoreSetup.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStoreSetup.java @@ -56,7 +56,7 @@ public class ScriptStoreSetup { new ShellTerminalInitCommand() { @Override public Optional terminalContent(ShellControl shellControl) { - return Optional.ofNullable(s.getStore().assembleScriptChain(shellControl)); + return Optional.ofNullable(s.getStore().assembleScriptChain(shellControl, false)); } @Override diff --git a/ext/base/src/main/java/io/xpipe/ext/base/script/SimpleScriptStore.java b/ext/base/src/main/java/io/xpipe/ext/base/script/SimpleScriptStore.java index d3893f98b..bd92ab28b 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/script/SimpleScriptStore.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/script/SimpleScriptStore.java @@ -49,7 +49,7 @@ public class SimpleScriptStore extends ScriptStore implements SelfReferentialSto return minimumDialect == null || minimumDialect.isCompatibleTo(dialect); } - private String assembleScript(ShellControl shellControl) { + private String assembleScript(ShellControl shellControl, boolean args) { if (isCompatible(shellControl)) { var shebang = getCommands().startsWith("#"); // Fix new lines and shebang @@ -60,18 +60,18 @@ public class SimpleScriptStore extends ScriptStore implements SelfReferentialSto shellControl.getShellDialect().getNewLine().getNewLineString())); var targetType = shellControl.getOriginalShellDialect(); var script = ScriptHelper.createExecScript(targetType, shellControl, fixedCommands); - return targetType.sourceScriptCommand(shellControl, script.toString()) + " " - + targetType.getCatchAllVariable(); + return targetType.sourceScriptCommand(shellControl, script.toString()) + (args ? " " + + targetType.getCatchAllVariable() : ""); } return null; } - public String assembleScriptChain(ShellControl shellControl) { + public String assembleScriptChain(ShellControl shellControl, boolean args) { var nl = shellControl.getShellDialect().getNewLine().getNewLineString(); var all = queryFlattenedScripts(); var r = all.stream() - .map(ref -> ref.getStore().assembleScript(shellControl)) + .map(ref -> ref.getStore().assembleScript(shellControl, args)) .filter(s -> s != null) .toList(); if (r.isEmpty()) { diff --git a/lang/strings/translations_da.properties b/lang/strings/translations_da.properties index 218d51413..52f5e20db 100644 --- a/lang/strings/translations_da.properties +++ b/lang/strings/translations_da.properties @@ -1129,7 +1129,7 @@ gitVaultOverwriteLocalContent=Vil du tilsidesætte dine lokale vault-ændringer? rdpSimple.displayName=Direkte RDP-forbindelse rdpSimple.displayDescription=Opret forbindelse til en vært via RDP rdpUsername=Brugernavn -rdpUsernameDescription=Til brugeren for at logge ind som +rdpUsernameDescription=Den bruger, der skal logges ind som. Kan indeholde et domænepræfiks addressDescription=Hvor skal man oprette forbindelse til rdpAdditionalOptions=Yderligere RDP-muligheder rdpAdditionalOptionsDescription=Rå RDP-muligheder, der skal inkluderes, formateret på samme måde som i .rdp-filer @@ -1858,3 +1858,5 @@ syncBackgroundCommand=Blokering af baggrundskommando terminalBackgroundCommand=Terminal-kommando testingConnection=Test af forbindelse ... openManagementConsole=Åben administrationskonsol +keeperUseMfa=Brug 2FA-godkendelsesapp +keeperUseMfaDescription=Aktivér dette, hvis din Keeper-konto kræver en 2FA TOTP for at få adgang til adgangskoder. diff --git a/lang/strings/translations_de.properties b/lang/strings/translations_de.properties index 56c148a4d..74377c577 100644 --- a/lang/strings/translations_de.properties +++ b/lang/strings/translations_de.properties @@ -1121,7 +1121,7 @@ gitVaultOverwriteLocalContent=Willst du die Änderungen in deinem lokalen Tresor rdpSimple.displayName=Direkte RDP-Verbindung rdpSimple.displayDescription=Verbindung zu einem Host über RDP rdpUsername=Benutzername -rdpUsernameDescription=An Benutzer, der sich anmelden soll als +rdpUsernameDescription=Der Benutzer, mit dem du dich anmeldest. Kann ein Domänenpräfix enthalten addressDescription=Wohin soll die Verbindung gehen? rdpAdditionalOptions=Zusätzliche RDP-Optionen rdpAdditionalOptionsDescription=Rohe RDP-Optionen, die genauso formatiert sind wie in .rdp-Dateien @@ -1275,7 +1275,7 @@ activate=Aktivieren validUntil=Gültig bis licenseActivated=Lizenz aktiviert restart=Neustart -lockVault=Schlosstresor +lockVault=Tresor schließen restartApp=XPipe neu starten free=Kostenlos upgradeInfo=Informationen zum Upgrade auf eine Lizenz findest du weiter unten. @@ -1849,7 +1849,9 @@ commandTypeAsyncBackground=Losgelöst im Hintergrund laufen lassen commandTypeSyncBackground=Im Hintergrund laufen und auf das Ende warten commandTypeTerminalBackground=Im Terminal öffnen asyncBackgroundCommand=Hintergrund-Befehl -syncBackgroundCommand=Hintergrundbefehl blockieren +syncBackgroundCommand=Blockierender Hintergrundbefehl terminalBackgroundCommand=Terminal-Befehl testingConnection=Verbindung testen ... openManagementConsole=Verwaltungskonsole öffnen +keeperUseMfa=2FA-Authentifikator-App verwenden +keeperUseMfaDescription=Aktiviere dies, wenn dein Keeper-Konto ein 2FA TOTP erfordert, um auf Passwörter zuzugreifen. diff --git a/lang/strings/translations_en.properties b/lang/strings/translations_en.properties index 3389df1ae..b8796ebb9 100644 --- a/lang/strings/translations_en.properties +++ b/lang/strings/translations_en.properties @@ -1137,7 +1137,7 @@ gitVaultOverwriteLocalContent=Do you want to override your local vault changes? rdpSimple.displayName=Direct RDP connection rdpSimple.displayDescription=Connect to a host via RDP rdpUsername=Username -rdpUsernameDescription=To user to log in as +rdpUsernameDescription=The user to log in as. Can include a domain prefix addressDescription=Where to connect to rdpAdditionalOptions=Additional RDP options rdpAdditionalOptionsDescription=Raw RDP options to include, formatted the same as in .rdp files @@ -1296,6 +1296,7 @@ activate=Activate validUntil=Valid until licenseActivated=License activated restart=Restart +#context: verb, to close lockVault=Lock vault restartApp=Restart XPipe #context: No payment required @@ -1511,6 +1512,8 @@ updateFailActionDescription=Check out the latest releases at GitHub onePasswordPlaceholder=Item name computeDirectorySizes=Compute directory sizes computeSize=Compute size +customSpiceCommand=Custom command +customSpiceCommandDescription=The custom command to execute to launch SPICE sessions. The placeholder string $FILE will be replaced by the quoted file path to the .vv file when called. vncClient=VNC client vncClientDescription=The VNC client to launch when opening VNC connections in XPipe.\n\nYou have the option to either use the integrated VNC client within XPipe or alternatively launch an external locally installed VNC client if you are looking for more customization. integratedXPipeVncClient=Integrated XPipe VNC client @@ -1883,9 +1886,12 @@ commandTypeAsyncBackground=Run detached in background commandTypeSyncBackground=Run in background and wait for finish commandTypeTerminalBackground=Open in terminal asyncBackgroundCommand=Background command +#context: A command that does not return until finished syncBackgroundCommand=Blocking background command terminalBackgroundCommand=Terminal command testingConnection=Testing connection ... openManagementConsole=Open management console openLxcTerminal=Open LXC terminal -openContainerConsole=Open serial console \ No newline at end of file +openContainerConsole=Open serial console +keeperUseMfa=Use 2FA authenticator app +keeperUseMfaDescription=Enable this if your Keeper account requires an 2FA TOTP to access passwords. diff --git a/lang/strings/translations_es.properties b/lang/strings/translations_es.properties index 317394553..799c5e934 100644 --- a/lang/strings/translations_es.properties +++ b/lang/strings/translations_es.properties @@ -1090,7 +1090,7 @@ gitVaultOverwriteLocalContent=¿Quieres anular los cambios de tu repositorio loc rdpSimple.displayName=Conexión directa RDP rdpSimple.displayDescription=Conectarse a un host mediante RDP rdpUsername=Nombre de usuario -rdpUsernameDescription=Para que el usuario inicie sesión como +rdpUsernameDescription=El usuario con el que iniciar sesión. Puede incluir un prefijo de dominio addressDescription=Dónde conectarse rdpAdditionalOptions=Opciones RDP adicionales rdpAdditionalOptionsDescription=Opciones RDP en bruto a incluir, con el mismo formato que en los archivos .rdp @@ -1817,3 +1817,5 @@ syncBackgroundCommand=Comando de fondo de bloqueo terminalBackgroundCommand=Comando de terminal testingConnection=Probar la conexión ... openManagementConsole=Consola de gestión abierta +keeperUseMfa=Utilizar la aplicación de autenticación 2FA +keeperUseMfaDescription=Actívala si tu cuenta de Keeper requiere un TOTP 2FA para acceder a las contraseñas. diff --git a/lang/strings/translations_fr.properties b/lang/strings/translations_fr.properties index dfdba92a9..d054aa0ec 100644 --- a/lang/strings/translations_fr.properties +++ b/lang/strings/translations_fr.properties @@ -1121,7 +1121,7 @@ gitVaultOverwriteLocalContent=Veux-tu remplacer les modifications de ton coffre- rdpSimple.displayName=Connexion directe RDP rdpSimple.displayDescription=Se connecter à un hôte via RDP rdpUsername=Nom d'utilisateur -rdpUsernameDescription=A l'utilisateur de se connecter en tant que +rdpUsernameDescription=L'utilisateur sous lequel se connecter. Peut inclure un préfixe de domaine addressDescription=Où se connecter rdpAdditionalOptions=Options RDP supplémentaires rdpAdditionalOptionsDescription=Options RDP brutes à inclure, formatées de la même manière que dans les fichiers .rdp @@ -1857,3 +1857,5 @@ syncBackgroundCommand=Commande de blocage de l'arrière-plan terminalBackgroundCommand=Commande de terminal testingConnection=Test de connexion ... openManagementConsole=Console de gestion ouverte +keeperUseMfa=Utilise l'application 2FA authenticator +keeperUseMfaDescription=Active cette option si ton compte Keeper nécessite un TOTP 2FA pour accéder aux mots de passe. diff --git a/lang/strings/translations_id.properties b/lang/strings/translations_id.properties index 0f493777e..a0a2c2830 100644 --- a/lang/strings/translations_id.properties +++ b/lang/strings/translations_id.properties @@ -1090,7 +1090,7 @@ gitVaultOverwriteLocalContent=Apakah Anda ingin menimpa perubahan brankas lokal rdpSimple.displayName=Koneksi RDP langsung rdpSimple.displayDescription=Menyambung ke host melalui RDP rdpUsername=Nama pengguna -rdpUsernameDescription=Kepada pengguna untuk masuk sebagai +rdpUsernameDescription=Pengguna yang akan masuk sebagai. Dapat menyertakan awalan domain addressDescription=Tempat untuk menyambung ke rdpAdditionalOptions=Opsi RDP tambahan rdpAdditionalOptionsDescription=Opsi RDP mentah untuk disertakan, diformat sama seperti pada file .rdp @@ -1817,3 +1817,5 @@ syncBackgroundCommand=Memblokir perintah latar belakang terminalBackgroundCommand=Perintah terminal testingConnection=Menguji koneksi ... openManagementConsole=Konsol manajemen terbuka +keeperUseMfa=Menggunakan aplikasi pengautentikasi 2FA +keeperUseMfaDescription=Aktifkan ini jika akun Keeper Anda memerlukan TOTP 2FA untuk mengakses kata sandi. diff --git a/lang/strings/translations_it.properties b/lang/strings/translations_it.properties index 63d809e1f..bb8a8bfbd 100644 --- a/lang/strings/translations_it.properties +++ b/lang/strings/translations_it.properties @@ -1090,7 +1090,7 @@ gitVaultOverwriteLocalContent=Vuoi sovrascrivere le modifiche del tuo vault loca rdpSimple.displayName=Connessione diretta RDP rdpSimple.displayDescription=Connettersi a un host tramite RDP rdpUsername=Nome utente -rdpUsernameDescription=All'utente di accedere come +rdpUsernameDescription=L'utente con cui accedere. Può includere un prefisso di dominio addressDescription=Dove connettersi rdpAdditionalOptions=Opzioni RDP aggiuntive rdpAdditionalOptionsDescription=Opzioni RDP grezze da includere, formattate come nei file .rdp @@ -1243,7 +1243,7 @@ activate=Attivare validUntil=Valido fino a licenseActivated=Licenza attivata restart=Riavvio -lockVault=Una cassaforte con serratura +lockVault=Cassaforte con serratura restartApp=Riavviare XPipe free=Gratuito upgradeInfo=Qui di seguito puoi trovare informazioni sull'aggiornamento della licenza. @@ -1813,7 +1813,9 @@ commandTypeAsyncBackground=Eseguire in background commandTypeSyncBackground=Eseguire in background e attendere il completamento commandTypeTerminalBackground=Aprire nel terminale asyncBackgroundCommand=Comando di sfondo -syncBackgroundCommand=Comando di blocco dello sfondo +syncBackgroundCommand=Comando di blocco in background terminalBackgroundCommand=Comando del terminale testingConnection=Verifica della connessione ... openManagementConsole=Console di gestione aperta +keeperUseMfa=Usa l'applicazione autenticatore 2FA +keeperUseMfaDescription=Abilita questa opzione se il tuo account Keeper richiede un 2FA TOTP per accedere alle password. diff --git a/lang/strings/translations_ja.properties b/lang/strings/translations_ja.properties index 12db967e1..9d002b655 100644 --- a/lang/strings/translations_ja.properties +++ b/lang/strings/translations_ja.properties @@ -1090,7 +1090,7 @@ gitVaultOverwriteLocalContent=ローカルの保管庫の変更を上書きす rdpSimple.displayName=直接RDP接続 rdpSimple.displayDescription=RDPでホストに接続する rdpUsername=ユーザー名 -rdpUsernameDescription=としてログインする +rdpUsernameDescription=ログインするユーザー。ドメインプレフィックスを含むことができる。 addressDescription=接続先 rdpAdditionalOptions=RDPの追加オプション rdpAdditionalOptionsDescription=.rdpファイルと同じ書式で、RDPの生オプションを含める。 @@ -1817,3 +1817,5 @@ syncBackgroundCommand=バックグラウンドコマンドをブロックする terminalBackgroundCommand=ターミナルコマンド testingConnection=接続をテストする openManagementConsole=オープン管理コンソール +keeperUseMfa=2FA認証アプリを使用する +keeperUseMfaDescription=Keeperアカウントがパスワードにアクセスするために2FA TOTPを必要とする場合、これを有効にする。 diff --git a/lang/strings/translations_ko.properties b/lang/strings/translations_ko.properties index 482614c8c..8453ff7a6 100644 --- a/lang/strings/translations_ko.properties +++ b/lang/strings/translations_ko.properties @@ -1119,7 +1119,7 @@ gitVaultOverwriteLocalContent=로컬 볼트 변경 내용을 덮어쓰시겠습 rdpSimple.displayName=직접 RDP 연결 rdpSimple.displayDescription=RDP를 통해 호스트에 연결 rdpUsername=사용자 이름 -rdpUsernameDescription=로그인할 사용자 +rdpUsernameDescription=로그인할 사용자입니다. 도메인 접두사를 포함할 수 있습니다 addressDescription=연결할 위치 rdpAdditionalOptions=추가 RDP 옵션 rdpAdditionalOptionsDescription=포함할 원시 RDP 옵션(.rdp 파일과 동일한 형식) @@ -1870,3 +1870,5 @@ syncBackgroundCommand=백그라운드 명령 차단 terminalBackgroundCommand=터미널 명령 testingConnection=연결 테스트 중 ... openManagementConsole=관리 콘솔 열기 +keeperUseMfa=2FA 인증 앱 사용 +keeperUseMfaDescription=Keeper 계정에서 비밀번호에 액세스하기 위해 2FA TOTP가 필요한 경우 이 옵션을 사용 설정합니다. diff --git a/lang/strings/translations_nl.properties b/lang/strings/translations_nl.properties index 380abab3c..1c3837f19 100644 --- a/lang/strings/translations_nl.properties +++ b/lang/strings/translations_nl.properties @@ -1090,7 +1090,7 @@ gitVaultOverwriteLocalContent=Wil je je lokale kluiswijzigingen overschrijven? D rdpSimple.displayName=Directe RDP-verbinding rdpSimple.displayDescription=Verbinding maken met een host via RDP rdpUsername=Gebruikersnaam -rdpUsernameDescription=Naar gebruiker om in te loggen als +rdpUsernameDescription=De gebruiker om als in te loggen. Kan een domeinvoorvoegsel bevatten addressDescription=Waar je verbinding mee moet maken rdpAdditionalOptions=Extra RDP opties rdpAdditionalOptionsDescription=Rauwe RDP-opties om op te nemen, in dezelfde opmaak als in .rdp-bestanden @@ -1813,7 +1813,9 @@ commandTypeAsyncBackground=Vrijstaand op de achtergrond uitvoeren commandTypeSyncBackground=Op de achtergrond draaien en wachten tot het klaar is commandTypeTerminalBackground=Openen in terminal asyncBackgroundCommand=Opdracht op de achtergrond -syncBackgroundCommand=Opdracht voor achtergrond blokkeren +syncBackgroundCommand=Achtergrondcommando blokkeren terminalBackgroundCommand=Terminal commando testingConnection=Verbinding testen ... openManagementConsole=Open beheerconsole +keeperUseMfa=Gebruik 2FA authenticator app +keeperUseMfaDescription=Schakel dit in als je Keeper-account een 2FA TOTP vereist om toegang te krijgen tot wachtwoorden. diff --git a/lang/strings/translations_pl.properties b/lang/strings/translations_pl.properties index 5882aa498..7ecb3c752 100644 --- a/lang/strings/translations_pl.properties +++ b/lang/strings/translations_pl.properties @@ -1090,7 +1090,7 @@ gitVaultOverwriteLocalContent=Czy chcesz zastąpić zmiany w lokalnym sejfie? Sp rdpSimple.displayName=Bezpośrednie połączenie RDP rdpSimple.displayDescription=Połącz się z hostem przez RDP rdpUsername=Nazwa użytkownika -rdpUsernameDescription=Aby użytkownik zalogował się jako +rdpUsernameDescription=Użytkownik, jako który chcesz się zalogować. Może zawierać prefiks domeny addressDescription=Gdzie się połączyć rdpAdditionalOptions=Dodatkowe opcje RDP rdpAdditionalOptionsDescription=Surowe opcje RDP do uwzględnienia, sformatowane tak samo jak w plikach .rdp @@ -1244,7 +1244,7 @@ activate=Aktywuj validUntil=Ważny do licenseActivated=Aktywowana licencja restart=Restart -lockVault=Skarbiec z zamkiem +lockVault=Zamknięty skarbiec restartApp=Uruchom ponownie XPipe free=Darmowy upgradeInfo=Poniżej znajdziesz informacje na temat aktualizacji do licencji. @@ -1814,7 +1814,9 @@ commandTypeAsyncBackground=Uruchom odłączony w tle commandTypeSyncBackground=Uruchom w tle i czekaj na zakończenie commandTypeTerminalBackground=Otwórz w terminalu asyncBackgroundCommand=Polecenie tła -syncBackgroundCommand=Polecenie blokowania w tle +syncBackgroundCommand=Polecenie blokujące w tle terminalBackgroundCommand=Polecenie terminala testingConnection=Testowanie połączenia ... openManagementConsole=Otwarta konsola zarządzania +keeperUseMfa=Użyj aplikacji uwierzytelniającej 2FA +keeperUseMfaDescription=Włącz tę opcję, jeśli Twoje konto Keeper wymaga TOTP 2FA, aby uzyskać dostęp do haseł. diff --git a/lang/strings/translations_pt.properties b/lang/strings/translations_pt.properties index 22d27f6c9..f5faac89c 100644 --- a/lang/strings/translations_pt.properties +++ b/lang/strings/translations_pt.properties @@ -1090,7 +1090,7 @@ gitVaultOverwriteLocalContent=Queres substituir as alterações do teu cofre loc rdpSimple.displayName=Ligação direta RDP rdpSimple.displayDescription=Liga-te a um anfitrião através de RDP rdpUsername=Nome de utilizador -rdpUsernameDescription=Para que o utilizador inicie sessão como +rdpUsernameDescription=O utilizador com o qual deves iniciar sessão. Pode incluir um prefixo de domínio addressDescription=Onde te deves ligar rdpAdditionalOptions=Opções adicionais de RDP rdpAdditionalOptionsDescription=Opções RDP brutas a incluir, formatadas da mesma forma que nos ficheiros .rdp @@ -1243,7 +1243,7 @@ activate=Ativar validUntil=Válido até licenseActivated=Licença activada restart=Reinicia -lockVault=Cofre com fechadura +lockVault=Fecha o cofre restartApp=Reinicia o XPipe free=Gratuito upgradeInfo=Podes encontrar informações sobre a atualização para uma licença abaixo. @@ -1813,7 +1813,9 @@ commandTypeAsyncBackground=Executa a desanexação em segundo plano commandTypeSyncBackground=Corre em segundo plano e espera pela conclusão commandTypeTerminalBackground=Abre no terminal asyncBackgroundCommand=Comando de fundo -syncBackgroundCommand=Bloqueio do comando de fundo +syncBackgroundCommand=Bloqueia o comando de fundo terminalBackgroundCommand=Comando de terminal testingConnection=Testar a ligação ... openManagementConsole=Abre a consola de gestão +keeperUseMfa=Utiliza a aplicação de autenticação 2FA +keeperUseMfaDescription=Ative essa opção se a sua conta do Keeper exigir um TOTP 2FA para acessar senhas. diff --git a/lang/strings/translations_ru.properties b/lang/strings/translations_ru.properties index 07cb8f300..100544406 100644 --- a/lang/strings/translations_ru.properties +++ b/lang/strings/translations_ru.properties @@ -1181,7 +1181,7 @@ gitVaultOverwriteLocalContent=Хочешь отменить изменения rdpSimple.displayName=Прямое RDP-соединение rdpSimple.displayDescription=Подключение к хосту через RDP rdpUsername=Имя пользователя -rdpUsernameDescription=Чтобы пользователь вошел в систему как +rdpUsernameDescription=Пользователь, под которым нужно войти в систему. Может включать префикс домена addressDescription=К чему подключиться rdpAdditionalOptions=Дополнительные опции RDP rdpAdditionalOptionsDescription=Необработанные опции RDP, которые нужно включить, в том же формате, что и в файлах .rdp @@ -1925,7 +1925,9 @@ commandTypeAsyncBackground=Запускать detached в фоновом реж commandTypeSyncBackground=Запустить в фоновом режиме и дождаться завершения commandTypeTerminalBackground=Открыть в терминале asyncBackgroundCommand=Фоновая команда -syncBackgroundCommand=Блокировка фоновой команды +syncBackgroundCommand=Блокирующая фоновая команда terminalBackgroundCommand=Команда терминала testingConnection=Тестирование соединения ... openManagementConsole=Открытая консоль управления +keeperUseMfa=Используйте приложение аутентификатора 2FA +keeperUseMfaDescription=Включи эту опцию, если твой аккаунт Keeper требует 2FA TOTP для доступа к паролям. diff --git a/lang/strings/translations_sv.properties b/lang/strings/translations_sv.properties index 051ab09d8..013c92479 100644 --- a/lang/strings/translations_sv.properties +++ b/lang/strings/translations_sv.properties @@ -1090,7 +1090,7 @@ gitVaultOverwriteLocalContent=Vill du åsidosätta dina lokala valvändringar? D rdpSimple.displayName=Direkt RDP-anslutning rdpSimple.displayDescription=Ansluta till en värd via RDP rdpUsername=Användarnamn -rdpUsernameDescription=Till användare att logga in som +rdpUsernameDescription=Användaren att logga in som. Kan innehålla ett domänprefix addressDescription=Var ska man ansluta till rdpAdditionalOptions=Ytterligare RDP-alternativ rdpAdditionalOptionsDescription=Raw RDP-alternativ att inkludera, formaterade på samma sätt som i .rdp-filer @@ -1817,3 +1817,5 @@ syncBackgroundCommand=Blockering av bakgrundskommando terminalBackgroundCommand=Kommando för terminal testingConnection=Testning av anslutning ... openManagementConsole=Öppen hanteringskonsol +keeperUseMfa=Använd 2FA-autentiseringsapp +keeperUseMfaDescription=Aktivera detta om ditt Keeper-konto kräver en 2FA TOTP för att komma åt lösenord. diff --git a/lang/strings/translations_tr.properties b/lang/strings/translations_tr.properties index a570580d6..7c85bd6ff 100644 --- a/lang/strings/translations_tr.properties +++ b/lang/strings/translations_tr.properties @@ -1090,7 +1090,7 @@ gitVaultOverwriteLocalContent=Yerel kasa değişikliklerinizi geçersiz kılmak rdpSimple.displayName=Doğrudan RDP bağlantısı rdpSimple.displayDescription=RDP aracılığıyla bir ana bilgisayara bağlanma rdpUsername=Kullanıcı Adı -rdpUsernameDescription=Kullanıcı olarak oturum açmak için +rdpUsernameDescription=Oturum açılacak kullanıcı. Bir alan adı öneki içerebilir addressDescription=Nereye bağlanmalı rdpAdditionalOptions=Ek RDP seçenekleri rdpAdditionalOptionsDescription=Dahil edilecek ham RDP seçenekleri, .rdp dosyalarında olduğu gibi biçimlendirilir @@ -1817,3 +1817,5 @@ syncBackgroundCommand=Arka plan komutunu engelleme terminalBackgroundCommand=Terminal komutu testingConnection=Test bağlantısı ... openManagementConsole=Açık yönetim konsolu +keeperUseMfa=2FA kimlik doğrulayıcı uygulamasını kullanın +keeperUseMfaDescription=Keeper hesabınız parolalara erişmek için bir 2FA TOTP gerektiriyorsa bunu etkinleştirin. diff --git a/lang/strings/translations_vi.properties b/lang/strings/translations_vi.properties index 4a49cad48..e44a5e9c8 100644 --- a/lang/strings/translations_vi.properties +++ b/lang/strings/translations_vi.properties @@ -1090,7 +1090,7 @@ gitVaultOverwriteLocalContent=Bạn có muốn ghi đè các thay đổi trong k rdpSimple.displayName=Kết nối RDP trực tiếp rdpSimple.displayDescription=Kết nối với máy chủ qua RDP rdpUsername=Tên người dùng -rdpUsernameDescription=Để người dùng đăng nhập với tư cách là +rdpUsernameDescription=Tên người dùng để đăng nhập. Có thể bao gồm tiền tố miền addressDescription=Nơi kết nối đến rdpAdditionalOptions=Các tùy chọn RDP bổ sung rdpAdditionalOptionsDescription=Các tùy chọn RDP thô cần bao gồm, định dạng giống như trong các tệp .rdp @@ -1243,7 +1243,7 @@ activate=Kích hoạt validUntil=Hiệu lực đến licenseActivated=Giấy phép đã được kích hoạt restart=Khởi động lại -lockVault=Kho lưu trữ an toàn +lockVault=Khoá két sắt restartApp=Khởi động lại XPipe free=Miễn phí upgradeInfo=Cậu có thể tìm thấy thông tin về việc nâng cấp lên giấy phép ở phần dưới đây. @@ -1813,7 +1813,9 @@ commandTypeAsyncBackground=Chạy độc lập trong nền commandTypeSyncBackground=Chạy ngầm và chờ hoàn tất commandTypeTerminalBackground=Mở trong terminal asyncBackgroundCommand=Lệnh nền -syncBackgroundCommand=Chặn lệnh nền +syncBackgroundCommand=Lệnh nền chặn terminalBackgroundCommand=Lệnh terminal testingConnection=Kiểm tra kết nối ... openManagementConsole=Mở bảng điều khiển quản lý +keeperUseMfa=Sử dụng ứng dụng xác thực hai yếu tố (2FA) +keeperUseMfaDescription=Bật tùy chọn này nếu tài khoản Keeper của cậu yêu cầu xác thực hai yếu tố (2FA) bằng mã TOTP để truy cập mật khẩu. diff --git a/lang/strings/translations_zh-Hans.properties b/lang/strings/translations_zh-Hans.properties index 94043c4c7..f0deef851 100644 --- a/lang/strings/translations_zh-Hans.properties +++ b/lang/strings/translations_zh-Hans.properties @@ -2444,3 +2444,5 @@ syncBackgroundCommand=阻止后台命令 terminalBackgroundCommand=终端命令 testingConnection=测试连接... openManagementConsole=开放式管理控制台 +keeperUseMfa=使用 2FA 验证器应用程序 +keeperUseMfaDescription=如果 Keeper 帐户要求使用 2FA TOTP 访问密码,请启用此选项。 diff --git a/lang/strings/translations_zh-Hant.properties b/lang/strings/translations_zh-Hant.properties index 44898a81f..54bcd4acc 100644 --- a/lang/strings/translations_zh-Hant.properties +++ b/lang/strings/translations_zh-Hant.properties @@ -1090,7 +1090,7 @@ gitVaultOverwriteLocalContent=要覆蓋您本地儲存庫的變更?這會將 rdpSimple.displayName=直接 RDP 連線 rdpSimple.displayDescription=透過 RDP 連接到主機 rdpUsername=使用者名稱 -rdpUsernameDescription=要使用者以 +rdpUsernameDescription=要登入的使用者。可包含網域前綴 addressDescription=連接至何處 rdpAdditionalOptions=其他 RDP 選項 rdpAdditionalOptionsDescription=要包含的原始 RDP 選項,格式與 .rdp 檔案相同 @@ -1243,7 +1243,7 @@ activate=啟動 validUntil=有效期至 licenseActivated=已啟用的授權 restart=重新啟動 -lockVault=鎖庫 +lockVault=鎖金庫 restartApp=重新啟動 XPipe free=免費 upgradeInfo=您可以在下方找到升級為授權的相關資訊。 @@ -1817,3 +1817,5 @@ syncBackgroundCommand=封鎖背景指令 terminalBackgroundCommand=終端命令 testingConnection=測試連接 ... openManagementConsole=開放式管理主控台 +keeperUseMfa=使用 2FA 認證器應用程式 +keeperUseMfaDescription=如果您的 Keeper 帳戶需要 2FA TOTP 才能存取密碼,請啟用此項。 diff --git a/version b/version index b7265b8be..4d4a5f105 100644 --- a/version +++ b/version @@ -1 +1 @@ -20.3-8 +20.4-1