import { app, ipcMain, BrowserWindow, powerSaveBlocker, screen, shell, dialog, net, session, } from "electron"; import { File, Picture, Id3v2Settings } from "node-taglib-sharp"; import { parseFile } from "music-metadata"; import { getFonts } from "font-list"; import { MainTray } from "./tray"; import { Thumbar } from "./thumbar"; import { StoreType } from "./store"; import { isDev, getFileID, getFileMD5 } from "./utils"; import { isShortcutRegistered, registerShortcut, unregisterShortcuts } from "./shortcut"; import { join, basename, resolve } from "path"; import { download } from "electron-dl"; import { checkUpdate, startDownloadUpdate } from "./update"; import fs from "fs/promises"; import log from "../main/logger"; import Store from "electron-store"; import fg from "fast-glob"; // 注册 ipcMain const initIpcMain = ( win: BrowserWindow | null, lyricWin: BrowserWindow | null, loadingWin: BrowserWindow | null, tray: MainTray | null, thumbar: Thumbar | null, store: Store, ) => { initWinIpcMain(win, loadingWin, lyricWin, store); initLyricIpcMain(lyricWin, win, store); initTrayIpcMain(tray, win, lyricWin); initThumbarIpcMain(thumbar); initStoreIpcMain(store); initOtherIpcMain(win); }; // win const initWinIpcMain = ( win: BrowserWindow | null, loadingWin: BrowserWindow | null, lyricWin: BrowserWindow | null, store: Store, ) => { let preventId: number | null = null; // 当前窗口状态 ipcMain.on("win-state", (ev) => { ev.returnValue = win?.isMaximized(); }); // 加载完成 ipcMain.on("win-loaded", () => { if (loadingWin && !loadingWin.isDestroyed()) loadingWin.close(); win?.show(); win?.focus(); }); // 最小化 ipcMain.on("win-min", (ev) => { ev.preventDefault(); win?.minimize(); }); // 最大化 ipcMain.on("win-max", () => { win?.maximize(); }); // 还原 ipcMain.on("win-restore", () => { win?.restore(); }); // 关闭 ipcMain.on("win-close", (ev) => { ev.preventDefault(); win?.close(); app.quit(); }); // 隐藏 ipcMain.on("win-hide", () => { win?.hide(); }); // 显示 ipcMain.on("win-show", () => { win?.show(); }); // 重启 ipcMain.on("win-reload", () => { app.quit(); app.relaunch(); }); // 显示进度 ipcMain.on("set-bar", (_, val: number | "none" | "indeterminate" | "error" | "paused") => { switch (val) { case "none": win?.setProgressBar(-1); break; case "indeterminate": win?.setProgressBar(2, { mode: "indeterminate" }); break; case "error": win?.setProgressBar(1, { mode: "error" }); break; case "paused": win?.setProgressBar(1, { mode: "paused" }); break; default: if (typeof val === "number") { win?.setProgressBar(val / 100); } else { win?.setProgressBar(-1); } break; } }); // 开启控制台 ipcMain.on("open-dev-tools", () => { win?.webContents.openDevTools({ title: "SPlayer DevTools", mode: isDev ? "right" : "detach", }); }); // 获取系统全部字体 ipcMain.handle("get-all-fonts", async () => { try { const fonts = await getFonts(); return fonts; } catch (error) { log.error(`❌ Failed to get all system fonts: ${error}`); return []; } }); // 切换桌面歌词 ipcMain.on("change-desktop-lyric", (_, val: boolean) => { val ? lyricWin?.show() : lyricWin?.hide(); if (val) lyricWin?.setAlwaysOnTop(true, "screen-saver"); }); // 是否阻止系统息屏 ipcMain.on("prevent-sleep", (_, val: boolean) => { if (val) { preventId = powerSaveBlocker.start("prevent-display-sleep"); log.info("⏾ System sleep prevention started"); } else { if (preventId !== null) { powerSaveBlocker.stop(preventId); log.info("✅ System sleep prevention stopped"); } } }); // 默认文件夹 ipcMain.handle( "get-default-dir", (_, type: "documents" | "downloads" | "pictures" | "music" | "videos"): string => { return app.getPath(type); }, ); // 遍历音乐文件 ipcMain.handle("get-music-files", async (_, dirPath: string) => { try { // 查找指定目录下的所有音乐文件 const musicFiles = await fg("**/*.{mp3,wav,flac}", { cwd: dirPath }); // 解析元信息 const metadataPromises = musicFiles.map(async (file) => { const filePath = join(dirPath, file); // 处理元信息 const { common, format } = await parseFile(filePath); // 获取文件大小 const { size } = await fs.stat(filePath); // 判断音质等级 let quality: string; if ((format.sampleRate || 0) >= 96000 || (format.bitsPerSample || 0) > 16) { quality = "Hi-Res"; } else if ((format.sampleRate || 0) >= 44100) { quality = "HQ"; } else { quality = "SQ"; } return { id: getFileID(filePath), name: common.title || basename(filePath), artists: common.artists?.[0] || common.artist, album: common.album || "", alia: common.comment?.[0], duration: (format?.duration ?? 0) * 1000, size: (size / (1024 * 1024)).toFixed(2), path: filePath, quality, }; }); const metadataArray = await Promise.all(metadataPromises); return metadataArray; } catch (error) { log.error("❌ Error fetching music metadata:", error); throw error; } }); // 获取音乐元信息 ipcMain.handle("get-music-metadata", async (_, path: string) => { try { const { common, format } = await parseFile(path); return { // 文件名称 fileName: basename(path), // 文件大小 fileSize: (await fs.stat(path)).size / (1024 * 1024), // 元信息 common, // 音质信息 format, // md5 md5: await getFileMD5(path), }; } catch (error) { log.error("❌ Error fetching music metadata:", error); throw error; } }); // 获取音乐歌词 ipcMain.handle("get-music-lyric", async (_, path: string): Promise => { try { const { common, native } = await parseFile(path); const lyric = common?.lyrics; if (lyric && lyric.length > 0) return String(lyric[0]); else { // 尝试读取 UNSYNCEDLYRICS const nativeTags = native["ID3v2.3"] || native["ID3v2.4"]; const usltTag = nativeTags?.find((tag) => tag.id === "USLT"); if (usltTag) return String(usltTag.value.text); // 如果歌词数据不存在,尝试读取同名的 lrc 文件 else { const lrcFilePath = path.replace(/\.[^.]+$/, ".lrc"); try { await fs.access(lrcFilePath); const lrcData = await fs.readFile(lrcFilePath, "utf-8"); return lrcData || ""; } catch { return ""; } } } } catch (error) { log.error("❌ Error fetching music lyric:", error); throw error; } }); // 获取音乐封面 ipcMain.handle( "get-music-cover", async (_, path: string): Promise<{ data: Buffer; format: string } | null> => { try { const { common } = await parseFile(path); // 获取封面数据 const picture = common.picture?.[0]; if (picture) { return { data: Buffer.from(picture.data), format: picture.format }; } else { const coverFilePath = path.replace(/\.[^.]+$/, ".jpg"); try { await fs.access(coverFilePath); const coverData = await fs.readFile(coverFilePath); return { data: coverData, format: "image/jpeg" }; } catch { return null; } } } catch (error) { console.error("❌ Error fetching music cover:", error); throw error; } }, ); // 删除文件 ipcMain.handle("delete-file", async (_, path: string) => { try { // 规范化路径 const resolvedPath = resolve(path); // 检查文件是否存在 try { await fs.access(resolvedPath); } catch { throw new Error("❌ File not found"); } // 删除文件 await fs.unlink(resolvedPath); return true; } catch (error) { log.error("❌ File delete error", error); return false; } }); // 打开文件夹 ipcMain.on("open-folder", async (_, path: string) => { try { // 规范化路径 const resolvedPath = resolve(path); // 检查文件夹是否存在 try { await fs.access(resolvedPath); } catch { throw new Error("❌ Folder not found"); } // 打开文件夹 shell.showItemInFolder(resolvedPath); } catch (error) { log.error("❌ Folder open error", error); throw error; } }); // 图片选择窗口 ipcMain.handle("choose-image", async () => { try { const { filePaths } = await dialog.showOpenDialog({ properties: ["openFile"], filters: [{ name: "Images", extensions: ["jpg", "jpeg", "png"] }], }); if (!filePaths || filePaths.length === 0) return null; return filePaths[0]; } catch (error) { log.error("❌ Image choose error", error); return null; } }); // 路径选择窗口 ipcMain.handle("choose-path", async () => { try { const { filePaths } = await dialog.showOpenDialog({ title: "选择文件夹", defaultPath: app.getPath("downloads"), properties: ["openDirectory", "createDirectory"], buttonLabel: "选择文件夹", }); if (!filePaths || filePaths.length === 0) return null; return filePaths[0]; } catch (error) { log.error("❌ Path choose error", error); return null; } }); // 修改音乐元信息 ipcMain.handle("set-music-metadata", async (_, path: string, metadata: any) => { try { const { name, artist, album, alia, lyric, cover } = metadata; // 规范化路径 const songPath = resolve(path); const coverPath = cover ? resolve(cover) : null; // 读取歌曲文件 const songFile = File.createFromPath(songPath); // 读取封面文件 const songCover = coverPath ? Picture.fromPath(coverPath) : null; // 保存元数据 Id3v2Settings.forceDefaultVersion = true; Id3v2Settings.defaultVersion = 3; songFile.tag.title = name || "未知曲目"; songFile.tag.performers = [artist || "未知艺术家"]; songFile.tag.album = album || "未知专辑"; songFile.tag.albumArtists = [artist || "未知艺术家"]; songFile.tag.lyrics = lyric || ""; songFile.tag.description = alia || ""; songFile.tag.comment = alia || ""; if (songCover) songFile.tag.pictures = [songCover]; // 保存元信息 songFile.save(); songFile.dispose(); return true; } catch (error) { log.error("❌ Error setting music metadata:", error); throw error; } }); // 下载文件 ipcMain.handle( "download-file", async ( _, url: string, options: { fileName: string; fileType: string; path: string; downloadMeta?: boolean; downloadCover?: boolean; downloadLyric?: boolean; saveMetaFile?: boolean; lyric?: string; songData?: any; } = { fileName: "未知文件名", fileType: "mp3", path: app.getPath("downloads"), }, ): Promise => { try { if (!win) return false; // 获取配置 const { fileName, fileType, path, lyric, downloadMeta, downloadCover, downloadLyric, saveMetaFile, songData, } = options; // 规范化路径 const downloadPath = resolve(path); // 检查文件夹是否存在 try { await fs.access(downloadPath); } catch { throw new Error("❌ Folder not found"); } // 下载文件 const songDownload = await download(win, url, { directory: downloadPath, filename: `${fileName}.${fileType}`, }); if (!downloadMeta || !songData?.cover) return true; // 下载封面 const coverUrl = songData?.coverSize?.l || songData.cover; const coverDownload = await download(win, coverUrl, { directory: downloadPath, filename: `${fileName}.jpg`, }); // 读取歌曲文件 const songFile = File.createFromPath(songDownload.getSavePath()); // 生成图片信息 const songCover = Picture.fromPath(coverDownload.getSavePath()); // 保存修改后的元数据 Id3v2Settings.forceDefaultVersion = true; Id3v2Settings.defaultVersion = 3; songFile.tag.title = songData?.name || "未知曲目"; songFile.tag.album = songData?.album?.name || "未知专辑"; songFile.tag.performers = songData?.artists?.map((ar: any) => ar.name) || ["未知艺术家"]; songFile.tag.albumArtists = songData?.artists?.map((ar: any) => ar.name) || ["未知艺术家"]; if (lyric && downloadLyric) songFile.tag.lyrics = lyric; if (songCover && downloadCover) songFile.tag.pictures = [songCover]; // 保存元信息 songFile.save(); songFile.dispose(); // 创建同名歌词文件 if (lyric && saveMetaFile && downloadLyric) { const lrcPath = join(downloadPath, `${fileName}.lrc`); await fs.writeFile(lrcPath, lyric, "utf-8"); } // 是否删除封面 if (!saveMetaFile || !downloadCover) await fs.unlink(coverDownload.getSavePath()); return true; } catch (error) { log.error("❌ Error downloading file:", error); return false; } }, ); // 取消代理 ipcMain.on("remove-proxy", () => { store.set("proxy", ""); win?.webContents.session.setProxy({ proxyRules: "" }); log.info("✅ Remove proxy successfully"); }); // 配置网络代理 ipcMain.on("set-proxy", (_, config) => { const proxyRules = `${config.protocol}://${config.server}:${config.port}`; store.set("proxy", proxyRules); win?.webContents.session.setProxy({ proxyRules }); log.info("✅ Set proxy successfully:", proxyRules); }); // 代理测试 ipcMain.handle("test-proxy", async (_, config) => { const proxyRules = `${config.protocol}://${config.server}:${config.port}`; try { // 设置代理 const ses = session.defaultSession; await ses.setProxy({ proxyRules }); // 测试请求 const request = net.request({ url: "https://www.baidu.com" }); return new Promise((resolve) => { request.on("response", (response) => { if (response.statusCode === 200) { log.info("✅ Proxy test successful"); resolve(true); } else { log.error(`❌ Proxy test failed with status code: ${response.statusCode}`); resolve(false); } }); request.on("error", (error) => { log.error("❌ Error testing proxy:", error); resolve(false); }); request.end(); }); } catch (error) { log.error("❌ Error testing proxy:", error); return false; } }); // 重置全部设置 ipcMain.on("reset-setting", () => { store.reset(); log.info("✅ Reset setting successfully"); }); // 检查更新 ipcMain.on("check-update", (_, showTip) => checkUpdate(win!, showTip)); // 开始下载更新 ipcMain.on("start-download-update", () => startDownloadUpdate()); }; // lyric const initLyricIpcMain = ( lyricWin: BrowserWindow | null, mainWin: BrowserWindow | null, store: Store, ): void => { // 音乐名称更改 ipcMain.on("play-song-change", (_, title) => { if (!title) return; lyricWin?.webContents.send("play-song-change", title); }); // 音乐歌词更改 ipcMain.on("play-lyric-change", (_, lyricData) => { if (!lyricData) return; lyricWin?.webContents.send("play-lyric-change", lyricData); }); // 获取窗口位置 ipcMain.handle("get-window-bounds", () => { return lyricWin?.getBounds(); }); // 获取屏幕尺寸 ipcMain.handle("get-screen-size", () => { const { width, height } = screen.getPrimaryDisplay().workAreaSize; return { width, height }; }); // 移动窗口 ipcMain.on("move-window", (_, x, y, width, height) => { lyricWin?.setBounds({ x, y, width, height }); // 保存配置 store.set("lyric", { ...store.get("lyric"), x, y, width, height }); // 保持置顶 lyricWin?.setAlwaysOnTop(true, "screen-saver"); }); // 更新高度 ipcMain.on("update-window-height", (_, height) => { if (!lyricWin) return; const { width } = lyricWin.getBounds(); // 更新窗口高度 lyricWin.setBounds({ width, height }); }); // 获取配置 ipcMain.handle("get-desktop-lyric-option", () => { return store.get("lyric"); }); // 保存配置 ipcMain.on("set-desktop-lyric-option", (_, option, callback: boolean = false) => { store.set("lyric", option); // 触发窗口更新 if (callback && lyricWin) { lyricWin.webContents.send("desktop-lyric-option-change", option); } mainWin?.webContents.send("desktop-lyric-option-change", option); }); // 发送主程序事件 ipcMain.on("send-main-event", (_, name, val) => { mainWin?.webContents.send(name, val); }); // 关闭桌面歌词 ipcMain.on("closeDesktopLyric", () => { lyricWin?.hide(); mainWin?.webContents.send("closeDesktopLyric"); }); // 锁定/解锁桌面歌词 ipcMain.on("toogleDesktopLyricLock", (_, isLock: boolean) => { if (!lyricWin) return; // 是否穿透 if (isLock) { lyricWin.setIgnoreMouseEvents(true, { forward: true }); } else { lyricWin.setIgnoreMouseEvents(false); } }); }; // tray const initTrayIpcMain = ( tray: MainTray | null, win: BrowserWindow | null, lyricWin: BrowserWindow | null, ): void => { // 音乐播放状态更改 ipcMain.on("play-status-change", (_, playStatus: boolean) => { tray?.setPlayState(playStatus ? "play" : "pause"); lyricWin?.webContents.send("play-status-change", playStatus); }); // 音乐名称更改 ipcMain.on("play-song-change", (_, title) => { if (!title) return; // 更改标题 win?.setTitle(title); tray?.setTitle(title); tray?.setPlayName(title); }); // 播放模式切换 ipcMain.on("play-mode-change", (_, mode) => { tray?.setPlayMode(mode); }); // 桌面歌词开关 ipcMain.on("change-desktop-lyric", (_, val: boolean) => { tray?.setDesktopLyricShow(val); }); // 锁定/解锁桌面歌词 ipcMain.on("toogleDesktopLyricLock", (_, isLock: boolean) => { tray?.setDesktopLyricLock(isLock); }); }; // thumbar const initThumbarIpcMain = (thumbar: Thumbar | null): void => { if (!thumbar) return; }; // store const initStoreIpcMain = (store: Store): void => { if (!store) return; }; // other const initOtherIpcMain = (mainWin: BrowserWindow | null): void => { // 快捷键是否被注册 ipcMain.handle("is-shortcut-registered", (_, shortcut: string) => isShortcutRegistered(shortcut)); // 注册快捷键 ipcMain.handle("register-all-shortcut", (_, allShortcuts: any): string[] | false => { if (!mainWin || !allShortcuts) return false; // 卸载所有快捷键 unregisterShortcuts(); // 注册快捷键 const failedShortcuts: string[] = []; for (const key in allShortcuts) { const shortcut = allShortcuts[key].globalShortcut; if (!shortcut) continue; // 快捷键回调 const callback = () => mainWin.webContents.send(key); const isSuccess = registerShortcut(shortcut, callback); if (!isSuccess) failedShortcuts.push(shortcut); } return failedShortcuts; }); // 卸载所有快捷键 ipcMain.on("unregister-all-shortcut", () => unregisterShortcuts()); }; export default initIpcMain;