diff --git a/fix-sandbox.js b/fix-sandbox.js index cca78cd..2e99251 100644 --- a/fix-sandbox.js +++ b/fix-sandbox.js @@ -1,9 +1,21 @@ +/** + * 修复 Linux 下 Electron sandbox 权限问题 + * chrome-sandbox 需要 root 拥有且权限为 4755 + * + * 注意:此脚本需要 sudo 权限,仅在 CI 环境或手动执行时使用 + * 用法:sudo node fix-sandbox.js + */ const { execSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); if (process.platform === 'linux') { - // You need to make sure that - // /home/runner/work/VutronMusic/VutronMusic/node_modules/electron/dist/chrome-sandbox - // is owned by root and has mode 4755. - execSync('sudo chown root:root ./node_modules/electron/dist/chrome-sandbox'); - execSync('sudo chmod 4755 ./node_modules/electron/dist/chrome-sandbox'); + const sandboxPath = path.resolve('./node_modules/electron/dist/chrome-sandbox'); + if (fs.existsSync(sandboxPath)) { + execSync(`sudo chown root:root ${sandboxPath}`); + execSync(`sudo chmod 4755 ${sandboxPath}`); + console.log('[fix-sandbox] chrome-sandbox permissions fixed'); + } else { + console.log('[fix-sandbox] chrome-sandbox not found, skipping'); + } } diff --git a/package.json b/package.json index e5419c5..2483a87 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ "dev": "electron-vite dev", "dev:web": "vite dev", "build": "electron-vite build", - "postinstall": "node fix-sandbox.js && electron-builder install-app-deps", + "postinstall": "electron-builder install-app-deps", + "fix-sandbox": "node fix-sandbox.js", "build:unpack": "npm run build && electron-builder --dir", "build:win": "npm run build && electron-builder --win --publish never", "build:mac": "npm run build && electron-builder --mac --x64 --publish never && cp dist/latest-mac.yml dist/latest-mac-x64.yml && electron-builder --mac --arm64 --publish never && cp dist/latest-mac.yml dist/latest-mac-arm64.yml && node scripts/merge_latest_mac_yml.mjs dist/latest-mac-x64.yml dist/latest-mac-arm64.yml dist/latest-mac.yml", diff --git a/src/main/modules/mpris.ts b/src/main/modules/mpris.ts index 92a4903..f48e15c 100644 --- a/src/main/modules/mpris.ts +++ b/src/main/modules/mpris.ts @@ -1,6 +1,12 @@ import { app, BrowserWindow, ipcMain } from 'electron'; import Player from 'mpris-service'; -const dbus = require('@httptoolkit/dbus-native'); + +let dbusModule: any; +try { + dbusModule = require('@httptoolkit/dbus-native'); +} catch { + // dbus-native 不可用(非 Linux 环境) +} interface SongInfo { id?: number | string; @@ -27,11 +33,14 @@ let currentPosition = 0; let trayLyricIface: any = null; let trayLyricBus: any = null; +// 保存 IPC 处理函数引用,用于清理 +let onPositionUpdate: ((event: any, position: number) => void) | null = null; +let onTrayLyricUpdate: ((event: any, lrcObj: string) => void) | null = null; + export function initializeMpris(mainWindowRef: BrowserWindow) { if (process.platform !== 'linux') return; if (mprisPlayer) { - console.log('[MPRIS] Already initialized, skipping'); return; } @@ -79,13 +88,13 @@ export function initializeMpris(mainWindowRef: BrowserWindow) { mprisPlayer.on('pause', () => { if (mainWindow) { - mainWindow.webContents.send('global-shortcut', 'togglePlay'); + mainWindow.webContents.send('mpris-pause'); } }); mprisPlayer.on('play', () => { if (mainWindow) { - mainWindow.webContents.send('global-shortcut', 'togglePlay'); + mainWindow.webContents.send('mpris-play'); } }); @@ -97,12 +106,11 @@ export function initializeMpris(mainWindowRef: BrowserWindow) { mprisPlayer.on('stop', () => { if (mainWindow) { - mainWindow.webContents.send('global-shortcut', 'togglePlay'); + mainWindow.webContents.send('mpris-pause'); } }); mprisPlayer.getPosition = (): number => { - console.log('[MPRIS] getPosition called, returning:', currentPosition); return currentPosition; }; @@ -119,16 +127,20 @@ export function initializeMpris(mainWindowRef: BrowserWindow) { } }); - ipcMain.on('mpris-position-update', (_, position: number) => { + onPositionUpdate = (_, position: number) => { currentPosition = position * 1000 * 1000; - mprisPlayer.seeked(position * 1000 * 1000); - mprisPlayer.getPosition = () => position * 1000 * 1000; - mprisPlayer.position = position * 1000 * 1000; - }); + if (mprisPlayer) { + mprisPlayer.seeked(position * 1000 * 1000); + mprisPlayer.getPosition = () => position * 1000 * 1000; + mprisPlayer.position = position * 1000 * 1000; + } + }; + ipcMain.on('mpris-position-update', onPositionUpdate); - ipcMain.on('tray-lyric-update', async (_, lrcObj: string) => { + onTrayLyricUpdate = (_, lrcObj: string) => { sendTrayLyric(lrcObj); - }); + }; + ipcMain.on('tray-lyric-update', onTrayLyricUpdate); initTrayLyric(); @@ -176,6 +188,14 @@ export function updateMprisPosition(position: number) { } export function destroyMpris() { + if (onPositionUpdate) { + ipcMain.removeListener('mpris-position-update', onPositionUpdate); + onPositionUpdate = null; + } + if (onTrayLyricUpdate) { + ipcMain.removeListener('tray-lyric-update', onTrayLyricUpdate); + onTrayLyricUpdate = null; + } if (mprisPlayer) { mprisPlayer.quit(); mprisPlayer = null; @@ -183,25 +203,17 @@ export function destroyMpris() { } function initTrayLyric() { - if (process.platform !== 'linux') { - console.log('[TrayLyric] Not Linux, skipping'); - return; - } - - console.log('[TrayLyric] Initializing...'); + if (process.platform !== 'linux' || !dbusModule) return; const serviceName = 'org.gnome.Shell.TrayLyric'; try { - const sessionBus = dbus.sessionBus({}); + const sessionBus = dbusModule.sessionBus({}); trayLyricBus = sessionBus; - console.log('[TrayLyric] Session bus created, type:'); - // 使用 invoke 方法调用 D-Bus 方法 const dbusPath = '/org/freedesktop/DBus'; const dbusInterface = 'org.freedesktop.DBus'; - // 先尝试直接获取接口并使用 signals sessionBus.invoke( { path: dbusPath, @@ -212,26 +224,19 @@ function initTrayLyric() { body: [serviceName] }, (err: any, result: any) => { - console.log('[TrayLyric] GetNameOwner result:', err, result); if (err || !result) { - console.log('[TrayLyric] Service not running yet'); + console.log('[TrayLyric] Service not running'); } else { - console.log('[TrayLyric] Service is running, owner:', result[0]); onServiceAvailable(); } } ); } catch (err) { - console.error('[TrayLyric] Exception during init:', err); + console.error('[TrayLyric] Failed to init:', err); } function onServiceAvailable() { - console.log('[TrayLyric] onServiceAvailable called'); - if (!trayLyricBus) { - console.log('[TrayLyric] Bus not available'); - return; - } - console.log('[TrayLyric] Getting service interface...'); + if (!trayLyricBus) return; const path = '/' + serviceName.replace(/\./g, '/'); trayLyricBus.getService(serviceName).getInterface(path, serviceName, (err: any, iface: any) => { if (err) { @@ -245,12 +250,8 @@ function initTrayLyric() { } function sendTrayLyric(lrcObj: string) { - if (!trayLyricIface || !trayLyricBus) { - console.log('[TrayLyric] Interface or bus not ready, skipping'); - return; - } + if (!trayLyricIface || !trayLyricBus) return; - // 使用 invoke 方法调用 D-Bus 方法 trayLyricBus.invoke( { path: '/org/gnome/Shell/TrayLyric', diff --git a/src/main/types/mpris-service.d.ts b/src/main/types/mpris-service.d.ts new file mode 100644 index 0000000..bb92c97 --- /dev/null +++ b/src/main/types/mpris-service.d.ts @@ -0,0 +1,23 @@ +declare module 'mpris-service' { + interface PlayerOptions { + name: string; + identity: string; + supportedUriSchemes?: string[]; + supportedMimeTypes?: string[]; + supportedInterfaces?: string[]; + } + + interface Player { + on(event: string, callback: (...args: any[]) => void): void; + playbackStatus: string; + metadata: Record; + position: number; + getPosition: () => number; + seeked(position: number): void; + objectPath(path: string): string; + quit(): void; + } + + function Player(options: PlayerOptions): Player; + export = Player; +} diff --git a/src/renderer/hooks/MusicHook.ts b/src/renderer/hooks/MusicHook.ts index 06d6d36..49a7b37 100644 --- a/src/renderer/hooks/MusicHook.ts +++ b/src/renderer/hooks/MusicHook.ts @@ -54,6 +54,9 @@ export let artistList: ComputedRef; let lastIndex = -1; +// 缓存平台信息,避免每次歌词变化时同步 IPC 调用 +const cachedPlatform = isElectron ? window.electron.ipcRenderer.sendSync('get-platform') : 'web'; + export const musicDB = await useIndexedDB( 'musicDB', [ @@ -831,14 +834,7 @@ export const sendLyricToWin = () => { // 发送歌词到系统托盘歌词(TrayLyric) const sendTrayLyric = (index: number) => { - const platformValue = window.electron.ipcRenderer.sendSync('get-platform'); - console.log( - '[TrayLyric] sendTrayLyric called, isElectron:', - isElectron, - 'platform:', - platformValue - ); - if (!isElectron || platformValue !== 'linux') return; + if (!isElectron || cachedPlatform !== 'linux') return; try { const lyric = lrcArray.value[index]; diff --git a/src/renderer/utils/appShortcuts.ts b/src/renderer/utils/appShortcuts.ts index a85cbeb..819a3c9 100644 --- a/src/renderer/utils/appShortcuts.ts +++ b/src/renderer/utils/appShortcuts.ts @@ -38,15 +38,23 @@ const onUpdateAppShortcuts = (_event: unknown, shortcuts: unknown) => { updateAppShortcuts(shortcuts); }; -const onMprisSeek = (_event: unknown, position: number) => { +const onMprisSeekOrSetPosition = (_event: unknown, position: number) => { if (audioService) { audioService.seek(position); } }; -const onMprisSetPosition = (_event: unknown, position: number) => { - if (audioService) { - audioService.seek(position); +const onMprisPlay = async () => { + const playerStore = usePlayerStore(); + if (!playerStore.play && playerStore.playMusic?.id) { + await playerStore.setPlay({ ...playerStore.playMusic }); + } +}; + +const onMprisPause = async () => { + const playerStore = usePlayerStore(); + if (playerStore.play) { + await playerStore.handlePause(); } }; @@ -205,8 +213,10 @@ export function initAppShortcuts() { window.electron.ipcRenderer.on('global-shortcut', onGlobalShortcut); window.electron.ipcRenderer.on('update-app-shortcuts', onUpdateAppShortcuts); - window.electron.ipcRenderer.on('mpris-seek', onMprisSeek); - window.electron.ipcRenderer.on('mpris-set-position', onMprisSetPosition); + window.electron.ipcRenderer.on('mpris-seek', onMprisSeekOrSetPosition); + window.electron.ipcRenderer.on('mpris-set-position', onMprisSeekOrSetPosition); + window.electron.ipcRenderer.on('mpris-play', onMprisPlay); + window.electron.ipcRenderer.on('mpris-pause', onMprisPause); const storedShortcuts = window.electron.ipcRenderer.sendSync('get-store-value', 'shortcuts'); updateAppShortcuts(storedShortcuts); @@ -226,6 +236,10 @@ export function cleanupAppShortcuts() { window.electron.ipcRenderer.removeListener('global-shortcut', onGlobalShortcut); window.electron.ipcRenderer.removeListener('update-app-shortcuts', onUpdateAppShortcuts); + window.electron.ipcRenderer.removeListener('mpris-seek', onMprisSeekOrSetPosition); + window.electron.ipcRenderer.removeListener('mpris-set-position', onMprisSeekOrSetPosition); + window.electron.ipcRenderer.removeListener('mpris-play', onMprisPlay); + window.electron.ipcRenderer.removeListener('mpris-pause', onMprisPause); document.removeEventListener('keydown', handleKeyDown); }