diff --git a/config.example.toml b/config.example.toml index ddd7b04..2b965d1 100644 --- a/config.example.toml +++ b/config.example.toml @@ -30,3 +30,4 @@ next_floating = "ALT+F12" close_pane = "ALT+X" open_font_selector = "ALT+T" open_scrollback = "ALT+S" +paste = "CTRL+SHIFT+V" diff --git a/src/main/java/com/gregor/jprototerm/AppConfig.java b/src/main/java/com/gregor/jprototerm/AppConfig.java index 2424662..fac9e3c 100644 --- a/src/main/java/com/gregor/jprototerm/AppConfig.java +++ b/src/main/java/com/gregor/jprototerm/AppConfig.java @@ -43,7 +43,8 @@ public record AppConfig( "previous_tab", "next_tab", "open_font_selector", - "open_scrollback" + "open_scrollback", + "paste" ); public static AppConfig load() { @@ -102,7 +103,8 @@ public record AppConfig( Map.entry("previous_tab", KeyBinding.parse("ALT+SHIFT+H")), Map.entry("next_tab", KeyBinding.parse("ALT+SHIFT+L")), Map.entry("open_font_selector", KeyBinding.parse("ALT+T")), - Map.entry("open_scrollback", KeyBinding.parse("ALT+S")) + Map.entry("open_scrollback", KeyBinding.parse("ALT+S")), + Map.entry("paste", KeyBinding.parse("CTRL+SHIFT+V")) ) ); } diff --git a/src/main/java/com/gregor/jprototerm/Main.java b/src/main/java/com/gregor/jprototerm/Main.java index e7464e3..336afa7 100644 --- a/src/main/java/com/gregor/jprototerm/Main.java +++ b/src/main/java/com/gregor/jprototerm/Main.java @@ -11,6 +11,7 @@ import javafx.scene.control.Dialog; import javafx.scene.control.Label; import javafx.scene.control.Spinner; import javafx.scene.control.SpinnerValueFactory; +import javafx.scene.input.Clipboard; import javafx.scene.input.KeyEvent; import javafx.scene.layout.GridPane; import javafx.scene.layout.StackPane; @@ -131,6 +132,9 @@ public final class Main extends Application { } else if (config.keybindings().get("open_scrollback").matches(event)) { openScrollbackInEditor(); event.consume(); + } else if (config.keybindings().get("paste").matches(event)) { + pasteFromClipboard(); + event.consume(); } else { String encoded = KeyEncoder.encode(event); if (encoded != null) { @@ -152,6 +156,13 @@ public final class Main extends Application { } } + private void pasteFromClipboard() { + Clipboard clipboard = Clipboard.getSystemClipboard(); + if (clipboard.hasString()) { + compositor.activePane().paste(clipboard.getString()); + } + } + private void openFontSelector() { Dialog dialog = new Dialog<>(); dialog.setTitle("Font"); diff --git a/src/main/java/com/gregor/jprototerm/TerminalPane.java b/src/main/java/com/gregor/jprototerm/TerminalPane.java index de70d3c..592b945 100644 --- a/src/main/java/com/gregor/jprototerm/TerminalPane.java +++ b/src/main/java/com/gregor/jprototerm/TerminalPane.java @@ -115,6 +115,19 @@ public final class TerminalPane implements AutoCloseable, RenderTarget { } } + /** + * Paste text to the shell: ghostty sanitises it (stripping anything that could smuggle in + * control sequences) and wraps it in the bracketed-paste markers, then it goes to the pty + * like typed input. We always request bracketed mode — every modern shell and editor enables + * DECSET 2004, and the jlibghostty API does not expose querying the terminal's live mode. + */ + public void paste(String text) { + if (text == null || text.isEmpty()) { + return; + } + send(Ghostty.encodePaste(text, true)); + } + public boolean sendMouse(MouseInput input, MouseEncoderSize size, boolean anyButtonPressed) { synchronized (terminal) { mouseEncoder.syncFromTerminal(terminal);