mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-28 02:47:22 +08:00
1. 实现linux下的mpris和gnome状态栏歌词功能
This commit is contained in:
@@ -36,6 +36,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron-toolkit/preload": "^3.0.2",
|
"@electron-toolkit/preload": "^3.0.2",
|
||||||
"@electron-toolkit/utils": "^4.0.0",
|
"@electron-toolkit/utils": "^4.0.0",
|
||||||
|
"@httptoolkit/dbus-native": "^0.1.5",
|
||||||
"@unblockneteasemusic/server": "^0.27.10",
|
"@unblockneteasemusic/server": "^0.27.10",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
@@ -49,6 +50,7 @@
|
|||||||
"form-data": "^4.0.5",
|
"form-data": "^4.0.5",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"jsencrypt": "^3.5.4",
|
"jsencrypt": "^3.5.4",
|
||||||
|
"mpris-service": "^2.1.2",
|
||||||
"music-metadata": "^11.10.3",
|
"music-metadata": "^11.10.3",
|
||||||
"netease-cloud-music-api-alger": "^4.30.0",
|
"netease-cloud-music-api-alger": "^4.30.0",
|
||||||
"node-fetch": "^2.7.0",
|
"node-fetch": "^2.7.0",
|
||||||
@@ -221,5 +223,9 @@
|
|||||||
"electron",
|
"electron",
|
||||||
"esbuild"
|
"esbuild"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"jsbi": "^4.3.2",
|
||||||
|
"x11": "^2.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { initializeFonts } from './modules/fonts';
|
|||||||
import { initializeLocalMusicScanner } from './modules/localMusicScanner';
|
import { initializeLocalMusicScanner } from './modules/localMusicScanner';
|
||||||
import { initializeLoginWindow } from './modules/loginWindow';
|
import { initializeLoginWindow } from './modules/loginWindow';
|
||||||
import { initLxMusicHttp } from './modules/lxMusicHttp';
|
import { initLxMusicHttp } from './modules/lxMusicHttp';
|
||||||
|
import { initializeMpris, updateMprisCurrentSong, updateMprisPlayState } from './modules/mpris';
|
||||||
import { initializeOtherApi } from './modules/otherApi';
|
import { initializeOtherApi } from './modules/otherApi';
|
||||||
import { initializeRemoteControl } from './modules/remoteControl';
|
import { initializeRemoteControl } from './modules/remoteControl';
|
||||||
import { initializeShortcuts } from './modules/shortcuts';
|
import { initializeShortcuts } from './modules/shortcuts';
|
||||||
@@ -82,6 +83,9 @@ function initialize(configStore: any) {
|
|||||||
// 初始化远程控制服务
|
// 初始化远程控制服务
|
||||||
initializeRemoteControl(mainWindow);
|
initializeRemoteControl(mainWindow);
|
||||||
|
|
||||||
|
// 初始化 MPRIS 服务 (Linux)
|
||||||
|
initializeMpris(mainWindow);
|
||||||
|
|
||||||
// 初始化更新处理程序
|
// 初始化更新处理程序
|
||||||
setupUpdateHandlers(mainWindow);
|
setupUpdateHandlers(mainWindow);
|
||||||
}
|
}
|
||||||
@@ -92,6 +96,11 @@ const isSingleInstance = app.requestSingleInstanceLock();
|
|||||||
if (!isSingleInstance) {
|
if (!isSingleInstance) {
|
||||||
app.quit();
|
app.quit();
|
||||||
} else {
|
} else {
|
||||||
|
// 禁用 Chromium 内置的 MediaSession MPRIS 服务,避免重复显示
|
||||||
|
if (process.platform === 'linux') {
|
||||||
|
app.commandLine.appendSwitch('disable-features', 'MediaSessionService');
|
||||||
|
}
|
||||||
|
|
||||||
// 在应用准备就绪前初始化GPU加速设置
|
// 在应用准备就绪前初始化GPU加速设置
|
||||||
// 必须在 app.ready 之前调用 disableHardwareAcceleration
|
// 必须在 app.ready 之前调用 disableHardwareAcceleration
|
||||||
try {
|
try {
|
||||||
@@ -171,11 +180,13 @@ if (!isSingleInstance) {
|
|||||||
// 监听播放状态变化
|
// 监听播放状态变化
|
||||||
ipcMain.on('update-play-state', (_, playing: boolean) => {
|
ipcMain.on('update-play-state', (_, playing: boolean) => {
|
||||||
updatePlayState(playing);
|
updatePlayState(playing);
|
||||||
|
updateMprisPlayState(playing);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听当前歌曲变化
|
// 监听当前歌曲变化
|
||||||
ipcMain.on('update-current-song', (_, song: any) => {
|
ipcMain.on('update-current-song', (_, song: any) => {
|
||||||
updateCurrentSong(song);
|
updateCurrentSong(song);
|
||||||
|
updateMprisCurrentSong(song);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 所有窗口关闭时的处理
|
// 所有窗口关闭时的处理
|
||||||
|
|||||||
@@ -0,0 +1,269 @@
|
|||||||
|
import { app, BrowserWindow, ipcMain } from 'electron';
|
||||||
|
import Player from 'mpris-service';
|
||||||
|
const dbus = require('@httptoolkit/dbus-native');
|
||||||
|
|
||||||
|
interface SongInfo {
|
||||||
|
id?: number | string;
|
||||||
|
name: string;
|
||||||
|
picUrl?: string;
|
||||||
|
ar?: Array<{ name: string }>;
|
||||||
|
artists?: Array<{ name: string }>;
|
||||||
|
al?: { name: string };
|
||||||
|
album?: { name: string };
|
||||||
|
duration?: number;
|
||||||
|
dt?: number;
|
||||||
|
song?: {
|
||||||
|
artists?: Array<{ name: string }>;
|
||||||
|
album?: { name: string };
|
||||||
|
duration?: number;
|
||||||
|
picUrl?: string;
|
||||||
|
};
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mprisPlayer: Player | null = null;
|
||||||
|
let mainWindow: BrowserWindow | null = null;
|
||||||
|
let currentPosition = 0;
|
||||||
|
let trayLyricIface: any = null;
|
||||||
|
let trayLyricBus: any = null;
|
||||||
|
|
||||||
|
export function initializeMpris(mainWindowRef: BrowserWindow) {
|
||||||
|
if (process.platform !== 'linux') return;
|
||||||
|
|
||||||
|
if (mprisPlayer) {
|
||||||
|
console.log('[MPRIS] Already initialized, skipping');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mainWindow = mainWindowRef;
|
||||||
|
|
||||||
|
try {
|
||||||
|
mprisPlayer = Player({
|
||||||
|
name: 'AlgerMusicPlayer',
|
||||||
|
identity: 'Alger Music Player',
|
||||||
|
supportedUriSchemes: ['file', 'http', 'https'],
|
||||||
|
supportedMimeTypes: [
|
||||||
|
'audio/mpeg',
|
||||||
|
'audio/mp3',
|
||||||
|
'audio/flac',
|
||||||
|
'audio/wav',
|
||||||
|
'audio/ogg',
|
||||||
|
'audio/aac',
|
||||||
|
'audio/m4a'
|
||||||
|
],
|
||||||
|
supportedInterfaces: ['player']
|
||||||
|
});
|
||||||
|
|
||||||
|
mprisPlayer.on('quit', () => {
|
||||||
|
app.quit();
|
||||||
|
});
|
||||||
|
|
||||||
|
mprisPlayer.on('raise', () => {
|
||||||
|
if (mainWindow) {
|
||||||
|
mainWindow.show();
|
||||||
|
mainWindow.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mprisPlayer.on('next', () => {
|
||||||
|
if (mainWindow) {
|
||||||
|
mainWindow.webContents.send('global-shortcut', 'nextPlay');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mprisPlayer.on('previous', () => {
|
||||||
|
if (mainWindow) {
|
||||||
|
mainWindow.webContents.send('global-shortcut', 'prevPlay');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mprisPlayer.on('pause', () => {
|
||||||
|
if (mainWindow) {
|
||||||
|
mainWindow.webContents.send('global-shortcut', 'togglePlay');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mprisPlayer.on('play', () => {
|
||||||
|
if (mainWindow) {
|
||||||
|
mainWindow.webContents.send('global-shortcut', 'togglePlay');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mprisPlayer.on('playpause', () => {
|
||||||
|
if (mainWindow) {
|
||||||
|
mainWindow.webContents.send('global-shortcut', 'togglePlay');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mprisPlayer.on('stop', () => {
|
||||||
|
if (mainWindow) {
|
||||||
|
mainWindow.webContents.send('global-shortcut', 'togglePlay');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mprisPlayer.getPosition = (): number => {
|
||||||
|
console.log('[MPRIS] getPosition called, returning:', currentPosition);
|
||||||
|
return currentPosition;
|
||||||
|
};
|
||||||
|
|
||||||
|
mprisPlayer.on('seek', (offset: number) => {
|
||||||
|
if (mainWindow) {
|
||||||
|
const newPosition = Math.max(0, currentPosition + offset / 1000000);
|
||||||
|
mainWindow.webContents.send('mpris-seek', newPosition);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mprisPlayer.on('position', (event: { trackId: string; position: number }) => {
|
||||||
|
if (mainWindow) {
|
||||||
|
mainWindow.webContents.send('mpris-set-position', event.position / 1000000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('mpris-position-update', (_, position: number) => {
|
||||||
|
currentPosition = position * 1000 * 1000;
|
||||||
|
mprisPlayer.seeked(position * 1000 * 1000);
|
||||||
|
mprisPlayer.getPosition = () => position * 1000 * 1000;
|
||||||
|
mprisPlayer.position = position * 1000 * 1000;
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('tray-lyric-update', async (_, lrcObj: string) => {
|
||||||
|
sendTrayLyric(lrcObj);
|
||||||
|
});
|
||||||
|
|
||||||
|
initTrayLyric();
|
||||||
|
|
||||||
|
console.log('[MPRIS] Service initialized');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[MPRIS] Failed to initialize:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateMprisPlayState(playing: boolean) {
|
||||||
|
if (!mprisPlayer || process.platform !== 'linux') return;
|
||||||
|
mprisPlayer.playbackStatus = playing ? 'Playing' : 'Paused';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateMprisCurrentSong(song: SongInfo | null) {
|
||||||
|
if (!mprisPlayer || process.platform !== 'linux') return;
|
||||||
|
|
||||||
|
if (!song) {
|
||||||
|
mprisPlayer.metadata = {};
|
||||||
|
mprisPlayer.playbackStatus = 'Stopped';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const artists =
|
||||||
|
song.ar?.map((a) => a.name).join(', ') ||
|
||||||
|
song.artists?.map((a) => a.name).join(', ') ||
|
||||||
|
song.song?.artists?.map((a) => a.name).join(', ') ||
|
||||||
|
'';
|
||||||
|
const album = song.al?.name || song.album?.name || song.song?.album?.name || '';
|
||||||
|
const duration = song.duration || song.dt || song.song?.duration || 0;
|
||||||
|
|
||||||
|
mprisPlayer.metadata = {
|
||||||
|
'mpris:trackid': mprisPlayer.objectPath(`track/${song.id || 0}`),
|
||||||
|
'mpris:length': duration * 1000,
|
||||||
|
'mpris:artUrl': song.picUrl || '',
|
||||||
|
'xesam:title': song.name || '',
|
||||||
|
'xesam:album': album,
|
||||||
|
'xesam:artist': artists ? [artists] : []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateMprisPosition(position: number) {
|
||||||
|
if (!mprisPlayer || process.platform !== 'linux') return;
|
||||||
|
mprisPlayer.seeked(position * 1000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function destroyMpris() {
|
||||||
|
if (mprisPlayer) {
|
||||||
|
mprisPlayer.quit();
|
||||||
|
mprisPlayer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initTrayLyric() {
|
||||||
|
if (process.platform !== 'linux') {
|
||||||
|
console.log('[TrayLyric] Not Linux, skipping');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[TrayLyric] Initializing...');
|
||||||
|
|
||||||
|
const serviceName = 'org.gnome.Shell.TrayLyric';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const sessionBus = dbus.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,
|
||||||
|
interface: dbusInterface,
|
||||||
|
member: 'GetNameOwner',
|
||||||
|
destination: 'org.freedesktop.DBus',
|
||||||
|
signature: 's',
|
||||||
|
body: [serviceName]
|
||||||
|
},
|
||||||
|
(err: any, result: any) => {
|
||||||
|
console.log('[TrayLyric] GetNameOwner result:', err, result);
|
||||||
|
if (err || !result) {
|
||||||
|
console.log('[TrayLyric] Service not running yet');
|
||||||
|
} else {
|
||||||
|
console.log('[TrayLyric] Service is running, owner:', result[0]);
|
||||||
|
onServiceAvailable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[TrayLyric] Exception during init:', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onServiceAvailable() {
|
||||||
|
console.log('[TrayLyric] onServiceAvailable called');
|
||||||
|
if (!trayLyricBus) {
|
||||||
|
console.log('[TrayLyric] Bus not available');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log('[TrayLyric] Getting service interface...');
|
||||||
|
const path = '/' + serviceName.replace(/\./g, '/');
|
||||||
|
trayLyricBus.getService(serviceName).getInterface(path, serviceName, (err: any, iface: any) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('[TrayLyric] Failed to get service interface:', err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
trayLyricIface = iface;
|
||||||
|
console.log('[TrayLyric] Service interface ready');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendTrayLyric(lrcObj: string) {
|
||||||
|
if (!trayLyricIface || !trayLyricBus) {
|
||||||
|
console.log('[TrayLyric] Interface or bus not ready, skipping');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 invoke 方法调用 D-Bus 方法
|
||||||
|
trayLyricBus.invoke(
|
||||||
|
{
|
||||||
|
path: '/org/gnome/Shell/TrayLyric',
|
||||||
|
interface: 'org.gnome.Shell.TrayLyric',
|
||||||
|
member: 'UpdateLyric',
|
||||||
|
destination: 'org.gnome.Shell.TrayLyric',
|
||||||
|
signature: 's',
|
||||||
|
body: [lrcObj]
|
||||||
|
},
|
||||||
|
(err: any, _result: any) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('[TrayLyric] Failed to invoke UpdateLyric:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -52,6 +52,8 @@ export const textColors = ref<any>(getTextColors());
|
|||||||
export let playMusic: ComputedRef<SongResult>;
|
export let playMusic: ComputedRef<SongResult>;
|
||||||
export let artistList: ComputedRef<Artist[]>;
|
export let artistList: ComputedRef<Artist[]>;
|
||||||
|
|
||||||
|
let lastIndex = -1;
|
||||||
|
|
||||||
export const musicDB = await useIndexedDB(
|
export const musicDB = await useIndexedDB(
|
||||||
'musicDB',
|
'musicDB',
|
||||||
[
|
[
|
||||||
@@ -329,6 +331,12 @@ const setupAudioListeners = () => {
|
|||||||
sendLyricToWin();
|
sendLyricToWin();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (isElectron && lrcArray.value[nowIndex.value]) {
|
||||||
|
if (lastIndex !== nowIndex.value) {
|
||||||
|
sendTrayLyric(nowIndex.value);
|
||||||
|
lastIndex = nowIndex.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// === 逐字歌词行内进度 ===
|
// === 逐字歌词行内进度 ===
|
||||||
const { start, end } = currentLrcTiming.value;
|
const { start, end } = currentLrcTiming.value;
|
||||||
@@ -372,6 +380,15 @@ const setupAudioListeners = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === MPRIS 进度更新(每 ~1 秒)===
|
||||||
|
if (isElectron && lyricThrottleCounter % 20 === 0) {
|
||||||
|
try {
|
||||||
|
window.electron.ipcRenderer.send('mpris-position-update', currentTime);
|
||||||
|
} catch {
|
||||||
|
// 忽略发送失败
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('进度更新 interval 出错:', error);
|
console.error('进度更新 interval 出错:', error);
|
||||||
// 出错时不清除 interval,让下一次 tick 继续尝试
|
// 出错时不清除 interval,让下一次 tick 继续尝试
|
||||||
@@ -420,6 +437,11 @@ const setupAudioListeners = () => {
|
|||||||
if (typeof currentTime === 'number' && !Number.isNaN(currentTime)) {
|
if (typeof currentTime === 'number' && !Number.isNaN(currentTime)) {
|
||||||
nowTime.value = currentTime;
|
nowTime.value = currentTime;
|
||||||
|
|
||||||
|
// === MPRIS seek 时同步进度 ===
|
||||||
|
if (isElectron) {
|
||||||
|
window.electron.ipcRenderer.send('mpris-position-update', currentTime);
|
||||||
|
}
|
||||||
|
|
||||||
// 检查是否需要更新歌词
|
// 检查是否需要更新歌词
|
||||||
const newIndex = getLrcIndex(nowTime.value);
|
const newIndex = getLrcIndex(nowTime.value);
|
||||||
if (newIndex !== nowIndex.value) {
|
if (newIndex !== nowIndex.value) {
|
||||||
@@ -807,6 +829,37 @@ 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;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const lyric = lrcArray.value[index];
|
||||||
|
if (!lyric) return;
|
||||||
|
|
||||||
|
const currentTime = lrcTimeArray.value[index] || 0;
|
||||||
|
const nextTime = lrcTimeArray.value[index + 1] || currentTime + 3;
|
||||||
|
const duration = nextTime - currentTime;
|
||||||
|
|
||||||
|
const lrcObj = JSON.stringify({
|
||||||
|
content: lyric.text || '',
|
||||||
|
time: duration.toFixed(1),
|
||||||
|
sender: 'AlgerMusicPlayer'
|
||||||
|
});
|
||||||
|
|
||||||
|
window.electron.ipcRenderer.send('tray-lyric-update', lrcObj);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[TrayLyric] Failed to send:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 歌词同步定时器
|
// 歌词同步定时器
|
||||||
let lyricSyncInterval: any = null;
|
let lyricSyncInterval: any = null;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { onMounted, onUnmounted } from 'vue';
|
import { onMounted, onUnmounted } from 'vue';
|
||||||
|
|
||||||
import i18n from '@/../i18n/renderer';
|
import i18n from '@/../i18n/renderer';
|
||||||
|
import { audioService } from '@/services/audioService';
|
||||||
import { usePlayerStore, useSettingsStore } from '@/store';
|
import { usePlayerStore, useSettingsStore } from '@/store';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -37,6 +38,18 @@ const onUpdateAppShortcuts = (_event: unknown, shortcuts: unknown) => {
|
|||||||
updateAppShortcuts(shortcuts);
|
updateAppShortcuts(shortcuts);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onMprisSeek = (_event: unknown, position: number) => {
|
||||||
|
if (audioService) {
|
||||||
|
audioService.seek(position);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMprisSetPosition = (_event: unknown, position: number) => {
|
||||||
|
if (audioService) {
|
||||||
|
audioService.seek(position);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
function shouldSkipAction(action: ShortcutAction): boolean {
|
function shouldSkipAction(action: ShortcutAction): boolean {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const lastTimestamp = actionTimestamps.get(action) ?? 0;
|
const lastTimestamp = actionTimestamps.get(action) ?? 0;
|
||||||
@@ -192,6 +205,8 @@ 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-set-position', onMprisSetPosition);
|
||||||
|
|
||||||
const storedShortcuts = window.electron.ipcRenderer.sendSync('get-store-value', 'shortcuts');
|
const storedShortcuts = window.electron.ipcRenderer.sendSync('get-store-value', 'shortcuts');
|
||||||
updateAppShortcuts(storedShortcuts);
|
updateAppShortcuts(storedShortcuts);
|
||||||
|
|||||||
Reference in New Issue
Block a user