diff --git a/electron-builder.json5 b/electron-builder.json5 index 741975b..dc6687d 100644 --- a/electron-builder.json5 +++ b/electron-builder.json5 @@ -4,10 +4,9 @@ "appId": "com.siddharthvaddem.openscreen", "asar": true, // .node binaries can't be dlopen'd from inside an asar — must live unpacked. - "asarUnpack": [ - "node_modules/uiohook-napi/**/*", - "**/*.node" - ], + "asarUnpack": [ + "**/*.node" + ], "productName": "Openscreen", "npmRebuild": true, "buildDependenciesFromSource": true, @@ -15,12 +14,12 @@ "directories": { "output": "release/${version}" }, - "files": [ - "dist", - "dist-electron", - "!*.png", - "!preview*.png", - "!*.md", + "files": [ + "dist", + "dist-electron", + "!*.png", + "!preview*.png", + "!*.md", "!README.md", "!CONTRIBUTING.md", "!LICENSE" @@ -65,19 +64,19 @@ "artifactName": "${productName}-Linux-${version}.${ext}", "category": "AudioVideo" }, - "win": { - "target": [ - "nsis" - ], - "icon": "icons/icons/win/icon.ico", - "extraResources": [ - { - "from": "electron/native/bin", - "to": "electron/native/bin", - "filter": ["**/*"] - } - ] - }, + "win": { + "target": [ + "nsis" + ], + "icon": "icons/icons/win/icon.ico", + "extraResources": [ + { + "from": "electron/native/bin", + "to": "electron/native/bin", + "filter": ["**/*"] + } + ] + }, "nsis": { "oneClick": false, "allowToChangeInstallationDirectory": true diff --git a/electron/electron-env.d.ts b/electron/electron-env.d.ts index 05ed723..e92ce19 100644 --- a/electron/electron-env.d.ts +++ b/electron/electron-env.d.ts @@ -40,11 +40,6 @@ interface Window { status: string; error?: string; }>; - requestAccessibilityAccess: () => Promise<{ - success: boolean; - granted: boolean; - error?: string; - }>; assetBaseUrl: string; storeRecordedVideo: ( videoData: ArrayBuffer, diff --git a/electron/ipc/handlers.ts b/electron/ipc/handlers.ts index 6faaa4b..9797c95 100644 --- a/electron/ipc/handlers.ts +++ b/electron/ipc/handlers.ts @@ -975,22 +975,6 @@ export function registerIpcHandlers( } }); - // macOS Accessibility prompt for global click capture. First call shows the - // system dialog; the user has to toggle the app in System Settings (no - // programmatic grant exists for Accessibility). - ipcMain.handle("request-accessibility-access", () => { - if (process.platform !== "darwin") { - return { success: true, granted: true }; - } - try { - const granted = systemPreferences.isTrustedAccessibilityClient(true); - return { success: true, granted }; - } catch (error) { - console.error("Failed to request accessibility access:", error); - return { success: false, granted: false, error: String(error) }; - } - }); - ipcMain.handle("open-source-selector", () => { const sourceSelectorWin = getSourceSelectorWindow(); if (sourceSelectorWin) { diff --git a/electron/native-bridge/cursor/recording/factory.ts b/electron/native-bridge/cursor/recording/factory.ts index 7835841..e072b75 100644 --- a/electron/native-bridge/cursor/recording/factory.ts +++ b/electron/native-bridge/cursor/recording/factory.ts @@ -1,6 +1,6 @@ import type { Rectangle } from "electron"; -import type { CursorRecordingData } from "../../../../src/native/contracts"; import type { CursorRecordingSession } from "./session"; +import { TelemetryRecordingSession } from "./telemetryRecordingSession"; import { WindowsNativeRecordingSession } from "./windowsNativeRecordingSession"; interface CreateCursorRecordingSessionOptions { @@ -12,21 +12,6 @@ interface CreateCursorRecordingSessionOptions { startTimeMs?: number; } -class NoopCursorRecordingSession implements CursorRecordingSession { - async start(): Promise { - // Native cursor capture is currently Windows-only. - } - - async stop(): Promise { - return { - version: 2, - provider: "none", - assets: [], - samples: [], - }; - } -} - export function createCursorRecordingSession( options: CreateCursorRecordingSessionOptions, ): CursorRecordingSession { @@ -40,5 +25,13 @@ export function createCursorRecordingSession( }); } - return new NoopCursorRecordingSession(); + // macOS / Linux: capture cursor positions via Electron's `screen` API on an + // interval. No cursor sprites/assets and no clicks — just position telemetry, + // which is what auto-zoom and other features consume. + return new TelemetryRecordingSession({ + getDisplayBounds: options.getDisplayBounds, + maxSamples: options.maxSamples, + sampleIntervalMs: options.sampleIntervalMs, + startTimeMs: options.startTimeMs, + }); } diff --git a/electron/preload.ts b/electron/preload.ts index 022eb79..8302b95 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -48,10 +48,6 @@ contextBridge.exposeInMainWorld("electronAPI", { requestCameraAccess: () => { return ipcRenderer.invoke("request-camera-access"); }, - requestAccessibilityAccess: () => { - return ipcRenderer.invoke("request-accessibility-access"); - }, - storeRecordedVideo: (videoData: ArrayBuffer, fileName: string) => { return ipcRenderer.invoke("store-recorded-video", videoData, fileName); }, diff --git a/package-lock.json b/package-lock.json index afe2091..50ecc9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,6 @@ "": { "name": "openscreen", "version": "1.4.0", - "hasInstallScript": true, "dependencies": { "@fix-webm-duration/fix": "^1.0.1", "@pixi/filter-drop-shadow": "^5.2.0", @@ -48,7 +47,6 @@ "sonner": "^2.0.7", "tailwind-merge": "^3.5.0", "tailwindcss-animate": "^1.0.7", - "uiohook-napi": "^1.5.5", "uuid": "^13.0.0", "web-demuxer": "^4.0.0" }, @@ -188,7 +186,6 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -397,7 +394,6 @@ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" } @@ -721,7 +717,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=20.19.0" }, @@ -770,7 +765,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=20.19.0" } @@ -1202,6 +1196,7 @@ "dev": true, "license": "BSD-2-Clause", "optional": true, + "peer": true, "dependencies": { "cross-dirname": "^0.1.0", "debug": "^4.3.4", @@ -1223,6 +1218,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -1239,6 +1235,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "universalify": "^2.0.0" }, @@ -1253,6 +1250,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "engines": { "node": ">= 10.0.0" } @@ -1962,6 +1960,7 @@ "resolved": "https://registry.npmjs.org/@pixi/color/-/color-7.4.3.tgz", "integrity": "sha512-a6R+bXKeXMDcRmjYQoBIK+v2EYqxSX49wcjAY579EYM/WrFKS98nSees6lqVUcLKrcQh2DT9srJHX7XMny3voQ==", "license": "MIT", + "peer": true, "dependencies": { "@pixi/colord": "^2.9.6" } @@ -1976,7 +1975,8 @@ "version": "7.4.3", "resolved": "https://registry.npmjs.org/@pixi/constants/-/constants-7.4.3.tgz", "integrity": "sha512-QGmwJUNQy/vVEHzL6VGQvnwawLZ1wceZMI8HwJAT4/I2uAzbBeFDdmCS8WsTpSWLZjF/DszDc1D8BFp4pVJ5UQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@pixi/core": { "version": "7.4.3", @@ -2003,7 +2003,8 @@ "version": "7.4.3", "resolved": "https://registry.npmjs.org/@pixi/extensions/-/extensions-7.4.3.tgz", "integrity": "sha512-FhoiYkHQEDYHUE7wXhqfsTRz6KxLXjuMbSiAwnLb9uG1vAgp6q6qd6HEsf4X30YaZbLFY8a4KY6hFZWjF+4Fdw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@pixi/filter-drop-shadow": { "version": "5.2.0", @@ -2030,19 +2031,22 @@ "version": "7.4.3", "resolved": "https://registry.npmjs.org/@pixi/math/-/math-7.4.3.tgz", "integrity": "sha512-/uJOVhR2DOZ+zgdI6Bs/CwcXT4bNRKsS+TqX3ekRIxPCwaLra+Qdm7aDxT5cTToDzdxbKL5+rwiLu3Y1egILDw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@pixi/runner": { "version": "7.4.3", "resolved": "https://registry.npmjs.org/@pixi/runner/-/runner-7.4.3.tgz", "integrity": "sha512-TJyfp7y23u5vvRAyYhVSa7ytq0PdKSvPLXu4G3meoFh1oxTLHH6g/RIzLuxUAThPG2z7ftthuW3qWq6dRV+dhw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@pixi/settings": { "version": "7.4.3", "resolved": "https://registry.npmjs.org/@pixi/settings/-/settings-7.4.3.tgz", "integrity": "sha512-SmGK8smc0PxRB9nr0UJioEtE9hl4gvj9OedCvZx3bxBwA3omA5BmP3CyhQfN8XJ29+o2OUL01r3zAPVol4l4lA==", "license": "MIT", + "peer": true, "dependencies": { "@pixi/constants": "7.4.3", "@types/css-font-loading-module": "^0.0.12", @@ -2054,6 +2058,7 @@ "resolved": "https://registry.npmjs.org/@pixi/ticker/-/ticker-7.4.3.tgz", "integrity": "sha512-tHsAD0iOUb6QSGGw+c8cyRBvxsq/NlfzIFBZLEHhWZ+Bx4a0MmXup6I/yJDGmyPCYE+ctCcAfY13wKAzdiVFgQ==", "license": "MIT", + "peer": true, "dependencies": { "@pixi/extensions": "7.4.3", "@pixi/settings": "7.4.3", @@ -2065,6 +2070,7 @@ "resolved": "https://registry.npmjs.org/@pixi/utils/-/utils-7.4.3.tgz", "integrity": "sha512-NO3Y9HAn2UKS1YdxffqsPp+kDpVm8XWvkZcS/E+rBzY9VTLnNOI7cawSRm+dacdET3a8Jad3aDKEDZ0HmAqAFA==", "license": "MIT", + "peer": true, "dependencies": { "@pixi/color": "7.4.3", "@pixi/constants": "7.4.3", @@ -3643,7 +3649,8 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -3718,7 +3725,8 @@ "version": "0.0.12", "resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.12.tgz", "integrity": "sha512-x2tZZYkSxXqWvTDgveSynfjq/T2HyiZHXb00j/+gy19yp70PHCizM48XFdjBCWH7eHBD0R5i/pw9yMBP/BH5uA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/debug": { "version": "4.1.13", @@ -3756,7 +3764,8 @@ "version": "2.1.4", "resolved": "https://registry.npmjs.org/@types/earcut/-/earcut-2.1.4.tgz", "integrity": "sha512-qp3m9PPz4gULB9MhjGID7wpo3gJ4bTGXm7ltNDsmOvsPduTeHp8wSW9YckBj3mljeOh4F0m2z/0JKAALRKbmLQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/estree": { "version": "1.0.8", @@ -3855,7 +3864,6 @@ "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -3867,7 +3875,6 @@ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "devOptional": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^18.0.0" } @@ -4149,7 +4156,6 @@ "integrity": "sha512-CWy0lBQJq97nionyJJdnaU4961IXTl43a7UCu5nHy51IoKxAt6PVIJLo+76rVl7KOOgcWHNkG4kbJu/pW7knvA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/browser": "4.1.5", "@vitest/mocker": "4.1.5", @@ -4342,7 +4348,6 @@ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -4868,7 +4873,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -5050,6 +5054,7 @@ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "license": "MIT", + "peer": true, "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" @@ -5400,7 +5405,8 @@ "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", "dev": true, "license": "MIT", - "optional": true + "optional": true, + "peer": true }, "node_modules/cross-spawn": { "version": "7.0.6", @@ -5690,7 +5696,6 @@ "integrity": "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "app-builder-lib": "26.8.1", "builder-util": "26.8.1", @@ -5783,7 +5788,8 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/dotenv": { "version": "16.6.1", @@ -5832,7 +5838,8 @@ "version": "2.2.4", "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==", - "license": "ISC" + "license": "ISC", + "peer": true }, "node_modules/ejs": { "version": "3.1.10", @@ -6015,6 +6022,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "@electron/asar": "^3.2.1", "debug": "^4.1.1", @@ -6035,6 +6043,7 @@ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", @@ -6277,7 +6286,8 @@ "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/expect-type": { "version": "1.3.0", @@ -7689,6 +7699,7 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -7908,6 +7919,7 @@ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "minimist": "^1.2.6" }, @@ -8093,17 +8105,6 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/node-gyp-build": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", - "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", - "license": "MIT", - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, "node_modules/node-gyp/node_modules/isexe": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", @@ -8221,6 +8222,7 @@ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.4" }, @@ -8429,7 +8431,6 @@ "resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-8.18.1.tgz", "integrity": "sha512-6LUPWYgulZhp/w4kam2XHXB0QedISZIqrJbRdHLLQ3csn5a38uzKxAp6B5j6s89QFYaIJbg95kvgTRcbgpO1ow==", "license": "MIT", - "peer": true, "workspaces": [ "examples", "playground" @@ -8475,7 +8476,6 @@ "integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "playwright-core": "1.59.1" }, @@ -8546,7 +8546,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -8691,6 +8690,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "commander": "^9.4.0" }, @@ -8708,6 +8708,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "engines": { "node": "^12.20.0 || >=14" } @@ -8718,6 +8719,7 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -8733,6 +8735,7 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -8846,6 +8849,7 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", "license": "BSD-3-Clause", + "peer": true, "dependencies": { "side-channel": "^1.1.0" }, @@ -8904,7 +8908,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -8917,7 +8920,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -8954,7 +8956,8 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/react-refresh": { "version": "0.18.0", @@ -9275,6 +9278,7 @@ "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "glob": "^7.1.3" }, @@ -9481,6 +9485,7 @@ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "license": "MIT", + "peer": true, "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", @@ -9500,6 +9505,7 @@ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "license": "MIT", + "peer": true, "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4" @@ -9516,6 +9522,7 @@ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -9534,6 +9541,7 @@ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -9874,7 +9882,6 @@ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", "license": "MIT", - "peer": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -9958,6 +9965,7 @@ "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "mkdirp": "^0.5.1", "rimraf": "~2.6.2" @@ -10272,19 +10280,6 @@ "node": ">=14.17" } }, - "node_modules/uiohook-napi": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/uiohook-napi/-/uiohook-napi-1.5.5.tgz", - "integrity": "sha512-oSlTdnECw2GBfsJPTbBQBeE4v/EXP0EZmX6BJq5nzH/JgFaBE8JpFwEA/kLhiEP7HxQw28FViWiYgdIZzWuuJQ==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "node-gyp-build": "^4.8.4" - }, - "engines": { - "node": ">= 16" - } - }, "node_modules/undici": { "version": "7.25.0", "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", @@ -10358,6 +10353,7 @@ "resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz", "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==", "license": "MIT", + "peer": true, "dependencies": { "punycode": "^1.4.1", "qs": "^6.12.3" @@ -10370,7 +10366,8 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/use-callback-ref": { "version": "1.3.3", @@ -10463,7 +10460,6 @@ "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -10553,8 +10549,7 @@ "resolved": "https://registry.npmjs.org/vite-plugin-electron-renderer/-/vite-plugin-electron-renderer-0.14.6.tgz", "integrity": "sha512-oqkWFa7kQIkvHXG7+Mnl1RTroA4sP0yesKatmAy0gjZC4VwUqlvF9IvOpHd1fpLWsqYX/eZlVxlhULNtaQ78Jw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/vite/node_modules/fsevents": { "version": "2.3.3", @@ -10577,7 +10572,6 @@ "integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/expect": "4.1.5", "@vitest/mocker": "4.1.5", diff --git a/package.json b/package.json index 47b7ec3..9388bcd 100644 --- a/package.json +++ b/package.json @@ -41,9 +41,7 @@ "test:browser:install": "playwright install --with-deps chromium-headless-shell", "test:e2e": "playwright test", "test:e2e:windows-native-checklist": "playwright test tests/e2e/windows-native-checklist.spec.ts", - "prepare": "husky", - "rebuild:native": "node ./scripts/rebuild-native.mjs", - "postinstall": "npm run rebuild:native" + "prepare": "husky" }, "dependencies": { "@fix-webm-duration/fix": "^1.0.1", @@ -85,7 +83,6 @@ "sonner": "^2.0.7", "tailwind-merge": "^3.5.0", "tailwindcss-animate": "^1.0.7", - "uiohook-napi": "^1.5.5", "uuid": "^13.0.0", "web-demuxer": "^4.0.0" }, diff --git a/scripts/rebuild-native.mjs b/scripts/rebuild-native.mjs deleted file mode 100644 index e028602..0000000 --- a/scripts/rebuild-native.mjs +++ /dev/null @@ -1,21 +0,0 @@ -import { spawnSync } from "node:child_process"; -import process from "node:process"; - -// uiohook-napi click capture is macOS-only at runtime (gated in -// electron/ipc/handlers.ts). Skip the rebuild on other platforms so CI runners -// without X11 dev headers don't fail npm install. The library's prebuilt -// .node binaries are still bundled and loadable; we just don't need a fresh -// build against Electron's ABI on platforms where we don't load it. -if (process.platform !== "darwin") { - console.log( - `[rebuild:native] Skipping uiohook-napi rebuild on ${process.platform} (macOS-only).`, - ); - process.exit(0); -} - -const result = spawnSync( - process.execPath, - ["./node_modules/@electron/rebuild/lib/cli.js", "--force", "--only", "uiohook-napi"], - { stdio: "inherit" }, -); -process.exit(result.status ?? 0); diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx index 11e1b3c..cae959c 100644 --- a/src/components/video-editor/SettingsPanel.tsx +++ b/src/components/video-editor/SettingsPanel.tsx @@ -1,7 +1,6 @@ import * as SliderPrimitive from "@radix-ui/react-slider"; import { Bug, - ChevronDown, Crop, Download, FileDown, @@ -28,7 +27,6 @@ import { AccordionTrigger, } from "@/components/ui/accordion"; import { Button } from "@/components/ui/button"; -import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Select, SelectContent, @@ -221,12 +219,6 @@ const GRADIENTS = [ ]; interface SettingsPanelProps { - cursorHighlight?: import("./videoPlayback/cursorHighlight").CursorHighlightConfig; - onCursorHighlightChange?: ( - next: import("./videoPlayback/cursorHighlight").CursorHighlightConfig, - ) => void; - // macOS only — gates the "Only on clicks" toggle (needs uiohook). - cursorHighlightSupportsClicks?: boolean; selected: string; onWallpaperChange: (path: string) => void; selectedZoomDepth?: ZoomDepth | null; @@ -321,7 +313,6 @@ interface SettingsPanelProps { onCursorClickBounceChange?: (bounce: number) => void; hasCursorData?: boolean; showCursorSettings?: boolean; - showCursorHighlightSettings?: boolean; } export default SettingsPanel; @@ -338,9 +329,6 @@ const ZOOM_DEPTH_OPTIONS: Array<{ depth: ZoomDepth; label: string }> = [ type SettingsPanelMode = "background" | "effects" | "layout" | "cursor" | "export"; export function SettingsPanel({ - cursorHighlight, - onCursorHighlightChange, - cursorHighlightSupportsClicks = false, selected, onWallpaperChange, selectedZoomDepth, @@ -430,7 +418,6 @@ export function SettingsPanel({ onCursorClickBounceChange, hasCursorData = false, showCursorSettings = true, - showCursorHighlightSettings = true, }: SettingsPanelProps) { const t = useScopedT("settings"); const [activePanelMode, setActivePanelMode] = useState("background"); @@ -567,9 +554,7 @@ export function SettingsPanel({ const zoomEnabled = Boolean(selectedZoomDepth); const trimEnabled = Boolean(selectedTrimId); const hasTimelineSelection = Boolean(selectedZoomId || selectedTrimId || selectedSpeedId); - const hasCursorPanel = - (showCursorSettings && hasCursorData) || - (showCursorHighlightSettings && Boolean(cursorHighlight && onCursorHighlightChange)); + const hasCursorPanel = showCursorSettings && hasCursorData; const panelModes: Array<{ id: SettingsPanelMode; label: string; @@ -583,7 +568,7 @@ export function SettingsPanel({ ? [ { id: "cursor" as const, - label: t("effects.cursorHighlight.title"), + label: t("effects.title"), icon: MousePointerClick, }, ] @@ -1288,11 +1273,7 @@ export function SettingsPanel({ ) : ( )} - - {activePanelMode === "cursor" - ? t("effects.cursorHighlight.title") - : t("effects.title")} - + {t("effects.title")} @@ -1483,226 +1464,6 @@ export function SettingsPanel({ )} )} - - {activePanelMode === "cursor" && - showCursorHighlightSettings && - cursorHighlight && - onCursorHighlightChange && ( -
-
-
- {t("effects.cursorHighlight.title")} -
- -
-
- {(["dot", "ring"] as const).map((style) => ( - - ))} -
-
-
-
- {t("effects.cursorHighlight.size")} -
- - {cursorHighlight.sizePx}px - -
- - onCursorHighlightChange({ - ...cursorHighlight, - sizePx: values[0], - }) - } - min={10} - max={36} - step={1} - className="w-full [&_[role=slider]]:bg-[#34B27B] [&_[role=slider]]:border-[#34B27B] [&_[role=slider]]:h-3 [&_[role=slider]]:w-3" - /> -
- {cursorHighlightSupportsClicks && ( -
-
- {t("effects.cursorHighlight.onlyOnClicks")} -
- -
- )} -
-
- {t("effects.cursorHighlight.color")} -
- - - - - - - onCursorHighlightChange({ - ...cursorHighlight, - color, - }) - } - /> - - -
-
-
-
- {t("effects.cursorHighlight.offsetX")} -
- - {(cursorHighlight.offsetXNorm * 100).toFixed(1)}% - -
- - onCursorHighlightChange({ - ...cursorHighlight, - offsetXNorm: values[0], - }) - } - min={-0.25} - max={0.25} - step={0.005} - className="w-full [&_[role=slider]]:bg-[#34B27B] [&_[role=slider]]:border-[#34B27B] [&_[role=slider]]:h-3 [&_[role=slider]]:w-3" - /> -
-
-
-
- {t("effects.cursorHighlight.offsetY")} -
- - {(cursorHighlight.offsetYNorm * 100).toFixed(1)}% - -
- - onCursorHighlightChange({ - ...cursorHighlight, - offsetYNorm: values[0], - }) - } - min={-0.25} - max={0.25} - step={0.005} - className="w-full [&_[role=slider]]:bg-[#34B27B] [&_[role=slider]]:border-[#34B27B] [&_[role=slider]]:h-3 [&_[role=slider]]:w-3" - /> -
-
- )}
)} diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx index 0525d85..fd4ae9e 100644 --- a/src/components/video-editor/VideoEditor.tsx +++ b/src/components/video-editor/VideoEditor.tsx @@ -126,7 +126,6 @@ export default function VideoEditor() { webcamMaskShape, webcamSizePreset, webcamPosition, - cursorHighlight, } = editorState; // ── Non-undoable state @@ -205,17 +204,15 @@ export default function VideoEditor() { const nextSpeedIdRef = useRef(1); const { shortcuts, isMac } = useShortcuts(); + // Windows-only: the synthetic cursor overlay + cursor customization settings + // only apply when there's an actual native cursor recording (cursor frames + + // position samples produced by WindowsNativeRecordingSession). Mac and Linux + // keep their telemetry positions for auto-zoom but never render a synthetic + // cursor or expose cursor customization settings. const hasEditableCursorRecording = - recordingCursorCaptureMode === "editable-overlay" || - (recordingCursorCaptureMode === null && hasNativeCursorRecordingData(cursorRecordingData)); + nativePlatform === "win32" && hasNativeCursorRecordingData(cursorRecordingData); const effectiveShowCursor = showCursor && hasEditableCursorRecording; - const showCursorSettings = nativePlatform === "win32" && hasEditableCursorRecording; - // Off-Mac doesn't have click telemetry, so force `onlyOnClicks` off for - // renderers while keeping the persisted value intact for round-tripping. - const effectiveCursorHighlight = useMemo( - () => (isMac ? cursorHighlight : { ...cursorHighlight, onlyOnClicks: false }), - [cursorHighlight, isMac], - ); + const showCursorSettings = hasEditableCursorRecording; const { locale, setLocale, t: rawT } = useI18n(); const t = useScopedT("editor"); const ts = useScopedT("settings"); @@ -534,7 +531,6 @@ export default function VideoEditor() { gifFrameRate, gifLoop, gifSizePreset, - cursorHighlight, }; const projectData = createProjectData(currentProjectMedia, editorState); @@ -596,7 +592,6 @@ export default function VideoEditor() { videoPath, t, webcamSizePreset, - cursorHighlight, ], ); @@ -1569,7 +1564,6 @@ export default function VideoEditor() { previewHeight, cursorTelemetry, cursorClickTimestamps, - cursorHighlight: effectiveCursorHighlight, onProgress: (progress: ExportProgress) => { setExportProgress(progress); }, @@ -1715,7 +1709,6 @@ export default function VideoEditor() { previewHeight, cursorTelemetry, cursorClickTimestamps, - cursorHighlight: effectiveCursorHighlight, onProgress: (progress: ExportProgress) => { setExportProgress(progress); }, @@ -1797,7 +1790,6 @@ export default function VideoEditor() { handleExportSaved, cursorTelemetry, cursorClickTimestamps, - effectiveCursorHighlight, effectiveShowCursor, cursorSize, cursorSmoothing, @@ -2074,7 +2066,6 @@ export default function VideoEditor() { onBlurDataChange={handleBlurDataPreviewChange} onBlurDataCommit={commitState} cursorTelemetry={cursorTelemetry} - cursorHighlight={effectiveCursorHighlight} cursorClickTimestamps={cursorClickTimestamps} showCursor={effectiveShowCursor} cursorSize={cursorSize} @@ -2103,9 +2094,6 @@ export default function VideoEditor() {
pushState({ cursorHighlight: next })} - cursorHighlightSupportsClicks={isMac} selected={wallpaper} onWallpaperChange={(w) => pushState({ wallpaper: w })} selectedZoomDepth={ @@ -2240,7 +2228,6 @@ export default function VideoEditor() { cursorTelemetry.length > 0 || hasNativeCursorRecordingData(cursorRecordingData) } showCursorSettings={showCursorSettings} - showCursorHighlightSettings={isMac} />
diff --git a/src/components/video-editor/VideoPlayback.tsx b/src/components/video-editor/VideoPlayback.tsx index 10eb8a0..8b53ff9 100644 --- a/src/components/video-editor/VideoPlayback.tsx +++ b/src/components/video-editor/VideoPlayback.tsx @@ -77,17 +77,7 @@ import { ZOOM_SCALE_DEADZONE, ZOOM_TRANSLATION_DEADZONE_PX, } from "./videoPlayback/constants"; -import { - adaptiveSmoothFactor, - interpolateCursorAt, - smoothCursorFocus, -} from "./videoPlayback/cursorFollowUtils"; -import { - type CursorHighlightConfig, - clickEmphasisAlpha, - DEFAULT_CURSOR_HIGHLIGHT, - drawCursorHighlightGraphics, -} from "./videoPlayback/cursorHighlight"; +import { adaptiveSmoothFactor, smoothCursorFocus } from "./videoPlayback/cursorFollowUtils"; import { DEFAULT_CURSOR_CONFIG, PixiCursorOverlay, @@ -152,7 +142,6 @@ interface VideoPlaybackProps { onBlurDataChange?: (id: string, blurData: BlurData) => void; onBlurDataCommit?: () => void; cursorTelemetry?: CursorTelemetryPoint[]; - cursorHighlight?: CursorHighlightConfig; cursorClickTimestamps?: number[]; showCursor?: boolean; cursorSize?: number; @@ -253,7 +242,6 @@ const VideoPlayback = forwardRef( onBlurDataChange, onBlurDataCommit, cursorTelemetry = [], - cursorHighlight = DEFAULT_CURSOR_HIGHLIGHT, cursorClickTimestamps = [], showCursor = false, cursorSize = DEFAULT_CURSOR_SIZE, @@ -285,9 +273,7 @@ const VideoPlayback = forwardRef( const currentTimeRef = useRef(0); const zoomRegionsRef = useRef([]); const cursorTelemetryRef = useRef([]); - const cursorHighlightRef = useRef(DEFAULT_CURSOR_HIGHLIGHT); const cursorClickTimestampsRef = useRef([]); - const cursorHighlightGraphicsRef = useRef(null); const selectedZoomIdRef = useRef(null); const animationStateRef = useRef({ scale: 1, @@ -742,13 +728,6 @@ const VideoPlayback = forwardRef( cursorTelemetryRef.current = cursorTelemetry; }, [cursorTelemetry]); - useEffect(() => { - cursorHighlightRef.current = cursorHighlight; - if (cursorHighlightGraphicsRef.current) { - drawCursorHighlightGraphics(cursorHighlightGraphicsRef.current, cursorHighlight); - } - }, [cursorHighlight]); - useEffect(() => { cursorClickTimestampsRef.current = cursorClickTimestamps; }, [cursorClickTimestamps]); @@ -1084,11 +1063,6 @@ const VideoPlayback = forwardRef( videoContainer.addChild(cursorOverlayRef.current.container); } - const cursorHighlightGraphics = new Graphics(); - cursorHighlightGraphics.visible = false; - videoContainer.addChild(cursorHighlightGraphics); - cursorHighlightGraphicsRef.current = cursorHighlightGraphics; - drawCursorHighlightGraphics(cursorHighlightGraphics, cursorHighlightRef.current); videoContainer.addChild(nativeCursorSprite); animationStateRef.current = { @@ -1153,11 +1127,6 @@ const VideoPlayback = forwardRef( videoContainer.removeChild(maskGraphics); maskGraphics.destroy(); } - if (cursorHighlightGraphicsRef.current) { - videoContainer.removeChild(cursorHighlightGraphicsRef.current); - cursorHighlightGraphicsRef.current.destroy(); - cursorHighlightGraphicsRef.current = null; - } if (nativeCursorSpriteRef.current) { videoContainer.removeChild(nativeCursorSpriteRef.current); nativeCursorSpriteRef.current.destroy(); @@ -1385,39 +1354,6 @@ const VideoPlayback = forwardRef( motionVector, ); - const cursorGraphics = cursorHighlightGraphicsRef.current; - const cursorConfig = cursorHighlightRef.current; - const lockedDims = lockedVideoDimensionsRef.current; - if (cursorGraphics) { - if (cursorConfig.enabled && lockedDims && cursorTelemetryRef.current.length > 0) { - const emphasisAlpha = clickEmphasisAlpha( - currentTimeRef.current, - cursorClickTimestampsRef.current, - cursorConfig, - ); - const cursorPoint = - emphasisAlpha > 0 - ? interpolateCursorAt(cursorTelemetryRef.current, currentTimeRef.current) - : null; - if (cursorPoint) { - const baseScale = baseScaleRef.current; - const baseOffset = baseOffsetRef.current; - const cx = cursorPoint.cx + cursorConfig.offsetXNorm; - const cy = cursorPoint.cy + cursorConfig.offsetYNorm; - cursorGraphics.position.set( - baseOffset.x + cx * lockedDims.width * baseScale, - baseOffset.y + cy * lockedDims.height * baseScale, - ); - cursorGraphics.alpha = emphasisAlpha; - cursorGraphics.visible = true; - } else { - cursorGraphics.visible = false; - } - } else { - cursorGraphics.visible = false; - } - } - const isMotionBlurActive = (motionBlurAmountRef.current || 0) > 0 && isPlayingRef.current && !isScrubbingRef.current; diff --git a/src/components/video-editor/projectPersistence.ts b/src/components/video-editor/projectPersistence.ts index 4ea0040..7360142 100644 --- a/src/components/video-editor/projectPersistence.ts +++ b/src/components/video-editor/projectPersistence.ts @@ -80,7 +80,6 @@ export interface ProjectEditorState { gifFrameRate: GifFrameRate; gifLoop: boolean; gifSizePreset: GifSizePreset; - cursorHighlight: import("./videoPlayback/cursorHighlight").CursorHighlightConfig; } export interface EditorProjectData { @@ -489,52 +488,6 @@ export function normalizeProjectEditor(editor: Partial): Pro editor.gifSizePreset === "original" ? editor.gifSizePreset : "medium", - cursorHighlight: normalizeCursorHighlight(editor.cursorHighlight), - }; -} - -function normalizeCursorHighlight( - value: unknown, -): import("./videoPlayback/cursorHighlight").CursorHighlightConfig { - const fallback: import("./videoPlayback/cursorHighlight").CursorHighlightConfig = { - enabled: false, - style: "ring", - sizePx: 24, - color: "#FFD700", - opacity: 0.9, - onlyOnClicks: false, - clickEmphasisDurationMs: 350, - offsetXNorm: 0, - offsetYNorm: 0, - }; - if (!value || typeof value !== "object") return fallback; - const v = value as Partial; - return { - enabled: typeof v.enabled === "boolean" ? v.enabled : fallback.enabled, - style: v.style === "dot" || v.style === "ring" ? v.style : fallback.style, - sizePx: - typeof v.sizePx === "number" && v.sizePx >= 10 && v.sizePx <= 36 ? v.sizePx : fallback.sizePx, - color: - typeof v.color === "string" && /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(v.color) - ? v.color - : fallback.color, - opacity: - typeof v.opacity === "number" && v.opacity >= 0 && v.opacity <= 1 - ? v.opacity - : fallback.opacity, - onlyOnClicks: typeof v.onlyOnClicks === "boolean" ? v.onlyOnClicks : fallback.onlyOnClicks, - clickEmphasisDurationMs: - typeof v.clickEmphasisDurationMs === "number" && v.clickEmphasisDurationMs > 0 - ? v.clickEmphasisDurationMs - : fallback.clickEmphasisDurationMs, - offsetXNorm: - typeof v.offsetXNorm === "number" && Number.isFinite(v.offsetXNorm) - ? Math.max(-1, Math.min(1, v.offsetXNorm)) - : fallback.offsetXNorm, - offsetYNorm: - typeof v.offsetYNorm === "number" && Number.isFinite(v.offsetYNorm) - ? Math.max(-1, Math.min(1, v.offsetYNorm)) - : fallback.offsetYNorm, }; } diff --git a/src/components/video-editor/videoPlayback/cursorHighlight.ts b/src/components/video-editor/videoPlayback/cursorHighlight.ts deleted file mode 100644 index 273e7b2..0000000 --- a/src/components/video-editor/videoPlayback/cursorHighlight.ts +++ /dev/null @@ -1,125 +0,0 @@ -import type { Graphics } from "pixi.js"; - -export type CursorHighlightStyle = "dot" | "ring"; - -export interface CursorHighlightConfig { - enabled: boolean; - style: CursorHighlightStyle; - sizePx: number; - color: string; - opacity: number; - // Show only on clicks (macOS — depends on click telemetry from uiohook). - onlyOnClicks: boolean; - clickEmphasisDurationMs: number; - // Per-recording manual nudge. Cursor telemetry is normalized to the display, - // but window recordings frame a subset of the display so the highlight - // lands offset. Users dial these in once to align with the actual cursor. - offsetXNorm: number; - offsetYNorm: number; -} - -export const CURSOR_HIGHLIGHT_MIN_SIZE_PX = 10; -export const CURSOR_HIGHLIGHT_MAX_SIZE_PX = 36; - -export const DEFAULT_CURSOR_HIGHLIGHT: CursorHighlightConfig = { - enabled: false, - style: "ring", - sizePx: 24, - color: "#FFD700", - opacity: 0.9, - onlyOnClicks: false, - clickEmphasisDurationMs: 350, - offsetXNorm: 0, - offsetYNorm: 0, -}; - -export const CURSOR_HIGHLIGHT_OFFSET_RANGE = 0.25; // ±25% of recorded surface - -// Alpha multiplier for the highlight at `timeMs`. Returns 1 when not in -// click-only mode; in click-only mode fades 1→0 across each click's window. -export function clickEmphasisAlpha( - timeMs: number, - clickTimestampsMs: number[] | undefined, - config: CursorHighlightConfig, -): number { - if (!config.onlyOnClicks) return 1; - if (!clickTimestampsMs || clickTimestampsMs.length === 0) return 0; - const window = Math.max(1, config.clickEmphasisDurationMs); - for (let i = 0; i < clickTimestampsMs.length; i++) { - const dt = timeMs - clickTimestampsMs[i]; - if (dt >= 0 && dt <= window) { - return 1 - dt / window; - } - } - return 0; -} - -function parseHexColor(hex: string): number { - const cleaned = hex.replace("#", ""); - if (cleaned.length === 3) { - const r = cleaned[0]; - const g = cleaned[1]; - const b = cleaned[2]; - return Number.parseInt(`${r}${r}${g}${g}${b}${b}`, 16); - } - return Number.parseInt(cleaned.slice(0, 6), 16); -} - -export function drawCursorHighlightGraphics(g: Graphics, config: CursorHighlightConfig): void { - g.clear(); - if (!config.enabled) return; - - const color = parseHexColor(config.color); - const radius = Math.max(1, config.sizePx / 2); - const alpha = Math.max(0, Math.min(1, config.opacity)); - - switch (config.style) { - case "dot": { - g.circle(0, 0, radius); - g.fill({ color, alpha }); - break; - } - case "ring": { - g.circle(0, 0, radius); - g.stroke({ color, alpha, width: Math.max(2, radius * 0.18) }); - break; - } - } -} - -export function drawCursorHighlightCanvas( - ctx: CanvasRenderingContext2D, - cx: number, - cy: number, - config: CursorHighlightConfig, - pixelScale = 1, -): void { - if (!config.enabled) return; - - const radius = Math.max(1, (config.sizePx / 2) * pixelScale); - const alpha = Math.max(0, Math.min(1, config.opacity)); - const color = config.color; - - ctx.save(); - ctx.globalAlpha = alpha; - - switch (config.style) { - case "dot": { - ctx.fillStyle = color; - ctx.beginPath(); - ctx.arc(cx, cy, radius, 0, Math.PI * 2); - ctx.fill(); - break; - } - case "ring": { - ctx.beginPath(); - ctx.arc(cx, cy, radius, 0, Math.PI * 2); - ctx.strokeStyle = color; - ctx.lineWidth = Math.max(2, radius * 0.18); - ctx.stroke(); - break; - } - } - - ctx.restore(); -} diff --git a/src/hooks/useEditorHistory.ts b/src/hooks/useEditorHistory.ts index a655137..bd410da 100644 --- a/src/hooks/useEditorHistory.ts +++ b/src/hooks/useEditorHistory.ts @@ -17,10 +17,6 @@ import { DEFAULT_WEBCAM_POSITION, DEFAULT_WEBCAM_SIZE_PRESET, } from "@/components/video-editor/types"; -import { - type CursorHighlightConfig, - DEFAULT_CURSOR_HIGHLIGHT, -} from "@/components/video-editor/videoPlayback/cursorHighlight"; import { DEFAULT_WALLPAPER } from "@/lib/wallpaper"; import type { AspectRatio } from "@/utils/aspectRatioUtils"; @@ -43,7 +39,6 @@ export interface EditorState { webcamMaskShape: WebcamMaskShape; webcamSizePreset: WebcamSizePreset; webcamPosition: WebcamPosition | null; - cursorHighlight: CursorHighlightConfig; } export const INITIAL_EDITOR_STATE: EditorState = { @@ -63,7 +58,6 @@ export const INITIAL_EDITOR_STATE: EditorState = { webcamMaskShape: DEFAULT_WEBCAM_MASK_SHAPE, webcamSizePreset: DEFAULT_WEBCAM_SIZE_PRESET, webcamPosition: DEFAULT_WEBCAM_POSITION, - cursorHighlight: DEFAULT_CURSOR_HIGHLIGHT, }; type StateUpdate = Partial | ((prev: EditorState) => Partial); diff --git a/src/i18n/locales/ar/settings.json b/src/i18n/locales/ar/settings.json index 2d250b1..47fdca4 100644 --- a/src/i18n/locales/ar/settings.json +++ b/src/i18n/locales/ar/settings.json @@ -38,20 +38,7 @@ "on": "تشغيل", "shadow": "ظل", "roundness": "الاستدارة", - "padding": "المسافة البادئة", - "cursorHighlight": { - "title": "تمييز المؤشر", - "style": "النمط", - "dot": "نقطة", - "ring": "حلقة", - "size": "الحجم", - "onlyOnClicks": "عند النقر فقط", - "color": "اللون", - "offsetX": "إزاحة X (لتسجيلات النوافذ)", - "offsetY": "إزاحة Y", - "accessibilityPermissionTitle": "مطلوب إذن الوصول", - "accessibilityPermissionDescription": "افتح إعدادات النظام ← الخصوصية والأمان ← إمكانية الوصول، وقم بتفعيل Openscreen، ثم أعد تشغيل التطبيق." - } + "padding": "المسافة البادئة" }, "background": { "title": "الخلفية", diff --git a/src/i18n/locales/en/settings.json b/src/i18n/locales/en/settings.json index 3ec0819..9d41736 100644 --- a/src/i18n/locales/en/settings.json +++ b/src/i18n/locales/en/settings.json @@ -54,20 +54,7 @@ "on": "on", "shadow": "Shadow", "roundness": "Roundness", - "padding": "Padding", - "cursorHighlight": { - "title": "Cursor highlight", - "style": "Style", - "dot": "Dot", - "ring": "Ring", - "size": "Size", - "onlyOnClicks": "Only on clicks", - "color": "Color", - "offsetX": "Offset X (window recordings)", - "offsetY": "Offset Y", - "accessibilityPermissionTitle": "Accessibility permission needed", - "accessibilityPermissionDescription": "Open System Settings → Privacy & Security → Accessibility, enable Openscreen, then restart the app." - } + "padding": "Padding" }, "background": { "title": "Background", diff --git a/src/i18n/locales/ru/settings.json b/src/i18n/locales/ru/settings.json index e3be5ed..dc15c3f 100644 --- a/src/i18n/locales/ru/settings.json +++ b/src/i18n/locales/ru/settings.json @@ -46,20 +46,7 @@ "on": "вкл", "shadow": "Тень", "roundness": "Скругление", - "padding": "Отступ", - "cursorHighlight": { - "title": "Подсветка курсора", - "style": "Стиль", - "dot": "Точка", - "ring": "Кольцо", - "size": "Размер", - "onlyOnClicks": "Только при кликах", - "color": "Цвет", - "offsetX": "Смещение X (записи окон)", - "offsetY": "Смещение Y", - "accessibilityPermissionTitle": "Требуется разрешение на доступность", - "accessibilityPermissionDescription": "Откройте Системные настройки → Конфиденциальность и безопасность → Универсальный доступ, включите Openscreen, затем перезапустите приложение." - } + "padding": "Отступ" }, "background": { "title": "Фон", diff --git a/src/lib/exporter/frameRenderer.ts b/src/lib/exporter/frameRenderer.ts index ed87eb0..f6a64c0 100644 --- a/src/lib/exporter/frameRenderer.ts +++ b/src/lib/exporter/frameRenderer.ts @@ -33,14 +33,8 @@ import { } from "@/components/video-editor/videoPlayback/constants"; import { adaptiveSmoothFactor, - interpolateCursorAt, smoothCursorFocus, } from "@/components/video-editor/videoPlayback/cursorFollowUtils"; -import { - type CursorHighlightConfig, - clickEmphasisAlpha, - drawCursorHighlightCanvas, -} from "@/components/video-editor/videoPlayback/cursorHighlight"; import { clampFocusToScale } from "@/components/video-editor/videoPlayback/focusUtils"; import { findDominantRegion } from "@/components/video-editor/videoPlayback/zoomRegionUtils"; import { @@ -110,7 +104,6 @@ interface FrameRenderConfig { previewWidth?: number; previewHeight?: number; cursorTelemetry?: import("@/components/video-editor/types").CursorTelemetryPoint[]; - cursorHighlight?: CursorHighlightConfig; cursorClickTimestamps?: number[]; platform: string; } @@ -450,47 +443,6 @@ export class FrameRenderer { const willRotate = !isRotation3DIdentity(this.currentRotation3D); this.compositeWithShadows(webcamFrame, !willRotate); - // Cursor highlight overlay (rendered above video, below annotations) - // Drawn onto foreground so it rotates with the recording. - if ( - this.config.cursorHighlight?.enabled && - this.config.cursorTelemetry && - this.config.cursorTelemetry.length > 0 && - this.foregroundCtx - ) { - const emphasisAlpha = clickEmphasisAlpha( - timeMs, - this.config.cursorClickTimestamps, - this.config.cursorHighlight, - ); - const cursorPoint = - emphasisAlpha > 0 ? interpolateCursorAt(this.config.cursorTelemetry, timeMs) : null; - if (cursorPoint) { - const cx = cursorPoint.cx + this.config.cursorHighlight.offsetXNorm; - const cy = cursorPoint.cy + this.config.cursorHighlight.offsetYNorm; - const stageX = - layoutCache.baseOffset.x + cx * this.config.videoWidth * layoutCache.baseScale; - const stageY = - layoutCache.baseOffset.y + cy * this.config.videoHeight * layoutCache.baseScale; - const appliedScale = this.animationState.appliedScale; - const canvasX = stageX * appliedScale + this.animationState.x; - const canvasY = stageY * appliedScale + this.animationState.y; - const previewW = this.config.previewWidth ?? this.config.width; - const previewH = this.config.previewHeight ?? this.config.height; - const cursorScale = (this.config.width / previewW + this.config.height / previewH) / 2; - drawCursorHighlightCanvas( - this.foregroundCtx, - canvasX, - canvasY, - { - ...this.config.cursorHighlight, - opacity: this.config.cursorHighlight.opacity * emphasisAlpha, - }, - appliedScale * cursorScale, - ); - } - } - await this.drawNativeCursor(timeMs); // Render annotations on top of foreground (so they rotate with recording). diff --git a/src/lib/exporter/gifExporter.ts b/src/lib/exporter/gifExporter.ts index 6ff3f87..c7cde09 100644 --- a/src/lib/exporter/gifExporter.ts +++ b/src/lib/exporter/gifExporter.ts @@ -57,7 +57,6 @@ interface GifExporterConfig { previewWidth?: number; previewHeight?: number; cursorTelemetry?: import("@/components/video-editor/types").CursorTelemetryPoint[]; - cursorHighlight?: import("@/components/video-editor/videoPlayback/cursorHighlight").CursorHighlightConfig; cursorClickTimestamps?: number[]; onProgress?: (progress: ExportProgress) => void; } @@ -175,7 +174,6 @@ export class GifExporter { previewHeight: this.config.previewHeight, cursorTelemetry: this.config.cursorTelemetry, cursorClickTimestamps: this.config.cursorClickTimestamps, - cursorHighlight: this.config.cursorHighlight, platform, }); await this.renderer.initialize(); diff --git a/src/lib/exporter/videoExporter.test.ts b/src/lib/exporter/videoExporter.test.ts index d168b62..1b64255 100644 --- a/src/lib/exporter/videoExporter.test.ts +++ b/src/lib/exporter/videoExporter.test.ts @@ -106,25 +106,6 @@ describe("isSourceCopyFastPathEligible", () => { videoInfo, ), ).toBe(false); - expect( - isSourceCopyFastPathEligible( - createConfig({ - cursorHighlight: { - enabled: true, - style: "ring", - sizePx: 24, - color: "#ffffff", - opacity: 1, - onlyOnClicks: false, - clickEmphasisDurationMs: 350, - offsetXNorm: 0, - offsetYNorm: 0, - }, - cursorTelemetry: [{ timeMs: 0, cx: 0.5, cy: 0.5 }], - }), - videoInfo, - ), - ).toBe(false); }); }); diff --git a/src/lib/exporter/videoExporter.ts b/src/lib/exporter/videoExporter.ts index 10525a1..d0f178f 100644 --- a/src/lib/exporter/videoExporter.ts +++ b/src/lib/exporter/videoExporter.ts @@ -48,7 +48,6 @@ export interface VideoExporterConfig extends ExportConfig { previewWidth?: number; previewHeight?: number; cursorTelemetry?: import("@/components/video-editor/types").CursorTelemetryPoint[]; - cursorHighlight?: import("@/components/video-editor/videoPlayback/cursorHighlight").CursorHighlightConfig; cursorClickTimestamps?: number[]; onProgress?: (progress: ExportProgress) => void; } @@ -73,12 +72,6 @@ function hasNativeCursorOverlay(config: VideoExporterConfig) { return (config.cursorScale ?? 0) > 0; } -function hasCursorHighlightOverlay(config: VideoExporterConfig) { - return Boolean( - config.cursorHighlight?.enabled && config.cursorTelemetry && config.cursorTelemetry.length > 0, - ); -} - function isDefaultCrop(cropRegion: CropRegion) { return ( Math.abs(cropRegion.x) <= SOURCE_COPY_EPSILON && @@ -113,7 +106,6 @@ export function getSourceCopyFastPathBlockers( if (hasActiveTimeRegions(config.annotationRegions)) blockers.push("annotation regions are present"); if (hasNativeCursorOverlay(config)) blockers.push("editable cursor overlay is enabled"); - if (hasCursorHighlightOverlay(config)) blockers.push("cursor highlight overlay is enabled"); if (!isDefaultCrop(config.cropRegion)) blockers.push("crop is not default"); if ((config.padding ?? 0) > SOURCE_COPY_EPSILON) blockers.push("padding is not zero"); if ((config.videoPadding ?? 0) > SOURCE_COPY_EPSILON) blockers.push("video padding is not zero"); @@ -262,7 +254,6 @@ export class VideoExporter { previewHeight: this.config.previewHeight, cursorTelemetry: this.config.cursorTelemetry, cursorClickTimestamps: this.config.cursorClickTimestamps, - cursorHighlight: this.config.cursorHighlight, platform, }); this.renderer = renderer;