rm uiohook-napi

This commit is contained in:
Siddharth
2025-11-23 23:32:52 -07:00
parent 210977faf4
commit dae7dc5212
13 changed files with 109 additions and 441 deletions
+93 -200
View File
@@ -1,11 +1,10 @@
import { BrowserWindow as P, screen as O, ipcMain as c, desktopCapturer as M, shell as W, app as p, nativeImage as L, Tray as V, Menu as U } from "electron";
import { fileURLToPath as E } from "node:url";
import { BrowserWindow as v, screen as j, ipcMain as c, desktopCapturer as x, shell as D, app as l, nativeImage as F, Tray as W, Menu as O } from "electron";
import { fileURLToPath as P } from "node:url";
import t from "node:path";
import m from "node:fs/promises";
import { uIOhook as w } from "uiohook-napi";
const S = t.dirname(E(import.meta.url)), A = t.join(S, ".."), y = process.env.VITE_DEV_SERVER_URL, x = t.join(A, "dist");
function C() {
const e = new P({
import p from "node:fs/promises";
const _ = t.dirname(P(import.meta.url)), V = t.join(_, ".."), f = process.env.VITE_DEV_SERVER_URL, T = t.join(V, "dist");
function L() {
const e = new v({
width: 250,
height: 80,
minWidth: 250,
@@ -19,7 +18,7 @@ function C() {
skipTaskbar: !0,
hasShadow: !1,
webPreferences: {
preload: t.join(S, "preload.mjs"),
preload: t.join(_, "preload.mjs"),
nodeIntegration: !1,
contextIsolation: !0,
backgroundThrottling: !1
@@ -27,12 +26,12 @@ function C() {
});
return e.webContents.on("did-finish-load", () => {
e == null || e.webContents.send("main-process-message", (/* @__PURE__ */ new Date()).toLocaleString());
}), y ? e.loadURL(y + "?windowType=hud-overlay") : e.loadFile(t.join(x, "index.html"), {
}), f ? e.loadURL(f + "?windowType=hud-overlay") : e.loadFile(t.join(T, "index.html"), {
query: { windowType: "hud-overlay" }
}), e;
}
function N() {
const e = new P({
function k() {
const e = new v({
width: 1200,
height: 800,
minWidth: 800,
@@ -46,7 +45,7 @@ function N() {
title: "OpenScreen",
backgroundColor: "#000000",
webPreferences: {
preload: t.join(S, "preload.mjs"),
preload: t.join(_, "preload.mjs"),
nodeIntegration: !1,
contextIsolation: !0,
webSecurity: !1
@@ -54,146 +53,59 @@ function N() {
});
return e.maximize(), e.webContents.on("did-finish-load", () => {
e == null || e.webContents.send("main-process-message", (/* @__PURE__ */ new Date()).toLocaleString());
}), y ? e.loadURL(y + "?windowType=editor") : e.loadFile(t.join(x, "index.html"), {
}), f ? e.loadURL(f + "?windowType=editor") : e.loadFile(t.join(T, "index.html"), {
query: { windowType: "editor" }
}), e;
}
function H() {
const { width: e, height: n } = O.getPrimaryDisplay().workAreaSize, i = new P({
function U() {
const { width: e, height: s } = j.getPrimaryDisplay().workAreaSize, u = new v({
width: 620,
height: 420,
minHeight: 350,
maxHeight: 500,
x: Math.round((e - 620) / 2),
y: Math.round((n - 420) / 2),
y: Math.round((s - 420) / 2),
frame: !1,
resizable: !1,
alwaysOnTop: !0,
transparent: !0,
backgroundColor: "#00000000",
webPreferences: {
preload: t.join(S, "preload.mjs"),
preload: t.join(_, "preload.mjs"),
nodeIntegration: !1,
contextIsolation: !0
}
});
return y ? i.loadURL(y + "?windowType=source-selector") : i.loadFile(t.join(x, "index.html"), {
return f ? u.loadURL(f + "?windowType=source-selector") : u.loadFile(t.join(T, "index.html"), {
query: { windowType: "source-selector" }
}), i;
}), u;
}
let u = !1, _ = !1, d = 0, f = [];
function z() {
if (u)
return { success: !1, message: "Already tracking" };
if (u = !0, d = performance.now(), f = [], _)
return { success: !0, message: "Mouse tracking resumed", startTime: d };
q();
try {
return w.start(), _ = !0, { success: !0, message: "Mouse tracking started", startTime: d };
} catch (e) {
return console.error("Failed to start mouse tracking:", e), u = !1, { success: !1, message: "Failed to start hook", error: e };
}
}
function B() {
if (!u)
return { success: !1, message: "Not currently tracking" };
u = !1;
const e = performance.now() - d;
return {
success: !0,
message: "Mouse tracking stopped",
data: {
startTime: d,
events: f,
duration: e
}
};
}
function q() {
w.on("mousemove", (e) => {
if (u) {
const i = {
type: "move",
timestamp: performance.now() - d,
x: e.x,
y: e.y
};
f.push(i);
}
}), w.on("mousedown", (e) => {
if (u) {
const i = {
type: "down",
timestamp: performance.now() - d,
x: e.x,
y: e.y,
button: e.button,
clicks: e.clicks
};
f.push(i);
}
}), w.on("mouseup", (e) => {
if (u) {
const i = {
type: "up",
timestamp: performance.now() - d,
x: e.x,
y: e.y,
button: e.button
};
f.push(i);
}
}), w.on("click", (e) => {
if (u) {
const i = {
type: "click",
timestamp: performance.now() - d,
x: e.x,
y: e.y,
button: e.button,
clicks: e.clicks
};
f.push(i);
}
});
}
function $() {
return [...f];
}
function G() {
if (_)
try {
w.stop(), _ = !1, u = !1, f = [];
} catch (e) {
console.error("Error cleaning up mouse tracking:", e);
}
}
let b = null;
function J(e, n, i, v, T) {
c.handle("get-sources", async (o, a) => (await M.getSources(a)).map((r) => ({
let R = null;
function C(e, s, u, m, w) {
c.handle("get-sources", async (o, n) => (await x.getSources(n)).map((r) => ({
id: r.id,
name: r.name,
display_id: r.display_id,
thumbnail: r.thumbnail ? r.thumbnail.toDataURL() : null,
appIcon: r.appIcon ? r.appIcon.toDataURL() : null
}))), c.handle("select-source", (o, a) => {
b = a;
const s = v();
return s && s.close(), b;
}), c.handle("get-selected-source", () => b), c.handle("open-source-selector", () => {
const o = v();
}))), c.handle("select-source", (o, n) => {
R = n;
const i = m();
return i && i.close(), R;
}), c.handle("get-selected-source", () => R), c.handle("open-source-selector", () => {
const o = m();
if (o) {
o.focus();
return;
}
n();
s();
}), c.handle("switch-to-editor", () => {
const o = i();
const o = u();
o && o.close(), e();
}), c.handle("start-mouse-tracking", () => z()), c.handle("stop-mouse-tracking", () => B()), c.handle("store-recorded-video", async (o, a, s) => {
}), c.handle("store-recorded-video", async (o, n, i) => {
try {
const r = t.join(h, s);
return await m.writeFile(r, Buffer.from(a)), {
const r = t.join(h, i);
return await p.writeFile(r, Buffer.from(n)), {
success: !0,
path: r,
message: "Video stored successfully"
@@ -205,55 +117,36 @@ function J(e, n, i, v, T) {
error: String(r)
};
}
}), c.handle("store-mouse-tracking-data", async (o, a) => {
try {
const s = $();
if (s.length === 0)
return { success: !1, message: "No tracking data to save" };
const r = t.join(h, a);
return await m.writeFile(r, JSON.stringify(s, null, 2), "utf-8"), {
success: !0,
path: r,
eventCount: s.length,
message: "Mouse tracking data stored successfully"
};
} catch (s) {
return console.error("Failed to store mouse tracking data:", s), {
success: !1,
message: "Failed to store mouse tracking data",
error: String(s)
};
}
}), c.handle("get-recorded-video-path", async () => {
try {
const a = (await m.readdir(h)).filter((R) => R.endsWith(".webm"));
if (a.length === 0)
const n = (await p.readdir(h)).filter((y) => y.endsWith(".webm"));
if (n.length === 0)
return { success: !1, message: "No recorded video found" };
const s = a.sort().reverse()[0];
return { success: !0, path: t.join(h, s) };
const i = n.sort().reverse()[0];
return { success: !0, path: t.join(h, i) };
} catch (o) {
return console.error("Failed to get video path:", o), { success: !1, message: "Failed to get video path", error: String(o) };
}
}), c.handle("set-recording-state", (o, a) => {
T && T(a, (b || { name: "Screen" }).name);
}), c.handle("open-external-url", async (o, a) => {
}), c.handle("set-recording-state", (o, n) => {
w && w(n, (R || { name: "Screen" }).name);
}), c.handle("open-external-url", async (o, n) => {
try {
return await W.openExternal(a), { success: !0 };
} catch (s) {
return console.error("Failed to open URL:", s), { success: !1, error: String(s) };
return await D.openExternal(n), { success: !0 };
} catch (i) {
return console.error("Failed to open URL:", i), { success: !1, error: String(i) };
}
}), c.handle("get-asset-base-path", () => {
try {
return p.isPackaged ? t.join(process.resourcesPath, "assets") : t.join(p.getAppPath(), "public", "assets");
return l.isPackaged ? t.join(process.resourcesPath, "assets") : t.join(l.getAppPath(), "public", "assets");
} catch (o) {
return console.error("Failed to resolve asset base path:", o), null;
}
}), c.handle("save-exported-video", async (o, a, s) => {
}), c.handle("save-exported-video", async (o, n, i) => {
try {
const r = p.getPath("downloads"), R = t.join(r, s);
return await m.writeFile(R, Buffer.from(a)), {
const r = l.getPath("downloads"), y = t.join(r, i);
return await p.writeFile(y, Buffer.from(n)), {
success: !0,
path: R,
path: y,
message: "Video exported successfully"
};
} catch (r) {
@@ -265,79 +158,79 @@ function J(e, n, i, v, T) {
}
});
}
const K = t.dirname(E(import.meta.url)), h = t.join(p.getPath("userData"), "recordings");
async function Q() {
const A = t.dirname(P(import.meta.url)), h = t.join(l.getPath("userData"), "recordings");
async function M() {
try {
const e = await m.readdir(h), n = Date.now(), i = 1 * 24 * 60 * 60 * 1e3;
for (const v of e) {
const T = t.join(h, v), o = await m.stat(T);
n - o.mtimeMs > i && (await m.unlink(T), console.log(`Deleted old recording: ${v}`));
const e = await p.readdir(h), s = Date.now(), u = 1 * 24 * 60 * 60 * 1e3;
for (const m of e) {
const w = t.join(h, m), o = await p.stat(w);
s - o.mtimeMs > u && (await p.unlink(w), console.log(`Deleted old recording: ${m}`));
}
} catch (e) {
console.error("Failed to cleanup old recordings:", e);
}
}
async function X() {
async function z() {
try {
await m.mkdir(h, { recursive: !0 }), console.log("Recordings directory ready:", h);
await p.mkdir(h, { recursive: !0 }), console.log("Recordings directory ready:", h);
} catch (e) {
console.error("Failed to create recordings directory:", e);
}
}
process.env.APP_ROOT = t.join(K, "..");
const Y = process.env.VITE_DEV_SERVER_URL, ie = t.join(process.env.APP_ROOT, "dist-electron"), I = t.join(process.env.APP_ROOT, "dist");
process.env.VITE_PUBLIC = Y ? t.join(process.env.APP_ROOT, "public") : I;
let l = null, k = null, g = null, j = "";
function D() {
l = C();
process.env.APP_ROOT = t.join(A, "..");
const H = process.env.VITE_DEV_SERVER_URL, Q = t.join(process.env.APP_ROOT, "dist-electron"), b = t.join(process.env.APP_ROOT, "dist");
process.env.VITE_PUBLIC = H ? t.join(process.env.APP_ROOT, "public") : b;
let a = null, g = null, d = null, E = "";
function S() {
a = L();
}
function Z() {
const e = t.join(process.env.VITE_PUBLIC || I, "rec-button.png");
let n = L.createFromPath(e);
n = n.resize({ width: 24, height: 24, quality: "best" }), g = new V(n), F();
function B() {
const e = t.join(process.env.VITE_PUBLIC || b, "rec-button.png");
let s = F.createFromPath(e);
s = s.resize({ width: 24, height: 24, quality: "best" }), d = new W(s), I();
}
function F() {
if (!g) return;
function I() {
if (!d) return;
const e = [
{
label: "Stop Recording",
click: () => {
l && !l.isDestroyed() && l.webContents.send("stop-recording-from-tray");
a && !a.isDestroyed() && a.webContents.send("stop-recording-from-tray");
}
}
], n = U.buildFromTemplate(e);
g.setContextMenu(n), g.setToolTip(`Recording: ${j}`);
], s = O.buildFromTemplate(e);
d.setContextMenu(s), d.setToolTip(`Recording: ${E}`);
}
function ee() {
l && (l.close(), l = null), l = N();
function N() {
a && (a.close(), a = null), a = k();
}
function te() {
return k = H(), k.on("closed", () => {
k = null;
}), k;
function q() {
return g = U(), g.on("closed", () => {
g = null;
}), g;
}
p.on("window-all-closed", () => {
l.on("window-all-closed", () => {
});
p.on("activate", () => {
P.getAllWindows().length === 0 && D();
l.on("activate", () => {
v.getAllWindows().length === 0 && S();
});
p.on("before-quit", async (e) => {
e.preventDefault(), G(), await Q(), p.exit(0);
l.on("before-quit", async (e) => {
e.preventDefault(), await M(), l.exit(0);
});
p.whenReady().then(async () => {
await X(), J(
ee,
te,
() => l,
() => k,
(e, n) => {
j = n, e ? (g || Z(), F(), l && l.minimize()) : (g && (g.destroy(), g = null), l && l.restore());
l.whenReady().then(async () => {
await z(), C(
N,
q,
() => a,
() => g,
(e, s) => {
E = s, e ? (d || B(), I(), a && a.minimize()) : (d && (d.destroy(), d = null), a && a.restore());
}
), D();
), S();
});
export {
ie as MAIN_DIST,
Q as MAIN_DIST,
h as RECORDINGS_DIR,
I as RENDERER_DIST,
Y as VITE_DEV_SERVER_URL
b as RENDERER_DIST,
H as VITE_DEV_SERVER_URL
};
+1 -1
View File
@@ -1 +1 @@
"use strict";const e=require("electron");e.contextBridge.exposeInMainWorld("electronAPI",{getAssetBasePath:async()=>await e.ipcRenderer.invoke("get-asset-base-path"),getSources:async r=>await e.ipcRenderer.invoke("get-sources",r),switchToEditor:()=>e.ipcRenderer.invoke("switch-to-editor"),openSourceSelector:()=>e.ipcRenderer.invoke("open-source-selector"),selectSource:r=>e.ipcRenderer.invoke("select-source",r),getSelectedSource:()=>e.ipcRenderer.invoke("get-selected-source"),startMouseTracking:()=>e.ipcRenderer.invoke("start-mouse-tracking"),stopMouseTracking:()=>e.ipcRenderer.invoke("stop-mouse-tracking"),storeRecordedVideo:(r,t)=>e.ipcRenderer.invoke("store-recorded-video",r,t),storeMouseTrackingData:r=>e.ipcRenderer.invoke("store-mouse-tracking-data",r),getRecordedVideoPath:()=>e.ipcRenderer.invoke("get-recorded-video-path"),setRecordingState:r=>e.ipcRenderer.invoke("set-recording-state",r),onStopRecordingFromTray:r=>{const t=()=>r();return e.ipcRenderer.on("stop-recording-from-tray",t),()=>e.ipcRenderer.removeListener("stop-recording-from-tray",t)},openExternalUrl:r=>e.ipcRenderer.invoke("open-external-url",r),saveExportedVideo:(r,t)=>e.ipcRenderer.invoke("save-exported-video",r,t)});
"use strict";const e=require("electron");e.contextBridge.exposeInMainWorld("electronAPI",{getAssetBasePath:async()=>await e.ipcRenderer.invoke("get-asset-base-path"),getSources:async r=>await e.ipcRenderer.invoke("get-sources",r),switchToEditor:()=>e.ipcRenderer.invoke("switch-to-editor"),openSourceSelector:()=>e.ipcRenderer.invoke("open-source-selector"),selectSource:r=>e.ipcRenderer.invoke("select-source",r),getSelectedSource:()=>e.ipcRenderer.invoke("get-selected-source"),storeRecordedVideo:(r,t)=>e.ipcRenderer.invoke("store-recorded-video",r,t),getRecordedVideoPath:()=>e.ipcRenderer.invoke("get-recorded-video-path"),setRecordingState:r=>e.ipcRenderer.invoke("set-recording-state",r),onStopRecordingFromTray:r=>{const t=()=>r();return e.ipcRenderer.on("stop-recording-from-tray",t),()=>e.ipcRenderer.removeListener("stop-recording-from-tray",t)},openExternalUrl:r=>e.ipcRenderer.invoke("open-external-url",r),saveExportedVideo:(r,t)=>e.ipcRenderer.invoke("save-exported-video",r,t)});
+1 -3
View File
@@ -26,9 +26,7 @@
"to": "assets/wallpapers"
}
],
"asarUnpack": [
"**/node_modules/uiohook-napi/**/*"
],
"mac": {
"target": [
"dmg"
+1 -3
View File
@@ -29,10 +29,8 @@ interface Window {
openSourceSelector: () => Promise<void>
selectSource: (source: any) => Promise<any>
getSelectedSource: () => Promise<any>
startMouseTracking: () => Promise<void>
stopMouseTracking: () => Promise<void>
storeRecordedVideo: (videoData: ArrayBuffer, fileName: string) => Promise<{ success: boolean; path?: string; message?: string }>
storeMouseTrackingData: (fileName: string) => Promise<{ success: boolean; path?: string; eventCount?: number; message?: string }>
getRecordedVideoPath: () => Promise<{ success: boolean; path?: string; message?: string }>
setRecordingState: (recording: boolean) => Promise<void>
onStopRecordingFromTray: (callback: () => void) => () => void
+1 -32
View File
@@ -1,5 +1,5 @@
import { ipcMain, desktopCapturer, BrowserWindow, shell, app } from 'electron'
import { startMouseTracking, stopMouseTracking, getTrackingData } from './mouseTracking'
import fs from 'node:fs/promises'
import path from 'node:path'
import { RECORDINGS_DIR } from '../main'
@@ -54,13 +54,7 @@ export function registerIpcHandlers(
createEditorWindow()
})
ipcMain.handle('start-mouse-tracking', () => {
return startMouseTracking()
})
ipcMain.handle('stop-mouse-tracking', () => {
return stopMouseTracking()
})
ipcMain.handle('store-recorded-video', async (_, videoData: ArrayBuffer, fileName: string) => {
try {
@@ -82,32 +76,7 @@ export function registerIpcHandlers(
}
})
ipcMain.handle('store-mouse-tracking-data', async (_, fileName: string) => {
try {
const data = getTrackingData()
if (data.length === 0) {
return { success: false, message: 'No tracking data to save' }
}
const trackingPath = path.join(RECORDINGS_DIR, fileName)
await fs.writeFile(trackingPath, JSON.stringify(data, null, 2), 'utf-8')
return {
success: true,
path: trackingPath,
eventCount: data.length,
message: 'Mouse tracking data stored successfully'
}
} catch (error) {
console.error('Failed to store mouse tracking data:', error)
return {
success: false,
message: 'Failed to store mouse tracking data',
error: String(error)
}
}
})
ipcMain.handle('get-recorded-video-path', async () => {
try {
-155
View File
@@ -1,155 +0,0 @@
// uiohook-napi [WIP: scoping out mouse tracking with cross platform support for potential auto zoom/ post processing cursor effects]
// not currently being used.
import { uIOhook } from 'uiohook-napi'
let isMouseTrackingActive = false
let isHookStarted = false
let recordingStartTime: number = 0
let mouseEventData: MouseEvent[] = []
export interface MouseEvent {
type: 'move' | 'down' | 'up' | 'click'
timestamp: number // milliseconds since recording started
x: number
y: number
button?: unknown
clicks?: number
}
export interface MouseTrackingSession {
startTime: number
events: MouseEvent[]
duration: number
}
export function startMouseTracking() {
if (isMouseTrackingActive) {
return { success: false, message: 'Already tracking' }
}
isMouseTrackingActive = true
// Reset data for new recording session
recordingStartTime = performance.now()
mouseEventData = []
// Only start the hook once
if (!isHookStarted) {
setupMouseEventListeners()
try {
uIOhook.start()
isHookStarted = true
return { success: true, message: 'Mouse tracking started', startTime: recordingStartTime }
} catch (error) {
console.error('Failed to start mouse tracking:', error)
isMouseTrackingActive = false
return { success: false, message: 'Failed to start hook', error }
}
} else {
return { success: true, message: 'Mouse tracking resumed', startTime: recordingStartTime }
}
}
export function stopMouseTracking(): { success: boolean; message: string; data?: MouseTrackingSession } {
if (!isMouseTrackingActive) {
return { success: false, message: 'Not currently tracking' }
}
isMouseTrackingActive = false
const duration = performance.now() - recordingStartTime
const session: MouseTrackingSession = {
startTime: recordingStartTime,
events: mouseEventData,
duration: duration
}
return {
success: true,
message: 'Mouse tracking stopped',
data: session
}
}
function setupMouseEventListeners() {
// Track mouse movement
uIOhook.on('mousemove', (e) => {
if (isMouseTrackingActive) {
const timestamp = performance.now() - recordingStartTime
const event: MouseEvent = {
type: 'move',
timestamp,
x: e.x,
y: e.y
}
mouseEventData.push(event)
}
})
// Track mouse button press
uIOhook.on('mousedown', (e) => {
if (isMouseTrackingActive) {
const timestamp = performance.now() - recordingStartTime
const event: MouseEvent = {
type: 'down',
timestamp,
x: e.x,
y: e.y,
button: e.button,
clicks: e.clicks
}
mouseEventData.push(event)
}
})
// Track mouse button release
uIOhook.on('mouseup', (e) => {
if (isMouseTrackingActive) {
const timestamp = performance.now() - recordingStartTime
const event: MouseEvent = {
type: 'up',
timestamp,
x: e.x,
y: e.y,
button: e.button
}
mouseEventData.push(event)
}
})
// Track complete click events
uIOhook.on('click', (e) => {
if (isMouseTrackingActive) {
const timestamp = performance.now() - recordingStartTime
const event: MouseEvent = {
type: 'click',
timestamp,
x: e.x,
y: e.y,
button: e.button,
clicks: e.clicks
}
mouseEventData.push(event)
}
})
}
export function getTrackingData(): MouseEvent[] {
return [...mouseEventData]
}
export function cleanupMouseTracking() {
if (isHookStarted) {
try {
uIOhook.stop()
isHookStarted = false
isMouseTrackingActive = false
mouseEventData = []
} catch (error) {
console.error('Error cleaning up mouse tracking:', error)
}
}
}
+2 -2
View File
@@ -4,7 +4,7 @@ import path from 'node:path'
import fs from 'node:fs/promises'
import { createHudOverlayWindow, createEditorWindow, createSourceSelectorWindow } from './windows'
import { registerIpcHandlers } from './ipc/handlers'
import { cleanupMouseTracking } from './ipc/mouseTracking'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
@@ -126,7 +126,7 @@ app.on('activate', () => {
// Cleanup old recordings on quit (both macOS and other platforms)
app.on('before-quit', async (event) => {
event.preventDefault()
cleanupMouseTracking()
await cleanupOldRecordings()
app.exit(0)
})
+2 -9
View File
@@ -20,18 +20,11 @@ contextBridge.exposeInMainWorld('electronAPI', {
getSelectedSource: () => {
return ipcRenderer.invoke('get-selected-source')
},
startMouseTracking: () => {
return ipcRenderer.invoke('start-mouse-tracking')
},
stopMouseTracking: () => {
return ipcRenderer.invoke('stop-mouse-tracking')
},
storeRecordedVideo: (videoData: ArrayBuffer, fileName: string) => {
return ipcRenderer.invoke('store-recorded-video', videoData, fileName)
},
storeMouseTrackingData: (fileName: string) => {
return ipcRenderer.invoke('store-mouse-tracking-data', fileName)
},
getRecordedVideoPath: () => {
return ipcRenderer.invoke('get-recorded-video-path')
},
+2 -15
View File
@@ -29,8 +29,7 @@
"react-icons": "^5.5.0",
"sonner": "^2.0.7",
"tailwind-merge": "^3.3.1",
"tailwindcss-animate": "^1.0.7",
"uiohook-napi": "^1.5.4"
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@types/react": "^18.2.64",
@@ -9384,6 +9383,7 @@
"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==",
"dev": true,
"license": "MIT",
"bin": {
"node-gyp-build": "bin.js",
@@ -12262,19 +12262,6 @@
"node": ">=14.17"
}
},
"node_modules/uiohook-napi": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/uiohook-napi/-/uiohook-napi-1.5.4.tgz",
"integrity": "sha512-7vPVDNwgb6MwTgviA/dnF2MrW0X5xm76fAqaOAC3cEKkswqAZOPw1USu14Sr6383s5qhXegcJaR63CpJOPCNAg==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"node-gyp-build": "4.x.x"
},
"engines": {
"node": ">= 16"
}
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+1 -2
View File
@@ -32,8 +32,7 @@
"react-icons": "^5.5.0",
"sonner": "^2.0.7",
"tailwind-merge": "^3.3.1",
"tailwindcss-animate": "^1.0.7",
"uiohook-napi": "^1.5.4"
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@types/react": "^18.2.64",
+4 -7
View File
@@ -20,7 +20,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
}
mediaRecorder.current.stop();
setRecording(false);
window.electronAPI.stopMouseTracking();
window.electronAPI?.setRecordingState(false);
}
});
@@ -54,7 +54,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
alert("Please select a source to record");
return;
}
await window.electronAPI.startMouseTracking();
// Capture screen at source resolution without constraints
const mediaStream = await (navigator.mediaDevices as any).getUserMedia({
audio: false,
@@ -109,7 +109,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
chunks.current = [];
const timestamp = Date.now();
const videoFileName = `recording-${timestamp}.webm`;
const trackingFileName = `recording-${timestamp}_tracking.json`;
try {
const videoBlob = await fixWebmDuration(buggyBlob, duration);
const arrayBuffer = await videoBlob.arrayBuffer();
@@ -118,10 +118,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
console.error('Failed to store video:', videoResult.message);
return;
}
const trackingResult = await window.electronAPI.storeMouseTrackingData(trackingFileName);
if (!trackingResult.success) {
console.warn('Failed to store mouse tracking:', trackingResult.message);
}
await window.electronAPI.switchToEditor();
} catch (error) {
console.error('Error saving recording:', error);
-9
View File
@@ -16,21 +16,12 @@ interface Window {
openSourceSelector: () => Promise<void>
selectSource: (source: any) => Promise<any>
getSelectedSource: () => Promise<any>
startMouseTracking: () => Promise<{ success: boolean; startTime?: number }>
stopMouseTracking: () => Promise<{ success: boolean; data?: any }>
storeRecordedVideo: (videoData: ArrayBuffer, fileName: string) => Promise<{
success: boolean
path?: string
message: string
error?: string
}>
storeMouseTrackingData: (fileName: string) => Promise<{
success: boolean
path?: string
eventCount?: number
message: string
error?: string
}>
getRecordedVideoPath: () => Promise<{
success: boolean
path?: string
+1 -3
View File
@@ -14,9 +14,7 @@ export default defineConfig({
entry: 'electron/main.ts',
vite: {
build: {
rollupOptions: {
external: ['uiohook-napi']
}
}
}
},