import { app, BrowserWindow, clipboard, ipcMain, nativeImage, nativeTheme, screen, shell } from "electron"; import { spawn } from "child_process"; import { AppConfig } from "../../../src/config"; import { CommonConfig } from "../../config/common"; import { WindowConfig } from "../../config/window"; import { isDev, isMac, platformArch, platformName, platformUUID, platformVersion } from "../../lib/env"; import { preloadDefault, rendererDistPath } from "../../lib/env-main"; import { getResourceRoot } from "../../lib/resource-path"; import { Page } from "../../page"; import { ConfigMain } from "../config/main"; import { AppRuntime } from "../env"; import { Events } from "../event/main"; import { Files } from "../file/main"; import Apps from "./index"; import { AppPosition } from "./lib/position"; import { makeLoading } from "./loading"; import { SetupMain } from "./setup"; import { makeToast } from "./toast"; const getWindowByName = (name?: string) => { if (!name || "main" === name) { return AppRuntime.mainWindow; } return AppRuntime.windows[name]; }; const getCurrentWindow = (window, e) => { let originWindow = BrowserWindow.fromWebContents(e.sender); // if (originWindow !== window) originWindow = detachInstance.getWindow(); return originWindow; }; const quit = () => { // @ts-ignore app.quitForce = true; app.quit(); }; ipcMain.handle("app:quit", () => { quit(); }); const restart = () => { app.relaunch(); }; ipcMain.handle("app:restart", () => { restart(); }); const windowMin = (name?: string) => { getWindowByName(name)?.minimize(); }; const windowMax = (name?: string) => { const win = getWindowByName(name); if (!win) { return; } if (win.isFullScreen()) { win.setFullScreen(false); win.unmaximize(); win.center(); } else if (win.isMaximized()) { win.unmaximize(); win.center(); } else { win.setMinimumSize(WindowConfig.minWidth, WindowConfig.minHeight); win.maximize(); } }; const windowSetSize = ( name: string | null, width: number, height: number, option?: { includeMinimumSize: boolean; center: boolean; } ) => { width = parseInt(String(width)); height = parseInt(String(height)); // console.log('windowSetSize', name, width, height, option) const win = getWindowByName(name); if (!win) { return; } option = Object.assign( { includeMinimumSize: true, center: true, }, option ); if (option.includeMinimumSize) { win.setMinimumSize(width, height); } win.setSize(width, height); if (option.center) { win.center(); } }; ipcMain.handle("app:openExternal", (event, url: string) => { return shell.openExternal(url); }); ipcMain.handle("app:openPath", (event, url: string) => { return shell.openPath(url); }); ipcMain.handle("app:showItemInFolder", (event, url: string) => { return shell.showItemInFolder(url); }); ipcMain.handle("app:getPreload", event => { let preload = preloadDefault; if (!preload.startsWith("file://")) { preload = `file://${preload}`; } return preload; }); ipcMain.handle("window:min", (event, name: string) => { windowMin(name); }); ipcMain.handle("window:max", (event, name: string) => { windowMax(name); }); ipcMain.handle( "window:setSize", ( event, name: string | null, width: number, height: number, option?: { includeMinimumSize: boolean; center: boolean; } ) => { windowSetSize(name, width, height, option); } ); ipcMain.handle("window:close", (event, name: string) => { getWindowByName(name)?.close(); }); const windowOpen = async ( name: string, option?: { singleton?: boolean; parent?: BrowserWindow; [key: string]: any; } ) => { name = name || "main"; return Page.open(name, option); }; ipcMain.handle("window:open", (event, name: string, option: any) => { return windowOpen(name, option); }); ipcMain.handle("window:hide", (event, name: string) => { getWindowByName(name)?.hide(); if (isMac) { app.dock.hide(); } }); ipcMain.handle( "window:move", ( event, name: string | null, data: { mouseX: number; mouseY: number; width: number; height: number; } ) => { const { x, y } = screen.getCursorScreenPoint(); const originWindow = getWindowByName(name); if (!originWindow) return; originWindow.setBounds({ x: x - data.mouseX, y: y - data.mouseY, width: data.width, height: data.height }); AppPosition.set(name, x - data.mouseX, y - data.mouseY); } ); const getClipboardText = () => { return clipboard.readText("clipboard"); }; ipcMain.handle("app:getClipboardText", event => { return getClipboardText(); }); const setClipboardText = (text: string) => { clipboard.writeText(text, "clipboard"); }; ipcMain.handle("app:setClipboardText", (event, text: string) => { setClipboardText(text); }); const getClipboardImage = () => { const image = clipboard.readImage("clipboard"); return image.isEmpty() ? "" : image.toDataURL(); }; ipcMain.handle("app:getClipboardImage", event => { return getClipboardImage(); }); const setClipboardImage = (image: string) => { const img = nativeImage.createFromDataURL(image); clipboard.writeImage(img, "clipboard"); }; ipcMain.handle("app:setClipboardImage", (event, image: string) => { setClipboardImage(image); }); const isDarkMode = () => { if (!CommonConfig.darkModeEnable) { return false; } return nativeTheme.shouldUseDarkColors; }; const shouldDarkMode = async () => { if (!CommonConfig.darkModeEnable) { return false; } const darkMode = (await ConfigMain.get("darkMode")) || "auto"; if ("dark" === darkMode) { return true; } else if ("light" === darkMode) { return false; } else if ("auto" === darkMode) { return isDarkMode(); } return false; }; const defaultDarkModeBackgroundColor = async () => { if (await shouldDarkMode()) { return "#17171A"; } return "#FFFFFF"; }; nativeTheme.on("updated", () => { Events.broadcast("DarkModeChange", { isDarkMode: isDarkMode() }); AppsMain.defaultDarkModeBackgroundColor().then(color => { AppRuntime.mainWindow.setBackgroundColor(color) }); }); ipcMain.handle("app:isDarkMode", () => { return isDarkMode(); }); const getCurrentScreenDisplay = () => { const screenPoint = screen.getCursorScreenPoint(); const display = screen.getDisplayNearestPoint(screenPoint); return { bounds: display.bounds, workArea: display.workArea, }; }; const calcPositionInCurrentDisplay = ( position: "center" | "left-top" | "right-top" | "left-bottom" | "right-bottom", width: number, height: number ) => { const { bounds, workArea } = getCurrentScreenDisplay(); let x = 0; let y = 0; switch (position) { case "center": x = workArea.x + (workArea.width - width) / 2; y = workArea.y + (workArea.height - height) / 2; break; case "left-top": x = workArea.x; y = workArea.y; break; case "right-top": x = workArea.x + workArea.width - width; y = workArea.y; break; case "left-bottom": x = workArea.x; y = workArea.y + workArea.height - height; break; case "right-bottom": x = workArea.x + workArea.width - width; y = workArea.y + workArea.height - height; break; } return { x: Math.round(x), y: Math.round(y), }; }; const toast = ( msg: string, options?: { duration?: number; status?: "success" | "error" | "info"; } ) => { return makeToast(msg, options); }; ipcMain.handle("app:toast", (event, msg: string, option?: any) => { return toast(msg, option); }); const loading = ( msg: string, options?: { timeout?: number; percentAuto?: boolean; percentTotalSeconds?: number; } ): { close: () => void; percent: (value: number) => void; } => { return makeLoading(msg, options); }; ipcMain.handle("app:loading", (event, msg: string, option?: any) => { return loading(msg, option); }); ipcMain.handle("app:setupList", async () => { return SetupMain.list(); }); ipcMain.handle("app:setupOpen", async (event, name: string) => { return SetupMain.open(name); }); const setupIsOk = async () => { return SetupMain.isOk(); }; ipcMain.handle("app:setupIsOk", async () => { return setupIsOk(); }); const getBuildInfo = async () => { if (isDev) { return { buildId: "Development", }; } const json = await Files.read(rendererDistPath("build.json"), { isDataPath: false, }); return JSON.parse(json); }; ipcMain.handle("app:getBuildInfo", async () => { return getBuildInfo(); }); const collect = async (options?: {}) => { return { userAgent: Apps.getUserAgent(), name: AppConfig.name, version: AppConfig.version, uuid: platformUUID(), platformVersion: platformVersion(), platformName: platformName(), platformArch: platformArch(), }; }; ipcMain.handle("app:collect", async (event, options?: {}) => { return collect(options); }); const setAutoLaunch = async (enable: boolean, options?: {}) => { return app.setLoginItemSettings({ openAtLogin: enable, }); }; ipcMain.handle("app:setAutoLaunch", async (event, enable: boolean, options?: {}) => { return setAutoLaunch(enable, options); }); const getAutoLaunch = async (options?: {}) => { return app.getLoginItemSettings().openAtLogin; }; ipcMain.handle("app:getAutoLaunch", async (event, options?: {}) => { return getAutoLaunch(options); }); ipcMain.handle("app:getResourcesPath", () => { return getResourceRoot(); }); // FFprobe video info handler ipcMain.handle("app:ffprobeVideoInfo", async (event, videoPath: string) => { const { getFFprobePath } = await import('../shell'); try { const ffprobePath = await getFFprobePath(); // 获取视频流信息和格式信息(包含时长) const args = [ "-v", "error", "-show_entries", "stream=width,height,r_frame_rate:format=duration", "-of", "json", videoPath ]; return new Promise((resolve, reject) => { const child = spawn(ffprobePath, args, { shell: false }); let stdout = ''; let stderr = ''; child.stdout.on('data', (data) => { stdout += data.toString(); }); child.stderr.on('data', (data) => { stderr += data.toString(); }); child.on('close', (code) => { if (code !== 0) { reject(new Error(`ffprobe failed with code ${code}: ${stderr}`)); return; } try { const data = JSON.parse(stdout); // 获取时长 let duration = undefined; if (data.format && data.format.duration) { duration = parseFloat(data.format.duration); } // 如果有视频流,获取视频信息 if (data.streams && data.streams.length > 0) { const stream = data.streams[0]; const width = parseInt(stream.width, 10) || undefined; const height = parseInt(stream.height, 10) || undefined; // 解析帧率 let fps = 30; if (stream.r_frame_rate) { const fpsParts = stream.r_frame_rate.split("/"); fps = fpsParts.length === 2 ? parseFloat(fpsParts[0]) / parseFloat(fpsParts[1]) : parseFloat(stream.r_frame_rate); } resolve({ width, height, fps, duration }); } else { // 只有音频流的情况,只返回时长 resolve({ duration }); } } catch (e) { reject(e); } }); }); } catch (error) { throw error; } }); export default { quit, }; export const AppsMain = { shouldDarkMode, defaultDarkModeBackgroundColor, getWindowByName, getClipboardText, setClipboardText, getClipboardImage, setClipboardImage, getCurrentScreenDisplay, calcPositionInCurrentDisplay, toast, loading, setupIsOk, windowOpen, };