From 71e6050a71bd2c60e79b2251deb708ba22daa16d Mon Sep 17 00:00:00 2001 From: crschnick Date: Wed, 26 Mar 2025 19:51:05 +0000 Subject: [PATCH] Terminal rework --- .../io/xpipe/app/prefs/TerminalCategory.java | 2 +- .../app/terminal/TerminalMultiplexer.java | 1 + .../app/terminal/TmuxTerminalMultiplexer.java | 47 +++++++++++++++++++ .../terminal/ZellijTerminalMultiplexer.java | 4 +- .../xpipe/app/util/OptionsChoiceBuilder.java | 20 ++++++-- 5 files changed, 67 insertions(+), 7 deletions(-) create mode 100644 app/src/main/java/io/xpipe/app/terminal/TmuxTerminalMultiplexer.java 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 6c2f6f579..38355b632 100644 --- a/app/src/main/java/io/xpipe/app/prefs/TerminalCategory.java +++ b/app/src/main/java/io/xpipe/app/prefs/TerminalCategory.java @@ -217,7 +217,7 @@ public class TerminalCategory extends AppPrefsCategory { }); websiteLinkButton.minWidth(Region.USE_PREF_SIZE); websiteLinkButton.disable(Bindings.createBooleanBinding(() -> { - return prefs.terminalMultiplexer.getValue().getDocsLink() == null; + return prefs.terminalMultiplexer.getValue() == null || prefs.terminalMultiplexer.getValue().getDocsLink() == null; }, prefs.terminalMultiplexer)); var hbox = new HBox(entryComboBox, websiteLinkButton.createRegion()); 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 71f307336..674867490 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalMultiplexer.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalMultiplexer.java @@ -14,6 +14,7 @@ public interface TerminalMultiplexer { static List> getClasses() { var l = new ArrayList>(); + l.add(TmuxTerminalMultiplexer.class); l.add(ZellijTerminalMultiplexer.class); return l; } diff --git a/app/src/main/java/io/xpipe/app/terminal/TmuxTerminalMultiplexer.java b/app/src/main/java/io/xpipe/app/terminal/TmuxTerminalMultiplexer.java new file mode 100644 index 000000000..c5d83c718 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/terminal/TmuxTerminalMultiplexer.java @@ -0,0 +1,47 @@ +package io.xpipe.app.terminal; + +import com.fasterxml.jackson.annotation.JsonTypeName; +import io.xpipe.core.process.ShellControl; +import io.xpipe.core.process.ShellScript; +import io.xpipe.core.process.TerminalInitScriptConfig; +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; +import lombok.extern.jackson.Jacksonized; + +@Builder +@Jacksonized +@JsonTypeName("tmux") +public class TmuxTerminalMultiplexer implements TerminalMultiplexer { + + @Override + public String getDocsLink() { + return "https://github.com/tmux/tmux/wiki/Getting-Started"; + } + + @Override + public ShellScript launchScriptExternal(ShellControl control, String command, TerminalInitScriptConfig config) throws Exception { + return ShellScript.lines( + "tmux new-window -t xpipe -n \"" + escape(config.getDisplayName(), true) + "\" " + escape(command , false) + ); + } + + @Override + public ShellScript launchScriptSession(ShellControl control, String command, TerminalInitScriptConfig config) throws Exception { + return ShellScript.lines( + "tmux kill-session -t xpipe", + "tmux new-session -d -s xpipe", + "tmux rename-window \"" + escape(config.getDisplayName(), true) + "\"", + "tmux send-keys -t xpipe '" + escape(command, false) + "' Enter", + "tmux attach -d -t xpipe" + ); + } + + private String escape(String s, boolean quotes) { + var r = s.replaceAll("\\\\", "\\\\\\\\"); + if (quotes) { + r = r.replaceAll("\"", "\\\\\""); + } + return r; + } +} 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 dd51ed04a..d4fbf5cc4 100644 --- a/app/src/main/java/io/xpipe/app/terminal/ZellijTerminalMultiplexer.java +++ b/app/src/main/java/io/xpipe/app/terminal/ZellijTerminalMultiplexer.java @@ -22,9 +22,7 @@ import lombok.Getter; import lombok.ToString; import lombok.extern.jackson.Jacksonized; -@Getter -@Builder(toBuilder = true) -@ToString +@Builder @Jacksonized @JsonTypeName("zellij") public class ZellijTerminalMultiplexer implements TerminalMultiplexer { diff --git a/app/src/main/java/io/xpipe/app/util/OptionsChoiceBuilder.java b/app/src/main/java/io/xpipe/app/util/OptionsChoiceBuilder.java index f29cf0508..efd97dcc4 100644 --- a/app/src/main/java/io/xpipe/app/util/OptionsChoiceBuilder.java +++ b/app/src/main/java/io/xpipe/app/util/OptionsChoiceBuilder.java @@ -3,6 +3,7 @@ package io.xpipe.app.util; import com.fasterxml.jackson.annotation.JsonTypeName; import io.xpipe.app.comp.base.ChoicePaneComp; import io.xpipe.app.core.AppI18n; +import io.xpipe.app.terminal.ZellijTerminalMultiplexer; import javafx.beans.property.ObjectProperty; import javafx.beans.property.Property; import javafx.beans.property.SimpleIntegerProperty; @@ -29,12 +30,25 @@ public class OptionsChoiceBuilder { return null; } - private static OptionsBuilder createOptionsForClass(Class c, Property property) { + private static OptionsBuilder createOptionsForClass(Class c, Property property) { try { var method = c.getDeclaredMethod("createOptions", Property.class); method.setAccessible(true); var r = method.invoke(null, property); - return r != null ? (OptionsBuilder) r : new OptionsBuilder(); + if (r != null) { + return (OptionsBuilder) r; + } + } catch (Exception ignored) {} + + try { + var bm = c.getDeclaredMethod("builder"); + bm.setAccessible(true); + var b = bm.invoke(null); + var m = b.getClass().getDeclaredMethod("build"); + m.setAccessible(true); + var defValue = c.cast(m.invoke(b)); + var def = new OptionsBuilder().bind(() -> defValue, property); + return def; } catch (Exception e) { return new OptionsBuilder(); } @@ -80,7 +94,7 @@ public class OptionsChoiceBuilder { map.put(AppI18n.observable("none"), new OptionsBuilder()); } for (int i = 0; i < sub.size(); i++) { - map.put(AppI18n.observable(createIdForClass(sub.get(i))), createOptionsForClass(sub.get(i), properties.get(i))); + map.put(AppI18n.observable(createIdForClass(sub.get(i))), createOptionsForClass(sub.get(i), properties.get(i + (allowNull ? 1 : 0)))); } return new OptionsBuilder()