diff --git a/dist-electron/main.js b/dist-electron/main.js index 4a4ce2f..2d1e494 100644 --- a/dist-electron/main.js +++ b/dist-electron/main.js @@ -2,6 +2,7 @@ import { BrowserWindow, screen, ipcMain, desktopCapturer, app } from "electron"; import { fileURLToPath } from "node:url"; import path from "node:path"; import { uIOhook } from "uiohook-napi"; +import fs from "node:fs/promises"; const __dirname$1 = path.dirname(fileURLToPath(import.meta.url)); const APP_ROOT = path.join(__dirname$1, ".."); const VITE_DEV_SERVER_URL$1 = process.env["VITE_DEV_SERVER_URL"]; @@ -98,77 +99,112 @@ function createSourceSelectorWindow() { } let isMouseTrackingActive = false; let isHookStarted = false; +let recordingStartTime = 0; +let mouseEventData = []; function startMouseTracking() { if (isMouseTrackingActive) { - console.log("โš ๏ธ Mouse tracking already active"); return { success: false, message: "Already tracking" }; } - console.log("๐ŸŽฏ Starting mouse tracking..."); isMouseTrackingActive = true; + recordingStartTime = performance.now(); + mouseEventData = []; if (!isHookStarted) { setupMouseEventListeners(); try { uIOhook.start(); isHookStarted = true; - console.log("โœ… Mouse tracking started successfully"); - console.log('๐Ÿ’ก If you see "Accessibility API is disabled" error:'); - console.log(" Go to System Settings โ†’ Privacy & Security โ†’ Accessibility"); - console.log(" Enable permissions for Electron/Terminal/VS Code"); - return { success: true, message: "Mouse tracking started" }; + 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 { - console.log("โœ… Mouse tracking resumed"); - return { success: true, message: "Mouse tracking resumed" }; + return { success: true, message: "Mouse tracking resumed", startTime: recordingStartTime }; } } function stopMouseTracking() { if (!isMouseTrackingActive) { - console.log("โš ๏ธ Mouse tracking not active"); return { success: false, message: "Not currently tracking" }; } - console.log("๐Ÿ›‘ Stopping mouse tracking..."); isMouseTrackingActive = false; - console.log("โœ… Mouse tracking stopped (events will still be captured but not logged)"); - return { success: true, message: "Mouse tracking stopped" }; + const duration = performance.now() - recordingStartTime; + const session = { + startTime: recordingStartTime, + events: mouseEventData, + duration + }; + return { + success: true, + message: "Mouse tracking stopped", + data: session + }; } function setupMouseEventListeners() { uIOhook.on("mousemove", (e) => { if (isMouseTrackingActive) { - console.log(`[MOUSE MOVE] x: ${e.x}, y: ${e.y}`); + const timestamp = performance.now() - recordingStartTime; + const event = { + type: "move", + timestamp, + x: e.x, + y: e.y + }; + mouseEventData.push(event); } }); uIOhook.on("mousedown", (e) => { if (isMouseTrackingActive) { - console.log(`[MOUSE DOWN] x: ${e.x}, y: ${e.y}, button: ${e.button}, clicks: ${e.clicks}`); + const timestamp = performance.now() - recordingStartTime; + const event = { + type: "down", + timestamp, + x: e.x, + y: e.y, + button: e.button, + clicks: e.clicks + }; + mouseEventData.push(event); } }); uIOhook.on("mouseup", (e) => { if (isMouseTrackingActive) { - console.log(`[MOUSE UP] x: ${e.x}, y: ${e.y}, button: ${e.button}`); + const timestamp = performance.now() - recordingStartTime; + const event = { + type: "up", + timestamp, + x: e.x, + y: e.y, + button: e.button + }; + mouseEventData.push(event); } }); uIOhook.on("click", (e) => { if (isMouseTrackingActive) { - console.log(`[CLICK] x: ${e.x}, y: ${e.y}, button: ${e.button}, clicks: ${e.clicks}`); - } - }); - uIOhook.on("wheel", (e) => { - if (isMouseTrackingActive) { - console.log(`[WHEEL] x: ${e.x}, y: ${e.y}, amount: ${e.amount}, direction: ${e.direction}, rotation: ${e.rotation}`); + const timestamp = performance.now() - recordingStartTime; + const event = { + type: "click", + timestamp, + x: e.x, + y: e.y, + button: e.button, + clicks: e.clicks + }; + mouseEventData.push(event); } }); } +function getTrackingData() { + return [...mouseEventData]; +} function cleanupMouseTracking() { if (isHookStarted) { try { uIOhook.stop(); isHookStarted = false; isMouseTrackingActive = false; - console.log("๐Ÿงน Mouse tracking cleaned up"); + mouseEventData = []; } catch (error) { console.error("Error cleaning up mouse tracking:", error); } @@ -219,6 +255,29 @@ function registerIpcHandlers(createEditorWindow2, createSourceSelectorWindow2, g ipcMain.handle("stop-mouse-tracking", () => { return stopMouseTracking(); }); + ipcMain.handle("save-mouse-tracking-data", async (_, videoFileName) => { + try { + const data = getTrackingData(); + if (data.length === 0) { + return { success: false, message: "No tracking data to save" }; + } + const jsonFileName = videoFileName.replace(".webm", "_tracking.json"); + const filePath = path.join(process.env.HOME || "", "Downloads", jsonFileName); + await fs.writeFile(filePath, JSON.stringify(data, null, 2), "utf-8"); + return { + success: true, + message: "Tracking data saved", + filePath, + eventCount: data.length + }; + } catch (error) { + return { + success: false, + message: "Failed to save tracking data", + error: String(error) + }; + } + }); } const __dirname = path.dirname(fileURLToPath(import.meta.url)); process.env.APP_ROOT = path.join(__dirname, ".."); diff --git a/dist-electron/preload.mjs b/dist-electron/preload.mjs index 68ca74f..712f445 100644 --- a/dist-electron/preload.mjs +++ b/dist-electron/preload.mjs @@ -21,5 +21,8 @@ electron.contextBridge.exposeInMainWorld("electronAPI", { }, stopMouseTracking: () => { return electron.ipcRenderer.invoke("stop-mouse-tracking"); + }, + saveMouseTrackingData: (videoFileName) => { + return electron.ipcRenderer.invoke("save-mouse-tracking-data", videoFileName); } }); diff --git a/electron/ipc/handlers.ts b/electron/ipc/handlers.ts index 15d278d..924a82b 100644 --- a/electron/ipc/handlers.ts +++ b/electron/ipc/handlers.ts @@ -1,5 +1,7 @@ import { ipcMain, desktopCapturer, BrowserWindow } from 'electron' -import { startMouseTracking, stopMouseTracking } from './mouseTracking' +import { startMouseTracking, stopMouseTracking, getTrackingData } from './mouseTracking' +import fs from 'node:fs/promises' +import path from 'node:path' // Store selected source let selectedSource: any = null @@ -67,4 +69,34 @@ export function registerIpcHandlers( ipcMain.handle('stop-mouse-tracking', () => { return stopMouseTracking() }) + + // Save mouse tracking data to file + ipcMain.handle('save-mouse-tracking-data', async (_, videoFileName: string) => { + try { + const data = getTrackingData() + + if (data.length === 0) { + return { success: false, message: 'No tracking data to save' } + } + + // Save to the same directory as the video, with .json extension + const jsonFileName = videoFileName.replace('.webm', '_tracking.json') + const filePath = path.join(process.env.HOME || '', 'Downloads', jsonFileName) + + await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8') + + return { + success: true, + message: 'Tracking data saved', + filePath, + eventCount: data.length + } + } catch (error) { + return { + success: false, + message: 'Failed to save tracking data', + error: String(error) + } + } + }) } diff --git a/electron/ipc/mouseTracking.ts b/electron/ipc/mouseTracking.ts index 8fc6064..91be57b 100644 --- a/electron/ipc/mouseTracking.ts +++ b/electron/ipc/mouseTracking.ts @@ -2,15 +2,34 @@ 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) { - console.log('โš ๏ธ Mouse tracking already active') return { success: false, message: 'Already tracking' } } - console.log('๐ŸŽฏ Starting mouse tracking...') isMouseTrackingActive = true + + // Reset data for new recording session + recordingStartTime = performance.now() + mouseEventData = [] // Only start the hook once if (!isHookStarted) { @@ -19,70 +38,104 @@ export function startMouseTracking() { try { uIOhook.start() isHookStarted = true - console.log('โœ… Mouse tracking started successfully') - console.log('๐Ÿ’ก If you see "Accessibility API is disabled" error:') - console.log(' Go to System Settings โ†’ Privacy & Security โ†’ Accessibility') - console.log(' Enable permissions for Electron/Terminal/VS Code') - return { success: true, message: 'Mouse tracking started' } + 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 { - console.log('โœ… Mouse tracking resumed') - return { success: true, message: 'Mouse tracking resumed' } + return { success: true, message: 'Mouse tracking resumed', startTime: recordingStartTime } } } -export function stopMouseTracking() { +export function stopMouseTracking(): { success: boolean; message: string; data?: MouseTrackingSession } { if (!isMouseTrackingActive) { - console.log('โš ๏ธ Mouse tracking not active') return { success: false, message: 'Not currently tracking' } } - console.log('๐Ÿ›‘ Stopping mouse tracking...') isMouseTrackingActive = false - console.log('โœ… Mouse tracking stopped (events will still be captured but not logged)') - return { success: true, message: 'Mouse tracking stopped' } + + 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) { - console.log(`[MOUSE MOVE] x: ${e.x}, y: ${e.y}`) + 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) { - console.log(`[MOUSE DOWN] x: ${e.x}, y: ${e.y}, button: ${e.button}, clicks: ${e.clicks}`) + 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) { - console.log(`[MOUSE UP] x: ${e.x}, y: ${e.y}, button: ${e.button}`) + 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) { - console.log(`[CLICK] x: ${e.x}, y: ${e.y}, button: ${e.button}, clicks: ${e.clicks}`) + 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) } }) +} - // Track mouse wheel scrolling - uIOhook.on('wheel', (e) => { - if (isMouseTrackingActive) { - console.log(`[WHEEL] x: ${e.x}, y: ${e.y}, amount: ${e.amount}, direction: ${e.direction}, rotation: ${e.rotation}`) - } - }) +export function getTrackingData(): MouseEvent[] { + return [...mouseEventData] } export function cleanupMouseTracking() { @@ -91,7 +144,7 @@ export function cleanupMouseTracking() { uIOhook.stop() isHookStarted = false isMouseTrackingActive = false - console.log('๐Ÿงน Mouse tracking cleaned up') + mouseEventData = [] } catch (error) { console.error('Error cleaning up mouse tracking:', error) } diff --git a/electron/preload.ts b/electron/preload.ts index c52a403..132260a 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -21,5 +21,8 @@ contextBridge.exposeInMainWorld('electronAPI', { }, stopMouseTracking: () => { return ipcRenderer.invoke('stop-mouse-tracking') + }, + saveMouseTrackingData: (videoFileName: string) => { + return ipcRenderer.invoke('save-mouse-tracking-data', videoFileName) } }) \ No newline at end of file diff --git a/src/hooks/useScreenRecorder.ts b/src/hooks/useScreenRecorder.ts index 4d56d63..d658298 100644 --- a/src/hooks/useScreenRecorder.ts +++ b/src/hooks/useScreenRecorder.ts @@ -37,7 +37,6 @@ export function useScreenRecorder(): UseScreenRecorderReturn { } // Start mouse tracking - console.log('Starting mouse tracking from renderer...') await window.electronAPI.startMouseTracking(); // Use the selected source @@ -68,21 +67,36 @@ export function useScreenRecorder(): UseScreenRecorderReturn { chunksRef.current.push(e.data); } }; - recorder.onstop = () => { + recorder.onstop = async () => { if (streamRef.current) { streamRef.current.getTracks().forEach((track) => track.stop()); streamRef.current = null; } if (chunksRef.current.length === 0) return; + const blob = new Blob(chunksRef.current, { type: mimeType }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; - a.download = `recording-${Date.now()}.webm`; + + // Generate filename with timestamp + const videoFileName = `recording-${Date.now()}.webm`; + a.download = videoFileName; + document.body.appendChild(a); a.click(); document.body.removeChild(a); setTimeout(() => URL.revokeObjectURL(url), 100); + + // Save mouse tracking data alongside the video + try { + const result = await window.electronAPI.saveMouseTrackingData(videoFileName); + if (!result.success) { + console.warn('Failed to save tracking data:', result.message); + } + } catch (error) { + console.error('Error saving tracking data:', error); + } }; recorder.onerror = () => { setRecording(false); @@ -107,7 +121,6 @@ export function useScreenRecorder(): UseScreenRecorderReturn { setRecording(false); // Stop mouse tracking - console.log('Stopping mouse tracking from renderer...') window.electronAPI.stopMouseTracking(); } }; diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 905aaa5..8c897ad 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -15,7 +15,13 @@ interface Window { openSourceSelector: () => Promise selectSource: (source: any) => Promise getSelectedSource: () => Promise - startMouseTracking: () => Promise<{ success: boolean }> - stopMouseTracking: () => Promise<{ success: boolean }> + startMouseTracking: () => Promise<{ success: boolean; startTime?: number }> + stopMouseTracking: () => Promise<{ success: boolean; data?: any }> + saveMouseTrackingData: (videoFileName: string) => Promise<{ + success: boolean + message: string + filePath?: string + eventCount?: number + }> } } \ No newline at end of file