fix(opencode): image paste on Windows Terminal 1.25+ with kitty keyboard (#17674)
parent
ba244a6e62
commit
1a4a6eabe2
|
|
@ -186,7 +186,7 @@ export function tui(input: {
|
||||||
targetFps: 60,
|
targetFps: 60,
|
||||||
gatherStats: false,
|
gatherStats: false,
|
||||||
exitOnCtrlC: false,
|
exitOnCtrlC: false,
|
||||||
useKittyKeyboard: {},
|
useKittyKeyboard: { events: process.platform === "win32" },
|
||||||
autoFocus: false,
|
autoFocus: false,
|
||||||
openConsoleOnError: false,
|
openConsoleOnError: false,
|
||||||
consoleOptions: {
|
consoleOptions: {
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import { usePromptStash } from "./stash"
|
||||||
import { DialogStash } from "../dialog-stash"
|
import { DialogStash } from "../dialog-stash"
|
||||||
import { type AutocompleteRef, Autocomplete } from "./autocomplete"
|
import { type AutocompleteRef, Autocomplete } from "./autocomplete"
|
||||||
import { useCommandDialog } from "../dialog-command"
|
import { useCommandDialog } from "../dialog-command"
|
||||||
import { useRenderer } from "@opentui/solid"
|
import { useKeyboard, useRenderer } from "@opentui/solid"
|
||||||
import { Editor } from "@tui/util/editor"
|
import { Editor } from "@tui/util/editor"
|
||||||
import { useExit } from "../../context/exit"
|
import { useExit } from "../../context/exit"
|
||||||
import { Clipboard } from "../../util/clipboard"
|
import { Clipboard } from "../../util/clipboard"
|
||||||
|
|
@ -356,6 +356,20 @@ export function Prompt(props: PromptProps) {
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Windows Terminal 1.25+ handles Ctrl+V on keydown when kitty events are
|
||||||
|
// enabled, but still reports the kitty key-release event. Probe on release.
|
||||||
|
if (process.platform === "win32") {
|
||||||
|
useKeyboard(
|
||||||
|
(evt) => {
|
||||||
|
if (!input.focused) return
|
||||||
|
if (evt.name === "v" && evt.ctrl && evt.eventType === "release") {
|
||||||
|
command.trigger("prompt.paste")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ release: true },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const ref: PromptRef = {
|
const ref: PromptRef = {
|
||||||
get focused() {
|
get focused() {
|
||||||
return input.focused
|
return input.focused
|
||||||
|
|
@ -850,10 +864,9 @@ export function Prompt(props: PromptProps) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Handle clipboard paste (Ctrl+V) - check for images first on Windows
|
// Check clipboard for images before terminal-handled paste runs.
|
||||||
// This is needed because Windows terminal doesn't properly send image data
|
// This helps terminals that forward Ctrl+V to the app; Windows
|
||||||
// through bracketed paste, so we need to intercept the keypress and
|
// Terminal 1.25+ usually handles Ctrl+V before this path.
|
||||||
// directly read from clipboard before the terminal handles it
|
|
||||||
if (keybind.match("input_paste", e)) {
|
if (keybind.match("input_paste", e)) {
|
||||||
const content = await Clipboard.read()
|
const content = await Clipboard.read()
|
||||||
if (content?.mime.startsWith("image/")) {
|
if (content?.mime.startsWith("image/")) {
|
||||||
|
|
@ -936,6 +949,9 @@ export function Prompt(props: PromptProps) {
|
||||||
// Replace CRLF first, then any remaining CR
|
// Replace CRLF first, then any remaining CR
|
||||||
const normalizedText = decodePasteBytes(event.bytes).replace(/\r\n/g, "\n").replace(/\r/g, "\n")
|
const normalizedText = decodePasteBytes(event.bytes).replace(/\r\n/g, "\n").replace(/\r/g, "\n")
|
||||||
const pastedContent = normalizedText.trim()
|
const pastedContent = normalizedText.trim()
|
||||||
|
|
||||||
|
// Windows Terminal <1.25 can surface image-only clipboard as an
|
||||||
|
// empty bracketed paste. Windows Terminal 1.25+ does not.
|
||||||
if (!pastedContent) {
|
if (!pastedContent) {
|
||||||
command.trigger("prompt.paste")
|
command.trigger("prompt.paste")
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,14 @@ export namespace Clipboard {
|
||||||
mime: string
|
mime: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Checks clipboard for images first, then falls back to text.
|
||||||
|
//
|
||||||
|
// On Windows prompt/ can call this from multiple paste signals because
|
||||||
|
// terminals surface image paste differently:
|
||||||
|
// 1. A forwarded Ctrl+V keypress
|
||||||
|
// 2. An empty bracketed-paste hint for image-only clipboard in Windows
|
||||||
|
// Terminal <1.25
|
||||||
|
// 3. A kitty Ctrl+V key-release fallback for Windows Terminal 1.25+
|
||||||
export async function read(): Promise<Content | undefined> {
|
export async function read(): Promise<Content | undefined> {
|
||||||
const os = platform()
|
const os = platform()
|
||||||
|
|
||||||
|
|
@ -58,6 +66,8 @@ export namespace Clipboard {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Windows/WSL: probe clipboard for images via PowerShell.
|
||||||
|
// Bracketed paste can't carry image data so we read it directly.
|
||||||
if (os === "win32" || release().includes("WSL")) {
|
if (os === "win32" || release().includes("WSL")) {
|
||||||
const script =
|
const script =
|
||||||
"Add-Type -AssemblyName System.Windows.Forms; $img = [System.Windows.Forms.Clipboard]::GetImage(); if ($img) { $ms = New-Object System.IO.MemoryStream; $img.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png); [System.Convert]::ToBase64String($ms.ToArray()) }"
|
"Add-Type -AssemblyName System.Windows.Forms; $img = [System.Windows.Forms.Clipboard]::GetImage(); if ($img) { $ms = New-Object System.IO.MemoryStream; $img.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png); [System.Convert]::ToBase64String($ms.ToArray()) }"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue