uiohook mouse integration
This commit is contained in:
+183
-69
@@ -1,19 +1,13 @@
|
||||
import { app, BrowserWindow, ipcMain, desktopCapturer, screen } from "electron";
|
||||
import { createRequire } from "node:module";
|
||||
import { BrowserWindow, screen, ipcMain, desktopCapturer, app } from "electron";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import path from "node:path";
|
||||
createRequire(import.meta.url);
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
process.env.APP_ROOT = path.join(__dirname, "..");
|
||||
const VITE_DEV_SERVER_URL = process.env["VITE_DEV_SERVER_URL"];
|
||||
const MAIN_DIST = path.join(process.env.APP_ROOT, "dist-electron");
|
||||
const RENDERER_DIST = path.join(process.env.APP_ROOT, "dist");
|
||||
process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL ? path.join(process.env.APP_ROOT, "public") : RENDERER_DIST;
|
||||
let win;
|
||||
let sourceSelectorWindow = null;
|
||||
let selectedSource = null;
|
||||
import { uIOhook } from "uiohook-napi";
|
||||
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"];
|
||||
const RENDERER_DIST$1 = path.join(APP_ROOT, "dist");
|
||||
function createHudOverlayWindow() {
|
||||
win = new BrowserWindow({
|
||||
const win = new BrowserWindow({
|
||||
width: 250,
|
||||
height: 80,
|
||||
minWidth: 250,
|
||||
@@ -27,26 +21,26 @@ function createHudOverlayWindow() {
|
||||
skipTaskbar: true,
|
||||
hasShadow: false,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, "preload.mjs"),
|
||||
preload: path.join(__dirname$1, "preload.mjs"),
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
backgroundThrottling: false
|
||||
}
|
||||
});
|
||||
win.setResizable(false);
|
||||
win.webContents.on("did-finish-load", () => {
|
||||
win == null ? void 0 : win.webContents.send("main-process-message", (/* @__PURE__ */ new Date()).toLocaleString());
|
||||
});
|
||||
if (VITE_DEV_SERVER_URL) {
|
||||
win.loadURL(VITE_DEV_SERVER_URL + "?windowType=hud-overlay");
|
||||
if (VITE_DEV_SERVER_URL$1) {
|
||||
win.loadURL(VITE_DEV_SERVER_URL$1 + "?windowType=hud-overlay");
|
||||
} else {
|
||||
win.loadFile(path.join(RENDERER_DIST, "index.html"), {
|
||||
win.loadFile(path.join(RENDERER_DIST$1, "index.html"), {
|
||||
query: { windowType: "hud-overlay" }
|
||||
});
|
||||
}
|
||||
return win;
|
||||
}
|
||||
function createEditorWindow() {
|
||||
win = new BrowserWindow({
|
||||
const win = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 800,
|
||||
minWidth: 800,
|
||||
@@ -57,7 +51,7 @@ function createEditorWindow() {
|
||||
alwaysOnTop: false,
|
||||
skipTaskbar: false,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, "preload.mjs"),
|
||||
preload: path.join(__dirname$1, "preload.mjs"),
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true
|
||||
}
|
||||
@@ -65,17 +59,18 @@ function createEditorWindow() {
|
||||
win.webContents.on("did-finish-load", () => {
|
||||
win == null ? void 0 : win.webContents.send("main-process-message", (/* @__PURE__ */ new Date()).toLocaleString());
|
||||
});
|
||||
if (VITE_DEV_SERVER_URL) {
|
||||
win.loadURL(VITE_DEV_SERVER_URL + "?windowType=editor");
|
||||
if (VITE_DEV_SERVER_URL$1) {
|
||||
win.loadURL(VITE_DEV_SERVER_URL$1 + "?windowType=editor");
|
||||
} else {
|
||||
win.loadFile(path.join(RENDERER_DIST, "index.html"), {
|
||||
win.loadFile(path.join(RENDERER_DIST$1, "index.html"), {
|
||||
query: { windowType: "editor" }
|
||||
});
|
||||
}
|
||||
return win;
|
||||
}
|
||||
function createSourceSelectorWindow() {
|
||||
const { width, height } = screen.getPrimaryDisplay().workAreaSize;
|
||||
sourceSelectorWindow = new BrowserWindow({
|
||||
const win = new BrowserWindow({
|
||||
width: 620,
|
||||
height: 420,
|
||||
minHeight: 350,
|
||||
@@ -87,30 +82,174 @@ function createSourceSelectorWindow() {
|
||||
alwaysOnTop: true,
|
||||
backgroundColor: "#ffffff",
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, "preload.mjs"),
|
||||
preload: path.join(__dirname$1, "preload.mjs"),
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true
|
||||
}
|
||||
});
|
||||
sourceSelectorWindow.on("closed", () => {
|
||||
sourceSelectorWindow = null;
|
||||
});
|
||||
if (VITE_DEV_SERVER_URL) {
|
||||
sourceSelectorWindow.loadURL(VITE_DEV_SERVER_URL + "?windowType=source-selector");
|
||||
if (VITE_DEV_SERVER_URL$1) {
|
||||
win.loadURL(VITE_DEV_SERVER_URL$1 + "?windowType=source-selector");
|
||||
} else {
|
||||
sourceSelectorWindow.loadFile(path.join(RENDERER_DIST, "index.html"), {
|
||||
win.loadFile(path.join(RENDERER_DIST$1, "index.html"), {
|
||||
query: { windowType: "source-selector" }
|
||||
});
|
||||
}
|
||||
return sourceSelectorWindow;
|
||||
return win;
|
||||
}
|
||||
let isMouseTrackingActive = false;
|
||||
let isHookStarted = false;
|
||||
function startMouseTracking() {
|
||||
if (isMouseTrackingActive) {
|
||||
console.log("⚠️ Mouse tracking already active");
|
||||
return { success: false, message: "Already tracking" };
|
||||
}
|
||||
console.log("🎯 Starting mouse tracking...");
|
||||
isMouseTrackingActive = true;
|
||||
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" };
|
||||
} 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" };
|
||||
}
|
||||
}
|
||||
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" };
|
||||
}
|
||||
function setupMouseEventListeners() {
|
||||
uIOhook.on("mousemove", (e) => {
|
||||
if (isMouseTrackingActive) {
|
||||
console.log(`[MOUSE MOVE] x: ${e.x}, y: ${e.y}`);
|
||||
}
|
||||
});
|
||||
uIOhook.on("mousedown", (e) => {
|
||||
if (isMouseTrackingActive) {
|
||||
console.log(`[MOUSE DOWN] x: ${e.x}, y: ${e.y}, button: ${e.button}, clicks: ${e.clicks}`);
|
||||
}
|
||||
});
|
||||
uIOhook.on("mouseup", (e) => {
|
||||
if (isMouseTrackingActive) {
|
||||
console.log(`[MOUSE UP] x: ${e.x}, y: ${e.y}, button: ${e.button}`);
|
||||
}
|
||||
});
|
||||
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}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
function cleanupMouseTracking() {
|
||||
if (isHookStarted) {
|
||||
try {
|
||||
uIOhook.stop();
|
||||
isHookStarted = false;
|
||||
isMouseTrackingActive = false;
|
||||
console.log("🧹 Mouse tracking cleaned up");
|
||||
} catch (error) {
|
||||
console.error("Error cleaning up mouse tracking:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
let selectedSource = null;
|
||||
function registerIpcHandlers(createEditorWindow2, createSourceSelectorWindow2, getMainWindow, getSourceSelectorWindow) {
|
||||
ipcMain.handle("get-sources", async (_, opts) => {
|
||||
const sources = await desktopCapturer.getSources(opts);
|
||||
const processedSources = sources.map((source) => ({
|
||||
id: source.id,
|
||||
name: source.name,
|
||||
display_id: source.display_id,
|
||||
thumbnail: source.thumbnail ? source.thumbnail.toDataURL() : null,
|
||||
appIcon: source.appIcon ? source.appIcon.toDataURL() : null
|
||||
}));
|
||||
return processedSources;
|
||||
});
|
||||
ipcMain.handle("select-source", (_, source) => {
|
||||
selectedSource = source;
|
||||
const sourceSelectorWin = getSourceSelectorWindow();
|
||||
if (sourceSelectorWin) {
|
||||
sourceSelectorWin.close();
|
||||
}
|
||||
return selectedSource;
|
||||
});
|
||||
ipcMain.handle("get-selected-source", () => {
|
||||
return selectedSource;
|
||||
});
|
||||
ipcMain.handle("open-source-selector", () => {
|
||||
const sourceSelectorWin = getSourceSelectorWindow();
|
||||
if (sourceSelectorWin) {
|
||||
sourceSelectorWin.focus();
|
||||
return;
|
||||
}
|
||||
createSourceSelectorWindow2();
|
||||
});
|
||||
ipcMain.handle("switch-to-editor", () => {
|
||||
const mainWin = getMainWindow();
|
||||
if (mainWin) {
|
||||
mainWin.close();
|
||||
}
|
||||
createEditorWindow2();
|
||||
});
|
||||
ipcMain.handle("start-mouse-tracking", () => {
|
||||
return startMouseTracking();
|
||||
});
|
||||
ipcMain.handle("stop-mouse-tracking", () => {
|
||||
return stopMouseTracking();
|
||||
});
|
||||
}
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
process.env.APP_ROOT = path.join(__dirname, "..");
|
||||
const VITE_DEV_SERVER_URL = process.env["VITE_DEV_SERVER_URL"];
|
||||
const MAIN_DIST = path.join(process.env.APP_ROOT, "dist-electron");
|
||||
const RENDERER_DIST = path.join(process.env.APP_ROOT, "dist");
|
||||
process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL ? path.join(process.env.APP_ROOT, "public") : RENDERER_DIST;
|
||||
let mainWindow = null;
|
||||
let sourceSelectorWindow = null;
|
||||
function createWindow() {
|
||||
createHudOverlayWindow();
|
||||
mainWindow = createHudOverlayWindow();
|
||||
}
|
||||
function createEditorWindowWrapper() {
|
||||
if (mainWindow) {
|
||||
mainWindow.close();
|
||||
mainWindow = null;
|
||||
}
|
||||
mainWindow = createEditorWindow();
|
||||
}
|
||||
function createSourceSelectorWindowWrapper() {
|
||||
sourceSelectorWindow = createSourceSelectorWindow();
|
||||
sourceSelectorWindow.on("closed", () => {
|
||||
sourceSelectorWindow = null;
|
||||
});
|
||||
return sourceSelectorWindow;
|
||||
}
|
||||
app.on("window-all-closed", () => {
|
||||
if (process.platform !== "darwin") {
|
||||
cleanupMouseTracking();
|
||||
app.quit();
|
||||
win = null;
|
||||
mainWindow = null;
|
||||
}
|
||||
});
|
||||
app.on("activate", () => {
|
||||
@@ -118,43 +257,18 @@ app.on("activate", () => {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
ipcMain.handle("get-sources", async (_, opts) => {
|
||||
const sources = await desktopCapturer.getSources(opts);
|
||||
const processedSources = sources.map((source) => ({
|
||||
id: source.id,
|
||||
name: source.name,
|
||||
display_id: source.display_id,
|
||||
thumbnail: source.thumbnail ? source.thumbnail.toDataURL() : null,
|
||||
appIcon: source.appIcon ? source.appIcon.toDataURL() : null
|
||||
}));
|
||||
return processedSources;
|
||||
app.on("will-quit", () => {
|
||||
cleanupMouseTracking();
|
||||
});
|
||||
ipcMain.handle("select-source", (_, source) => {
|
||||
selectedSource = source;
|
||||
if (sourceSelectorWindow) {
|
||||
sourceSelectorWindow.close();
|
||||
sourceSelectorWindow = null;
|
||||
}
|
||||
return selectedSource;
|
||||
app.whenReady().then(() => {
|
||||
registerIpcHandlers(
|
||||
createEditorWindowWrapper,
|
||||
createSourceSelectorWindowWrapper,
|
||||
() => mainWindow,
|
||||
() => sourceSelectorWindow
|
||||
);
|
||||
createWindow();
|
||||
});
|
||||
ipcMain.handle("get-selected-source", () => {
|
||||
return selectedSource;
|
||||
});
|
||||
ipcMain.handle("open-source-selector", () => {
|
||||
if (sourceSelectorWindow) {
|
||||
sourceSelectorWindow.focus();
|
||||
return;
|
||||
}
|
||||
createSourceSelectorWindow();
|
||||
});
|
||||
ipcMain.handle("switch-to-editor", () => {
|
||||
if (win) {
|
||||
win.close();
|
||||
win = null;
|
||||
}
|
||||
createEditorWindow();
|
||||
});
|
||||
app.whenReady().then(createWindow);
|
||||
export {
|
||||
MAIN_DIST,
|
||||
RENDERER_DIST,
|
||||
|
||||
@@ -15,5 +15,11 @@ electron.contextBridge.exposeInMainWorld("electronAPI", {
|
||||
},
|
||||
getSelectedSource: () => {
|
||||
return electron.ipcRenderer.invoke("get-selected-source");
|
||||
},
|
||||
startMouseTracking: () => {
|
||||
return electron.ipcRenderer.invoke("start-mouse-tracking");
|
||||
},
|
||||
stopMouseTracking: () => {
|
||||
return electron.ipcRenderer.invoke("stop-mouse-tracking");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import { ipcMain, desktopCapturer, BrowserWindow } from 'electron'
|
||||
import { startMouseTracking, stopMouseTracking } from './mouseTracking'
|
||||
|
||||
// Store selected source
|
||||
let selectedSource: any = null
|
||||
|
||||
export function registerIpcHandlers(
|
||||
createEditorWindow: () => void,
|
||||
createSourceSelectorWindow: () => BrowserWindow,
|
||||
getMainWindow: () => BrowserWindow | null,
|
||||
getSourceSelectorWindow: () => BrowserWindow | null
|
||||
) {
|
||||
// Get available desktop capturer sources
|
||||
ipcMain.handle('get-sources', async (_, opts) => {
|
||||
const sources = await desktopCapturer.getSources(opts)
|
||||
const processedSources = sources.map(source => ({
|
||||
id: source.id,
|
||||
name: source.name,
|
||||
display_id: source.display_id,
|
||||
thumbnail: source.thumbnail ? source.thumbnail.toDataURL() : null,
|
||||
appIcon: source.appIcon ? source.appIcon.toDataURL() : null
|
||||
}))
|
||||
|
||||
return processedSources
|
||||
})
|
||||
|
||||
// Select a source for recording
|
||||
ipcMain.handle('select-source', (_, source) => {
|
||||
selectedSource = source
|
||||
const sourceSelectorWin = getSourceSelectorWindow()
|
||||
if (sourceSelectorWin) {
|
||||
sourceSelectorWin.close()
|
||||
}
|
||||
return selectedSource
|
||||
})
|
||||
|
||||
// Get the currently selected source
|
||||
ipcMain.handle('get-selected-source', () => {
|
||||
return selectedSource
|
||||
})
|
||||
|
||||
// Open the source selector window
|
||||
ipcMain.handle('open-source-selector', () => {
|
||||
const sourceSelectorWin = getSourceSelectorWindow()
|
||||
if (sourceSelectorWin) {
|
||||
sourceSelectorWin.focus()
|
||||
return
|
||||
}
|
||||
createSourceSelectorWindow()
|
||||
})
|
||||
|
||||
// Switch from HUD overlay to editor window
|
||||
ipcMain.handle('switch-to-editor', () => {
|
||||
const mainWin = getMainWindow()
|
||||
if (mainWin) {
|
||||
mainWin.close()
|
||||
}
|
||||
createEditorWindow()
|
||||
})
|
||||
|
||||
// Start mouse tracking
|
||||
ipcMain.handle('start-mouse-tracking', () => {
|
||||
return startMouseTracking()
|
||||
})
|
||||
|
||||
// Stop mouse tracking
|
||||
ipcMain.handle('stop-mouse-tracking', () => {
|
||||
return stopMouseTracking()
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import { uIOhook } from 'uiohook-napi'
|
||||
|
||||
let isMouseTrackingActive = false
|
||||
let isHookStarted = false
|
||||
|
||||
export function startMouseTracking() {
|
||||
if (isMouseTrackingActive) {
|
||||
console.log('⚠️ Mouse tracking already active')
|
||||
return { success: false, message: 'Already tracking' }
|
||||
}
|
||||
|
||||
console.log('🎯 Starting mouse tracking...')
|
||||
isMouseTrackingActive = true
|
||||
|
||||
// Only start the hook once
|
||||
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' }
|
||||
} 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' }
|
||||
}
|
||||
}
|
||||
|
||||
export 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' }
|
||||
}
|
||||
|
||||
|
||||
function setupMouseEventListeners() {
|
||||
// Track mouse movement
|
||||
uIOhook.on('mousemove', (e) => {
|
||||
if (isMouseTrackingActive) {
|
||||
console.log(`[MOUSE MOVE] x: ${e.x}, y: ${e.y}`)
|
||||
}
|
||||
})
|
||||
|
||||
// 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}`)
|
||||
}
|
||||
})
|
||||
|
||||
// Track mouse button release
|
||||
uIOhook.on('mouseup', (e) => {
|
||||
if (isMouseTrackingActive) {
|
||||
console.log(`[MOUSE UP] x: ${e.x}, y: ${e.y}, button: ${e.button}`)
|
||||
}
|
||||
})
|
||||
|
||||
// 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}`)
|
||||
}
|
||||
})
|
||||
|
||||
// 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 cleanupMouseTracking() {
|
||||
if (isHookStarted) {
|
||||
try {
|
||||
uIOhook.stop()
|
||||
isHookStarted = false
|
||||
isMouseTrackingActive = false
|
||||
console.log('🧹 Mouse tracking cleaned up')
|
||||
} catch (error) {
|
||||
console.error('Error cleaning up mouse tracking:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
+36
-152
@@ -1,9 +1,10 @@
|
||||
import { app, BrowserWindow, ipcMain, desktopCapturer, screen } from 'electron'
|
||||
import { createRequire } from 'node:module'
|
||||
import { app, BrowserWindow } from 'electron'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import path from 'node:path'
|
||||
import { createHudOverlayWindow, createEditorWindow, createSourceSelectorWindow } from './windows'
|
||||
import { registerIpcHandlers } from './ipc/handlers'
|
||||
import { cleanupMouseTracking } from './ipc/mouseTracking'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
// The built directory structure
|
||||
@@ -24,117 +25,28 @@ export const RENDERER_DIST = path.join(process.env.APP_ROOT, 'dist')
|
||||
|
||||
process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL ? path.join(process.env.APP_ROOT, 'public') : RENDERER_DIST
|
||||
|
||||
let win: BrowserWindow | null
|
||||
// Window references
|
||||
let mainWindow: BrowserWindow | null = null
|
||||
let sourceSelectorWindow: BrowserWindow | null = null
|
||||
let selectedSource: any = null
|
||||
|
||||
function createHudOverlayWindow() {
|
||||
win = new BrowserWindow({
|
||||
width: 250,
|
||||
height: 80,
|
||||
minWidth: 250,
|
||||
maxWidth: 250,
|
||||
minHeight: 80,
|
||||
maxHeight: 80,
|
||||
frame: false,
|
||||
transparent: true,
|
||||
resizable: false,
|
||||
alwaysOnTop: true,
|
||||
skipTaskbar: true,
|
||||
hasShadow: false,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.mjs'),
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
backgroundThrottling: false,
|
||||
},
|
||||
})
|
||||
|
||||
// Absolutely lock the size
|
||||
win.setResizable(false)
|
||||
|
||||
win.webContents.on('did-finish-load', () => {
|
||||
win?.webContents.send('main-process-message', (new Date).toLocaleString())
|
||||
})
|
||||
|
||||
if (VITE_DEV_SERVER_URL) {
|
||||
win.loadURL(VITE_DEV_SERVER_URL + '?windowType=hud-overlay')
|
||||
} else {
|
||||
win.loadFile(path.join(RENDERER_DIST, 'index.html'), {
|
||||
query: { windowType: 'hud-overlay' }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function createEditorWindow() {
|
||||
win = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 800,
|
||||
minWidth: 800,
|
||||
minHeight: 600,
|
||||
frame: true,
|
||||
transparent: false,
|
||||
resizable: true,
|
||||
alwaysOnTop: false,
|
||||
skipTaskbar: false,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.mjs'),
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
},
|
||||
})
|
||||
|
||||
win.webContents.on('did-finish-load', () => {
|
||||
win?.webContents.send('main-process-message', (new Date).toLocaleString())
|
||||
})
|
||||
|
||||
if (VITE_DEV_SERVER_URL) {
|
||||
win.loadURL(VITE_DEV_SERVER_URL + '?windowType=editor')
|
||||
} else {
|
||||
win.loadFile(path.join(RENDERER_DIST, 'index.html'), {
|
||||
query: { windowType: 'editor' }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function createSourceSelectorWindow() {
|
||||
const { width, height } = screen.getPrimaryDisplay().workAreaSize;
|
||||
|
||||
sourceSelectorWindow = new BrowserWindow({
|
||||
width: 620,
|
||||
height: 420,
|
||||
minHeight: 350,
|
||||
maxHeight: 500,
|
||||
x: Math.round((width - 620) / 2),
|
||||
y: Math.round((height - 420) / 2),
|
||||
frame: false,
|
||||
resizable: false,
|
||||
alwaysOnTop: true,
|
||||
backgroundColor: '#ffffff',
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.mjs'),
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
},
|
||||
});
|
||||
|
||||
sourceSelectorWindow.on('closed', () => {
|
||||
sourceSelectorWindow = null;
|
||||
});
|
||||
|
||||
if (VITE_DEV_SERVER_URL) {
|
||||
sourceSelectorWindow.loadURL(VITE_DEV_SERVER_URL + '?windowType=source-selector');
|
||||
} else {
|
||||
sourceSelectorWindow.loadFile(path.join(RENDERER_DIST, 'index.html'), {
|
||||
query: { windowType: 'source-selector' }
|
||||
});
|
||||
}
|
||||
|
||||
return sourceSelectorWindow;
|
||||
}
|
||||
|
||||
function createWindow() {
|
||||
createHudOverlayWindow()
|
||||
mainWindow = createHudOverlayWindow()
|
||||
}
|
||||
|
||||
function createEditorWindowWrapper() {
|
||||
if (mainWindow) {
|
||||
mainWindow.close()
|
||||
mainWindow = null
|
||||
}
|
||||
mainWindow = createEditorWindow()
|
||||
}
|
||||
|
||||
function createSourceSelectorWindowWrapper() {
|
||||
sourceSelectorWindow = createSourceSelectorWindow()
|
||||
sourceSelectorWindow.on('closed', () => {
|
||||
sourceSelectorWindow = null
|
||||
})
|
||||
return sourceSelectorWindow
|
||||
}
|
||||
|
||||
// Quit when all windows are closed, except on macOS. There, it's common
|
||||
@@ -142,8 +54,9 @@ function createWindow() {
|
||||
// explicitly with Cmd + Q.
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
cleanupMouseTracking()
|
||||
app.quit()
|
||||
win = null
|
||||
mainWindow = null
|
||||
}
|
||||
})
|
||||
|
||||
@@ -155,46 +68,17 @@ app.on('activate', () => {
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle('get-sources', async (_, opts) => {
|
||||
const sources = await desktopCapturer.getSources(opts)
|
||||
const processedSources = sources.map(source => ({
|
||||
id: source.id,
|
||||
name: source.name,
|
||||
display_id: source.display_id,
|
||||
thumbnail: source.thumbnail ? source.thumbnail.toDataURL() : null,
|
||||
appIcon: source.appIcon ? source.appIcon.toDataURL() : null
|
||||
}))
|
||||
|
||||
return processedSources
|
||||
app.on('will-quit', () => {
|
||||
cleanupMouseTracking()
|
||||
})
|
||||
|
||||
ipcMain.handle('select-source', (_, source) => {
|
||||
selectedSource = source
|
||||
if (sourceSelectorWindow) {
|
||||
sourceSelectorWindow.close();
|
||||
sourceSelectorWindow = null;
|
||||
}
|
||||
return selectedSource
|
||||
// Register all IPC handlers when app is ready
|
||||
app.whenReady().then(() => {
|
||||
registerIpcHandlers(
|
||||
createEditorWindowWrapper,
|
||||
createSourceSelectorWindowWrapper,
|
||||
() => mainWindow,
|
||||
() => sourceSelectorWindow
|
||||
)
|
||||
createWindow()
|
||||
})
|
||||
|
||||
ipcMain.handle('get-selected-source', () => {
|
||||
return selectedSource
|
||||
})
|
||||
|
||||
ipcMain.handle('open-source-selector', () => {
|
||||
if (sourceSelectorWindow) {
|
||||
sourceSelectorWindow.focus();
|
||||
return;
|
||||
}
|
||||
createSourceSelectorWindow();
|
||||
})
|
||||
|
||||
ipcMain.handle('switch-to-editor', () => {
|
||||
if (win) {
|
||||
win.close()
|
||||
win = null
|
||||
}
|
||||
createEditorWindow()
|
||||
})
|
||||
|
||||
app.whenReady().then(createWindow)
|
||||
|
||||
@@ -15,5 +15,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')
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,112 @@
|
||||
import { BrowserWindow, screen } from 'electron'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
const APP_ROOT = path.join(__dirname, '..')
|
||||
const VITE_DEV_SERVER_URL = process.env['VITE_DEV_SERVER_URL']
|
||||
const RENDERER_DIST = path.join(APP_ROOT, 'dist')
|
||||
|
||||
export function createHudOverlayWindow(): BrowserWindow {
|
||||
const win = new BrowserWindow({
|
||||
width: 250,
|
||||
height: 80,
|
||||
minWidth: 250,
|
||||
maxWidth: 250,
|
||||
minHeight: 80,
|
||||
maxHeight: 80,
|
||||
frame: false,
|
||||
transparent: true,
|
||||
resizable: false,
|
||||
alwaysOnTop: true,
|
||||
skipTaskbar: true,
|
||||
hasShadow: false,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.mjs'),
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
backgroundThrottling: false,
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
win.webContents.on('did-finish-load', () => {
|
||||
win?.webContents.send('main-process-message', (new Date).toLocaleString())
|
||||
})
|
||||
|
||||
if (VITE_DEV_SERVER_URL) {
|
||||
win.loadURL(VITE_DEV_SERVER_URL + '?windowType=hud-overlay')
|
||||
} else {
|
||||
win.loadFile(path.join(RENDERER_DIST, 'index.html'), {
|
||||
query: { windowType: 'hud-overlay' }
|
||||
})
|
||||
}
|
||||
|
||||
return win
|
||||
}
|
||||
|
||||
export function createEditorWindow(): BrowserWindow {
|
||||
const win = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 800,
|
||||
minWidth: 800,
|
||||
minHeight: 600,
|
||||
frame: true,
|
||||
transparent: false,
|
||||
resizable: true,
|
||||
alwaysOnTop: false,
|
||||
skipTaskbar: false,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.mjs'),
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
},
|
||||
})
|
||||
|
||||
win.webContents.on('did-finish-load', () => {
|
||||
win?.webContents.send('main-process-message', (new Date).toLocaleString())
|
||||
})
|
||||
|
||||
if (VITE_DEV_SERVER_URL) {
|
||||
win.loadURL(VITE_DEV_SERVER_URL + '?windowType=editor')
|
||||
} else {
|
||||
win.loadFile(path.join(RENDERER_DIST, 'index.html'), {
|
||||
query: { windowType: 'editor' }
|
||||
})
|
||||
}
|
||||
|
||||
return win
|
||||
}
|
||||
|
||||
export function createSourceSelectorWindow(): BrowserWindow {
|
||||
const { width, height } = screen.getPrimaryDisplay().workAreaSize
|
||||
|
||||
const win = new BrowserWindow({
|
||||
width: 620,
|
||||
height: 420,
|
||||
minHeight: 350,
|
||||
maxHeight: 500,
|
||||
x: Math.round((width - 620) / 2),
|
||||
y: Math.round((height - 420) / 2),
|
||||
frame: false,
|
||||
resizable: false,
|
||||
alwaysOnTop: true,
|
||||
backgroundColor: '#ffffff',
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.mjs'),
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
},
|
||||
})
|
||||
|
||||
if (VITE_DEV_SERVER_URL) {
|
||||
win.loadURL(VITE_DEV_SERVER_URL + '?windowType=source-selector')
|
||||
} else {
|
||||
win.loadFile(path.join(RENDERER_DIST, 'index.html'), {
|
||||
query: { windowType: 'source-selector' }
|
||||
})
|
||||
}
|
||||
|
||||
return win
|
||||
}
|
||||
Generated
+1081
-9
File diff suppressed because it is too large
Load Diff
+3
-1
@@ -19,7 +19,8 @@
|
||||
"react-dom": "^18.2.0",
|
||||
"react-icons": "^5.5.0",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"uiohook-napi": "^1.5.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.64",
|
||||
@@ -30,6 +31,7 @@
|
||||
"autoprefixer": "^10.4.21",
|
||||
"electron": "^30.0.1",
|
||||
"electron-builder": "^24.13.3",
|
||||
"electron-rebuild": "^3.2.9",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.5",
|
||||
|
||||
@@ -36,6 +36,10 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
return;
|
||||
}
|
||||
|
||||
// Start mouse tracking
|
||||
console.log('Starting mouse tracking from renderer...')
|
||||
await window.electronAPI.startMouseTracking();
|
||||
|
||||
// Use the selected source
|
||||
const stream = await (navigator.mediaDevices as any).getUserMedia({
|
||||
audio: false,
|
||||
@@ -101,6 +105,10 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
) {
|
||||
mediaRecorderRef.current.stop();
|
||||
setRecording(false);
|
||||
|
||||
// Stop mouse tracking
|
||||
console.log('Stopping mouse tracking from renderer...')
|
||||
window.electronAPI.stopMouseTracking();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Vendored
+2
@@ -15,5 +15,7 @@ interface Window {
|
||||
openSourceSelector: () => Promise<void>
|
||||
selectSource: (source: any) => Promise<any>
|
||||
getSelectedSource: () => Promise<any>
|
||||
startMouseTracking: () => Promise<{ success: boolean }>
|
||||
stopMouseTracking: () => Promise<{ success: boolean }>
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,13 @@ export default defineConfig({
|
||||
main: {
|
||||
// Shortcut of `build.lib.entry`.
|
||||
entry: 'electron/main.ts',
|
||||
vite: {
|
||||
build: {
|
||||
rollupOptions: {
|
||||
external: ['uiohook-napi']
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
preload: {
|
||||
// Shortcut of `build.rollupOptions.input`.
|
||||
|
||||
Reference in New Issue
Block a user