Skip to content

Enable native touch copy and paste on iOS#5961

Open
williamstein wants to merge 1 commit into
xtermjs:masterfrom
sagemathinc:issue-3727-native-dom-pr
Open

Enable native touch copy and paste on iOS#5961
williamstein wants to merge 1 commit into
xtermjs:masterfrom
sagemathinc:issue-3727-native-dom-pr

Conversation

@williamstein

Copy link
Copy Markdown

Summary

Fixes #3727 by letting iOS use native DOM selection and the native paste menu for terminals rendered with DOM rows.

The change is intentionally narrow:

  • Detect Apple touch devices with a coarse primary pointer and add xterm-native-touch-selection to the terminal element.
  • In that mode, make .xterm-rows selectable and avoid xterm's internal gesture/selection mousedown handling when mouse reporting is inactive.
  • Keep mouse reporting behavior intact for applications that enable mouse events.
  • Move the helper textarea to the cursor with a usable touch target so long-press paste works natively.
  • Prevent default textarea insertion when the paste event already provides clipboard data, avoiding duplicate pasted text.
  • Make the demo prefer the DOM renderer on coarse-pointer devices, since native iOS selection cannot select text rendered only to a WebGL canvas.

Verification

  • npm run build
  • npm run lint-changes
  • npm run test-unit
  • npm run esbuild
  • npm run esbuild-demo-client
  • npm run esbuild-demo-server
  • Chromium integration suites for core and all addons, run against system Chromium on this host

I also manually verified on iPhone Safari that terminal output can be selected/copied with the native iOS UI and that long-pressing the prompt cursor offers Paste and inserts text once.

Note: the local host is Ubuntu 26.04, and npx playwright install currently refuses to install Playwright browser binaries for this OS. Because of that I could run Chromium integration coverage via system Chromium, but not local Firefox/WebKit integration coverage.

@williamstein

williamstein commented May 29, 2026

Copy link
Copy Markdown
Author

Heh @Tyriar (Daniel) this is William (lead dev of cocalc, sagemath guy, we had lunch at Microsoft once), and I was considering a WASM competitors to xtermjs because it supported iphone copy/paste, which is something that didn't get supported when you built xterm.js out of termjs. #3727 is about this and has been open since I created it in 2022. I would much rather switch with xterm.js, so I wondered if codex could figure this copy/paste problem out, and after a bunch of false starts and some interactive debugging, it did I think properly solve it. This PR isn't a workaround like #3727 (comment) but an actual fix.

I don't use android and have no idea how this is relevant to that.

-- william

Add an iOS touch mode that lets native DOM selection handle terminal rows instead of xterm's internal gesture and selection handling. The mode is limited to Apple touch devices with coarse primary pointers, keeps mouse reporting behavior intact, and makes the helper textarea usable as the native paste target at the cursor.

Prevent default textarea insertion when paste events provide clipboard data so native paste does not duplicate input. Also make the demo prefer the DOM renderer on coarse-pointer devices, since native selection needs real row text rather than the WebGL canvas.

Verified with npm run build, npm run lint-changes, npm run test-unit, npm run esbuild, npm run esbuild-demo-client, npm run esbuild-demo-server, and Chromium integration suites for core and all addons. Manually verified native iOS copy and paste in the demo.
@williamstein williamstein force-pushed the issue-3727-native-dom-pr branch from 476617e to 91d1754 Compare May 29, 2026 19:57
@williamstein

Copy link
Copy Markdown
Author

Note that the PR includes a few related fixes in this commit: iOS native selection/paste, hidden helper caret, and hidden measurement DOM. I noticed these when fully testing the integration with cocalc. Basically there was an issue where the cursor was confusing and this resolved it.

@xtermjs xtermjs deleted a comment from cursor Bot May 30, 2026

@Tyriar Tyriar left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yep I remember out lunch 😃

Comment on lines +63 to +67
function shouldUseNativeTouchSelection(targetWindow: Window): boolean {
const navigator = targetWindow.navigator as Navigator & { maxTouchPoints?: number };
return (/iPad|iPhone|iPod/.test(navigator.userAgent) || (navigator.platform === 'MacIntel' && (navigator.maxTouchPoints ?? 0) > 1))
&& (targetWindow.matchMedia?.('(hover: none) and (pointer: coarse)').matches ?? true);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could we have a new option nativeSelection: 'auto' | boolean for this? It could explain that it only applies to DOM renderer and can only select within viewport by design. In the option's validation logic it could also resolve to true/false such that the webgl addon/demo can check it for the resolved value?


function shouldUseNativeTouchSelection(targetWindow: Window): boolean {
const navigator = targetWindow.navigator as Navigator & { maxTouchPoints?: number };
return (/iPad|iPhone|iPod/.test(navigator.userAgent) || (navigator.platform === 'MacIntel' && (navigator.maxTouchPoints ?? 0) > 1))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should support Android too, if you get AI to do this I can try test it. Is just checking course pointer/no hover the approach for this instead of getting into the UA?

function shouldUseNativeTouchSelection(targetWindow: Window): boolean {
const navigator = targetWindow.navigator as Navigator & { maxTouchPoints?: number };
return (/iPad|iPhone|iPod/.test(navigator.userAgent) || (navigator.platform === 'MacIntel' && (navigator.maxTouchPoints ?? 0) > 1))
&& (targetWindow.matchMedia?.('(hover: none) and (pointer: coarse)').matches ?? true);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Comment about the hover: none part making this stricter would be good

Comment thread demo/client/client.ts
addons.webgl.instance = new WebglAddon();
} catch (e) {
console.warn(e);
if (!hasCoarsePrimaryPointer()) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Instead of demo customization, could we check the option inside the webgl addon and throw when touch/native selection is enabled?

import { addDisposableListener } from 'browser/Dom';
import { MutableDisposable, toDisposable } from 'common/Lifecycle';

function shouldUseNativeTouchSelection(targetWindow: Window): boolean {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think MouseService should own the detection logic and set the class, if other classes need it, a method should be exposed on MouseService

@williamstein

Copy link
Copy Markdown
Author

thanks @Tyriar -- I'm extremely busy right now and also don't have access to android directly. I think the best thing is to let this sit for a bit until I've launched it in production and got some feed from cocalc users (which will have android). I'll follow up later.

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.

Copy and paste do not work on touch devices

2 participants