diff --git a/src/main/assets/icons/taskbar/next.png b/src/main/assets/icons/taskbar/next.png new file mode 100644 index 0000000..a2a42c0 Binary files /dev/null and b/src/main/assets/icons/taskbar/next.png differ diff --git a/src/main/assets/icons/taskbar/pause.png b/src/main/assets/icons/taskbar/pause.png new file mode 100644 index 0000000..8f2927f Binary files /dev/null and b/src/main/assets/icons/taskbar/pause.png differ diff --git a/src/main/assets/icons/taskbar/play.png b/src/main/assets/icons/taskbar/play.png new file mode 100644 index 0000000..d29ccc0 Binary files /dev/null and b/src/main/assets/icons/taskbar/play.png differ diff --git a/src/main/assets/icons/taskbar/previous.png b/src/main/assets/icons/taskbar/previous.png new file mode 100644 index 0000000..6003dd2 Binary files /dev/null and b/src/main/assets/icons/taskbar/previous.png differ diff --git a/src/main/assets/icons/exit.png b/src/main/assets/icons/tray/exit.png similarity index 100% rename from src/main/assets/icons/exit.png rename to src/main/assets/icons/tray/exit.png diff --git a/src/main/assets/icons/left.png b/src/main/assets/icons/tray/left.png similarity index 100% rename from src/main/assets/icons/left.png rename to src/main/assets/icons/tray/left.png diff --git a/src/main/assets/icons/like.png b/src/main/assets/icons/tray/like.png similarity index 100% rename from src/main/assets/icons/like.png rename to src/main/assets/icons/tray/like.png diff --git a/src/main/assets/icons/menu.png b/src/main/assets/icons/tray/menu.png similarity index 100% rename from src/main/assets/icons/menu.png rename to src/main/assets/icons/tray/menu.png diff --git a/src/main/assets/icons/menu@88.png b/src/main/assets/icons/tray/menu@88.png similarity index 100% rename from src/main/assets/icons/menu@88.png rename to src/main/assets/icons/tray/menu@88.png diff --git a/src/main/assets/icons/pause.png b/src/main/assets/icons/tray/pause.png similarity index 100% rename from src/main/assets/icons/pause.png rename to src/main/assets/icons/tray/pause.png diff --git a/src/main/assets/icons/play.png b/src/main/assets/icons/tray/play.png similarity index 100% rename from src/main/assets/icons/play.png rename to src/main/assets/icons/tray/play.png diff --git a/src/main/assets/icons/repeat.png b/src/main/assets/icons/tray/repeat.png similarity index 100% rename from src/main/assets/icons/repeat.png rename to src/main/assets/icons/tray/repeat.png diff --git a/src/main/assets/icons/right.png b/src/main/assets/icons/tray/right.png similarity index 100% rename from src/main/assets/icons/right.png rename to src/main/assets/icons/tray/right.png diff --git a/src/main/assets/icons/unlike.png b/src/main/assets/icons/tray/unlike.png similarity index 100% rename from src/main/assets/icons/unlike.png rename to src/main/assets/icons/tray/unlike.png diff --git a/src/main/index.ts b/src/main/index.ts index 8bd000b..d052fd6 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -14,6 +14,7 @@ import log from './log' import { initIpcMain } from './ipcMain' import { createTray, YPMTray } from './tray' import { IpcChannels } from '@/shared/IpcChannels' +import { createTaskbar, Thumbar } from './windowsTaskbar' const isWindows = process.platform === 'win32' const isMac = process.platform === 'darwin' @@ -32,6 +33,7 @@ interface TypedElectronStore { class Main { win: BrowserWindow | null = null tray: YPMTray | null = null + thumbar: Thumbar | null = null store = new Store({ defaults: { window: { @@ -62,7 +64,8 @@ class Main { this.handleAppEvents() this.handleWindowEvents() this.createTray() - initIpcMain(this.win, this.tray) + this.createThumbar() + initIpcMain(this.win, this.tray, this.thumbar) this.initDevTools() }) } @@ -93,6 +96,10 @@ class Main { } } + createThumbar() { + if (isWindows) this.thumbar = createTaskbar(this.win!) + } + createWindow() { const options: BrowserWindowConstructorOptions = { title: 'YesPlayMusic', @@ -150,7 +157,7 @@ class Main { handleAppEvents() { app.on('window-all-closed', () => { this.win = null - if (process.platform !== 'darwin') app.quit() + if (!isMac) app.quit() }) app.on('second-instance', () => { diff --git a/src/main/ipcMain.ts b/src/main/ipcMain.ts index a6cb2c3..6078a35 100644 --- a/src/main/ipcMain.ts +++ b/src/main/ipcMain.ts @@ -6,6 +6,7 @@ import log from './log' import fs from 'fs' import { APIs } from '../shared/CacheAPIs' import { YPMTray } from './tray' +import { Thumbar } from './windowsTaskbar' const on = ( channel: T, @@ -14,9 +15,14 @@ const on = ( ipcMain.on(channel, listener) } -export function initIpcMain(win: BrowserWindow | null, tray: YPMTray | null) { +export function initIpcMain( + win: BrowserWindow | null, + tray: YPMTray | null, + thumbar: Thumbar | null +) { initWindowIpcMain(win) initTrayIpcMain(tray) + initTaskbarIpcMain(thumbar) } /** @@ -45,7 +51,7 @@ function initWindowIpcMain(win: BrowserWindow | null) { function initTrayIpcMain(tray: YPMTray | null) { on(IpcChannels.SetTrayTooltip, (e, { text }) => tray?.setTooltip(text)) - on(IpcChannels.SetTrayLikeState, (e, { isLiked }) => + on(IpcChannels.Like, (e, { isLiked }) => tray?.setLikeState(isLiked) ) @@ -55,6 +61,15 @@ function initTrayIpcMain(tray: YPMTray | null) { on(IpcChannels.Repeat, (e, { mode }) => tray?.setRepeatMode(mode)) } +/** + * 处理需要thumbar对象的事件 + * @param {Thumbar} thumbar + */ +function initTaskbarIpcMain(thumbar: Thumbar | null) { + on(IpcChannels.Play, () => thumbar?.setPlayState(true)) + on(IpcChannels.Pause, () => thumbar?.setPlayState(false)) +} + /** * 清除API缓存 */ diff --git a/src/main/tray.ts b/src/main/tray.ts index cc46bd2..4191c7a 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -12,8 +12,8 @@ import { RepeatMode } from '@/shared/playerDataTypes' const iconDirRoot = process.env.NODE_ENV === 'development' - ? path.join(process.cwd(), './src/main/assets/icons') - : path.join(__dirname, './assets/icons') + ? path.join(process.cwd(), './src/main/assets/icons/tray') + : path.join(__dirname, './assets/icons/tray') enum MenuItemIDs { Play = 'play', diff --git a/src/main/windowsTaskbar.ts b/src/main/windowsTaskbar.ts new file mode 100644 index 0000000..ffac443 --- /dev/null +++ b/src/main/windowsTaskbar.ts @@ -0,0 +1,86 @@ +import { IpcChannels } from '@/shared/IpcChannels' +import { BrowserWindow, nativeImage, ThumbarButton } from 'electron' +import path from 'path' + +enum ItemKeys { + Play = 'play', + Pause = 'pause', + Previous = 'previous', + Next = 'next', +} + +type ThumbarButtonMap = Map + +const iconDirRoot = + process.env.NODE_ENV === 'development' + ? path.join(process.cwd(), './src/main/assets/icons/taskbar') + : path.join(__dirname, './assets/icons/taskbar') + +function createNativeImage(filename: string) { + return nativeImage.createFromPath(path.join(iconDirRoot, filename)) +} + +function createThumbarButtons(win: BrowserWindow): ThumbarButtonMap { + return new Map() + .set(ItemKeys.Play, { + click: () => win.webContents.send(IpcChannels.Play), + icon: createNativeImage('play.png'), + tooltip: '播放', + }) + .set(ItemKeys.Pause, { + click: () => win.webContents.send(IpcChannels.Pause), + icon: createNativeImage('pause.png'), + tooltip: '暂停', + }) + .set(ItemKeys.Previous, { + click: () => win.webContents.send(IpcChannels.Previous), + icon: createNativeImage('previous.png'), + tooltip: '上一首', + }) + .set(ItemKeys.Next, { + click: () => win.webContents.send(IpcChannels.Next), + icon: createNativeImage('next.png'), + tooltip: '下一首', + }) +} + +export interface Thumbar { + setPlayState(isPlaying: boolean): void +} + +class ThumbarImpl implements Thumbar { + private _win: BrowserWindow + private _buttons: ThumbarButtonMap + + private _playOrPause: ThumbarButton + private _previous: ThumbarButton + private _next: ThumbarButton + + constructor(win: BrowserWindow) { + this._win = win + this._buttons = createThumbarButtons(win) + + this._playOrPause = this._buttons.get(ItemKeys.Play)! + this._previous = this._buttons.get(ItemKeys.Previous)! + this._next = this._buttons.get(ItemKeys.Next)! + } + + private _updateThumbarButtons(clear: boolean) { + this._win.setThumbarButtons( + clear + ? [] + : [this._previous, this._playOrPause, this._next] + ) + } + + setPlayState(isPlaying: boolean) { + this._playOrPause = this._buttons.get( + isPlaying ? ItemKeys.Pause : ItemKeys.Play + )! + this._updateThumbarButtons(false) + } +} + +export function createTaskbar(win: BrowserWindow): Thumbar { + return new ThumbarImpl(win) +} diff --git a/src/renderer/IpcRendererReact.tsx b/src/renderer/IpcRendererReact.tsx index fcf5c80..52a880c 100644 --- a/src/renderer/IpcRendererReact.tsx +++ b/src/renderer/IpcRendererReact.tsx @@ -33,7 +33,7 @@ const IpcRendererReact = () => { }, [track]) useEffect(() => { - window.ipcRenderer?.send(IpcChannels.SetTrayLikeState, { + window.ipcRenderer?.send(IpcChannels.Like, { isLiked: userLikedSongs?.ids?.includes(track?.id ?? 0) ?? false, }) }, [userLikedSongs, track]) @@ -46,6 +46,13 @@ const IpcRendererReact = () => { setIsPlaying(playing) }, [state]) + useEffectOnce(() => { + // 用于显示 windows taskbar buttons + if (playerSnapshot.track?.id) { + window.ipcRenderer?.send(IpcChannels.Pause) + } + }) + return <> } diff --git a/src/shared/IpcChannels.ts b/src/shared/IpcChannels.ts index 98400e7..c2f09e6 100644 --- a/src/shared/IpcChannels.ts +++ b/src/shared/IpcChannels.ts @@ -11,7 +11,6 @@ export const enum IpcChannels { DevDbExportJson = 'dev-db-export-json', CacheCoverColor = 'cache-cover-color', SetTrayTooltip = 'set-tray-tooltip', - SetTrayLikeState = 'set-tray-like-state', // 准备三个播放相关channel, 为 mpris 预留接口 Play = 'play', Pause = 'pause', @@ -41,9 +40,6 @@ export interface IpcChannelsParams { [IpcChannels.SetTrayTooltip]: { text: string } - [IpcChannels.SetTrayLikeState]: { - isLiked: boolean - } [IpcChannels.Play]: void [IpcChannels.Pause]: void [IpcChannels.PlayOrPause]: void @@ -68,7 +64,6 @@ export interface IpcChannelsReturns { [IpcChannels.DevDbExportJson]: void [IpcChannels.CacheCoverColor]: void [IpcChannels.SetTrayTooltip]: void - [IpcChannels.SetTrayLikeState]: void [IpcChannels.Play]: void [IpcChannels.Pause]: void [IpcChannels.PlayOrPause]: void