Skip to content

Conversation

@tobilg
Copy link

@tobilg tobilg commented Dec 7, 2025

Problem

When selecting text in the terminal and pressing Cmd+C (or releasing the mouse after a selection), the clipboard copy operation fails silently in Safari. The same operations work correctly in Chrome.

Root Cause

Safari has stricter security requirements for clipboard operations than Chrome:

  1. User Gesture Requirement: Safari requires clipboard operations to happen synchronously within a user gesture (click, keypress, etc.)

  2. Async Context Invalidation: The original copyToClipboard method was async and used await navigator.clipboard.writeText(). By the time the await resolves, Safari has already invalidated the user gesture context, causing the operation to fail silently.

  3. Canvas-based Selection: When pressing Cmd+C, the input handler was letting the event pass through to the browser. However, since ghostty-web uses a <canvas> for rendering (not actual DOM text), there's no native text selection for the browser to copy.

Solution

1. Use Modern Clipboard APIs with Safari Compatibility (selection-manager.ts)

Rewrote the clipboard copy strategy to prioritize modern APIs while maintaining Safari compatibility:

  1. First try: ClipboardItem API

    • Modern API that works in Safari and Chrome
    • Safari requires the ClipboardItem to be created synchronously within the user gesture
    • The actual write is async but Safari allows this pattern
  2. Second try: navigator.clipboard.writeText()

    • Modern async API
    • Works in Chrome and Firefox
    • May fail in Safari (falls back to execCommand)
  3. Third try: Legacy document.execCommand('copy')

    • Fallback for older browsers
    • Uses hidden textarea to copy text

Helper methods extracted for cleaner fallback chain:

  • copyWithWriteText() - wraps writeText with execCommand fallback
  • copyWithExecCommand() - legacy copy using textarea

2. Add Public copySelection() Method (selection-manager.ts, terminal.ts)

Added a public method to programmatically trigger clipboard copy:

// SelectionManager
copySelection(): boolean {
  if (!this.hasSelection()) return false;
  const text = this.getSelection();
  if (text) {
    this.copyToClipboard(text);
    return true;
  }
  return false;
}

// Terminal
public copySelection(): boolean {
  return this.selectionManager?.copySelection() || false;
}

3. Handle Cmd+C Keyboard Shortcut (input-handler.ts, terminal.ts)

Added an onCopy callback to the InputHandler that gets called when Cmd+C is pressed:

// In InputHandler constructor
onCopy?: () => boolean

// In handleKeyDown
if (event.metaKey && event.code === 'KeyC') {
  if (this.onCopyCallback && this.onCopyCallback()) {
    event.preventDefault();
  }
  return;
}

// Terminal passes the callback
() => {
  return this.copySelection();
}

Files Changed

File Changes
lib/selection-manager.ts Rewrote copyToClipboard() to try modern APIs first (ClipboardItem → writeText → execCommand); extracted helper methods; added public copySelection() method
lib/input-handler.ts Added onCopy callback parameter; updated Cmd+C handling to use callback
lib/terminal.ts Added public copySelection() method; pass copy callback to InputHandler

Browser Compatibility

Browser Method Used Copy on Selection Cmd+C Copy
Chrome ClipboardItem
Safari ClipboardItem
Firefox writeText*
Edge ClipboardItem
Older browsers execCommand

*Firefox stable doesn't support ClipboardItem, so it falls through to writeText which works well.

Testing

To verify the fix works:

  1. Open your terminal application in Safari
  2. Select some text by clicking and dragging
  3. Release the mouse - text should be copied to clipboard automatically
  4. Alternatively, select text and press Cmd+C
  5. Paste (Cmd+V) in another application to verify the text was copied

References

Copy link
Member

@kylecarbs kylecarbs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants