diff --git a/electron/main/ipc/ipc-lyric.ts b/electron/main/ipc/ipc-lyric.ts index a24dd24..b72308f 100644 --- a/electron/main/ipc/ipc-lyric.ts +++ b/electron/main/ipc/ipc-lyric.ts @@ -13,56 +13,82 @@ const initLyricIpc = (): void => { // 歌词窗口 let lyricWin: BrowserWindow | null = null; + /** + * 窗口是否存活 + * @param win 窗口实例 + * @returns 是否存活 + */ + const isWinAlive = (win: BrowserWindow | null): win is BrowserWindow => + !!win && !win.isDestroyed(); + // 切换桌面歌词 ipcMain.on("toggle-desktop-lyric", (_event, val: boolean) => { if (val) { - if (!lyricWin) { + if (!isWinAlive(lyricWin)) { lyricWin = lyricWindow.create(); + // 监听关闭,置空引用,防止后续调用报错 + lyricWin?.on("closed", () => { + lyricWin = null; + }); + // 设置位置 + const { x, y } = store.get("lyric"); + const xPos = Number(x); + const yPos = Number(y); + if (Number.isFinite(xPos) && Number.isFinite(yPos)) { + lyricWin?.setPosition(Math.round(xPos), Math.round(yPos)); + } } else { - lyricWin?.show(); + lyricWin.show(); + } + if (isWinAlive(lyricWin)) { + lyricWin.setAlwaysOnTop(true, "screen-saver"); } - lyricWin?.setAlwaysOnTop(true, "screen-saver"); } else { // 关闭:不销毁窗口,直接隐藏,保留位置与状态 - if (!lyricWin) return; + if (!isWinAlive(lyricWin)) return; lyricWin.hide(); } }); + // 向主窗口发送事件 + ipcMain.on("send-to-main", (_, eventName, ...args) => { + mainWin?.webContents.send(eventName, ...args); + }); + // 更新歌词窗口数据 ipcMain.on("update-desktop-lyric-data", (_, lyricData) => { - if (!lyricData || !lyricWin) return; - lyricWin?.webContents.send("update-desktop-lyric-data", lyricData); + if (!lyricData || !isWinAlive(lyricWin)) return; + lyricWin.webContents.send("update-desktop-lyric-data", lyricData); }); // 更新歌词窗口配置 ipcMain.on("update-desktop-lyric-option", (_, option) => { - if (!option || !lyricWin) return; - lyricWin?.webContents.send("desktop-lyric-option-change", option); + if (!option || !isWinAlive(lyricWin)) return; + lyricWin.webContents.send("desktop-lyric-option-change", option); }); // 播放状态更改 ipcMain.on("play-status-change", (_, status) => { - if (!status || !lyricWin) return; - lyricWin?.webContents.send("update-desktop-lyric-data", { playStatus: status }); + if (!isWinAlive(lyricWin)) return; + lyricWin.webContents.send("update-desktop-lyric-data", { playStatus: status }); }); // 音乐名称更改 ipcMain.on("play-song-change", (_, title) => { - if (!title || !lyricWin) return; - lyricWin?.webContents.send("update-desktop-lyric-data", { playName: title }); + if (!title || !isWinAlive(lyricWin)) return; + lyricWin.webContents.send("update-desktop-lyric-data", { playName: title }); }); // 音乐歌词更改 ipcMain.on("play-lyric-change", (_, lyricData) => { - if (!lyricData || !lyricWin) return; - lyricWin?.webContents.send("update-desktop-lyric-data", lyricData); + if (!lyricData || !isWinAlive(lyricWin)) return; + lyricWin.webContents.send("update-desktop-lyric-data", lyricData); }); // 获取窗口位置 ipcMain.handle("get-window-bounds", () => { - if (!lyricWin) return {}; - return lyricWin?.getBounds(); + if (!isWinAlive(lyricWin)) return {}; + return lyricWin.getBounds(); }); // 获取屏幕尺寸 @@ -71,24 +97,40 @@ const initLyricIpc = (): void => { return { width, height }; }); + // 获取多屏虚拟边界(支持负坐标) + ipcMain.handle("get-virtual-screen-bounds", () => { + const displays = screen.getAllDisplays(); + const bounds = displays.map((d) => d.workArea); + const minX = Math.min(...bounds.map((b) => b.x)); + const minY = Math.min(...bounds.map((b) => b.y)); + const maxX = Math.max(...bounds.map((b) => b.x + b.width)); + const maxY = Math.max(...bounds.map((b) => b.y + b.height)); + return { minX, minY, maxX, maxY }; + }); + // 移动窗口 ipcMain.on("move-window", (_, x, y, width, height) => { - if (!lyricWin) return; + if (!isWinAlive(lyricWin)) return; 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; + if (!isWinAlive(lyricWin)) return; const { width } = lyricWin.getBounds(); // 更新窗口高度 lyricWin.setBounds({ width, height }); }); + // 请求歌词数据及配置 + ipcMain.on("request-desktop-lyric-data", () => { + if (!isWinAlive(lyricWin)) return; + // 触发窗口更新 + mainWin?.webContents.send("request-desktop-lyric-data"); + }); + // 获取配置 ipcMain.handle("get-desktop-lyric-option", () => { return store.get("lyric"); @@ -98,7 +140,7 @@ const initLyricIpc = (): void => { ipcMain.on("set-desktop-lyric-option", (_, option, callback: boolean = false) => { store.set("lyric", option); // 触发窗口更新 - if (callback && lyricWin) { + if (callback && isWinAlive(lyricWin)) { lyricWin.webContents.send("desktop-lyric-option-change", option); } mainWin?.webContents.send("desktop-lyric-option-change", option); @@ -111,14 +153,14 @@ const initLyricIpc = (): void => { // 关闭桌面歌词 ipcMain.on("closeDesktopLyric", () => { - if (!lyricWin) return; + if (!isWinAlive(lyricWin)) return; lyricWin.hide(); mainWin?.webContents.send("closeDesktopLyric"); }); // 锁定/解锁桌面歌词 ipcMain.on("toogleDesktopLyricLock", (_, isLock: boolean) => { - if (!lyricWin) return; + if (!isWinAlive(lyricWin)) return; // 是否穿透 if (isLock) { lyricWin.setIgnoreMouseEvents(true, { forward: true }); diff --git a/electron/main/ipc/ipc-window.ts b/electron/main/ipc/ipc-window.ts index cdd63b6..f984f7b 100644 --- a/electron/main/ipc/ipc-window.ts +++ b/electron/main/ipc/ipc-window.ts @@ -29,6 +29,16 @@ const initWindowsIpc = (): void => { if (isMaximized) mainWin?.maximize(); mainWin?.show(); mainWin?.focus(); + // 解决窗口不立即显示 + mainWin?.setAlwaysOnTop(true); + // 100ms 后取消置顶 + const timer = setTimeout(() => { + if (mainWin && !mainWin.isDestroyed()) { + mainWin.setAlwaysOnTop(false); + mainWin.focus(); + clearTimeout(timer); + } + }, 100); // 初始化缩略图工具栏 if (mainWin) initThumbar(mainWin); }); @@ -64,6 +74,7 @@ const initWindowsIpc = (): void => { // 显示 ipcMain.on("win-show", () => { mainWin?.show(); + mainWin?.focus(); }); // 重启 diff --git a/electron/main/store/index.ts b/electron/main/store/index.ts index c41d988..2d047f4 100644 --- a/electron/main/store/index.ts +++ b/electron/main/store/index.ts @@ -1,5 +1,5 @@ -import { screen } from "electron"; import { storeLog } from "../logger"; +import type { LyricConfig } from "../../../src/types/desktop-lyric"; import Store from "electron-store"; storeLog.info("🌱 Store init"); @@ -21,6 +21,8 @@ export interface StoreType { y?: number; width?: number; height?: number; + // 配置 + config?: LyricConfig; }; proxy: string; } @@ -40,10 +42,22 @@ export const useStore = () => { fontSize: 30, mainColor: "#fff", shadowColor: "rgba(0, 0, 0, 0.5)", - x: screen.getPrimaryDisplay().workAreaSize.width / 2 - 400, - y: screen.getPrimaryDisplay().workAreaSize.height - 90, + x: 0, + y: 0, width: 800, height: 180, + config: { + isLock: false, + playedColor: "#fe7971", + unplayedColor: "#ccc", + stroke: "#000", + strokeWidth: 2, + fontFamily: "system-ui", + fontSize: 24, + isDoubleLine: true, + position: "both", + limitBounds: false, + }, }, proxy: "", }, diff --git a/electron/main/windows/index.ts b/electron/main/windows/index.ts index 5790c23..fd47f1c 100644 --- a/electron/main/windows/index.ts +++ b/electron/main/windows/index.ts @@ -12,7 +12,7 @@ export const createWindow = ( title: appName, width: 1280, height: 720, - frame: false, // 创建后是否显示窗口 + frame: false, // 是否显示窗口边框 center: true, // 窗口居中 icon, // 窗口图标 autoHideMenuBar: true, // 隐藏菜单栏 diff --git a/electron/main/windows/lyric-window.ts b/electron/main/windows/lyric-window.ts index 7c26f46..397c5db 100644 --- a/electron/main/windows/lyric-window.ts +++ b/electron/main/windows/lyric-window.ts @@ -38,25 +38,27 @@ class LyricWindow { height: height || 180, minWidth: 440, minHeight: 120, - maxWidth: 1600, - maxHeight: 300, + center: !(x && y), // 没有指定位置时居中显示 + // maxWidth: 1600, + // maxHeight: 300, // 窗口位置 x, y, - transparent: true, - backgroundColor: "rgba(0, 0, 0, 0)", + // transparent: true, + // backgroundColor: "rgba(0, 0, 0, 0)", alwaysOnTop: true, resizable: true, movable: true, show: false, // 不在任务栏显示 - skipTaskbar: true, - // 窗口不能最小化 - minimizable: false, - // 窗口不能最大化 - maximizable: false, - // 窗口不能进入全屏状态 - fullscreenable: false, + // skipTaskbar: true, + // // 窗口不能最小化 + // minimizable: false, + // // 窗口不能最大化 + // maximizable: false, + // // 窗口不能进入全屏状态 + // fullscreenable: false, + frame: true, }); if (!this.win) return null; // 加载地址 diff --git a/src/views/DesktopLyric/index.d.ts b/src/types/desktop-lyric.d.ts similarity index 65% rename from src/views/DesktopLyric/index.d.ts rename to src/types/desktop-lyric.d.ts index 36c07a3..aed5704 100644 --- a/src/views/DesktopLyric/index.d.ts +++ b/src/types/desktop-lyric.d.ts @@ -3,16 +3,16 @@ import { LyricType } from "@/types/main"; /** 桌面歌词数据 */ export interface LyricData { /** 播放歌曲名称 */ - playName: string; + playName?: string; /** 播放状态 */ - playStatus: boolean; + playStatus?: boolean; /** 播放进度 */ - progress: number; + progress?: number; /** 歌词数据 */ - lrcData: LyricType[]; - yrcData: LyricType[]; + lrcData?: LyricType[]; + yrcData?: LyricType[]; /** 歌词播放索引 */ - lyricIndex: number; + lyricIndex?: number; } /** 桌面歌词配置 */ @@ -31,10 +31,22 @@ export interface LyricConfig { fontFamily: string; /** 字体大小 */ fontSize: number; - /** 行高 */ - lineHeight: number; /** 是否双行 */ isDoubleLine: boolean; /** 文本排版位置 */ position: "left" | "center" | "right" | "both"; + /** 是否限制在屏幕边界内拖动 */ + limitBounds: boolean; +} + +/** + * 渲染的歌词行 + */ +interface RenderLine { + /** 歌词文本 */ + text: string; + /** 唯一键 */ + key: string; + /** 是否高亮 */ + active: boolean; } diff --git a/src/utils/initIpc.ts b/src/utils/initIpc.ts index a35a5d2..63bea3d 100644 --- a/src/utils/initIpc.ts +++ b/src/utils/initIpc.ts @@ -3,6 +3,8 @@ import { openUpdateApp } from "./modal"; import { useMusicStore, useDataStore, useStatusStore } from "@/stores"; import { toLikeSong } from "./auth"; import player from "./player"; +import { cloneDeep } from "lodash-es"; +import { getPlayerInfo } from "./player-utils/song"; // 关闭更新状态 const closeUpdateStatus = () => { @@ -39,6 +41,23 @@ const initIpc = () => { // 桌面歌词开关 window.electron.ipcRenderer.on("toogleDesktopLyric", () => player.toggleDesktopLyric()); window.electron.ipcRenderer.on("closeDesktopLyric", () => player.toggleDesktopLyric()); + // 请求歌词数据 + window.electron.ipcRenderer.on("request-desktop-lyric-data", () => { + const musicStore = useMusicStore(); + const statusStore = useStatusStore(); + if (player) { + window.electron.ipcRenderer.send( + "update-desktop-lyric-data", + cloneDeep({ + playStatus: statusStore.playStatus, + playName: getPlayerInfo() ?? "未知歌曲", + lrcData: musicStore.songLyric.lrcData ?? [], + yrcData: musicStore.songLyric.yrcData ?? [], + lyricIndex: statusStore.lyricIndex, + }), + ); + } + }); // 无更新 window.electron.ipcRenderer.on("update-not-available", () => { closeUpdateStatus(); diff --git a/src/views/DesktopLyric/index.vue b/src/views/DesktopLyric/index.vue index 8c27fd0..3eeab47 100644 --- a/src/views/DesktopLyric/index.vue +++ b/src/views/DesktopLyric/index.vue @@ -3,15 +3,15 @@
- -