diff --git a/electron/main/ipc/ipc-taskbar.ts b/electron/main/ipc/ipc-taskbar.ts index 9a37d8348..e9c67c82f 100644 --- a/electron/main/ipc/ipc-taskbar.ts +++ b/electron/main/ipc/ipc-taskbar.ts @@ -38,6 +38,10 @@ const initTaskbarIpc = () => { taskbarLyricManager.setContentWidth(width); }); + ipcMain.on("taskbar:set-ignore-mouse-events", (_event, ignore: boolean) => { + taskbarLyricManager.setMousePassthrough(ignore); + }); + ipcMain.handle(TASKBAR_IPC_CHANNELS.GET_OPTION, () => getTaskbarConfig()); // 设置配置(增量合并) @@ -104,6 +108,16 @@ const initTaskbarIpc = () => { taskbarLyricManager.handleFadeDone(); }); + // 强制重载歌词窗口 + ipcMain.on(TASKBAR_IPC_CHANNELS.FORCE_RELOAD, () => { + const currentConfig = getTaskbarConfig(); + if (!currentConfig.enabled) return; + taskbarLyricManager.close(false); + setTimeout(() => { + updateWindowVisibility(currentConfig); + }, 500); + }); + // 把事件发射到 app 里不太好,但是我觉得也没有必要为了这一个事件创建一个事件总线 // TODO: 如果有了事件总线,通过那个事件总线发射这个事件 (app as EventEmitter).on("explorer-restarted", () => { diff --git a/electron/main/utils/taskbar-lyric-manager.ts b/electron/main/utils/taskbar-lyric-manager.ts index 9db4fb48f..ef1126f65 100644 --- a/electron/main/utils/taskbar-lyric-manager.ts +++ b/electron/main/utils/taskbar-lyric-manager.ts @@ -50,6 +50,10 @@ class TaskbarLyricManager { this.getActive().handleFadeDone(); } + setMousePassthrough(ignore: boolean) { + this.getActive().setMousePassthrough(ignore); + } + send(channel: string, ...args: unknown[]) { this.getActive().send(channel, ...args); } diff --git a/electron/main/windows/floating-taskbar-lyric-window.ts b/electron/main/windows/floating-taskbar-lyric-window.ts index c5942e9b9..d479bc0ca 100644 --- a/electron/main/windows/floating-taskbar-lyric-window.ts +++ b/electron/main/windows/floating-taskbar-lyric-window.ts @@ -18,6 +18,7 @@ class FloatingTaskbarLyricWindow { private isFadingOut = false; private lastFloatingAlign: "left" | "right" | null = null; private lastAlwaysOnTop: boolean | null = null; + private lastFloatingLock: boolean | null = null; private debouncedSaveBounds = debounce((bounds: Electron.Rectangle) => { const store = useStore(); @@ -73,6 +74,7 @@ class FloatingTaskbarLyricWindow { if (!this.win) return null; this.applyAlwaysOnTop(true); + this.applyFloatingLock(true); this.win.loadURL(floatingTaskbarLyricUrl); const sendTheme = () => { @@ -164,6 +166,15 @@ class FloatingTaskbarLyricWindow { } } + private applyFloatingLock(force: boolean) { + if (!this.win || this.win.isDestroyed()) return; + const store = useStore(); + const floatingLock = store.get("taskbar.floatingLock", false); + if (!force && this.lastFloatingLock === floatingLock) return; + this.lastFloatingLock = floatingLock; + this.setMousePassthrough(floatingLock); + } + updateLayout(_animate: boolean = false) { if (!this.win || this.win.isDestroyed()) return; @@ -210,6 +221,7 @@ class FloatingTaskbarLyricWindow { this.win.setBounds({ x: nextX, y: nextY, width: nextWidth, height: nextHeight }); this.applyAlwaysOnTop(false); + this.applyFloatingLock(false); this.sendFloatingAlign(false); } @@ -249,6 +261,15 @@ class FloatingTaskbarLyricWindow { } } + setMousePassthrough(ignore: boolean) { + if (!this.win || this.win.isDestroyed()) return; + if (ignore) { + this.win.setIgnoreMouseEvents(true, { forward: true }); + } else { + this.win.setIgnoreMouseEvents(false); + } + } + close() { if (this.win && !this.win.isDestroyed()) { this.win.close(); diff --git a/electron/main/windows/taskbar-lyric-window.ts b/electron/main/windows/taskbar-lyric-window.ts index 085e38594..31fd301ca 100644 --- a/electron/main/windows/taskbar-lyric-window.ts +++ b/electron/main/windows/taskbar-lyric-window.ts @@ -468,6 +468,15 @@ class TaskbarLyricWindow { } } + public setMousePassthrough(ignore: boolean) { + if (!this.win || this.win.isDestroyed()) return; + if (ignore) { + this.win.setIgnoreMouseEvents(true, { forward: true }); + } else { + this.win.setIgnoreMouseEvents(false); + } + } + public destroy() { if (this.isNativeDisposed) return; this.debouncedUpdateLayout.cancel(); diff --git a/src/components/Setting/config/lyric.ts b/src/components/Setting/config/lyric.ts index 09a6e6770..3732863d4 100644 --- a/src/components/Setting/config/lyric.ts +++ b/src/components/Setting/config/lyric.ts @@ -905,6 +905,20 @@ export const useLyricSettings = (): SettingConfig => { }, }), }, + { + key: "taskbarLyricFloatingLock", + label: "锁定模式", + type: "switch", + description: "开启后鼠标将穿透窗口,关闭后可拖动窗口", + show: () => taskbarLyricConfig.mode === "floating", + value: computed({ + get: () => taskbarLyricConfig.floatingLock, + set: (v) => { + taskbarLyricConfig.floatingLock = v ?? false; + saveTaskbarLyricConfig({ floatingLock: taskbarLyricConfig.floatingLock }); + }, + }), + }, { key: "taskbarLyricFloatingAutoWidth", label: "悬浮自动宽度", @@ -972,6 +986,19 @@ export const useLyricSettings = (): SettingConfig => { }, }), }, + { + key: "taskbarLyricHideLyrics", + label: "隐藏歌词", + type: "switch", + description: "开启后仅显示歌名和歌手,不显示歌词内容", + value: computed({ + get: () => taskbarLyricConfig.hideLyrics, + set: (v) => { + taskbarLyricConfig.hideLyrics = v ?? false; + saveTaskbarLyricConfig({ hideLyrics: taskbarLyricConfig.hideLyrics }); + }, + }), + }, { key: "taskbarLyricUseThemeColor", label: "跟随封面颜色", @@ -1238,6 +1265,21 @@ export const useLyricSettings = (): SettingConfig => { }), defaultValue: 0.8, }, + { + key: "taskbarLyricForceReload", + label: "强制重载", + type: "button", + description: "关闭并重新创建任务栏歌词窗口,用于修复显示异常", + buttonLabel: "重载", + action: () => { + if (!statusStore.showTaskbarLyric) { + window.$message.warning("请先开启任务栏歌词"); + return; + } + window.electron.ipcRenderer.send(TASKBAR_IPC_CHANNELS.FORCE_RELOAD); + window.$message.success("任务栏歌词已重载"); + }, + }, { key: "taskbarLyricRestore", label: "恢复默认配置", diff --git a/src/types/shared/taskbar-ipc.ts b/src/types/shared/taskbar-ipc.ts index c3dcea188..648507247 100644 --- a/src/types/shared/taskbar-ipc.ts +++ b/src/types/shared/taskbar-ipc.ts @@ -25,6 +25,8 @@ export interface TaskbarConfig { floatingHeight: number; /** 悬浮置顶 */ floatingAlwaysOnTop: boolean; + /** 悬浮锁定模式 */ + floatingLock: boolean; /** 是否启用 */ enabled: boolean; /** 暂停时显示 */ @@ -53,6 +55,8 @@ export interface TaskbarConfig { mainScale: number; /** 副歌词缩放比例 */ subScale: number; + /** 隐藏歌词,仅显示歌曲信息 */ + hideLyrics: boolean; } export interface TrackData { @@ -130,6 +134,7 @@ export const DEFAULT_TASKBAR_CONFIG: TaskbarConfig = { floatingWidth: 300, floatingHeight: 48, floatingAlwaysOnTop: false, + floatingLock: false, enabled: false, showWhenPaused: true, showCover: true, @@ -144,6 +149,7 @@ export const DEFAULT_TASKBAR_CONFIG: TaskbarConfig = { lineHeight: 1.1, mainScale: 1.0, subScale: 0.8, + hideLyrics: false, }; export const TASKBAR_IPC_CHANNELS = { @@ -171,4 +177,8 @@ export const TASKBAR_IPC_CHANNELS = { * 渲染进程 -> 主进程 (初始化握手) */ REQUEST_DATA: "taskbar:request-data", + /** + * 渲染进程 -> 主进程 (强制重载歌词窗口) + */ + FORCE_RELOAD: "taskbar:force-reload", } as const; diff --git a/src/views/TaskbarLyric/index.vue b/src/views/TaskbarLyric/index.vue index c692939f2..7231b27c4 100644 --- a/src/views/TaskbarLyric/index.vue +++ b/src/views/TaskbarLyric/index.vue @@ -92,6 +92,7 @@ const taskbarConfig = reactive({ ...DEFAULT_TASKBAR_CONFIG }); const isFloating = computed(() => route.query.mode === "floating"); const isHovering = ref(false); const showControls = computed(() => !isFloating.value && isHovering.value); +let lastMousePassthrough: boolean | null = null; /** * 只有当 IPC 时间与本地时间误差超过 250ms 时,才同步 IPC 的时间 @@ -190,6 +191,14 @@ const handleMouseLeave = () => { isHovering.value = false; }; +const setMousePassthrough = (ignore: boolean) => { + const ipc = window.electron?.ipcRenderer; + if (!ipc) return; + if (lastMousePassthrough === ignore) return; + lastMousePassthrough = ignore; + ipc.send("taskbar:set-ignore-mouse-events", ignore); +}; + const controlAction = (action: "playPrev" | "playOrPause" | "playNext") => { const ipc = window.electron?.ipcRenderer; if (!ipc) return; @@ -355,7 +364,7 @@ const updateBgCache = () => { watch(mainLyricIndex, updateBgCache); const displayItems = computed(() => { - if (!currentLyricText.value) { + if (taskbarConfig.hideLyrics || !currentLyricText.value) { return createMetadataItems(state.title, state.artist); } @@ -565,6 +574,15 @@ onMounted(() => { const ipc = window.electron?.ipcRenderer; if (!ipc) return; + if (isFloating.value) { + setMousePassthrough(taskbarConfig.floatingLock); + watch( + () => taskbarConfig.floatingLock, + (v) => setMousePassthrough(v), + { immediate: true }, + ); + } + ipc.on(TASKBAR_IPC_CHANNELS.SYNC_STATE, (_, payload: SyncStatePayload) => { switch (payload.type) { case "full-hydration": { @@ -698,6 +716,7 @@ $radius: 4px; margin: 5px 0; padding: 0 0.9em; box-sizing: border-box; + position: relative; display: flex; align-items: center; justify-content: flex-start;