remove macos cursor highlight; wire telemetry session for non-windows
This commit is contained in:
+22
-23
@@ -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
|
||||
|
||||
Vendored
-5
@@ -40,11 +40,6 @@ interface Window {
|
||||
status: string;
|
||||
error?: string;
|
||||
}>;
|
||||
requestAccessibilityAccess: () => Promise<{
|
||||
success: boolean;
|
||||
granted: boolean;
|
||||
error?: string;
|
||||
}>;
|
||||
assetBaseUrl: string;
|
||||
storeRecordedVideo: (
|
||||
videoData: ArrayBuffer,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<void> {
|
||||
// Native cursor capture is currently Windows-only.
|
||||
}
|
||||
|
||||
async stop(): Promise<CursorRecordingData> {
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
Generated
+53
-59
@@ -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",
|
||||
|
||||
+1
-4
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
@@ -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<SettingsPanelMode>("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({
|
||||
) : (
|
||||
<SlidersHorizontal className="w-4 h-4 text-[#34B27B]" />
|
||||
)}
|
||||
<span className="text-xs font-medium">
|
||||
{activePanelMode === "cursor"
|
||||
? t("effects.cursorHighlight.title")
|
||||
: t("effects.title")}
|
||||
</span>
|
||||
<span className="text-xs font-medium">{t("effects.title")}</span>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="pb-3">
|
||||
@@ -1483,226 +1464,6 @@ export function SettingsPanel({
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activePanelMode === "cursor" &&
|
||||
showCursorHighlightSettings &&
|
||||
cursorHighlight &&
|
||||
onCursorHighlightChange && (
|
||||
<div className="p-2 rounded-lg editor-control-surface mt-2 space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-[10px] font-medium text-slate-300">
|
||||
{t("effects.cursorHighlight.title")}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
onCursorHighlightChange({
|
||||
...cursorHighlight,
|
||||
enabled: !cursorHighlight.enabled,
|
||||
})
|
||||
}
|
||||
className={`text-[10px] px-2 py-0.5 rounded border transition-colors ${
|
||||
cursorHighlight.enabled
|
||||
? "bg-[#34B27B]/20 border-[#34B27B]/50 text-[#34B27B]"
|
||||
: "bg-white/5 border-white/10 text-slate-400"
|
||||
}`}
|
||||
>
|
||||
{cursorHighlight.enabled ? t("effects.on") : t("effects.off")}
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className={`grid grid-cols-2 gap-1 ${cursorHighlight.enabled ? "" : "opacity-40 pointer-events-none"}`}
|
||||
>
|
||||
{(["dot", "ring"] as const).map((style) => (
|
||||
<button
|
||||
key={style}
|
||||
type="button"
|
||||
onClick={() =>
|
||||
onCursorHighlightChange({ ...cursorHighlight, style })
|
||||
}
|
||||
className={`text-[10px] px-2 py-1 rounded border capitalize transition-colors ${
|
||||
cursorHighlight.style === style
|
||||
? "bg-[#34B27B]/20 border-[#34B27B]/50 text-[#34B27B]"
|
||||
: "bg-white/5 border-white/10 text-slate-300 hover:border-white/20"
|
||||
}`}
|
||||
>
|
||||
{t(`effects.cursorHighlight.${style}`)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
cursorHighlight.enabled ? "" : "opacity-40 pointer-events-none"
|
||||
}
|
||||
>
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<div className="text-[10px] text-slate-400">
|
||||
{t("effects.cursorHighlight.size")}
|
||||
</div>
|
||||
<span className="text-[10px] text-slate-500 font-mono">
|
||||
{cursorHighlight.sizePx}px
|
||||
</span>
|
||||
</div>
|
||||
<Slider
|
||||
value={[cursorHighlight.sizePx]}
|
||||
onValueChange={(values) =>
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
{cursorHighlightSupportsClicks && (
|
||||
<div
|
||||
className={`flex items-center justify-between ${cursorHighlight.enabled ? "" : "opacity-40 pointer-events-none"}`}
|
||||
>
|
||||
<div className="text-[10px] text-slate-400">
|
||||
{t("effects.cursorHighlight.onlyOnClicks")}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
const turningOn = !cursorHighlight.onlyOnClicks;
|
||||
if (turningOn) {
|
||||
try {
|
||||
const result =
|
||||
await window.electronAPI?.requestAccessibilityAccess?.();
|
||||
if (!result?.granted) {
|
||||
toast.message(
|
||||
t("effects.cursorHighlight.accessibilityPermissionTitle"),
|
||||
{
|
||||
description: t(
|
||||
"effects.cursorHighlight.accessibilityPermissionDescription",
|
||||
),
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn("Accessibility request failed:", err);
|
||||
}
|
||||
}
|
||||
onCursorHighlightChange({
|
||||
...cursorHighlight,
|
||||
onlyOnClicks: turningOn,
|
||||
});
|
||||
}}
|
||||
className={`text-[10px] px-2 py-0.5 rounded border transition-colors ${
|
||||
cursorHighlight.onlyOnClicks
|
||||
? "bg-[#34B27B]/20 border-[#34B27B]/50 text-[#34B27B]"
|
||||
: "bg-white/5 border-white/10 text-slate-400"
|
||||
}`}
|
||||
>
|
||||
{cursorHighlight.onlyOnClicks ? t("effects.on") : t("effects.off")}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={
|
||||
cursorHighlight.enabled ? "" : "opacity-40 pointer-events-none"
|
||||
}
|
||||
>
|
||||
<div className="text-[10px] text-slate-400 mb-1">
|
||||
{t("effects.cursorHighlight.color")}
|
||||
</div>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full h-8 justify-start gap-2 bg-white/5 border-white/10 hover:bg-white/10 px-2"
|
||||
>
|
||||
<div
|
||||
className="w-4 h-4 rounded-full border border-white/20"
|
||||
style={{ backgroundColor: cursorHighlight.color }}
|
||||
/>
|
||||
<span className="text-[10px] text-slate-300 truncate flex-1 text-left font-mono">
|
||||
{cursorHighlight.color}
|
||||
</span>
|
||||
<ChevronDown className="h-3 w-3 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
side="top"
|
||||
className="w-[260px] p-3 bg-[#1a1a1c] border border-white/10 rounded-xl shadow-xl"
|
||||
>
|
||||
<ColorPicker
|
||||
selectedColor={cursorHighlight.color}
|
||||
colorPalette={colorPalette}
|
||||
translations={{
|
||||
colorWheel: t("background.colorWheel"),
|
||||
colorPalette: t("background.colorPalette"),
|
||||
}}
|
||||
onUpdateColor={(color) =>
|
||||
onCursorHighlightChange({
|
||||
...cursorHighlight,
|
||||
color,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
cursorHighlight.enabled ? "" : "opacity-40 pointer-events-none"
|
||||
}
|
||||
>
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<div className="text-[10px] text-slate-400">
|
||||
{t("effects.cursorHighlight.offsetX")}
|
||||
</div>
|
||||
<span className="text-[10px] text-slate-500 font-mono">
|
||||
{(cursorHighlight.offsetXNorm * 100).toFixed(1)}%
|
||||
</span>
|
||||
</div>
|
||||
<Slider
|
||||
value={[cursorHighlight.offsetXNorm]}
|
||||
onValueChange={(values) =>
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
cursorHighlight.enabled ? "" : "opacity-40 pointer-events-none"
|
||||
}
|
||||
>
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<div className="text-[10px] text-slate-400">
|
||||
{t("effects.cursorHighlight.offsetY")}
|
||||
</div>
|
||||
<span className="text-[10px] text-slate-500 font-mono">
|
||||
{(cursorHighlight.offsetYNorm * 100).toFixed(1)}%
|
||||
</span>
|
||||
</div>
|
||||
<Slider
|
||||
value={[cursorHighlight.offsetYNorm]}
|
||||
onValueChange={(values) =>
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
)}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
<div className="editor-settings-rail min-w-0 h-full">
|
||||
<SettingsPanel
|
||||
cursorHighlight={cursorHighlight}
|
||||
onCursorHighlightChange={(next) => 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}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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<VideoPlaybackRef, VideoPlaybackProps>(
|
||||
onBlurDataChange,
|
||||
onBlurDataCommit,
|
||||
cursorTelemetry = [],
|
||||
cursorHighlight = DEFAULT_CURSOR_HIGHLIGHT,
|
||||
cursorClickTimestamps = [],
|
||||
showCursor = false,
|
||||
cursorSize = DEFAULT_CURSOR_SIZE,
|
||||
@@ -285,9 +273,7 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(
|
||||
const currentTimeRef = useRef(0);
|
||||
const zoomRegionsRef = useRef<ZoomRegion[]>([]);
|
||||
const cursorTelemetryRef = useRef<CursorTelemetryPoint[]>([]);
|
||||
const cursorHighlightRef = useRef<CursorHighlightConfig>(DEFAULT_CURSOR_HIGHLIGHT);
|
||||
const cursorClickTimestampsRef = useRef<number[]>([]);
|
||||
const cursorHighlightGraphicsRef = useRef<Graphics | null>(null);
|
||||
const selectedZoomIdRef = useRef<string | null>(null);
|
||||
const animationStateRef = useRef({
|
||||
scale: 1,
|
||||
@@ -742,13 +728,6 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(
|
||||
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<VideoPlaybackRef, VideoPlaybackProps>(
|
||||
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<VideoPlaybackRef, VideoPlaybackProps>(
|
||||
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<VideoPlaybackRef, VideoPlaybackProps>(
|
||||
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;
|
||||
|
||||
|
||||
@@ -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<ProjectEditorState>): 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<import("./videoPlayback/cursorHighlight").CursorHighlightConfig>;
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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<EditorState> | ((prev: EditorState) => Partial<EditorState>);
|
||||
|
||||
@@ -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": "الخلفية",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "Фон",
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user