mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-13 22:20:51 +08:00
fix(mpris): 修复 MPRIS 模块多项安全和性能问题
- 将 fix-sandbox.js 从 postinstall 移除,避免 npm install 时执行 sudo - 修复 play/pause/stop 事件语义错误,不再全部映射到 togglePlay - 缓存平台信息避免 sendSync 阻塞渲染进程 - 修复 cleanupAppShortcuts 中缺少 MPRIS 监听器清理导致的事件泄漏 - destroyMpris 中添加 IPC 监听器清理 - 清理冗余调试日志,安全加载 dbus-native 模块 - 添加 mpris-service 类型声明解决跨平台类型检查问题
This commit is contained in:
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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',
|
||||
|
||||
23
src/main/types/mpris-service.d.ts
vendored
Normal file
23
src/main/types/mpris-service.d.ts
vendored
Normal file
@@ -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<string, any>;
|
||||
position: number;
|
||||
getPosition: () => number;
|
||||
seeked(position: number): void;
|
||||
objectPath(path: string): string;
|
||||
quit(): void;
|
||||
}
|
||||
|
||||
function Player(options: PlayerOptions): Player;
|
||||
export = Player;
|
||||
}
|
||||
@@ -54,6 +54,9 @@ export let artistList: ComputedRef<Artist[]>;
|
||||
|
||||
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];
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user