Files
dekupai/main.js
T

405 lines
12 KiB
JavaScript

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();
});