Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76d6fc2749 | ||
|
|
2f013b4751 | ||
|
|
66dce4415a | ||
|
|
6460349fc7 | ||
|
|
6926c27f20 | ||
|
|
cbf82f9317 |
+13
-9
@@ -30,15 +30,15 @@ module.exports = {
|
||||
],
|
||||
// use `security find-identity -v -p codesigning` to find your identity
|
||||
// for macos signing
|
||||
// Use ad-hoc signing with entitlements for local development
|
||||
osxSign: {
|
||||
identity: '-', // ad-hoc signing (no Apple Developer account needed)
|
||||
optionsForFile: (filePath) => {
|
||||
return {
|
||||
entitlements: 'entitlements.plist',
|
||||
};
|
||||
},
|
||||
},
|
||||
// Disabled for local builds - ad-hoc signing causes issues
|
||||
// osxSign: {
|
||||
// identity: '-', // ad-hoc signing (no Apple Developer account needed)
|
||||
// optionsForFile: (filePath) => {
|
||||
// return {
|
||||
// entitlements: 'entitlements.plist',
|
||||
// };
|
||||
// },
|
||||
// },
|
||||
// notarize is off - requires Apple Developer account
|
||||
// osxNotarize: {
|
||||
// appleId: 'your apple id',
|
||||
@@ -61,6 +61,10 @@ module.exports = {
|
||||
{
|
||||
name: '@electron-forge/maker-dmg',
|
||||
platforms: ['darwin'],
|
||||
config: {
|
||||
name: 'CheatingDaddy',
|
||||
format: 'ULFO',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '@reforged/maker-appimage',
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "cheating-daddy",
|
||||
"productName": "cheating-daddy",
|
||||
"version": "0.5.3",
|
||||
"version": "0.5.9",
|
||||
"description": "cheating daddy",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
@@ -19,8 +19,8 @@
|
||||
"cheating daddy ai assistant for interviews"
|
||||
],
|
||||
"author": {
|
||||
"name": "sohzm",
|
||||
"email": "sohambharambe9@gmail.com"
|
||||
"name": "ShiftyX1",
|
||||
"email": "lead@pyserve.org"
|
||||
},
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
|
||||
+15
-2
@@ -281,6 +281,7 @@ async function sendImageMessage(base64Image, prompt) {
|
||||
let audioChunks = [];
|
||||
let lastAudioTime = 0;
|
||||
const SILENCE_THRESHOLD_MS = 1500; // 1.5 seconds of silence
|
||||
let silenceCheckTimer = null;
|
||||
|
||||
async function processAudioChunk(base64Audio, mimeType) {
|
||||
if (!openaiClient) {
|
||||
@@ -294,8 +295,20 @@ async function processAudioChunk(base64Audio, mimeType) {
|
||||
audioChunks.push(buffer);
|
||||
lastAudioTime = now;
|
||||
|
||||
// Check for silence (no new audio for SILENCE_THRESHOLD_MS)
|
||||
// This is a simple approach - in production you'd want proper VAD
|
||||
// Clear existing timer
|
||||
if (silenceCheckTimer) {
|
||||
clearTimeout(silenceCheckTimer);
|
||||
}
|
||||
|
||||
// Set timer to check for silence
|
||||
silenceCheckTimer = setTimeout(async () => {
|
||||
const silenceDuration = Date.now() - lastAudioTime;
|
||||
if (silenceDuration >= SILENCE_THRESHOLD_MS && audioChunks.length > 0) {
|
||||
console.log('Silence detected, flushing audio for transcription...');
|
||||
await flushAudioAndTranscribe();
|
||||
}
|
||||
}, SILENCE_THRESHOLD_MS);
|
||||
|
||||
return { success: true, buffering: true };
|
||||
}
|
||||
|
||||
|
||||
+26
-75
@@ -295,9 +295,6 @@ async function startCapture(screenshotIntervalSeconds = 5, imageQuality = 'mediu
|
||||
console.log('Linux capture started - system audio:', mediaStream.getAudioTracks().length > 0, 'microphone mode:', audioMode);
|
||||
} else {
|
||||
// Windows - use display media with loopback for system audio
|
||||
logToMain('info', '=== Starting Windows audio capture ===');
|
||||
cheatingDaddy.setStatus('Requesting screen & audio...');
|
||||
|
||||
mediaStream = await navigator.mediaDevices.getDisplayMedia({
|
||||
video: {
|
||||
frameRate: 1,
|
||||
@@ -313,6 +310,7 @@ async function startCapture(screenshotIntervalSeconds = 5, imageQuality = 'mediu
|
||||
},
|
||||
});
|
||||
|
||||
console.log('Windows capture started with loopback audio');
|
||||
const audioTracks = mediaStream.getAudioTracks();
|
||||
const videoTracks = mediaStream.getVideoTracks();
|
||||
|
||||
@@ -328,14 +326,8 @@ async function startCapture(screenshotIntervalSeconds = 5, imageQuality = 'mediu
|
||||
})),
|
||||
});
|
||||
|
||||
if (audioTracks.length === 0) {
|
||||
logToMain('warn', 'WARNING: No audio tracks! User must check "Share audio" in screen picker dialog');
|
||||
cheatingDaddy.setStatus('Warning: No audio - enable "Share audio" checkbox');
|
||||
} else {
|
||||
logToMain('info', 'Audio track acquired, setting up processing...');
|
||||
// Setup audio processing for Windows loopback audio only
|
||||
setupWindowsLoopbackProcessing();
|
||||
}
|
||||
// Setup audio processing for Windows loopback audio only
|
||||
setupWindowsLoopbackProcessing();
|
||||
|
||||
if (audioMode === 'mic_only' || audioMode === 'both') {
|
||||
let micStream = null;
|
||||
@@ -451,73 +443,32 @@ function setupLinuxSystemAudioProcessing() {
|
||||
|
||||
function setupWindowsLoopbackProcessing() {
|
||||
// Setup audio processing for Windows loopback audio only
|
||||
logToMain('info', 'Setting up Windows loopback audio processing...');
|
||||
|
||||
try {
|
||||
audioContext = new AudioContext({ sampleRate: SAMPLE_RATE });
|
||||
|
||||
logToMain('info', 'AudioContext created:', {
|
||||
state: audioContext.state,
|
||||
sampleRate: audioContext.sampleRate,
|
||||
});
|
||||
|
||||
// Resume AudioContext if suspended (Chrome policy)
|
||||
if (audioContext.state === 'suspended') {
|
||||
logToMain('warn', 'AudioContext suspended, attempting resume...');
|
||||
audioContext.resume().then(() => {
|
||||
logToMain('info', 'AudioContext resumed successfully');
|
||||
}).catch(err => {
|
||||
logToMain('error', 'Failed to resume AudioContext:', err.message);
|
||||
audioContext = new AudioContext({ sampleRate: SAMPLE_RATE });
|
||||
const source = audioContext.createMediaStreamSource(mediaStream);
|
||||
audioProcessor = audioContext.createScriptProcessor(BUFFER_SIZE, 1, 1);
|
||||
|
||||
let audioBuffer = [];
|
||||
const samplesPerChunk = SAMPLE_RATE * AUDIO_CHUNK_DURATION;
|
||||
|
||||
audioProcessor.onaudioprocess = async e => {
|
||||
const inputData = e.inputBuffer.getChannelData(0);
|
||||
audioBuffer.push(...inputData);
|
||||
|
||||
// Process audio in chunks
|
||||
while (audioBuffer.length >= samplesPerChunk) {
|
||||
const chunk = audioBuffer.splice(0, samplesPerChunk);
|
||||
const pcmData16 = convertFloat32ToInt16(chunk);
|
||||
const base64Data = arrayBufferToBase64(pcmData16.buffer);
|
||||
|
||||
await ipcRenderer.invoke('send-audio-content', {
|
||||
data: base64Data,
|
||||
mimeType: 'audio/pcm;rate=24000',
|
||||
});
|
||||
}
|
||||
|
||||
const source = audioContext.createMediaStreamSource(mediaStream);
|
||||
audioProcessor = audioContext.createScriptProcessor(BUFFER_SIZE, 1, 1);
|
||||
};
|
||||
|
||||
let audioBuffer = [];
|
||||
const samplesPerChunk = SAMPLE_RATE * AUDIO_CHUNK_DURATION;
|
||||
let chunkCount = 0;
|
||||
let totalSamples = 0;
|
||||
|
||||
audioProcessor.onaudioprocess = async e => {
|
||||
const inputData = e.inputBuffer.getChannelData(0);
|
||||
audioBuffer.push(...inputData);
|
||||
totalSamples += inputData.length;
|
||||
|
||||
// Process audio in chunks
|
||||
while (audioBuffer.length >= samplesPerChunk) {
|
||||
const chunk = audioBuffer.splice(0, samplesPerChunk);
|
||||
const pcmData16 = convertFloat32ToInt16(chunk);
|
||||
const base64Data = arrayBufferToBase64(pcmData16.buffer);
|
||||
|
||||
await ipcRenderer.invoke('send-audio-content', {
|
||||
data: base64Data,
|
||||
mimeType: 'audio/pcm;rate=24000',
|
||||
});
|
||||
|
||||
chunkCount++;
|
||||
|
||||
// Log progress every 100 chunks (~10 seconds)
|
||||
if (chunkCount === 1) {
|
||||
logToMain('info', 'First audio chunk sent to AI');
|
||||
cheatingDaddy.setStatus('Listening...');
|
||||
} else if (chunkCount % 100 === 0) {
|
||||
// Calculate max amplitude to check if we're getting real audio
|
||||
const maxAmp = Math.max(...chunk.map(Math.abs));
|
||||
logToMain('info', `Audio progress: ${chunkCount} chunks, maxAmplitude: ${maxAmp.toFixed(4)}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
source.connect(audioProcessor);
|
||||
audioProcessor.connect(audioContext.destination);
|
||||
|
||||
logToMain('info', 'Windows audio processing pipeline connected');
|
||||
|
||||
} catch (err) {
|
||||
logToMain('error', 'Error setting up Windows audio:', err.message, err.stack);
|
||||
cheatingDaddy.setStatus('Audio error: ' + err.message);
|
||||
}
|
||||
source.connect(audioProcessor);
|
||||
audioProcessor.connect(audioContext.destination);
|
||||
}
|
||||
|
||||
async function captureScreenshot(imageQuality = 'medium', isManual = false) {
|
||||
|
||||
+8
-84
@@ -33,90 +33,14 @@ function createWindow(sendToRenderer, geminiSessionRef) {
|
||||
});
|
||||
|
||||
const { session, desktopCapturer } = require('electron');
|
||||
|
||||
// Setup display media request handler for screen capture
|
||||
if (process.platform === 'darwin') {
|
||||
// On macOS, use SystemAudioDump for audio (not browser loopback)
|
||||
// So we just need to capture the screen
|
||||
session.defaultSession.setDisplayMediaRequestHandler(
|
||||
async (request, callback) => {
|
||||
try {
|
||||
const sources = await desktopCapturer.getSources({
|
||||
types: ['screen'],
|
||||
thumbnailSize: { width: 0, height: 0 } // Skip thumbnail generation for speed
|
||||
});
|
||||
|
||||
if (sources.length === 0) {
|
||||
console.error('No screen sources available');
|
||||
callback(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// On macOS, directly use the first screen (system already granted permission)
|
||||
// Audio is handled separately by SystemAudioDump
|
||||
console.log('Screen capture source:', sources[0].name);
|
||||
callback({ video: sources[0], audio: 'loopback' });
|
||||
} catch (error) {
|
||||
console.error('Error getting screen sources:', error);
|
||||
callback(null);
|
||||
}
|
||||
},
|
||||
{ useSystemPicker: false } // Disable system picker, use our source directly
|
||||
);
|
||||
} else if (process.platform === 'win32') {
|
||||
// On Windows, use desktopCapturer with loopback audio
|
||||
// This captures system audio via WASAPI without needing user interaction
|
||||
session.defaultSession.setDisplayMediaRequestHandler(
|
||||
async (request, callback) => {
|
||||
try {
|
||||
console.log('Windows: Getting screen sources with loopback audio...');
|
||||
const sources = await desktopCapturer.getSources({
|
||||
types: ['screen'],
|
||||
thumbnailSize: { width: 0, height: 0 }
|
||||
});
|
||||
|
||||
if (sources.length === 0) {
|
||||
console.error('No screen sources available on Windows');
|
||||
callback(null);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Windows: Using screen source:', sources[0].name, 'with loopback audio');
|
||||
// 'loopback' enables system audio capture via WASAPI on Windows
|
||||
callback({ video: sources[0], audio: 'loopback' });
|
||||
} catch (error) {
|
||||
console.error('Error getting screen sources on Windows:', error);
|
||||
callback(null);
|
||||
}
|
||||
},
|
||||
{ useSystemPicker: false }
|
||||
);
|
||||
} else {
|
||||
// On Linux, try to get system audio via loopback
|
||||
session.defaultSession.setDisplayMediaRequestHandler(
|
||||
async (request, callback) => {
|
||||
try {
|
||||
const sources = await desktopCapturer.getSources({
|
||||
types: ['screen'],
|
||||
thumbnailSize: { width: 0, height: 0 }
|
||||
});
|
||||
|
||||
if (sources.length === 0) {
|
||||
console.error('No screen sources available');
|
||||
callback(null);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Linux: Using screen source with loopback audio');
|
||||
callback({ video: sources[0], audio: 'loopback' });
|
||||
} catch (error) {
|
||||
console.error('Error getting screen sources:', error);
|
||||
callback(null);
|
||||
}
|
||||
},
|
||||
{ useSystemPicker: false }
|
||||
);
|
||||
}
|
||||
session.defaultSession.setDisplayMediaRequestHandler(
|
||||
(request, callback) => {
|
||||
desktopCapturer.getSources({ types: ['screen'] }).then(sources => {
|
||||
callback({ video: sources[0], audio: 'loopback' });
|
||||
});
|
||||
},
|
||||
{ useSystemPicker: true }
|
||||
);
|
||||
|
||||
mainWindow.setResizable(false);
|
||||
mainWindow.setContentProtection(true);
|
||||
|
||||
Reference in New Issue
Block a user