From ef5ec5dc28ce668585f11c76d89622d6c13724f0 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Wed, 11 Feb 2026 07:17:01 -0600 Subject: [PATCH] fix(app): terminal copy/paste --- packages/app/src/components/terminal.tsx | 53 +++++++++++------------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/packages/app/src/components/terminal.tsx b/packages/app/src/components/terminal.tsx index 7a1b81f0c..2527c74ec 100644 --- a/packages/app/src/components/terminal.tsx +++ b/packages/app/src/components/terminal.tsx @@ -130,11 +130,12 @@ export const Terminal = (props: TerminalProps) => { const t = term if (!t) return t.focus() + t.textarea?.focus() setTimeout(() => t.textarea?.focus(), 0) } const handlePointerDown = () => { const activeElement = document.activeElement - if (activeElement instanceof HTMLElement && activeElement !== container) { + if (activeElement instanceof HTMLElement && activeElement !== container && !container.contains(activeElement)) { activeElement.blur() } focusTerminal() @@ -204,44 +205,32 @@ export const Terminal = (props: TerminalProps) => { ghostty = g term = t - const copy = () => { + const handleCopy = (event: ClipboardEvent) => { const selection = t.getSelection() - if (!selection) return false + if (!selection) return - const body = document.body - if (body) { - const textarea = document.createElement("textarea") - textarea.value = selection - textarea.setAttribute("readonly", "") - textarea.style.position = "fixed" - textarea.style.opacity = "0" - body.appendChild(textarea) - textarea.select() - const copied = document.execCommand("copy") - body.removeChild(textarea) - if (copied) return true - } + const clipboard = event.clipboardData + if (!clipboard) return - const clipboard = navigator.clipboard - if (clipboard?.writeText) { - clipboard.writeText(selection).catch(() => {}) - return true - } + event.preventDefault() + clipboard.setData("text/plain", selection) + } - return false + const handlePaste = (event: ClipboardEvent) => { + const clipboard = event.clipboardData + const text = clipboard?.getData("text/plain") ?? clipboard?.getData("text") ?? "" + if (!text) return + + event.preventDefault() + event.stopPropagation() + t.paste(text) } t.attachCustomKeyEventHandler((event) => { const key = event.key.toLowerCase() if (event.ctrlKey && event.shiftKey && !event.metaKey && key === "c") { - copy() - return true - } - - if (event.metaKey && !event.ctrlKey && !event.altKey && key === "c") { - if (!t.hasSelection()) return true - copy() + document.execCommand("copy") return true } @@ -252,6 +241,12 @@ export const Terminal = (props: TerminalProps) => { return matchKeybind(keybinds, event) }) + container.addEventListener("copy", handleCopy, true) + cleanups.push(() => container.removeEventListener("copy", handleCopy, true)) + + container.addEventListener("paste", handlePaste, true) + cleanups.push(() => container.removeEventListener("paste", handlePaste, true)) + const fit = new mod.FitAddon() const serializer = new SerializeAddon() cleanups.push(() => disposeIfDisposable(fit))