From b20d04682b82e0ee3079797dcfef6f5a31dfa3af Mon Sep 17 00:00:00 2001 From: crschnick Date: Mon, 9 Feb 2026 11:20:24 +0000 Subject: [PATCH 01/42] Bump ospackage plugin --- .../main/java/io/xpipe/app/issue/SentryErrorHandler.java | 8 +++++--- dist/build.gradle | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/io/xpipe/app/issue/SentryErrorHandler.java b/app/src/main/java/io/xpipe/app/issue/SentryErrorHandler.java index 1eaeef369..ef6bc7331 100644 --- a/app/src/main/java/io/xpipe/app/issue/SentryErrorHandler.java +++ b/app/src/main/java/io/xpipe/app/issue/SentryErrorHandler.java @@ -74,9 +74,11 @@ public class SentryErrorHandler implements ErrorHandler { ObjectInputStream ois = new ObjectInputStream(bais); var copy = (Throwable) ois.readObject(); - var msgField = Throwable.class.getDeclaredField("detailMessage"); - msgField.setAccessible(true); - msgField.set(copy, null); + if (!(copy instanceof NullPointerException)) { + var msgField = Throwable.class.getDeclaredField("detailMessage"); + msgField.setAccessible(true); + msgField.set(copy, null); + } if (copy instanceof FileSystemException) { var fileField = FileSystemException.class.getDeclaredField("file"); diff --git a/dist/build.gradle b/dist/build.gradle index 1f7c58a86..c70933656 100644 --- a/dist/build.gradle +++ b/dist/build.gradle @@ -1,7 +1,7 @@ plugins { id 'org.beryx.jlink' version '3.2.1' - id("com.netflix.nebula.ospackage") version "12.1.1" + id("com.netflix.nebula.ospackage") version "12.2.0" id 'org.gradle.crypto.checksum' version '1.4.0' id 'signing' } From c2e17cbe590c75cc5d381bd75bec132c9ceaa30f Mon Sep 17 00:00:00 2001 From: crschnick Date: Mon, 9 Feb 2026 13:21:16 +0000 Subject: [PATCH 02/42] Use dekstop browse instead of open --- app/src/main/java/io/xpipe/app/util/DesktopHelper.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/io/xpipe/app/util/DesktopHelper.java b/app/src/main/java/io/xpipe/app/util/DesktopHelper.java index dacdcebe9..87a24bf66 100644 --- a/app/src/main/java/io/xpipe/app/util/DesktopHelper.java +++ b/app/src/main/java/io/xpipe/app/util/DesktopHelper.java @@ -61,7 +61,7 @@ public class DesktopHelper { return; } - if (!Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { + if (!Desktop.getDesktop().isSupported(Desktop.Action.OPEN)) { if (OsType.ofLocal() == OsType.LINUX) { LocalExec.readStdoutIfPossible("xdg-open", file.toString()); return; @@ -70,9 +70,9 @@ public class DesktopHelper { // This can be a blocking operation ThreadHelper.runAsync(() -> { - if (Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { + if (Desktop.getDesktop().isSupported(Desktop.Action.OPEN)) { try { - Desktop.getDesktop().browse(file.toFile().toURI()); + Desktop.getDesktop().open(file.toFile()); return; } catch (Exception e) { // Some basic linux systems have trouble with the API call From 2cfbe66409b0b8a8c269a54e4bbeddbe9a495638 Mon Sep 17 00:00:00 2001 From: crschnick Date: Mon, 9 Feb 2026 13:53:02 +0000 Subject: [PATCH 03/42] Include OOB message in report --- app/src/main/java/io/xpipe/app/issue/SentryErrorHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/io/xpipe/app/issue/SentryErrorHandler.java b/app/src/main/java/io/xpipe/app/issue/SentryErrorHandler.java index ef6bc7331..944f0cfd9 100644 --- a/app/src/main/java/io/xpipe/app/issue/SentryErrorHandler.java +++ b/app/src/main/java/io/xpipe/app/issue/SentryErrorHandler.java @@ -74,7 +74,7 @@ public class SentryErrorHandler implements ErrorHandler { ObjectInputStream ois = new ObjectInputStream(bais); var copy = (Throwable) ois.readObject(); - if (!(copy instanceof NullPointerException)) { + if (!(copy instanceof NullPointerException) && !(copy instanceof IndexOutOfBoundsException)) { var msgField = Throwable.class.getDeclaredField("detailMessage"); msgField.setAccessible(true); msgField.set(copy, null); From 6bd0c4f61ae0971c7d3bcfce6ccce33f3241a763 Mon Sep 17 00:00:00 2001 From: crschnick Date: Mon, 9 Feb 2026 13:53:18 +0000 Subject: [PATCH 04/42] Run all desktop open commands async --- app/src/main/java/io/xpipe/app/util/DesktopHelper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/io/xpipe/app/util/DesktopHelper.java b/app/src/main/java/io/xpipe/app/util/DesktopHelper.java index 87a24bf66..ed98ae824 100644 --- a/app/src/main/java/io/xpipe/app/util/DesktopHelper.java +++ b/app/src/main/java/io/xpipe/app/util/DesktopHelper.java @@ -63,7 +63,7 @@ public class DesktopHelper { if (!Desktop.getDesktop().isSupported(Desktop.Action.OPEN)) { if (OsType.ofLocal() == OsType.LINUX) { - LocalExec.readStdoutIfPossible("xdg-open", file.toString()); + LocalExec.executeAsync("xdg-open", file.toString()); return; } } @@ -99,7 +99,7 @@ public class DesktopHelper { // Windows does not support Action.BROWSE_FILE_DIR if (OsType.ofLocal() == OsType.WINDOWS) { // Explorer does not support single quotes, so use normal quotes - LocalExec.readStdoutIfPossible("explorer", "/select,", "\"" + file + "\""); + LocalExec.executeAsync("explorer", "/select,", "\"" + file + "\""); return; } From ff79accd8aa8e961a4ddb0b545115e3dc58379de Mon Sep 17 00:00:00 2001 From: crschnick Date: Tue, 10 Feb 2026 04:47:43 +0000 Subject: [PATCH 05/42] Register scale updates for dock --- .../io/xpipe/app/terminal/TerminalDockHubComp.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubComp.java b/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubComp.java index 1ccaa6451..22f602d7a 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubComp.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubComp.java @@ -3,6 +3,7 @@ package io.xpipe.app.terminal; import io.xpipe.app.comp.SimpleRegionBuilder; import io.xpipe.app.core.window.AppMainWindow; +import io.xpipe.app.util.GlobalTimer; import javafx.application.Platform; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; @@ -13,6 +14,7 @@ import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; import javafx.stage.WindowEvent; +import java.time.Duration; import java.util.concurrent.atomic.AtomicReference; public class TerminalDockHubComp extends SimpleRegionBuilder { @@ -46,6 +48,16 @@ public class TerminalDockHubComp extends SimpleRegionBuilder { update(stack); } }; + var scale = new ChangeListener() { + @Override + public void changed(ObservableValue observable, Number oldValue, Number newValue) { + GlobalTimer.delay(() -> { + Platform.runLater(() -> { + update(stack); + }); + }, Duration.ofMillis(500)); + } + }; var update = new ChangeListener() { @Override public void changed(ObservableValue observable, Number oldValue, Number newValue) { @@ -87,6 +99,7 @@ public class TerminalDockHubComp extends SimpleRegionBuilder { s.iconifiedProperty().removeListener(iconified); s.removeEventFilter(WindowEvent.WINDOW_SHOWN, show); s.removeEventFilter(WindowEvent.WINDOW_HIDING, hide); + s.outputScaleXProperty().addListener(scale); if (parent.get() != null) { parent.get().boundsInParentProperty().removeListener(bounds); parent.set(null); @@ -97,6 +110,7 @@ public class TerminalDockHubComp extends SimpleRegionBuilder { s.widthProperty().addListener(update); s.heightProperty().addListener(update); s.iconifiedProperty().addListener(iconified); + s.outputScaleXProperty().removeListener(scale); s.addEventFilter(WindowEvent.WINDOW_SHOWN, show); s.addEventFilter(WindowEvent.WINDOW_HIDING, hide); // As in practice this node is wrapped in another stack pane From 5d4b22ba44eebfdbd8ee64c999527cb604e52cd5 Mon Sep 17 00:00:00 2001 From: crschnick Date: Tue, 10 Feb 2026 04:51:49 +0000 Subject: [PATCH 06/42] Pause if repo updates fail --- .../java/io/xpipe/app/update/AppDistributionType.java | 8 +++++++- app/src/main/java/io/xpipe/app/update/AppInstaller.java | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/io/xpipe/app/update/AppDistributionType.java b/app/src/main/java/io/xpipe/app/update/AppDistributionType.java index 9f834415d..d301a6b95 100644 --- a/app/src/main/java/io/xpipe/app/update/AppDistributionType.java +++ b/app/src/main/java/io/xpipe/app/update/AppDistributionType.java @@ -28,7 +28,10 @@ public enum AppDistributionType implements Translatable { HOMEBREW("homebrew", true, () -> { var pkg = AppNames.ofCurrent().getKebapName(); return new CommandUpdater( - ShellScript.lines("brew upgrade --cask xpipe-io/tap/" + pkg, AppRestart.getTerminalRestartCommand())); + ShellScript.lines( + "brew upgrade --cask xpipe-io/tap/" + pkg, + "if [ \"$?\" != 0 ]; then echo \"Update failed ...\"; read key; fi", + AppRestart.getTerminalRestartCommand())); }), APT_REPO("apt", true, () -> { var pkg = AppNames.ofCurrent().getKebapName(); @@ -36,6 +39,7 @@ public enum AppDistributionType implements Translatable { "echo \"+ sudo apt update && sudo apt install -y " + pkg + "\"", "sudo apt update", "sudo apt install -y " + pkg, + "if [ \"$?\" != 0 ]; then echo \"Update failed ...\"; read key; fi", AppRestart.getTerminalRestartCommand())); }), RPM_REPO("rpm", true, () -> { @@ -43,6 +47,7 @@ public enum AppDistributionType implements Translatable { return new CommandUpdater(ShellScript.lines( "echo \"+ sudo yum upgrade " + pkg + " --refresh -y\"", "sudo yum upgrade " + pkg + " --refresh -y", + "if [ \"$?\" != 0 ]; then echo \"Update failed ...\"; read key; fi", AppRestart.getTerminalRestartCommand())); }), AUR("aur", true, () -> { @@ -50,6 +55,7 @@ public enum AppDistributionType implements Translatable { return new CommandUpdater(ShellScript.lines( "echo \"+ git clone https://aur.archlinux.org/" + pkg + " . && makepkg -si\"", "cd $(mktemp -d) && git clone https://aur.archlinux.org/" + pkg + " . && makepkg -si --noconfirm", + "if [ \"$?\" != 0 ]; then echo \"Update failed ...\"; read key; fi", AppRestart.getTerminalRestartCommand())); }), WEBTOP("webtop", true, () -> new WebtopUpdater()), diff --git a/app/src/main/java/io/xpipe/app/update/AppInstaller.java b/app/src/main/java/io/xpipe/app/update/AppInstaller.java index 0009c236d..6b769d376 100644 --- a/app/src/main/java/io/xpipe/app/update/AppInstaller.java +++ b/app/src/main/java/io/xpipe/app/update/AppInstaller.java @@ -229,7 +229,7 @@ public class AppInstaller { runinstaller if [ "$?" != 0 ]; then echo "Update failed ..." - read -rs -k 1 key + read key fi """, file, file, AppRestart.getTerminalRestartCommand())); AppOperationMode.executeAfterShutdown(() -> { From af5ef8c8e00bd9dbcd88d509a97b26c5dee15332 Mon Sep 17 00:00:00 2001 From: crschnick Date: Tue, 10 Feb 2026 12:19:21 +0000 Subject: [PATCH 07/42] Only disable avx on x64 --- CONTRIBUTING.md | 4 ++-- .../browser/file/BrowserFileSystemTabModel.java | 15 ++++++++++++--- .../java/io/xpipe/app/ext/NetworkTunnelStore.java | 2 +- .../java/io/xpipe/app/prefs/SyncCategory.java | 2 +- .../java/io/xpipe/app/storage/DataStorage.java | 4 ++++ .../io/xpipe/app/storage/StandardStorage.java | 4 ++++ .../app/terminal/TerminalDockHubManager.java | 7 +++++++ dist/changelog/21.1.1.md | 1 + dist/changelog/21.1.md | 9 +++++++++ dist/licenses/graalvm.properties | 2 +- version | 2 +- 11 files changed, 43 insertions(+), 9 deletions(-) create mode 100644 dist/changelog/21.1.1.md create mode 100644 dist/changelog/21.1.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d97236eab..5b24c4759 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,8 +20,8 @@ If you are on Linux or macOS, you can easily accomplish that by using [SDKMAN](h ```bash curl -s "https://get.sdkman.io" | bash . "$HOME/.sdkman/bin/sdkman-init.sh" -sdk install java 25.0.2-graalce -sdk default java 25.0.2-graalce +sdk install java 25.0.1-graalce +sdk default java 25.0.1-graalce ``` On Windows, you have to manually install a JDK, e.g. from [Adoptium](https://adoptium.net/temurin/releases/?version=25). diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemTabModel.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemTabModel.java index 7799005b8..f3818bc7c 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemTabModel.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemTabModel.java @@ -413,10 +413,19 @@ public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab { + BooleanScope.executeExclusive(busy, () -> { + try (var ignored = sub.start()) { + openTerminalSync(name, directory, sub, true); + } + }); + }); } else { - var cc = fileSystem.getShell().get().command(adjustedPath); - openTerminalAsync(name, directory, cc, true); + ThreadHelper.runFailableAsync(() -> { + BooleanScope.executeExclusive(busy, () -> { + openTerminalSync(name, directory, fileSystem.getShell().get().command(adjustedPath), true); + }); + }); } }); return Optional.ofNullable(cps); diff --git a/app/src/main/java/io/xpipe/app/ext/NetworkTunnelStore.java b/app/src/main/java/io/xpipe/app/ext/NetworkTunnelStore.java index 3a59ef799..8c555af3a 100644 --- a/app/src/main/java/io/xpipe/app/ext/NetworkTunnelStore.java +++ b/app/src/main/java/io/xpipe/app/ext/NetworkTunnelStore.java @@ -27,7 +27,7 @@ public interface NetworkTunnelStore extends DataStore, SelfReferentialStore { } default HostAddress getTunnelHostName() { - return null; + return HostAddress.empty(); } default Optional> getUnsupportedParent() { diff --git a/app/src/main/java/io/xpipe/app/prefs/SyncCategory.java b/app/src/main/java/io/xpipe/app/prefs/SyncCategory.java index 775b4b229..a0b2524d2 100644 --- a/app/src/main/java/io/xpipe/app/prefs/SyncCategory.java +++ b/app/src/main/java/io/xpipe/app/prefs/SyncCategory.java @@ -82,7 +82,7 @@ public class SyncCategory extends AppPrefsCategory { .sub(prefs.getCustomOptions("gitVaultIdentityStrategy")) .pref(prefs.syncMode) .addComp( - ChoiceComp.ofTranslatable(prefs.syncMode, Arrays.asList(SyncMode.values()), false), + ChoiceComp.ofTranslatable(prefs.syncMode, Arrays.asList(SyncMode.values()), false).maxWidth(getCompWidth()), prefs.syncMode) .addComp(createManualControls()) .hide(prefs.syncMode.isNotEqualTo(SyncMode.MANUAL).or(prefs.enableGitStorage.not())) diff --git a/app/src/main/java/io/xpipe/app/storage/DataStorage.java b/app/src/main/java/io/xpipe/app/storage/DataStorage.java index 5d0561a88..29371cd46 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStorage.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStorage.java @@ -271,6 +271,10 @@ public abstract class DataStorage { return dir.resolve("data"); } + public Path getIconsDir() { + return dir.resolve("icons"); + } + protected Path getCategoriesDir() { return dir.resolve("categories"); } diff --git a/app/src/main/java/io/xpipe/app/storage/StandardStorage.java b/app/src/main/java/io/xpipe/app/storage/StandardStorage.java index cd75fc584..32f8ccff4 100644 --- a/app/src/main/java/io/xpipe/app/storage/StandardStorage.java +++ b/app/src/main/java/io/xpipe/app/storage/StandardStorage.java @@ -91,11 +91,13 @@ public class StandardStorage extends DataStorage { var storesDir = getStoresDir(); var categoriesDir = getCategoriesDir(); var dataDir = getDataDir(); + var iconsDir = getIconsDir(); try { FileUtils.forceMkdir(storesDir.toFile()); FileUtils.forceMkdir(categoriesDir.toFile()); FileUtils.forceMkdir(dataDir.toFile()); + FileUtils.forceMkdir(iconsDir.toFile()); } catch (Exception e) { ErrorEventFactory.fromThrowable("Unable to create vault directory", e) .terminal(true) @@ -400,6 +402,8 @@ public class StandardStorage extends DataStorage { try { FileUtils.forceMkdir(getStoresDir().toFile()); FileUtils.forceMkdir(getCategoriesDir().toFile()); + FileUtils.forceMkdir(getDataDir().toFile()); + FileUtils.forceMkdir(getIconsDir().toFile()); } catch (Exception e) { ErrorEventFactory.fromThrowable(e) .description("Unable to create storage directory " + getStoresDir()) diff --git a/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubManager.java b/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubManager.java index e58b8ec51..fac77c18f 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubManager.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubManager.java @@ -18,6 +18,7 @@ import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.collections.ListChangeListener; +import javafx.stage.Screen; import lombok.Getter; import org.kordamp.ikonli.javafx.FontIcon; @@ -58,6 +59,12 @@ public class TerminalDockHubManager { return false; } + var primaryScreen = Screen.getPrimary(); + var uniformScale = Screen.getScreens().stream().allMatch(screen -> screen.getOutputScaleX() == primaryScreen.getOutputScaleX()); + if (!uniformScale) { + return false; + } + return true; } diff --git a/dist/changelog/21.1.1.md b/dist/changelog/21.1.1.md new file mode 100644 index 000000000..cee857a9f --- /dev/null +++ b/dist/changelog/21.1.1.md @@ -0,0 +1 @@ +- Fix Warp terminal hyperlinks being opened in browser as well diff --git a/dist/changelog/21.1.md b/dist/changelog/21.1.md new file mode 100644 index 000000000..08aa09afa --- /dev/null +++ b/dist/changelog/21.1.md @@ -0,0 +1,9 @@ +- Fix various hyperlinks and files being opened in web browser instead of associated application +- Fix NullPointer when opening split terminal +- Fix continuous NullPointer when git vault was enabled but no remote repository was specified +- Fix OutOfBounds issue for the cisco switch version detection +- Fix SSH config entries always using default id_rsa key if none was specified +- Fix SSH config entries not preferring custom identity set in XPipe +- Fix NullPointers with SSH configs on remote systems +- Fix local custom icons directory not being created by default +- Don't enable terminal docking with mixed display scale screens due to wrong translated window coordinates diff --git a/dist/licenses/graalvm.properties b/dist/licenses/graalvm.properties index ae86cefde..4aec72c40 100644 --- a/dist/licenses/graalvm.properties +++ b/dist/licenses/graalvm.properties @@ -1,4 +1,4 @@ name=GraalVM Community -version=25.0.2 +version=25.0.1 license=GPL2 with the Classpath Exception link=https://www.graalvm.org/ \ No newline at end of file diff --git a/version b/version index 5f39e9144..2d978e312 100644 --- a/version +++ b/version @@ -1 +1 @@ -21.0 +21.1.1 From f14fbb5c0b2a544a108a123064097c705b62f8d3 Mon Sep 17 00:00:00 2001 From: crschnick Date: Tue, 10 Feb 2026 12:34:26 +0000 Subject: [PATCH 08/42] Fix jdk 25.0.2 update URL handling --- CONTRIBUTING.md | 4 +-- .../xpipe/app/terminal/WarpTerminalType.java | 8 +++--- .../java/io/xpipe/app/util/DesktopHelper.java | 26 ++++++++++++++++++- .../java/io/xpipe/app/util/Hyperlinks.java | 2 +- dist/licenses/graalvm.properties | 2 +- 5 files changed, 33 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5b24c4759..d97236eab 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,8 +20,8 @@ If you are on Linux or macOS, you can easily accomplish that by using [SDKMAN](h ```bash curl -s "https://get.sdkman.io" | bash . "$HOME/.sdkman/bin/sdkman-init.sh" -sdk install java 25.0.1-graalce -sdk default java 25.0.1-graalce +sdk install java 25.0.2-graalce +sdk default java 25.0.2-graalce ``` On Windows, you have to manually install a JDK, e.g. from [Adoptium](https://adoptium.net/temurin/releases/?version=25). diff --git a/app/src/main/java/io/xpipe/app/terminal/WarpTerminalType.java b/app/src/main/java/io/xpipe/app/terminal/WarpTerminalType.java index 052674398..9f21b1527 100644 --- a/app/src/main/java/io/xpipe/app/terminal/WarpTerminalType.java +++ b/app/src/main/java/io/xpipe/app/terminal/WarpTerminalType.java @@ -89,9 +89,9 @@ public interface WarpTerminalType extends ExternalTerminalType, TrackableTermina var scriptArg = URLEncoder.encode(movedScriptFile.toString(), StandardCharsets.UTF_8); if (!configuration.isPreferTabs()) { - DesktopHelper.openUrl("warp://action/new_window?path=" + scriptArg); + DesktopHelper.openAssociatedApplication("warp://action/new_window?path=" + scriptArg); } else { - DesktopHelper.openUrl("warp://action/new_tab?path=" + scriptArg); + DesktopHelper.openAssociatedApplication("warp://action/new_tab?path=" + scriptArg); } } } @@ -125,9 +125,9 @@ public interface WarpTerminalType extends ExternalTerminalType, TrackableTermina public void launch(TerminalLaunchConfiguration configuration) { var pane = configuration.single(); if (!configuration.isPreferTabs()) { - DesktopHelper.openUrl("warp://action/new_window?path=" + pane.getScriptFile()); + DesktopHelper.openAssociatedApplication("warp://action/new_window?path=" + pane.getScriptFile()); } else { - DesktopHelper.openUrl("warp://action/new_tab?path=" + pane.getScriptFile()); + DesktopHelper.openAssociatedApplication("warp://action/new_tab?path=" + pane.getScriptFile()); } } } diff --git a/app/src/main/java/io/xpipe/app/util/DesktopHelper.java b/app/src/main/java/io/xpipe/app/util/DesktopHelper.java index ed98ae824..1bb181303 100644 --- a/app/src/main/java/io/xpipe/app/util/DesktopHelper.java +++ b/app/src/main/java/io/xpipe/app/util/DesktopHelper.java @@ -16,7 +16,7 @@ import java.util.List; public class DesktopHelper { - public static void openUrl(String uri) { + public static void openBrowser(String uri) { if (uri == null) { return; } @@ -56,6 +56,30 @@ public class DesktopHelper { }); } + public static void openAssociatedApplication(String uri) { + if (uri == null) { + return; + } + + URI parsed; + try { + parsed = URI.create(uri); + } catch (IllegalArgumentException e) { + ErrorEventFactory.fromThrowable("Invalid URI: " + uri, e.getCause() != null ? e.getCause() : e) + .handle(); + return; + } + + // Windows URL open always uses browser + if (OsType.ofLocal() == OsType.WINDOWS) { + LocalExec.executeAsync("rundll32", "url.dll,FileProtocolHandler", parsed.toString()); + return; + } + + // Other OS use associated app + openBrowser(uri); + } + public static void browseFile(Path file) { if (file == null || !Files.exists(file)) { return; diff --git a/app/src/main/java/io/xpipe/app/util/Hyperlinks.java b/app/src/main/java/io/xpipe/app/util/Hyperlinks.java index 0de806ecc..649f0a30a 100644 --- a/app/src/main/java/io/xpipe/app/util/Hyperlinks.java +++ b/app/src/main/java/io/xpipe/app/util/Hyperlinks.java @@ -11,6 +11,6 @@ public class Hyperlinks { public static final String GITHUB_WEBTOP = "https://github.com/xpipe-io/xpipe-webtop"; public static void open(String uri) { - DesktopHelper.openUrl(uri); + DesktopHelper.openBrowser(uri); } } diff --git a/dist/licenses/graalvm.properties b/dist/licenses/graalvm.properties index 4aec72c40..ae86cefde 100644 --- a/dist/licenses/graalvm.properties +++ b/dist/licenses/graalvm.properties @@ -1,4 +1,4 @@ name=GraalVM Community -version=25.0.1 +version=25.0.2 license=GPL2 with the Classpath Exception link=https://www.graalvm.org/ \ No newline at end of file From 0cbe0f4cc85b07db9665293ed4311881ef3ac2ce Mon Sep 17 00:00:00 2001 From: crschnick Date: Wed, 11 Feb 2026 06:14:20 +0000 Subject: [PATCH 09/42] Fix terminal dock for mixed display scales --- .../app/terminal/TerminalDockBrowserComp.java | 15 +++++++++++---- .../xpipe/app/terminal/TerminalDockHubComp.java | 11 +++++++++-- .../app/terminal/TerminalDockHubManager.java | 6 ------ .../io/xpipe/app/terminal/TerminalDockView.java | 15 +++++++++++++++ 4 files changed, 35 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/io/xpipe/app/terminal/TerminalDockBrowserComp.java b/app/src/main/java/io/xpipe/app/terminal/TerminalDockBrowserComp.java index dce7f986a..43993f316 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalDockBrowserComp.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalDockBrowserComp.java @@ -5,6 +5,7 @@ import io.xpipe.app.comp.base.LoadingIconComp; import io.xpipe.app.core.AppFontSizes; import io.xpipe.app.core.AppI18n; import io.xpipe.app.core.window.AppMainWindow; +import io.xpipe.app.platform.NativeWinWindowControl; import io.xpipe.app.platform.PlatformThread; import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.util.GlobalTimer; @@ -157,10 +158,16 @@ public class TerminalDockBrowserComp extends SimpleRegionBuilder { var p = region.getPadding(); var sx = region.getScene().getWindow().getOutputScaleX(); var sy = region.getScene().getWindow().getOutputScaleY(); + + var scene = region.getScene(); + var windowRect = NativeWinWindowControl.MAIN_WINDOW.getBounds(); + var x = windowRect.getX() + ((p.getLeft() + scene.getX()) * sx); + var y = windowRect.getY() + ((p.getTop() + scene.getY()) * sy); + model.resizeView( - (int) Math.ceil(bounds.getMinX() * sx + p.getLeft()), - (int) Math.ceil(bounds.getMinY() * sy + p.getTop()), - (int) Math.floor(bounds.getWidth() * sx - p.getRight() - p.getLeft()), - (int) Math.floor(bounds.getHeight() * sy - p.getBottom() - p.getTop())); + (int) Math.round(x), + (int) Math.round(y), + (int) Math.round(bounds.getWidth() * sx - p.getRight() - p.getLeft()), + (int) Math.round(bounds.getHeight() * sy - p.getBottom() - p.getTop())); } } diff --git a/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubComp.java b/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubComp.java index 22f602d7a..5a28441fb 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubComp.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubComp.java @@ -3,6 +3,7 @@ package io.xpipe.app.terminal; import io.xpipe.app.comp.SimpleRegionBuilder; import io.xpipe.app.core.window.AppMainWindow; +import io.xpipe.app.platform.NativeWinWindowControl; import io.xpipe.app.util.GlobalTimer; import javafx.application.Platform; import javafx.beans.value.ChangeListener; @@ -131,9 +132,15 @@ public class TerminalDockHubComp extends SimpleRegionBuilder { var p = region.getPadding(); var sx = region.getScene().getWindow().getOutputScaleX(); var sy = region.getScene().getWindow().getOutputScaleY(); + + var scene = region.getScene(); + var windowRect = NativeWinWindowControl.MAIN_WINDOW.getBounds(); + var x = windowRect.getX() + ((p.getLeft() + scene.getX()) * sx); + var y = windowRect.getY() + ((p.getTop() + scene.getY()) * sy); + model.resizeView( - (int) Math.round(bounds.getMinX() * sx + p.getLeft()), - (int) Math.round(bounds.getMinY() * sy + p.getTop()), + (int) Math.round(x), + (int) Math.round(y), (int) Math.round(bounds.getWidth() * sx - p.getRight() - p.getLeft()), (int) Math.round(bounds.getHeight() * sy - p.getBottom() - p.getTop())); } diff --git a/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubManager.java b/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubManager.java index fac77c18f..03854799e 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubManager.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubManager.java @@ -59,12 +59,6 @@ public class TerminalDockHubManager { return false; } - var primaryScreen = Screen.getPrimary(); - var uniformScale = Screen.getScreens().stream().allMatch(screen -> screen.getOutputScaleX() == primaryScreen.getOutputScaleX()); - if (!uniformScale) { - return false; - } - return true; } diff --git a/app/src/main/java/io/xpipe/app/terminal/TerminalDockView.java b/app/src/main/java/io/xpipe/app/terminal/TerminalDockView.java index 27551dce0..e2094cdb4 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalDockView.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalDockView.java @@ -47,7 +47,22 @@ public class TerminalDockView { public synchronized void updateCustomBounds() { terminalInstances.forEach(terminal -> { + var wasCustom = terminal.isCustomBounds(); terminal.updateBoundsState(); + + if (wasCustom && viewBounds != null) { + var currentBounds = terminal.getLastBounds(); + var targetBounds = windowBoundsFunction.apply(viewBounds); + var sum = Math.abs(targetBounds.getX() - currentBounds.getX()) + + Math.abs(targetBounds.getY() - currentBounds.getY()) + + Math.abs(targetBounds.getW() - currentBounds.getW()) + + Math.abs(targetBounds.getH() - currentBounds.getH()); + if (sum < 10) { + trackTerminal(terminal, true); + return; + } + } + if (terminal.isCustomBounds()) { terminal.disown(); } From 18a014dbaf814445cd3fb095d4c6b48f19f2f234 Mon Sep 17 00:00:00 2001 From: crschnick Date: Wed, 11 Feb 2026 06:14:39 +0000 Subject: [PATCH 10/42] Fix transfer box layout --- .../java/io/xpipe/app/browser/file/BrowserTransferComp.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserTransferComp.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserTransferComp.java index c7b84e5e5..3676bf9af 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserTransferComp.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserTransferComp.java @@ -73,7 +73,7 @@ public class BrowserTransferComp extends SimpleRegionBuilder { }, sourceItem.get().getProgress()); } - }); + }).vgrow(); var dragNotice = new LabelComp(AppI18n.observable("dragLocalFiles")) .apply(struc -> struc.setGraphic(new FontIcon("mdi2h-hand-back-left-outline"))) .apply(struc -> struc.setWrapText(true)) From b3d58fcc8240f737a9b7c6bdc92f93cabb72e766 Mon Sep 17 00:00:00 2001 From: crschnick Date: Wed, 11 Feb 2026 06:14:46 +0000 Subject: [PATCH 11/42] Rework custom javafx dep script --- build.gradle | 4 ++-- dist/jpackage.gradle | 2 +- gradle/gradle_scripts/javafx.gradle | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index d1621c264..cba465978 100644 --- a/build.gradle +++ b/build.gradle @@ -301,8 +301,8 @@ project.ext { useBundledJavaFx = fullVersion bundledJdkJavaFx = ModuleFinder.ofSystem().find("javafx.base").isPresent() // Define a custom JavaFX SDK location - // customJavaFxLibsPath = file("C:\\Projects\\jfx\\build\\sdk\\lib") - // customJavaFxJmodsPath = file("C:\\Projects\\jfx\\build\\jmods") + customJavaFxLibsPath = null; // file("C:\\Projects\\jfx\\build\\sdk\\lib") + customJavaFxJmodsPath = null; // file("C:\\Projects\\jfx\\build\\jmods") // Other deeplApiKey = findProperty('DEEPL_API_KEY') != null ? findProperty('DEEPL_API_KEY') : "" diff --git a/dist/jpackage.gradle b/dist/jpackage.gradle index e754d3eac..9700ab2c4 100644 --- a/dist/jpackage.gradle +++ b/dist/jpackage.gradle @@ -47,7 +47,7 @@ jlink { options.addAll('--strip-native-debug-symbols', 'exclude-debuginfo-files') } - if (hasProperty("customJavaFxJmodsPath")) { + if (customJavaFxJmodsPath != null) { addExtraModulePath(customJavaFxJmodsPath.toString()) } else if (useBundledJavaFx && !bundledJdkJavaFx) { addExtraModulePath(layout.projectDirectory.dir("javafx/${platformName}/${arch}").toString()) diff --git a/gradle/gradle_scripts/javafx.gradle b/gradle/gradle_scripts/javafx.gradle index d40e04ce0..1848ef20d 100644 --- a/gradle/gradle_scripts/javafx.gradle +++ b/gradle/gradle_scripts/javafx.gradle @@ -18,7 +18,7 @@ configurations { javafx } -if (hasProperty("customJavaFxLibsPath")) { +if (customJavaFxLibsPath != null) { repositories { flatDir { dirs customJavaFxLibsPath From d729bcb25e015c1eb637cac271f7df00e47ffc48 Mon Sep 17 00:00:00 2001 From: crschnick Date: Wed, 11 Feb 2026 06:15:08 +0000 Subject: [PATCH 12/42] Fix NPE --- .../ext/base/script/RunTerminalScriptActionProvider.java | 4 ++++ 1 file changed, 4 insertions(+) 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 794055cef..12312908d 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 @@ -26,6 +26,10 @@ public class RunTerminalScriptActionProvider implements ActionProvider { public void executeImpl() throws Exception { var sc = ref.getStore().getOrStartSession(); var script = scriptStore.getStore().assembleScriptChain(sc, false); + if (script == null) { + return; + } + TerminalLaunch.builder() .entry(ref.get()) .title(scriptStore.get().getName()) From bf7a110c0bc133de4993ef935700c769d1d153c9 Mon Sep 17 00:00:00 2001 From: crschnick Date: Wed, 11 Feb 2026 06:17:47 +0000 Subject: [PATCH 13/42] [stage] --- dist/changelog/21.2.md | 5 +++++ version | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 dist/changelog/21.2.md diff --git a/dist/changelog/21.2.md b/dist/changelog/21.2.md new file mode 100644 index 000000000..8cca86f48 --- /dev/null +++ b/dist/changelog/21.2.md @@ -0,0 +1,5 @@ +- Fix terminal docking not working properly on multiple displays with different scale factor +- Fix terminal docking not automatically reattaching windows that were moved back to the dock +- Fix automatic updater not pausing terminal session when update failed +- Automatically delete corrupted git index file when detected +- diff --git a/version b/version index 2d978e312..931463eee 100644 --- a/version +++ b/version @@ -1 +1 @@ -21.1.1 +21.2-1 From 184cfb596f6db88ab8def67185ad134de32a6560 Mon Sep 17 00:00:00 2001 From: crschnick Date: Thu, 12 Feb 2026 04:58:35 +0000 Subject: [PATCH 14/42] Fix links --- app/src/main/java/io/xpipe/app/util/DocumentationLink.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 ccd857c59..564dfa8d3 100644 --- a/app/src/main/java/io/xpipe/app/util/DocumentationLink.java +++ b/app/src/main/java/io/xpipe/app/util/DocumentationLink.java @@ -13,8 +13,8 @@ public enum DocumentationLink { LICENSE_ACTIVATION("troubleshoot/license-activation"), TLS_DECRYPTION("troubleshoot/license-activation#tls-decryption"), UPDATE_FAIL("troubleshoot/update-fail"), - PRIVACY("legal/privacy"), - EULA("legal/eula"), + PRIVACY("legal/privacy-policy"), + EULA("legal/end-user-license-agreement"), WEBTOP_UPDATE("guide/webtop#updating"), WEBTOP_TUN("guide/webtop#networking-tailscale-and-netbird"), SYNC("guide/sync"), From 6e802def4cbd075d43f41f0d31b23ab30908cc7e Mon Sep 17 00:00:00 2001 From: crschnick Date: Thu, 12 Feb 2026 04:58:59 +0000 Subject: [PATCH 15/42] Docking fixes --- .../file/BrowserFileSystemTabModel.java | 11 +++++++++-- .../app/terminal/TerminalDockBrowserComp.java | 12 +++++++----- .../app/terminal/TerminalDockHubComp.java | 12 +++++++----- .../xpipe/app/terminal/TerminalDockView.java | 18 +++++++++--------- 4 files changed, 32 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemTabModel.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemTabModel.java index f3818bc7c..32f41fbb4 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemTabModel.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemTabModel.java @@ -44,6 +44,8 @@ import java.util.stream.Stream; @Getter public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab { + private static boolean wasTerminalDocked; + private final Property filter = new SimpleStringProperty(); private final BrowserFileListModel fileList; private final ReadOnlyObjectWrapper currentPath = new ReadOnlyObjectWrapper<>(); @@ -580,19 +582,24 @@ public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab request.equals(s.getRequest()) && s.getTerminal().isRunning()) - .map(s -> s.getTerminal().controllable()) - .flatMap(Optional::stream) + var others = terminalInstances.stream() + .filter(terminal -> terminal.getTerminalProcess().isAlive()) + .filter(terminal -> TerminalView.get().getSessions().stream() + .noneMatch(shellSession -> shellSession.getRequest().equals(request) && + shellSession.getTerminal().equals(terminal))) .toList(); - for (int i = 0; i < tv.size() - 1; i++) { - closeTerminal(tv.get(i)); + for (ControllableTerminalSession other : others) { + closeTerminal(other); } - return tv.size() > 1; + return others.size() > 0; } public synchronized void closeTerminal(ControllableTerminalSession terminal) { From b70b7633c15baca8b26022240058a573af2c83f2 Mon Sep 17 00:00:00 2001 From: crschnick Date: Thu, 12 Feb 2026 04:59:12 +0000 Subject: [PATCH 16/42] Fix NPE --- .../main/java/io/xpipe/app/secret/SecretInPlaceStrategy.java | 2 +- app/src/main/java/io/xpipe/app/secret/SecretQueryResult.java | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/io/xpipe/app/secret/SecretInPlaceStrategy.java b/app/src/main/java/io/xpipe/app/secret/SecretInPlaceStrategy.java index 3d169b14b..e47a904de 100644 --- a/app/src/main/java/io/xpipe/app/secret/SecretInPlaceStrategy.java +++ b/app/src/main/java/io/xpipe/app/secret/SecretInPlaceStrategy.java @@ -61,7 +61,7 @@ public class SecretInPlaceStrategy implements SecretRetrievalStrategy { return new SecretQuery() { @Override public SecretQueryResult query(String prompt) { - return new SecretQueryResult(value, SecretQueryState.NORMAL); + return value != null ? new SecretQueryResult(value, SecretQueryState.NORMAL) : new SecretQueryResult(null, SecretQueryState.RETRIEVAL_FAILURE); } @Override diff --git a/app/src/main/java/io/xpipe/app/secret/SecretQueryResult.java b/app/src/main/java/io/xpipe/app/secret/SecretQueryResult.java index 9531c136a..b1494db03 100644 --- a/app/src/main/java/io/xpipe/app/secret/SecretQueryResult.java +++ b/app/src/main/java/io/xpipe/app/secret/SecretQueryResult.java @@ -9,4 +9,9 @@ public class SecretQueryResult { SecretValue secret; SecretQueryState state; + + public SecretQueryResult(SecretValue secret, SecretQueryState state) { + this.secret = secret; + this.state = state; + } } From 71c6ac344c18f0e3a629d2049bdb14d19dd4a085 Mon Sep 17 00:00:00 2001 From: crschnick Date: Thu, 12 Feb 2026 16:13:55 +0000 Subject: [PATCH 17/42] Small fixes --- app/src/main/java/io/xpipe/app/prefs/TerminalCategory.java | 6 ------ .../main/java/io/xpipe/app/terminal/TerminalLauncher.java | 2 +- .../main/java/io/xpipe/app/terminal/WezTerminalType.java | 2 +- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/io/xpipe/app/prefs/TerminalCategory.java b/app/src/main/java/io/xpipe/app/prefs/TerminalCategory.java index 5e7847176..e42691ff6 100644 --- a/app/src/main/java/io/xpipe/app/prefs/TerminalCategory.java +++ b/app/src/main/java/io/xpipe/app/prefs/TerminalCategory.java @@ -178,12 +178,6 @@ public class TerminalCategory extends AppPrefsCategory { return new OptionsBuilder() .addTitle("terminalConfiguration") .sub(terminalChoice(true)) - .hide(Bindings.createBooleanBinding( - () -> { - return !TerminalDockHubManager.isSupported(); - }, - prefs.terminalType, - prefs.terminalMultiplexer)) .sub(terminalPrompt()) .sub(terminalProxy()) .sub(terminalMultiplexer()) diff --git a/app/src/main/java/io/xpipe/app/terminal/TerminalLauncher.java b/app/src/main/java/io/xpipe/app/terminal/TerminalLauncher.java index eb366ee8c..510cf1ab7 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalLauncher.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalLauncher.java @@ -167,7 +167,7 @@ public class TerminalLauncher { var effectivePreferTabs = preferTabs && AppPrefs.get().preferTerminalTabs().get(); - var launchConfig = new TerminalLaunchConfiguration(color, adjustedTitle, cleanTitle, preferTabs, paneList); + var launchConfig = new TerminalLaunchConfiguration(color, adjustedTitle, cleanTitle, effectivePreferTabs, paneList); if (effectivePreferTabs && AppPrefs.get().enableConnectionHubTerminalDocking().get() diff --git a/app/src/main/java/io/xpipe/app/terminal/WezTerminalType.java b/app/src/main/java/io/xpipe/app/terminal/WezTerminalType.java index 70a8eec9e..db5953c77 100644 --- a/app/src/main/java/io/xpipe/app/terminal/WezTerminalType.java +++ b/app/src/main/java/io/xpipe/app/terminal/WezTerminalType.java @@ -115,7 +115,7 @@ public interface WezTerminalType extends ExternalTerminalType, TrackableTerminal var base = getWeztermCommandBase(); var activeSocket = waitForInstanceStart(1); // Always start a new window for split panes as we can't find the pane index to start with - if (activeSocket.isEmpty() || configuration.getPanes().size() > 1) { + if (activeSocket.isEmpty() || configuration.getPanes().size() > 1 || !configuration.isPreferTabs()) { var gui = CommandBuilder.of().add(base.buildSimple().replace("wezterm.exe", "wezterm-gui.exe")); var command = CommandBuilder.of() .add(gui) From ea1c7f7f7e6d606e71ea19ed0c120ba63b3ee2ba Mon Sep 17 00:00:00 2001 From: crschnick Date: Fri, 13 Feb 2026 07:28:48 +0000 Subject: [PATCH 18/42] Show dock detached status --- .../app/terminal/TerminalDockHubManager.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubManager.java b/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubManager.java index 03854799e..5964d4552 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubManager.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubManager.java @@ -14,13 +14,17 @@ import io.xpipe.app.util.Rect; import io.xpipe.core.OsType; import javafx.application.Platform; +import javafx.beans.binding.Bindings; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.collections.ListChangeListener; import javafx.stage.Screen; import lombok.Getter; +import org.kordamp.ikonli.Ikon; +import org.kordamp.ikonli.Ikonli; import org.kordamp.ikonli.javafx.FontIcon; +import org.kordamp.ikonli.materialdesign2.MaterialDesignC; import java.time.Duration; import java.util.HashSet; @@ -101,10 +105,13 @@ public class TerminalDockHubManager { }); private final AppLayoutModel.QueueEntry queueEntry = new AppLayoutModel.QueueEntry( AppI18n.observable("toggleTerminalDock"), new LabelGraphic.NodeGraphic(() -> { - var fi = new FontIcon("mdi2c-console"); - fi.getStyleClass().add("graphic"); - fi.getStyleClass().add("terminal-dock-button"); - return fi; + var inner = new FontIcon(); + inner.iconCodeProperty().bind(Bindings.createObjectBinding(() -> { + return detached.get() || minimized.get() ? MaterialDesignC.CONSOLE_LINE : MaterialDesignC.CONSOLE; + }, detached, minimized)); + inner.getStyleClass().add("graphic"); + inner.getStyleClass().add("terminal-dock-button"); + return inner; }), () -> { refreshDockStatus(); From c2a54c4d90b50409f762ab11b43bcd8e9de03108 Mon Sep 17 00:00:00 2001 From: crschnick Date: Fri, 13 Feb 2026 07:29:03 +0000 Subject: [PATCH 19/42] Fix switch connection count --- .../main/java/io/xpipe/app/ext/CountGroupStoreProvider.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/java/io/xpipe/app/ext/CountGroupStoreProvider.java b/app/src/main/java/io/xpipe/app/ext/CountGroupStoreProvider.java index 9f40edcb0..16515d30e 100644 --- a/app/src/main/java/io/xpipe/app/ext/CountGroupStoreProvider.java +++ b/app/src/main/java/io/xpipe/app/ext/CountGroupStoreProvider.java @@ -8,6 +8,11 @@ import javafx.beans.value.ObservableValue; public interface CountGroupStoreProvider extends DataStoreProvider { + @Override + default boolean includeInConnectionCount() { + return false; + } + @Override default ObservableValue informationString(StoreSection section) { return Bindings.createStringBinding( From a35b304773aa8ce339c1680152a607f52026b8bb Mon Sep 17 00:00:00 2001 From: Lennard Date: Fri, 13 Feb 2026 10:17:10 +0100 Subject: [PATCH 20/42] Various improvements and bug fixes for 21.x (#781) * Fix rebase conflict (cherry picked from commit 90b3df859487c6e486da291fe4a4d2b714f6db12) * Add nvim as an ExternalEditorType for windows macos and linux (cherry picked from commit 978596fec4b45379390c409df14cf768dc3a1beb) * Add browser option to gradlew files to run tasks (cherry picked from commit c6d05a56db17842176d9f79e0fcb55f138ebab01) * Fix problem with tab titles and socket Dir resolving on linux (cherry picked from commit fb96dbc6613811ec8bf7f9c04318681fa754a798) --- .../menu/impl/GradleRunMenuProvider.java | 93 +++++++++++++++++++ .../xpipe/app/prefs/ExternalEditorType.java | 82 +++++++++++++++- .../xpipe/app/terminal/WezTerminalType.java | 34 +++++-- .../xpipe/app/update/AppDistributionType.java | 4 +- app/src/main/java/module-info.java | 1 + lang/strings/translations_en.properties | 2 + 6 files changed, 203 insertions(+), 13 deletions(-) create mode 100644 app/src/main/java/io/xpipe/app/browser/menu/impl/GradleRunMenuProvider.java diff --git a/app/src/main/java/io/xpipe/app/browser/menu/impl/GradleRunMenuProvider.java b/app/src/main/java/io/xpipe/app/browser/menu/impl/GradleRunMenuProvider.java new file mode 100644 index 000000000..0b44ec376 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/browser/menu/impl/GradleRunMenuProvider.java @@ -0,0 +1,93 @@ +package io.xpipe.app.browser.menu.impl; + +import io.xpipe.app.browser.file.BrowserEntry; +import io.xpipe.app.browser.file.BrowserFileSystemTabModel; +import io.xpipe.app.browser.menu.BrowserMenuCategory; +import io.xpipe.app.browser.menu.BrowserMenuLeafProvider; +import io.xpipe.app.comp.RegionBuilder; +import io.xpipe.app.comp.base.ModalOverlay; +import io.xpipe.app.core.AppI18n; +import io.xpipe.app.ext.FileKind; +import io.xpipe.app.platform.LabelGraphic; +import io.xpipe.app.process.CommandBuilder; +import io.xpipe.core.OsType; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.value.ObservableValue; +import javafx.scene.control.TextField; + +import java.util.List; + +public class GradleRunMenuProvider implements BrowserMenuLeafProvider { + + @Override + public boolean isApplicable(BrowserFileSystemTabModel model, List entries) { + if (model.getFileSystem().getShell().isEmpty()) { + return false; + } + + if (entries.size() != 1) { + return false; + } + + if (entries.getFirst().getRawFileEntry().getKind() != FileKind.FILE) { + return false; + } + + OsType.Any osType = model.getFileSystem().getShell().orElseThrow().getOsType(); + var ext = switch (osType) { + case OsType.Windows ignored -> "gradlew.bat"; + default -> "gradlew"; + }; + + if (!entries.getFirst().getFileName().equalsIgnoreCase(ext)) { + return false; + } + + return true; + } + + @Override + public ObservableValue getName(BrowserFileSystemTabModel model, List entries) { + return AppI18n.observable("runTask"); + } + + @Override + public BrowserMenuCategory getCategory() { + return BrowserMenuCategory.CUSTOM; + } + + @Override + public LabelGraphic getIcon() { + return new LabelGraphic.IconGraphic("mdi2e-elephant"); + } + + @Override + public void execute(BrowserFileSystemTabModel model, List entries) { + var tasks = new SimpleStringProperty(); + var modal = ModalOverlay.of( + "gradleTasks", + RegionBuilder.of(() -> { + var creationName = new TextField(); + creationName.textProperty().bindBidirectional(tasks); + return creationName; + }) + .prefWidth(350)); + modal.withDefaultButtons(() -> { + var fixedTasks = tasks.getValue(); + if (fixedTasks == null) { + return; + } + + var parent = entries.getFirst().getRawFileEntry().getPath().getParent(); + var command = model.getFileSystem().getShell().orElseThrow().command(CommandBuilder.of() + .add("sh") + .addFile(entries.getFirst().getRawFileEntry().getPath()) + .add(fixedTasks) + ); + + model.openTerminalAsync(fixedTasks, parent, command, true); + }); + modal.show(); + } + +} diff --git a/app/src/main/java/io/xpipe/app/prefs/ExternalEditorType.java b/app/src/main/java/io/xpipe/app/prefs/ExternalEditorType.java index 795ae83b6..4ac5dd5bf 100644 --- a/app/src/main/java/io/xpipe/app/prefs/ExternalEditorType.java +++ b/app/src/main/java/io/xpipe/app/prefs/ExternalEditorType.java @@ -23,7 +23,6 @@ import java.util.Optional; import java.util.function.Supplier; public interface ExternalEditorType extends PrefsChoiceValue { - ExternalEditorType NOTEPAD = new WindowsType() { @Override @@ -280,6 +279,68 @@ public interface ExternalEditorType extends PrefsChoiceValue { LinuxPathType KIRO_LINUX = new LinuxPathType("app.kiro", "kiro", "https://kiro.dev/"); + LinuxType NEOVIM_LINUX = new LinuxType("app.neovim", "nvim", "https://neovim.io/", null) { + @Override + public void launch(Path file) throws Exception { + TerminalLaunch.builder() + .title(file.toString()) + .localScript(sc -> new ShellScript(CommandBuilder.of() + .add(getExecutable()) + .addFile(file.toString()) + .buildFull(sc))) + .logIfEnabled(false) + .preferTabs(false) + .launch(); + } + }; + + WindowsType NEOVIM_WINDOWS = new WindowsType() { + @Override + public String getId() { + return "app.neovim"; + } + + @Override + public boolean detach() { + return false; + } + + @Override + public String getExecutable() { + return "nvim"; + } + + + @Override + public String getWebsite() { + return "https://neovim.io/"; + } + + + @Override + public Optional determineInstallation() { + var local = AppSystemInfo.ofWindows().getLocalAppData().resolve("Programs", "nvim").resolve("nvim.exe"); + if (Files.exists(local)) return Optional.of(local); + var programFiles = AppSystemInfo.ofWindows().getProgramFiles().resolve("Programs", "nvim").resolve("nvim.exe"); + if (Files.exists(programFiles)) return Optional.of(programFiles); + return Optional.empty(); + } + + @Override + public void launch(Path file) throws Exception { + TerminalLaunch.builder() + .title(file.toString()) + .localScript(sc -> new ShellScript(CommandBuilder.of() + .add(findExecutable().toString()) + .addFile(file) + .buildFull(sc))) + .logIfEnabled(false) + .preferTabs(false) + .launch(); + } + + }; + WindowsType ZED_WINDOWS = new WindowsType() { @Override @@ -356,6 +417,20 @@ public interface ExternalEditorType extends PrefsChoiceValue { ExternalEditorType WINDSURF_MACOS = new MacOsEditor("app.windsurf", "Windsurf", "https://windsurf.com/editor"); ExternalEditorType KIRO_MACOS = new MacOsEditor("app.kiro", "Kiro", "https://kiro.dev/"); ExternalEditorType TRAE_MACOS = new MacOsEditor("app.trae", "Trae", "https://www.trae.ai/"); + ExternalEditorType NEOVIM_MACOS = new MacOsEditor("app.neovim", "Neovim", "https://neovim.io/") { + @Override + public void launch(Path file) throws Exception { + TerminalLaunch.builder() + .title(file.toString()) + .localScript(sc -> new ShellScript(CommandBuilder.of() + .add("nvim") + .addFile(file.toString()) + .buildFull(sc))) + .logIfEnabled(false) + .preferTabs(false) + .launch(); + } + }; ExternalEditorType CUSTOM = new ExternalEditorType() { @Override @@ -422,7 +497,8 @@ public interface ExternalEditorType extends PrefsChoiceValue { VSCODE_INSIDERS_WINDOWS, VSCODE_WINDOWS, NOTEPADPLUSPLUS, - NOTEPAD); + NOTEPAD, + NEOVIM_WINDOWS); List LINUX_EDITORS = List.of( ExternalEditorType.WINDSURF_LINUX, ExternalEditorType.KIRO_LINUX, @@ -435,6 +511,7 @@ public interface ExternalEditorType extends PrefsChoiceValue { PLUMA, LEAFPAD, MOUSEPAD, + NEOVIM_LINUX, GNOME, ExternalEditorType.COSMIC_EDIT, ExternalEditorType.WESTON_EDITOR, @@ -451,6 +528,7 @@ public interface ExternalEditorType extends PrefsChoiceValue { VSCODE_MACOS, SUBLIME_MACOS, ZED_MACOS, + NEOVIM_MACOS, TEXT_EDIT); List CROSS_PLATFORM_EDITORS = List.of(FLEET, INTELLIJ, PYCHARM, WEBSTORM, CLION); diff --git a/app/src/main/java/io/xpipe/app/terminal/WezTerminalType.java b/app/src/main/java/io/xpipe/app/terminal/WezTerminalType.java index db5953c77..bba58f365 100644 --- a/app/src/main/java/io/xpipe/app/terminal/WezTerminalType.java +++ b/app/src/main/java/io/xpipe/app/terminal/WezTerminalType.java @@ -12,6 +12,7 @@ import io.xpipe.app.util.ThreadHelper; import io.xpipe.app.util.WindowsRegistry; import io.xpipe.core.FilePath; +import io.xpipe.core.OsType; import lombok.SneakyThrows; import java.io.IOException; @@ -56,7 +57,11 @@ public interface WezTerminalType extends ExternalTerminalType, TrackableTerminal } default Path getSocketDir() { - return AppSystemInfo.ofCurrent().getUserHome().resolve(".local", "share", "wezterm"); + if (OsType.ofLocal() == OsType.LINUX) { + return Path.of(System.getenv("XDG_RUNTIME_DIR"), "wezterm"); + } else { + return AppSystemInfo.ofCurrent().getUserHome().resolve(".local", "share", "wezterm"); + } } default Optional waitForInstanceStart(int count) { @@ -114,6 +119,7 @@ public interface WezTerminalType extends ExternalTerminalType, TrackableTerminal default void launch(TerminalLaunchConfiguration configuration) throws Exception { var base = getWeztermCommandBase(); var activeSocket = waitForInstanceStart(1); + var tabid = "0"; // Always start a new window for split panes as we can't find the pane index to start with if (activeSocket.isEmpty() || configuration.getPanes().size() > 1 || !configuration.isPreferTabs()) { var gui = CommandBuilder.of().add(base.buildSimple().replace("wezterm.exe", "wezterm-gui.exe")); @@ -122,23 +128,34 @@ public interface WezTerminalType extends ExternalTerminalType, TrackableTerminal .add("start", "--always-new-process") .add(configuration.getPanes().getFirst().getDialectLaunchCommand()); ExternalApplicationHelper.startAsync(command); + activeSocket = waitForInstanceStart(50); + if (activeSocket.isEmpty()) { + return; + } } else { var command = CommandBuilder.of() .add(base) .add("cli", "spawn") .add(configuration.getPanes().getFirst().getDialectLaunchCommand()); command.fixedEnvironment("WEZTERM_UNIX_SOCKET", activeSocket.get().toString()); - LocalShell.getShell() + tabid = LocalShell.getShell() .command(command) .withWorkingDirectory(FilePath.of(getSocketDir())) - .execute(); + .readStdoutOrThrow(); } + var titleCommand = CommandBuilder.of() + .add(base) + .add("cli", "set-tab-title") + .add("--tab-id", tabid) + .addQuoted(configuration.getColoredTitle()); + titleCommand.fixedEnvironment("WEZTERM_UNIX_SOCKET", activeSocket.get().toString()); + LocalShell.getShell() + .command(titleCommand) + .withWorkingDirectory(FilePath.of(getSocketDir())) + .execute(); + if (configuration.getPanes().size() > 1) { - activeSocket = waitForInstanceStart(50); - if (activeSocket.isEmpty()) { - return; - } var direction = AppPrefs.get().terminalSplitStrategy().getValue(); var directionIterator = direction.iterator(); @@ -163,8 +180,7 @@ public interface WezTerminalType extends ExternalTerminalType, TrackableTerminal activeSocket.get().toString())) .withWorkingDirectory(FilePath.of(getSocketDir())) .execute(); - directionIterator.next(); - } + directionIterator.next(); } } } diff --git a/app/src/main/java/io/xpipe/app/update/AppDistributionType.java b/app/src/main/java/io/xpipe/app/update/AppDistributionType.java index d301a6b95..1a0c74a90 100644 --- a/app/src/main/java/io/xpipe/app/update/AppDistributionType.java +++ b/app/src/main/java/io/xpipe/app/update/AppDistributionType.java @@ -53,8 +53,8 @@ public enum AppDistributionType implements Translatable { AUR("aur", true, () -> { var pkg = AppNames.ofCurrent().getKebapName(); return new CommandUpdater(ShellScript.lines( - "echo \"+ git clone https://aur.archlinux.org/" + pkg + " . && makepkg -si\"", - "cd $(mktemp -d) && git clone https://aur.archlinux.org/" + pkg + " . && makepkg -si --noconfirm", + "echo \"+ git -c core.autocrlf=false clone https://aur.archlinux.org/" + pkg + " . && makepkg -si\"", + "cd $(mktemp -d) && git -c core.autocrlf=false clone https://aur.archlinux.org/" + pkg + " . && makepkg -si --noconfirm", "if [ \"$?\" != 0 ]; then echo \"Update failed ...\"; read key; fi", AppRestart.getTerminalRestartCommand())); }), diff --git a/app/src/main/java/module-info.java b/app/src/main/java/module-info.java index f310696e6..d1ecd7c85 100644 --- a/app/src/main/java/module-info.java +++ b/app/src/main/java/module-info.java @@ -128,6 +128,7 @@ open module io.xpipe.app { uses CloudSetupProvider; provides ActionProvider with + GradleRunMenuProvider, RefreshHubLeafProvider, SetupToolActionProvider, XPipeUrlProvider, diff --git a/lang/strings/translations_en.properties b/lang/strings/translations_en.properties index 4488edd79..98596270d 100644 --- a/lang/strings/translations_en.properties +++ b/lang/strings/translations_en.properties @@ -778,6 +778,8 @@ hub=Hub #context: Computer script script=script genericScript=Generic +gradleTasks=Gradle tasks +runTask=Run task archiveName=Archive name compress=Compress compressContents=Compress contents From 977739b2251c684b2b982c4ad85b0b33413a8c1e Mon Sep 17 00:00:00 2001 From: crschnick Date: Fri, 13 Feb 2026 09:52:56 +0000 Subject: [PATCH 21/42] Implement various for merged PR --- .../io/xpipe/app/prefs/ExternalEditorType.java | 18 +++++++++++------- .../io/xpipe/app/prefs/TerminalCategory.java | 2 +- .../xpipe/app/prefs/TroubleshootCategory.java | 2 +- .../app/pwman/BitwardenPasswordManager.java | 2 +- .../app/pwman/DashlanePasswordManager.java | 2 +- .../xpipe/app/pwman/KeeperPasswordManager.java | 2 +- .../app/pwman/LastpassPasswordManager.java | 2 +- .../io/xpipe/app/terminal/TerminalLaunch.java | 5 ++--- .../xpipe/app/terminal/TerminalLauncher.java | 4 +--- .../io/xpipe/app/terminal/WezTerminalType.java | 5 +++-- .../RunTerminalScriptActionProvider.java | 2 +- .../PodmanContainerLogsActionProvider.java | 2 +- lang/strings/fixed_en.properties | 1 + lang/strings/translations_da.properties | 2 ++ lang/strings/translations_de.properties | 2 ++ lang/strings/translations_es.properties | 2 ++ lang/strings/translations_fr.properties | 2 ++ lang/strings/translations_id.properties | 2 ++ lang/strings/translations_it.properties | 2 ++ lang/strings/translations_ja.properties | 2 ++ lang/strings/translations_ko.properties | 2 ++ lang/strings/translations_nl.properties | 2 ++ lang/strings/translations_pl.properties | 2 ++ lang/strings/translations_pt.properties | 2 ++ lang/strings/translations_ru.properties | 2 ++ lang/strings/translations_sv.properties | 2 ++ lang/strings/translations_tr.properties | 2 ++ lang/strings/translations_vi.properties | 2 ++ lang/strings/translations_zh-Hans.properties | 2 ++ lang/strings/translations_zh-Hant.properties | 2 ++ 30 files changed, 60 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/io/xpipe/app/prefs/ExternalEditorType.java b/app/src/main/java/io/xpipe/app/prefs/ExternalEditorType.java index 4ac5dd5bf..6b0f194ad 100644 --- a/app/src/main/java/io/xpipe/app/prefs/ExternalEditorType.java +++ b/app/src/main/java/io/xpipe/app/prefs/ExternalEditorType.java @@ -285,11 +285,12 @@ public interface ExternalEditorType extends PrefsChoiceValue { TerminalLaunch.builder() .title(file.toString()) .localScript(sc -> new ShellScript(CommandBuilder.of() - .add(getExecutable()) + .addFile(getExecutable()) .addFile(file.toString()) .buildFull(sc))) .logIfEnabled(false) .preferTabs(false) + .pauseOnExit(false) .launch(); } }; @@ -319,10 +320,10 @@ public interface ExternalEditorType extends PrefsChoiceValue { @Override public Optional determineInstallation() { - var local = AppSystemInfo.ofWindows().getLocalAppData().resolve("Programs", "nvim").resolve("nvim.exe"); - if (Files.exists(local)) return Optional.of(local); - var programFiles = AppSystemInfo.ofWindows().getProgramFiles().resolve("Programs", "nvim").resolve("nvim.exe"); - if (Files.exists(programFiles)) return Optional.of(programFiles); + var programFiles = AppSystemInfo.ofWindows().getProgramFiles().resolve("Neovim", "bin").resolve("nvim.exe"); + if (Files.exists(programFiles)) { + return Optional.of(programFiles); + } return Optional.empty(); } @@ -331,11 +332,12 @@ public interface ExternalEditorType extends PrefsChoiceValue { TerminalLaunch.builder() .title(file.toString()) .localScript(sc -> new ShellScript(CommandBuilder.of() - .add(findExecutable().toString()) + .addFile(findExecutable().toString()) .addFile(file) .buildFull(sc))) .logIfEnabled(false) .preferTabs(false) + .pauseOnExit(false) .launch(); } @@ -423,11 +425,12 @@ public interface ExternalEditorType extends PrefsChoiceValue { TerminalLaunch.builder() .title(file.toString()) .localScript(sc -> new ShellScript(CommandBuilder.of() - .add("nvim") + .addFile("nvim") .addFile(file.toString()) .buildFull(sc))) .logIfEnabled(false) .preferTabs(false) + .pauseOnExit(false) .launch(); } }; @@ -455,6 +458,7 @@ public interface ExternalEditorType extends PrefsChoiceValue { .localScript(sc -> new ShellScript(command.buildFull(sc))) .logIfEnabled(false) .preferTabs(false) + .pauseOnExit(false) .launch(); } else { ExternalApplicationHelper.startAsync(command); diff --git a/app/src/main/java/io/xpipe/app/prefs/TerminalCategory.java b/app/src/main/java/io/xpipe/app/prefs/TerminalCategory.java index e42691ff6..13eb99a4f 100644 --- a/app/src/main/java/io/xpipe/app/prefs/TerminalCategory.java +++ b/app/src/main/java/io/xpipe/app/prefs/TerminalCategory.java @@ -117,7 +117,7 @@ public class TerminalCategory extends AppPrefsCategory { "If you can read this, the terminal integration works", false))) .preferTabs(false) .logIfEnabled(false) - .alwaysKeepOpen(true) + .pauseOnExit(true) .launch(); } }); diff --git a/app/src/main/java/io/xpipe/app/prefs/TroubleshootCategory.java b/app/src/main/java/io/xpipe/app/prefs/TroubleshootCategory.java index 47cc3eabe..fbc21d7bc 100644 --- a/app/src/main/java/io/xpipe/app/prefs/TroubleshootCategory.java +++ b/app/src/main/java/io/xpipe/app/prefs/TroubleshootCategory.java @@ -73,7 +73,7 @@ public class TroubleshootCategory extends AppPrefsCategory { var script = AppInstallation.ofCurrent().getDaemonDebugScriptPath(); TerminalLaunch.builder() .title(AppNames.ofCurrent().getName() + " Debug") - .alwaysKeepOpen(true) + .pauseOnExit(true) .localScript(sc -> new ShellScript( sc.getShellDialect().runScriptCommand(sc, script.toString()))) .launch(); diff --git a/app/src/main/java/io/xpipe/app/pwman/BitwardenPasswordManager.java b/app/src/main/java/io/xpipe/app/pwman/BitwardenPasswordManager.java index aec36f929..cf51d02a8 100644 --- a/app/src/main/java/io/xpipe/app/pwman/BitwardenPasswordManager.java +++ b/app/src/main/java/io/xpipe/app/pwman/BitwardenPasswordManager.java @@ -92,7 +92,7 @@ public class BitwardenPasswordManager implements PasswordManager { .localScript(script) .logIfEnabled(false) .preferTabs(false) - .alwaysKeepOpen(true) + .pauseOnExit(true) .launch(); return null; } diff --git a/app/src/main/java/io/xpipe/app/pwman/DashlanePasswordManager.java b/app/src/main/java/io/xpipe/app/pwman/DashlanePasswordManager.java index e2155eade..267bd2d31 100644 --- a/app/src/main/java/io/xpipe/app/pwman/DashlanePasswordManager.java +++ b/app/src/main/java/io/xpipe/app/pwman/DashlanePasswordManager.java @@ -48,7 +48,7 @@ public class DashlanePasswordManager implements PasswordManager { .title("Dashlane login") .localScript(script) .logIfEnabled(false) - .alwaysKeepOpen(true) + .pauseOnExit(true) .launch(); return null; } 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 01c552ce0..814dba92d 100644 --- a/app/src/main/java/io/xpipe/app/pwman/KeeperPasswordManager.java +++ b/app/src/main/java/io/xpipe/app/pwman/KeeperPasswordManager.java @@ -105,7 +105,7 @@ public class KeeperPasswordManager implements PasswordManager { .title("Keeper login") .localScript(script) .logIfEnabled(false) - .alwaysKeepOpen(true) + .pauseOnExit(true) .launch(); return null; } diff --git a/app/src/main/java/io/xpipe/app/pwman/LastpassPasswordManager.java b/app/src/main/java/io/xpipe/app/pwman/LastpassPasswordManager.java index e9c006923..7b4139fb0 100644 --- a/app/src/main/java/io/xpipe/app/pwman/LastpassPasswordManager.java +++ b/app/src/main/java/io/xpipe/app/pwman/LastpassPasswordManager.java @@ -54,7 +54,7 @@ public class LastpassPasswordManager implements PasswordManager { .title("LastPass login") .localScript(script) .logIfEnabled(false) - .alwaysKeepOpen(true) + .pauseOnExit(true) .launch(); } return null; diff --git a/app/src/main/java/io/xpipe/app/terminal/TerminalLaunch.java b/app/src/main/java/io/xpipe/app/terminal/TerminalLaunch.java index f5f5f89b2..4894d45a6 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalLaunch.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalLaunch.java @@ -37,7 +37,7 @@ public class TerminalLaunch { boolean logIfEnabled = true; @Builder.Default - boolean alwaysKeepOpen = false; + boolean pauseOnExit = AppPrefs.get().terminalAlwaysPauseOnExit().getValue(); ExternalTerminalType terminal; @@ -68,8 +68,7 @@ public class TerminalLaunch { getFullTitle(), directory, request != null ? request : UUID.randomUUID(), - logIfEnabled, - alwaysKeepOpen, + logIfEnabled, pauseOnExit, command); TerminalLauncher.open(List.of(pane), preferTabs, type); } diff --git a/app/src/main/java/io/xpipe/app/terminal/TerminalLauncher.java b/app/src/main/java/io/xpipe/app/terminal/TerminalLauncher.java index 510cf1ab7..5e62664e5 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalLauncher.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalLauncher.java @@ -143,8 +143,6 @@ public class TerminalLauncher { config.getProcessControl() instanceof ShellControl ? type.additionalInitCommands() : TerminalInitFunction.none()); - var alwaysPromptRestart = config.isAlwaysKeepOpen() - || AppPrefs.get().terminalAlwaysPauseOnExit().getValue(); TerminalLauncherManager.submitAsync( config.getRequest(), config.getProcessControl(), terminalConfig, config.getDirectory(), latch); var effectivePreferTabs = @@ -152,7 +150,7 @@ public class TerminalLauncher { var paneIndex = configs.indexOf(config); var paneConfig = TerminalPaneConfiguration.create( - config.getRequest(), entry, config.getTitle(), paneIndex, effectivePreferTabs, alwaysPromptRestart); + config.getRequest(), entry, config.getTitle(), paneIndex, effectivePreferTabs, config.isAlwaysKeepOpen()); paneList.add(paneConfig); } diff --git a/app/src/main/java/io/xpipe/app/terminal/WezTerminalType.java b/app/src/main/java/io/xpipe/app/terminal/WezTerminalType.java index bba58f365..83362f556 100644 --- a/app/src/main/java/io/xpipe/app/terminal/WezTerminalType.java +++ b/app/src/main/java/io/xpipe/app/terminal/WezTerminalType.java @@ -150,13 +150,14 @@ public interface WezTerminalType extends ExternalTerminalType, TrackableTerminal .add("--tab-id", tabid) .addQuoted(configuration.getColoredTitle()); titleCommand.fixedEnvironment("WEZTERM_UNIX_SOCKET", activeSocket.get().toString()); + // Sometimes the tab ids don't exist even though it just returned them to us + // So just ignore any errors LocalShell.getShell() .command(titleCommand) .withWorkingDirectory(FilePath.of(getSocketDir())) - .execute(); + .executeAndCheck(); if (configuration.getPanes().size() > 1) { - var direction = AppPrefs.get().terminalSplitStrategy().getValue(); var directionIterator = direction.iterator(); for (int i = 1; i < configuration.getPanes().size(); i++) { 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 12312908d..bf16a9937 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 @@ -34,7 +34,7 @@ public class RunTerminalScriptActionProvider implements ActionProvider { .entry(ref.get()) .title(scriptStore.get().getName()) .command(sc.command(script)) - .alwaysKeepOpen(true) + .pauseOnExit(true) .launch(); } diff --git a/ext/system/src/main/java/io/xpipe/ext/system/podman/PodmanContainerLogsActionProvider.java b/ext/system/src/main/java/io/xpipe/ext/system/podman/PodmanContainerLogsActionProvider.java index dd2279998..06de720fc 100644 --- a/ext/system/src/main/java/io/xpipe/ext/system/podman/PodmanContainerLogsActionProvider.java +++ b/ext/system/src/main/java/io/xpipe/ext/system/podman/PodmanContainerLogsActionProvider.java @@ -49,7 +49,7 @@ public class PodmanContainerLogsActionProvider implements HubLeafProvider Date: Fri, 13 Feb 2026 09:54:33 +0000 Subject: [PATCH 22/42] [stage] --- dist/changelog/21.3.md | 1 + version | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 dist/changelog/21.3.md diff --git a/dist/changelog/21.3.md b/dist/changelog/21.3.md new file mode 100644 index 000000000..06f78c444 --- /dev/null +++ b/dist/changelog/21.3.md @@ -0,0 +1 @@ +- Add support for neovim editor diff --git a/version b/version index 931463eee..bb1d46cb5 100644 --- a/version +++ b/version @@ -1 +1 @@ -21.2-1 +21.3-1 From f9021fd299cd2c9bf27443ccf3fd632771ab3386 Mon Sep 17 00:00:00 2001 From: crschnick Date: Fri, 13 Feb 2026 18:11:59 +0000 Subject: [PATCH 23/42] Bump javafx [stage] [all] --- .../java/io/xpipe/app/platform/PlatformState.java | 13 +++++++------ build.gradle | 2 +- dist/changelog/21.3.md | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) 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 65034da67..101e48c48 100644 --- a/app/src/main/java/io/xpipe/app/platform/PlatformState.java +++ b/app/src/main/java/io/xpipe/app/platform/PlatformState.java @@ -113,12 +113,13 @@ public enum PlatformState { } } - if (SystemUtils.IS_OS_WINDOWS) { - // This is primarily intended to fix Windows unified stage transparency issues - // (https://bugs.openjdk.org/browse/JDK-8329382) - // But apparently it can also occur without a custom stage on Windows - System.setProperty("prism.forceUploadingPainter", "true"); - } + // This issue is now fixed in 27-ea+4 + // if (SystemUtils.IS_OS_WINDOWS) { + // This is primarily intended to fix Windows unified stage transparency issues + // (https://bugs.openjdk.org/browse/JDK-8329382) + // But apparently it can also occur without a custom stage on Windows + // System.setProperty("prism.forceUploadingPainter", "true"); + // } if (AppPrefs.get() != null && AppPrefs.get().disableHardwareAcceleration().get()) { diff --git a/build.gradle b/build.gradle index cba465978..aa47d3683 100644 --- a/build.gradle +++ b/build.gradle @@ -296,7 +296,7 @@ project.ext { } // JavaFX config - devJavafxVersion = '26-ea+19' + devJavafxVersion = '27-ea+4' platformName = getPlatformName() useBundledJavaFx = fullVersion bundledJdkJavaFx = ModuleFinder.ofSystem().find("javafx.base").isPresent() diff --git a/dist/changelog/21.3.md b/dist/changelog/21.3.md index 06f78c444..73ce0ebe9 100644 --- a/dist/changelog/21.3.md +++ b/dist/changelog/21.3.md @@ -1 +1 @@ -- Add support for neovim editor +- Add support for neovim editor (Thanks to @leycm) From a9f49670937531ade610979ee7a43237f2a23184 Mon Sep 17 00:00:00 2001 From: crschnick Date: Fri, 13 Feb 2026 19:18:04 +0000 Subject: [PATCH 24/42] [stage] --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index bb1d46cb5..43bc083d1 100644 --- a/version +++ b/version @@ -1 +1 @@ -21.3-1 +21.3-2 From 63666c2dd564ed07f091669442c4d49eaf9930ec Mon Sep 17 00:00:00 2001 From: crschnick Date: Sat, 14 Feb 2026 07:24:38 +0000 Subject: [PATCH 25/42] Fix hooks for new hwnds --- .../io/xpipe/app/core/AppWindowsLock.java | 16 +++++++++++++ .../io/xpipe/app/core/AppWindowsShutdown.java | 15 ++++++++++++ .../xpipe/app/core/window/AppMainWindow.java | 24 +++++++++++++++---- .../main/java/io/xpipe/app/util/User32Ex.java | 3 +++ 4 files changed, 53 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/io/xpipe/app/core/AppWindowsLock.java b/app/src/main/java/io/xpipe/app/core/AppWindowsLock.java index 322e6f139..83400aab5 100644 --- a/app/src/main/java/io/xpipe/app/core/AppWindowsLock.java +++ b/app/src/main/java/io/xpipe/app/core/AppWindowsLock.java @@ -35,6 +35,22 @@ public class AppWindowsLock { } } + public static void unregisterHook(WinDef.HWND hwnd) { + try { + int windowThreadID = User32.INSTANCE.GetWindowThreadProcessId(hwnd, null); + if (windowThreadID == 0) { + return; + } + + User32Ex.INSTANCE.SetWindowLongPtr(hwnd, GWLP_WNDPROC, PROC.oldWindowProc); + PROC.oldWindowProc = null; + + Wtsapi32.INSTANCE.WTSUnRegisterSessionNotification(hwnd); + } catch (Throwable t) { + ErrorEventFactory.fromThrowable(t).omit().handle(); + } + } + public interface WinMsgProc extends StdCallLibrary.StdCallCallback { @SuppressWarnings("unused") diff --git a/app/src/main/java/io/xpipe/app/core/AppWindowsShutdown.java b/app/src/main/java/io/xpipe/app/core/AppWindowsShutdown.java index d4ec24f11..77e93c47f 100644 --- a/app/src/main/java/io/xpipe/app/core/AppWindowsShutdown.java +++ b/app/src/main/java/io/xpipe/app/core/AppWindowsShutdown.java @@ -39,6 +39,21 @@ public class AppWindowsShutdown { } } + public static void unregisterHook(WinDef.HWND hwnd) { + try { + int windowThreadID = User32.INSTANCE.GetWindowThreadProcessId(hwnd, null); + if (windowThreadID == 0) { + return; + } + + User32.INSTANCE.UnhookWindowsHookEx(PROC.hhook); + PROC.hhook = null; + PROC.hwnd = null; + } catch (Throwable t) { + ErrorEventFactory.fromThrowable(t).omit().handle(); + } + } + public interface WinHookProc extends WinUser.HOOKPROC { @SuppressWarnings("unused") 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 a223b36aa..aef2e1e8c 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 @@ -170,12 +170,26 @@ public class AppMainWindow { public void show() { stage.show(); - if (OsType.ofLocal() == OsType.WINDOWS && !shown) { - var ctrl = new NativeWinWindowControl(stage); - NativeWinWindowControl.MAIN_WINDOW = ctrl; - AppWindowsLock.registerHook(ctrl.getWindowHandle()); - AppWindowsShutdown.registerHook(ctrl.getWindowHandle()); + + if (OsType.ofLocal() == OsType.WINDOWS) { + var currentControl = new NativeWinWindowControl(stage); + + // Clean up old window as the HWND might not be reused + var oldControl = NativeWinWindowControl.MAIN_WINDOW; + if (oldControl != null && !oldControl.getWindowHandle().equals(currentControl.getWindowHandle())) { + AppWindowsLock.unregisterHook(oldControl.getWindowHandle()); + AppWindowsShutdown.unregisterHook(oldControl.getWindowHandle()); + NativeWinWindowControl.MAIN_WINDOW = null; + } + + if (NativeWinWindowControl.MAIN_WINDOW == null) { + var ctrl = new NativeWinWindowControl(stage); + NativeWinWindowControl.MAIN_WINDOW = ctrl; + AppWindowsLock.registerHook(ctrl.getWindowHandle()); + AppWindowsShutdown.registerHook(ctrl.getWindowHandle()); + } } + shown = true; } diff --git a/app/src/main/java/io/xpipe/app/util/User32Ex.java b/app/src/main/java/io/xpipe/app/util/User32Ex.java index 8091879c8..b69761e33 100644 --- a/app/src/main/java/io/xpipe/app/util/User32Ex.java +++ b/app/src/main/java/io/xpipe/app/util/User32Ex.java @@ -1,6 +1,7 @@ package io.xpipe.app.util; import com.sun.jna.Native; +import com.sun.jna.Pointer; import com.sun.jna.platform.win32.WinDef; import com.sun.jna.win32.W32APIOptions; import io.xpipe.app.core.AppWindowsLock; @@ -11,5 +12,7 @@ public interface User32Ex extends W32APIOptions { int SetWindowLongPtr(WinDef.HWND hWnd, int nIndex, AppWindowsLock.WinMsgProc callback); + int SetWindowLongPtr(WinDef.HWND hWnd, int nIndex, Pointer p); + int SetWindowLongPtr(WinDef.HWND hWnd, int nIndex, WinDef.HWND w); } From d635559f6629c8dc15c1dbde8bad393e6da007e0 Mon Sep 17 00:00:00 2001 From: crschnick Date: Sat, 14 Feb 2026 08:00:44 +0000 Subject: [PATCH 26/42] Various fixes [stage] --- .../main/java/io/xpipe/app/terminal/TerminalDockHubComp.java | 2 +- .../java/io/xpipe/app/terminal/TerminalDockHubManager.java | 4 ++++ version | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubComp.java b/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubComp.java index 160e9b429..885d50e7b 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubComp.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubComp.java @@ -124,7 +124,7 @@ public class TerminalDockHubComp extends SimpleRegionBuilder { } private void update(Region region) { - if (region.getScene() == null || region.getScene().getWindow() == null) { + if (region.getScene() == null || region.getScene().getWindow() == null || NativeWinWindowControl.MAIN_WINDOW == null) { return; } diff --git a/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubManager.java b/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubManager.java index 5964d4552..011c5a36f 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubManager.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubManager.java @@ -63,6 +63,10 @@ public class TerminalDockHubManager { return false; } + if (NativeWinWindowControl.MAIN_WINDOW == null) { + return false; + } + return true; } diff --git a/version b/version index 43bc083d1..27040fe00 100644 --- a/version +++ b/version @@ -1 +1 @@ -21.3-2 +21.3-3 From 0e07e9d1960e2a6ac762ba175771a0ad47f35b7a Mon Sep 17 00:00:00 2001 From: crschnick Date: Sat, 14 Feb 2026 09:47:39 +0000 Subject: [PATCH 27/42] Various fixes --- app/src/main/java/io/xpipe/app/core/AppWindowsLock.java | 4 ++++ app/src/main/java/io/xpipe/app/core/AppWindowsShutdown.java | 4 ++++ app/src/main/java/io/xpipe/app/storage/StandardStorage.java | 6 ++++-- .../io/xpipe/ext/base/identity/ssh/PageantStrategy.java | 3 ++- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/io/xpipe/app/core/AppWindowsLock.java b/app/src/main/java/io/xpipe/app/core/AppWindowsLock.java index 83400aab5..19290788d 100644 --- a/app/src/main/java/io/xpipe/app/core/AppWindowsLock.java +++ b/app/src/main/java/io/xpipe/app/core/AppWindowsLock.java @@ -42,6 +42,10 @@ public class AppWindowsLock { return; } + if (PROC.oldWindowProc == null) { + return; + } + User32Ex.INSTANCE.SetWindowLongPtr(hwnd, GWLP_WNDPROC, PROC.oldWindowProc); PROC.oldWindowProc = null; diff --git a/app/src/main/java/io/xpipe/app/core/AppWindowsShutdown.java b/app/src/main/java/io/xpipe/app/core/AppWindowsShutdown.java index 77e93c47f..0a7a3446c 100644 --- a/app/src/main/java/io/xpipe/app/core/AppWindowsShutdown.java +++ b/app/src/main/java/io/xpipe/app/core/AppWindowsShutdown.java @@ -46,6 +46,10 @@ public class AppWindowsShutdown { return; } + if (PROC.hhook == null) { + return; + } + User32.INSTANCE.UnhookWindowsHookEx(PROC.hhook); PROC.hhook = null; PROC.hwnd = null; diff --git a/app/src/main/java/io/xpipe/app/storage/StandardStorage.java b/app/src/main/java/io/xpipe/app/storage/StandardStorage.java index 32f8ccff4..d9d16495f 100644 --- a/app/src/main/java/io/xpipe/app/storage/StandardStorage.java +++ b/app/src/main/java/io/xpipe/app/storage/StandardStorage.java @@ -266,8 +266,10 @@ public class StandardStorage extends DataStorage { // Bring entries into completed validity if possible // Needed for chained stores refreshEntries(); - // Let providers work on complete stores - callProviders(); + if (initialLoad) { + // Let providers work on complete stores + callProviders(); + } // Update validities after any possible changes refreshEntries(); // Add any possible missing synthetic parents diff --git a/ext/base/src/main/java/io/xpipe/ext/base/identity/ssh/PageantStrategy.java b/ext/base/src/main/java/io/xpipe/ext/base/identity/ssh/PageantStrategy.java index 292d7ba80..100cc841c 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/identity/ssh/PageantStrategy.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/identity/ssh/PageantStrategy.java @@ -1,6 +1,7 @@ package io.xpipe.ext.base.identity.ssh; import io.xpipe.app.comp.base.TextFieldComp; +import io.xpipe.app.core.AppSystemInfo; import io.xpipe.app.issue.ErrorEventFactory; import io.xpipe.app.platform.OptionsBuilder; import io.xpipe.app.prefs.AppPrefs; @@ -124,7 +125,7 @@ public class PageantStrategy implements SshIdentityStrategy { private String getPageantWindowsPipe() { Memory p = new Memory(WinBase.WIN32_FIND_DATA.sizeOf()); - var r = Kernel32.INSTANCE.FindFirstFile("\\\\.\\pipe\\*pageant*", p); + var r = Kernel32.INSTANCE.FindFirstFile("\\\\.\\pipe\\*pageant." + AppSystemInfo.ofCurrent().getUser() + "*", p); if (r == WinBase.INVALID_HANDLE_VALUE) { throw ErrorEventFactory.expected(new IllegalStateException("Pageant is not running")); } From 00079b3f89f9ee4a3b6bb1e235af4c214aef4fe3 Mon Sep 17 00:00:00 2001 From: crschnick Date: Sat, 14 Feb 2026 10:49:49 +0000 Subject: [PATCH 28/42] Fix dock pos update after reshow --- .../io/xpipe/app/terminal/TerminalDockHubComp.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubComp.java b/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubComp.java index 885d50e7b..f416f7e8e 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubComp.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubComp.java @@ -80,7 +80,11 @@ public class TerminalDockHubComp extends SimpleRegionBuilder { var show = new EventHandler() { @Override public void handle(WindowEvent event) { - update(stack); + GlobalTimer.delay(() -> { + Platform.runLater(() -> { + update(stack); + }); + }, Duration.ofMillis(100)); } }; var hide = new EventHandler() { @@ -135,6 +139,10 @@ public class TerminalDockHubComp extends SimpleRegionBuilder { var scene = region.getScene(); var windowRect = NativeWinWindowControl.MAIN_WINDOW.getBounds(); + if (windowRect.getX() == 0.0 && windowRect.getY() == 0.0 && windowRect.getW() == 0 && windowRect.getH() == 0) { + return; + } + var x = windowRect.getX() + ((bounds.getMinX() + p.getLeft() + scene.getX()) * sx); var y = windowRect.getY() + ((bounds.getMinY() + p.getTop() + scene.getY()) * sy); var w = (bounds.getWidth() * sx) - p.getRight() - p.getLeft(); From cfda407fabf1ef10390eb00c1f772ebb5d7804e5 Mon Sep 17 00:00:00 2001 From: crschnick Date: Sat, 14 Feb 2026 11:38:39 +0000 Subject: [PATCH 29/42] Small fixes --- .../java/io/xpipe/app/action/ActionShortcutComp.java | 9 +++++---- .../java/io/xpipe/app/terminal/TerminalDockView.java | 7 +++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/io/xpipe/app/action/ActionShortcutComp.java b/app/src/main/java/io/xpipe/app/action/ActionShortcutComp.java index 563b81e9f..41b4a0a9b 100644 --- a/app/src/main/java/io/xpipe/app/action/ActionShortcutComp.java +++ b/app/src/main/java/io/xpipe/app/action/ActionShortcutComp.java @@ -7,6 +7,7 @@ import io.xpipe.app.comp.base.ButtonComp; import io.xpipe.app.comp.base.InputGroupComp; import io.xpipe.app.comp.base.TextFieldComp; import io.xpipe.app.core.AppI18n; +import io.xpipe.app.core.AppProperties; import io.xpipe.app.platform.BindingsHelper; import io.xpipe.app.platform.ClipboardHelper; import io.xpipe.app.platform.OptionsBuilder; @@ -46,8 +47,6 @@ public class ActionShortcutComp extends SimpleRegionBuilder { AppDistributionType.get().toTranslatedString().getValue())); options.addComp(createUrlComp()).disable(!AppDistributionType.get().isSupportsUrls()); options.nameAndDescription("actionApiCall").addComp(createApiComp()); - // options.nameAndDescription("actionMacro") - // .addComp(createMacroComp()); return options.build(); } @@ -83,8 +82,10 @@ public class ActionShortcutComp extends SimpleRegionBuilder { }); var copyButton = new ButtonComp(null, new FontIcon("mdi2f-file-move-outline"), () -> { ThreadHelper.runFailableAsync(() -> { - var file = - DesktopShortcuts.createOpen(name.getValue(), "open \"" + url.getValue() + "\"", null); + var file = DesktopShortcuts.createOpen( + name.getValue(), + "open \"" + url.getValue() + "\" -d \"" + AppProperties.get().getDataDir() + "\"", + null); DesktopHelper.browseFileInDirectory(file); }); }) diff --git a/app/src/main/java/io/xpipe/app/terminal/TerminalDockView.java b/app/src/main/java/io/xpipe/app/terminal/TerminalDockView.java index 549dfddd1..6a6c07e43 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalDockView.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalDockView.java @@ -1,6 +1,7 @@ package io.xpipe.app.terminal; import io.xpipe.app.issue.TrackEvent; +import io.xpipe.app.platform.NativeWinWindowControl; import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.util.GlobalTimer; import io.xpipe.app.util.Rect; @@ -71,12 +72,18 @@ public class TerminalDockView { public synchronized void trackTerminal(ControllableTerminalSession terminal, boolean dock) { if (viewActive && dock && viewBounds != null) { + // Bring main window to foreground since initial launch + NativeWinWindowControl.MAIN_WINDOW.activate(); + // The window might be minimized // We always want to show the terminal though terminal.show(); terminal.own(); + // Bring terminal window in front of main window + terminal.focus(); + terminal.updatePosition(windowBoundsFunction.apply(viewBounds)); updateCustomBounds(); } From 6a4c0ea86414dfc5f3629d826904feba78fd079e Mon Sep 17 00:00:00 2001 From: crschnick Date: Sat, 14 Feb 2026 12:24:38 +0000 Subject: [PATCH 30/42] Fix dock focus --- .../main/java/io/xpipe/app/terminal/TerminalDockView.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/io/xpipe/app/terminal/TerminalDockView.java b/app/src/main/java/io/xpipe/app/terminal/TerminalDockView.java index 6a6c07e43..e2904aeb8 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalDockView.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalDockView.java @@ -72,6 +72,8 @@ public class TerminalDockView { public synchronized void trackTerminal(ControllableTerminalSession terminal, boolean dock) { if (viewActive && dock && viewBounds != null) { + terminal.own(); + // Bring main window to foreground since initial launch NativeWinWindowControl.MAIN_WINDOW.activate(); @@ -79,11 +81,6 @@ public class TerminalDockView { // We always want to show the terminal though terminal.show(); - terminal.own(); - - // Bring terminal window in front of main window - terminal.focus(); - terminal.updatePosition(windowBoundsFunction.apply(viewBounds)); updateCustomBounds(); } From b8e756f3cd7752ee019c7fb17b1d2dc7190b39f9 Mon Sep 17 00:00:00 2001 From: crschnick Date: Sun, 15 Feb 2026 08:53:06 +0000 Subject: [PATCH 31/42] Rework undocking --- .../app/platform/NativeWinWindowControl.java | 16 ++++++++++++++++ .../terminal/ControllableTerminalSession.java | 6 ++---- .../app/terminal/TerminalDockHubManager.java | 6 +----- .../io/xpipe/app/terminal/TerminalDockView.java | 2 +- .../java/io/xpipe/app/terminal/TerminalView.java | 11 +++++++---- .../app/terminal/WindowsTerminalSession.java | 15 ++++++++------- 6 files changed, 35 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/io/xpipe/app/platform/NativeWinWindowControl.java b/app/src/main/java/io/xpipe/app/platform/NativeWinWindowControl.java index 14ace9539..46f15366a 100644 --- a/app/src/main/java/io/xpipe/app/platform/NativeWinWindowControl.java +++ b/app/src/main/java/io/xpipe/app/platform/NativeWinWindowControl.java @@ -76,6 +76,22 @@ public class NativeWinWindowControl { var style = User32.INSTANCE.GetWindowLong(windowHandle, User32.GWL_STYLE); var mod = style & ~(User32.WS_CAPTION | User32.WS_THICKFRAME | User32.WS_MAXIMIZEBOX); User32.INSTANCE.SetWindowLong(windowHandle, User32.GWL_STYLE, mod); + + var rect = getBounds(); + User32.INSTANCE.SetWindowPos(windowHandle, null, rect.getX(), rect.getY(), rect.getW() + 1, rect.getH(), + User32.SWP_NOACTIVATE | User32.SWP_NOMOVE | User32.SWP_NOZORDER); + } + + public void restoreBorders() { + var style = User32.INSTANCE.GetWindowLong(windowHandle, User32.GWL_STYLE); + var mod = style | User32.WS_CAPTION | User32.WS_THICKFRAME | User32.WS_MAXIMIZEBOX; + User32.INSTANCE.SetWindowLong(windowHandle, User32.GWL_STYLE, mod); + + var rect = getBounds(); + User32.INSTANCE.SetWindowPos(windowHandle, null, rect.getX(), rect.getY(), rect.getW() + 1, rect.getH(), + User32.SWP_NOACTIVATE | User32.SWP_NOMOVE | User32.SWP_NOZORDER); + User32.INSTANCE.SetWindowPos(windowHandle, null, rect.getX(), rect.getY(), rect.getW(), rect.getH(), + User32.SWP_NOACTIVATE | User32.SWP_NOMOVE | User32.SWP_NOZORDER); } public void takeOwnership(WinDef.HWND owner) { diff --git a/app/src/main/java/io/xpipe/app/terminal/ControllableTerminalSession.java b/app/src/main/java/io/xpipe/app/terminal/ControllableTerminalSession.java index d8670bbeb..00164afc0 100644 --- a/app/src/main/java/io/xpipe/app/terminal/ControllableTerminalSession.java +++ b/app/src/main/java/io/xpipe/app/terminal/ControllableTerminalSession.java @@ -10,16 +10,14 @@ public abstract class ControllableTerminalSession extends TerminalView.TerminalS protected Rect lastBounds; protected boolean customBounds; - protected ControllableTerminalSession(ProcessHandle terminalProcess) { - super(terminalProcess); + protected ControllableTerminalSession(ProcessHandle terminalProcess, ExternalTerminalType terminalType) { + super(terminalProcess, terminalType); } public abstract void own(); public abstract void disown(); - public abstract void removeBorders(); - public abstract void show(); public abstract void minimize(); diff --git a/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubManager.java b/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubManager.java index 011c5a36f..f85d76835 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubManager.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubManager.java @@ -186,15 +186,11 @@ public class TerminalDockHubManager { return; } - var term = AppPrefs.get().terminalType().getValue(); + var term = controllable.get().getTerminalType(); if (term instanceof TrackableTerminalType t) { if (t.getDockMode() == TerminalDockMode.UNSUPPORTED) { return; } - - if (t.getDockMode() == TerminalDockMode.BORDERLESS) { - controllable.get().removeBorders(); - } } var dock = !detached.get(); diff --git a/app/src/main/java/io/xpipe/app/terminal/TerminalDockView.java b/app/src/main/java/io/xpipe/app/terminal/TerminalDockView.java index e2904aeb8..be347d4c7 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalDockView.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalDockView.java @@ -64,7 +64,7 @@ public class TerminalDockView { } } - if (terminal.isCustomBounds()) { + if (!wasCustom && terminal.isCustomBounds()) { terminal.disown(); } }); diff --git a/app/src/main/java/io/xpipe/app/terminal/TerminalView.java b/app/src/main/java/io/xpipe/app/terminal/TerminalView.java index 5e7ea9209..54ba1ad6f 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalView.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalView.java @@ -144,9 +144,10 @@ public class TerminalView { } private Optional createTerminalSession(ProcessHandle terminalProcess) { + var type = AppPrefs.get().terminalType().getValue(); return switch (OsType.ofLocal()) { - case OsType.Linux ignored -> Optional.of(new TerminalSession(terminalProcess)); - case OsType.MacOs ignored -> Optional.of(new TerminalSession(terminalProcess)); + case OsType.Linux ignored -> Optional.of(new TerminalSession(terminalProcess, type)); + case OsType.MacOs ignored -> Optional.of(new TerminalSession(terminalProcess, type)); case OsType.Windows ignored -> { var controls = NativeWinWindowControl.byPid(terminalProcess.pid()); if (controls.isEmpty()) { @@ -158,7 +159,7 @@ public class TerminalView { } } - yield Optional.of(new WindowsTerminalSession(terminalProcess, controls.getFirst())); + yield Optional.of(new WindowsTerminalSession(terminalProcess, type, controls.getFirst())); } }; } @@ -239,9 +240,11 @@ public class TerminalView { public static class TerminalSession { protected final ProcessHandle terminalProcess; + protected final ExternalTerminalType terminalType; - protected TerminalSession(ProcessHandle terminalProcess) { + protected TerminalSession(ProcessHandle terminalProcess, ExternalTerminalType terminalType) { this.terminalProcess = terminalProcess; + this.terminalType = terminalType; } public boolean isRunning() { diff --git a/app/src/main/java/io/xpipe/app/terminal/WindowsTerminalSession.java b/app/src/main/java/io/xpipe/app/terminal/WindowsTerminalSession.java index 5f4596f7a..2c08b3af9 100644 --- a/app/src/main/java/io/xpipe/app/terminal/WindowsTerminalSession.java +++ b/app/src/main/java/io/xpipe/app/terminal/WindowsTerminalSession.java @@ -17,8 +17,8 @@ public final class WindowsTerminalSession extends ControllableTerminalSession { NativeWinWindowControl control; - public WindowsTerminalSession(ProcessHandle terminal, NativeWinWindowControl control) { - super(terminal); + public WindowsTerminalSession(ProcessHandle terminal, ExternalTerminalType terminalType, NativeWinWindowControl control) { + super(terminal, terminalType); this.control = control; } @@ -46,16 +46,17 @@ public final class WindowsTerminalSession extends ControllableTerminalSession { @Override public void own() { control.takeOwnership(NativeWinWindowControl.MAIN_WINDOW.getWindowHandle()); + if (terminalType != null && terminalType instanceof TrackableTerminalType t && t.getDockMode() == TerminalDockMode.BORDERLESS) { + control.removeBorders(); + } } @Override public void disown() { control.releaseOwnership(); - } - - @Override - public void removeBorders() { - control.removeBorders(); + if (terminalType != null && terminalType instanceof TrackableTerminalType t && t.getDockMode() == TerminalDockMode.BORDERLESS) { + control.restoreBorders(); + } } @Override From 339f05f23808e40806249087ec44f9bbb222e812 Mon Sep 17 00:00:00 2001 From: crschnick Date: Sun, 15 Feb 2026 10:03:40 +0000 Subject: [PATCH 32/42] Various docking fixes [stage] --- .../file/BrowserTerminalDockTabModel.java | 12 ++++++ .../app/platform/NativeWinWindowControl.java | 8 +++- .../app/terminal/TerminalDockBrowserComp.java | 40 +++++++++++++++++-- .../app/terminal/TerminalDockHubComp.java | 15 ++++++- .../xpipe/app/terminal/TerminalDockView.java | 2 +- .../io/xpipe/app/resources/style/browser.css | 2 +- version | 2 +- 7 files changed, 70 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserTerminalDockTabModel.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserTerminalDockTabModel.java index 5ee01ed93..d7db04dd3 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserTerminalDockTabModel.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserTerminalDockTabModel.java @@ -14,6 +14,7 @@ import io.xpipe.app.terminal.TerminalDockBrowserComp; import io.xpipe.app.terminal.TerminalDockView; import io.xpipe.app.terminal.TerminalView; import io.xpipe.app.terminal.WindowsTerminalType; +import io.xpipe.app.util.GlobalTimer; import io.xpipe.app.util.ThreadHelper; import javafx.application.Platform; @@ -25,6 +26,7 @@ import javafx.beans.value.ObservableValue; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; +import java.time.Duration; import java.util.UUID; import java.util.function.UnaryOperator; @@ -36,6 +38,7 @@ public final class BrowserTerminalDockTabModel extends BrowserSessionTab { private final BooleanProperty opened = new SimpleBooleanProperty(); private TerminalView.Listener listener; private ObservableBooleanValue viewActive; + private boolean closed; public BrowserTerminalDockTabModel( BrowserAbstractSessionModel browserModel, @@ -147,6 +150,14 @@ public final class BrowserTerminalDockTabModel extends BrowserSessionTab { } } }); + + GlobalTimer.scheduleUntil(Duration.ofMillis(300), false, () -> { + if (viewActive.get()) { + dockModel.clearDeadTerminals(); + dockModel.updateCustomBounds(); + } + return closed; + }); } @Override @@ -155,6 +166,7 @@ public final class BrowserTerminalDockTabModel extends BrowserSessionTab { TerminalView.get().removeListener(listener); } dockModel.onClose(); + closed = true; } @Override diff --git a/app/src/main/java/io/xpipe/app/platform/NativeWinWindowControl.java b/app/src/main/java/io/xpipe/app/platform/NativeWinWindowControl.java index 46f15366a..afb8c7a81 100644 --- a/app/src/main/java/io/xpipe/app/platform/NativeWinWindowControl.java +++ b/app/src/main/java/io/xpipe/app/platform/NativeWinWindowControl.java @@ -73,21 +73,25 @@ public class NativeWinWindowControl { } public void removeBorders() { + var rect = getBounds(); + var style = User32.INSTANCE.GetWindowLong(windowHandle, User32.GWL_STYLE); var mod = style & ~(User32.WS_CAPTION | User32.WS_THICKFRAME | User32.WS_MAXIMIZEBOX); User32.INSTANCE.SetWindowLong(windowHandle, User32.GWL_STYLE, mod); - var rect = getBounds(); User32.INSTANCE.SetWindowPos(windowHandle, null, rect.getX(), rect.getY(), rect.getW() + 1, rect.getH(), User32.SWP_NOACTIVATE | User32.SWP_NOMOVE | User32.SWP_NOZORDER); + User32.INSTANCE.SetWindowPos(windowHandle, null, rect.getX(), rect.getY(), rect.getW(), rect.getH(), + User32.SWP_NOACTIVATE | User32.SWP_NOMOVE | User32.SWP_NOZORDER); } public void restoreBorders() { + var rect = getBounds(); + var style = User32.INSTANCE.GetWindowLong(windowHandle, User32.GWL_STYLE); var mod = style | User32.WS_CAPTION | User32.WS_THICKFRAME | User32.WS_MAXIMIZEBOX; User32.INSTANCE.SetWindowLong(windowHandle, User32.GWL_STYLE, mod); - var rect = getBounds(); User32.INSTANCE.SetWindowPos(windowHandle, null, rect.getX(), rect.getY(), rect.getW() + 1, rect.getH(), User32.SWP_NOACTIVATE | User32.SWP_NOMOVE | User32.SWP_NOZORDER); User32.INSTANCE.SetWindowPos(windowHandle, null, rect.getX(), rect.getY(), rect.getW(), rect.getH(), diff --git a/app/src/main/java/io/xpipe/app/terminal/TerminalDockBrowserComp.java b/app/src/main/java/io/xpipe/app/terminal/TerminalDockBrowserComp.java index 2211c87fd..6e628a62a 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalDockBrowserComp.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalDockBrowserComp.java @@ -10,6 +10,7 @@ import io.xpipe.app.platform.PlatformThread; import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.util.GlobalTimer; +import javafx.application.Platform; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableBooleanValue; @@ -108,7 +109,11 @@ public class TerminalDockBrowserComp extends SimpleRegionBuilder { var show = new EventHandler() { @Override public void handle(WindowEvent event) { - update(stack); + GlobalTimer.delay(() -> { + Platform.runLater(() -> { + update(stack); + }); + }, Duration.ofMillis(100)); } }; var hide = new EventHandler() { @@ -117,6 +122,16 @@ public class TerminalDockBrowserComp extends SimpleRegionBuilder { model.onClose(); } }; + var scale = new ChangeListener() { + @Override + public void changed(ObservableValue observable, Number oldValue, Number newValue) { + GlobalTimer.delay(() -> { + Platform.runLater(() -> { + update(stack); + }); + }, Duration.ofMillis(500)); + } + }; var parent = new AtomicReference(); stack.sceneProperty().subscribe(scene -> { @@ -128,6 +143,7 @@ public class TerminalDockBrowserComp extends SimpleRegionBuilder { s.iconifiedProperty().removeListener(iconified); s.removeEventFilter(WindowEvent.WINDOW_SHOWN, show); s.removeEventFilter(WindowEvent.WINDOW_HIDING, hide); + s.outputScaleXProperty().addListener(scale); if (parent.get() != null) { parent.get().boundsInParentProperty().removeListener(bounds); parent.set(null); @@ -140,6 +156,7 @@ public class TerminalDockBrowserComp extends SimpleRegionBuilder { s.iconifiedProperty().addListener(iconified); s.addEventFilter(WindowEvent.WINDOW_SHOWN, show); s.addEventFilter(WindowEvent.WINDOW_HIDING, hide); + s.outputScaleXProperty().removeListener(scale); // As in practice this node is wrapped in another stack pane // We have to listen to the parent bounds to actually receive bounds changes stack.getParent().boundsInParentProperty().addListener(bounds); @@ -150,7 +167,7 @@ public class TerminalDockBrowserComp extends SimpleRegionBuilder { } private void update(Region region) { - if (region.getScene() == null || region.getScene().getWindow() == null) { + if (region.getScene() == null || region.getScene().getWindow() == null || NativeWinWindowControl.MAIN_WINDOW == null) { return; } @@ -161,11 +178,26 @@ public class TerminalDockBrowserComp extends SimpleRegionBuilder { var scene = region.getScene(); var windowRect = NativeWinWindowControl.MAIN_WINDOW.getBounds(); - var x = windowRect.getX() + ((bounds.getMinX() + p.getLeft() + scene.getX()) * sx); - var y = windowRect.getY() + ((bounds.getMinY() + p.getTop() + scene.getY()) * sy); + if (windowRect.getX() == 0.0 && windowRect.getY() == 0.0 && windowRect.getW() == 0 && windowRect.getH() == 0) { + return; + } + + var xPadding = ((bounds.getMinX() + p.getLeft() + scene.getX()) * sx); + var yPadding = ((bounds.getMinY() + p.getTop() + scene.getY()) * sy); + var x = windowRect.getX() + xPadding; + var y = windowRect.getY() + yPadding; var w = (bounds.getWidth() * sx) - p.getRight() - p.getLeft(); var h = (bounds.getHeight() * sy) - p.getBottom() - p.getTop(); + if (x + w > windowRect.getX() + windowRect.getW()) { + x = windowRect.getX() + 10; + w = windowRect.getW() - 20; + } + if (y + h > windowRect.getY() + windowRect.getH()) { + y = windowRect.getY() + 10; + h = windowRect.getH() - 20; + } + model.resizeView( (int) Math.round(x), (int) Math.round(y), diff --git a/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubComp.java b/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubComp.java index f416f7e8e..5ef0e5870 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubComp.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubComp.java @@ -143,11 +143,22 @@ public class TerminalDockHubComp extends SimpleRegionBuilder { return; } - var x = windowRect.getX() + ((bounds.getMinX() + p.getLeft() + scene.getX()) * sx); - var y = windowRect.getY() + ((bounds.getMinY() + p.getTop() + scene.getY()) * sy); + var xPadding = ((bounds.getMinX() + p.getLeft() + scene.getX()) * sx); + var yPadding = ((bounds.getMinY() + p.getTop() + scene.getY()) * sy); + var x = windowRect.getX() + xPadding; + var y = windowRect.getY() + yPadding; var w = (bounds.getWidth() * sx) - p.getRight() - p.getLeft(); var h = (bounds.getHeight() * sy) - p.getBottom() - p.getTop(); + if (x + w > windowRect.getX() + windowRect.getW()) { + x = windowRect.getX() + 10; + w = windowRect.getW() - 20; + } + if (y + h > windowRect.getY() + windowRect.getH()) { + y = windowRect.getY() + 10; + h = windowRect.getH() - 20; + } + model.resizeView( (int) Math.round(x), (int) Math.round(y), diff --git a/app/src/main/java/io/xpipe/app/terminal/TerminalDockView.java b/app/src/main/java/io/xpipe/app/terminal/TerminalDockView.java index be347d4c7..bc41dd3fe 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalDockView.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalDockView.java @@ -228,7 +228,7 @@ public class TerminalDockView { }); } - public void resizeView(int x, int y, int w, int h) { + public synchronized void resizeView(int x, int y, int w, int h) { if (w < 100 || h < 100) { return; } diff --git a/app/src/main/resources/io/xpipe/app/resources/style/browser.css b/app/src/main/resources/io/xpipe/app/resources/style/browser.css index 9edc5a91e..e7649fea6 100644 --- a/app/src/main/resources/io/xpipe/app/resources/style/browser.css +++ b/app/src/main/resources/io/xpipe/app/resources/style/browser.css @@ -77,7 +77,7 @@ } .browser .terminal-dock-comp { - -fx-padding: 7 0 7 3; + -fx-padding: 7 7 7 3; } .browser .terminal-dock-comp:empty { diff --git a/version b/version index 27040fe00..2a39e3cf9 100644 --- a/version +++ b/version @@ -1 +1 @@ -21.3-3 +21.3-4 From 14e5c6e86a6c41b67ca56c02e8a57f34b1a78657 Mon Sep 17 00:00:00 2001 From: crschnick Date: Mon, 16 Feb 2026 13:28:25 +0000 Subject: [PATCH 33/42] Keeper fixes --- .../app/pwman/KeeperPasswordManager.java | 456 +++++++++++++++--- .../io/xpipe/app/util/AppJacksonModule.java | 2 + lang/strings/fixed_en.properties | 1 + lang/strings/translations_da.properties | 7 +- lang/strings/translations_de.properties | 7 +- lang/strings/translations_en.properties | 8 +- lang/strings/translations_es.properties | 7 +- lang/strings/translations_fr.properties | 7 +- lang/strings/translations_id.properties | 7 +- lang/strings/translations_it.properties | 7 +- lang/strings/translations_ja.properties | 7 +- lang/strings/translations_ko.properties | 7 +- lang/strings/translations_nl.properties | 7 +- lang/strings/translations_pl.properties | 7 +- lang/strings/translations_pt.properties | 7 +- lang/strings/translations_ru.properties | 7 +- lang/strings/translations_sv.properties | 7 +- lang/strings/translations_tr.properties | 7 +- lang/strings/translations_vi.properties | 7 +- lang/strings/translations_zh-Hans.properties | 7 +- lang/strings/translations_zh-Hant.properties | 7 +- 21 files changed, 472 insertions(+), 114 deletions(-) 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 814dba92d..22305f768 100644 --- a/app/src/main/java/io/xpipe/app/pwman/KeeperPasswordManager.java +++ b/app/src/main/java/io/xpipe/app/pwman/KeeperPasswordManager.java @@ -1,9 +1,11 @@ package io.xpipe.app.pwman; +import com.fasterxml.jackson.annotation.JsonTypeInfo; import io.xpipe.app.core.AppI18n; import io.xpipe.app.ext.ProcessControlProvider; import io.xpipe.app.issue.ErrorEventFactory; import io.xpipe.app.platform.OptionsBuilder; +import io.xpipe.app.platform.OptionsChoiceBuilder; import io.xpipe.app.process.*; import io.xpipe.app.secret.SecretManager; import io.xpipe.app.secret.SecretPromptStrategy; @@ -13,7 +15,7 @@ import io.xpipe.app.util.AskpassAlert; import io.xpipe.core.*; import javafx.beans.property.Property; -import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -23,12 +25,16 @@ import com.fasterxml.jackson.databind.JsonNode; import lombok.Builder; import lombok.Getter; import lombok.ToString; +import lombok.Value; import lombok.extern.jackson.Jacksonized; import java.time.Duration; +import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.UUID; +import java.util.regex.Pattern; +import java.util.stream.Collectors; @JsonTypeName("keeper") @Getter @@ -37,11 +43,347 @@ import java.util.UUID; @Jacksonized public class KeeperPasswordManager implements PasswordManager { + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") + public interface KeeperAuth { + + static List> getClasses() { + var l = new ArrayList>(); + l.add(None.class); + l.add(Sms.class); + l.add(AuthenticatorApp.class); + l.add(SecurityKey.class); + l.add(Other.class); + return l; + } + + + default List getTotpDurationValues() { + var values = List.of("login", "12_hours", "24_hours", "30_days", "forever"); + return values; + } + + String constructKeeperInput(KeeperPasswordManager passwordManager) throws Exception; + + Duration getCacheDuration(); + + Duration getCommandTimeout(); + + String cleanMessage(String output); + + @JsonTypeName("sms") + @Value + @Jacksonized + @Builder + class Sms implements KeeperAuth { + + @SuppressWarnings("unused") + public static OptionsBuilder createOptions(Property p) { + var duration = new SimpleStringProperty(p.getValue().getTotpDuration()); + return new OptionsBuilder() + .name("keeperTotpDuration") + .description(AppI18n.observable( + "keeperTotpDurationDescription", "login | 12_hours | 24_hours | 30_days | forever")) + .addString(duration) + .bind( + () -> { + return Sms.builder() + .totpDuration(duration.get()) + .build(); + }, + p); + } + + String totpDuration; + + private int getTotpDurationIndex() { + var values = getTotpDurationValues(); + var index = totpDuration != null ? values.indexOf(totpDuration) : -1; + return index; + } + + private void sendInitialSms() throws Exception { + var sc = getOrStartShell(); + var b = CommandBuilder.of() + .add(getExecutable(), "get") + .addLiteral("test"); + var file = sc.getSystemTemporaryDirectory().join("keeper" + Math.abs(new Random().nextInt()) + ".txt"); + var input = """ + + 1 + - + q + """; + sc.view().writeTextFile(file, input); + + var fullCommand = CommandBuilder.of() + .add(sc.getShellDialect() == ShellDialects.CMD ? "type" : "cat") + .addFile(file) + .add("|") + .add(b); + sc.command(fullCommand).sensitive().execute(); + } + + @Override + public String constructKeeperInput(KeeperPasswordManager passwordManager) throws Exception { + sendInitialSms(); + + var index = getTotpDurationIndex(); + if (passwordManager.isHasCompletedRequestInSession() && index > 0) { + var input = """ + + 1 + + """; + return input; + } else { + var totp = AskpassAlert.queryRaw("Enter Keeper Commander SMS Code", null, true); + if (totp.getState() != SecretQueryState.NORMAL) { + return null; + } + + var input = """ + + 1%s + %s + + """.formatted( + index != -1 ? "\n" + getTotpDurationValues().get(index) : "", + totp.getSecret().getSecretValue()); + return input; + } + } + + @Override + public Duration getCacheDuration() { + return getTotpDurationIndex() < 1 ? Duration.ofDays(1) : Duration.ofSeconds(30); + } + + @Override + public Duration getCommandTimeout() { + return Duration.ofSeconds(25); + } + + @Override + public String cleanMessage(String output) { + return output + .replaceFirst(""" + Select your 2FA method: + 1. Send SMS Code.+ + q. Cancel login + """, "") + .replace(" Invalid entry, additional factors of authentication shown may be configured if not currently enabled.", "") + .replace(""" + 2FA Code Duration: Require Every Login. + To change duration: 2fa_duration=login|12_hours|24_hours|30_days|forever + """, ""); + } + } + + @JsonTypeName("authenticatorApp") + @Value + @Jacksonized + @Builder + class AuthenticatorApp implements KeeperAuth { + + @SuppressWarnings("unused") + public static OptionsBuilder createOptions(Property p) { + var duration = new SimpleStringProperty(p.getValue().getTotpDuration()); + return new OptionsBuilder() + .name("keeperTotpDuration") + .description(AppI18n.observable( + "keeperTotpDurationDescription", "login | 12_hours | 24_hours | 30_days | forever")) + .addString(duration) + .bind( + () -> { + return AuthenticatorApp.builder() + .totpDuration(duration.get()) + .build(); + }, + p); + } + + String totpDuration; + + private int getTotpDurationIndex() { + var values = getTotpDurationValues(); + var index = totpDuration != null ? values.indexOf(totpDuration) : -1; + return index; + } + + @Override + public String constructKeeperInput(KeeperPasswordManager passwordManager) { + var index = getTotpDurationIndex(); + if (passwordManager.isHasCompletedRequestInSession() && index > 0) { + var input = """ + + 1 + + """; + return input; + } else { + var totp = AskpassAlert.queryRaw("Enter Keeper 2FA Code", null, true); + if (totp.getState() != SecretQueryState.NORMAL) { + return null; + } + + var input = """ + + 1%s + %s + + """.formatted( + index != -1 ? "\n" + getTotpDurationValues().get(index) : "", + totp.getSecret().getSecretValue()); + return input; + } + } + + @Override + public Duration getCacheDuration() { + return getTotpDurationIndex() < 1 ? Duration.ofDays(1) : Duration.ofSeconds(30); + } + + @Override + public Duration getCommandTimeout() { + return Duration.ofSeconds(25); + } + + @Override + public String cleanMessage(String output) { + return output.replace(""" + Select your 2FA method: + 1. TOTP (Google and Microsoft Authenticator) \s + q. Cancel login + """, "") + .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 + """, ""); + } + + } + + @JsonTypeName("securityKey") + @Value + @Jacksonized + @Builder + class SecurityKey implements KeeperAuth { + + @Override + public String constructKeeperInput(KeeperPasswordManager passwordManager) { + var input = """ + + 1 + + """; + return input; + } + + @Override + public Duration getCacheDuration() { + return Duration.ofDays(1); + } + + @Override + public Duration getCommandTimeout() { + return null; + } + + @Override + public String cleanMessage(String output) { + return output.replace(""" + Select your 2FA method: + 1. WebAuthN (FIDO2 Security Key) \s + q. Cancel login + """, "") + .replace(" Invalid entry, additional factors of authentication shown may be configured if not currently enabled.", ""); + } + } + + + @JsonTypeName("other") + @Value + @Jacksonized + @Builder + class Other implements KeeperAuth { + + @SuppressWarnings("unused") + public static String getOptionsNameKey() { + return "keeperOtherAuth"; + } + + @Override + public Duration getCommandTimeout() { + return null; + } + + @Override + public String cleanMessage(String output) { + return output; + } + + @Override + public String constructKeeperInput(KeeperPasswordManager passwordManager) { + var input = """ + + 1 + + """; + return input; + } + + @Override + public Duration getCacheDuration() { + return Duration.ofDays(1); + } + } + + @JsonTypeName("none") + @Value + @Jacksonized + @Builder + class None implements KeeperAuth { + + @Override + public Duration getCommandTimeout() { + return Duration.ofSeconds(25); + } + + @Override + public String cleanMessage(String output) { + return output; + } + + @Override + public String constructKeeperInput(KeeperPasswordManager passwordManager) { + var input = """ + + 1 + + """; + return input; + } + + @Override + public Duration getCacheDuration() { + return Duration.ofSeconds(30); + } + } + } + private static final UUID KEEPER_PASSWORD_ID = UUID.randomUUID(); private static ShellControl SHELL; - private final Boolean mfa; - private final String totpDuration; - + private final KeeperAuth twoFactorAuth; @JsonIgnore private boolean hasCompletedRequestInSession; @@ -53,28 +395,27 @@ public class KeeperPasswordManager implements PasswordManager { return SHELL; } - private String getExecutable(ShellControl sc) { + private static String getExecutable() { return OsType.ofLocal() == OsType.WINDOWS ? "keeper-commander" : "keeper"; } @SuppressWarnings("unused") public static OptionsBuilder createOptions(Property p) { - var mfa = new SimpleBooleanProperty( - p.getValue().getMfa() != null ? p.getValue().getMfa() : false); - var duration = new SimpleStringProperty(p.getValue().getTotpDuration()); + var mfa = new SimpleObjectProperty<>(p.getValue().getTwoFactorAuth() != null ? p.getValue().getTwoFactorAuth() : new KeeperAuth.None()); + + var choice = OptionsChoiceBuilder.builder() + .allowNull(false) + .available(KeeperAuth.getClasses()) + .property(mfa) + .build(); + return new OptionsBuilder() - .nameAndDescription("keeperUseMfa") - .addToggle(mfa) - .name("keeperTotpDuration") - .description(AppI18n.observable( - "keeperTotpDurationDescription", "login | 12_hours | 24_hours | 30_days | forever")) - .addString(duration) - .hide(mfa.not()) + .nameAndDescription("keeper2fa") + .sub(choice.build(), mfa) .bind( () -> { return KeeperPasswordManager.builder() - .mfa(mfa.get()) - .totpDuration(duration.get()) + .twoFactorAuth(mfa.get()) .build(); }, p); @@ -100,7 +441,7 @@ public class KeeperPasswordManager implements PasswordManager { 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"); + getExecutable() + " login"); TerminalLaunch.builder() .title("Keeper login") .localScript(script) @@ -128,41 +469,19 @@ public class KeeperPasswordManager implements PasswordManager { } var b = CommandBuilder.of() - .add(getExecutable(sc), "get") + .add(getExecutable(), "get") .addLiteral(key) .add("--format", "json", "--unmask") .add("--password") .addLiteral(r.getSecretValue()); FilePath file = sc.getSystemTemporaryDirectory().join("keeper" + Math.abs(new Random().nextInt()) + ".txt"); - if (mfa != null && mfa) { - var index = getTotpDurationIndex(); - if (hasCompletedRequestInSession && index > 0) { - var input = """ - 1 - - """; - sc.view().writeTextFile(file, input); - } else { - var totp = AskpassAlert.queryRaw("Enter Keeper 2FA Code", null, true); - if (totp.getState() != SecretQueryState.NORMAL) { - return null; - } - - var input = """ - - 1%s - %s - - """.formatted( - index != -1 ? "\n" + getTotpDurationValues().get(index) : "", - totp.getSecret().getSecretValue()); - sc.view().writeTextFile(file, input); - } - } else { - var input = "\n"; - sc.view().writeTextFile(file, input); + var effectiveTwoFactor = twoFactorAuth != null ? twoFactorAuth : new KeeperAuth.None(); + var input = effectiveTwoFactor.constructKeeperInput(this); + if (input == null) { + return null; } + sc.view().writeTextFile(file, input); var fullB = CommandBuilder.of() .add(sc.getShellDialect() == ShellDialects.CMD ? "type" : "cat") @@ -171,7 +490,11 @@ public class KeeperPasswordManager implements PasswordManager { .add(b); var queryCommand = sc.command(fullB); queryCommand.sensitive(); - queryCommand.killOnTimeout(CountDown.of().start(25_000)); + + if (effectiveTwoFactor.getCommandTimeout() != null) { + var timeout = effectiveTwoFactor.getCommandTimeout().toMillis(); + queryCommand.killOnTimeout(CountDown.of().start(timeout)); + } var result = queryCommand.readStdoutAndStderr(); var exitCode = queryCommand.getExitCode(); @@ -179,26 +502,11 @@ public class KeeperPasswordManager implements PasswordManager { sc.view().deleteFileIfPossible(file); var out = result[0] - .replace("\r\n", "\n") - .replace(""" - Select your 2FA method: - 1. TOTP (Google and Microsoft Authenticator) \s - q. Cancel login - """, "") - .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:", "") + .replace("\r\n", "\n"); + out = effectiveTwoFactor.cleanMessage(out); + out = out.replace("Selection:", "") .strip(); + var err = result[1] .replace("\r\n", "\n") .replace("EOF when reading a line", "") @@ -211,6 +519,8 @@ public class KeeperPasswordManager implements PasswordManager { } var outPrefix = jsonStart <= 0 ? out : out.substring(0, jsonStart); + outPrefix = outPrefix.lines().filter(s -> !s.isBlank()).map(s -> s.strip()).collect(Collectors.joining("\n")); + var outJson = jsonStart <= 0 ? (jsonEnd != -1 ? out.substring(0, jsonEnd) : out) : (jsonEnd != -1 ? out.substring(jsonStart, jsonEnd) : out.substring(jsonStart)); @@ -297,17 +607,6 @@ public class KeeperPasswordManager implements PasswordManager { } } - private List getTotpDurationValues() { - var values = List.of("login", "12_hours", "24_hours", "30_days", "forever"); - return values; - } - - private int getTotpDurationIndex() { - var values = getTotpDurationValues(); - var index = totpDuration != null ? values.indexOf(totpDuration) : -1; - return index; - } - @Override public String getKeyPlaceholder() { return "Record UID"; @@ -320,6 +619,7 @@ public class KeeperPasswordManager implements PasswordManager { @Override public Duration getCacheDuration() { - return (mfa != null && mfa && getTotpDurationIndex() < 1) ? Duration.ofDays(10) : Duration.ofSeconds(30); + var effectiveTwoFactor = twoFactorAuth != null ? twoFactorAuth : new KeeperAuth.None(); + return effectiveTwoFactor.getCacheDuration(); } } 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 7748ea455..094be3c7b 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.process.ShellDialects; import io.xpipe.app.process.ShellScript; import io.xpipe.app.pwman.KeePassXcAssociationKey; import io.xpipe.app.pwman.KeePassXcPasswordManager; +import io.xpipe.app.pwman.KeeperPasswordManager; import io.xpipe.app.pwman.PasswordManager; import io.xpipe.app.rdp.ExternalRdpClient; import io.xpipe.app.secret.*; @@ -87,6 +88,7 @@ public class AppJacksonModule extends SimpleModule { context.registerSubtypes(ExternalSpiceClient.getClasses()); context.registerSubtypes(SecretRetrievalStrategy.getClasses()); context.registerSubtypes(DataStorageGroupStrategy.getClasses()); + context.registerSubtypes(KeeperPasswordManager.KeeperAuth.getClasses()); super.setupModule(context); } diff --git a/lang/strings/fixed_en.properties b/lang/strings/fixed_en.properties index 2ebde94df..184906291 100644 --- a/lang/strings/fixed_en.properties +++ b/lang/strings/fixed_en.properties @@ -160,3 +160,4 @@ yakuake=Yakuake remoteViewer=Virt viewer cosmicEdit=Cosmic Editor neovim=Neovim +sms=SMS diff --git a/lang/strings/translations_da.properties b/lang/strings/translations_da.properties index 573a565c1..948bb7a7c 100644 --- a/lang/strings/translations_da.properties +++ b/lang/strings/translations_da.properties @@ -1877,10 +1877,11 @@ testingConnection=Test af forbindelse ... openManagementConsole=Åben administrationskonsol openLxcTerminal=Åbn LXC-terminalen openContainerConsole=Åbn seriel konsol -keeperUseMfa=Brug 2FA-godkendelsesapp -keeperUseMfaDescription=Aktivér dette, hvis din Keeper-konto kræver en 2FA TOTP for at få adgang til adgangskoder. +keeper2fa=2FA-metode +keeper2faDescription=Den primære to-faktor-godkendelsesmetode, der er konfigureret til din konto. Aktivér denne, hvis din Keeper-konto kræver to-faktor-autentificering for at få adgang til adgangskoder. keeperTotpDuration=Varighed af brugerdefineret 2FA-kode keeperTotpDurationDescription=Tilsidesæt standardvarigheden for, hvor længe en 2FA-kode er gyldig. Gælder kun, hvis din organisations politik tillader ændring af varigheden.\n\nMulige værdier er: $VALUES$ +keeperOtherAuth=Andet (RSA SecurID, Duo Security, Keeper DNA osv.) extractReusableIdentities=Udtræk af genanvendelige identiteter identitiesAdded=Identiteter tilføjet syncMode=Synkroniseringstilstand @@ -1947,3 +1948,5 @@ showStatus=Vis status showAllPorts=Vis alle porte activeLicense=Licens activeLicenseDescription=Aktiver en XPipe-licensnøgle +authenticatorApp=Autentificerings-app +securityKey=Sikkerhedsnøgle diff --git a/lang/strings/translations_de.properties b/lang/strings/translations_de.properties index 7c10377e4..84a718d5a 100644 --- a/lang/strings/translations_de.properties +++ b/lang/strings/translations_de.properties @@ -1872,10 +1872,11 @@ testingConnection=Verbindung testen ... openManagementConsole=Verwaltungskonsole öffnen openLxcTerminal=LXC-Terminal öffnen openContainerConsole=Serielle Konsole öffnen -keeperUseMfa=2FA-Authentifikator-App verwenden -keeperUseMfaDescription=Aktiviere dies, wenn dein Keeper-Konto ein 2FA TOTP erfordert, um auf Passwörter zuzugreifen. +keeper2fa=2FA-Methode +keeper2faDescription=Die primäre Zwei-Faktor-Authentifizierungsmethode, die für dein Konto konfiguriert ist. Aktiviere dies, wenn dein Keeper-Konto eine Zwei-Faktor-Authentifizierung für den Zugriff auf Passwörter erfordert. keeperTotpDuration=Dauer des benutzerdefinierten 2FA-Codes keeperTotpDurationDescription=Überschreibe die Standarddauer, wie lange ein 2FA-Code gültig ist. Gilt nur, wenn deine Unternehmensrichtlinie das Ändern der Dauer erlaubt.\n\nMögliche Werte sind: $VALUES$ +keeperOtherAuth=Andere (RSA SecurID, Duo Security, Keeper DNA, etc.) extractReusableIdentities=Wiederverwendbare Identitäten extrahieren identitiesAdded=Hinzugefügte Identitäten syncMode=Sync-Modus @@ -1942,3 +1943,5 @@ showStatus=Status anzeigen showAllPorts=Alle Ports anzeigen activeLicense=Lizenz activeLicenseDescription=Aktivieren eines XPipe-Lizenzschlüssels +authenticatorApp=Authenticator-App +securityKey=Sicherheitsschlüssel diff --git a/lang/strings/translations_en.properties b/lang/strings/translations_en.properties index 98596270d..4d322b602 100644 --- a/lang/strings/translations_en.properties +++ b/lang/strings/translations_en.properties @@ -1907,10 +1907,12 @@ testingConnection=Testing connection ... openManagementConsole=Open management console openLxcTerminal=Open LXC terminal openContainerConsole=Open serial console -keeperUseMfa=Use 2FA authenticator app -keeperUseMfaDescription=Enable this if your Keeper account requires an 2FA TOTP to access passwords. +keeper2fa=2FA method +keeper2faDescription=The primary two-factor authentication method that is configured for your account. Enable this if your Keeper account requires two-factor auth to access passwords. keeperTotpDuration=Custom 2FA code duration keeperTotpDurationDescription=Override the default duration on how long a 2FA code is valid. Only applies if your organization policy allows changing the duration.\n\nPossible values are: $VALUES$ +#context: Brand and product names +keeperOtherAuth=Other (RSA SecurID, Duo Security, Keeper DNA, etc.) extractReusableIdentities=Extract reusable identities identitiesAdded=Identities added syncMode=Sync mode @@ -1977,3 +1979,5 @@ showStatus=Show status showAllPorts=Show all ports activeLicense=License activeLicenseDescription=Activate an XPipe license key +authenticatorApp=Authenticator app +securityKey=Security key diff --git a/lang/strings/translations_es.properties b/lang/strings/translations_es.properties index f391c4179..e2ab03979 100644 --- a/lang/strings/translations_es.properties +++ b/lang/strings/translations_es.properties @@ -1836,10 +1836,11 @@ testingConnection=Probar la conexión ... openManagementConsole=Consola de gestión abierta openLxcTerminal=Abrir terminal LXC openContainerConsole=Abrir consola serie -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. +keeper2fa=método 2FA +keeper2faDescription=El método principal de autenticación de dos factores que está configurado para tu cuenta. Actívalo si tu cuenta de Keeper requiere autenticación de dos factores para acceder a las contraseñas. keeperTotpDuration=Duración del código 2FA personalizado keeperTotpDurationDescription=Anula la duración por defecto de la validez de un código 2FA. Sólo se aplica si la política de tu organización permite cambiar la duración.\n\nLos valores posibles son: $VALUES$ +keeperOtherAuth=Otros (RSA SecurID, Duo Security, Keeper DNA, etc.) extractReusableIdentities=Extraer identidades reutilizables identitiesAdded=Identidades añadidas syncMode=Modo de sincronización @@ -1906,3 +1907,5 @@ showStatus=Mostrar estado showAllPorts=Mostrar todos los puertos activeLicense=Licencia activeLicenseDescription=Activar una clave de licencia XPipe +authenticatorApp=Aplicación Autenticador +securityKey=Clave de seguridad diff --git a/lang/strings/translations_fr.properties b/lang/strings/translations_fr.properties index bfedce179..df76d47a6 100644 --- a/lang/strings/translations_fr.properties +++ b/lang/strings/translations_fr.properties @@ -1876,10 +1876,11 @@ testingConnection=Test de connexion ... openManagementConsole=Console de gestion ouverte openLxcTerminal=Ouvrir le terminal LXC openContainerConsole=Ouvrir une console série -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. +keeper2fa=méthode 2FA +keeper2faDescription=La principale méthode d'authentification à deux facteurs qui est configurée pour ton compte. Active cette option si ton compte Keeper nécessite une authentification à deux facteurs pour accéder aux mots de passe. keeperTotpDuration=Durée du code 2FA personnalisé keeperTotpDurationDescription=Remplacer la durée par défaut de la validité d'un code 2FA. Ne s'applique que si la politique de ton organisation permet de modifier la durée.\n\nLes valeurs possibles sont : $VALUES$ +keeperOtherAuth=Autre (RSA SecurID, Duo Security, Keeper DNA, etc.) extractReusableIdentities=Extraire des identités réutilisables identitiesAdded=Identités ajoutées syncMode=Mode de synchronisation @@ -1946,3 +1947,5 @@ showStatus=Afficher l'état showAllPorts=Afficher tous les ports activeLicense=Licence activeLicenseDescription=Activer une clé de licence XPipe +authenticatorApp=Application Authenticator +securityKey=Clé de sécurité diff --git a/lang/strings/translations_id.properties b/lang/strings/translations_id.properties index e5f065eb1..9e78e9619 100644 --- a/lang/strings/translations_id.properties +++ b/lang/strings/translations_id.properties @@ -1836,10 +1836,11 @@ testingConnection=Menguji koneksi ... openManagementConsole=Konsol manajemen terbuka openLxcTerminal=Membuka terminal LXC openContainerConsole=Membuka konsol serial -keeperUseMfa=Menggunakan aplikasi pengautentikasi 2FA -keeperUseMfaDescription=Aktifkan ini jika akun Keeper Anda memerlukan TOTP 2FA untuk mengakses kata sandi. +keeper2fa=metode 2FA +keeper2faDescription=Metode autentikasi dua faktor utama yang dikonfigurasikan untuk akun Anda. Aktifkan ini jika akun Keeper Anda memerlukan autentikasi dua faktor untuk mengakses kata sandi. keeperTotpDuration=Durasi kode 2FA khusus keeperTotpDurationDescription=Mengganti durasi default tentang berapa lama kode 2FA berlaku. Hanya berlaku jika kebijakan organisasi Anda mengizinkan pengubahan durasi.\n\nNilai yang mungkin adalah: $VALUES$ +keeperOtherAuth=Lainnya (RSA SecurID, Duo Security, Keeper DNA, dll.) extractReusableIdentities=Mengekstrak identitas yang dapat digunakan kembali identitiesAdded=Identitas ditambahkan syncMode=Mode sinkronisasi @@ -1906,3 +1907,5 @@ showStatus=Menampilkan status showAllPorts=Menampilkan semua port activeLicense=Lisensi activeLicenseDescription=Mengaktifkan kunci lisensi XPipe +authenticatorApp=Aplikasi autentikator +securityKey=Kunci keamanan diff --git a/lang/strings/translations_it.properties b/lang/strings/translations_it.properties index 05755100f..d6d7d703f 100644 --- a/lang/strings/translations_it.properties +++ b/lang/strings/translations_it.properties @@ -1836,10 +1836,11 @@ testingConnection=Verifica della connessione ... openManagementConsole=Console di gestione aperta openLxcTerminal=Aprire il terminale LXC openContainerConsole=Aprire la console seriale -keeperUseMfa=Usa l'applicazione autenticatore 2FA -keeperUseMfaDescription=Abilita questa opzione se il tuo account Keeper richiede un 2FA TOTP per accedere alle password. +keeper2fa=metodo 2FA +keeper2faDescription=Il metodo principale di autenticazione a due fattori configurato per il tuo account. Abilita questa opzione se il tuo account Keeper richiede l'autenticazione a due fattori per accedere alle password. keeperTotpDuration=Durata del codice 2FA personalizzato keeperTotpDurationDescription=Annulla la durata predefinita della validità di un codice 2FA. Si applica solo se la politica dell'organizzazione consente di modificare la durata.\n\nI valori possibili sono: $VALUES$ +keeperOtherAuth=Altro (RSA SecurID, Duo Security, Keeper DNA, ecc.) extractReusableIdentities=Estrarre identità riutilizzabili identitiesAdded=Identità aggiunte syncMode=Modalità di sincronizzazione @@ -1906,3 +1907,5 @@ showStatus=Mostra stato showAllPorts=Mostra tutte le porte activeLicense=Licenza activeLicenseDescription=Attivare una chiave di licenza XPipe +authenticatorApp=App Autenticatore +securityKey=Chiave di sicurezza diff --git a/lang/strings/translations_ja.properties b/lang/strings/translations_ja.properties index edb01aaa4..f885561dd 100644 --- a/lang/strings/translations_ja.properties +++ b/lang/strings/translations_ja.properties @@ -1836,10 +1836,11 @@ testingConnection=接続をテストする openManagementConsole=オープン管理コンソール openLxcTerminal=LXCターミナルを開く openContainerConsole=シリアルコンソールを開く -keeperUseMfa=2FA認証アプリを使用する -keeperUseMfaDescription=Keeperアカウントがパスワードにアクセスするために2FA TOTPを必要とする場合、これを有効にする。 +keeper2fa=2FA方式 +keeper2faDescription=アカウントに設定されているプライマリ2要素認証方法。Keeperアカウントがパスワードにアクセスするために2要素認証が必要な場合、これを有効にする。 keeperTotpDuration=カスタム2FAコード期間 keeperTotpDurationDescription=2FAコードの有効期間に関するデフォルトの期間をオーバーライドする。組織のポリシーで期間の変更が許可されている場合にのみ適用される。\n\n可能な値は以下の通り:$VALUES$ +keeperOtherAuth=その他(RSA SecurID、Duo Security、Keeper DNAなど) extractReusableIdentities=再利用可能なIDを抽出する identitiesAdded=IDが追加された syncMode=同期モード @@ -1906,3 +1907,5 @@ showStatus=ステータスを表示する showAllPorts=すべてのポートを表示する activeLicense=ライセンス activeLicenseDescription=XPipeライセンスキーをアクティベートする +authenticatorApp=認証アプリ +securityKey=セキュリティキー diff --git a/lang/strings/translations_ko.properties b/lang/strings/translations_ko.properties index fcfc78baf..3525fbfaa 100644 --- a/lang/strings/translations_ko.properties +++ b/lang/strings/translations_ko.properties @@ -1889,10 +1889,11 @@ testingConnection=연결 테스트 중 ... openManagementConsole=관리 콘솔 열기 openLxcTerminal=LXC 터미널 열기 openContainerConsole=직렬 콘솔 열기 -keeperUseMfa=2FA 인증 앱 사용 -keeperUseMfaDescription=Keeper 계정에서 비밀번호에 액세스하기 위해 2FA TOTP가 필요한 경우 이 옵션을 사용 설정합니다. +keeper2fa=2FA 방법 +keeper2faDescription=계정에 대해 구성된 기본 2단계 인증 방법입니다. Keeper 계정에서 비밀번호에 액세스하기 위해 2단계 인증이 필요한 경우 이 옵션을 사용 설정합니다. keeperTotpDuration=사용자 지정 2FA 코드 기간 keeperTotpDurationDescription=2FA 코드의 유효 기간에 대한 기본 기간을 재정의합니다. 조직 정책에서 기간 변경을 허용하는 경우에만 적용됩니다.\n\n가능한 값은 다음과 같습니다: $VALUES$ +keeperOtherAuth=기타(RSA SecurID, Duo Security, Keeper DNA 등) extractReusableIdentities=재사용 가능한 ID 추출 identitiesAdded=추가된 ID syncMode=동기화 모드 @@ -1959,3 +1960,5 @@ showStatus=상태 표시 showAllPorts=모든 포트 표시 activeLicense=라이선스 activeLicenseDescription=XPipe 라이선스 키 활성화 +authenticatorApp=인증 앱 +securityKey=보안 키 diff --git a/lang/strings/translations_nl.properties b/lang/strings/translations_nl.properties index 010271b48..6b1ecd4c0 100644 --- a/lang/strings/translations_nl.properties +++ b/lang/strings/translations_nl.properties @@ -1836,10 +1836,11 @@ testingConnection=Verbinding testen ... openManagementConsole=Open beheerconsole openLxcTerminal=LXC-terminal openen openContainerConsole=Seriële console openen -keeperUseMfa=Gebruik 2FA authenticator app -keeperUseMfaDescription=Schakel dit in als je Keeper-account een 2FA TOTP vereist om toegang te krijgen tot wachtwoorden. +keeper2fa=2FA methode +keeper2faDescription=De primaire twee-factor authenticatiemethode die is geconfigureerd voor je account. Schakel dit in als je Keeper-account tweefactorauthenticatie vereist om toegang te krijgen tot wachtwoorden. keeperTotpDuration=Aangepaste 2FA code duur keeperTotpDurationDescription=Overschrijf de standaardduur van de geldigheid van een 2FA code. Alleen van toepassing als het beleid van je organisatie het wijzigen van de duur toestaat.\n\nMogelijke waarden zijn: $VALUES$ +keeperOtherAuth=Andere (RSA SecurID, Duo Security, Keeper DNA, etc.) extractReusableIdentities=Herbruikbare identiteiten extraheren identitiesAdded=Identiteiten toegevoegd syncMode=Synchronisatiemodus @@ -1906,3 +1907,5 @@ showStatus=Status weergeven showAllPorts=Toon alle poorten activeLicense=Licentie activeLicenseDescription=Een XPipe-licentiesleutel activeren +authenticatorApp=Authenticator app +securityKey=Beveiligingssleutel diff --git a/lang/strings/translations_pl.properties b/lang/strings/translations_pl.properties index c10823a68..70a0f8a06 100644 --- a/lang/strings/translations_pl.properties +++ b/lang/strings/translations_pl.properties @@ -1837,10 +1837,11 @@ testingConnection=Testowanie połączenia ... openManagementConsole=Otwarta konsola zarządzania openLxcTerminal=Otwórz terminal LXC openContainerConsole=Otwórz konsolę szeregową -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ł. +keeper2fa=metoda 2FA +keeper2faDescription=Podstawowa metoda uwierzytelniania dwuskładnikowego skonfigurowana dla Twojego konta. Włącz tę opcję, jeśli Twoje konto Keeper wymaga uwierzytelniania dwuskładnikowego w celu uzyskania dostępu do haseł. keeperTotpDuration=Czas trwania niestandardowego kodu 2FA keeperTotpDurationDescription=Zastąp domyślny czas ważności kodu 2FA. Ma zastosowanie tylko wtedy, gdy zasady Twojej organizacji zezwalają na zmianę czasu trwania.\n\nMożliwe wartości to: $VALUES$ +keeperOtherAuth=Inne (RSA SecurID, Duo Security, Keeper DNA itp.) extractReusableIdentities=Wyodrębnij tożsamości wielokrotnego użytku identitiesAdded=Dodane tożsamości syncMode=Tryb synchronizacji @@ -1907,3 +1908,5 @@ showStatus=Pokaż status showAllPorts=Pokaż wszystkie porty activeLicense=Licencja activeLicenseDescription=Aktywuj klucz licencyjny XPipe +authenticatorApp=Aplikacja uwierzytelniająca +securityKey=Klucz zabezpieczeń diff --git a/lang/strings/translations_pt.properties b/lang/strings/translations_pt.properties index 062d33fec..ea71fe82c 100644 --- a/lang/strings/translations_pt.properties +++ b/lang/strings/translations_pt.properties @@ -1836,10 +1836,11 @@ testingConnection=Testar a ligação ... openManagementConsole=Abre a consola de gestão openLxcTerminal=Abre o terminal LXC openContainerConsole=Abre a consola de série -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. +keeper2fa=método 2FA +keeper2faDescription=O método primário de autenticação de dois fatores que está configurado para a tua conta. Ative isso se sua conta do Keeper exigir autenticação de dois fatores para acessar senhas. keeperTotpDuration=Duração do código 2FA personalizado keeperTotpDurationDescription=Substitui a duração predefinida de quanto tempo um código 2FA é válido. Só se aplica se a política da tua organização permitir alterar a duração.\n\nOs valores possíveis são: $VALUES$ +keeperOtherAuth=Outros (RSA SecurID, Duo Security, Keeper DNA, etc.) extractReusableIdentities=Extrai identidades reutilizáveis identitiesAdded=Identificações adicionadas syncMode=Modo de sincronização @@ -1906,3 +1907,5 @@ showStatus=Mostra o estado showAllPorts=Mostra todas as portas activeLicense=Licença activeLicenseDescription=Ativar uma chave de licença XPipe +authenticatorApp=Aplicação de autenticação +securityKey=Chave de segurança diff --git a/lang/strings/translations_ru.properties b/lang/strings/translations_ru.properties index 37a0a648d..640d2d465 100644 --- a/lang/strings/translations_ru.properties +++ b/lang/strings/translations_ru.properties @@ -1948,10 +1948,11 @@ testingConnection=Тестирование соединения ... openManagementConsole=Открытая консоль управления openLxcTerminal=Откройте терминал LXC openContainerConsole=Открыть последовательную консоль -keeperUseMfa=Используйте приложение аутентификатора 2FA -keeperUseMfaDescription=Включи эту опцию, если твой аккаунт Keeper требует 2FA TOTP для доступа к паролям. +keeper2fa=метод 2FA +keeper2faDescription=Основной метод двухфакторной аутентификации, который настроен для твоего аккаунта. Включи этот параметр, если твой аккаунт Keeper требует двухфакторной аутентификации для доступа к паролям. keeperTotpDuration=Продолжительность действия пользовательского кода 2FA keeperTotpDurationDescription=Переопредели стандартную продолжительность действия кода 2FA. Применяется только в том случае, если политика твоей организации позволяет изменять продолжительность.\n\nВозможные значения: $VALUES$ +keeperOtherAuth=Прочее (RSA SecurID, Duo Security, Keeper DNA и так далее) extractReusableIdentities=Извлечение многократно используемых идентификационных данных identitiesAdded=Добавлены идентификаторы syncMode=Режим синхронизации @@ -2018,3 +2019,5 @@ showStatus=Показать состояние showAllPorts=Показать все порты activeLicense=Лицензия activeLicenseDescription=Активировать лицензионный ключ XPipe +authenticatorApp=Приложение-аутентификатор +securityKey=Ключ безопасности diff --git a/lang/strings/translations_sv.properties b/lang/strings/translations_sv.properties index 56c91099f..96890c238 100644 --- a/lang/strings/translations_sv.properties +++ b/lang/strings/translations_sv.properties @@ -1836,10 +1836,11 @@ testingConnection=Testning av anslutning ... openManagementConsole=Öppen hanteringskonsol openLxcTerminal=Öppna LXC-terminalen openContainerConsole=Öppna seriell konsol -keeperUseMfa=Använd 2FA-autentiseringsapp -keeperUseMfaDescription=Aktivera detta om ditt Keeper-konto kräver en 2FA TOTP för att komma åt lösenord. +keeper2fa=2FA-metod +keeper2faDescription=Den primära tvåfaktorsautentiseringsmetoden som är konfigurerad för ditt konto. Aktivera detta om ditt Keeper-konto kräver tvåfaktorsautentisering för att komma åt lösenord. keeperTotpDuration=Anpassad 2FA-kods varaktighet keeperTotpDurationDescription=Åsidosätt standardtiden för hur länge en 2FA-kod är giltig. Gäller endast om organisationens policy tillåter ändring av giltighetstiden.\n\nMöjliga värden är: $VALUES$ +keeperOtherAuth=Annat (RSA SecurID, Duo Security, Keeper DNA, etc.) extractReusableIdentities=Extrahera återanvändbara identiteter identitiesAdded=Identiteter tillagda syncMode=Synkroniseringsläge @@ -1906,3 +1907,5 @@ showStatus=Visa status showAllPorts=Visa alla portar activeLicense=Licens activeLicenseDescription=Aktivera en XPipe-licensnyckel +authenticatorApp=Autentiseringsapplikation +securityKey=Säkerhetsnyckel diff --git a/lang/strings/translations_tr.properties b/lang/strings/translations_tr.properties index 8126a244b..4ae82ae33 100644 --- a/lang/strings/translations_tr.properties +++ b/lang/strings/translations_tr.properties @@ -1836,10 +1836,11 @@ testingConnection=Test bağlantısı ... openManagementConsole=Açık yönetim konsolu openLxcTerminal=LXC terminalini açın openContainerConsole=Seri konsolu açın -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. +keeper2fa=2FA yöntemi +keeper2faDescription=Hesabınız için yapılandırılmış olan birincil iki faktörlü kimlik doğrulama yöntemi. Keeper hesabınız parolalara erişmek için iki faktörlü kimlik doğrulama gerektiriyorsa bunu etkinleştirin. keeperTotpDuration=Özel 2FA kodu süresi keeperTotpDurationDescription=Bir 2FA kodunun ne kadar süreyle geçerli olacağına ilişkin varsayılan süreyi geçersiz kılın. Yalnızca kuruluş politikanız sürenin değiştirilmesine izin veriyorsa geçerlidir.\n\nOlası değerler şunlardır: $VALUES$ +keeperOtherAuth=Diğer (RSA SecurID, Duo Security, Keeper DNA, vb.) extractReusableIdentities=Yeniden kullanılabilir kimlikleri ayıklayın identitiesAdded=Kimlikler eklendi syncMode=Senkronizasyon modu @@ -1906,3 +1907,5 @@ showStatus=Durumu göster showAllPorts=Tüm bağlantı noktalarını göster activeLicense=Lisans activeLicenseDescription=XPipe lisans anahtarını etkinleştirme +authenticatorApp=Authenticator uygulaması +securityKey=Güvenlik anahtarı diff --git a/lang/strings/translations_vi.properties b/lang/strings/translations_vi.properties index 9b809d1a1..e1a7a493f 100644 --- a/lang/strings/translations_vi.properties +++ b/lang/strings/translations_vi.properties @@ -1836,10 +1836,11 @@ testingConnection=Kiểm tra kết nối ... openManagementConsole=Mở bảng điều khiển quản lý openLxcTerminal=Mở cửa sổ terminal LXC openContainerConsole=Mở giao diện điều khiển serial -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. +keeper2fa=phương pháp xác thực hai yếu tố (2FA) +keeper2faDescription=Phương thức xác thực hai yếu tố chính được cấu hình cho tài khoản của cậu. Bật tính năng 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ố để truy cập mật khẩu. keeperTotpDuration=Thời gian hiệu lực của mã 2FA tùy chỉnh keeperTotpDurationDescription=Thay đổi thời gian mặc định mà mã xác thực hai yếu tố (2FA) có hiệu lực. Tính năng này chỉ áp dụng nếu chính sách của tổ chức cho phép thay đổi thời gian.\n\nCác giá trị có thể là: $VALUES$ +keeperOtherAuth=Khác (RSA SecurID, Duo Security, Keeper DNA, v.v.) extractReusableIdentities=Trích xuất các danh tính có thể tái sử dụng identitiesAdded=Thêm danh tính syncMode=Chế độ đồng bộ hóa @@ -1906,3 +1907,5 @@ showStatus=Hiển thị trạng thái showAllPorts=Hiển thị tất cả các cổng activeLicense=Giấy phép activeLicenseDescription=Kích hoạt khóa cấp phép XPipe +authenticatorApp=Ứng dụng xác thực +securityKey=Khóa bảo mật diff --git a/lang/strings/translations_zh-Hans.properties b/lang/strings/translations_zh-Hans.properties index 46dfa5e9e..ee63d21cd 100644 --- a/lang/strings/translations_zh-Hans.properties +++ b/lang/strings/translations_zh-Hans.properties @@ -2461,10 +2461,11 @@ testingConnection=测试连接... openManagementConsole=开放式管理控制台 openLxcTerminal=打开 LXC 终端 openContainerConsole=打开串行控制台 -keeperUseMfa=使用 2FA 验证器应用程序 -keeperUseMfaDescription=如果 Keeper 帐户要求使用 2FA TOTP 访问密码,请启用此选项。 +keeper2fa=2FA 方法 +keeper2faDescription=为您的账户配置的主要双因素身份验证方法。如果您的 Keeper 帐户需要双因素身份验证才能访问密码,请启用此选项。 keeperTotpDuration=自定义 2FA 代码持续时间 keeperTotpDurationDescription=覆盖 2FA 验证码有效期的默认持续时间。仅适用于组织政策允许更改持续时间的情况。\n\n可能的值有$VALUES$ +keeperOtherAuth=其他(RSA SecurID、Duo Security、Keeper DNA 等) extractReusableIdentities=提取可重复使用的身份 identitiesAdded=添加的身份信息 syncMode=同步模式 @@ -2531,3 +2532,5 @@ showStatus=显示状态 showAllPorts=显示所有端口 activeLicense=许可证 activeLicenseDescription=激活 XPipe 许可证密钥 +authenticatorApp=验证器应用程序 +securityKey=安全密钥 diff --git a/lang/strings/translations_zh-Hant.properties b/lang/strings/translations_zh-Hant.properties index 3522269ba..d5722ae26 100644 --- a/lang/strings/translations_zh-Hant.properties +++ b/lang/strings/translations_zh-Hant.properties @@ -1836,10 +1836,11 @@ testingConnection=測試連接 ... openManagementConsole=開放式管理主控台 openLxcTerminal=開啟 LXC 終端機 openContainerConsole=開啟序列控制台 -keeperUseMfa=使用 2FA 認證器應用程式 -keeperUseMfaDescription=如果您的 Keeper 帳戶需要 2FA TOTP 才能存取密碼,請啟用此項。 +keeper2fa=2FA 方法 +keeper2faDescription=為您帳戶設定的主要雙因素驗證方法。如果您的 Keeper 帳戶需要雙因素認證才能存取密碼,請啟用此項。 keeperTotpDuration=自訂 2FA 碼持續時間 keeperTotpDurationDescription=覆寫 2FA 密碼有效期的預設持續時間。僅適用於組織政策允許變更持續時間的情況。\n\n可能的值有$VALUES$ +keeperOtherAuth=其他 (RSA SecurID、Duo Security、Keeper DNA 等) extractReusableIdentities=擷取可重複使用的身分 identitiesAdded=新增的身分 syncMode=同步模式 @@ -1906,3 +1907,5 @@ showStatus=顯示狀態 showAllPorts=顯示所有連接埠 activeLicense=許可證 activeLicenseDescription=啟動 XPipe 授權金鑰 +authenticatorApp=驗證器應用程式 +securityKey=安全金鑰 From d4b408daa58f8c15d24ceaa79f5b2d93883deb5c Mon Sep 17 00:00:00 2001 From: crschnick Date: Mon, 16 Feb 2026 13:32:03 +0000 Subject: [PATCH 34/42] OneDrive fixes --- app/src/main/java/io/xpipe/app/issue/ErrorEventFactory.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/java/io/xpipe/app/issue/ErrorEventFactory.java b/app/src/main/java/io/xpipe/app/issue/ErrorEventFactory.java index 2a4a3dfb5..3bcb9b173 100644 --- a/app/src/main/java/io/xpipe/app/issue/ErrorEventFactory.java +++ b/app/src/main/java/io/xpipe/app/issue/ErrorEventFactory.java @@ -84,6 +84,11 @@ public class ErrorEventFactory { b.expected(); } + if (OsType.ofLocal() == OsType.WINDOWS && t.getMessage().contains("The cloud file provider is not running")) { + b.description("The OneDrive cloud file provider is not running. Verify that your cloud storage is working and you are logged in."); + b.expected(); + } + if (t instanceof AccessDeniedException) { b.expected(); } From eda905e0eb761c8c48e559ac59476f394be487f6 Mon Sep 17 00:00:00 2001 From: crschnick Date: Mon, 16 Feb 2026 13:33:11 +0000 Subject: [PATCH 35/42] [stage] --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index 2a39e3cf9..4a780ee6f 100644 --- a/version +++ b/version @@ -1 +1 @@ -21.3-4 +21.3-5 From 8cdfa21a787c2fdd73cf6f3d5ae43a201f1d673a Mon Sep 17 00:00:00 2001 From: crschnick Date: Mon, 16 Feb 2026 15:15:23 +0000 Subject: [PATCH 36/42] Sync fixes [stage] --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index 4a780ee6f..5b447970d 100644 --- a/version +++ b/version @@ -1 +1 @@ -21.3-5 +21.3-6 From 61a9282cd07cedd3ff0cb3164cbc5c98ecee59be Mon Sep 17 00:00:00 2001 From: crschnick Date: Tue, 17 Feb 2026 17:50:39 +0000 Subject: [PATCH 37/42] Various fixes --- .../main/java/io/xpipe/app/comp/base/SideMenuBarComp.java | 5 +++-- app/src/main/java/io/xpipe/app/core/AppLayoutModel.java | 5 ++++- .../io/xpipe/ext/base/script/RunFileScriptMenuProvider.java | 4 ++++ .../ext/base/script/RunHubBatchScriptActionProvider.java | 4 ++++ .../xpipe/ext/base/script/RunHubScriptActionProvider.java | 6 ++++-- 5 files changed, 19 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/io/xpipe/app/comp/base/SideMenuBarComp.java b/app/src/main/java/io/xpipe/app/comp/base/SideMenuBarComp.java index 64fae5e7d..612f39020 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/SideMenuBarComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/SideMenuBarComp.java @@ -49,10 +49,11 @@ public class SideMenuBarComp extends RegionBuilder { if (e.action() != null) { e.action().run(); - return; } - value.setValue(e); + if (e.comp() != null) { + value.setValue(e); + } }); b.describe(d -> d.name(e.name())); diff --git a/app/src/main/java/io/xpipe/app/core/AppLayoutModel.java b/app/src/main/java/io/xpipe/app/core/AppLayoutModel.java index a360a0bfb..06e0915c5 100644 --- a/app/src/main/java/io/xpipe/app/core/AppLayoutModel.java +++ b/app/src/main/java/io/xpipe/app/core/AppLayoutModel.java @@ -7,6 +7,7 @@ import io.xpipe.app.hub.comp.StoreLayoutComp; import io.xpipe.app.platform.LabelGraphic; import io.xpipe.app.platform.PlatformThread; import io.xpipe.app.prefs.AppPrefsComp; +import io.xpipe.app.terminal.TerminalDockHubManager; import io.xpipe.app.update.AppDistributionType; import io.xpipe.app.util.*; @@ -117,7 +118,9 @@ public class AppLayoutModel { AppI18n.observable("connections"), new LabelGraphic.IconGraphic("mdi2c-connection"), new StoreLayoutComp(), - null, + () -> { + TerminalDockHubManager.get().hideDock(); + }, new KeyCodeCombination(KeyCode.DIGIT1, KeyCombination.SHORTCUT_DOWN)), new Entry( AppI18n.observable("browser"), 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 3a6d6ec0a..2cfe03d6a 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 @@ -143,6 +143,10 @@ public class RunFileScriptMenuProvider implements BrowserMenuBranchProvider { protected List createCommand(BrowserFileSystemTabModel model, List entries) { var sc = model.getFileSystem().getShell().orElseThrow(); var content = ref.getStore().assembleScriptChain(sc, true); + if (content == null) { + return List.of(); + } + var script = ScriptHelper.createExecScript(sc, content.getValue()); 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 fda0e5f7d..3f673f55b 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,6 +30,10 @@ public class RunHubBatchScriptActionProvider implements ActionProvider { for (DataStoreEntryRef ref : refs) { var sc = ref.getStore().getOrStartSession(); var script = scriptStore.getStore().assembleScriptChain(sc, false); + if (script == null) { + continue; + } + var cmd = sc.command(script); list.add(new CommandDialog.CommandEntry(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 e394be1f2..77875bd5a 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 @@ -26,8 +26,10 @@ public class RunHubScriptActionProvider implements ActionProvider { public void executeImpl() throws Exception { var sc = ref.getStore().getOrStartSession(); var script = scriptStore.getStore().assembleScriptChain(sc, false); - var cmd = sc.command(script); - CommandDialog.runAndShow(cmd); + if (script != null) { + var cmd = sc.command(script); + CommandDialog.runAndShow(cmd); + } } @Override From 32734b2ca1795638219cd974887b5c66c02f1fd3 Mon Sep 17 00:00:00 2001 From: crschnick Date: Tue, 17 Feb 2026 18:24:08 +0000 Subject: [PATCH 38/42] Improve API shell handling [stage] --- app/src/main/java/io/xpipe/app/beacon/AppBeaconCache.java | 6 ------ app/src/main/java/io/xpipe/app/beacon/mcp/McpTools.java | 7 ++----- .../main/java/io/xpipe/app/ext/ProcessControlProvider.java | 2 ++ .../main/java/io/xpipe/app/storage/DataStorageQuery.java | 2 +- version | 2 +- 5 files changed, 6 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/io/xpipe/app/beacon/AppBeaconCache.java b/app/src/main/java/io/xpipe/app/beacon/AppBeaconCache.java index 9452e6c72..025c4e1e1 100644 --- a/app/src/main/java/io/xpipe/app/beacon/AppBeaconCache.java +++ b/app/src/main/java/io/xpipe/app/beacon/AppBeaconCache.java @@ -36,12 +36,6 @@ public class AppBeaconCache { control.setNonInteractive(); control.start(); - var d = control.getShellDialect().getDumbMode(); - if (!d.supportsAnyPossibleInteraction()) { - control.close(); - d.throwIfUnsupported(); - } - if (existing.isEmpty()) { AppBeaconServer.get().getCache().getShellSessions().add(new BeaconShellSession(ref.get(), control)); } diff --git a/app/src/main/java/io/xpipe/app/beacon/mcp/McpTools.java b/app/src/main/java/io/xpipe/app/beacon/mcp/McpTools.java index 79e7c027f..276765930 100644 --- a/app/src/main/java/io/xpipe/app/beacon/mcp/McpTools.java +++ b/app/src/main/java/io/xpipe/app/beacon/mcp/McpTools.java @@ -3,10 +3,7 @@ package io.xpipe.app.beacon.mcp; import io.xpipe.app.beacon.AppBeaconServer; import io.xpipe.app.core.AppExtensionManager; import io.xpipe.app.core.AppNames; -import io.xpipe.app.ext.ConnectionFileSystem; -import io.xpipe.app.ext.FileEntry; -import io.xpipe.app.ext.FileInfo; -import io.xpipe.app.ext.SingletonSessionStore; +import io.xpipe.app.ext.*; import io.xpipe.app.process.ScriptHelper; import io.xpipe.app.process.ShellControl; import io.xpipe.app.process.TerminalInitScriptConfig; @@ -350,7 +347,7 @@ public final class McpTools { var shellStore = req.getShellStoreRef(system); var shellSession = AppBeaconServer.get().getCache().getOrStart(shellStore); - var out = shellSession.getControl().command(command).readStdoutOrThrow(); + var out = ProcessControlProvider.get().executeMcpCommand(shellSession.getControl(), command); var formatted = CommandDialog.formatOutput(out); return McpSchema.CallToolResult.builder() 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 d9d3da1fc..aa7739ab2 100644 --- a/app/src/main/java/io/xpipe/app/ext/ProcessControlProvider.java +++ b/app/src/main/java/io/xpipe/app/ext/ProcessControlProvider.java @@ -58,6 +58,8 @@ public abstract class ProcessControlProvider { public abstract ShellDialect getEffectiveLocalDialect(); + public abstract String executeMcpCommand(ShellControl sc, String command) throws Exception; + public ShellDialect getNextFallbackDialect() { var av = getAvailableLocalDialects(); var index = av.indexOf(getEffectiveLocalDialect()); diff --git a/app/src/main/java/io/xpipe/app/storage/DataStorageQuery.java b/app/src/main/java/io/xpipe/app/storage/DataStorageQuery.java index e8b32a382..35cd20c7d 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStorageQuery.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStorageQuery.java @@ -12,7 +12,7 @@ public class DataStorageQuery { var narrow = found.stream() .filter(dataStoreEntry -> dataStoreEntry.getName().equalsIgnoreCase(connection)) .toList(); - if (narrow.size() == 1) { + if (narrow.size() >= 1) { return narrow; } } diff --git a/version b/version index 5b447970d..20d912890 100644 --- a/version +++ b/version @@ -1 +1 @@ -21.3-6 +21.3-7 From 131ff1ae5d54a81b2bd4b52574a39307ca7b38d8 Mon Sep 17 00:00:00 2001 From: crschnick Date: Tue, 17 Feb 2026 19:15:01 +0000 Subject: [PATCH 39/42] [stage] --- app/src/main/java/io/xpipe/app/beacon/mcp/McpToolHandler.java | 2 +- version | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/io/xpipe/app/beacon/mcp/McpToolHandler.java b/app/src/main/java/io/xpipe/app/beacon/mcp/McpToolHandler.java index f4d9ff768..a46c56891 100644 --- a/app/src/main/java/io/xpipe/app/beacon/mcp/McpToolHandler.java +++ b/app/src/main/java/io/xpipe/app/beacon/mcp/McpToolHandler.java @@ -37,7 +37,7 @@ public interface McpToolHandler .isError(true) .build(); } catch (Throwable e) { - ErrorEventFactory.fromThrowable(e).handle(); + ErrorEventFactory.fromThrowable(e).omit().handle(); return McpSchema.CallToolResult.builder() .addTextContent(e.getMessage()) .isError(true) diff --git a/version b/version index 20d912890..2fedca72b 100644 --- a/version +++ b/version @@ -1 +1 @@ -21.3-7 +21.3-8 From f09d30c98624004dcd73c4b78b6ed10f989b19a7 Mon Sep 17 00:00:00 2001 From: crschnick Date: Tue, 17 Feb 2026 20:56:02 +0000 Subject: [PATCH 40/42] Rework mcp system prompt [stage] --- .../java/io/xpipe/app/beacon/mcp/AppMcpServer.java | 2 +- app/src/main/java/io/xpipe/app/prefs/AppPrefs.java | 10 ++++++++++ app/src/main/java/io/xpipe/app/prefs/McpCategory.java | 9 ++++++++- lang/strings/translations_en.properties | 3 +++ version | 2 +- 5 files changed, 23 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/io/xpipe/app/beacon/mcp/AppMcpServer.java b/app/src/main/java/io/xpipe/app/beacon/mcp/AppMcpServer.java index 8b225909d..e91e86e6d 100644 --- a/app/src/main/java/io/xpipe/app/beacon/mcp/AppMcpServer.java +++ b/app/src/main/java/io/xpipe/app/beacon/mcp/AppMcpServer.java @@ -52,8 +52,8 @@ public class AppMcpServer { .resources(true, true) .tools(true) .prompts(false) - .completions() .build()) + .instructions(AppPrefs.get().mcpAdditionalContext().getValue()) .build(); var readOnlyTools = new ArrayList(); 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 804b05403..1b435e5a8 100644 --- a/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java +++ b/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java @@ -125,6 +125,12 @@ public final class AppPrefs { .key("enableMcpMutationTools") .valueClass(Boolean.class) .build()); + final StringProperty mcpAdditionalContext = map(Mapping.builder() + .property(new GlobalStringProperty(null)) + .key("mcpAdditionalContext") + .valueClass(String.class) + .requiresRestart(true) + .build()); final BooleanProperty dontAutomaticallyStartVmSshServer = mapVaultShared(new GlobalBooleanProperty(false), "dontAutomaticallyStartVmSshServer", Boolean.class, false); final BooleanProperty dontAcceptNewHostKeys = @@ -582,6 +588,10 @@ public final class AppPrefs { return enableMcpMutationTools; } + public ObservableValue mcpAdditionalContext() { + return mcpAdditionalContext; + } + public ObservableBooleanValue pinLocalMachineOnStartup() { return pinLocalMachineOnStartup; } diff --git a/app/src/main/java/io/xpipe/app/prefs/McpCategory.java b/app/src/main/java/io/xpipe/app/prefs/McpCategory.java index c6d4e09ee..8d8d87c1c 100644 --- a/app/src/main/java/io/xpipe/app/prefs/McpCategory.java +++ b/app/src/main/java/io/xpipe/app/prefs/McpCategory.java @@ -2,7 +2,9 @@ package io.xpipe.app.prefs; import io.xpipe.app.beacon.AppBeaconServer; import io.xpipe.app.comp.BaseRegionBuilder; +import io.xpipe.app.comp.base.IntegratedTextAreaComp; import io.xpipe.app.comp.base.TextAreaComp; +import io.xpipe.app.core.AppI18n; import io.xpipe.app.core.AppNames; import io.xpipe.app.platform.LabelGraphic; import io.xpipe.app.platform.OptionsBuilder; @@ -65,7 +67,12 @@ public class McpCategory extends AppPrefsCategory { struc.getTextArea().setEditable(false); struc.getTextArea().setPrefRowCount(12); })) - .hide(prefs.enableMcpServer.not())) + .hide(prefs.enableMcpServer.not()) + .pref(prefs.mcpAdditionalContext) + .addComp(new IntegratedTextAreaComp(prefs.mcpAdditionalContext, false, "prompt", new SimpleStringProperty("txt")).applyStructure(structure -> { + structure.getTextArea().promptTextProperty().bind(AppI18n.observable("mcpAdditionalContextSample")); + })) + ) .buildComp(); } } diff --git a/lang/strings/translations_en.properties b/lang/strings/translations_en.properties index 4d322b602..e37404aaf 100644 --- a/lang/strings/translations_en.properties +++ b/lang/strings/translations_en.properties @@ -1981,3 +1981,6 @@ activeLicense=License activeLicenseDescription=Activate an XPipe license key authenticatorApp=Authenticator app securityKey=Security key +mcpAdditionalContext=Additional MCP context +mcpAdditionalContextDescription=Additional instructions to pass to the MCP client. Use this to control the agent behaviour and supply additional context for your individual setup. +mcpAdditionalContextSample=Treat all systems in category XY as production environments and do not modify them diff --git a/version b/version index 2fedca72b..539b8a835 100644 --- a/version +++ b/version @@ -1 +1 @@ -21.3-8 +21.3-9 From e6e63e44e4248efb2bd5af9f1f5031db5830f5d9 Mon Sep 17 00:00:00 2001 From: crschnick Date: Wed, 18 Feb 2026 14:45:35 +0000 Subject: [PATCH 41/42] Fix NPEs --- .../io/xpipe/app/hub/comp/StoreEntryBatchSelectComp.java | 9 +++++++++ .../main/java/io/xpipe/app/hub/comp/StoreViewState.java | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/app/src/main/java/io/xpipe/app/hub/comp/StoreEntryBatchSelectComp.java b/app/src/main/java/io/xpipe/app/hub/comp/StoreEntryBatchSelectComp.java index 38bdcb450..824be633a 100644 --- a/app/src/main/java/io/xpipe/app/hub/comp/StoreEntryBatchSelectComp.java +++ b/app/src/main/java/io/xpipe/app/hub/comp/StoreEntryBatchSelectComp.java @@ -1,6 +1,7 @@ package io.xpipe.app.hub.comp; import io.xpipe.app.comp.SimpleRegionBuilder; +import io.xpipe.app.storage.DataStoreEntry; import io.xpipe.app.util.BooleanScope; import javafx.application.Platform; @@ -23,6 +24,7 @@ public class StoreEntryBatchSelectComp extends SimpleRegionBuilder { protected Region createSimple() { var selfUpdate = new SimpleBooleanProperty(false); var cb = new CheckBox(); + externalUpdate(cb); cb.setAllowIndeterminate(true); cb.selectedProperty().addListener((observable, oldValue, newValue) -> { BooleanScope.executeExclusive(selfUpdate, () -> { @@ -64,6 +66,13 @@ public class StoreEntryBatchSelectComp extends SimpleRegionBuilder { } private void externalUpdate(CheckBox checkBox) { + if (section.getWrapper() != null && section.getWrapper().getEntry().getValidity() == DataStoreEntry.Validity.LOAD_FAILED) { + checkBox.setSelected(false); + checkBox.setIndeterminate(false); + checkBox.setDisable(true); + return; + } + var isSelected = section.getWrapper() == null ? checkBox.isSelected() : StoreViewState.get().isBatchModeSelected(section.getWrapper()); diff --git a/app/src/main/java/io/xpipe/app/hub/comp/StoreViewState.java b/app/src/main/java/io/xpipe/app/hub/comp/StoreViewState.java index 80eb0c2aa..0de017a1a 100644 --- a/app/src/main/java/io/xpipe/app/hub/comp/StoreViewState.java +++ b/app/src/main/java/io/xpipe/app/hub/comp/StoreViewState.java @@ -189,6 +189,9 @@ public class StoreViewState { public void selectBatchMode(StoreSection section) { var wrapper = section.getWrapper(); + if (wrapper != null && wrapper.getEntry().getValidity() == DataStoreEntry.Validity.LOAD_FAILED) { + return; + } if (wrapper != null && !batchModeSelectionSet.contains(wrapper)) { batchModeSelection.getList().add(wrapper); } @@ -199,6 +202,9 @@ public class StoreViewState { public void unselectBatchMode(StoreSection section) { var wrapper = section.getWrapper(); + if (wrapper != null && wrapper.getEntry().getValidity() == DataStoreEntry.Validity.LOAD_FAILED) { + return; + } if (wrapper != null) { batchModeSelection.getList().remove(wrapper); } From 91e05e9f8e2c1bd2620305e8dc8cd0d0117dde85 Mon Sep 17 00:00:00 2001 From: crschnick Date: Wed, 18 Feb 2026 15:46:15 +0000 Subject: [PATCH 42/42] Various fixes [stage] --- .../app/comp/base/IntegratedTextAreaComp.java | 10 +- .../java/io/xpipe/app/prefs/McpCategory.java | 124 ++++++++++++++---- dist/changelog/21.3.md | 44 +++++++ lang/strings/fixed_en.properties | 1 + lang/strings/translations_da.properties | 3 + lang/strings/translations_de.properties | 3 + lang/strings/translations_en.properties | 2 +- lang/strings/translations_es.properties | 3 + lang/strings/translations_fr.properties | 3 + lang/strings/translations_id.properties | 3 + lang/strings/translations_it.properties | 3 + lang/strings/translations_ja.properties | 3 + lang/strings/translations_ko.properties | 3 + lang/strings/translations_nl.properties | 3 + lang/strings/translations_pl.properties | 3 + lang/strings/translations_pt.properties | 3 + lang/strings/translations_ru.properties | 3 + lang/strings/translations_sv.properties | 3 + lang/strings/translations_tr.properties | 3 + lang/strings/translations_vi.properties | 3 + lang/strings/translations_zh-Hans.properties | 3 + lang/strings/translations_zh-Hant.properties | 3 + version | 2 +- 23 files changed, 205 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/io/xpipe/app/comp/base/IntegratedTextAreaComp.java b/app/src/main/java/io/xpipe/app/comp/base/IntegratedTextAreaComp.java index bb76a4671..e2b2c969c 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/IntegratedTextAreaComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/IntegratedTextAreaComp.java @@ -89,13 +89,19 @@ public class IntegratedTextAreaComp extends RegionStructureBuilder { var val = value.getValue() != null ? value.getValue() : ""; - var count = (int) val.lines().count() + (val.endsWith("\n") ? 1 : 0); + var valCount = (int) val.lines().count() + (val.endsWith("\n") ? 1 : 0); + + var promptVal = struc.getTextArea().getPromptText() != null ? struc.getTextArea().getPromptText() : ""; + var promptValCount = (int) promptVal.lines().count() + (promptVal.endsWith("\n") ? 1 : 0); + + var count = Math.max(valCount, promptValCount); // Somehow the handling of trailing newlines is weird // This makes the handling better for JavaFX text areas count++; return Math.max(1, count); }, - value)); + value, + struc.getTextArea().promptTextProperty())); }); var textAreaStruc = textArea.buildStructure(); var copyButton = createOpenButton(); diff --git a/app/src/main/java/io/xpipe/app/prefs/McpCategory.java b/app/src/main/java/io/xpipe/app/prefs/McpCategory.java index 8d8d87c1c..d59ce98ad 100644 --- a/app/src/main/java/io/xpipe/app/prefs/McpCategory.java +++ b/app/src/main/java/io/xpipe/app/prefs/McpCategory.java @@ -1,7 +1,9 @@ package io.xpipe.app.prefs; +import atlantafx.base.theme.Styles; import io.xpipe.app.beacon.AppBeaconServer; import io.xpipe.app.comp.BaseRegionBuilder; +import io.xpipe.app.comp.RegionBuilder; import io.xpipe.app.comp.base.IntegratedTextAreaComp; import io.xpipe.app.comp.base.TextAreaComp; import io.xpipe.app.core.AppI18n; @@ -11,6 +13,10 @@ import io.xpipe.app.platform.OptionsBuilder; import javafx.beans.binding.Bindings; import javafx.beans.property.SimpleStringProperty; +import javafx.beans.value.ObservableValue; +import javafx.scene.control.Tab; +import javafx.scene.control.TabPane; +import javafx.scene.control.TextArea; public class McpCategory extends AppPrefsCategory { @@ -24,26 +30,11 @@ public class McpCategory extends AppPrefsCategory { return new LabelGraphic.IconGraphic("mdi2c-chat-processing-outline"); } - @Override - protected BaseRegionBuilder create() { + private ObservableValue createMcpConfig(String format) { var prefs = AppPrefs.get(); - - var mcpConfig = Bindings.createStringBinding( + return Bindings.createStringBinding( () -> { - var template = """ - { - "mcpServers": { - "%s": { - "type": "streamable-http", - "url": "http://localhost:%s/mcp", - "headers": { - "Authorization": "Bearer %s" - } - } - } - } - """; - return template.formatted( + return format.formatted( AppNames.ofCurrent().getKebapName(), AppBeaconServer.get().getPort(), prefs.apiKey().get() != null @@ -52,26 +43,109 @@ public class McpCategory extends AppPrefsCategory { .strip(); }, prefs.apiKey()); - var mcpConfigProp = new SimpleStringProperty(); - mcpConfigProp.bind(mcpConfig); + } + + @Override + protected BaseRegionBuilder create() { + var prefs = AppPrefs.get(); + + var vsCodeTemplate = createMcpConfig(""" + { + "servers": { + "%s": { + "type": "http", + "url": "http://localhost:%s/mcp", + "headers": { + "Authorization": "Bearer %s" + } + } + } + } + """); + + var cursorTemplate = createMcpConfig(""" + { + "mcpServers": { + "%s": { + "type": "streamable-http", + "url": "http://localhost:%s/mcp", + "headers": { + "Authorization": "Bearer %s" + } + } + } + } + """); + + var warpTemplate = createMcpConfig(""" + { + "%s": { + "serverUrl": "http://localhost:%s/mcp", + "headers": { + "Authorization": "Bearer %s" + } + } + } + """); + + var tabComp = RegionBuilder.of(() -> { + var vsCode = new TextArea(); + vsCode.setEditable(false); + vsCode.textProperty().bind(vsCodeTemplate); + vsCode.setPrefRowCount(12); + var vsCodeTab = new Tab(); + vsCodeTab.textProperty().bind(AppI18n.observable("vscode")); + vsCodeTab.setContent(vsCode); + vsCodeTab.setClosable(false); + + var cursor = new TextArea(); + cursor.setEditable(false); + cursor.textProperty().bind(cursorTemplate); + cursor.setPrefRowCount(12); + var cursorTab = new Tab(); + cursorTab.textProperty().bind(AppI18n.observable("cursor")); + cursorTab.setContent(cursor); + cursorTab.setClosable(false); + + var warp = new TextArea(); + warp.setEditable(false); + warp.textProperty().bind(warpTemplate); + warp.setPrefRowCount(12); + var warpTab = new Tab(); + warpTab.textProperty().bind(AppI18n.observable("warp")); + warpTab.setContent(warp); + warpTab.setClosable(false); + + var claude = new TextArea(); + claude.setEditable(false); + claude.textProperty().bind(vsCodeTemplate); + claude.setPrefRowCount(12); + var claudeTab = new Tab(); + claudeTab.textProperty().bind(AppI18n.observable("claude")); + claudeTab.setContent(claude); + claudeTab.setClosable(false); + + var tabPane = new TabPane(); + tabPane.getTabs().addAll(vsCodeTab, cursorTab, warpTab, claudeTab); + return tabPane; + }); return new OptionsBuilder() .addTitle("mcpServer") .sub(new OptionsBuilder() .pref(prefs.enableMcpServer) .addToggle(prefs.enableMcpServer) + .nameAndDescription("mcpClientConfigurationDetails") + .addComp(tabComp) + .hide(prefs.enableMcpServer.not()) .pref(prefs.enableMcpMutationTools) .addToggle(prefs.enableMcpMutationTools) - .nameAndDescription("mcpClientConfigurationDetails") - .addComp(new TextAreaComp(mcpConfigProp).applyStructure(struc -> { - struc.getTextArea().setEditable(false); - struc.getTextArea().setPrefRowCount(12); - })) .hide(prefs.enableMcpServer.not()) .pref(prefs.mcpAdditionalContext) .addComp(new IntegratedTextAreaComp(prefs.mcpAdditionalContext, false, "prompt", new SimpleStringProperty("txt")).applyStructure(structure -> { structure.getTextArea().promptTextProperty().bind(AppI18n.observable("mcpAdditionalContextSample")); })) + .hide(prefs.enableMcpServer.not()) ) .buildComp(); } diff --git a/dist/changelog/21.3.md b/dist/changelog/21.3.md index 73ce0ebe9..f9d11c943 100644 --- a/dist/changelog/21.3.md +++ b/dist/changelog/21.3.md @@ -1 +1,45 @@ +## Terminal docking + +- Restore proper window borders once a terminal is undocked +- Fix terminals randomly undocking +- Fix docked terminals in background sometimes being larger than main window +- The connection hub button will now always hide the terminal when clicked +- Fix file browser docking tabbing being inconsistent +- Fix dock being broken after application window has been hidden once + +## Shell detection + +Add new experimental heuristic for detecting custom shells of limited/embedded systems. Now, even if XPipe does not know the shell type and can't communicate with it, it will use heuristics to determine whether the shell is responding properly or the connection timeouted. + +This should make the process of adding connections to custom systems that don't run a common operating system much easier. + +## MCP + +The MCP server integration has been improved. The connection query by name for agents has been improved to reduce ambiguous results. Furthermore, the MCP instructions haven been augmented for various popular clients: + +![](https://xpipe.io/assets/images/BlogPage/mcp-instructions.png) + +There is now also the option to pass additional context to any agent to apply consistent rules and information: + +![](https://xpipe.io/assets/images/BlogPage/mcp-context.png) + +The MCP integration can also now be used to manage cisco switches via the new integration: + +![](https://xpipe.io/assets/images/BlogPage/mcp-switch.png) + +## Other + +- Add support for all other Keeper password manager 2FA methods +- Fix vault sync failing in OneDrive directories +- Improve rendering performance on Windows - Add support for neovim editor (Thanks to @leycm) +- Add browser context menu action for gradlew files to run tasks (Thanks to @leycm) +- Fix AUR update failing if git core.autocrlf was set to true (Thanks to @leycm) +- Fix WezTerm not using tabs on Linux (Thanks to @leycm) +- Fix WezTerm tab titles not being overridden (Thanks to @leycm) +- Fix SSH config update for VsCode integration creating connection duplicates +- Fix various terminal updaters instantly closing when update failed +- Fix VsCode not launching in Webtop due to sandbox restrictions +- Add custom theming to Webtop +- Fix various NullPointers + diff --git a/lang/strings/fixed_en.properties b/lang/strings/fixed_en.properties index 184906291..72879831d 100644 --- a/lang/strings/fixed_en.properties +++ b/lang/strings/fixed_en.properties @@ -12,6 +12,7 @@ elementaryTerminal=Elementary Terminal macosTerminal=Terminal.app iterm2=iTerm2 warp=Warp +claude=Claude wave=Wave tabby=Tabby alacritty=Alacritty diff --git a/lang/strings/translations_da.properties b/lang/strings/translations_da.properties index 948bb7a7c..4fe25d563 100644 --- a/lang/strings/translations_da.properties +++ b/lang/strings/translations_da.properties @@ -1950,3 +1950,6 @@ activeLicense=Licens activeLicenseDescription=Aktiver en XPipe-licensnøgle authenticatorApp=Autentificerings-app securityKey=Sikkerhedsnøgle +mcpAdditionalContext=Yderligere MCP-kontekst +mcpAdditionalContextDescription=Yderligere instruktioner, der skal sendes til MCP-klienten. Brug dette til at styre agentens adfærd og give yderligere kontekst til din individuelle opsætning. +mcpAdditionalContextSample=- Genstart ikke tjenester og dæmoner automatisk uden at bekræfte det først\n- Når du konfigurerer en netværksgrænseflade, skal du altid bruge 192.168.1.1/24 som gateway diff --git a/lang/strings/translations_de.properties b/lang/strings/translations_de.properties index 84a718d5a..39adb7eae 100644 --- a/lang/strings/translations_de.properties +++ b/lang/strings/translations_de.properties @@ -1945,3 +1945,6 @@ activeLicense=Lizenz activeLicenseDescription=Aktivieren eines XPipe-Lizenzschlüssels authenticatorApp=Authenticator-App securityKey=Sicherheitsschlüssel +mcpAdditionalContext=Zusätzlicher MCP-Kontext +mcpAdditionalContextDescription=Zusätzliche Anweisungen, die an den MCP-Client weitergegeben werden. Damit kannst du das Verhalten des Agenten steuern und zusätzlichen Kontext für dein individuelles Setup liefern. +mcpAdditionalContextSample=- Starten Sie keine Dienste und Daemons automatisch neu, ohne dies vorher zu bestätigen\n- Wenn du eine Netzwerkschnittstelle konfigurierst, verwende immer 192.168.1.1/24 als Gateway diff --git a/lang/strings/translations_en.properties b/lang/strings/translations_en.properties index e37404aaf..e8cbcbf81 100644 --- a/lang/strings/translations_en.properties +++ b/lang/strings/translations_en.properties @@ -1983,4 +1983,4 @@ authenticatorApp=Authenticator app securityKey=Security key mcpAdditionalContext=Additional MCP context mcpAdditionalContextDescription=Additional instructions to pass to the MCP client. Use this to control the agent behaviour and supply additional context for your individual setup. -mcpAdditionalContextSample=Treat all systems in category XY as production environments and do not modify them +mcpAdditionalContextSample=- Do not restart any services and daemons automatically without confirming first\n- When configuring a network interface, always use 192.168.1.1/24 as the gateway diff --git a/lang/strings/translations_es.properties b/lang/strings/translations_es.properties index e2ab03979..9ac72ba87 100644 --- a/lang/strings/translations_es.properties +++ b/lang/strings/translations_es.properties @@ -1909,3 +1909,6 @@ activeLicense=Licencia activeLicenseDescription=Activar una clave de licencia XPipe authenticatorApp=Aplicación Autenticador securityKey=Clave de seguridad +mcpAdditionalContext=Contexto MCP adicional +mcpAdditionalContextDescription=Instrucciones adicionales para pasar al cliente MCP. Utilízalo para controlar el comportamiento del agente y proporcionar contexto adicional para tu configuración individual. +mcpAdditionalContextSample=- No reinicies ningún servicio o demonio automáticamente sin confirmarlo primero\n- Al configurar una interfaz de red, utiliza siempre 192.168.1.1/24 como puerta de enlace diff --git a/lang/strings/translations_fr.properties b/lang/strings/translations_fr.properties index df76d47a6..e9c944c83 100644 --- a/lang/strings/translations_fr.properties +++ b/lang/strings/translations_fr.properties @@ -1949,3 +1949,6 @@ activeLicense=Licence activeLicenseDescription=Activer une clé de licence XPipe authenticatorApp=Application Authenticator securityKey=Clé de sécurité +mcpAdditionalContext=Contexte MCP supplémentaire +mcpAdditionalContextDescription=Instructions supplémentaires à transmettre au client MCP. Sert à contrôler le comportement de l'agent et à fournir un contexte supplémentaire pour ta configuration individuelle. +mcpAdditionalContextSample=- Ne redémarre pas automatiquement les services et les démons sans confirmer au préalable\n- Lors de la configuration d'une interface réseau, utilise toujours 192.168.1.1/24 comme passerelle diff --git a/lang/strings/translations_id.properties b/lang/strings/translations_id.properties index 9e78e9619..f25bf1aa8 100644 --- a/lang/strings/translations_id.properties +++ b/lang/strings/translations_id.properties @@ -1909,3 +1909,6 @@ activeLicense=Lisensi activeLicenseDescription=Mengaktifkan kunci lisensi XPipe authenticatorApp=Aplikasi autentikator securityKey=Kunci keamanan +mcpAdditionalContext=Konteks MCP tambahan +mcpAdditionalContextDescription=Instruksi tambahan untuk diteruskan ke klien MCP. Gunakan ini untuk mengontrol perilaku agen dan memberikan konteks tambahan untuk pengaturan individual Anda. +mcpAdditionalContextSample=- Jangan memulai ulang layanan dan daemon apa pun secara otomatis tanpa mengonfirmasi terlebih dahulu\n- Saat mengonfigurasi antarmuka jaringan, selalu gunakan 192.168.1.1/24 sebagai gateway diff --git a/lang/strings/translations_it.properties b/lang/strings/translations_it.properties index d6d7d703f..121798c5b 100644 --- a/lang/strings/translations_it.properties +++ b/lang/strings/translations_it.properties @@ -1909,3 +1909,6 @@ activeLicense=Licenza activeLicenseDescription=Attivare una chiave di licenza XPipe authenticatorApp=App Autenticatore securityKey=Chiave di sicurezza +mcpAdditionalContext=Contesto MCP aggiuntivo +mcpAdditionalContextDescription=Istruzioni aggiuntive da passare al client MCP. Utilizzale per controllare il comportamento dell'agente e fornire un contesto aggiuntivo per la tua configurazione individuale. +mcpAdditionalContextSample=- Non riavviare automaticamente i servizi e i demoni senza averne prima avuto conferma\n- Quando configuri un'interfaccia di rete, usa sempre 192.168.1.1/24 come gateway diff --git a/lang/strings/translations_ja.properties b/lang/strings/translations_ja.properties index f885561dd..78baee7c2 100644 --- a/lang/strings/translations_ja.properties +++ b/lang/strings/translations_ja.properties @@ -1909,3 +1909,6 @@ activeLicense=ライセンス activeLicenseDescription=XPipeライセンスキーをアクティベートする authenticatorApp=認証アプリ securityKey=セキュリティキー +mcpAdditionalContext=追加のMCPコンテキスト +mcpAdditionalContextDescription=MCP クライアントに渡す追加の指示。エージェントの動作を制御し、個々の設定に追加のコンテキストを提供するために使用する。 +mcpAdditionalContextSample=- 最初に確認することなく、自動的にサービスやデーモンを再起動しないこと\n- ネットワークインターフェイスを設定するときは、常に192.168.1.1/24をゲートウェイとして使用すること diff --git a/lang/strings/translations_ko.properties b/lang/strings/translations_ko.properties index 3525fbfaa..4b70fc50d 100644 --- a/lang/strings/translations_ko.properties +++ b/lang/strings/translations_ko.properties @@ -1962,3 +1962,6 @@ activeLicense=라이선스 activeLicenseDescription=XPipe 라이선스 키 활성화 authenticatorApp=인증 앱 securityKey=보안 키 +mcpAdditionalContext=추가 MCP 컨텍스트 +mcpAdditionalContextDescription=MCP 클라이언트에 전달할 추가 지침입니다. 이를 사용하여 상담원 동작을 제어하고 개별 설정에 대한 추가 컨텍스트를 제공할 수 있습니다. +mcpAdditionalContextSample=- 먼저 확인하지 않고 서비스 및 데몬을 자동으로 다시 시작하지 마세요\n- 네트워크 인터페이스를 구성할 때는 항상 192.168.1.1/24를 게이트웨이로 사용하세요 diff --git a/lang/strings/translations_nl.properties b/lang/strings/translations_nl.properties index 6b1ecd4c0..01e699076 100644 --- a/lang/strings/translations_nl.properties +++ b/lang/strings/translations_nl.properties @@ -1909,3 +1909,6 @@ activeLicense=Licentie activeLicenseDescription=Een XPipe-licentiesleutel activeren authenticatorApp=Authenticator app securityKey=Beveiligingssleutel +mcpAdditionalContext=Extra MCP-context +mcpAdditionalContextDescription=Extra instructies om door te geven aan de MCP-client. Gebruik dit om het gedrag van de agent te regelen en aanvullende context te leveren voor je individuele opstelling. +mcpAdditionalContextSample=- Start geen services en daemons automatisch opnieuw op zonder dit eerst te bevestigen\n- Gebruik bij het configureren van een netwerkinterface altijd 192.168.1.1/24 als gateway diff --git a/lang/strings/translations_pl.properties b/lang/strings/translations_pl.properties index 70a0f8a06..6dcb92fdf 100644 --- a/lang/strings/translations_pl.properties +++ b/lang/strings/translations_pl.properties @@ -1910,3 +1910,6 @@ activeLicense=Licencja activeLicenseDescription=Aktywuj klucz licencyjny XPipe authenticatorApp=Aplikacja uwierzytelniająca securityKey=Klucz zabezpieczeń +mcpAdditionalContext=Dodatkowy kontekst MCP +mcpAdditionalContextDescription=Dodatkowe instrukcje do przekazania klientowi MCP. Użyj tego, aby kontrolować zachowanie agenta i zapewnić dodatkowy kontekst dla indywidualnej konfiguracji. +mcpAdditionalContextSample=- Nie uruchamiaj ponownie żadnych usług i demonów automatycznie bez uprzedniego potwierdzenia\n- Podczas konfigurowania interfejsu sieciowego zawsze używaj 192.168.1.1/24 jako bramy diff --git a/lang/strings/translations_pt.properties b/lang/strings/translations_pt.properties index ea71fe82c..8ab26e449 100644 --- a/lang/strings/translations_pt.properties +++ b/lang/strings/translations_pt.properties @@ -1909,3 +1909,6 @@ activeLicense=Licença activeLicenseDescription=Ativar uma chave de licença XPipe authenticatorApp=Aplicação de autenticação securityKey=Chave de segurança +mcpAdditionalContext=Contexto adicional do MCP +mcpAdditionalContextDescription=Instruções adicionais para passar para o cliente MCP. Utiliza isto para controlar o comportamento do agente e fornecer contexto adicional para a sua configuração individual. +mcpAdditionalContextSample=- Não reinicies automaticamente quaisquer serviços e daemons sem confirmar primeiro\n- Ao configurar uma interface de rede, utiliza sempre 192.168.1.1/24 como gateway diff --git a/lang/strings/translations_ru.properties b/lang/strings/translations_ru.properties index 640d2d465..2442716f6 100644 --- a/lang/strings/translations_ru.properties +++ b/lang/strings/translations_ru.properties @@ -2021,3 +2021,6 @@ activeLicense=Лицензия activeLicenseDescription=Активировать лицензионный ключ XPipe authenticatorApp=Приложение-аутентификатор securityKey=Ключ безопасности +mcpAdditionalContext=Дополнительный контекст MCP +mcpAdditionalContextDescription=Дополнительные инструкции, которые нужно передать MCP-клиенту. Используй это, чтобы управлять поведением агента и предоставлять дополнительный контекст для твоей индивидуальной настройки. +mcpAdditionalContextSample=- Не перезапускай службы и демоны автоматически без предварительного подтверждения\n- При настройке сетевого интерфейса всегда используй 192.168.1.1/24 в качестве шлюза diff --git a/lang/strings/translations_sv.properties b/lang/strings/translations_sv.properties index 96890c238..eba569271 100644 --- a/lang/strings/translations_sv.properties +++ b/lang/strings/translations_sv.properties @@ -1909,3 +1909,6 @@ activeLicense=Licens activeLicenseDescription=Aktivera en XPipe-licensnyckel authenticatorApp=Autentiseringsapplikation securityKey=Säkerhetsnyckel +mcpAdditionalContext=Ytterligare MCP-kontext +mcpAdditionalContextDescription=Ytterligare instruktioner att vidarebefordra till MCP-klienten. Använd detta för att styra agentens beteende och ge ytterligare sammanhang för din individuella installation. +mcpAdditionalContextSample=- Starta inte om några tjänster och daemons automatiskt utan att först bekräfta\n- När du konfigurerar ett nätverksgränssnitt ska du alltid använda 192.168.1.1/24 som gateway diff --git a/lang/strings/translations_tr.properties b/lang/strings/translations_tr.properties index 4ae82ae33..3977435be 100644 --- a/lang/strings/translations_tr.properties +++ b/lang/strings/translations_tr.properties @@ -1909,3 +1909,6 @@ activeLicense=Lisans activeLicenseDescription=XPipe lisans anahtarını etkinleştirme authenticatorApp=Authenticator uygulaması securityKey=Güvenlik anahtarı +mcpAdditionalContext=Ek MCP bağlamı +mcpAdditionalContextDescription=MCP istemcisine iletilecek ek talimatlar. Aracı davranışını kontrol etmek ve bireysel kurulumunuz için ek bağlam sağlamak için bunu kullanın. +mcpAdditionalContextSample=- Önce onaylamadan hiçbir hizmeti ve daemon'u otomatik olarak yeniden başlatmayın\n- Bir ağ arayüzünü yapılandırırken, ağ geçidi olarak her zaman 192.168.1.1/24 adresini kullanın diff --git a/lang/strings/translations_vi.properties b/lang/strings/translations_vi.properties index e1a7a493f..26399b136 100644 --- a/lang/strings/translations_vi.properties +++ b/lang/strings/translations_vi.properties @@ -1909,3 +1909,6 @@ activeLicense=Giấy phép activeLicenseDescription=Kích hoạt khóa cấp phép XPipe authenticatorApp=Ứng dụng xác thực securityKey=Khóa bảo mật +mcpAdditionalContext=Bối cảnh bổ sung của MCP +mcpAdditionalContextDescription=Hướng dẫn bổ sung để truyền cho khách hàng MCP. Sử dụng điều này để kiểm soát hành vi của đại lý và cung cấp bối cảnh bổ sung cho cấu hình cá nhân của cậu. +mcpAdditionalContextSample=- Không tự động khởi động lại bất kỳ dịch vụ hoặc daemon nào mà không xác nhận trước\n- Khi cấu hình giao diện mạng, luôn sử dụng 192.168.1.1/24 làm cổng mặc định diff --git a/lang/strings/translations_zh-Hans.properties b/lang/strings/translations_zh-Hans.properties index ee63d21cd..97783515f 100644 --- a/lang/strings/translations_zh-Hans.properties +++ b/lang/strings/translations_zh-Hans.properties @@ -2534,3 +2534,6 @@ activeLicense=许可证 activeLicenseDescription=激活 XPipe 许可证密钥 authenticatorApp=验证器应用程序 securityKey=安全密钥 +mcpAdditionalContext=其他 MCP 上下文 +mcpAdditionalContextDescription=传递给 MCP 客户端的附加指令。用它来控制代理行为,并为您的个性化设置提供额外的上下文。 +mcpAdditionalContextSample=- 未经确认,请勿自动重启任何服务和守护进程\n- 配置网络接口时,始终使用 192.168.1.1/24 作为网关 diff --git a/lang/strings/translations_zh-Hant.properties b/lang/strings/translations_zh-Hant.properties index d5722ae26..7037528fa 100644 --- a/lang/strings/translations_zh-Hant.properties +++ b/lang/strings/translations_zh-Hant.properties @@ -1909,3 +1909,6 @@ activeLicense=許可證 activeLicenseDescription=啟動 XPipe 授權金鑰 authenticatorApp=驗證器應用程式 securityKey=安全金鑰 +mcpAdditionalContext=其他 MCP 上下文 +mcpAdditionalContextDescription=傳送給 MCP 用戶端的附加指示。使用此項可控制代理程式的行為,並為您的個別設定提供額外的上下文。 +mcpAdditionalContextSample=- 在未確認之前,請勿自動重新啟動任何服務和 daemons\n- 設定網路介面時,請務必使用 192.168.1.1/24 作為閘道 diff --git a/version b/version index 539b8a835..425fa5e64 100644 --- a/version +++ b/version @@ -1 +1 @@ -21.3-9 +21.3-10