mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-27 18:37:22 +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:
+17
-5
@@ -1,9 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* 修复 Linux 下 Electron sandbox 权限问题
|
||||||
|
* chrome-sandbox 需要 root 拥有且权限为 4755
|
||||||
|
*
|
||||||
|
* 注意:此脚本需要 sudo 权限,仅在 CI 环境或手动执行时使用
|
||||||
|
* 用法:sudo node fix-sandbox.js
|
||||||
|
*/
|
||||||
const { execSync } = require('child_process');
|
const { execSync } = require('child_process');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
if (process.platform === 'linux') {
|
if (process.platform === 'linux') {
|
||||||
// You need to make sure that
|
const sandboxPath = path.resolve('./node_modules/electron/dist/chrome-sandbox');
|
||||||
// /home/runner/work/VutronMusic/VutronMusic/node_modules/electron/dist/chrome-sandbox
|
if (fs.existsSync(sandboxPath)) {
|
||||||
// is owned by root and has mode 4755.
|
execSync(`sudo chown root:root ${sandboxPath}`);
|
||||||
execSync('sudo chown root:root ./node_modules/electron/dist/chrome-sandbox');
|
execSync(`sudo chmod 4755 ${sandboxPath}`);
|
||||||
execSync('sudo chmod 4755 ./node_modules/electron/dist/chrome-sandbox');
|
console.log('[fix-sandbox] chrome-sandbox permissions fixed');
|
||||||
|
} else {
|
||||||
|
console.log('[fix-sandbox] chrome-sandbox not found, skipping');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -17,7 +17,8 @@
|
|||||||
"dev": "electron-vite dev",
|
"dev": "electron-vite dev",
|
||||||
"dev:web": "vite dev",
|
"dev:web": "vite dev",
|
||||||
"build": "electron-vite build",
|
"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:unpack": "npm run build && electron-builder --dir",
|
||||||
"build:win": "npm run build && electron-builder --win --publish never",
|
"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",
|
"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",
|
||||||
|
|||||||
+39
-38
@@ -1,6 +1,12 @@
|
|||||||
import { app, BrowserWindow, ipcMain } from 'electron';
|
import { app, BrowserWindow, ipcMain } from 'electron';
|
||||||
import Player from 'mpris-service';
|
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 {
|
interface SongInfo {
|
||||||
id?: number | string;
|
id?: number | string;
|
||||||
@@ -27,11 +33,14 @@ let currentPosition = 0;
|
|||||||
let trayLyricIface: any = null;
|
let trayLyricIface: any = null;
|
||||||
let trayLyricBus: 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) {
|
export function initializeMpris(mainWindowRef: BrowserWindow) {
|
||||||
if (process.platform !== 'linux') return;
|
if (process.platform !== 'linux') return;
|
||||||
|
|
||||||
if (mprisPlayer) {
|
if (mprisPlayer) {
|
||||||
console.log('[MPRIS] Already initialized, skipping');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,13 +88,13 @@ export function initializeMpris(mainWindowRef: BrowserWindow) {
|
|||||||
|
|
||||||
mprisPlayer.on('pause', () => {
|
mprisPlayer.on('pause', () => {
|
||||||
if (mainWindow) {
|
if (mainWindow) {
|
||||||
mainWindow.webContents.send('global-shortcut', 'togglePlay');
|
mainWindow.webContents.send('mpris-pause');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
mprisPlayer.on('play', () => {
|
mprisPlayer.on('play', () => {
|
||||||
if (mainWindow) {
|
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', () => {
|
mprisPlayer.on('stop', () => {
|
||||||
if (mainWindow) {
|
if (mainWindow) {
|
||||||
mainWindow.webContents.send('global-shortcut', 'togglePlay');
|
mainWindow.webContents.send('mpris-pause');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
mprisPlayer.getPosition = (): number => {
|
mprisPlayer.getPosition = (): number => {
|
||||||
console.log('[MPRIS] getPosition called, returning:', currentPosition);
|
|
||||||
return 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;
|
currentPosition = position * 1000 * 1000;
|
||||||
mprisPlayer.seeked(position * 1000 * 1000);
|
if (mprisPlayer) {
|
||||||
mprisPlayer.getPosition = () => position * 1000 * 1000;
|
mprisPlayer.seeked(position * 1000 * 1000);
|
||||||
mprisPlayer.position = 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);
|
sendTrayLyric(lrcObj);
|
||||||
});
|
};
|
||||||
|
ipcMain.on('tray-lyric-update', onTrayLyricUpdate);
|
||||||
|
|
||||||
initTrayLyric();
|
initTrayLyric();
|
||||||
|
|
||||||
@@ -176,6 +188,14 @@ export function updateMprisPosition(position: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function destroyMpris() {
|
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) {
|
if (mprisPlayer) {
|
||||||
mprisPlayer.quit();
|
mprisPlayer.quit();
|
||||||
mprisPlayer = null;
|
mprisPlayer = null;
|
||||||
@@ -183,25 +203,17 @@ export function destroyMpris() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function initTrayLyric() {
|
function initTrayLyric() {
|
||||||
if (process.platform !== 'linux') {
|
if (process.platform !== 'linux' || !dbusModule) return;
|
||||||
console.log('[TrayLyric] Not Linux, skipping');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('[TrayLyric] Initializing...');
|
|
||||||
|
|
||||||
const serviceName = 'org.gnome.Shell.TrayLyric';
|
const serviceName = 'org.gnome.Shell.TrayLyric';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const sessionBus = dbus.sessionBus({});
|
const sessionBus = dbusModule.sessionBus({});
|
||||||
trayLyricBus = sessionBus;
|
trayLyricBus = sessionBus;
|
||||||
console.log('[TrayLyric] Session bus created, type:');
|
|
||||||
|
|
||||||
// 使用 invoke 方法调用 D-Bus 方法
|
|
||||||
const dbusPath = '/org/freedesktop/DBus';
|
const dbusPath = '/org/freedesktop/DBus';
|
||||||
const dbusInterface = 'org.freedesktop.DBus';
|
const dbusInterface = 'org.freedesktop.DBus';
|
||||||
|
|
||||||
// 先尝试直接获取接口并使用 signals
|
|
||||||
sessionBus.invoke(
|
sessionBus.invoke(
|
||||||
{
|
{
|
||||||
path: dbusPath,
|
path: dbusPath,
|
||||||
@@ -212,26 +224,19 @@ function initTrayLyric() {
|
|||||||
body: [serviceName]
|
body: [serviceName]
|
||||||
},
|
},
|
||||||
(err: any, result: any) => {
|
(err: any, result: any) => {
|
||||||
console.log('[TrayLyric] GetNameOwner result:', err, result);
|
|
||||||
if (err || !result) {
|
if (err || !result) {
|
||||||
console.log('[TrayLyric] Service not running yet');
|
console.log('[TrayLyric] Service not running');
|
||||||
} else {
|
} else {
|
||||||
console.log('[TrayLyric] Service is running, owner:', result[0]);
|
|
||||||
onServiceAvailable();
|
onServiceAvailable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[TrayLyric] Exception during init:', err);
|
console.error('[TrayLyric] Failed to init:', err);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onServiceAvailable() {
|
function onServiceAvailable() {
|
||||||
console.log('[TrayLyric] onServiceAvailable called');
|
if (!trayLyricBus) return;
|
||||||
if (!trayLyricBus) {
|
|
||||||
console.log('[TrayLyric] Bus not available');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log('[TrayLyric] Getting service interface...');
|
|
||||||
const path = '/' + serviceName.replace(/\./g, '/');
|
const path = '/' + serviceName.replace(/\./g, '/');
|
||||||
trayLyricBus.getService(serviceName).getInterface(path, serviceName, (err: any, iface: any) => {
|
trayLyricBus.getService(serviceName).getInterface(path, serviceName, (err: any, iface: any) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
@@ -245,12 +250,8 @@ function initTrayLyric() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function sendTrayLyric(lrcObj: string) {
|
function sendTrayLyric(lrcObj: string) {
|
||||||
if (!trayLyricIface || !trayLyricBus) {
|
if (!trayLyricIface || !trayLyricBus) return;
|
||||||
console.log('[TrayLyric] Interface or bus not ready, skipping');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用 invoke 方法调用 D-Bus 方法
|
|
||||||
trayLyricBus.invoke(
|
trayLyricBus.invoke(
|
||||||
{
|
{
|
||||||
path: '/org/gnome/Shell/TrayLyric',
|
path: '/org/gnome/Shell/TrayLyric',
|
||||||
|
|||||||
Vendored
+23
@@ -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;
|
let lastIndex = -1;
|
||||||
|
|
||||||
|
// 缓存平台信息,避免每次歌词变化时同步 IPC 调用
|
||||||
|
const cachedPlatform = isElectron ? window.electron.ipcRenderer.sendSync('get-platform') : 'web';
|
||||||
|
|
||||||
export const musicDB = await useIndexedDB(
|
export const musicDB = await useIndexedDB(
|
||||||
'musicDB',
|
'musicDB',
|
||||||
[
|
[
|
||||||
@@ -831,14 +834,7 @@ export const sendLyricToWin = () => {
|
|||||||
|
|
||||||
// 发送歌词到系统托盘歌词(TrayLyric)
|
// 发送歌词到系统托盘歌词(TrayLyric)
|
||||||
const sendTrayLyric = (index: number) => {
|
const sendTrayLyric = (index: number) => {
|
||||||
const platformValue = window.electron.ipcRenderer.sendSync('get-platform');
|
if (!isElectron || cachedPlatform !== 'linux') return;
|
||||||
console.log(
|
|
||||||
'[TrayLyric] sendTrayLyric called, isElectron:',
|
|
||||||
isElectron,
|
|
||||||
'platform:',
|
|
||||||
platformValue
|
|
||||||
);
|
|
||||||
if (!isElectron || platformValue !== 'linux') return;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const lyric = lrcArray.value[index];
|
const lyric = lrcArray.value[index];
|
||||||
|
|||||||
@@ -38,15 +38,23 @@ const onUpdateAppShortcuts = (_event: unknown, shortcuts: unknown) => {
|
|||||||
updateAppShortcuts(shortcuts);
|
updateAppShortcuts(shortcuts);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onMprisSeek = (_event: unknown, position: number) => {
|
const onMprisSeekOrSetPosition = (_event: unknown, position: number) => {
|
||||||
if (audioService) {
|
if (audioService) {
|
||||||
audioService.seek(position);
|
audioService.seek(position);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onMprisSetPosition = (_event: unknown, position: number) => {
|
const onMprisPlay = async () => {
|
||||||
if (audioService) {
|
const playerStore = usePlayerStore();
|
||||||
audioService.seek(position);
|
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('global-shortcut', onGlobalShortcut);
|
||||||
window.electron.ipcRenderer.on('update-app-shortcuts', onUpdateAppShortcuts);
|
window.electron.ipcRenderer.on('update-app-shortcuts', onUpdateAppShortcuts);
|
||||||
window.electron.ipcRenderer.on('mpris-seek', onMprisSeek);
|
window.electron.ipcRenderer.on('mpris-seek', onMprisSeekOrSetPosition);
|
||||||
window.electron.ipcRenderer.on('mpris-set-position', onMprisSetPosition);
|
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');
|
const storedShortcuts = window.electron.ipcRenderer.sendSync('get-store-value', 'shortcuts');
|
||||||
updateAppShortcuts(storedShortcuts);
|
updateAppShortcuts(storedShortcuts);
|
||||||
@@ -226,6 +236,10 @@ export function cleanupAppShortcuts() {
|
|||||||
|
|
||||||
window.electron.ipcRenderer.removeListener('global-shortcut', onGlobalShortcut);
|
window.electron.ipcRenderer.removeListener('global-shortcut', onGlobalShortcut);
|
||||||
window.electron.ipcRenderer.removeListener('update-app-shortcuts', onUpdateAppShortcuts);
|
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);
|
document.removeEventListener('keydown', handleKeyDown);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user