From f17eed8fbbe9ef2a1903df22e38c9554b596d992 Mon Sep 17 00:00:00 2001 From: crschnick Date: Fri, 12 Jun 2026 15:45:11 +0000 Subject: [PATCH] Rework --- .../io/xpipe/app/prefs/TerminalCategory.java | 7 +++++ .../terminal/ScreenTerminalMultiplexer.java | 19 ++++++++++++ .../xpipe/app/terminal/TerminalLauncher.java | 4 +++ .../app/terminal/TerminalMultiplexer.java | 4 +++ .../terminal/TerminalMultiplexerManager.java | 30 ++++++++++++++++++- .../app/terminal/TmuxTerminalMultiplexer.java | 15 ++++++++++ .../terminal/ZellijTerminalMultiplexer.java | 16 ++++++++++ lang/strings/translations_en.properties | 5 ++++ 8 files changed, 99 insertions(+), 1 deletion(-) 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 53127f2d4..fb33509c6 100644 --- a/app/src/main/java/io/xpipe/app/prefs/TerminalCategory.java +++ b/app/src/main/java/io/xpipe/app/prefs/TerminalCategory.java @@ -12,6 +12,7 @@ import io.xpipe.app.hub.comp.StoreChoiceComp; import io.xpipe.app.hub.comp.StoreViewState; import io.xpipe.app.issue.ErrorEventFactory; import io.xpipe.app.platform.*; +import io.xpipe.app.process.LocalShell; import io.xpipe.app.process.ShellScript; import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStoreEntryRef; @@ -22,6 +23,7 @@ import io.xpipe.app.util.OsType; import javafx.beans.binding.Bindings; import javafx.beans.property.ReadOnlyBooleanWrapper; import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.geometry.Insets; import javafx.geometry.Pos; @@ -333,9 +335,11 @@ public class TerminalCategory extends AppPrefsCategory { var choice = choiceBuilder.build().buildComp(); choice.maxWidth(600); + var testCounter = new SimpleIntegerProperty(); var testDisabled = new SimpleBooleanProperty(); var test = new ButtonComp(AppI18n.observable("test"), new FontIcon("mdi2p-play"), () -> { testDisabled.set(true); + testCounter.set(testCounter.getValue() + 1); ThreadHelper.runFailableAsync(() -> { try { var term = AppPrefs.get().terminalType().getValue(); @@ -382,6 +386,9 @@ public class TerminalCategory extends AppPrefsCategory { .documentationLink(DocumentationLink.TERMINAL_MULTIPLEXER) .addComp(choice); if (OsType.ofLocal() == OsType.WINDOWS) { + options.disable(Bindings.createBooleanBinding(() -> { + return prefs.terminalProxy() == null && !TerminalMultiplexerManager.isAvailableOnWindows(); + }, prefs.terminalProxy(), testCounter)); options.disable(BindingsHelper.map(prefs.terminalProxy(), uuid -> uuid == null)); } options.addComp(test).hide(prefs.terminalMultiplexer.isNull()); diff --git a/app/src/main/java/io/xpipe/app/terminal/ScreenTerminalMultiplexer.java b/app/src/main/java/io/xpipe/app/terminal/ScreenTerminalMultiplexer.java index 3bd920dd4..744204683 100644 --- a/app/src/main/java/io/xpipe/app/terminal/ScreenTerminalMultiplexer.java +++ b/app/src/main/java/io/xpipe/app/terminal/ScreenTerminalMultiplexer.java @@ -1,19 +1,38 @@ package io.xpipe.app.terminal; +import io.xpipe.app.prefs.AppPrefs; +import io.xpipe.app.prefs.ExternalApplicationHelper; import io.xpipe.app.process.*; import com.fasterxml.jackson.annotation.JsonTypeName; +import io.xpipe.app.util.OsType; import io.xpipe.app.webtop.WebtopApp; import lombok.Builder; import lombok.extern.jackson.Jacksonized; import java.util.ArrayList; +import java.util.Optional; @Builder @Jacksonized @JsonTypeName("screen") public class ScreenTerminalMultiplexer implements TerminalMultiplexer { + @Override + public boolean requiresUnixEnvironment() { + return true; + } + + @Override + public boolean isSupported() throws Exception { + if (OsType.ofLocal() == OsType.WINDOWS) { + var p = TerminalProxyManager.getProxy(); + return p.isPresent() && p.get().view().findProgram("screen").isPresent(); + } else { + return LocalShell.getShell().view().findProgram("screen").isPresent(); + } + } + @Override public WebtopApp getRequiredWebtopApp() { return null; 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 9dd8db362..bed7e87c0 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalLauncher.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalLauncher.java @@ -239,6 +239,10 @@ public class TerminalLauncher { return false; } + if (OsType.ofLocal() == OsType.WINDOWS && multiplexer.get().requiresUnixEnvironment() && TerminalProxyManager.getProxy().isEmpty()) { + throw ErrorEventFactory.expected(new IllegalStateException("Terminal multiplexer is only supported with a WSL terminal environment on Windows")); + } + var control = TerminalProxyManager.getProxy().orElse(LocalShell.getShell()); // Throw if not supported diff --git a/app/src/main/java/io/xpipe/app/terminal/TerminalMultiplexer.java b/app/src/main/java/io/xpipe/app/terminal/TerminalMultiplexer.java index 428161cdf..2c4cedfba 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalMultiplexer.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalMultiplexer.java @@ -49,6 +49,10 @@ public interface TerminalMultiplexer { return null; } + boolean requiresUnixEnvironment(); + + boolean isSupported() throws Exception; + WebtopApp getRequiredWebtopApp(); boolean supportsSplitView(); diff --git a/app/src/main/java/io/xpipe/app/terminal/TerminalMultiplexerManager.java b/app/src/main/java/io/xpipe/app/terminal/TerminalMultiplexerManager.java index fb0b1d5b4..917e6e5d3 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalMultiplexerManager.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalMultiplexerManager.java @@ -1,6 +1,9 @@ package io.xpipe.app.terminal; +import io.xpipe.app.issue.ErrorEventFactory; import io.xpipe.app.prefs.AppPrefs; +import io.xpipe.app.process.LocalShell; +import io.xpipe.app.update.AppDistributionType; import io.xpipe.app.util.ThreadHelper; import io.xpipe.app.util.OsType; @@ -11,6 +14,7 @@ public class TerminalMultiplexerManager { private static final Map connectionHubRequests = new HashMap<>(); private static UUID pendingMultiplexerLaunch; private static UUID runningMultiplexerContainer; + private static Boolean availableOnWindows; public static void registerMultiplexerContainerLaunch(UUID uuid) { pendingMultiplexerLaunch = uuid; @@ -42,6 +46,19 @@ public class TerminalMultiplexerManager { TerminalView.get().addListener(listener); } + public static boolean isAvailableOnWindows() { + if (availableOnWindows != null) { + return availableOnWindows; + } + + try { + return (availableOnWindows = LocalShell.getShell().view().findProgram("zellij").isPresent()); + } catch (Exception e) { + ErrorEventFactory.fromThrowable(e).handle(); + return (availableOnWindows = false); + } + } + public static Optional getEffectiveMultiplexer() { var multiplexer = AppPrefs.get().terminalMultiplexer().getValue(); if (multiplexer == null) { @@ -53,9 +70,20 @@ public class TerminalMultiplexerManager { return Optional.empty(); } + // Don't apply multiplexer on webtop if it is not installed yet + if (AppDistributionType.get() == AppDistributionType.WEBTOP) { + try { + if (!multiplexer.isSupported()) { + return Optional.empty(); + } + } catch (Exception e) { + ErrorEventFactory.fromThrowable(e).handle(); + } + } + if (OsType.ofLocal() == OsType.WINDOWS) { var hasProxy = AppPrefs.get().terminalProxy().getValue() != null; - if (!hasProxy) { + if (!hasProxy && multiplexer.requiresUnixEnvironment()) { return Optional.empty(); } } diff --git a/app/src/main/java/io/xpipe/app/terminal/TmuxTerminalMultiplexer.java b/app/src/main/java/io/xpipe/app/terminal/TmuxTerminalMultiplexer.java index e5c804edf..e14b98588 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TmuxTerminalMultiplexer.java +++ b/app/src/main/java/io/xpipe/app/terminal/TmuxTerminalMultiplexer.java @@ -7,6 +7,7 @@ import io.xpipe.app.process.ShellControl; import io.xpipe.app.process.ShellScript; import com.fasterxml.jackson.annotation.JsonTypeName; +import io.xpipe.app.util.OsType; import io.xpipe.app.webtop.WebtopApp; import lombok.Builder; import lombok.extern.jackson.Jacksonized; @@ -19,6 +20,20 @@ import java.util.List; @JsonTypeName("tmux") public class TmuxTerminalMultiplexer implements TerminalMultiplexer { + @Override + public boolean requiresUnixEnvironment() { + return true; + } + + @Override + public boolean isSupported() throws Exception { + if (OsType.ofLocal() == OsType.WINDOWS) { + var p = TerminalProxyManager.getProxy(); + return p.isPresent() && p.get().view().findProgram("tmux").isPresent(); + } else { + return LocalShell.getShell().view().findProgram("tmux").isPresent(); + } + } @Override public WebtopApp getRequiredWebtopApp() { return WebtopApp.TMUX; diff --git a/app/src/main/java/io/xpipe/app/terminal/ZellijTerminalMultiplexer.java b/app/src/main/java/io/xpipe/app/terminal/ZellijTerminalMultiplexer.java index 83b86319f..3e803aea6 100644 --- a/app/src/main/java/io/xpipe/app/terminal/ZellijTerminalMultiplexer.java +++ b/app/src/main/java/io/xpipe/app/terminal/ZellijTerminalMultiplexer.java @@ -20,6 +20,22 @@ import java.util.List; @JsonTypeName("zellij") public class ZellijTerminalMultiplexer implements TerminalMultiplexer { + @Override + public boolean requiresUnixEnvironment() { + return true; + } + + @Override + public boolean isSupported() throws Exception { + if (OsType.ofLocal() == OsType.WINDOWS) { + var p = TerminalProxyManager.getProxy(); + if (p.isPresent() && p.get().view().findProgram("zellij").isPresent()) { + return true; + } + } + return LocalShell.getShell().view().findProgram("zellij").isPresent(); + } + @Override public WebtopApp getRequiredWebtopApp() { return WebtopApp.ZELLIJ; diff --git a/lang/strings/translations_en.properties b/lang/strings/translations_en.properties index 41eab1c67..bc4dc3716 100644 --- a/lang/strings/translations_en.properties +++ b/lang/strings/translations_en.properties @@ -2236,3 +2236,8 @@ configureWebtop=Configure webtop #context: verb transfer=Transfer rebuild=Rebuild +webtop.displayName=XPipe Webtop +webtop.displayDescription=Access and manage a webtop environment remotely +webtopHost=Host +webtopHostDescription=The container the webtop environment is running on +