Enable native touch copy and paste on iOS#5961
Conversation
|
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 |
5810171 to
476617e
Compare
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.
476617e to
91d1754
Compare
|
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. |
| 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); | ||
| } |
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
Comment about the hover: none part making this stricter would be good
| addons.webgl.instance = new WebglAddon(); | ||
| } catch (e) { | ||
| console.warn(e); | ||
| if (!hasCoarsePrimaryPointer()) { |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
I think MouseService should own the detection logic and set the class, if other classes need it, a method should be exposed on MouseService
|
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. |
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:
xterm-native-touch-selectionto the terminal element..xterm-rowsselectable and avoid xterm's internal gesture/selection mousedown handling when mouse reporting is inactive.Verification
npm run buildnpm run lint-changesnpm run test-unitnpm run esbuildnpm run esbuild-demo-clientnpm run esbuild-demo-serverI 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 installcurrently 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.