mirror of
https://github.com/antebudimir/feishin.git
synced 2026-01-01 02:13:33 +00:00
Merge remote-tracking branch 'upstream/development' into origin/fix/#202
This commit is contained in:
commit
f0f2f54e5a
263 changed files with 18176 additions and 9719 deletions
63
src/main/features/core/discord-rpc/index.ts
Normal file
63
src/main/features/core/discord-rpc/index.ts
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import { Client, SetActivity } from '@xhayper/discord-rpc';
|
||||
import { ipcMain } from 'electron';
|
||||
|
||||
const FEISHIN_DISCORD_APPLICATION_ID = '1165957668758900787';
|
||||
|
||||
let client: Client | null = null;
|
||||
|
||||
const createClient = (clientId?: string) => {
|
||||
client = new Client({
|
||||
clientId: clientId || FEISHIN_DISCORD_APPLICATION_ID,
|
||||
});
|
||||
|
||||
client.login();
|
||||
|
||||
return client;
|
||||
};
|
||||
|
||||
const setActivity = (activity: SetActivity) => {
|
||||
if (client) {
|
||||
client.user?.setActivity({
|
||||
...activity,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const clearActivity = () => {
|
||||
if (client) {
|
||||
client.user?.clearActivity();
|
||||
}
|
||||
};
|
||||
|
||||
const quit = () => {
|
||||
if (client) {
|
||||
client?.destroy();
|
||||
}
|
||||
};
|
||||
|
||||
ipcMain.handle('discord-rpc-initialize', (_event, clientId?: string) => {
|
||||
createClient(clientId);
|
||||
});
|
||||
|
||||
ipcMain.handle('discord-rpc-set-activity', (_event, activity: SetActivity) => {
|
||||
if (client) {
|
||||
setActivity(activity);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('discord-rpc-clear-activity', () => {
|
||||
if (client) {
|
||||
clearActivity();
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('discord-rpc-quit', () => {
|
||||
quit();
|
||||
});
|
||||
|
||||
export const discordRpc = {
|
||||
clearActivity,
|
||||
createClient,
|
||||
quit,
|
||||
setActivity,
|
||||
};
|
||||
|
|
@ -2,3 +2,4 @@ import './lyrics';
|
|||
import './player';
|
||||
import './remote';
|
||||
import './settings';
|
||||
import './discord-rpc';
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import axios, { AxiosResponse } from 'axios';
|
||||
import { load } from 'cheerio';
|
||||
import { orderSearchResults } from './shared';
|
||||
import {
|
||||
LyricSource,
|
||||
InternetProviderLyricResponse,
|
||||
InternetProviderLyricSearchResponse,
|
||||
LyricSearchQuery,
|
||||
} from '../../../../renderer/api/types';
|
||||
import { orderSearchResults } from './shared';
|
||||
|
||||
const SEARCH_URL = 'https://genius.com/api/search/song';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
// Credits to https://github.com/tranxuanthang/lrcget for API implementation
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
import { orderSearchResults } from './shared';
|
||||
import {
|
||||
InternetProviderLyricResponse,
|
||||
InternetProviderLyricSearchResponse,
|
||||
LyricSearchQuery,
|
||||
LyricSource,
|
||||
} from '../../../../renderer/api/types';
|
||||
import { orderSearchResults } from './shared';
|
||||
|
||||
const FETCH_URL = 'https://lrclib.net/api/get';
|
||||
const SEEARCH_URL = 'https://lrclib.net/api/search';
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
import console from 'console';
|
||||
import { ipcMain } from 'electron';
|
||||
import { getMainWindow, getMpvInstance } from '../../../main';
|
||||
import { getMpvInstance } from '../../../main';
|
||||
import { PlayerData } from '/@/renderer/store';
|
||||
|
||||
declare module 'node-mpv';
|
||||
|
||||
function wait(timeout: number) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve('resolved');
|
||||
}, timeout);
|
||||
});
|
||||
}
|
||||
// function wait(timeout: number) {
|
||||
// return new Promise((resolve) => {
|
||||
// setTimeout(() => {
|
||||
// resolve('resolved');
|
||||
// }, timeout);
|
||||
// });
|
||||
// }
|
||||
|
||||
ipcMain.handle('player-is-running', async () => {
|
||||
return getMpvInstance()?.isRunning();
|
||||
|
|
@ -101,6 +101,7 @@ ipcMain.on('player-set-queue', async (_event, data: PlayerData, pause?: boolean)
|
|||
.catch((err) => {
|
||||
console.log('MPV failed to clear playlist', err);
|
||||
});
|
||||
|
||||
await getMpvInstance()
|
||||
?.pause()
|
||||
.catch((err) => {
|
||||
|
|
@ -109,42 +110,25 @@ ipcMain.on('player-set-queue', async (_event, data: PlayerData, pause?: boolean)
|
|||
return;
|
||||
}
|
||||
|
||||
let complete = false;
|
||||
let tryAttempts = 0;
|
||||
try {
|
||||
if (data.queue.current) {
|
||||
await getMpvInstance()
|
||||
?.load(data.queue.current.streamUrl, 'replace')
|
||||
.catch((err) => {
|
||||
console.log('MPV failed to load song', err);
|
||||
getMpvInstance()?.play();
|
||||
});
|
||||
|
||||
while (!complete) {
|
||||
if (tryAttempts > 3) {
|
||||
getMainWindow()?.webContents.send('renderer-player-error', 'Failed to load song');
|
||||
complete = true;
|
||||
} else {
|
||||
try {
|
||||
if (data.queue.current) {
|
||||
await getMpvInstance()
|
||||
?.load(data.queue.current.streamUrl, 'replace')
|
||||
.catch((err) => {
|
||||
console.log('MPV failed to load song', err);
|
||||
});
|
||||
}
|
||||
|
||||
if (data.queue.next) {
|
||||
await getMpvInstance()
|
||||
?.load(data.queue.next.streamUrl, 'append')
|
||||
.catch((err) => {
|
||||
console.log('MPV failed to load next song', err);
|
||||
});
|
||||
}
|
||||
|
||||
complete = true;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
tryAttempts += 1;
|
||||
await wait(500);
|
||||
if (data.queue.next) {
|
||||
await getMpvInstance()?.load(data.queue.next.streamUrl, 'append');
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
if (pause) {
|
||||
await getMpvInstance()?.pause();
|
||||
getMpvInstance()?.pause();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -186,6 +170,7 @@ ipcMain.on('player-auto-next', async (_event, data: PlayerData) => {
|
|||
?.playlistRemove(0)
|
||||
.catch((err) => {
|
||||
console.log('MPV failed to remove song from playlist', err);
|
||||
getMpvInstance()?.pause();
|
||||
});
|
||||
|
||||
if (data.queue.next) {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { deflate, gzip } from 'zlib';
|
|||
import axios from 'axios';
|
||||
import { app, ipcMain } from 'electron';
|
||||
import { Server as WsServer, WebSocketServer, WebSocket } from 'ws';
|
||||
import manifest from './manifest.json';
|
||||
import { ClientEvent, ServerEvent } from '../../../../remote/types';
|
||||
import { PlayerRepeat, SongUpdate } from '../../../../renderer/types';
|
||||
import { getMainWindow } from '../../../main';
|
||||
|
|
@ -34,6 +35,7 @@ interface MimeType {
|
|||
|
||||
interface StatefulWebSocket extends WebSocket {
|
||||
alive: boolean;
|
||||
auth: boolean;
|
||||
}
|
||||
|
||||
let server: Server | undefined;
|
||||
|
|
@ -52,7 +54,9 @@ type SendData = ServerEvent & {
|
|||
|
||||
function send({ client, event, data }: SendData): void {
|
||||
if (client.readyState === WebSocket.OPEN) {
|
||||
client.send(JSON.stringify({ data, event }));
|
||||
if (client.alive && client.auth) {
|
||||
client.send(JSON.stringify({ data, event }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -141,17 +145,9 @@ async function serveFile(
|
|||
res: ServerResponse,
|
||||
): Promise<void> {
|
||||
const fileName = `${file}.${extension}`;
|
||||
let path: string;
|
||||
|
||||
if (extension === 'ico') {
|
||||
path = app.isPackaged
|
||||
? join(process.resourcesPath, 'assets', fileName)
|
||||
: join(__dirname, '../../../../../assets', fileName);
|
||||
} else {
|
||||
path = app.isPackaged
|
||||
? join(__dirname, '../remote', fileName)
|
||||
: join(__dirname, '../../../../../.erb/dll', fileName);
|
||||
}
|
||||
const path = app.isPackaged
|
||||
? join(__dirname, '../remote', fileName)
|
||||
: join(__dirname, '../../../../../.erb/dll', fileName);
|
||||
|
||||
let stats: Stats;
|
||||
|
||||
|
|
@ -291,7 +287,7 @@ const enableServer = (config: RemoteConfig): Promise<void> => {
|
|||
break;
|
||||
}
|
||||
case '/favicon.ico': {
|
||||
await serveFile(req, 'icon', 'ico', res);
|
||||
await serveFile(req, 'favicon', 'ico', res);
|
||||
break;
|
||||
}
|
||||
case '/remote.css': {
|
||||
|
|
@ -302,10 +298,26 @@ const enableServer = (config: RemoteConfig): Promise<void> => {
|
|||
await serveFile(req, 'remote', 'js', res);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
res.statusCode = 404;
|
||||
case '/manifest.json': {
|
||||
res.statusCode = 200;
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify(manifest));
|
||||
break;
|
||||
}
|
||||
case '/credentials': {
|
||||
res.statusCode = 200;
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
res.end('Not FOund');
|
||||
res.end(req.headers.authorization);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (req.url?.startsWith('/worker.js')) {
|
||||
await serveFile(req, 'worker', 'js', res);
|
||||
} else {
|
||||
res.statusCode = 404;
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
res.end('Not Found');
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
@ -318,14 +330,20 @@ const enableServer = (config: RemoteConfig): Promise<void> => {
|
|||
server.listen(config.port, resolve);
|
||||
wsServer = new WebSocketServer({ server });
|
||||
|
||||
wsServer.on('connection', (ws, req) => {
|
||||
if (!authorize(req)) {
|
||||
ws.close(4003);
|
||||
return;
|
||||
}
|
||||
|
||||
wsServer.on('connection', (ws) => {
|
||||
let authFail: number | undefined;
|
||||
ws.alive = true;
|
||||
|
||||
if (!settings.username && !settings.password) {
|
||||
ws.auth = true;
|
||||
} else {
|
||||
authFail = setTimeout(() => {
|
||||
if (!ws.auth) {
|
||||
ws.close();
|
||||
}
|
||||
}, 10000) as unknown as number;
|
||||
}
|
||||
|
||||
ws.on('error', console.error);
|
||||
|
||||
ws.on('message', (data) => {
|
||||
|
|
@ -333,6 +351,25 @@ const enableServer = (config: RemoteConfig): Promise<void> => {
|
|||
const json = JSON.parse(data.toString()) as ClientEvent;
|
||||
const event = json.event;
|
||||
|
||||
if (!ws.auth) {
|
||||
if (event === 'authenticate') {
|
||||
const auth = json.header.split(' ')[1];
|
||||
const [login, password] = Buffer.from(auth, 'base64')
|
||||
.toString()
|
||||
.split(':');
|
||||
|
||||
if (login === settings.username && password === settings.password) {
|
||||
ws.auth = true;
|
||||
} else {
|
||||
ws.close();
|
||||
}
|
||||
|
||||
clearTimeout(authFail);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
switch (event) {
|
||||
case 'pause': {
|
||||
getMainWindow()?.webContents.send('renderer-player-pause');
|
||||
|
|
|
|||
17
src/main/features/core/remote/manifest.json
Normal file
17
src/main/features/core/remote/manifest.json
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "Feishin Remote",
|
||||
"short_name": "Feishin Remote",
|
||||
"start_url": "/",
|
||||
"background_color": "#000100",
|
||||
"theme_color": "#E7E7E7",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "32x32",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable any"
|
||||
}
|
||||
],
|
||||
"display": "standalone",
|
||||
"orientation": "portrait"
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import Store from 'electron-store';
|
||||
import { ipcMain, safeStorage } from 'electron';
|
||||
import Store from 'electron-store';
|
||||
|
||||
export const store = new Store();
|
||||
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ ipcMain.on('update-song', (_event, args: SongUpdate) => {
|
|||
|
||||
mprisPlayer.metadata = {
|
||||
'mpris:artUrl': upsizedImageUrl,
|
||||
'mpris:length': song.duration ? Math.round((song.duration || 0) * 1e6) : null,
|
||||
'mpris:length': song.duration ? Math.round((song.duration || 0) * 1e3) : null,
|
||||
'mpris:trackid': song.id
|
||||
? mprisPlayer.objectPath(`track/${song.id?.replace('-', '')}`)
|
||||
: '',
|
||||
|
|
|
|||
109
src/main/main.ts
109
src/main/main.ts
|
|
@ -21,6 +21,8 @@ import {
|
|||
Menu,
|
||||
nativeImage,
|
||||
BrowserWindowConstructorOptions,
|
||||
protocol,
|
||||
net,
|
||||
} from 'electron';
|
||||
import electronLocalShortcut from 'electron-localshortcut';
|
||||
import log from 'electron-log';
|
||||
|
|
@ -43,6 +45,8 @@ export default class AppUpdater {
|
|||
}
|
||||
}
|
||||
|
||||
protocol.registerSchemesAsPrivileged([{ privileges: { bypassCSP: true }, scheme: 'feishin' }]);
|
||||
|
||||
process.on('uncaughtException', (error: any) => {
|
||||
console.log('Error in main process', error);
|
||||
});
|
||||
|
|
@ -80,16 +84,6 @@ const installExtensions = async () => {
|
|||
.catch(console.log);
|
||||
};
|
||||
|
||||
const singleInstance = app.requestSingleInstanceLock();
|
||||
|
||||
if (!singleInstance) {
|
||||
app.quit();
|
||||
} else {
|
||||
app.on('second-instance', () => {
|
||||
mainWindow?.show();
|
||||
});
|
||||
}
|
||||
|
||||
const RESOURCES_PATH = app.isPackaged
|
||||
? path.join(process.resourcesPath, 'assets')
|
||||
: path.join(__dirname, '../../assets');
|
||||
|
|
@ -129,7 +123,9 @@ const createTray = () => {
|
|||
return;
|
||||
}
|
||||
|
||||
tray = isLinux() ? new Tray(getAssetPath('icon.png')) : new Tray(getAssetPath('icon.ico'));
|
||||
tray = isLinux()
|
||||
? new Tray(getAssetPath('icons/icon.png'))
|
||||
: new Tray(getAssetPath('icons/icon.ico'));
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
click: () => {
|
||||
|
|
@ -212,7 +208,7 @@ const createWindow = async () => {
|
|||
autoHideMenuBar: true,
|
||||
frame: false,
|
||||
height: 900,
|
||||
icon: getAssetPath('icon.png'),
|
||||
icon: getAssetPath('icons/icon.png'),
|
||||
minHeight: 640,
|
||||
minWidth: 480,
|
||||
show: false,
|
||||
|
|
@ -257,6 +253,11 @@ const createWindow = async () => {
|
|||
mainWindow?.close();
|
||||
});
|
||||
|
||||
ipcMain.on('window-quit', () => {
|
||||
mainWindow?.close();
|
||||
app.exit();
|
||||
});
|
||||
|
||||
ipcMain.on('app-restart', () => {
|
||||
// Fix for .AppImage
|
||||
if (process.env.APPIMAGE) {
|
||||
|
|
@ -426,7 +427,7 @@ const prefetchPlaylistParams = [
|
|||
];
|
||||
|
||||
const DEFAULT_MPV_PARAMETERS = (extraParameters?: string[]) => {
|
||||
const parameters = ['--idle=yes'];
|
||||
const parameters = ['--idle=yes', '--no-config', '--load-scripts=no'];
|
||||
|
||||
if (!extraParameters?.some((param) => prefetchPlaylistParams.includes(param))) {
|
||||
parameters.push('--prefetch-playlist=yes');
|
||||
|
|
@ -443,22 +444,28 @@ const createMpv = (data: { extraParameters?: string[]; properties?: Record<strin
|
|||
const params = uniq([...DEFAULT_MPV_PARAMETERS(extraParameters), ...(extraParameters || [])]);
|
||||
console.log('Setting mpv params: ', params);
|
||||
|
||||
const extra = isDevelopment ? '-dev' : '';
|
||||
|
||||
const mpv = new MpvAPI(
|
||||
{
|
||||
audio_only: true,
|
||||
auto_restart: false,
|
||||
binary: MPV_BINARY_PATH || '',
|
||||
socket: isWindows() ? `\\\\.\\pipe\\mpvserver${extra}` : `/tmp/node-mpv${extra}.sock`,
|
||||
time_update: 1,
|
||||
},
|
||||
params,
|
||||
);
|
||||
|
||||
console.log('Setting MPV properties: ', properties);
|
||||
mpv.setMultipleProperties(properties || {});
|
||||
|
||||
mpv.start().catch((error) => {
|
||||
console.log('MPV failed to start', error);
|
||||
});
|
||||
// eslint-disable-next-line promise/catch-or-return
|
||||
mpv.start()
|
||||
.catch((error) => {
|
||||
console.log('MPV failed to start', error);
|
||||
})
|
||||
.finally(() => {
|
||||
console.log('Setting MPV properties: ', properties);
|
||||
mpv.setMultipleProperties(properties || {});
|
||||
});
|
||||
|
||||
mpv.on('status', (status, ...rest) => {
|
||||
console.log('MPV Event: status', status.property, status.value, rest);
|
||||
|
|
@ -640,14 +647,56 @@ app.on('window-all-closed', () => {
|
|||
}
|
||||
});
|
||||
|
||||
app.whenReady()
|
||||
.then(() => {
|
||||
createWindow();
|
||||
createTray();
|
||||
app.on('activate', () => {
|
||||
// On macOS it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (mainWindow === null) createWindow();
|
||||
});
|
||||
})
|
||||
.catch(console.log);
|
||||
const FONT_HEADERS = [
|
||||
'font/collection',
|
||||
'font/otf',
|
||||
'font/sfnt',
|
||||
'font/ttf',
|
||||
'font/woff',
|
||||
'font/woff2',
|
||||
];
|
||||
|
||||
const singleInstance = app.requestSingleInstanceLock();
|
||||
|
||||
if (!singleInstance) {
|
||||
app.quit();
|
||||
} else {
|
||||
app.on('second-instance', () => {
|
||||
if (mainWindow) {
|
||||
if (mainWindow.isMinimized()) {
|
||||
mainWindow.restore();
|
||||
}
|
||||
|
||||
mainWindow.focus();
|
||||
}
|
||||
});
|
||||
|
||||
app.whenReady()
|
||||
.then(() => {
|
||||
protocol.handle('feishin', async (request) => {
|
||||
const filePath = `file://${request.url.slice('feishin://'.length)}`;
|
||||
const response = await net.fetch(filePath);
|
||||
const contentType = response.headers.get('content-type');
|
||||
|
||||
if (!contentType || !FONT_HEADERS.includes(contentType)) {
|
||||
getMainWindow()?.webContents.send('custom-font-error', filePath);
|
||||
|
||||
return new Response(null, {
|
||||
status: 403,
|
||||
statusText: 'Forbidden',
|
||||
});
|
||||
}
|
||||
|
||||
return response;
|
||||
});
|
||||
|
||||
createWindow();
|
||||
createTray();
|
||||
app.on('activate', () => {
|
||||
// On macOS it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (mainWindow === null) createWindow();
|
||||
});
|
||||
})
|
||||
.catch(console.log);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { contextBridge } from 'electron';
|
||||
import { browser } from './preload/browser';
|
||||
import { discordRpc } from './preload/discord-rpc';
|
||||
import { ipc } from './preload/ipc';
|
||||
import { localSettings } from './preload/local-settings';
|
||||
import { lyrics } from './preload/lyrics';
|
||||
|
|
@ -10,6 +11,7 @@ import { utils } from './preload/utils';
|
|||
|
||||
contextBridge.exposeInMainWorld('electron', {
|
||||
browser,
|
||||
discordRpc,
|
||||
ipc,
|
||||
localSettings,
|
||||
lyrics,
|
||||
|
|
|
|||
|
|
@ -3,16 +3,23 @@ import { ipcRenderer } from 'electron';
|
|||
const exit = () => {
|
||||
ipcRenderer.send('window-close');
|
||||
};
|
||||
|
||||
const maximize = () => {
|
||||
ipcRenderer.send('window-maximize');
|
||||
};
|
||||
|
||||
const minimize = () => {
|
||||
ipcRenderer.send('window-minimize');
|
||||
};
|
||||
|
||||
const unmaximize = () => {
|
||||
ipcRenderer.send('window-unmaximize');
|
||||
};
|
||||
|
||||
const quit = () => {
|
||||
ipcRenderer.send('window-quit');
|
||||
};
|
||||
|
||||
const devtools = () => {
|
||||
ipcRenderer.send('window-dev-tools');
|
||||
};
|
||||
|
|
@ -22,5 +29,6 @@ export const browser = {
|
|||
exit,
|
||||
maximize,
|
||||
minimize,
|
||||
quit,
|
||||
unmaximize,
|
||||
};
|
||||
|
|
|
|||
28
src/main/preload/discord-rpc.ts
Normal file
28
src/main/preload/discord-rpc.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { SetActivity } from '@xhayper/discord-rpc';
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
const initialize = (clientId: string) => {
|
||||
const client = ipcRenderer.invoke('discord-rpc-initialize', clientId);
|
||||
return client;
|
||||
};
|
||||
|
||||
const clearActivity = () => {
|
||||
ipcRenderer.invoke('discord-rpc-clear-activity');
|
||||
};
|
||||
|
||||
const setActivity = (activity: SetActivity) => {
|
||||
ipcRenderer.invoke('discord-rpc-set-activity', activity);
|
||||
};
|
||||
|
||||
const quit = () => {
|
||||
ipcRenderer.invoke('discord-rpc-quit');
|
||||
};
|
||||
|
||||
export const discordRpc = {
|
||||
clearActivity,
|
||||
initialize,
|
||||
quit,
|
||||
setActivity,
|
||||
};
|
||||
|
||||
export type DiscordRpc = typeof discordRpc;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { IpcRendererEvent, ipcRenderer, webFrame } from 'electron';
|
||||
import Store from 'electron-store';
|
||||
import { ipcRenderer, webFrame } from 'electron';
|
||||
|
||||
const store = new Store();
|
||||
|
||||
|
|
@ -39,9 +39,14 @@ const setZoomFactor = (zoomFactor: number) => {
|
|||
webFrame.setZoomFactor(zoomFactor / 100);
|
||||
};
|
||||
|
||||
const fontError = (cb: (event: IpcRendererEvent, file: string) => void) => {
|
||||
ipcRenderer.on('custom-font-error', cb);
|
||||
};
|
||||
|
||||
export const localSettings = {
|
||||
disableMediaKeys,
|
||||
enableMediaKeys,
|
||||
fontError,
|
||||
get,
|
||||
passwordGet,
|
||||
passwordRemove,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue