From b8228844dfd863b73733b3adc435769b70a1a371 Mon Sep 17 00:00:00 2001 From: Steffen Martinsen Date: Sat, 31 Jan 2026 03:01:02 +0700 Subject: [PATCH] feat: Add support for player controls in macOS dock menu (#1627) * Added simple macOS dock menu similar to tray menu * Enhanced and moved dock menu to darwin folder and enabled mpris on macOS to support play/pause state * Added missing property sortName to silence TS error --- src/main/features/darwin/dock-menu.ts | 57 +++++++++++++++++++ src/main/features/darwin/index.ts | 1 + .../features/player/hooks/use-mpris.ts | 5 +- 3 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 src/main/features/darwin/dock-menu.ts diff --git a/src/main/features/darwin/dock-menu.ts b/src/main/features/darwin/dock-menu.ts new file mode 100644 index 000000000..2af926711 --- /dev/null +++ b/src/main/features/darwin/dock-menu.ts @@ -0,0 +1,57 @@ +import { app, ipcMain, Menu } from 'electron'; + +import { getMainWindow } from '/@/main/index'; +import { PlayerStatus } from '/@/shared/types/types'; + +let currentStatus: PlayerStatus = PlayerStatus.PAUSED; + +const updateDockMenu = () => { + if (!app.dock) return; + + const isPlaying = currentStatus === PlayerStatus.PLAYING; + + const dockMenu = Menu.buildFromTemplate([ + { + click: () => { + getMainWindow()?.webContents.send('renderer-player-play-pause'); + }, + label: isPlaying ? 'Pause' : 'Play', + }, + { + type: 'separator', + }, + { + click: () => { + getMainWindow()?.webContents.send('renderer-player-next'); + }, + label: 'Next', + }, + { + click: () => { + getMainWindow()?.webContents.send('renderer-player-previous'); + }, + label: 'Previous', + }, + { + type: 'separator', + }, + { + click: () => { + getMainWindow()?.webContents.send('renderer-player-stop'); + }, + label: 'Stop', + }, + ]); + + app.dock.setMenu(dockMenu); +}; + +ipcMain.on('update-playback', (_event, status: PlayerStatus) => { + currentStatus = status; + updateDockMenu(); +}); + +// Initialize dock menu after app is ready +app.whenReady().then(() => { + updateDockMenu(); +}); diff --git a/src/main/features/darwin/index.ts b/src/main/features/darwin/index.ts index e69de29bb..51e6fcee2 100644 --- a/src/main/features/darwin/index.ts +++ b/src/main/features/darwin/index.ts @@ -0,0 +1 @@ +import './dock-menu'; diff --git a/src/renderer/features/player/hooks/use-mpris.ts b/src/renderer/features/player/hooks/use-mpris.ts index 9d9958f64..98499caf0 100644 --- a/src/renderer/features/player/hooks/use-mpris.ts +++ b/src/renderer/features/player/hooks/use-mpris.ts @@ -13,7 +13,7 @@ import { PlayerShuffle, ServerType } from '/@/shared/types/types'; const ipc = isElectron() ? window.api.ipc : null; const utils = isElectron() ? window.api.utils : null; -const mpris = isElectron() && utils?.isLinux() ? window.api.mpris : null; +const mpris = isElectron() && (utils?.isLinux() || utils?.isMacOS()) ? window.api.mpris : null; export const useMPRIS = () => { const player = usePlayerStore(); @@ -102,6 +102,7 @@ export const useMPRIS = () => { releaseYear: null, sampleRate: null, size: 0, + sortName: title, tags: null, trackNumber: 0, trackSubtitle: null, @@ -221,7 +222,7 @@ const MPRISHookInner = () => { export const MPRISHook = () => { const isElectronEnv = isElectron(); const utils = isElectronEnv ? window.api.utils : null; - const mpris = isElectronEnv && utils?.isLinux() ? window.api.mpris : null; + const mpris = isElectronEnv && (utils?.isLinux() || utils?.isMacOS()) ? window.api.mpris : null; if (mpris === null) { return null;