rm uiohook-napi
This commit is contained in:
+93
-200
@@ -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 @@
|
||||
"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)});
|
||||
|
||||
@@ -26,9 +26,7 @@
|
||||
"to": "assets/wallpapers"
|
||||
}
|
||||
],
|
||||
"asarUnpack": [
|
||||
"**/node_modules/uiohook-napi/**/*"
|
||||
],
|
||||
|
||||
"mac": {
|
||||
"target": [
|
||||
"dmg"
|
||||
|
||||
Vendored
+1
-3
@@ -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,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 {
|
||||
|
||||
@@ -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
@@ -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
@@ -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')
|
||||
},
|
||||
|
||||
Generated
+2
-15
@@ -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
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
Vendored
-9
@@ -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
@@ -14,9 +14,7 @@ export default defineConfig({
|
||||
entry: 'electron/main.ts',
|
||||
vite: {
|
||||
build: {
|
||||
rollupOptions: {
|
||||
external: ['uiohook-napi']
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user