Hmbown/CodeWhale · error · ExecError
cursor position does not exist on touch devices
Error message
cursor position does not exist on touch devices
What it means
The HarmonyOS computer-use backend throws this from `cursor_position` because hdc-controlled HarmonyOS devices are touch devices with no mouse cursor; the backend fails closed instead of returning a fake coordinate. There is no way to query a pointer position on this platform, so the operation is intentionally unsupported.
Solutions
- Wrap cursor_position in a try/catch and treat 'not supported on touch devices' as 'no cursor' in your UI.
- Skip cursor-position logic entirely when the selected backend is HarmonyOS (touch-only).
- Use an element target with click/longClick instead of cursor-relative positioning for HarmonyOS automation.
Example fix
// before
const pos = await backend.cursor_position();
// after
let pos = null;
try { pos = await backend.cursor_position(); }
catch (e) { /* touch device: no cursor concept */ } Defensive patterns
Strategy: try-catch
Validate before calling
const isTouchBackend = backendName === "harmonyos";
Type guard
const cursorSupported = typeof backend?.cursor_position === "function" && backendName !== "harmonyos";
Try / catch
let pos;
try { pos = await backend.cursor_position(); }
catch (e) { if (String(e.message).includes("touch devices")) pos = null; else throw e; } Prevention
- Gate cursor-position UI on the backend's platform capabilities
- Never assume mouse semantics for touch-only backends
When it happens
Trigger: Calling `backend.cursor_position()` on a backend created via `create()` in harmonyos.mjs (backends/harmonyos.mjs:271).
Common situations: Generic computer-use automation code that queries cursor position across platforms; porting macOS/Windows-style mouse-based workflows to a HarmonyOS phone/tablet; implementing a UI status bar that shows cursor coordinates.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- harmony backend exposes display 1 only
- zoom is not supported on the harmony backend yet —…
- aa start failed
- Antigravity cloud-code is stream-only; blocking…
- Antigravity cloud-code tools are not implemented yet; send…
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/7873fd69d0e72400.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/src/backends/harmonyos.mjs:271
const d = Math.max(1, Math.min(5, Number(duration) || 1));
return uiInput(["longClick", "300", "300"]);
},
set_value: async ({ target, value }) => {
const b = await centerOf(target);
await uiInput(["click", String(b.cx), String(b.cy)]);
await new Promise((r) => setTimeout(r, 300));
await uiInput(["inputText", String(b.cx), String(b.cy), escDeviceText(String(value))]);
return { action_sent: true, strategy: "uitest-element" };
},
select_text: async () => { throw new ExecError("select_text is not exposed by uitest dumpLayout/uiInput on the harmony backend"); },
perform_action: async ({ target, action }) => {
const b = await centerOf(target);
if (action === "longClick") return uiInput(["longClick", String(b.cx), String(b.cy)]);
return uiInput(["click", String(b.cx), String(b.cy)]);
},
read_clipboard: async () => { throw new ExecError("clipboard read is not exposed by hdc on current HarmonyOS builds"); },
write_clipboard: async () => { throw new ExecError("clipboard write is not exposed by hdc on current HarmonyOS builds"); },
cursor_position: async () => { throw new ExecError("cursor position does not exist on touch devices"); },
recordingStart: async ({ intervalMs = 400 } = {}) => {
if (recording) throw new ExecError(`recording ${recording.id} already running`);
if (!(await have("ffmpeg"))) throw new ExecError("ffmpeg is required on the host to mux harmony snapshot-series recordings");
const id = crypto.randomBytes(4).toString("hex");
const dir = fs.mkdtempSync(path.join(os.tmpdir(), `cu-rec-${id}-`));
const startedAt = new Date().toISOString();
const rec = recording = { id, dir, startedAt, intervalMs, seq: 0, controller: new AbortController() };
const tick = () => {
if (rec.stopped || rec.pending) return rec.pending;
rec.pending = (async () => {
const opts = { timeoutMs: 15_000, signal: rec.controller.signal };
const remote = `${DEVICE_TMP}-rec-${id}-${String(rec.seq).padStart(5, "0")}.jpeg`;
try {
await deviceOut(["snapshot_display", "-f", remote], opts);
await exec.pullFile(remote, path.join(dir, `f${String(rec.seq).padStart(5, "0")}.jpeg`), opts);
rec.seq++;
} finally { await shell(["rm", "-f", remote], opts).catch(() => {}); }
})().catch(() => {}).finally(() => { rec.pending = null; });View on GitHub (pinned to 73e0f67d83)