const { app, BrowserWindow, ipcMain, dialog, shell } = require('electron'); const path = require('path'); const { spawn, spawnSync } = require('child_process'); const fs = require('fs'); let mainWindow; function createWindow() { mainWindow = new BrowserWindow({ width: 1200, height: 820, minWidth: 1000, minHeight: 700, icon: path.join(__dirname, 'docs', 'dekupai-logo.png'), // Custom app logo frame: false, // Sleek frameless design transparent: false, // Set to false to avoid rendering bugs on some Windows setups backgroundColor: '#0a0a0f', // Dark baseline background webPreferences: { preload: path.join(__dirname, 'preload.js'), contextIsolation: true, nodeIntegration: false, webSecurity: false // Disabled to allow canvas pixel reading on local file:// assets for Auto-Trim } }); mainWindow.loadFile(path.join(__dirname, 'index.html')); mainWindow.setMenu(null); // Disable default menu and browser shortcuts (F5, F11, Ctrl+F5) to act as a proper native application. // Open DevTools if running in dev (optional) // mainWindow.webContents.openDevTools(); } app.whenReady().then(() => { createWindow(); app.on('activate', function () { if (BrowserWindow.getAllWindows().length === 0) createWindow(); }); }); app.on('window-all-closed', function () { if (process.platform !== 'darwin') app.quit(); }); let cachedSystemPython = null; function getResourceRoot() { if (app.isPackaged) { const unpacked = path.join(process.resourcesPath, 'app.asar.unpacked'); if (fs.existsSync(unpacked)) return unpacked; return process.resourcesPath; } return __dirname; } function getDataRoot() { return app.isPackaged ? app.getPath('userData') : __dirname; } function getBackendPath(...segments) { return path.join(getResourceRoot(), 'backend', ...segments); } function readRegistryValue(key, valueName) { try { const result = spawnSync('reg', ['query', key, '/v', valueName], { encoding: 'utf8', windowsHide: true, timeout: 5000, }); if (result.status !== 0) return ''; const match = result.stdout.match(new RegExp(`${valueName}\\s+REG_(?:EXPAND_)?SZ\\s+(.*)`, 'i')); return match ? match[1].trim() : ''; } catch (_) { return ''; } } function getSpawnEnv() { const env = { ...process.env }; if (process.platform !== 'win32') return env; const pathEntries = new Set(); const addPaths = (value) => { if (!value) return; value.split(';').forEach((entry) => { const trimmed = entry.trim(); if (trimmed) pathEntries.add(trimmed); }); }; addPaths(process.env.PATH); addPaths(readRegistryValue('HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment', 'Path')); addPaths(readRegistryValue('HKCU\\Environment', 'Path')); const localAppData = process.env.LOCALAPPDATA || (process.env.USERPROFILE ? path.join(process.env.USERPROFILE, 'AppData', 'Local') : null); if (localAppData) { pathEntries.add(path.join(localAppData, 'Programs', 'Python', 'Launcher')); for (const version of ['312', '311', '310']) { pathEntries.add(path.join(localAppData, 'Programs', 'Python', `Python${version}`)); pathEntries.add(path.join(localAppData, 'Programs', 'Python', `Python${version}`, 'Scripts')); } } if (process.env.ProgramFiles) { for (const version of ['312', '311', '310']) { pathEntries.add(path.join(process.env.ProgramFiles, `Python${version}`)); pathEntries.add(path.join(process.env.ProgramFiles, `Python${version}`, 'Scripts')); } } env.PATH = [...pathEntries].join(';'); return env; } function getWindowsPythonCandidates() { const candidates = []; const seen = new Set(); const addCandidate = (command, args = []) => { const key = `${command}::${args.join(' ')}`; if (seen.has(key)) return; seen.add(key); candidates.push({ command, args }); }; const localAppData = process.env.LOCALAPPDATA || (process.env.USERPROFILE ? path.join(process.env.USERPROFILE, 'AppData', 'Local') : null); if (localAppData) { const pyLauncher = path.join(localAppData, 'Programs', 'Python', 'Launcher', 'py.exe'); if (fs.existsSync(pyLauncher)) { for (const flag of ['-3.12', '-3.11', '-3.10', '-3']) { addCandidate(pyLauncher, [flag]); } } for (const version of ['312', '311', '310']) { const pythonExe = path.join(localAppData, 'Programs', 'Python', `Python${version}`, 'python.exe'); if (fs.existsSync(pythonExe)) addCandidate(pythonExe, []); } } if (process.env.ProgramFiles) { for (const version of ['312', '311', '310']) { const pythonExe = path.join(process.env.ProgramFiles, `Python${version}`, 'python.exe'); if (fs.existsSync(pythonExe)) addCandidate(pythonExe, []); } } addCandidate('py', ['-3.12']); addCandidate('py', ['-3.11']); addCandidate('py', ['-3.10']); addCandidate('py', ['-3']); addCandidate('python', []); return candidates; } function verifyPython(command, args, env) { try { const result = spawnSync(command, [...args, '--version'], { encoding: 'utf8', windowsHide: true, timeout: 6000, env, }); return result.status === 0 && !result.error; } catch (_) { return false; } } function resolveSystemPython() { if (cachedSystemPython) return cachedSystemPython; const env = getSpawnEnv(); const candidates = process.platform === 'win32' ? getWindowsPythonCandidates() : [ { command: 'python3', args: [] }, { command: 'python', args: [] }, ]; for (const candidate of candidates) { if (verifyPython(candidate.command, candidate.args, env)) { cachedSystemPython = candidate; console.log(`[Python Resolver] Selected: ${candidate.command} ${candidate.args.join(' ')}`); return cachedSystemPython; } } console.warn('[Python Resolver] No working Python found; falling back to "python".'); cachedSystemPython = { command: 'python', args: [], unverified: true }; return cachedSystemPython; } function spawnPython(command, args, options = {}) { return spawn(command, args, { shell: false, windowsHide: true, env: getSpawnEnv(), ...options, }); } function attachProcessListeners(child, { onLine, onClose, onError }) { child.stdout.on('data', (data) => { const output = data.toString(); for (const line of output.split('\n')) { if (line.trim()) onLine(line.trim()); } }); child.stderr.on('data', (data) => { const errStr = data.toString(); for (const line of errStr.split('\n')) { if (line.trim()) onLine(`[ERROR] ${line.trim()}`); } }); child.on('error', (err) => onError(err)); child.on('close', (code) => onClose(code)); } // Helper: Get Python Path function getPythonPaths() { const isWindows = process.platform === 'win32'; const venvPython = isWindows ? path.join(getDataRoot(), '.venv', 'Scripts', 'python.exe') : path.join(getDataRoot(), '.venv', 'bin', 'python'); const systemPython = resolveSystemPython(); const hasVenv = fs.existsSync(venvPython); return { venvPython, systemPython, hasVenv }; } // IPC: Check Python Setup Status ipcMain.handle('check-python-setup', async () => { const { hasVenv } = getPythonPaths(); return { hasVenv }; }); // IPC: Run Setup ipcMain.handle('run-python-setup', async (event) => { const { systemPython } = getPythonPaths(); const setupScript = getBackendPath('setup.py'); const dataDir = getDataRoot(); return new Promise((resolve) => { const { command, args: pythonArgs } = systemPython; const spawnArgs = [...pythonArgs, setupScript, '--data-dir', dataDir]; console.log(`[Main Process] Spawning setup: ${command} ${spawnArgs.join(' ')}`); let stderrData = ''; let settled = false; const settle = (payload) => { if (settled) return; settled = true; resolve(payload); }; const setupProcess = spawnPython(command, spawnArgs, { cwd: dataDir }); attachProcessListeners(setupProcess, { onLine: (line) => { console.log(`[Setup Output] ${line}`); event.sender.send('setup-progress', line); if (line.startsWith('[ERROR]')) stderrData += `${line}\n`; }, onError: (err) => { console.error(`[Setup Error] Failed to start process: ${err.message}`); settle({ code: 1, error: `Python baslatilamadi: ${err.message}. Python 3.10-3.12 kurulu oldugundan emin olun.`, }); }, onClose: (code) => { console.log(`[Main Process] Setup completed with exit code ${code}`); settle({ code, error: stderrData }); }, }); }); }); // IPC: Run Background Removal Process ipcMain.handle('remove-background', async (event, { inputPath, outputPath }) => { const { venvPython, hasVenv } = getPythonPaths(); if (!hasVenv) { return { success: false, error: 'Python virtual environment not set up. Please run setup first.' }; } const processScript = getBackendPath('process.py'); return new Promise((resolve) => { console.log(`[Main Process] Spawning background removal: ${venvPython} ${processScript}`); const args = ['--input', inputPath, '--output', outputPath]; let stderrData = ''; let settled = false; const settle = (payload) => { if (settled) return; settled = true; resolve(payload); }; const bgProcess = spawnPython(venvPython, [processScript, ...args], { cwd: getDataRoot() }); attachProcessListeners(bgProcess, { onLine: (line) => { console.log(`[Python Output] ${line}`); event.sender.send('process-progress', line); if (line.startsWith('[ERROR]')) stderrData += `${line}\n`; }, onError: (err) => { settle({ success: false, error: `Python baslatilamadi: ${err.message}` }); }, onClose: (code) => { console.log(`[Main Process] Process completed with exit code ${code}`); if (code === 0) { settle({ success: true, outputPath }); } else { settle({ success: false, error: stderrData || `Process failed with exit code ${code}` }); } }, }); }); }); // IPC: Select Image File Dialog ipcMain.handle('select-image', async () => { const result = await dialog.showOpenDialog(mainWindow, { properties: ['openFile'], filters: [ { name: 'Images', extensions: ['jpg', 'jpeg', 'png', 'webp', 'bmp'] } ] }); if (!result.canceled && result.filePaths.length > 0) { return result.filePaths[0]; } return null; }); // IPC: Get Temp Output File Path ipcMain.handle('get-temp-path', async (event, fileName) => { const tempDir = app.getPath('temp'); // Ensure unique name to avoid cache issues const uniqueName = `birefnet_${Date.now()}_${fileName}`; return path.join(tempDir, uniqueName); }); // IPC: Export Image Dialog ipcMain.handle('export-image', async (event, { base64Data, defaultName }) => { const result = await dialog.showSaveDialog(mainWindow, { defaultPath: defaultName || 'processed_image.png', filters: [ { name: 'PNG Image', extensions: ['png'] }, { name: 'JPEG Image', extensions: ['jpg', 'jpeg'] }, { name: 'WebP Image', extensions: ['webp'] } ] }); if (!result.canceled && result.filePath) { try { // Strip base64 headers if present const base64Content = base64Data.replace(/^data:image\/\w+;base64,/, ""); fs.writeFileSync(result.filePath, base64Content, 'base64'); return { success: true, filePath: result.filePath }; } catch (err) { return { success: false, error: err.message }; } } return { success: false, canceled: true }; }); // IPC: Show in File Explorer ipcMain.handle('show-in-explorer', async (event, filePath) => { if (fs.existsSync(filePath)) { shell.showItemInFolder(filePath); return true; } return false; }); // IPC: Frameless Window Controls ipcMain.on('window-minimize', () => { if (mainWindow) mainWindow.minimize(); }); ipcMain.on('window-maximize', () => { if (mainWindow) { if (mainWindow.isMaximized()) { mainWindow.unmaximize(); } else { mainWindow.maximize(); } } }); ipcMain.on('window-close', () => { if (mainWindow) mainWindow.close(); });