})
Date: Sat, 2 May 2026 14:36:59 +0200
Subject: [PATCH 13/59] fix: scope IPC close-confirm responses to the
originating window
Both ipcMain.once handlers now check event.sender.id against
windowToClose.webContents.id and ignore messages from any other
renderer, preventing cross-window response mix-ups if multiple editor
windows are ever open simultaneously.
---
electron/main.ts | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/electron/main.ts b/electron/main.ts
index 94f0a42..3e0b232 100644
--- a/electron/main.ts
+++ b/electron/main.ts
@@ -295,14 +295,16 @@ function createEditorWindowWrapper() {
// Ask renderer to show the custom in-app dialog
windowToClose.webContents.send("request-close-confirm");
- ipcMain.once("close-confirm-response", (_, choice: "save" | "discard" | "cancel") => {
+ ipcMain.once("close-confirm-response", (event, choice: "save" | "discard" | "cancel") => {
+ if (event.sender.id !== windowToClose?.webContents.id) return;
isCloseConfirmInFlight = false;
if (!windowToClose || windowToClose.isDestroyed()) return;
if (choice === "save") {
// Tell renderer to save the project, then close when done
windowToClose.webContents.send("request-save-before-close");
- ipcMain.once("save-before-close-done", (_, shouldClose: boolean) => {
+ ipcMain.once("save-before-close-done", (event, shouldClose: boolean) => {
+ if (event.sender.id !== windowToClose?.webContents.id) return;
if (!shouldClose) return;
forceCloseEditorWindow(windowToClose);
});
From e4eeff0ea34ed2bcf14396e52f21802f8744a34b Mon Sep 17 00:00:00 2001
From: hiroppelx <66677513+hiroppelx@users.noreply.github.com>
Date: Sun, 3 May 2026 11:03:20 +0900
Subject: [PATCH 14/59] =?UTF-8?q?=E6=97=A5=E6=9C=AC=E8=AA=9E=E8=A8=B3?=
=?UTF-8?q?=E3=82=92=E6=94=B9=E5=96=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/i18n/locales/ja-JP/common.json | 4 +--
src/i18n/locales/ja-JP/dialogs.json | 24 ++++++++--------
src/i18n/locales/ja-JP/editor.json | 16 +++++------
src/i18n/locales/ja-JP/launch.json | 6 ++--
src/i18n/locales/ja-JP/settings.json | 43 ++++++++++++----------------
src/i18n/locales/ja-JP/timeline.json | 18 ++++++------
6 files changed, 52 insertions(+), 59 deletions(-)
diff --git a/src/i18n/locales/ja-JP/common.json b/src/i18n/locales/ja-JP/common.json
index ee2205a..ee804f0 100644
--- a/src/i18n/locales/ja-JP/common.json
+++ b/src/i18n/locales/ja-JP/common.json
@@ -7,7 +7,7 @@
"share": "共有",
"done": "完了",
"open": "開く",
- "upload": "アップロード",
+ "upload": "読み込む",
"export": "エクスポート",
"showInFolder": "フォルダに表示",
"file": "ファイル",
@@ -15,7 +15,7 @@
"view": "表示",
"window": "ウィンドウ",
"quit": "終了",
- "stopRecording": "録画停止"
+ "stopRecording": "録画を停止"
},
"playback": {
"play": "再生",
diff --git a/src/i18n/locales/ja-JP/dialogs.json b/src/i18n/locales/ja-JP/dialogs.json
index 3c3fce5..a59cde7 100644
--- a/src/i18n/locales/ja-JP/dialogs.json
+++ b/src/i18n/locales/ja-JP/dialogs.json
@@ -1,22 +1,22 @@
{
"export": {
"complete": "エクスポート完了",
- "yourFormatReady": "あなたの{{format}}が準備できました",
+ "yourFormatReady": "{{format}}の準備ができました",
"showInFolder": "フォルダで表示",
- "finalizingVideo": "ビデオのエクスポートを最終処理中...",
- "compilingGifProgress": "GIFをコンパイル中... {{progress}}%",
- "compilingGifWait": "GIFをコンパイル中... しばらくお待ちください",
+ "finalizingVideo": "動画のエクスポートを仕上げています...",
+ "compilingGifProgress": "GIFを生成中... {{progress}}%",
+ "compilingGifWait": "GIFを生成中... しばらくお待ちください",
"takeMoment": "少々お待ちください...",
"failed": "エクスポートに失敗しました",
"tryAgain": "もう一度お試しください",
- "finalizingVideoTitle": "ビデオの最終処理",
- "compilingGif": "GIFをコンパイル中",
+ "finalizingVideoTitle": "動画の仕上げ",
+ "compilingGif": "GIFを生成中",
"exportingFormat": "{{format}}をエクスポート中",
- "compiling": "コンパイル中",
+ "compiling": "生成中",
"renderingFrames": "フレームをレンダリング中",
"processing": "処理中...",
"finalizing": "最終処理中...",
- "compilingStatus": "コンパイル中...",
+ "compilingStatus": "生成中...",
"status": "ステータス",
"format": "フォーマット",
"frames": "フレーム",
@@ -58,13 +58,13 @@
},
"fileDialogs": {
"saveGif": "エクスポートしたGIFを保存",
- "saveVideo": "エクスポートしたビデオを保存",
- "selectVideo": "ビデオファイルを選択",
+ "saveVideo": "エクスポートした動画を保存",
+ "selectVideo": "動画ファイルを選択",
"saveProject": "OpenScreen プロジェクトを保存",
"openProject": "OpenScreen プロジェクトを開く",
"gifImage": "GIF 画像",
- "mp4Video": "MP4 ビデオ",
- "videoFiles": "ビデオファイル",
+ "mp4Video": "MP4 動画",
+ "videoFiles": "動画ファイル",
"openscreenProject": "OpenScreen プロジェクト",
"allFiles": "すべてのファイル"
}
diff --git a/src/i18n/locales/ja-JP/editor.json b/src/i18n/locales/ja-JP/editor.json
index 401dbc7..051335f 100644
--- a/src/i18n/locales/ja-JP/editor.json
+++ b/src/i18n/locales/ja-JP/editor.json
@@ -5,18 +5,18 @@
"cancel": "キャンセル",
"confirm": "確認"
},
- "loadingVideo": "ビデオを読み込み中...",
+ "loadingVideo": "動画を読み込み中...",
"errors": {
- "noVideoLoaded": "ビデオが読み込まれていません",
- "videoNotReady": "ビデオが準備できていません",
- "unableToDetermineSourcePath": "ソースビデオのパスを特定できません",
+ "noVideoLoaded": "動画が読み込まれていません",
+ "videoNotReady": "動画の準備ができていません",
+ "unableToDetermineSourcePath": "元動画のパスを特定できません",
"failedToSaveGif": "GIFの保存に失敗しました",
"gifExportFailed": "GIFのエクスポートに失敗しました",
- "failedToSaveVideo": "ビデオの保存に失敗しました",
+ "failedToSaveVideo": "動画の保存に失敗しました",
"exportFailed": "エクスポートに失敗しました",
"exportFailedWithError": "エクスポートに失敗しました: {{error}}",
"failedToSaveExport": "エクスポートの保存に失敗しました",
- "failedToSaveExportedVideo": "エクスポートしたビデオの保存に失敗しました",
+ "failedToSaveExportedVideo": "エクスポートした動画の保存に失敗しました",
"failedToRevealInFolder": "フォルダの表示に失敗しました: {{error}}",
"exportBackgroundLoadFailed": "エクスポートに失敗しました: 背景画像を読み込めませんでした ({{url}})"
},
@@ -35,8 +35,8 @@
"recording": {
"failedCameraAccess": "カメラのアクセス要求に失敗しました。",
"cameraBlocked": "カメラのアクセスがブロックされています。システム設定で有効にして、ウェブカメラを使用してください。",
- "systemAudioUnavailable": "システムオーディオが利用できません。システムオーディオなしで録画します。",
- "microphoneDenied": "マイクのアクセスが拒否されました。オーディオなしで録画を続行します。",
+ "systemAudioUnavailable": "システム音声を利用できません。システム音声なしで録画します。",
+ "microphoneDenied": "マイクへのアクセスが拒否されました。音声なしで録画を続行します。",
"cameraDenied": "カメラのアクセスが拒否されました。ウェブカメラなしで録画を続行します。",
"permissionDenied": "録画の権限が拒否されました。画面録画を許可してください。",
"cameraDisconnected": "ウェブカメラが切断されました。",
diff --git a/src/i18n/locales/ja-JP/launch.json b/src/i18n/locales/ja-JP/launch.json
index 4504b00..51e3833 100644
--- a/src/i18n/locales/ja-JP/launch.json
+++ b/src/i18n/locales/ja-JP/launch.json
@@ -6,12 +6,12 @@
"cancelRecording": "録画をキャンセル",
"pauseRecording": "録画を一時停止",
"resumeRecording": "録画を再開",
- "openVideoFile": "ビデオファイルを開く",
+ "openVideoFile": "動画ファイルを開く",
"openProject": "プロジェクトを開く"
},
"audio": {
- "enableSystemAudio": "システムオーディオを有効にする",
- "disableSystemAudio": "システムオーディオを無効にする",
+ "enableSystemAudio": "システム音声を有効にする",
+ "disableSystemAudio": "システム音声を無効にする",
"enableMicrophone": "マイクを有効にする",
"disableMicrophone": "マイクを無効にする",
"defaultMicrophone": "デフォルトのマイク"
diff --git a/src/i18n/locales/ja-JP/settings.json b/src/i18n/locales/ja-JP/settings.json
index 129217c..800d078 100644
--- a/src/i18n/locales/ja-JP/settings.json
+++ b/src/i18n/locales/ja-JP/settings.json
@@ -7,20 +7,13 @@
"title": "フォーカスモード",
"manual": "手動",
"auto": "自動",
- "autoDescription": "カメラが録画中のカーソル位置に追従します"
- },
- "speed": {
- "title": "ズーム速度",
- "instant": "即時",
- "fast": "高速",
- "smooth": "滑らか",
- "lazy": "遅延"
+ "autoDescription": "表示範囲が録画中のカーソル位置に追従します"
}
},
"speed": {
"playbackSpeed": "再生速度",
- "selectRegion": "速度範囲を選択して調整",
- "deleteRegion": "速度範囲を削除",
+ "selectRegion": "再生速度の範囲を選択して調整",
+ "deleteRegion": "再生速度の範囲を削除",
"customPlaybackSpeed": "カスタム再生速度",
"maxSpeedError": "速度は16×を超えることはできません"
},
@@ -31,14 +24,14 @@
"title": "レイアウト",
"preset": "プリセット",
"selectPreset": "プリセットを選択",
- "pictureInPicture": "ピクチャーインピクチャー",
- "verticalStack": "縦積み",
+ "pictureInPicture": "ピクチャーインピクチャ",
+ "verticalStack": "縦並び",
"dualFrame": "デュアルフレーム",
"webcamShape": "カメラの形状",
"webcamSize": "カメラのサイズ"
},
"effects": {
- "title": "ビデオ効果",
+ "title": "動画効果",
"blurBg": "背景をぼかす",
"motionBlur": "モーションブラー",
"off": "オフ",
@@ -51,14 +44,14 @@
"image": "画像",
"color": "色",
"gradient": "グラデーション",
- "uploadCustom": "カスタムをアップロード",
+ "uploadCustom": "カスタム画像を読み込む",
"gradientLabel": "グラデーション {{index}}",
"colorWheel": "カラーホイール",
"colorPalette": "カラーパレット"
},
"crop": {
"title": "クロップ",
- "cropVideo": "ビデオをクロップ",
+ "cropVideo": "動画をクロップ",
"dragInstruction": "各辺をドラッグしてクロップ範囲を調整",
"ratio": "比率",
"free": "自由",
@@ -69,8 +62,8 @@
"exportFormat": {
"mp4": "MP4",
"gif": "GIF",
- "mp4Video": "MP4 ビデオ",
- "mp4Description": "高品質のビデオファイル",
+ "mp4Video": "MP4 動画",
+ "mp4Description": "高品質の動画ファイル",
"gifAnimation": "GIF アニメーション",
"gifDescription": "共有用のアニメーション画像"
},
@@ -90,7 +83,7 @@
"load": "プロジェクトを読み込む"
},
"export": {
- "videoButton": "ビデオをエクスポート",
+ "videoButton": "動画をエクスポート",
"gifButton": "GIF をエクスポート",
"chooseSaveLocation": "保存場所を選択"
},
@@ -100,9 +93,9 @@
},
"imageUpload": {
"invalidFileType": "無効なファイル形式",
- "jpgOnly": "JPG または JPEG 画像ファイルをアップロードしてください。",
- "uploadSuccess": "カスタム画像が正常にアップロードされました!",
- "failedToUpload": "画像のアップロードに失敗しました",
+ "jpgOnly": "JPG または JPEG 画像ファイルを選択してください。",
+ "uploadSuccess": "カスタム画像を読み込みました。",
+ "failedToUpload": "画像の読み込みに失敗しました",
"errorReading": "ファイルの読み取り中にエラーが発生しました。"
},
"annotation": {
@@ -125,7 +118,7 @@
"colorWheel": "カラーホイール",
"colorPalette": "カラーパレット",
"clearBackground": "背景をクリア",
- "uploadImage": "画像をアップロード",
+ "uploadImage": "画像を読み込む",
"supportedFormats": "サポートされている形式: JPG, PNG, GIF, WebP",
"arrowDirection": "矢印の方向",
"strokeWidth": "線の太さ: {{width}}px",
@@ -148,9 +141,9 @@
"tipTabCycle": "Tabキーを使用して重なっている項目を順に切り替えます。",
"tipShiftTabCycle": "Shift+Tabキーを使用して逆順に切り替えます。",
"invalidImageType": "無効なファイル形式",
- "imageFormatsOnly": "JPG、PNG、GIF、またはWebP画像ファイルをアップロードしてください。",
- "imageUploadSuccess": "画像が正常にアップロードされました!",
- "failedImageUpload": "画像のアップロードに失敗しました"
+ "imageFormatsOnly": "JPG、PNG、GIF、またはWebP画像ファイルを選択してください。",
+ "imageUploadSuccess": "画像を読み込みました。",
+ "failedImageUpload": "画像の読み込みに失敗しました"
},
"fontStyles": {
"classic": "クラシック",
diff --git a/src/i18n/locales/ja-JP/timeline.json b/src/i18n/locales/ja-JP/timeline.json
index e0507f6..ec9472a 100644
--- a/src/i18n/locales/ja-JP/timeline.json
+++ b/src/i18n/locales/ja-JP/timeline.json
@@ -5,23 +5,23 @@
"addTrim": "トリムを追加 (T)",
"addAnnotation": "注釈を追加 (A)",
"addBlur": "ぼかしを追加 (B)",
- "addSpeed": "速度を追加 (S)"
+ "addSpeed": "再生速度を追加 (S)"
},
"hints": {
"pressZoom": "Zキーを押してズームを追加",
"pressTrim": "Tキーを押してトリムを追加",
"pressAnnotation": "Aキーを押して注釈を追加",
"pressBlur": "Bキーを押してぼかしを追加",
- "pressSpeed": "Sキーを押して速度を追加"
+ "pressSpeed": "Sキーを押して再生速度を追加"
},
"labels": {
"pan": "移動",
"zoom": "ズーム",
"trim": "トリム",
- "speed": "速度",
+ "speed": "再生速度",
"zoomItem": "ズーム {{index}}",
"trimItem": "トリム {{index}}",
- "speedItem": "速度 {{index}}",
+ "speedItem": "再生速度 {{index}}",
"annotationItem": "注釈",
"blurItem": "ぼかし {{index}}",
"imageItem": "画像",
@@ -36,17 +36,17 @@
"zoomExistsAtLocation": "この場所にはすでにズームが存在するか、十分なスペースがありません。",
"zoomSuggestionUnavailable": "ズームの自動提案機能が利用できません",
"noCursorTelemetry": "カーソルの動きが記録されていません",
- "noCursorTelemetryDescription": "まず画面収録を行い、カーソルに基づく提案を生成してください。",
+ "noCursorTelemetryDescription": "まず画面録画を行い、カーソルに基づく提案を生成してください。",
"noUsableTelemetry": "使用可能なカーソルの動きデータがありません",
"noUsableTelemetryDescription": "録画には十分なカーソルの動きデータが含まれていません。",
"noDwellMoments": "カーソルが静止したポイントが見つかりません",
"noDwellMomentsDescription": "強調したい操作の際に、カーソルを一時停止させて録画してみてください。",
"noAutoZoomSlots": "自動ズームを適用できる箇所がありません",
"noAutoZoomSlotsDescription": "検出された滞留ポイントが既存のズーム領域と重なっています。",
- "cannotPlaceTrim": "ここに切り取りを配置できません",
- "trimExistsAtLocation": "この場所にはすでに切り取りが存在するか、十分なスペースがありません。",
- "cannotPlaceSpeed": "ここに速度を配置できません",
- "speedExistsAtLocation": "この場所にはすでに速度が存在するか、十分なスペースがありません。"
+ "cannotPlaceTrim": "ここにトリムを配置できません",
+ "trimExistsAtLocation": "この場所にはすでにトリムが存在するか、十分なスペースがありません。",
+ "cannotPlaceSpeed": "ここに再生速度を配置できません",
+ "speedExistsAtLocation": "この場所にはすでに再生速度の範囲が存在するか、十分なスペースがありません。"
},
"success": {
"addedZoomSuggestions": "カーソルに基づくズーム提案を {{count}} 件追加しました",
From 8d79a14e3bba0a195df71ec57cd5c6540a06c04c Mon Sep 17 00:00:00 2001
From: Siddharth
Date: Sat, 2 May 2026 23:03:14 -0700
Subject: [PATCH 15/59] cursor highlighting and clicks
---
electron-builder.json5 | 5 +
electron/electron-env.d.ts | 6 +
electron/ipc/handlers.ts | 145 ++-
electron/preload.ts | 3 +
package-lock.json | 1054 ++---------------
package.json | 6 +-
src/components/video-editor/SettingsPanel.tsx | 186 +++
src/components/video-editor/VideoEditor.tsx | 24 +
src/components/video-editor/VideoPlayback.tsx | 74 +-
.../video-editor/projectPersistence.ts | 47 +
.../videoPlayback/cursorHighlight.ts | 125 ++
src/hooks/useEditorHistory.ts | 6 +
src/lib/exporter/frameRenderer.ts | 48 +
src/lib/exporter/gifExporter.ts | 4 +
src/lib/exporter/videoExporter.ts | 4 +
15 files changed, 769 insertions(+), 968 deletions(-)
create mode 100644 src/components/video-editor/videoPlayback/cursorHighlight.ts
diff --git a/electron-builder.json5 b/electron-builder.json5
index ca053ef..1770238 100644
--- a/electron-builder.json5
+++ b/electron-builder.json5
@@ -3,6 +3,11 @@
"$schema": "https://raw.githubusercontent.com/electron-userland/electron-builder/master/packages/app-builder-lib/scheme.json",
"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"
+ ],
"productName": "Openscreen",
"npmRebuild": true,
"buildDependenciesFromSource": true,
diff --git a/electron/electron-env.d.ts b/electron/electron-env.d.ts
index 85d8294..d9ebab2 100644
--- a/electron/electron-env.d.ts
+++ b/electron/electron-env.d.ts
@@ -37,6 +37,11 @@ interface Window {
status: string;
error?: string;
}>;
+ requestAccessibilityAccess: () => Promise<{
+ success: boolean;
+ granted: boolean;
+ error?: string;
+ }>;
assetBaseUrl: string;
storeRecordedVideo: (
videoData: ArrayBuffer,
@@ -68,6 +73,7 @@ interface Window {
getCursorTelemetry: (videoPath?: string) => Promise<{
success: boolean;
samples: CursorTelemetryPoint[];
+ clicks: number[];
message?: string;
error?: string;
}>;
diff --git a/electron/ipc/handlers.ts b/electron/ipc/handlers.ts
index e067f59..30fbf23 100644
--- a/electron/ipc/handlers.ts
+++ b/electron/ipc/handlers.ts
@@ -1,6 +1,10 @@
import fs from "node:fs/promises";
+import { createRequire } from "node:module";
import path from "node:path";
import { fileURLToPath } from "node:url";
+
+const nodeRequire = createRequire(import.meta.url);
+
import {
app,
BrowserWindow,
@@ -280,19 +284,24 @@ async function storeRecordedSessionFiles(payload: StoreRecordedSessionInput) {
const telemetryPath = `${screenVideoPath}.cursor.json`;
const pendingBatch = cursorTelemetryBuffer.takeNextBatch();
- if (pendingBatch && pendingBatch.samples.length > 0) {
+ const pendingClicks = takeCursorClickTimestamps();
+ if ((pendingBatch && pendingBatch.samples.length > 0) || pendingClicks.length > 0) {
try {
await fs.writeFile(
telemetryPath,
JSON.stringify(
- { version: CURSOR_TELEMETRY_VERSION, samples: pendingBatch.samples },
+ {
+ version: CURSOR_TELEMETRY_VERSION,
+ samples: pendingBatch?.samples ?? [],
+ clicks: pendingClicks,
+ },
null,
2,
),
"utf-8",
);
} catch (err) {
- cursorTelemetryBuffer.prependBatch(pendingBatch);
+ if (pendingBatch) cursorTelemetryBuffer.prependBatch(pendingBatch);
throw err;
}
}
@@ -321,15 +330,114 @@ const cursorTelemetryBuffer = createCursorTelemetryBuffer({
maxActiveSamples: MAX_CURSOR_SAMPLES,
});
+// Mouse click timestamps (macOS only — uiohook-napi behind Accessibility).
+const MAX_CURSOR_CLICKS = 60 * 60 * 60; // ~1 click/sec for an hour
+let cursorClickTimestampsMs: number[] = [];
+let uioHookInstance: {
+ start: () => void;
+ stop: () => void;
+ on: (...a: unknown[]) => void;
+ off?: (...a: unknown[]) => void;
+ removeListener?: (...a: unknown[]) => void;
+} | null = null;
+let uioHookMouseDownHandler: ((event: { time?: number }) => void) | null = null;
+let uioHookFailureLogged = false;
+
function clamp(value: number, min: number, max: number) {
return Math.min(max, Math.max(min, value));
}
+function loadUioHookForClicks(): typeof uioHookInstance {
+ try {
+ // Dynamic require + try/catch so a broken native binary doesn't crash startup.
+ const mod = nodeRequire("uiohook-napi");
+ const candidate = mod.uIOhook ?? mod.default?.uIOhook ?? mod.uiohook ?? mod.default;
+ if (candidate && typeof candidate.start === "function" && typeof candidate.on === "function") {
+ return candidate;
+ }
+ return null;
+ } catch (error) {
+ if (!uioHookFailureLogged) {
+ uioHookFailureLogged = true;
+ console.warn("[clickCapture] uiohook-napi unavailable:", error);
+ }
+ return null;
+ }
+}
+
+function startClickCapture() {
+ if (process.platform !== "darwin") return;
+ if (uioHookInstance) return;
+
+ // Passive check — the prompt fires from the renderer when the user toggles
+ // "Only on clicks" so it doesn't stack with the screen-recording prompt.
+ try {
+ if (!systemPreferences.isTrustedAccessibilityClient(false)) {
+ if (!uioHookFailureLogged) {
+ uioHookFailureLogged = true;
+ console.warn(
+ "[clickCapture] Accessibility permission not granted — click capture disabled.",
+ );
+ }
+ return;
+ }
+ } catch {
+ // fall through; uiohook will fail defensively below
+ }
+
+ const hook = loadUioHookForClicks();
+ if (!hook) return;
+
+ uioHookMouseDownHandler = (event) => {
+ const elapsed = Math.max(0, Date.now() - cursorCaptureStartTimeMs);
+ void event;
+ if (cursorClickTimestampsMs.length >= MAX_CURSOR_CLICKS) return;
+ cursorClickTimestampsMs.push(elapsed);
+ };
+
+ try {
+ hook.on("mousedown", uioHookMouseDownHandler);
+ hook.start();
+ uioHookInstance = hook;
+ } catch (error) {
+ if (!uioHookFailureLogged) {
+ uioHookFailureLogged = true;
+ console.warn("[clickCapture] failed to start uiohook:", error);
+ }
+ uioHookMouseDownHandler = null;
+ }
+}
+
+function stopClickCapture() {
+ if (!uioHookInstance) return;
+ try {
+ if (uioHookMouseDownHandler) {
+ if (typeof uioHookInstance.off === "function") {
+ uioHookInstance.off("mousedown", uioHookMouseDownHandler);
+ } else if (typeof uioHookInstance.removeListener === "function") {
+ uioHookInstance.removeListener("mousedown", uioHookMouseDownHandler);
+ }
+ }
+ uioHookInstance.stop();
+ } catch (error) {
+ console.warn("[clickCapture] failed to stop uiohook:", error);
+ }
+ uioHookInstance = null;
+ uioHookMouseDownHandler = null;
+}
+
+function takeCursorClickTimestamps(): number[] {
+ const out = cursorClickTimestampsMs;
+ cursorClickTimestampsMs = [];
+ return out;
+}
+
function stopCursorCapture() {
if (cursorCaptureInterval) {
clearInterval(cursorCaptureInterval);
cursorCaptureInterval = null;
}
+ stopClickCapture();
}
function sampleCursorPoint() {
@@ -594,6 +702,22 @@ 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) {
@@ -723,6 +847,8 @@ export function registerIpcHandlers(
const id = typeof recordingId === "number" ? recordingId : Date.now();
cursorTelemetryBuffer.startSession(id);
cursorCaptureStartTimeMs = Date.now();
+ cursorClickTimestampsMs = [];
+ startClickCapture();
sampleCursorPoint();
cursorCaptureInterval = setInterval(sampleCursorPoint, CURSOR_SAMPLE_INTERVAL_MS);
} else {
@@ -787,11 +913,19 @@ export function registerIpcHandlers(
})
.sort((a: CursorTelemetryPoint, b: CursorTelemetryPoint) => a.timeMs - b.timeMs);
- return { success: true, samples };
+ const rawClicks = Array.isArray(parsed?.clicks) ? parsed.clicks : [];
+ const clicks: number[] = rawClicks
+ .map((value: unknown) =>
+ typeof value === "number" && Number.isFinite(value) ? Math.max(0, value) : null,
+ )
+ .filter((v: number | null): v is number => v !== null)
+ .sort((a: number, b: number) => a - b);
+
+ return { success: true, samples, clicks };
} catch (error) {
const nodeError = error as NodeJS.ErrnoException;
if (nodeError.code === "ENOENT") {
- return { success: true, samples: [] };
+ return { success: true, samples: [], clicks: [] };
}
console.error("Failed to load cursor telemetry:", error);
return {
@@ -799,6 +933,7 @@ export function registerIpcHandlers(
message: "Failed to load cursor telemetry",
error: String(error),
samples: [],
+ clicks: [],
};
}
});
diff --git a/electron/preload.ts b/electron/preload.ts
index 46e16f0..6c705d7 100644
--- a/electron/preload.ts
+++ b/electron/preload.ts
@@ -40,6 +40,9 @@ 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 a449101..e823ad1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -47,11 +47,13 @@
"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"
},
"devDependencies": {
"@biomejs/biome": "^2.4.12",
+ "@electron/rebuild": "^4.0.4",
"@playwright/test": "^1.59.1",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
@@ -1078,25 +1080,18 @@
}
},
"node_modules/@electron/rebuild": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-4.0.3.tgz",
- "integrity": "sha512-u9vpTHRMkOYCs/1FLiSVAFZ7FbjsXK+bQuzviJZa+lG7BHZl1nz52/IcGvwa3sk80/fc3llutBkbCq10Vh8WQA==",
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-4.0.4.tgz",
+ "integrity": "sha512-Rzc39XPdk/+/wBG8MfwAHohXflep0ITUfulb6Rgz3R0NeSB1noE+E9/M/cb8ftCAiyDD9PPhLuuWgE1GaInbKg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@malept/cross-spawn-promise": "^2.0.0",
"debug": "^4.1.1",
- "detect-libc": "^2.0.1",
- "got": "^11.7.0",
- "graceful-fs": "^4.2.11",
"node-abi": "^4.2.0",
"node-api-version": "^0.2.1",
- "node-gyp": "^11.2.0",
- "ora": "^5.1.0",
- "read-binary-file-arch": "^1.0.6",
- "semver": "^7.3.5",
- "tar": "^7.5.6",
- "yargs": "^17.0.1"
+ "node-gyp": "^12.2.0",
+ "read-binary-file-arch": "^1.0.6"
},
"bin": {
"electron-rebuild": "lib/cli.js"
@@ -1105,19 +1100,6 @@
"node": ">=22.12.0"
}
},
- "node_modules/@electron/rebuild/node_modules/semver": {
- "version": "7.7.4",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
- "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/@electron/universal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@electron/universal/-/universal-2.0.3.tgz",
@@ -1791,80 +1773,6 @@
"integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
"license": "MIT"
},
- "node_modules/@isaacs/cliui": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
- "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "string-width": "^5.1.2",
- "string-width-cjs": "npm:string-width@^4.2.0",
- "strip-ansi": "^7.0.1",
- "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
- "wrap-ansi": "^8.1.0",
- "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/ansi-styles": {
- "version": "6.2.3",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
- "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/emoji-regex": {
- "version": "9.2.2",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
- "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@isaacs/cliui/node_modules/string-width": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
- "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "eastasianwidth": "^0.2.0",
- "emoji-regex": "^9.2.2",
- "strip-ansi": "^7.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
- "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^6.1.0",
- "string-width": "^5.0.1",
- "strip-ansi": "^7.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
"node_modules/@isaacs/fs-minipass": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
@@ -2048,56 +1956,6 @@
"node": ">= 8"
}
},
- "node_modules/@npmcli/agent": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz",
- "integrity": "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "agent-base": "^7.1.0",
- "http-proxy-agent": "^7.0.0",
- "https-proxy-agent": "^7.0.1",
- "lru-cache": "^10.0.1",
- "socks-proxy-agent": "^8.0.3"
- },
- "engines": {
- "node": "^18.17.0 || >=20.5.0"
- }
- },
- "node_modules/@npmcli/agent/node_modules/lru-cache": {
- "version": "10.4.3",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
- "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/@npmcli/fs": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz",
- "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "semver": "^7.3.5"
- },
- "engines": {
- "node": "^18.17.0 || >=20.5.0"
- }
- },
- "node_modules/@npmcli/fs/node_modules/semver": {
- "version": "7.7.4",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
- "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/@pixi/color": {
"version": "7.4.3",
"resolved": "https://registry.npmjs.org/@pixi/color/-/color-7.4.3.tgz",
@@ -2224,17 +2082,6 @@
"url": "^0.11.0"
}
},
- "node_modules/@pkgjs/parseargs": {
- "version": "0.11.0",
- "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
- "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "engines": {
- "node": ">=14"
- }
- },
"node_modules/@playwright/test": {
"version": "1.59.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.1.tgz",
@@ -4464,13 +4311,13 @@
"license": "MIT"
},
"node_modules/abbrev": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz",
- "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz",
+ "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==",
"dev": true,
"license": "ISC",
"engines": {
- "node": "^18.17.0 || >=20.5.0"
+ "node": "^20.17.0 || >=22.9.0"
}
},
"node_modules/acorn": {
@@ -4973,18 +4820,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/bl": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
- "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "buffer": "^5.5.0",
- "inherits": "^2.0.4",
- "readable-stream": "^3.4.0"
- }
- },
"node_modules/boolean": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz",
@@ -5073,6 +4908,7 @@
}
],
"license": "MIT",
+ "optional": true,
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
@@ -5172,92 +5008,6 @@
"node": ">= 10.0.0"
}
},
- "node_modules/cacache": {
- "version": "19.0.1",
- "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz",
- "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "@npmcli/fs": "^4.0.0",
- "fs-minipass": "^3.0.0",
- "glob": "^10.2.2",
- "lru-cache": "^10.0.1",
- "minipass": "^7.0.3",
- "minipass-collect": "^2.0.1",
- "minipass-flush": "^1.0.5",
- "minipass-pipeline": "^1.2.4",
- "p-map": "^7.0.2",
- "ssri": "^12.0.0",
- "tar": "^7.4.3",
- "unique-filename": "^4.0.0"
- },
- "engines": {
- "node": "^18.17.0 || >=20.5.0"
- }
- },
- "node_modules/cacache/node_modules/balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/cacache/node_modules/brace-expansion": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
- "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/cacache/node_modules/glob": {
- "version": "10.5.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
- "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
- "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "foreground-child": "^3.1.0",
- "jackspeak": "^3.1.2",
- "minimatch": "^9.0.4",
- "minipass": "^7.1.2",
- "package-json-from-dist": "^1.0.0",
- "path-scurry": "^1.11.1"
- },
- "bin": {
- "glob": "dist/esm/bin.mjs"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/cacache/node_modules/lru-cache": {
- "version": "10.4.3",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
- "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/cacache/node_modules/minimatch": {
- "version": "9.0.9",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
- "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.2"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
"node_modules/cacheable-lookup": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz",
@@ -5471,19 +5221,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/cli-spinners": {
- "version": "2.9.2",
- "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
- "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/cli-truncate": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz",
@@ -5548,16 +5285,6 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
- "node_modules/clone": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
- "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.8"
- }
- },
"node_modules/clone-response": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz",
@@ -5828,19 +5555,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/defaults": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
- "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "clone": "^1.0.2"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/defer-to-connect": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
@@ -5909,16 +5623,6 @@
"node": ">=6"
}
},
- "node_modules/detect-libc": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
- "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/detect-node": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
@@ -6138,13 +5842,6 @@
"license": "ISC",
"peer": true
},
- "node_modules/eastasianwidth": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
- "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/ejs": {
"version": "3.1.10",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
@@ -6396,17 +6093,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/encoding": {
- "version": "0.1.13",
- "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
- "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "iconv-lite": "^0.6.2"
- }
- },
"node_modules/end-of-stream": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
@@ -6818,36 +6504,6 @@
"integrity": "sha512-IKlE+pNvL2R+kVL1kEhUYqRxVqeFnjiIvHWDMLFXNaqyUdFXQM2wte44EfMYJNHkW16X991t2Zg8apKkhv7OBA==",
"license": "MIT"
},
- "node_modules/foreground-child": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
- "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "cross-spawn": "^7.0.6",
- "signal-exit": "^4.0.1"
- },
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/foreground-child/node_modules/signal-exit": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
- "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
"node_modules/form-data": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
@@ -6921,19 +6577,6 @@
"node": ">=6 <7 || >=8"
}
},
- "node_modules/fs-minipass": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz",
- "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "minipass": "^7.0.3"
- },
- "engines": {
- "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
- }
- },
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -7466,17 +7109,8 @@
"url": "https://feross.org/support"
}
],
- "license": "BSD-3-Clause"
- },
- "node_modules/imurmurhash": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
- "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.8.19"
- }
+ "license": "BSD-3-Clause",
+ "optional": true
},
"node_modules/indent-string": {
"version": "4.0.0",
@@ -7507,16 +7141,6 @@
"dev": true,
"license": "ISC"
},
- "node_modules/ip-address": {
- "version": "10.1.0",
- "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
- "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 12"
- }
- },
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -7575,16 +7199,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/is-interactive": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz",
- "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
@@ -7601,19 +7215,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/is-unicode-supported": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
- "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/isbinaryfile": {
"version": "5.0.7",
"resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz",
@@ -7643,22 +7244,6 @@
"integrity": "sha512-VaFW53yt8QO61k2WJui0dHf4SlL8lxBofUuUmwBo0ljPk0Drz2TiuDW4jo3wDcv41qy/SxrJ+VAzJ/qYqsmzRw==",
"license": "MIT"
},
- "node_modules/jackspeak": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
- "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "@isaacs/cliui": "^8.0.2"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- },
- "optionalDependencies": {
- "@pkgjs/parseargs": "^0.11.0"
- }
- },
"node_modules/jake": {
"version": "10.9.4",
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz",
@@ -8002,23 +7587,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/log-symbols": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
- "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "chalk": "^4.1.0",
- "is-unicode-supported": "^0.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/log-update": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz",
@@ -8147,29 +7715,6 @@
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
- "node_modules/make-fetch-happen": {
- "version": "14.0.3",
- "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz",
- "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "@npmcli/agent": "^3.0.0",
- "cacache": "^19.0.1",
- "http-cache-semantics": "^4.1.1",
- "minipass": "^7.0.2",
- "minipass-fetch": "^4.0.0",
- "minipass-flush": "^1.0.5",
- "minipass-pipeline": "^1.2.4",
- "negotiator": "^1.0.0",
- "proc-log": "^5.0.0",
- "promise-retry": "^2.0.1",
- "ssri": "^12.0.0"
- },
- "engines": {
- "node": "^18.17.0 || >=20.5.0"
- }
- },
"node_modules/matcher": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
@@ -8287,16 +7832,6 @@
"node": ">= 0.6"
}
},
- "node_modules/mimic-fn": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
- "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/mimic-function": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
@@ -8366,136 +7901,6 @@
"node": ">=16 || 14 >=14.17"
}
},
- "node_modules/minipass-collect": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz",
- "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "minipass": "^7.0.3"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- }
- },
- "node_modules/minipass-fetch": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz",
- "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "minipass": "^7.0.3",
- "minipass-sized": "^1.0.3",
- "minizlib": "^3.0.1"
- },
- "engines": {
- "node": "^18.17.0 || >=20.5.0"
- },
- "optionalDependencies": {
- "encoding": "^0.1.13"
- }
- },
- "node_modules/minipass-flush": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.7.tgz",
- "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "minipass": "^3.0.0"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/minipass-flush/node_modules/minipass": {
- "version": "3.3.6",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
- "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "yallist": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/minipass-flush/node_modules/yallist": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
- "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/minipass-pipeline": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz",
- "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "minipass": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/minipass-pipeline/node_modules/minipass": {
- "version": "3.3.6",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
- "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "yallist": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/minipass-pipeline/node_modules/yallist": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
- "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/minipass-sized": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz",
- "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "minipass": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/minipass-sized/node_modules/minipass": {
- "version": "3.3.6",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
- "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "yallist": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/minipass-sized/node_modules/yallist": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
- "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
- "dev": true,
- "license": "ISC"
- },
"node_modules/minizlib": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz",
@@ -8619,16 +8024,6 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
- "node_modules/negotiator": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
- "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
"node_modules/node-abi": {
"version": "4.28.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.28.0.tgz",
@@ -8687,28 +8082,49 @@
}
},
"node_modules/node-gyp": {
- "version": "11.5.0",
- "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.5.0.tgz",
- "integrity": "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ==",
+ "version": "12.3.0",
+ "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.3.0.tgz",
+ "integrity": "sha512-QNcUWM+HgJplcPzBvFBZ9VXacyGZ4+VTOb80PwWR+TlVzoHbRKULNEzpRsnaoxG3Wzr7Qh7BYxGDU3CbKib2Yg==",
"dev": true,
"license": "MIT",
"dependencies": {
"env-paths": "^2.2.0",
"exponential-backoff": "^3.1.1",
"graceful-fs": "^4.2.6",
- "make-fetch-happen": "^14.0.3",
- "nopt": "^8.0.0",
- "proc-log": "^5.0.0",
+ "nopt": "^9.0.0",
+ "proc-log": "^6.0.0",
"semver": "^7.3.5",
- "tar": "^7.4.3",
+ "tar": "^7.5.4",
"tinyglobby": "^0.2.12",
- "which": "^5.0.0"
+ "undici": "^6.25.0",
+ "which": "^6.0.0"
},
"bin": {
"node-gyp": "bin/node-gyp.js"
},
"engines": {
- "node": "^18.17.0 || >=20.5.0"
+ "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",
+ "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=20"
}
},
"node_modules/node-gyp/node_modules/semver": {
@@ -8724,6 +8140,32 @@
"node": ">=10"
}
},
+ "node_modules/node-gyp/node_modules/undici": {
+ "version": "6.25.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-6.25.0.tgz",
+ "integrity": "sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.17"
+ }
+ },
+ "node_modules/node-gyp/node_modules/which": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz",
+ "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^4.0.0"
+ },
+ "bin": {
+ "node-which": "bin/which.js"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
"node_modules/node-releases": {
"version": "2.0.37",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz",
@@ -8732,19 +8174,19 @@
"license": "MIT"
},
"node_modules/nopt": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz",
- "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==",
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz",
+ "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==",
"dev": true,
"license": "ISC",
"dependencies": {
- "abbrev": "^3.0.0"
+ "abbrev": "^4.0.0"
},
"bin": {
"nopt": "bin/nopt.js"
},
"engines": {
- "node": "^18.17.0 || >=20.5.0"
+ "node": "^20.17.0 || >=22.9.0"
}
},
"node_modules/normalize-path": {
@@ -8848,86 +8290,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/ora": {
- "version": "5.4.1",
- "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz",
- "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "bl": "^4.1.0",
- "chalk": "^4.1.0",
- "cli-cursor": "^3.1.0",
- "cli-spinners": "^2.5.0",
- "is-interactive": "^1.0.0",
- "is-unicode-supported": "^0.1.0",
- "log-symbols": "^4.1.0",
- "strip-ansi": "^6.0.0",
- "wcwidth": "^1.0.1"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/ora/node_modules/cli-cursor": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
- "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "restore-cursor": "^3.1.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/ora/node_modules/onetime": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
- "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "mimic-fn": "^2.1.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/ora/node_modules/restore-cursor": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
- "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "onetime": "^5.1.0",
- "signal-exit": "^3.0.2"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/ora/node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/p-cancelable": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz",
@@ -8954,26 +8316,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/p-map": {
- "version": "7.0.4",
- "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz",
- "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/package-json-from-dist": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
- "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
- "dev": true,
- "license": "BlueOak-1.0.0"
- },
"node_modules/parse-svg-path": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/parse-svg-path/-/parse-svg-path-0.1.2.tgz",
@@ -9019,30 +8361,6 @@
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"license": "MIT"
},
- "node_modules/path-scurry": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
- "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "lru-cache": "^10.2.0",
- "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
- },
- "engines": {
- "node": ">=16 || 14 >=14.18"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/path-scurry/node_modules/lru-cache": {
- "version": "10.4.3",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
- "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
- "dev": true,
- "license": "ISC"
- },
"node_modules/pathe": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
@@ -9438,13 +8756,13 @@
}
},
"node_modules/proc-log": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz",
- "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==",
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz",
+ "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==",
"dev": true,
"license": "ISC",
"engines": {
- "node": "^18.17.0 || >=20.5.0"
+ "node": "^20.17.0 || >=22.9.0"
}
},
"node_modules/progress": {
@@ -9785,21 +9103,6 @@
"pify": "^2.3.0"
}
},
- "node_modules/readable-stream": {
- "version": "3.6.2",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
- "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "inherits": "^2.0.3",
- "string_decoder": "^1.1.1",
- "util-deprecate": "^1.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
"node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@@ -10082,27 +9385,6 @@
"queue-microtask": "^1.2.2"
}
},
- "node_modules/safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@@ -10363,41 +9645,12 @@
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
"dev": true,
"license": "MIT",
+ "optional": true,
"engines": {
"node": ">= 6.0.0",
"npm": ">= 3.0.0"
}
},
- "node_modules/socks": {
- "version": "2.8.7",
- "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz",
- "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ip-address": "^10.0.1",
- "smart-buffer": "^4.2.0"
- },
- "engines": {
- "node": ">= 10.0.0",
- "npm": ">= 3.0.0"
- }
- },
- "node_modules/socks-proxy-agent": {
- "version": "8.0.5",
- "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz",
- "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "agent-base": "^7.1.2",
- "debug": "^4.3.4",
- "socks": "^2.8.3"
- },
- "engines": {
- "node": ">= 14"
- }
- },
"node_modules/sonner": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz",
@@ -10446,19 +9699,6 @@
"license": "BSD-3-Clause",
"optional": true
},
- "node_modules/ssri": {
- "version": "12.0.0",
- "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz",
- "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "minipass": "^7.0.3"
- },
- "engines": {
- "node": "^18.17.0 || >=20.5.0"
- }
- },
"node_modules/stackback": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
@@ -10483,16 +9723,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/string_decoder": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
- "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "safe-buffer": "~5.2.0"
- }
- },
"node_modules/string-argv": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz",
@@ -10518,35 +9748,6 @@
"node": ">=8"
}
},
- "node_modules/string-width-cjs": {
- "name": "string-width",
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/string-width-cjs/node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/string-width/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
@@ -10576,20 +9777,6 @@
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
- "node_modules/strip-ansi-cjs": {
- "name": "strip-ansi",
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/strip-ansi/node_modules/ansi-regex": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
@@ -11105,6 +10292,19 @@
"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",
@@ -11122,32 +10322,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/unique-filename": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz",
- "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "unique-slug": "^5.0.0"
- },
- "engines": {
- "node": "^18.17.0 || >=20.5.0"
- }
- },
- "node_modules/unique-slug": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz",
- "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "imurmurhash": "^0.1.4"
- },
- "engines": {
- "node": "^18.17.0 || >=20.5.0"
- }
- },
"node_modules/universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
@@ -11520,16 +10694,6 @@
"node": ">=18"
}
},
- "node_modules/wcwidth": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
- "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "defaults": "^1.0.3"
- }
- },
"node_modules/web-demuxer": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/web-demuxer/-/web-demuxer-4.0.0.tgz",
@@ -11625,38 +10789,6 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
- "node_modules/wrap-ansi-cjs": {
- "name": "wrap-ansi",
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
- "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/wrap-ansi/node_modules/ansi-styles": {
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
diff --git a/package.json b/package.json
index 102e97c..2709d3e 100644
--- a/package.json
+++ b/package.json
@@ -29,7 +29,9 @@
"test:browser": "vitest --config vitest.browser.config.ts --run",
"test:browser:install": "playwright install --with-deps chromium-headless-shell",
"test:e2e": "playwright test",
- "prepare": "husky"
+ "prepare": "husky",
+ "rebuild:native": "node ./node_modules/@electron/rebuild/lib/cli.js --force --only uiohook-napi",
+ "postinstall": "npm run rebuild:native"
},
"dependencies": {
"@fix-webm-duration/fix": "^1.0.1",
@@ -71,11 +73,13 @@
"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"
},
"devDependencies": {
"@biomejs/biome": "^2.4.12",
+ "@electron/rebuild": "^4.0.4",
"@playwright/test": "^1.59.1",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx
index 82e106c..5cac573 100644
--- a/src/components/video-editor/SettingsPanel.tsx
+++ b/src/components/video-editor/SettingsPanel.tsx
@@ -1,5 +1,6 @@
import {
Bug,
+ ChevronDown,
Crop,
Download,
Film,
@@ -22,6 +23,7 @@ import {
AccordionTrigger,
} from "@/components/ui/accordion";
import { Button } from "@/components/ui/button";
+import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import {
Select,
SelectContent,
@@ -151,6 +153,12 @@ 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;
@@ -238,6 +246,9 @@ const ZOOM_DEPTH_OPTIONS: Array<{ depth: ZoomDepth; label: string }> = [
];
export function SettingsPanel({
+ cursorHighlight,
+ onCursorHighlightChange,
+ cursorHighlightSupportsClicks = false,
selected,
onWallpaperChange,
selectedZoomDepth,
@@ -991,6 +1002,181 @@ export function SettingsPanel({