#1537·ttyd

Support Ctrl+C to copy selected text in the web terminal

Author: yuuuuuuanCreated May 13, 2026Updated May 13, 2026
Labelsenhancement

Feature request: Support Ctrl+C to copy selected text in the web terminal

Description

Currently, in ttyd's web terminal, Ctrl+C is mainly used to send SIGINT to the running shell/program, while Ctrl+Shift+C may conflict with the browser's developer tools shortcut.

This makes copying text from the terminal inconvenient, especially when using ttyd in a browser for daily remote development.

Expected behavior

I would like ttyd to support the following behavior:

  • If there is selected text in the terminal:
    • Pressing Ctrl+C should copy the selected text to the clipboard.
  • If there is no selected text:
    • Pressing Ctrl+C should keep the current behavior and send SIGINT to the terminal.

This behavior is similar to many modern terminal applications.

Current behavior

  • Ctrl+C always sends SIGINT.
  • Ctrl+Shift+C may trigger browser developer tools or element selection mode, depending on the browser.
  • Copying terminal text requires using the mouse context menu or other browser-specific shortcuts.

Why this is useful

This would improve the user experience when using ttyd as a web-based terminal, especially for users who frequently copy logs, command output, paths, or error messages.

It also avoids conflicting with browser shortcuts such as Ctrl+Shift+C.

Possible implementation idea

The frontend could use xterm.js's custom key event handling logic.

For example:

typescript
term.attachCustomKeyEventHandler((ev: KeyboardEvent) => {
  if (ev.type !== "keydown") {
    return true;
  }

  const key = ev.key.toLowerCase();

  if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && key === "c") {
    const selectedText = term.getSelection();

    if (selectedText && selectedText.length > 0) {
      navigator.clipboard.writeText(selectedText).catch((err) => {
        console.error("copy failed:", err);
      });

      term.clearSelection();

      ev.preventDefault();
      ev.stopPropagation();

      return false;
    }

    return true;
  }

  return true;
});