Skip to content

Commit 9fcd5b0

Browse files
committed
✨ feat: 完善歌词窗口 IPC
1 parent a1be1e1 commit 9fcd5b0

8 files changed

Lines changed: 327 additions & 92 deletions

File tree

electron/main/ipc/ipc-lyric.ts

Lines changed: 65 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,56 +13,82 @@ const initLyricIpc = (): void => {
1313
// 歌词窗口
1414
let lyricWin: BrowserWindow | null = null;
1515

16+
/**
17+
* 窗口是否存活
18+
* @param win 窗口实例
19+
* @returns 是否存活
20+
*/
21+
const isWinAlive = (win: BrowserWindow | null): win is BrowserWindow =>
22+
!!win && !win.isDestroyed();
23+
1624
// 切换桌面歌词
1725
ipcMain.on("toggle-desktop-lyric", (_event, val: boolean) => {
1826
if (val) {
19-
if (!lyricWin) {
27+
if (!isWinAlive(lyricWin)) {
2028
lyricWin = lyricWindow.create();
29+
// 监听关闭,置空引用,防止后续调用报错
30+
lyricWin?.on("closed", () => {
31+
lyricWin = null;
32+
});
33+
// 设置位置
34+
const { x, y } = store.get("lyric");
35+
const xPos = Number(x);
36+
const yPos = Number(y);
37+
if (Number.isFinite(xPos) && Number.isFinite(yPos)) {
38+
lyricWin?.setPosition(Math.round(xPos), Math.round(yPos));
39+
}
2140
} else {
22-
lyricWin?.show();
41+
lyricWin.show();
42+
}
43+
if (isWinAlive(lyricWin)) {
44+
lyricWin.setAlwaysOnTop(true, "screen-saver");
2345
}
24-
lyricWin?.setAlwaysOnTop(true, "screen-saver");
2546
} else {
2647
// 关闭:不销毁窗口,直接隐藏,保留位置与状态
27-
if (!lyricWin) return;
48+
if (!isWinAlive(lyricWin)) return;
2849
lyricWin.hide();
2950
}
3051
});
3152

53+
// 向主窗口发送事件
54+
ipcMain.on("send-to-main", (_, eventName, ...args) => {
55+
mainWin?.webContents.send(eventName, ...args);
56+
});
57+
3258
// 更新歌词窗口数据
3359
ipcMain.on("update-desktop-lyric-data", (_, lyricData) => {
34-
if (!lyricData || !lyricWin) return;
35-
lyricWin?.webContents.send("update-desktop-lyric-data", lyricData);
60+
if (!lyricData || !isWinAlive(lyricWin)) return;
61+
lyricWin.webContents.send("update-desktop-lyric-data", lyricData);
3662
});
3763

3864
// 更新歌词窗口配置
3965
ipcMain.on("update-desktop-lyric-option", (_, option) => {
40-
if (!option || !lyricWin) return;
41-
lyricWin?.webContents.send("desktop-lyric-option-change", option);
66+
if (!option || !isWinAlive(lyricWin)) return;
67+
lyricWin.webContents.send("desktop-lyric-option-change", option);
4268
});
4369

4470
// 播放状态更改
4571
ipcMain.on("play-status-change", (_, status) => {
46-
if (!status || !lyricWin) return;
47-
lyricWin?.webContents.send("update-desktop-lyric-data", { playStatus: status });
72+
if (!isWinAlive(lyricWin)) return;
73+
lyricWin.webContents.send("update-desktop-lyric-data", { playStatus: status });
4874
});
4975

5076
// 音乐名称更改
5177
ipcMain.on("play-song-change", (_, title) => {
52-
if (!title || !lyricWin) return;
53-
lyricWin?.webContents.send("update-desktop-lyric-data", { playName: title });
78+
if (!title || !isWinAlive(lyricWin)) return;
79+
lyricWin.webContents.send("update-desktop-lyric-data", { playName: title });
5480
});
5581

5682
// 音乐歌词更改
5783
ipcMain.on("play-lyric-change", (_, lyricData) => {
58-
if (!lyricData || !lyricWin) return;
59-
lyricWin?.webContents.send("update-desktop-lyric-data", lyricData);
84+
if (!lyricData || !isWinAlive(lyricWin)) return;
85+
lyricWin.webContents.send("update-desktop-lyric-data", lyricData);
6086
});
6187

6288
// 获取窗口位置
6389
ipcMain.handle("get-window-bounds", () => {
64-
if (!lyricWin) return {};
65-
return lyricWin?.getBounds();
90+
if (!isWinAlive(lyricWin)) return {};
91+
return lyricWin.getBounds();
6692
});
6793

6894
// 获取屏幕尺寸
@@ -71,24 +97,40 @@ const initLyricIpc = (): void => {
7197
return { width, height };
7298
});
7399

100+
// 获取多屏虚拟边界(支持负坐标)
101+
ipcMain.handle("get-virtual-screen-bounds", () => {
102+
const displays = screen.getAllDisplays();
103+
const bounds = displays.map((d) => d.workArea);
104+
const minX = Math.min(...bounds.map((b) => b.x));
105+
const minY = Math.min(...bounds.map((b) => b.y));
106+
const maxX = Math.max(...bounds.map((b) => b.x + b.width));
107+
const maxY = Math.max(...bounds.map((b) => b.y + b.height));
108+
return { minX, minY, maxX, maxY };
109+
});
110+
74111
// 移动窗口
75112
ipcMain.on("move-window", (_, x, y, width, height) => {
76-
if (!lyricWin) return;
113+
if (!isWinAlive(lyricWin)) return;
77114
lyricWin.setBounds({ x, y, width, height });
78115
// 保存配置
79116
store.set("lyric", { ...store.get("lyric"), x, y, width, height });
80-
// 保持置顶
81-
lyricWin?.setAlwaysOnTop(true, "screen-saver");
82117
});
83118

84119
// 更新高度
85120
ipcMain.on("update-window-height", (_, height) => {
86-
if (!lyricWin) return;
121+
if (!isWinAlive(lyricWin)) return;
87122
const { width } = lyricWin.getBounds();
88123
// 更新窗口高度
89124
lyricWin.setBounds({ width, height });
90125
});
91126

127+
// 请求歌词数据及配置
128+
ipcMain.on("request-desktop-lyric-data", () => {
129+
if (!isWinAlive(lyricWin)) return;
130+
// 触发窗口更新
131+
mainWin?.webContents.send("request-desktop-lyric-data");
132+
});
133+
92134
// 获取配置
93135
ipcMain.handle("get-desktop-lyric-option", () => {
94136
return store.get("lyric");
@@ -98,7 +140,7 @@ const initLyricIpc = (): void => {
98140
ipcMain.on("set-desktop-lyric-option", (_, option, callback: boolean = false) => {
99141
store.set("lyric", option);
100142
// 触发窗口更新
101-
if (callback && lyricWin) {
143+
if (callback && isWinAlive(lyricWin)) {
102144
lyricWin.webContents.send("desktop-lyric-option-change", option);
103145
}
104146
mainWin?.webContents.send("desktop-lyric-option-change", option);
@@ -111,14 +153,14 @@ const initLyricIpc = (): void => {
111153

112154
// 关闭桌面歌词
113155
ipcMain.on("closeDesktopLyric", () => {
114-
if (!lyricWin) return;
156+
if (!isWinAlive(lyricWin)) return;
115157
lyricWin.hide();
116158
mainWin?.webContents.send("closeDesktopLyric");
117159
});
118160

119161
// 锁定/解锁桌面歌词
120162
ipcMain.on("toogleDesktopLyricLock", (_, isLock: boolean) => {
121-
if (!lyricWin) return;
163+
if (!isWinAlive(lyricWin)) return;
122164
// 是否穿透
123165
if (isLock) {
124166
lyricWin.setIgnoreMouseEvents(true, { forward: true });

electron/main/ipc/ipc-window.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@ const initWindowsIpc = (): void => {
2929
if (isMaximized) mainWin?.maximize();
3030
mainWin?.show();
3131
mainWin?.focus();
32+
// 解决窗口不立即显示
33+
mainWin?.setAlwaysOnTop(true);
34+
// 100ms 后取消置顶
35+
const timer = setTimeout(() => {
36+
if (mainWin && !mainWin.isDestroyed()) {
37+
mainWin.setAlwaysOnTop(false);
38+
mainWin.focus();
39+
clearTimeout(timer);
40+
}
41+
}, 100);
3242
// 初始化缩略图工具栏
3343
if (mainWin) initThumbar(mainWin);
3444
});
@@ -64,6 +74,7 @@ const initWindowsIpc = (): void => {
6474
// 显示
6575
ipcMain.on("win-show", () => {
6676
mainWin?.show();
77+
mainWin?.focus();
6778
});
6879

6980
// 重启

electron/main/store/index.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { screen } from "electron";
21
import { storeLog } from "../logger";
2+
import type { LyricConfig } from "../../../src/types/desktop-lyric";
33
import Store from "electron-store";
44

55
storeLog.info("🌱 Store init");
@@ -21,6 +21,8 @@ export interface StoreType {
2121
y?: number;
2222
width?: number;
2323
height?: number;
24+
// 配置
25+
config?: LyricConfig;
2426
};
2527
proxy: string;
2628
}
@@ -40,10 +42,22 @@ export const useStore = () => {
4042
fontSize: 30,
4143
mainColor: "#fff",
4244
shadowColor: "rgba(0, 0, 0, 0.5)",
43-
x: screen.getPrimaryDisplay().workAreaSize.width / 2 - 400,
44-
y: screen.getPrimaryDisplay().workAreaSize.height - 90,
45+
x: 0,
46+
y: 0,
4547
width: 800,
4648
height: 180,
49+
config: {
50+
isLock: false,
51+
playedColor: "#fe7971",
52+
unplayedColor: "#ccc",
53+
stroke: "#000",
54+
strokeWidth: 2,
55+
fontFamily: "system-ui",
56+
fontSize: 24,
57+
isDoubleLine: true,
58+
position: "both",
59+
limitBounds: false,
60+
},
4761
},
4862
proxy: "",
4963
},

electron/main/windows/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export const createWindow = (
1212
title: appName,
1313
width: 1280,
1414
height: 720,
15-
frame: false, // 创建后是否显示窗口
15+
frame: false, // 是否显示窗口边框
1616
center: true, // 窗口居中
1717
icon, // 窗口图标
1818
autoHideMenuBar: true, // 隐藏菜单栏

electron/main/windows/lyric-window.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,25 +38,27 @@ class LyricWindow {
3838
height: height || 180,
3939
minWidth: 440,
4040
minHeight: 120,
41-
maxWidth: 1600,
42-
maxHeight: 300,
41+
center: !(x && y), // 没有指定位置时居中显示
42+
// maxWidth: 1600,
43+
// maxHeight: 300,
4344
// 窗口位置
4445
x,
4546
y,
46-
transparent: true,
47-
backgroundColor: "rgba(0, 0, 0, 0)",
47+
// transparent: true,
48+
// backgroundColor: "rgba(0, 0, 0, 0)",
4849
alwaysOnTop: true,
4950
resizable: true,
5051
movable: true,
5152
show: false,
5253
// 不在任务栏显示
53-
skipTaskbar: true,
54-
// 窗口不能最小化
55-
minimizable: false,
56-
// 窗口不能最大化
57-
maximizable: false,
58-
// 窗口不能进入全屏状态
59-
fullscreenable: false,
54+
// skipTaskbar: true,
55+
// // 窗口不能最小化
56+
// minimizable: false,
57+
// // 窗口不能最大化
58+
// maximizable: false,
59+
// // 窗口不能进入全屏状态
60+
// fullscreenable: false,
61+
frame: true,
6062
});
6163
if (!this.win) return null;
6264
// 加载地址
Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@ import { LyricType } from "@/types/main";
33
/** 桌面歌词数据 */
44
export interface LyricData {
55
/** 播放歌曲名称 */
6-
playName: string;
6+
playName?: string;
77
/** 播放状态 */
8-
playStatus: boolean;
8+
playStatus?: boolean;
99
/** 播放进度 */
10-
progress: number;
10+
progress?: number;
1111
/** 歌词数据 */
12-
lrcData: LyricType[];
13-
yrcData: LyricType[];
12+
lrcData?: LyricType[];
13+
yrcData?: LyricType[];
1414
/** 歌词播放索引 */
15-
lyricIndex: number;
15+
lyricIndex?: number;
1616
}
1717

1818
/** 桌面歌词配置 */
@@ -31,10 +31,22 @@ export interface LyricConfig {
3131
fontFamily: string;
3232
/** 字体大小 */
3333
fontSize: number;
34-
/** 行高 */
35-
lineHeight: number;
3634
/** 是否双行 */
3735
isDoubleLine: boolean;
3836
/** 文本排版位置 */
3937
position: "left" | "center" | "right" | "both";
38+
/** 是否限制在屏幕边界内拖动 */
39+
limitBounds: boolean;
40+
}
41+
42+
/**
43+
* 渲染的歌词行
44+
*/
45+
interface RenderLine {
46+
/** 歌词文本 */
47+
text: string;
48+
/** 唯一键 */
49+
key: string;
50+
/** 是否高亮 */
51+
active: boolean;
4052
}

src/utils/initIpc.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { openUpdateApp } from "./modal";
33
import { useMusicStore, useDataStore, useStatusStore } from "@/stores";
44
import { toLikeSong } from "./auth";
55
import player from "./player";
6+
import { cloneDeep } from "lodash-es";
7+
import { getPlayerInfo } from "./player-utils/song";
68

79
// 关闭更新状态
810
const closeUpdateStatus = () => {
@@ -39,6 +41,23 @@ const initIpc = () => {
3941
// 桌面歌词开关
4042
window.electron.ipcRenderer.on("toogleDesktopLyric", () => player.toggleDesktopLyric());
4143
window.electron.ipcRenderer.on("closeDesktopLyric", () => player.toggleDesktopLyric());
44+
// 请求歌词数据
45+
window.electron.ipcRenderer.on("request-desktop-lyric-data", () => {
46+
const musicStore = useMusicStore();
47+
const statusStore = useStatusStore();
48+
if (player) {
49+
window.electron.ipcRenderer.send(
50+
"update-desktop-lyric-data",
51+
cloneDeep({
52+
playStatus: statusStore.playStatus,
53+
playName: getPlayerInfo() ?? "未知歌曲",
54+
lrcData: musicStore.songLyric.lrcData ?? [],
55+
yrcData: musicStore.songLyric.yrcData ?? [],
56+
lyricIndex: statusStore.lyricIndex,
57+
}),
58+
);
59+
}
60+
});
4261
// 无更新
4362
window.electron.ipcRenderer.on("update-not-available", () => {
4463
closeUpdateStatus();

0 commit comments

Comments
 (0)