From b7e71fb727dc91979ba47f6a1e6b17a86f4841ea Mon Sep 17 00:00:00 2001
From: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com>
Date: Mon, 20 Apr 2026 14:15:57 +0530
Subject: [PATCH] fix(tui): fix Linux Ctrl+C regression, remove double
clipboard write
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Fix critical regression: on Linux, Ctrl+C could not interrupt/clear/exit
because isAction(key,'c') shadowed the isCtrl block (both resolve to k.ctrl
on non-macOS). Restructured: isAction block now falls through to interrupt
logic on non-macOS when no selection exists.
- Remove double pbcopy: ink's copySelection() already calls setClipboard()
which handles pbcopy+tmux+OSC52. The extra writeClipboardText call in
useInputHandlers copySelection() was firing pbcopy a second time.
- Remove allowClipboardHotkeys prop from TextInput — every caller passed
isMac, and TextInput already imports isMac. Eliminated prop-drilling
through appLayout, maskedPrompt, and prompts.
- Remove dead code: the isCtrl copy paths (lines 277-288) were unreachable
on any platform after the isAction block changes.
- Simplify textInput Cmd+C: use writeClipboardText directly without the
redundant OSC52 fallback (this path is macOS-only where pbcopy works).
---
ui-tui/src/app/useInputHandlers.ts | 47 +++++++-------------------
ui-tui/src/components/appLayout.tsx | 2 --
ui-tui/src/components/maskedPrompt.tsx | 3 +-
ui-tui/src/components/prompts.tsx | 2 +-
ui-tui/src/components/textInput.tsx | 15 +++-----
5 files changed, 18 insertions(+), 51 deletions(-)
diff --git a/ui-tui/src/app/useInputHandlers.ts b/ui-tui/src/app/useInputHandlers.ts
index 83fd3385..be2e5379 100644
--- a/ui-tui/src/app/useInputHandlers.ts
+++ b/ui-tui/src/app/useInputHandlers.ts
@@ -8,8 +8,6 @@ import type {
VoiceRecordResponse
} from '../gatewayTypes.js'
-import { writeClipboardText } from '../lib/clipboard.js'
-import { writeOsc52Clipboard } from '../lib/osc52.js'
import { isAction, isMac } from '../lib/platform.js'
import { getInputSelection } from './inputSelectionStore.js'
@@ -30,19 +28,13 @@ export function useInputHandlers(ctx: InputHandlerContext): InputHandlerResult {
const pagerPageSize = Math.max(5, (terminal.stdout?.rows ?? 24) - 6)
const copySelection = () => {
+ // ink's copySelection() already calls setClipboard() which handles
+ // pbcopy (macOS), wl-copy/xclip (Linux), tmux, and OSC 52 fallback.
const text = terminal.selection.copySelection()
- if (!text) {
- return
+ if (text) {
+ actions.sys(`copied ${text.length} chars`)
}
-
- void writeClipboardText(text).then(copied => {
- if (!copied) {
- writeOsc52Clipboard(text)
- }
- })
-
- actions.sys(`copied ${text.length} chars`)
}
const clearSelection = () => {
@@ -259,34 +251,19 @@ export function useInputHandlers(ctx: InputHandlerContext): InputHandlerResult {
const inputSel = getInputSelection()
if (inputSel && inputSel.end > inputSel.start) {
- const text = inputSel.value.slice(inputSel.start, inputSel.end)
-
- void writeClipboardText(text).then(copied => {
- if (!copied) {
- writeOsc52Clipboard(text)
- }
- })
-
- inputSel.clear()
- }
-
- return
- }
-
- if (isCtrl(key, ch, 'c')) {
- if (!isMac && terminal.hasSelection) {
- return copySelection()
- }
-
- const inputSel = getInputSelection()
-
- if (!isMac && inputSel && inputSel.end > inputSel.start) {
- writeOsc52Clipboard(inputSel.value.slice(inputSel.start, inputSel.end))
inputSel.clear()
return
}
+ // On macOS, Cmd+C with no selection is a no-op (Ctrl+C below handles interrupt).
+ // On non-macOS, isAction uses Ctrl, so fall through to interrupt/clear/exit.
+ if (isMac) {
+ return
+ }
+ }
+
+ if (key.ctrl && ch.toLowerCase() === 'c') {
if (live.busy && live.sid) {
return turnController.interruptTurn({
appendMessage: actions.appendMessage,
diff --git a/ui-tui/src/components/appLayout.tsx b/ui-tui/src/components/appLayout.tsx
index e1a4b558..f13adf1b 100644
--- a/ui-tui/src/components/appLayout.tsx
+++ b/ui-tui/src/components/appLayout.tsx
@@ -6,7 +6,6 @@ import type { AppLayoutProgressProps, AppLayoutProps } from '../app/interfaces.j
import { $isBlocked } from '../app/overlayStore.js'
import { $uiState } from '../app/uiStore.js'
import { PLACEHOLDER } from '../content/placeholders.js'
-import { isMac } from '../lib/platform.js'
import type { Theme } from '../theme.js'
import type { DetailsMode } from '../types.js'
@@ -236,7 +235,6 @@ const ComposerPane = memo(function ComposerPane({
{'> '}
-
+
)
diff --git a/ui-tui/src/components/prompts.tsx b/ui-tui/src/components/prompts.tsx
index 97c7c028..967634d4 100644
--- a/ui-tui/src/components/prompts.tsx
+++ b/ui-tui/src/components/prompts.tsx
@@ -126,7 +126,7 @@ export function ClarifyPrompt({ cols = 80, onAnswer, onCancel, req, t }: Clarify
{'> '}
-
+
diff --git a/ui-tui/src/components/textInput.tsx b/ui-tui/src/components/textInput.tsx
index 1d16bb21..d3529df3 100644
--- a/ui-tui/src/components/textInput.tsx
+++ b/ui-tui/src/components/textInput.tsx
@@ -5,7 +5,6 @@ import { useEffect, useMemo, useRef, useState } from 'react'
import { setInputSelection } from '../app/inputSelectionStore.js'
import { readClipboardText, writeClipboardText } from '../lib/clipboard.js'
import { isActionMod, isMac } from '../lib/platform.js'
-import { writeOsc52Clipboard } from '../lib/osc52.js'
type InkExt = typeof Ink & {
stringWidth: (s: string) => number
@@ -282,7 +281,6 @@ export function TextInput({
onChange,
onPaste,
onSubmit,
- allowClipboardHotkeys = false,
mask,
placeholder = '',
focus = true
@@ -508,12 +506,12 @@ export function TextInput({
(inp: string, k: Key, event: InputEvent) => {
const eventRaw = event.keypress.raw
- if (eventRaw === '\x1bv' || eventRaw === '\x1bV' || eventRaw === '\x16' || (allowClipboardHotkeys && isMac && k.meta && inp.toLowerCase() === 'v')) {
+ if (eventRaw === '\x1bv' || eventRaw === '\x1bV' || eventRaw === '\x16' || (isMac && k.meta && inp.toLowerCase() === 'v')) {
if (cbPaste.current) {
return void emitPaste({ cursor: curRef.current, hotkey: true, text: '', value: vRef.current })
}
- if (allowClipboardHotkeys) {
+ if (isMac) {
void readClipboardText().then(text => {
if (text) {
pastePlainText(text)
@@ -524,17 +522,13 @@ export function TextInput({
return
}
- if (allowClipboardHotkeys && isMac && k.meta && inp.toLowerCase() === 'c') {
+ if (isMac && k.meta && inp.toLowerCase() === 'c') {
const range = selRange()
if (range) {
const text = vRef.current.slice(range.start, range.end)
- void writeClipboardText(text).then(copied => {
- if (!copied) {
- writeOsc52Clipboard(text)
- }
- })
+ void writeClipboardText(text)
}
return
@@ -735,7 +729,6 @@ export interface PasteEvent {
}
interface TextInputProps {
- allowClipboardHotkeys?: boolean
columns?: number
focus?: boolean
mask?: string