v2.0.0
This commit is contained in:
+244
@@ -0,0 +1,244 @@
|
||||
import { join, dirname } from 'path'
|
||||
import { existsSync, mkdirSync } from 'fs'
|
||||
import { app, shell, screen, nativeTheme } from 'electron'
|
||||
import { URL_SCHEME_RXP } from '@common/constants'
|
||||
import { getTheme, initHotKey, initSetting, parseEnvParams } from './utils'
|
||||
import { navigationUrlWhiteList } from '@common/config'
|
||||
import defaultSetting from '@common/defaultSetting'
|
||||
import { closeWindow, isExistWindow as isExistMainWindow, showWindow as showMainWindow } from './modules/winMain'
|
||||
import { createAppEvent, createListEvent } from '@main/event'
|
||||
import { encodePath, isMac, log } from '@common/utils'
|
||||
import createWorkers from './worker'
|
||||
import { migrateDBData } from './utils/migrate'
|
||||
|
||||
export const initGlobalData = () => {
|
||||
global.isDev = process.env.NODE_ENV !== 'production'
|
||||
const envParams = parseEnvParams()
|
||||
global.envParams = {
|
||||
cmdParams: envParams.cmdParams,
|
||||
deeplink: envParams.deeplink,
|
||||
}
|
||||
|
||||
if (global.isDev) {
|
||||
// eslint-disable-next-line no-undef
|
||||
global.staticPath = webpackStaticPath
|
||||
} else {
|
||||
global.staticPath = join(__dirname, '/static')
|
||||
}
|
||||
}
|
||||
|
||||
export const initSingleInstanceHandle = () => {
|
||||
// 单例应用程序
|
||||
if (!app.requestSingleInstanceLock()) {
|
||||
app.quit()
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
app.on('second-instance', (event, argv, cwd) => {
|
||||
for (const param of argv) {
|
||||
if (URL_SCHEME_RXP.test(param)) {
|
||||
global.envParams.deeplink = param
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (isExistMainWindow()) {
|
||||
if (global.envParams.deeplink) global.lx.event_app.deeplink(global.envParams.deeplink)
|
||||
else showMainWindow()
|
||||
} else {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const applyElectronEnvParams = () => {
|
||||
// Is disable hardware acceleration
|
||||
if (global.envParams.cmdParams.dha) app.disableHardwareAcceleration()
|
||||
if (global.envParams.cmdParams.dhmkh) app.commandLine.appendSwitch('disable-features', 'HardwareMediaKeyHandling')
|
||||
|
||||
// fix linux transparent fail. https://github.com/electron/electron/issues/25153#issuecomment-843688494
|
||||
if (process.platform == 'linux') app.commandLine.appendSwitch('use-gl', 'desktop')
|
||||
|
||||
// https://github.com/electron/electron/issues/22691
|
||||
app.commandLine.appendSwitch('wm-window-animations-disabled')
|
||||
|
||||
// proxy
|
||||
if (global.envParams.cmdParams['proxy-server']) {
|
||||
app.commandLine.appendSwitch('proxy-server', global.envParams.cmdParams['proxy-server'])
|
||||
app.commandLine.appendSwitch('proxy-bypass-list', global.envParams.cmdParams['proxy-bypass-list'] ?? '<local>')
|
||||
}
|
||||
}
|
||||
|
||||
export const setUserDataPath = () => {
|
||||
// windows平台下如果应用目录下存在 portable 文件夹则将数据存在此文件下
|
||||
if (process.platform == 'win32') {
|
||||
const portablePath = join(dirname(app.getPath('exe')), '/portable')
|
||||
if (existsSync(portablePath)) {
|
||||
app.setPath('appData', portablePath)
|
||||
const appDataPath = join(portablePath, '/userData')
|
||||
if (!existsSync(appDataPath)) mkdirSync(appDataPath)
|
||||
app.setPath('userData', appDataPath)
|
||||
}
|
||||
}
|
||||
|
||||
const userDataPath = app.getPath('userData')
|
||||
global.lxDataPath = join(encodePath(userDataPath.replaceAll('\\', '/')), 'LxDatas')
|
||||
global.lxOldDataPath = encodePath(userDataPath.replaceAll('\\', '/'))
|
||||
if (!existsSync(global.lxDataPath)) mkdirSync(global.lxDataPath)
|
||||
}
|
||||
|
||||
export const registerDeeplink = (startApp: () => void) => {
|
||||
if (global.isDev && process.platform === 'win32') {
|
||||
// Set the path of electron.exe and your app.
|
||||
// These two additional parameters are only available on windows.
|
||||
// console.log(process.execPath, process.argv)
|
||||
app.setAsDefaultProtocolClient('lxmusic', process.execPath, process.argv.slice(1))
|
||||
} else {
|
||||
app.setAsDefaultProtocolClient('lxmusic')
|
||||
}
|
||||
|
||||
// deep link
|
||||
app.on('open-url', (event, url) => {
|
||||
if (!URL_SCHEME_RXP.test(url)) return
|
||||
event.preventDefault()
|
||||
global.envParams.deeplink = url
|
||||
if (isExistMainWindow()) {
|
||||
if (global.envParams.deeplink) global.lx.event_app.deeplink(global.envParams.deeplink)
|
||||
else showMainWindow()
|
||||
} else {
|
||||
startApp()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const listenerAppEvent = (startApp: () => void) => {
|
||||
app.on('web-contents-created', (event, contents) => {
|
||||
contents.on('will-navigate', (event, navigationUrl) => {
|
||||
if (global.isDev) return console.log('navigation to url:', navigationUrl)
|
||||
if (!navigationUrlWhiteList.some(url => url.test(navigationUrl))) return event.preventDefault()
|
||||
console.log('navigation to url:', navigationUrl)
|
||||
})
|
||||
contents.setWindowOpenHandler(({ url }) => {
|
||||
if (!/^devtools/.test(url) && /^https?:\/\//.test(url)) {
|
||||
void shell.openExternal(url)
|
||||
}
|
||||
console.log(url)
|
||||
return { action: 'deny' }
|
||||
})
|
||||
contents.on('will-attach-webview', (event, webPreferences, params) => {
|
||||
// Strip away preload scripts if unused or verify their location is legitimate
|
||||
delete webPreferences.preload
|
||||
// delete webPreferences.preloadURL
|
||||
|
||||
// Disable Node.js integration
|
||||
webPreferences.nodeIntegration = false
|
||||
|
||||
// Verify URL being loaded
|
||||
if (!navigationUrlWhiteList.some(url => url.test(params.src))) {
|
||||
event.preventDefault()
|
||||
}
|
||||
})
|
||||
|
||||
// disable create dictionary
|
||||
// https://github.com/lyswhut/lx-music-desktop/issues/773
|
||||
contents.session.setSpellCheckerDictionaryDownloadURL('http://0.0.0.0')
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
if (isExistMainWindow()) {
|
||||
showMainWindow()
|
||||
} else {
|
||||
startApp()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (isMac) return
|
||||
|
||||
app.quit()
|
||||
})
|
||||
|
||||
const initScreenParams = () => {
|
||||
global.envParams.workAreaSize = screen.getPrimaryDisplay().workAreaSize
|
||||
}
|
||||
app.on('ready', () => {
|
||||
screen.on('display-metrics-changed', initScreenParams)
|
||||
initScreenParams()
|
||||
})
|
||||
|
||||
nativeTheme.addListener('updated', (event: any) => {
|
||||
const themeInfo: Electron.NativeTheme = event.sender
|
||||
global.lx.event_app.system_theme_change(themeInfo.shouldUseDarkColors)
|
||||
})
|
||||
}
|
||||
|
||||
const initTheme = () => {
|
||||
global.lx.theme = getTheme()
|
||||
const themeConfigKeys = ['theme.id', 'theme.lightId', 'theme.darkId']
|
||||
global.lx.event_app.on('updated_config', (keys) => {
|
||||
let requireUpdate = false
|
||||
for (const key of keys) {
|
||||
if (themeConfigKeys.includes(key)) {
|
||||
requireUpdate = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (requireUpdate) {
|
||||
global.lx.theme = getTheme()
|
||||
global.lx.event_app.theme_change()
|
||||
}
|
||||
})
|
||||
global.lx.event_app.on('system_theme_change', () => {
|
||||
if (global.lx.appSetting['theme.id'] == 'auto') {
|
||||
global.lx.theme = getTheme()
|
||||
global.lx.event_app.theme_change()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let isInitialized = false
|
||||
export const initAppSetting = async() => {
|
||||
if (!global.lx) {
|
||||
const config = await initHotKey()
|
||||
global.lx = {
|
||||
isTrafficLightClose: false,
|
||||
isQuitting: false,
|
||||
event_app: createAppEvent(),
|
||||
event_list: createListEvent(),
|
||||
appSetting: defaultSetting,
|
||||
worker: createWorkers(),
|
||||
hotKey: {
|
||||
enable: true,
|
||||
config: {
|
||||
local: config.local,
|
||||
global: config.global,
|
||||
},
|
||||
state: new Map(),
|
||||
},
|
||||
theme: {
|
||||
shouldUseDarkColors: false,
|
||||
theme: {
|
||||
id: '',
|
||||
name: '',
|
||||
isDark: false,
|
||||
colors: {},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (!isInitialized) {
|
||||
const dbFileExists = await global.lx.worker.dbService.init(global.lxDataPath)
|
||||
global.lx.appSetting = (await initSetting()).setting
|
||||
if (!dbFileExists) await migrateDBData().catch(err => log.error(err))
|
||||
initTheme()
|
||||
}
|
||||
// global.lx.theme = getTheme()
|
||||
|
||||
isInitialized = true
|
||||
}
|
||||
|
||||
export const quitApp = () => {
|
||||
global.lx.isQuitting = true
|
||||
closeWindow()
|
||||
}
|
||||
Vendored
-23
@@ -1,23 +0,0 @@
|
||||
const urlSchemeRxp = /^lxmusic:\/\//
|
||||
|
||||
const parseEnv = () => {
|
||||
const params = {}
|
||||
const rx = /^-\w+/
|
||||
for (let param of process.argv) {
|
||||
if (urlSchemeRxp.test(param)) {
|
||||
global.envParams.deeplink = param
|
||||
}
|
||||
|
||||
if (!rx.test(param)) continue
|
||||
param = param.substring(1)
|
||||
let index = param.indexOf('=')
|
||||
if (index < 0) {
|
||||
params[param] = true
|
||||
} else {
|
||||
params[param.substring(0, index)] = param.substring(index + 1)
|
||||
}
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
global.envParams.cmdParams = parseEnv()
|
||||
Vendored
-4
@@ -1,4 +0,0 @@
|
||||
global.envParams = {}
|
||||
|
||||
require('./cmdParams')
|
||||
require('./screen')
|
||||
Vendored
-11
@@ -1,11 +0,0 @@
|
||||
const { app, screen } = require('electron')
|
||||
|
||||
const initScreenParams = () => {
|
||||
global.envParams.workAreaSize = screen.getPrimaryDisplay().workAreaSize
|
||||
}
|
||||
|
||||
|
||||
app.on('ready', () => {
|
||||
screen.on('display-metrics-changed', initScreenParams)
|
||||
initScreenParams()
|
||||
})
|
||||
@@ -1,34 +0,0 @@
|
||||
const { common: COMMON_EVENT_NAME, mainWindow: MAIN_WINDOW_EVENT_NAME } = require('./events/_name')
|
||||
const { mainSend, NAMES: { mainWindow: ipcMainWindowNames } } = require('./../common/ipc')
|
||||
const { getAppHotKeyConfig } = require('./utils')
|
||||
|
||||
global.lx_event.common.on(COMMON_EVENT_NAME.configStatus, name => {
|
||||
if (MAIN_WINDOW_EVENT_NAME.name === name) return
|
||||
if (global.modules.mainWindow) mainSend(global.modules.mainWindow, ipcMainWindowNames.set_config, global.appSetting)
|
||||
})
|
||||
|
||||
global.lx_event.common.on(COMMON_EVENT_NAME.initHotKey, () => {
|
||||
global.appHotKey.config = getAppHotKeyConfig()
|
||||
})
|
||||
|
||||
global.lx_event.mainWindow.on(MAIN_WINDOW_EVENT_NAME.quit, () => {
|
||||
global.isTrafficLightClose = false
|
||||
global.isQuitting = true
|
||||
global.modules.mainWindow.close()
|
||||
})
|
||||
global.lx_event.mainWindow.on(MAIN_WINDOW_EVENT_NAME.toggle_minimize, () => {
|
||||
if (global.modules.mainWindow.isMinimized()) {
|
||||
if (!global.modules.mainWindow.isVisible()) {
|
||||
global.modules.mainWindow.show()
|
||||
}
|
||||
global.modules.mainWindow.restore()
|
||||
global.modules.mainWindow.focus()
|
||||
} else {
|
||||
global.modules.mainWindow.minimize()
|
||||
}
|
||||
})
|
||||
global.lx_event.mainWindow.on(MAIN_WINDOW_EVENT_NAME.toggle_hide, () => {
|
||||
global.modules.mainWindow.isVisible()
|
||||
? global.modules.mainWindow.hide()
|
||||
: global.modules.mainWindow.show()
|
||||
})
|
||||
@@ -0,0 +1,101 @@
|
||||
import { EventEmitter } from 'events'
|
||||
|
||||
import { saveAppHotKeyConfig, updateSetting } from '@main/utils'
|
||||
|
||||
export class Event extends EventEmitter {
|
||||
// closeAll() {
|
||||
// this.emit(COMMON_EVENT_NAME.closeAll)
|
||||
// }
|
||||
// initSetting() {
|
||||
// this.emit(COMMON_EVENT_NAME.initConfig)
|
||||
// // this.configStatus(null)
|
||||
// }
|
||||
|
||||
/**
|
||||
* 初始化APP
|
||||
*/
|
||||
app_inited() {
|
||||
this.emit('app_inited')
|
||||
}
|
||||
|
||||
/**
|
||||
* 已更新的配置
|
||||
* @param keys 已更新配置的key
|
||||
* @param setting 已更新配置
|
||||
*/
|
||||
updated_config(keys: Array<keyof LX.AppSetting>, setting: Partial<LX.AppSetting>) {
|
||||
this.emit('updated_config', keys, setting)
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新配置
|
||||
* @param setting 新设置
|
||||
*/
|
||||
update_config(setting: Partial<LX.AppSetting>) {
|
||||
const { setting: newSetting, updatedSettingKeys, updatedSetting } = updateSetting(setting)
|
||||
global.lx.appSetting = newSetting
|
||||
if (!updatedSettingKeys.length) return
|
||||
this.emit('update_config', newSetting)
|
||||
// console.log(updatedSetting)
|
||||
this.updated_config(updatedSettingKeys, updatedSetting)
|
||||
}
|
||||
|
||||
system_theme_change(isDark: boolean) {
|
||||
this.emit('system_theme_change', isDark)
|
||||
}
|
||||
|
||||
theme_change() {
|
||||
this.emit('theme_change')
|
||||
}
|
||||
|
||||
deeplink(link: string) {
|
||||
this.emit('deeplink', link)
|
||||
}
|
||||
|
||||
hot_key_down(keyInfo: LX.HotKeyDownInfo) {
|
||||
this.emit('hot_key_down', keyInfo)
|
||||
}
|
||||
|
||||
hot_key_config_update(config: LX.HotKeyConfigAll) {
|
||||
saveAppHotKeyConfig(config)
|
||||
this.emit('hot_key_config_update', config)
|
||||
}
|
||||
|
||||
main_window_ready_to_show() {
|
||||
this.emit('main_window_ready_to_show')
|
||||
}
|
||||
|
||||
main_window_inited() {
|
||||
this.emit('main_window_inited')
|
||||
}
|
||||
|
||||
main_window_show() {
|
||||
this.emit('main_window_show')
|
||||
}
|
||||
|
||||
main_window_hide() {
|
||||
this.emit('main_window_hide')
|
||||
}
|
||||
|
||||
main_window_focus() {
|
||||
this.emit('main_window_focus')
|
||||
}
|
||||
|
||||
main_window_blur() {
|
||||
this.emit('main_window_blur')
|
||||
}
|
||||
|
||||
main_window_close() {
|
||||
this.emit('main_window_close')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
type EventMethods = Omit<EventType, keyof EventEmitter>
|
||||
declare class EventType extends Event {
|
||||
on<K extends keyof EventMethods>(event: K, listener: EventMethods[K]): this
|
||||
once<K extends keyof EventMethods>(event: K, listener: EventMethods[K]): this
|
||||
off<K extends keyof EventMethods>(event: K, listener: EventMethods[K]): this
|
||||
}
|
||||
|
||||
export declare type Type = Omit<EventType, keyof Omit<EventEmitter, 'on' | 'off' | 'once'>>
|
||||
@@ -0,0 +1,174 @@
|
||||
import { EventEmitter } from 'events'
|
||||
// import {
|
||||
// // getAllUserList as getAllUserListByDB,
|
||||
// createUserLists,
|
||||
// removeUserLists,
|
||||
// updateUserLists,
|
||||
// updateUserListsPosition,
|
||||
// musicsAdd,
|
||||
// musicsMove,
|
||||
// musicsRemove,
|
||||
// musicsUpdate,
|
||||
// musicsClear,
|
||||
// musicsPositionUpdate,
|
||||
// musicOverwrite,
|
||||
// } from '@main/workers/dbService/modules/list'
|
||||
|
||||
export class Event extends EventEmitter {
|
||||
list_changed() {
|
||||
this.emit('list_changed')
|
||||
}
|
||||
|
||||
/**
|
||||
* 覆盖整个列表数据
|
||||
* @param listData 列表数据
|
||||
* @param isRemote 是否属于远程操作
|
||||
*/
|
||||
async list_data_overwrite(listData: MakeOptional<LX.List.ListDataFull, 'tempList'>, isRemote: boolean = false) {
|
||||
await global.lx.worker.dbService.listDataOverwrite(listData)
|
||||
this.emit('list_data_overwrite', listData, isRemote)
|
||||
this.list_changed()
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量创建列表
|
||||
* @param position 列表位置
|
||||
* @param lists 列表信息
|
||||
* @param isRemote 是否属于远程操作
|
||||
*/
|
||||
async list_create(position: number, lists: LX.List.UserListInfo[], isRemote: boolean = false) {
|
||||
await global.lx.worker.dbService.createUserLists(position, lists)
|
||||
this.emit('list_create', position, lists, isRemote)
|
||||
this.list_changed()
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除列表及列表内歌曲
|
||||
* @param ids 列表ids
|
||||
* @param isRemote 是否属于远程操作
|
||||
*/
|
||||
async list_remove(ids: string[], isRemote: boolean = false) {
|
||||
await global.lx.worker.dbService.removeUserLists(ids)
|
||||
this.emit('list_remove', ids, isRemote)
|
||||
this.list_changed()
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新列表信息
|
||||
* @param lists 列表信息
|
||||
* @param isRemote 是否属于远程操作
|
||||
*/
|
||||
async list_update(lists: LX.List.UserListInfo[], isRemote: boolean = false) {
|
||||
await global.lx.worker.dbService.updateUserLists(lists)
|
||||
this.emit('list_update', lists, isRemote)
|
||||
this.list_changed()
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新列表位置
|
||||
* @param position 列表位置
|
||||
* @param ids 列表ids
|
||||
* @param isRemote 是否属于远程操作
|
||||
*/
|
||||
async list_update_position(position: number, ids: string[], isRemote: boolean = false) {
|
||||
await global.lx.worker.dbService.updateUserListsPosition(position, ids)
|
||||
this.emit('list_update_position', position, ids, isRemote)
|
||||
this.list_changed()
|
||||
}
|
||||
|
||||
/**
|
||||
* 覆盖列表内歌曲
|
||||
* @param listId 列表id
|
||||
* @param musicInfos 音乐信息
|
||||
* @param isRemote 是否属于远程操作
|
||||
*/
|
||||
async list_music_overwrite(listId: string, musicInfos: LX.Music.MusicInfo[], isRemote: boolean = false) {
|
||||
await global.lx.worker.dbService.musicOverwrite(listId, musicInfos)
|
||||
this.emit('list_music_overwrite', listId, musicInfos, isRemote)
|
||||
this.list_changed()
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加歌曲到列表
|
||||
* @param listId 列表id
|
||||
* @param musicInfos 添加的歌曲信息
|
||||
* @param addMusicLocationType 添加在到列表的位置
|
||||
* @param isRemote 是否属于远程操作
|
||||
*/
|
||||
async list_music_add(listId: string, musicInfos: LX.Music.MusicInfo[], addMusicLocationType: LX.AddMusicLocationType, isRemote: boolean = false) {
|
||||
await global.lx.worker.dbService.musicsAdd(listId, musicInfos, addMusicLocationType)
|
||||
this.emit('list_music_add', listId, musicInfos, addMusicLocationType, isRemote)
|
||||
this.list_changed()
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量移动歌曲
|
||||
* @param fromId 源列表id
|
||||
* @param toId 目标列表id
|
||||
* @param musicInfos 移动的歌曲信息
|
||||
* @param addMusicLocationType 添加在到列表的位置
|
||||
* @param isRemote 是否属于远程操作
|
||||
*/
|
||||
async list_music_move(fromId: string, toId: string, musicInfos: LX.Music.MusicInfo[], addMusicLocationType: LX.AddMusicLocationType, isRemote: boolean = false) {
|
||||
await global.lx.worker.dbService.musicsMove(fromId, toId, musicInfos, addMusicLocationType)
|
||||
this.emit('list_music_move', fromId, toId, musicInfos, addMusicLocationType, isRemote)
|
||||
this.list_changed()
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量移除歌曲
|
||||
* @param listId
|
||||
* @param listId 列表Id
|
||||
* @param ids 要删除歌曲的id
|
||||
* @param isRemote 是否属于远程操作
|
||||
*/
|
||||
async list_music_remove(listId: string, ids: string[], isRemote: boolean = false) {
|
||||
await global.lx.worker.dbService.musicsRemove(listId, ids)
|
||||
this.emit('list_music_remove', listId, ids, isRemote)
|
||||
this.list_changed()
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新歌曲信息
|
||||
* @param musicInfos 歌曲&列表信息
|
||||
* @param isRemote 是否属于远程操作
|
||||
*/
|
||||
async list_music_update(musicInfos: LX.List.ListActionMusicUpdate, isRemote: boolean = false) {
|
||||
await global.lx.worker.dbService.musicsUpdate(musicInfos)
|
||||
this.emit('list_music_update', musicInfos, isRemote)
|
||||
this.list_changed()
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空列表内的歌曲
|
||||
* @param listId 列表Id
|
||||
* @param isRemote 是否属于远程操作
|
||||
*/
|
||||
async list_music_clear(listId: string, isRemote: boolean = false) {
|
||||
await global.lx.worker.dbService.musicsClear(listId)
|
||||
this.emit('list_music_clear', listId, isRemote)
|
||||
this.list_changed()
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新歌曲位置
|
||||
* @param listId 列表ID
|
||||
* @param position 新位置
|
||||
* @param ids 歌曲id
|
||||
* @param isRemote 是否属于远程操作
|
||||
*/
|
||||
async list_music_update_position(listId: string, position: number, ids: string[], isRemote: boolean = false) {
|
||||
await global.lx.worker.dbService.musicsPositionUpdate(listId, position, ids)
|
||||
this.emit('list_music_update_position', listId, position, ids, isRemote)
|
||||
this.list_changed()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
type EventMethods = Omit<EventType, keyof EventEmitter>
|
||||
declare class EventType extends Event {
|
||||
on<K extends keyof EventMethods>(event: K, listener: EventMethods[K]): this
|
||||
once<K extends keyof EventMethods>(event: K, listener: EventMethods[K]): this
|
||||
off<K extends keyof EventMethods>(event: K, listener: EventMethods[K]): this
|
||||
}
|
||||
export declare type Type = Omit<EventType, keyof Omit<EventEmitter, 'on' | 'off' | 'once'>>
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Event as App, Type as AppType } from './AppEvent'
|
||||
import { Event as List, Type as ListType } from './ListEvent'
|
||||
|
||||
export {
|
||||
AppType,
|
||||
ListType,
|
||||
}
|
||||
|
||||
export const createAppEvent = (): AppType => {
|
||||
return new App()
|
||||
}
|
||||
|
||||
export const createListEvent = (): ListType => {
|
||||
return new List()
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
const { EventEmitter } = require('events')
|
||||
const { common: COMMON_EVENT_NAME } = require('./_name')
|
||||
|
||||
class Common extends EventEmitter {
|
||||
initSetting() {
|
||||
this.emit(COMMON_EVENT_NAME.initConfig)
|
||||
this.configStatus(null)
|
||||
}
|
||||
|
||||
configStatus(name) {
|
||||
this.emit(COMMON_EVENT_NAME.configStatus, name)
|
||||
}
|
||||
|
||||
saveMyList(data) {
|
||||
this.emit(COMMON_EVENT_NAME.saveMyList, data)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Common
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
const { EventEmitter } = require('events')
|
||||
const { hotKey: HOT_KEY_EVENT_NAME } = require('./_name')
|
||||
const { saveAppHotKeyConfig } = require('../utils')
|
||||
|
||||
class HotKey extends EventEmitter {
|
||||
init() {
|
||||
this.emit(HOT_KEY_EVENT_NAME.init)
|
||||
}
|
||||
|
||||
saveConfig(config, source) {
|
||||
if (config) saveAppHotKeyConfig(config)
|
||||
this.emit(HOT_KEY_EVENT_NAME.config, config, source)
|
||||
}
|
||||
|
||||
keyDown(info) {
|
||||
this.emit(HOT_KEY_EVENT_NAME.keyDown, info)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = HotKey
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
const { EventEmitter } = require('events')
|
||||
const { mainWindow: MAIN_WINDOW_EVENT_NAME } = require('./_name')
|
||||
|
||||
class MainWindow extends EventEmitter {
|
||||
setLyricInfo(info) {
|
||||
this.emit(MAIN_WINDOW_EVENT_NAME.setLyricInfo, info)
|
||||
}
|
||||
|
||||
quit() {
|
||||
this.emit(MAIN_WINDOW_EVENT_NAME.quit)
|
||||
}
|
||||
|
||||
toggleMinimize() {
|
||||
this.emit(MAIN_WINDOW_EVENT_NAME.toggle_minimize)
|
||||
}
|
||||
|
||||
toggleHide() {
|
||||
this.emit(MAIN_WINDOW_EVENT_NAME.toggle_hide)
|
||||
}
|
||||
|
||||
readyToShow() {
|
||||
this.emit(MAIN_WINDOW_EVENT_NAME.ready_to_show)
|
||||
}
|
||||
|
||||
show() {
|
||||
this.emit(MAIN_WINDOW_EVENT_NAME.show)
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.emit(MAIN_WINDOW_EVENT_NAME.focus)
|
||||
}
|
||||
|
||||
blur() {
|
||||
this.emit(MAIN_WINDOW_EVENT_NAME.blur)
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.emit(MAIN_WINDOW_EVENT_NAME.hide)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MainWindow
|
||||
@@ -1,14 +0,0 @@
|
||||
const { EventEmitter } = require('events')
|
||||
const { tray: TRAY_EVENT_NAME } = require('./_name')
|
||||
|
||||
class Tray extends EventEmitter {
|
||||
create() {
|
||||
this.emit(TRAY_EVENT_NAME.create)
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.emit(TRAY_EVENT_NAME.destroy)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Tray
|
||||
@@ -1,15 +0,0 @@
|
||||
const { EventEmitter } = require('events')
|
||||
const { winLyric: WIN_LYRIC_EVENT_NAME } = require('./_name')
|
||||
// const { updateSetting } = require('../utils')
|
||||
|
||||
class WinLyric extends EventEmitter {
|
||||
create() {
|
||||
this.emit(WIN_LYRIC_EVENT_NAME.create)
|
||||
}
|
||||
|
||||
close() {
|
||||
this.emit(WIN_LYRIC_EVENT_NAME.close)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WinLyric
|
||||
@@ -1,38 +0,0 @@
|
||||
exports.common = {
|
||||
initConfig: 'initConfig',
|
||||
configStatus: 'config',
|
||||
saveMyList: 'saveMyList',
|
||||
}
|
||||
|
||||
exports.mainWindow = {
|
||||
name: 'mainWindow',
|
||||
setLyricInfo: 'setLyricInfo',
|
||||
destroy: 'destroy',
|
||||
quit: 'quit',
|
||||
toggle_minimize: 'toggle_minimize',
|
||||
toggle_hide: 'toggle_hide',
|
||||
ready_to_show: 'ready_to_show',
|
||||
show: 'show',
|
||||
hide: 'hide',
|
||||
focus: 'focus',
|
||||
blur: 'blur',
|
||||
}
|
||||
|
||||
exports.tray = {
|
||||
name: 'tray',
|
||||
create: 'create',
|
||||
destroy: 'destroy',
|
||||
}
|
||||
|
||||
exports.winLyric = {
|
||||
name: 'winLyric',
|
||||
create: 'create',
|
||||
close: 'close',
|
||||
}
|
||||
|
||||
exports.hotKey = {
|
||||
name: 'hotKey',
|
||||
init: 'init',
|
||||
config: 'config',
|
||||
keyDown: 'keyDown',
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
if (!global.lx_event) global.lx_event = {}
|
||||
|
||||
const Common = require('./Common')
|
||||
const MainWindow = require('./MainWindow')
|
||||
const Tray = require('./Tray')
|
||||
const WinLyric = require('./WinLyric')
|
||||
const HotKey = require('./HotKey')
|
||||
|
||||
const { Event: TaskBar } = require('../modules/taskbar')
|
||||
const { Event: UserApi } = require('../modules/userApi')
|
||||
const { Event: Sync } = require('../modules/sync')
|
||||
|
||||
if (!global.lx_event.common) global.lx_event.common = new Common()
|
||||
if (!global.lx_event.mainWindow) global.lx_event.mainWindow = new MainWindow()
|
||||
if (!global.lx_event.tray) global.lx_event.tray = new Tray()
|
||||
if (!global.lx_event.winLyric) global.lx_event.winLyric = new WinLyric()
|
||||
if (!global.lx_event.hotKey) global.lx_event.hotKey = new HotKey()
|
||||
|
||||
if (!global.lx_event.taskbar) global.lx_event.taskbar = new TaskBar()
|
||||
if (!global.lx_event.userApi) global.lx_event.userApi = new UserApi()
|
||||
if (!global.lx_event.sync) global.lx_event.sync = new Sync()
|
||||
@@ -4,9 +4,10 @@
|
||||
* modify this file, but it can be used to extend your development
|
||||
* environment.
|
||||
*/
|
||||
const electron = require('electron')
|
||||
const electronDebug = require('electron-debug')
|
||||
const { default: installExtension, VUEJS_DEVTOOLS } = require('electron-devtools-installer')
|
||||
|
||||
import { app } from 'electron'
|
||||
import * as electronDebug from 'electron-debug'
|
||||
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
|
||||
// Install `electron-debug` with `devtron`
|
||||
electronDebug({
|
||||
showDevTools: true,
|
||||
@@ -14,10 +15,10 @@ electronDebug({
|
||||
})
|
||||
|
||||
// Install `vue-devtools`
|
||||
electron.app.on('ready', () => {
|
||||
app.on('ready', () => {
|
||||
installExtension(VUEJS_DEVTOOLS)
|
||||
.then(name => console.log(`Added Extension: ${name}`))
|
||||
.catch(err => console.log('An error occurred: ', err))
|
||||
.then((name: string) => console.log(`Added Extension: ${name}`))
|
||||
.catch((err: Error) => console.log('An error occurred: ', err))
|
||||
})
|
||||
|
||||
// Require `main` process to boot app
|
||||
@@ -1,250 +0,0 @@
|
||||
const { app, BrowserWindow, shell, nativeTheme } = require('electron')
|
||||
const path = require('path')
|
||||
|
||||
const urlSchemeRxp = /^lxmusic:\/\//
|
||||
|
||||
// 单例应用程序
|
||||
if (!app.requestSingleInstanceLock()) {
|
||||
app.quit()
|
||||
process.exit(0)
|
||||
}
|
||||
if (!global.modules) global.modules = {}
|
||||
app.on('second-instance', (event, argv, cwd) => {
|
||||
for (const param of argv) {
|
||||
if (urlSchemeRxp.test(param)) {
|
||||
global.envParams.deeplink = param
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (global.modules.mainWindow) {
|
||||
if (global.modules.mainWindow.isMinimized()) {
|
||||
global.modules.mainWindow.restore()
|
||||
}
|
||||
if (global.modules.mainWindow.isVisible()) {
|
||||
global.modules.mainWindow.focus()
|
||||
} else {
|
||||
global.modules.mainWindow.show()
|
||||
}
|
||||
} else {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
// windows平台下如果应用目录下存在 portable 文件夹则将数据存在此文件下
|
||||
if (process.platform === 'win32') {
|
||||
const fs = require('fs')
|
||||
const portablePath = path.join(path.dirname(app.getPath('exe')), '/portable')
|
||||
if (fs.existsSync(portablePath)) {
|
||||
app.setPath('appData', portablePath)
|
||||
const appDataPath = path.join(portablePath, '/userData')
|
||||
if (!fs.existsSync(appDataPath)) fs.mkdirSync(appDataPath)
|
||||
app.setPath('userData', appDataPath)
|
||||
}
|
||||
}
|
||||
|
||||
const isDev = global.isDev = process.env.NODE_ENV !== 'production'
|
||||
require('./env')
|
||||
// console.log(global.envParams.cmdParams)
|
||||
|
||||
// Is disable hardware acceleration
|
||||
if (global.envParams.cmdParams.dha) app.disableHardwareAcceleration()
|
||||
|
||||
if (global.envParams.cmdParams.dt == null && global.envParams.cmdParams.nt != null) global.envParams.cmdParams.dt = global.envParams.cmdParams.nt
|
||||
if (global.envParams.cmdParams.dhmkh) app.commandLine.appendSwitch('disable-features', 'HardwareMediaKeyHandling')
|
||||
// fix linux transparent fail. https://github.com/electron/electron/issues/25153#issuecomment-843688494
|
||||
if (process.platform == 'linux') app.commandLine.appendSwitch('use-gl', 'desktop')
|
||||
|
||||
// https://github.com/electron/electron/issues/22691
|
||||
app.commandLine.appendSwitch('wm-window-animations-disabled')
|
||||
|
||||
// proxy
|
||||
if (global.envParams.cmdParams['proxy-server']) {
|
||||
app.commandLine.appendSwitch('proxy-server', global.envParams.cmdParams['proxy-server'])
|
||||
app.commandLine.appendSwitch('proxy-bypass-list', global.envParams.cmdParams['proxy-bypass-list'] ?? '<local>')
|
||||
}
|
||||
// if (global.envParams.cmdParams['proxy-pac-url']) app.commandLine.appendSwitch('proxy-pac-url', global.envParams.cmdParams['proxy-pac-url'])
|
||||
|
||||
// deep link
|
||||
app.on('open-url', (event, url) => {
|
||||
if (!urlSchemeRxp.test(url)) return
|
||||
event.preventDefault()
|
||||
global.envParams.deeplink = url
|
||||
if (global.modules.mainWindow) {
|
||||
if (global.modules.mainWindow.isMinimized()) {
|
||||
global.modules.mainWindow.restore()
|
||||
}
|
||||
if (global.modules.mainWindow.isVisible()) {
|
||||
global.modules.mainWindow.focus()
|
||||
} else {
|
||||
global.modules.mainWindow.show()
|
||||
}
|
||||
} else if (global.modules.mainWindow === null) {
|
||||
init()
|
||||
}
|
||||
})
|
||||
// if (isDev && process.platform === 'win32') {
|
||||
// // Set the path of electron.exe and your app.
|
||||
// // These two additional parameters are only available on windows.
|
||||
// // console.log(process.execPath, process.argv)
|
||||
// app.setAsDefaultProtocolClient('lxmusic', process.execPath, process.argv.slice(1))
|
||||
// }
|
||||
if (!isDev) app.setAsDefaultProtocolClient('lxmusic')
|
||||
|
||||
const { navigationUrlWhiteList, themes } = require('../common/config')
|
||||
const { getWindowSizeInfo, initSetting, updateSetting } = require('./utils')
|
||||
const { isMac, isLinux, initHotKey } = require('../common/utils')
|
||||
|
||||
|
||||
// https://github.com/electron/electron/issues/18397
|
||||
// 开发模式下为true时 多次引入native模块会导致渲染进程卡死
|
||||
// https://github.com/electron/electron/issues/22791
|
||||
// app.allowRendererProcessReuse = !isDev
|
||||
|
||||
|
||||
app.on('web-contents-created', (event, contents) => {
|
||||
contents.on('will-navigate', (event, navigationUrl) => {
|
||||
if (isDev) return console.log('navigation to url:', navigationUrl)
|
||||
if (!navigationUrlWhiteList.some(url => url.test(navigationUrl))) return event.preventDefault()
|
||||
console.log('navigation to url:', navigationUrl)
|
||||
})
|
||||
contents.setWindowOpenHandler(({ url }) => {
|
||||
if (!/^devtools/.test(url) && /^https?:\/\//.test(url)) {
|
||||
shell.openExternal(url)
|
||||
}
|
||||
console.log(url)
|
||||
return { action: 'deny' }
|
||||
})
|
||||
contents.on('will-attach-webview', (event, webPreferences, params) => {
|
||||
// Strip away preload scripts if unused or verify their location is legitimate
|
||||
delete webPreferences.preload
|
||||
delete webPreferences.preloadURL
|
||||
|
||||
// Disable Node.js integration
|
||||
webPreferences.nodeIntegration = false
|
||||
|
||||
// Verify URL being loaded
|
||||
if (!navigationUrlWhiteList.some(url => url.test(params.src))) {
|
||||
event.preventDefault()
|
||||
}
|
||||
})
|
||||
|
||||
// disable create dictionary
|
||||
// https://github.com/lyswhut/lx-music-desktop/issues/773
|
||||
contents.session.setSpellCheckerDictionaryDownloadURL('http://0.0.0.0')
|
||||
})
|
||||
|
||||
|
||||
require('../common/error')
|
||||
require('./events')
|
||||
require('./event')
|
||||
require('./rendererEvents')
|
||||
const winEvent = require('./rendererEvents/winEvent')
|
||||
const autoUpdate = require('./utils/autoUpdate')
|
||||
|
||||
|
||||
let winURL
|
||||
|
||||
if (isDev) {
|
||||
// eslint-disable-next-line no-undef
|
||||
global.__static = __static
|
||||
winURL = 'http://localhost:9080'
|
||||
} else {
|
||||
global.__static = path.join(__dirname, '/static')
|
||||
winURL = `file://${path.join(__dirname, 'index.html')}`
|
||||
}
|
||||
|
||||
function createWindow() {
|
||||
const windowSizeInfo = getWindowSizeInfo(global.appSetting)
|
||||
/**
|
||||
* Initial window options
|
||||
*/
|
||||
const options = {
|
||||
height: windowSizeInfo.height,
|
||||
useContentSize: true,
|
||||
width: windowSizeInfo.width,
|
||||
frame: false,
|
||||
transparent: !global.envParams.cmdParams.dt,
|
||||
enableRemoteModule: false,
|
||||
// icon: path.join(global.__static, isWin ? 'icons/256x256.ico' : 'icons/512x512.png'),
|
||||
resizable: false,
|
||||
maximizable: false,
|
||||
fullscreenable: true,
|
||||
show: false,
|
||||
webPreferences: {
|
||||
contextIsolation: false,
|
||||
webSecurity: !isDev,
|
||||
nodeIntegration: true,
|
||||
spellcheck: false, // 禁用拼写检查器
|
||||
},
|
||||
}
|
||||
if (global.appSetting.startInFullscreen) {
|
||||
options.fullscreen = true
|
||||
if (isLinux) options.resizable = true
|
||||
}
|
||||
global.modules.mainWindow = new BrowserWindow(options)
|
||||
|
||||
const shouldUseDarkColors = nativeTheme.shouldUseDarkColors
|
||||
const themeId = global.appSetting.theme.id == 'auto'
|
||||
? shouldUseDarkColors
|
||||
? global.appSetting.theme.darkId
|
||||
: global.appSetting.theme.lightId
|
||||
: global.appSetting.theme.id
|
||||
const themeClass = themes.find(t => t.id == themeId)?.className ?? themes[0].className
|
||||
global.modules.mainWindow.loadURL(winURL + `?dt=${!!global.envParams.cmdParams.dt}&dark=${shouldUseDarkColors}&theme=${themeClass}`)
|
||||
|
||||
winEvent(global.modules.mainWindow)
|
||||
if (global.envParams.cmdParams.odt) require('@main/utils').openDevTools(global.modules.mainWindow.webContents)
|
||||
// global.modules.mainWindow.webContents.openDevTools()
|
||||
|
||||
if (!isDev) autoUpdate()
|
||||
}
|
||||
|
||||
global.appHotKey = {
|
||||
enable: true,
|
||||
config: {},
|
||||
state: null,
|
||||
}
|
||||
|
||||
global.lx_core = {
|
||||
setAppConfig(setting, name) {
|
||||
updateSetting(setting)
|
||||
global.lx_event.common.configStatus(name)
|
||||
},
|
||||
}
|
||||
|
||||
function init() {
|
||||
console.log('init')
|
||||
initSetting()
|
||||
global.appHotKey.config = initHotKey()
|
||||
global.lx_event.common.initSetting()
|
||||
global.lx_event.hotKey.init()
|
||||
createWindow()
|
||||
}
|
||||
|
||||
// https://github.com/electron/electron/issues/16809
|
||||
app.on('ready', isLinux ? () => setTimeout(init, 300) : init)
|
||||
|
||||
app.on('activate', () => {
|
||||
if (global.modules.mainWindow) {
|
||||
if (global.modules.mainWindow.isMinimized()) {
|
||||
global.modules.mainWindow.restore()
|
||||
} else if (global.modules.mainWindow.isVisible()) {
|
||||
global.modules.mainWindow.focus()
|
||||
} else {
|
||||
global.modules.mainWindow.show()
|
||||
}
|
||||
} else if (global.modules.mainWindow === null) {
|
||||
init()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (isMac) {
|
||||
global.lx_event.tray.destroy()
|
||||
} else {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
require('./modules')
|
||||
@@ -0,0 +1,33 @@
|
||||
import { app } from 'electron'
|
||||
import '@common/error'
|
||||
import {
|
||||
initGlobalData,
|
||||
initSingleInstanceHandle,
|
||||
applyElectronEnvParams,
|
||||
setUserDataPath,
|
||||
registerDeeplink,
|
||||
listenerAppEvent,
|
||||
} from './app'
|
||||
import { isLinux } from '@common/utils'
|
||||
import { initAppSetting } from '@main/app'
|
||||
import registerModules from '@main/modules'
|
||||
|
||||
// 初始化应用
|
||||
const init = () => {
|
||||
console.log('init')
|
||||
void initAppSetting().then(() => {
|
||||
registerModules()
|
||||
global.lx.event_app.app_inited()
|
||||
})
|
||||
}
|
||||
|
||||
initGlobalData()
|
||||
initSingleInstanceHandle()
|
||||
applyElectronEnvParams()
|
||||
setUserDataPath()
|
||||
registerDeeplink(init)
|
||||
listenerAppEvent(init)
|
||||
|
||||
|
||||
// https://github.com/electron/electron/issues/16809
|
||||
void app.whenReady().then(() => isLinux ? setTimeout(init, 300) : init())
|
||||
@@ -1,48 +0,0 @@
|
||||
const { app, Menu } = require('electron')
|
||||
const { isMac } = require('../../common/utils')
|
||||
|
||||
if (isMac) {
|
||||
const template = [
|
||||
{
|
||||
label: app.getName(),
|
||||
submenu: [
|
||||
{ label: '关于洛雪音乐', role: 'about' },
|
||||
{ type: 'separator' },
|
||||
{ label: '隐藏', role: 'hide' },
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: '退出',
|
||||
accelerator: 'Command+Q',
|
||||
click() {
|
||||
global.isQuitting = true
|
||||
app.quit()
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '窗口',
|
||||
role: 'window',
|
||||
submenu: [
|
||||
{ label: '最小化', role: 'minimize', accelerator: 'Command+W' },
|
||||
{ label: '关闭', role: 'close' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '编辑',
|
||||
submenu: [
|
||||
{ label: '撤销', accelerator: 'Command+Z', role: 'undo' },
|
||||
{ label: '恢复', accelerator: 'Shift+Command+Z', role: 'redo' },
|
||||
{ type: 'separator' },
|
||||
{ label: '剪切', accelerator: 'Command+X', role: 'cut' },
|
||||
{ label: '复制', accelerator: 'Command+C', role: 'copy' },
|
||||
{ label: '粘贴', accelerator: 'Command+V', role: 'paste' },
|
||||
{ label: '选择全部', accelerator: 'Command+A', role: 'selectAll' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
Menu.setApplicationMenu(Menu.buildFromTemplate(template))
|
||||
} else {
|
||||
Menu.setApplicationMenu(null)
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { app, Menu } from 'electron'
|
||||
import { isMac } from '@common/utils'
|
||||
import { quitApp } from '@main/app'
|
||||
|
||||
|
||||
export default () => {
|
||||
if (isMac) {
|
||||
const template: Electron.MenuItemConstructorOptions[] = [
|
||||
{
|
||||
label: app.getName(),
|
||||
submenu: [
|
||||
{ label: '关于洛雪音乐', role: 'about' },
|
||||
{ type: 'separator' },
|
||||
{ label: '隐藏', role: 'hide' },
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: '退出',
|
||||
accelerator: 'Command+Q',
|
||||
click() {
|
||||
quitApp()
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '窗口',
|
||||
role: 'window',
|
||||
submenu: [
|
||||
{ label: '最小化', role: 'minimize', accelerator: 'Command+W' },
|
||||
{ label: '关闭', role: 'close' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '编辑',
|
||||
submenu: [
|
||||
{ label: '撤销', accelerator: 'Command+Z', role: 'undo' },
|
||||
{ label: '恢复', accelerator: 'Shift+Command+Z', role: 'redo' },
|
||||
{ type: 'separator' },
|
||||
{ label: '剪切', accelerator: 'Command+X', role: 'cut' },
|
||||
{ label: '复制', accelerator: 'Command+C', role: 'copy' },
|
||||
{ label: '粘贴', accelerator: 'Command+V', role: 'paste' },
|
||||
{ label: '选择全部', accelerator: 'Command+A', role: 'selectAll' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
Menu.setApplicationMenu(Menu.buildFromTemplate(template))
|
||||
} else {
|
||||
Menu.setApplicationMenu(null)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export { registerRendererEvents } from './winRendererEvent'
|
||||
|
||||
export { default } from './rendererEvent'
|
||||
@@ -0,0 +1,26 @@
|
||||
import { mainHandle, mainOn } from '@common/mainIpc'
|
||||
import { CMMON_EVENT_NAME } from '@common/ipcNames'
|
||||
import { getFonts } from '@main/utils/fontManage'
|
||||
|
||||
// 公共操作事件(公共,只注册一次)
|
||||
export default () => {
|
||||
mainHandle<LX.AppSetting>(CMMON_EVENT_NAME.get_app_setting, async() => {
|
||||
return global.lx.appSetting
|
||||
})
|
||||
mainHandle<Partial<LX.AppSetting>>(CMMON_EVENT_NAME.set_app_setting, async({ params: config }) => {
|
||||
global.lx.event_app.update_config(config)
|
||||
})
|
||||
|
||||
mainHandle<LX.EnvParams>(CMMON_EVENT_NAME.get_env_params, async() => {
|
||||
return global.envParams
|
||||
})
|
||||
|
||||
mainOn(CMMON_EVENT_NAME.clear_env_params_deeplink, () => {
|
||||
global.envParams.deeplink = null
|
||||
})
|
||||
|
||||
mainHandle<string[]>(CMMON_EVENT_NAME.get_system_fonts, async() => {
|
||||
return await getFonts()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { CMMON_EVENT_NAME } from '@common/ipcNames'
|
||||
|
||||
// 发送列表操作事件到渲染进程的注册方法
|
||||
// 哪个渲染进程需要接收则引入此方法注册
|
||||
export const registerRendererEvents = (sendEvent: <T = any>(name: string, params?: T | undefined) => void) => {
|
||||
const sendDeeplink = (link: string) => {
|
||||
sendEvent(CMMON_EVENT_NAME.deeplink, link)
|
||||
}
|
||||
const sendSystemThemeChange = () => {
|
||||
sendEvent(CMMON_EVENT_NAME.theme_change, global.lx.theme)
|
||||
}
|
||||
|
||||
global.lx.event_app.on('deeplink', sendDeeplink)
|
||||
global.lx.event_app.on('theme_change', sendSystemThemeChange)
|
||||
|
||||
return () => {
|
||||
global.lx.event_app.off('deeplink', sendDeeplink)
|
||||
global.lx.event_app.off('theme_change', sendSystemThemeChange)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import common from './common'
|
||||
import list from './list'
|
||||
|
||||
export default () => {
|
||||
common()
|
||||
list()
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export { registerRendererEvents } from './winRendererEvent'
|
||||
|
||||
export { default } from './rendererEvent'
|
||||
@@ -0,0 +1,54 @@
|
||||
import { mainHandle } from '@common/mainIpc'
|
||||
import { PLAYER_EVENT_NAME } from '@common/ipcNames'
|
||||
|
||||
// 列表操作事件(公共,只注册一次)
|
||||
export default () => {
|
||||
mainHandle<LX.List.UserListInfo[]>(PLAYER_EVENT_NAME.list_get, async() => {
|
||||
return await global.lx.worker.dbService.getAllUserList()
|
||||
})
|
||||
mainHandle<LX.List.ListActionDataOverwrite>(PLAYER_EVENT_NAME.list_data_overwire, async({ params: listData }) => {
|
||||
await global.lx.event_list.list_data_overwrite(listData, false)
|
||||
})
|
||||
mainHandle<LX.List.ListActionAdd>(PLAYER_EVENT_NAME.list_add, async({ params: { position, listInfos } }) => {
|
||||
await global.lx.event_list.list_create(position, listInfos, false)
|
||||
})
|
||||
mainHandle<LX.List.ListActionRemove>(PLAYER_EVENT_NAME.list_remove, async({ params: ids }) => {
|
||||
await global.lx.event_list.list_remove(ids, false)
|
||||
})
|
||||
mainHandle<LX.List.ListActionUpdate>(PLAYER_EVENT_NAME.list_update, async({ params: listInfos }) => {
|
||||
await global.lx.event_list.list_update(listInfos, false)
|
||||
})
|
||||
mainHandle<LX.List.ListActionUpdatePosition>(PLAYER_EVENT_NAME.list_update_position, async({ params: { position, ids } }) => {
|
||||
await global.lx.event_list.list_update_position(position, ids, false)
|
||||
})
|
||||
mainHandle<string, LX.Music.MusicInfo[]>(PLAYER_EVENT_NAME.list_music_get, async({ params: listId }) => {
|
||||
return await global.lx.worker.dbService.getListMusics(listId)
|
||||
})
|
||||
mainHandle<LX.List.ListActionMusicAdd>(PLAYER_EVENT_NAME.list_music_add, async({ params: { id, musicInfos, addMusicLocationType } }) => {
|
||||
await global.lx.event_list.list_music_add(id, musicInfos, addMusicLocationType, false)
|
||||
})
|
||||
mainHandle<LX.List.ListActionMusicMove>(PLAYER_EVENT_NAME.list_music_move, async({ params: { fromId, toId, musicInfos, addMusicLocationType } }) => {
|
||||
await global.lx.event_list.list_music_move(fromId, toId, musicInfos, addMusicLocationType, false)
|
||||
})
|
||||
mainHandle<LX.List.ListActionMusicRemove>(PLAYER_EVENT_NAME.list_music_remove, async({ params: { listId, ids } }) => {
|
||||
await global.lx.event_list.list_music_remove(listId, ids, false)
|
||||
})
|
||||
mainHandle<LX.List.ListActionMusicUpdate>(PLAYER_EVENT_NAME.list_music_update, async({ params: musicInfos }) => {
|
||||
await global.lx.event_list.list_music_update(musicInfos, false)
|
||||
})
|
||||
mainHandle<LX.List.ListActionMusicUpdatePosition>(PLAYER_EVENT_NAME.list_music_update_position, async({ params: { listId, position, ids } }) => {
|
||||
await global.lx.event_list.list_music_update_position(listId, position, ids, false)
|
||||
})
|
||||
mainHandle<LX.List.ListActionMusicOverwrite>(PLAYER_EVENT_NAME.list_music_overwrite, async({ params: { listId, musicInfos } }) => {
|
||||
await global.lx.event_list.list_music_overwrite(listId, musicInfos, false)
|
||||
})
|
||||
mainHandle<LX.List.ListActionMusicClear>(PLAYER_EVENT_NAME.list_music_clear, async({ params: listId }) => {
|
||||
await global.lx.event_list.list_music_clear(listId, false)
|
||||
})
|
||||
mainHandle<LX.List.ListActionCheckMusicExistList, boolean>(PLAYER_EVENT_NAME.list_music_check_exist, async({ params: { listId, musicInfoId } }) => {
|
||||
return await global.lx.worker.dbService.checkListExistMusic(listId, musicInfoId)
|
||||
})
|
||||
mainHandle<string, string[]>(PLAYER_EVENT_NAME.list_music_get_list_ids, async({ params: musicInfoId }) => {
|
||||
return await global.lx.worker.dbService.getMusicExistListIds(musicInfoId)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { PLAYER_EVENT_NAME } from '@common/ipcNames'
|
||||
|
||||
// 发送列表操作事件到渲染进程的注册方法
|
||||
// 哪个渲染进程需要接收则引入此方法注册
|
||||
export const registerRendererEvents = (sendEvent: <T = any>(name: string, params?: T | undefined) => void) => {
|
||||
const list_data_overwrite = async(listData: LX.List.ListActionDataOverwrite) => {
|
||||
sendEvent<LX.List.ListActionDataOverwrite>(PLAYER_EVENT_NAME.list_data_overwire, listData)
|
||||
}
|
||||
const list_create = async(position: number, listInfos: LX.List.UserListInfo[]) => {
|
||||
sendEvent<LX.List.ListActionAdd>(PLAYER_EVENT_NAME.list_add, { position, listInfos })
|
||||
}
|
||||
const list_remove = async(ids: string[]) => {
|
||||
sendEvent<LX.List.ListActionRemove>(PLAYER_EVENT_NAME.list_remove, ids)
|
||||
}
|
||||
const list_update = async(lists: LX.List.UserListInfo[]) => {
|
||||
sendEvent<LX.List.ListActionUpdate>(PLAYER_EVENT_NAME.list_update, lists)
|
||||
}
|
||||
const list_update_position = async(position: number, ids: string[]) => {
|
||||
sendEvent<LX.List.ListActionUpdatePosition>(PLAYER_EVENT_NAME.list_update_position, { position, ids })
|
||||
}
|
||||
const list_music_add = async(id: string, musicInfos: LX.Music.MusicInfo[], addMusicLocationType: LX.AddMusicLocationType) => {
|
||||
sendEvent<LX.List.ListActionMusicAdd>(PLAYER_EVENT_NAME.list_music_add, { id, musicInfos, addMusicLocationType })
|
||||
}
|
||||
const list_music_move = async(fromId: string, toId: string, musicInfos: LX.Music.MusicInfo[], addMusicLocationType: LX.AddMusicLocationType) => {
|
||||
sendEvent<LX.List.ListActionMusicMove>(PLAYER_EVENT_NAME.list_music_move, { fromId, toId, musicInfos, addMusicLocationType })
|
||||
}
|
||||
const list_music_remove = async(listId: string, ids: string[]) => {
|
||||
sendEvent<LX.List.ListActionMusicRemove>(PLAYER_EVENT_NAME.list_music_remove, { listId, ids })
|
||||
}
|
||||
const list_music_update = async(musicInfos: LX.List.ListActionMusicUpdate) => {
|
||||
sendEvent<LX.List.ListActionMusicUpdate>(PLAYER_EVENT_NAME.list_music_update, musicInfos)
|
||||
}
|
||||
const list_music_update_position = async(listId: string, position: number, ids: string[]) => {
|
||||
sendEvent<LX.List.ListActionMusicUpdatePosition>(PLAYER_EVENT_NAME.list_music_update_position, { listId, position, ids })
|
||||
}
|
||||
const list_music_overwrite = async(listId: string, musicInfos: LX.Music.MusicInfo[]) => {
|
||||
sendEvent<LX.List.ListActionMusicOverwrite>(PLAYER_EVENT_NAME.list_music_overwrite, { listId, musicInfos })
|
||||
}
|
||||
const list_music_clear = async(listId: string) => {
|
||||
sendEvent<LX.List.ListActionMusicClear>(PLAYER_EVENT_NAME.list_data_overwire, listId)
|
||||
}
|
||||
global.lx.event_list.on('list_data_overwrite', list_data_overwrite)
|
||||
global.lx.event_list.on('list_create', list_create)
|
||||
global.lx.event_list.on('list_remove', list_remove)
|
||||
global.lx.event_list.on('list_update', list_update)
|
||||
global.lx.event_list.on('list_update_position', list_update_position)
|
||||
global.lx.event_list.on('list_music_add', list_music_add)
|
||||
global.lx.event_list.on('list_music_move', list_music_move)
|
||||
global.lx.event_list.on('list_music_remove', list_music_remove)
|
||||
global.lx.event_list.on('list_music_update', list_music_update)
|
||||
global.lx.event_list.on('list_music_update_position', list_music_update_position)
|
||||
global.lx.event_list.on('list_music_overwrite', list_music_overwrite)
|
||||
global.lx.event_list.on('list_music_clear', list_music_clear)
|
||||
|
||||
return () => {
|
||||
global.lx.event_list.off('list_data_overwrite', list_data_overwrite)
|
||||
global.lx.event_list.off('list_create', list_create)
|
||||
global.lx.event_list.off('list_remove', list_remove)
|
||||
global.lx.event_list.off('list_update', list_update)
|
||||
global.lx.event_list.off('list_update_position', list_update_position)
|
||||
global.lx.event_list.off('list_music_add', list_music_add)
|
||||
global.lx.event_list.off('list_music_move', list_music_move)
|
||||
global.lx.event_list.off('list_music_remove', list_music_remove)
|
||||
global.lx.event_list.off('list_music_update', list_music_update)
|
||||
global.lx.event_list.off('list_music_update_position', list_music_update_position)
|
||||
global.lx.event_list.off('list_music_overwrite', list_music_overwrite)
|
||||
global.lx.event_list.off('list_music_clear', list_music_clear)
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
const { app } = require('electron')
|
||||
const { init, unRegisterHotkeyAll } = require('./utils')
|
||||
const { hotKey: HOT_KEY_EVENT_NAME } = require('../../events/_name')
|
||||
|
||||
global.appHotKey.state = {}
|
||||
|
||||
app.on('will-quit', unRegisterHotkeyAll)
|
||||
|
||||
global.lx_event.hotKey.on(HOT_KEY_EVENT_NAME.init, init)
|
||||
|
||||
|
||||
require('./rendererEvent')
|
||||
@@ -0,0 +1,9 @@
|
||||
import { app } from 'electron'
|
||||
import { unRegisterHotkeyAll } from './utils'
|
||||
|
||||
import init from './rendererEvent'
|
||||
|
||||
export default () => {
|
||||
app.on('will-quit', unRegisterHotkeyAll)
|
||||
init()
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
const {
|
||||
mainHandle,
|
||||
NAMES: {
|
||||
hotKey: ipcHotKeyNames,
|
||||
},
|
||||
} = require('../../../common/ipc')
|
||||
const { init, registerHotkey, unRegisterHotkey, unRegisterHotkeyAll } = require('./utils')
|
||||
|
||||
mainHandle(ipcHotKeyNames.set_config, async(event, { action, data, source }) => {
|
||||
switch (action) {
|
||||
case 'config':
|
||||
global.lx_event.hotKey.saveConfig(data, source)
|
||||
return true
|
||||
case 'enable':
|
||||
data ? init(true) : unRegisterHotkeyAll()
|
||||
return true
|
||||
case 'register':
|
||||
return registerHotkey(data)
|
||||
case 'unregister':
|
||||
return unRegisterHotkey(data)
|
||||
}
|
||||
})
|
||||
|
||||
mainHandle(ipcHotKeyNames.status, async() => global.appHotKey.state)
|
||||
|
||||
mainHandle(ipcHotKeyNames.enable, async(event, flag) => {
|
||||
flag ? init() : unRegisterHotkeyAll()
|
||||
})
|
||||
@@ -0,0 +1,32 @@
|
||||
import { HOTKEY_RENDERER_EVENT_NAME } from '@common/ipcNames'
|
||||
import { mainHandle } from '@common/mainIpc'
|
||||
import { init, registerHotkey, unRegisterHotkey, unRegisterHotkeyAll } from './utils'
|
||||
|
||||
|
||||
export default () => {
|
||||
mainHandle<LX.HotKeyActions, boolean>(HOTKEY_RENDERER_EVENT_NAME.set_config, async({ params }) => {
|
||||
switch (params.action) {
|
||||
case 'config':
|
||||
// global.lx.event_app.saveConfig(data, source)
|
||||
global.lx.event_app.hot_key_config_update(params.data)
|
||||
return true
|
||||
case 'enable':
|
||||
global.lx.hotKey.enable = params.data
|
||||
params.data ? init(true) : unRegisterHotkeyAll()
|
||||
return true
|
||||
case 'register':
|
||||
return registerHotkey(params.data)
|
||||
case 'unregister':
|
||||
unRegisterHotkey(params.data)
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
mainHandle<LX.HotKeyState>(HOTKEY_RENDERER_EVENT_NAME.status, async() => global.lx.hotKey.state)
|
||||
|
||||
mainHandle<boolean>(HOTKEY_RENDERER_EVENT_NAME.enable, async({ params: flag }) => {
|
||||
flag ? init() : unRegisterHotkeyAll()
|
||||
})
|
||||
|
||||
init()
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
const { globalShortcut } = require('electron')
|
||||
const { log } = require('../../../common/utils')
|
||||
|
||||
const handleKeyDown = key => {
|
||||
if (!global.appHotKey.enable) return
|
||||
global.lx_event.hotKey.keyDown({ type: 'global', key })
|
||||
}
|
||||
|
||||
const transformedKeyRxp = /(^|\+)[a-z]/g
|
||||
|
||||
const transformedKey = key => {
|
||||
if (key.includes('arrow')) key = key.replace(/arrow/g, '')
|
||||
return key.replace('mod', 'CommandOrControl').replace(transformedKeyRxp, l => l.toUpperCase())
|
||||
}
|
||||
exports.registerHotkey = ({ key, info }) => {
|
||||
if (global.appHotKey.state[key] && global.appHotKey.state[key].status) return true
|
||||
let transKey = transformedKey(key)
|
||||
// console.log('Register key:', transKey)
|
||||
if (!global.appHotKey.state[key]) {
|
||||
global.appHotKey.state[key] = {
|
||||
status: false,
|
||||
info: null,
|
||||
}
|
||||
}
|
||||
global.appHotKey.state[key].info = info
|
||||
let status = global.appHotKey.state[key].status = globalShortcut.isRegistered(transKey)
|
||||
? false
|
||||
: globalShortcut.register(transKey, () => {
|
||||
handleKeyDown(key)
|
||||
})
|
||||
return status
|
||||
}
|
||||
|
||||
exports.unRegisterHotkey = key => {
|
||||
let transKey = transformedKey(key)
|
||||
// console.log('Unregister key:', transKey)
|
||||
globalShortcut.unregister(transKey)
|
||||
delete global.appHotKey.state[key]
|
||||
}
|
||||
|
||||
exports.unRegisterHotkeyAll = () => {
|
||||
global.appHotKey.state = {}
|
||||
globalShortcut.unregisterAll()
|
||||
}
|
||||
|
||||
exports.handleKeyDown = handleKeyDown
|
||||
exports.transformedKey = transformedKey
|
||||
|
||||
const handleRegisterHotkey = data => {
|
||||
let ret = exports.registerHotkey(data)
|
||||
if (!ret) log.info('Register hot key failed:', data.key)
|
||||
}
|
||||
|
||||
|
||||
exports.init = (isForce = false) => {
|
||||
exports.unRegisterHotkeyAll()
|
||||
if (!isForce && !global.appHotKey.config.global.enable) return
|
||||
global.appHotKey.state = {}
|
||||
// console.log(global.appHotKey.config.global.keys)
|
||||
for (const key of Object.keys(global.appHotKey.config.global.keys)) {
|
||||
try {
|
||||
handleRegisterHotkey({ key, info: global.appHotKey.config.global.keys[key] })
|
||||
} catch (err) {
|
||||
log.info(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { globalShortcut } from 'electron'
|
||||
import { log } from '@common/utils'
|
||||
|
||||
export const handleKeyDown = (key: string) => {
|
||||
if (!global.lx.hotKey.enable) return
|
||||
global.lx.event_app.hot_key_down({ type: 'global', key })
|
||||
}
|
||||
|
||||
const transformedKeyRxp = /(^|\+)[a-z]/g
|
||||
|
||||
export const transformedKey = (key: string): string => {
|
||||
if (key.includes('arrow')) key = key.replace(/arrow/g, '')
|
||||
return key.replace('mod', 'CommandOrControl').replace(transformedKeyRxp, l => l.toUpperCase())
|
||||
}
|
||||
|
||||
export const registerHotkey = ({ key, info }: LX.RegisterKeyInfo): boolean => {
|
||||
let targetKey = global.lx.hotKey.state.get(key)
|
||||
if (targetKey?.status) return true
|
||||
const transKey = transformedKey(key)
|
||||
// console.log('Register key:', transKey)
|
||||
if (targetKey) {
|
||||
targetKey.info = info
|
||||
} else {
|
||||
targetKey = {
|
||||
status: false,
|
||||
info,
|
||||
}
|
||||
global.lx.hotKey.state.set(key, targetKey)
|
||||
}
|
||||
const status = targetKey.status = globalShortcut.isRegistered(transKey)
|
||||
? false
|
||||
: globalShortcut.register(transKey, () => {
|
||||
handleKeyDown(key)
|
||||
})
|
||||
return status
|
||||
}
|
||||
|
||||
export const unRegisterHotkey = (key: string) => {
|
||||
let transKey = transformedKey(key)
|
||||
// console.log('Unregister key:', transKey)
|
||||
globalShortcut.unregister(transKey)
|
||||
global.lx.hotKey.state.delete(key)
|
||||
}
|
||||
|
||||
export const unRegisterHotkeyAll = () => {
|
||||
global.lx.hotKey.state.clear()
|
||||
globalShortcut.unregisterAll()
|
||||
}
|
||||
|
||||
|
||||
const handleRegisterHotkey = (data: LX.RegisterKeyInfo) => {
|
||||
let ret = registerHotkey(data)
|
||||
if (!ret) log.info('Register hot key failed:', data.key)
|
||||
}
|
||||
|
||||
|
||||
export const init = (isForce = false) => {
|
||||
unRegisterHotkeyAll()
|
||||
if (!isForce && !global.lx.hotKey.config.global.enable) return
|
||||
// global.lx.hotKey.state = {}
|
||||
// console.log(global.lx.hotKey.config.global.keys)
|
||||
for (const key of Object.keys(global.lx.hotKey.config.global.keys)) {
|
||||
try {
|
||||
handleRegisterHotkey({ key, info: global.lx.hotKey.config.global.keys[key] })
|
||||
} catch (err) {
|
||||
log.info(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
require('./appMenu')
|
||||
require('./winLyric')
|
||||
require('./tray')
|
||||
require('./hotKey')
|
||||
@@ -0,0 +1,20 @@
|
||||
import registerUserApi from './userApi'
|
||||
import registerWinMain from './winMain'
|
||||
import registerHotKey from './hotKey'
|
||||
import registerTray from './tray'
|
||||
import registerAppMenu from './appMenu'
|
||||
import registerWinLyric from './winLyric'
|
||||
import registerCommonRenderers from './commonRenderers'
|
||||
|
||||
let isRegistered = false
|
||||
export default () => {
|
||||
if (isRegistered) return
|
||||
registerUserApi()
|
||||
registerCommonRenderers()
|
||||
registerWinMain()
|
||||
registerHotKey()
|
||||
registerTray()
|
||||
registerAppMenu()
|
||||
registerWinLyric()
|
||||
isRegistered = true
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
const Event = require('./event/event')
|
||||
const eventNames = require('./event/name')
|
||||
const modules = require('./modules')
|
||||
const { startServer, stopServer, getStatus, generateCode } = require('./server/server')
|
||||
|
||||
|
||||
module.exports = {
|
||||
startServer,
|
||||
stopServer,
|
||||
getStatus,
|
||||
generateCode,
|
||||
Event,
|
||||
eventNames,
|
||||
modules,
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// import Event from './event/event'
|
||||
// import eventNames from './event/name'
|
||||
import * as modules from './modules'
|
||||
import { startServer, stopServer, getStatus, generateCode } from './server/server'
|
||||
|
||||
|
||||
export {
|
||||
startServer,
|
||||
stopServer,
|
||||
getStatus,
|
||||
generateCode,
|
||||
// Event,
|
||||
// eventNames,
|
||||
modules,
|
||||
}
|
||||
|
||||
export default () => {
|
||||
global.lx.event_app.on('main_window_close', () => {
|
||||
void stopServer()
|
||||
})
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
exports.list = require('./list')
|
||||
@@ -0,0 +1 @@
|
||||
export * as list from './list'
|
||||
@@ -1,41 +0,0 @@
|
||||
const { encryptMsg, decryptMsg } = require('../server/utils')
|
||||
let io
|
||||
|
||||
const handleListAction = ({ action, data }) => {
|
||||
// console.log(action, data)
|
||||
global.lx_event.sync.action_list({ action, data })
|
||||
}
|
||||
|
||||
// const addMusic = (orderId, callback) => {
|
||||
// // ...
|
||||
// }
|
||||
|
||||
const broadcast = async(action, data, excludeIds = []) => {
|
||||
if (!io) return
|
||||
const sockets = await io.fetchSockets()
|
||||
for (const socket of sockets) {
|
||||
if (excludeIds.includes(socket.data.keyInfo.clientId)) continue
|
||||
socket.emit(action, encryptMsg(socket.data.keyInfo, data))
|
||||
}
|
||||
}
|
||||
|
||||
exports.sendListAction = (action, data) => {
|
||||
// io.sockets
|
||||
return broadcast('list:action', JSON.stringify({ action, data }))
|
||||
}
|
||||
|
||||
exports.registerListHandler = (_io, socket) => {
|
||||
io = _io
|
||||
socket.on('list:action', msg => {
|
||||
// console.log(msg)
|
||||
msg = decryptMsg(socket.data.keyInfo, msg)
|
||||
if (!msg) return
|
||||
handleListAction(JSON.parse(msg))
|
||||
broadcast('list:action', msg, [socket.data.keyInfo.clientId])
|
||||
// socket.broadcast.emit('list:action', { action: 'list_remove', data: { id: 'default', index: 0 } })
|
||||
})
|
||||
// socket.on('list:add', addMusic)
|
||||
}
|
||||
exports.unregisterListHandler = () => {
|
||||
io = null
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
// import { throttle } from '@common/utils/common'
|
||||
import { Server } from 'socket.io'
|
||||
// import { sendSyncActionList } from '@main/modules/winMain'
|
||||
import { encryptMsg, decryptMsg } from '../server/utils'
|
||||
|
||||
let io: Server | null
|
||||
let removeListener: (() => void) | null
|
||||
|
||||
type listAction = 'list:action'
|
||||
|
||||
const handleListAction = ({ action, data }: LX.Sync.ActionList) => {
|
||||
// console.log(action, data)
|
||||
|
||||
switch (action) {
|
||||
case 'list_data_overwrite':
|
||||
void global.lx.event_list.list_data_overwrite(data, true)
|
||||
break
|
||||
case 'list_create':
|
||||
void global.lx.event_list.list_create(data.position, data.listInfos, true)
|
||||
break
|
||||
case 'list_remove':
|
||||
void global.lx.event_list.list_remove(data, true)
|
||||
break
|
||||
case 'list_update':
|
||||
void global.lx.event_list.list_update(data, true)
|
||||
break
|
||||
case 'list_update_position':
|
||||
void global.lx.event_list.list_update_position(data.position, data.ids, true)
|
||||
break
|
||||
case 'list_music_add':
|
||||
void global.lx.event_list.list_music_add(data.id, data.musicInfos, data.addMusicLocationType, true)
|
||||
break
|
||||
case 'list_music_move':
|
||||
void global.lx.event_list.list_music_move(data.fromId, data.toId, data.musicInfos, data.addMusicLocationType, true)
|
||||
break
|
||||
case 'list_music_remove':
|
||||
void global.lx.event_list.list_music_remove(data.listId, data.ids, true)
|
||||
break
|
||||
case 'list_music_update':
|
||||
void global.lx.event_list.list_music_update(data, true)
|
||||
break
|
||||
case 'list_music_update_position':
|
||||
void global.lx.event_list.list_music_update_position(data.listId, data.position, data.ids, true)
|
||||
break
|
||||
case 'list_music_overwrite':
|
||||
void global.lx.event_list.list_music_overwrite(data.listId, data.musicInfos, true)
|
||||
break
|
||||
case 'list_music_clear':
|
||||
void global.lx.event_list.list_music_clear(data, true)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const registerListActionEvent = () => {
|
||||
const list_data_overwrite = async(listData: MakeOptional<LX.List.ListDataFull, 'tempList'>, isRemote: boolean = false) => {
|
||||
if (isRemote) return
|
||||
await sendListAction('list_data_overwrite', listData)
|
||||
}
|
||||
const list_create = async(position: number, lists: LX.List.UserListInfo[], isRemote: boolean = false) => {
|
||||
if (isRemote) return
|
||||
await sendListAction('list_create', { position, lists })
|
||||
}
|
||||
const list_remove = async(ids: string[], isRemote: boolean = false) => {
|
||||
if (isRemote) return
|
||||
await sendListAction('list_remove', ids)
|
||||
}
|
||||
const list_update = async(lists: LX.List.UserListInfo[], isRemote: boolean = false) => {
|
||||
if (isRemote) return
|
||||
await sendListAction('list_update', lists)
|
||||
}
|
||||
const list_update_position = async(position: number, ids: string[], isRemote: boolean = false) => {
|
||||
if (isRemote) return
|
||||
await sendListAction('list_update_position', { position, ids })
|
||||
}
|
||||
const list_music_overwrite = async(listId: string, musicInfos: LX.Music.MusicInfo[], isRemote: boolean = false) => {
|
||||
if (isRemote) return
|
||||
await sendListAction('list_music_overwrite', { listId, musicInfos })
|
||||
}
|
||||
const list_music_add = async(listId: string, musicInfos: LX.Music.MusicInfo[], addMusicLocationType: LX.AddMusicLocationType, isRemote: boolean = false) => {
|
||||
if (isRemote) return
|
||||
await sendListAction('list_music_add', { listId, musicInfos, addMusicLocationType })
|
||||
}
|
||||
const list_music_move = async(fromId: string, toId: string, musicInfos: LX.Music.MusicInfo[], addMusicLocationType: LX.AddMusicLocationType, isRemote: boolean = false) => {
|
||||
if (isRemote) return
|
||||
await sendListAction('list_music_move', { fromId, toId, musicInfos, addMusicLocationType })
|
||||
}
|
||||
const list_music_remove = async(listId: string, ids: string[], isRemote: boolean = false) => {
|
||||
if (isRemote) return
|
||||
await sendListAction('list_music_remove', { listId, ids })
|
||||
}
|
||||
const list_music_update = async(musicInfos: LX.List.ListActionMusicUpdate, isRemote: boolean = false) => {
|
||||
if (isRemote) return
|
||||
await sendListAction('list_music_update', musicInfos)
|
||||
}
|
||||
const list_music_clear = async(listId: string, isRemote: boolean = false) => {
|
||||
if (isRemote) return
|
||||
await sendListAction('list_music_clear', listId)
|
||||
}
|
||||
const list_music_update_position = async(listId: string, position: number, ids: string[], isRemote: boolean = false) => {
|
||||
if (isRemote) return
|
||||
await sendListAction('list_music_update_position', { listId, position, ids })
|
||||
}
|
||||
global.lx.event_list.on('list_data_overwrite', list_data_overwrite)
|
||||
global.lx.event_list.on('list_create', list_create)
|
||||
global.lx.event_list.on('list_remove', list_remove)
|
||||
global.lx.event_list.on('list_update', list_update)
|
||||
global.lx.event_list.on('list_update_position', list_update_position)
|
||||
global.lx.event_list.on('list_music_overwrite', list_music_overwrite)
|
||||
global.lx.event_list.on('list_music_add', list_music_add)
|
||||
global.lx.event_list.on('list_music_move', list_music_move)
|
||||
global.lx.event_list.on('list_music_remove', list_music_remove)
|
||||
global.lx.event_list.on('list_music_update', list_music_update)
|
||||
global.lx.event_list.on('list_music_clear', list_music_clear)
|
||||
global.lx.event_list.on('list_music_update_position', list_music_update_position)
|
||||
return () => {
|
||||
global.lx.event_list.off('list_data_overwrite', list_data_overwrite)
|
||||
global.lx.event_list.off('list_create', list_create)
|
||||
global.lx.event_list.off('list_remove', list_remove)
|
||||
global.lx.event_list.off('list_update', list_update)
|
||||
global.lx.event_list.off('list_update_position', list_update_position)
|
||||
global.lx.event_list.off('list_music_overwrite', list_music_overwrite)
|
||||
global.lx.event_list.off('list_music_add', list_music_add)
|
||||
global.lx.event_list.off('list_music_move', list_music_move)
|
||||
global.lx.event_list.off('list_music_remove', list_music_remove)
|
||||
global.lx.event_list.off('list_music_update', list_music_update)
|
||||
global.lx.event_list.off('list_music_clear', list_music_clear)
|
||||
global.lx.event_list.off('list_music_update_position', list_music_update_position)
|
||||
}
|
||||
}
|
||||
|
||||
// const addMusic = (orderId, callback) => {
|
||||
// // ...
|
||||
// }
|
||||
|
||||
const broadcast = async(action: listAction, data: any, excludeIds: string[] = []) => {
|
||||
if (!io) return
|
||||
const sockets: LX.Sync.RemoteSocket[] = await io.fetchSockets()
|
||||
for (const socket of sockets) {
|
||||
if (excludeIds.includes(socket.data.keyInfo.clientId)) continue
|
||||
socket.emit(action, encryptMsg(socket.data.keyInfo, data))
|
||||
}
|
||||
}
|
||||
|
||||
export const sendListAction = async(action: string, data: any) => {
|
||||
// io.sockets
|
||||
return await broadcast('list:action', JSON.stringify({ action, data }))
|
||||
}
|
||||
|
||||
export const registerListHandler = (_io: Server, socket: LX.Sync.Socket) => {
|
||||
io = _io
|
||||
socket.on('list:action', msg => {
|
||||
// console.log(msg)
|
||||
msg = decryptMsg(socket.data.keyInfo, msg)
|
||||
if (!msg) return
|
||||
handleListAction(JSON.parse(msg))
|
||||
void broadcast('list:action', msg, [socket.data.keyInfo.clientId])
|
||||
// socket.broadcast.emit('list:action', { action: 'list_remove', data: { id: 'default', index: 0 } })
|
||||
})
|
||||
|
||||
removeListener = registerListActionEvent()
|
||||
// socket.on('list:add', addMusic)
|
||||
}
|
||||
export const unregisterListHandler = () => {
|
||||
io = null
|
||||
|
||||
if (removeListener) {
|
||||
removeListener()
|
||||
removeListener = null
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
const { aesEncrypt, aesDecrypt, createClientKeyInfo, getClientKeyInfo, setClientKeyInfo } = require('./utils')
|
||||
|
||||
const authMsg = 'lx-music auth::'
|
||||
const helloMsg = 'Hello~::^-^::'
|
||||
|
||||
exports.authCode = async(req, res, authCode) => {
|
||||
let code = 401
|
||||
let msg = 'Forbidden'
|
||||
// console.log(req.headers)
|
||||
if (req.headers.m) {
|
||||
label:
|
||||
if (req.headers.i) {
|
||||
const keyInfo = getClientKeyInfo(req.headers.i)
|
||||
if (!keyInfo) break label
|
||||
let text
|
||||
try {
|
||||
text = aesDecrypt(req.headers.m, keyInfo.key, keyInfo.iv)
|
||||
} catch (err) {
|
||||
break label
|
||||
}
|
||||
console.log(text)
|
||||
if (text.startsWith(authMsg)) {
|
||||
code = 200
|
||||
const deviceName = text.replace(authMsg, '') || 'Unknown'
|
||||
if (deviceName != keyInfo.deviceName) {
|
||||
keyInfo.deviceName = deviceName
|
||||
setClientKeyInfo(keyInfo)
|
||||
}
|
||||
msg = aesEncrypt(helloMsg, keyInfo.key, keyInfo.iv)
|
||||
}
|
||||
} else {
|
||||
let key = ''.padStart(16, Buffer.from(authCode).toString('hex'))
|
||||
const iv = Buffer.from(key.split('').reverse().join('')).toString('base64')
|
||||
key = Buffer.from(key).toString('base64')
|
||||
// console.log(authCode, key, iv)
|
||||
let text
|
||||
try {
|
||||
text = aesDecrypt(req.headers.m, key, iv)
|
||||
} catch (err) {
|
||||
break label
|
||||
}
|
||||
console.log(text)
|
||||
if (text.startsWith(authMsg)) {
|
||||
code = 200
|
||||
const deviceName = text.replace(authMsg, '') || 'Unknown'
|
||||
msg = aesEncrypt(JSON.stringify(createClientKeyInfo(deviceName)), key, iv)
|
||||
}
|
||||
}
|
||||
}
|
||||
res.writeHead(code)
|
||||
res.end(msg)
|
||||
}
|
||||
|
||||
exports.authConnect = async req => {
|
||||
const { i, t } = req._query
|
||||
label:
|
||||
if (i && t) {
|
||||
const keyInfo = getClientKeyInfo(i)
|
||||
if (!keyInfo) break label
|
||||
let text
|
||||
try {
|
||||
text = aesDecrypt(t, keyInfo.key, keyInfo.iv)
|
||||
} catch (err) {
|
||||
break label
|
||||
}
|
||||
if (text == 'lx-music connect') return
|
||||
}
|
||||
throw new Error('failed')
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import http from 'http'
|
||||
import {
|
||||
aesEncrypt,
|
||||
aesDecrypt,
|
||||
createClientKeyInfo,
|
||||
getClientKeyInfo,
|
||||
setClientKeyInfo,
|
||||
} from './utils'
|
||||
|
||||
const authMsg = 'lx-music auth::'
|
||||
const helloMsg = 'Hello~::^-^::~v2'
|
||||
|
||||
const requestIps = new Map<string, number>()
|
||||
|
||||
export const authCode = async(req: http.IncomingMessage, res: http.ServerResponse, authCode: string) => {
|
||||
let code = 401
|
||||
let msg = 'Forbidden'
|
||||
|
||||
let ip = req.socket.remoteAddress
|
||||
// console.log(req.headers)
|
||||
if (typeof req.headers.m == 'string' && ip && (requestIps.get(ip) ?? 0) > 10) {
|
||||
if (req.headers.m) {
|
||||
label:
|
||||
if (req.headers.i) {
|
||||
if (typeof req.headers.i != 'string') break label
|
||||
const keyInfo = getClientKeyInfo(req.headers.i)
|
||||
if (!keyInfo) break label
|
||||
let text
|
||||
try {
|
||||
text = aesDecrypt(req.headers.m, keyInfo.key, keyInfo.iv)
|
||||
} catch (err) {
|
||||
break label
|
||||
}
|
||||
console.log(text)
|
||||
if (text.startsWith(authMsg)) {
|
||||
code = 200
|
||||
const deviceName = text.replace(authMsg, '') || 'Unknown'
|
||||
if (deviceName != keyInfo.deviceName) {
|
||||
keyInfo.deviceName = deviceName
|
||||
setClientKeyInfo(keyInfo)
|
||||
}
|
||||
msg = aesEncrypt(helloMsg, keyInfo.key, keyInfo.iv)
|
||||
}
|
||||
} else {
|
||||
let key = ''.padStart(16, Buffer.from(authCode).toString('hex'))
|
||||
const iv = Buffer.from(key.split('').reverse().join('')).toString('base64')
|
||||
key = Buffer.from(key).toString('base64')
|
||||
// console.log(authCode, key, iv)
|
||||
let text
|
||||
try {
|
||||
text = aesDecrypt(req.headers.m, key, iv)
|
||||
} catch (err) {
|
||||
break label
|
||||
}
|
||||
console.log(text)
|
||||
if (text.startsWith(authMsg)) {
|
||||
code = 200
|
||||
const deviceName = text.replace(authMsg, '') || 'Unknown'
|
||||
msg = aesEncrypt(JSON.stringify(createClientKeyInfo(deviceName)), key, iv)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
res.writeHead(code)
|
||||
res.end(msg)
|
||||
|
||||
if (ip && code != 200) {
|
||||
const num = requestIps.get(ip) ?? 0
|
||||
if (num > 20) return
|
||||
requestIps.set(ip, num + 1)
|
||||
}
|
||||
}
|
||||
|
||||
export const authConnect = async(req: http.IncomingMessage) => {
|
||||
const url = new URL(req.url as string)
|
||||
const i = url.searchParams.get('i')
|
||||
const t = url.searchParams.get('t')
|
||||
label:
|
||||
if (i && t) {
|
||||
const keyInfo = getClientKeyInfo(i)
|
||||
if (!keyInfo) break label
|
||||
let text
|
||||
try {
|
||||
text = aesDecrypt(t, keyInfo.key, keyInfo.iv)
|
||||
} catch (err) {
|
||||
break label
|
||||
}
|
||||
if (text == 'lx-music connect') return
|
||||
}
|
||||
throw new Error('failed')
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
const { startServer, stopServer, getStatus } = require('./server')
|
||||
|
||||
module.exports = {
|
||||
startServer,
|
||||
stopServer,
|
||||
getStatus,
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { startServer, stopServer, getStatus } from './server'
|
||||
|
||||
export {
|
||||
startServer,
|
||||
stopServer,
|
||||
getStatus,
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
const http = require('http')
|
||||
const sio = require('socket.io')
|
||||
const { createHttpTerminator } = require('http-terminator')
|
||||
const modules = require('../modules')
|
||||
const { authCode, authConnect } = require('./auth')
|
||||
const { getAddress, getServerId, generateCode, getClientKeyInfo, setClientKeyInfo } = require('./utils')
|
||||
const { syncList, removeSnapshot } = require('./syncList')
|
||||
const { log } = require('@common/utils')
|
||||
import http from 'http'
|
||||
import { Server, Socket } from 'socket.io'
|
||||
import { createHttpTerminator, HttpTerminator } from 'http-terminator'
|
||||
import * as modules from '../modules'
|
||||
import { authCode, authConnect } from './auth'
|
||||
import { getAddress, getServerId, generateCode as handleGenerateCode, getClientKeyInfo, setClientKeyInfo } from './utils'
|
||||
import { syncList, removeSnapshot } from './syncList'
|
||||
import { log } from '@common/utils'
|
||||
import { sendStatus } from '@main/modules/winMain'
|
||||
|
||||
|
||||
let status = {
|
||||
let status: LX.Sync.Status = {
|
||||
status: false,
|
||||
message: '',
|
||||
address: [],
|
||||
@@ -16,7 +17,26 @@ let status = {
|
||||
devices: [],
|
||||
}
|
||||
|
||||
const handleConnection = (io, socket) => {
|
||||
const codeTools: {
|
||||
timeout: NodeJS.Timer | null
|
||||
start: () => void
|
||||
stop: () => void
|
||||
} = {
|
||||
timeout: null,
|
||||
start() {
|
||||
this.stop()
|
||||
this.timeout = setInterval(() => {
|
||||
void generateCode()
|
||||
}, 60 * 3 * 1000)
|
||||
},
|
||||
stop() {
|
||||
if (!this.timeout) return
|
||||
clearInterval(this.timeout)
|
||||
this.timeout = null
|
||||
},
|
||||
}
|
||||
|
||||
const handleConnection = (io: Server, socket: LX.Sync.Socket) => {
|
||||
console.log('connection')
|
||||
// console.log(socket.handshake.query)
|
||||
for (const module of Object.values(modules)) {
|
||||
@@ -24,7 +44,7 @@ const handleConnection = (io, socket) => {
|
||||
}
|
||||
}
|
||||
|
||||
const authConnection = (req, callback) => {
|
||||
const authConnection = (req: http.IncomingMessage, callback: (err: string | null | undefined, success: boolean) => void) => {
|
||||
// console.log(req.headers)
|
||||
// // console.log(req.auth)
|
||||
// console.log(req._query.authCode)
|
||||
@@ -35,10 +55,10 @@ const authConnection = (req, callback) => {
|
||||
})
|
||||
}
|
||||
|
||||
let httpTerminator = null
|
||||
let io = null
|
||||
let httpTerminator: HttpTerminator | null = null
|
||||
let io: Server | null = null
|
||||
|
||||
const handleStartServer = (port = 9527) => new Promise((resolve, reject) => {
|
||||
const handleStartServer = async(port = 9527) => await new Promise((resolve, reject) => {
|
||||
const httpServer = http.createServer((req, res) => {
|
||||
// console.log(req.url)
|
||||
let code
|
||||
@@ -46,14 +66,14 @@ const handleStartServer = (port = 9527) => new Promise((resolve, reject) => {
|
||||
switch (req.url) {
|
||||
case '/hello':
|
||||
code = 200
|
||||
msg = 'Hello~::^-^::'
|
||||
msg = 'Hello~::^-^::~v2'
|
||||
break
|
||||
case '/id':
|
||||
code = 200
|
||||
msg = 'OjppZDo6' + getServerId()
|
||||
break
|
||||
case '/ah':
|
||||
authCode(req, res, status.code)
|
||||
void authCode(req, res, status.code)
|
||||
break
|
||||
default:
|
||||
code = 401
|
||||
@@ -67,7 +87,7 @@ const handleStartServer = (port = 9527) => new Promise((resolve, reject) => {
|
||||
httpTerminator = createHttpTerminator({
|
||||
server: httpServer,
|
||||
})
|
||||
io = sio(httpServer, {
|
||||
io = new Server(httpServer, {
|
||||
path: '/sync',
|
||||
serveClient: false,
|
||||
connectTimeout: 10000,
|
||||
@@ -77,13 +97,16 @@ const handleStartServer = (port = 9527) => new Promise((resolve, reject) => {
|
||||
transports: ['websocket'],
|
||||
})
|
||||
|
||||
io.on('connection', async socket => {
|
||||
io.on('connection', async(_socket: Socket) => {
|
||||
const socket = _socket as LX.Sync.Socket
|
||||
socket.on('disconnect', reason => {
|
||||
console.log('disconnect', reason)
|
||||
status.devices.splice(status.devices.findIndex(k => k.clientId == keyInfo.clientId), 1)
|
||||
global.lx_event.sync.status(status)
|
||||
status.devices.splice(status.devices.findIndex(k => k.clientId == keyInfo?.clientId), 1)
|
||||
sendStatus(status)
|
||||
})
|
||||
if (typeof socket.handshake.query.i != 'string') return socket.disconnect(true)
|
||||
const keyInfo = getClientKeyInfo(socket.handshake.query.i)
|
||||
if (!keyInfo || !io) return socket.disconnect(true)
|
||||
keyInfo.connectionTime = Date.now()
|
||||
setClientKeyInfo(keyInfo)
|
||||
// socket.lx_keyInfo = keyInfo
|
||||
@@ -96,8 +119,8 @@ const handleStartServer = (port = 9527) => new Promise((resolve, reject) => {
|
||||
return
|
||||
}
|
||||
status.devices.push(keyInfo)
|
||||
handleConnection(io, socket, keyInfo)
|
||||
global.lx_event.sync.status(status)
|
||||
handleConnection(io, socket)
|
||||
sendStatus(status)
|
||||
})
|
||||
|
||||
httpServer.on('error', error => {
|
||||
@@ -107,9 +130,10 @@ const handleStartServer = (port = 9527) => new Promise((resolve, reject) => {
|
||||
|
||||
httpServer.on('listening', () => {
|
||||
const addr = httpServer.address()
|
||||
const bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port
|
||||
if (!addr) return reject(new Error('address is null'))
|
||||
const bind = typeof addr == 'string' ? `pipe ${addr}` : `port ${addr.port}`
|
||||
console.info(`Listening on ${bind}`)
|
||||
resolve()
|
||||
resolve(null)
|
||||
})
|
||||
|
||||
httpServer.listen(port)
|
||||
@@ -117,23 +141,25 @@ const handleStartServer = (port = 9527) => new Promise((resolve, reject) => {
|
||||
|
||||
const handleStopServer = async() => {
|
||||
if (!httpTerminator) return
|
||||
await io.close()
|
||||
if (!io) return
|
||||
io.close()
|
||||
await httpTerminator.terminate().catch(() => {})
|
||||
io = null
|
||||
httpTerminator = null
|
||||
}
|
||||
|
||||
exports.stopServer = async() => {
|
||||
export const stopServer = async() => {
|
||||
codeTools.stop()
|
||||
if (!status.status) {
|
||||
status.status = false
|
||||
status.message = ''
|
||||
status.address = []
|
||||
status.code = ''
|
||||
global.lx_event.sync.status(status)
|
||||
sendStatus(status)
|
||||
return
|
||||
}
|
||||
console.log('stoping sync server...')
|
||||
return handleStopServer().then(() => {
|
||||
return await handleStopServer().then(() => {
|
||||
console.log('sync server stoped')
|
||||
status.status = false
|
||||
status.message = ''
|
||||
@@ -143,19 +169,22 @@ exports.stopServer = async() => {
|
||||
console.log(err)
|
||||
status.message = err.message
|
||||
}).finally(() => {
|
||||
global.lx_event.sync.status(status)
|
||||
sendStatus(status)
|
||||
})
|
||||
}
|
||||
exports.startServer = async port => {
|
||||
|
||||
export const startServer = async(port: number) => {
|
||||
if (status.status) await handleStopServer()
|
||||
|
||||
console.log('starting sync server...')
|
||||
return handleStartServer(port).then(() => {
|
||||
return await handleStartServer(port).then(() => {
|
||||
console.log('sync server started')
|
||||
status.status = true
|
||||
status.message = ''
|
||||
status.address = getAddress()
|
||||
status.code = generateCode()
|
||||
|
||||
void generateCode()
|
||||
codeTools.start()
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
status.status = false
|
||||
@@ -163,16 +192,18 @@ exports.startServer = async port => {
|
||||
status.address = []
|
||||
status.code = ''
|
||||
}).finally(() => {
|
||||
global.lx_event.sync.status(status)
|
||||
sendStatus(status)
|
||||
})
|
||||
}
|
||||
|
||||
exports.getStatus = () => status
|
||||
export const getStatus = (): LX.Sync.Status => status
|
||||
|
||||
exports.generateCode = async() => {
|
||||
status.code = generateCode()
|
||||
global.lx_event.sync.status(status)
|
||||
export const generateCode = async() => {
|
||||
status.code = handleGenerateCode()
|
||||
sendStatus(status)
|
||||
return status.code
|
||||
}
|
||||
|
||||
exports.removeSnapshot = removeSnapshot
|
||||
export {
|
||||
removeSnapshot,
|
||||
}
|
||||
@@ -1,438 +0,0 @@
|
||||
const fs = require('fs')
|
||||
const fsPromises = fs.promises
|
||||
const { encryptMsg, decryptMsg, getSnapshotFilePath } = require('./utils')
|
||||
const SYNC_EVENT_NAMES = require('../event/name')
|
||||
const { common: COMMON_EVENT_NAME } = require('@main/events/_name')
|
||||
const { throttle } = require('@common/utils')
|
||||
|
||||
let io
|
||||
let syncingId = null
|
||||
const wait = (time = 1000) => new Promise((resolve, reject) => setTimeout(resolve, time))
|
||||
|
||||
const patchListData = listData => {
|
||||
return Object.assign({}, {
|
||||
defaultList: {
|
||||
id: 'default',
|
||||
name: '试听列表',
|
||||
list: [],
|
||||
},
|
||||
loveList: {
|
||||
id: 'love',
|
||||
name: '我的收藏',
|
||||
list: [],
|
||||
},
|
||||
userList: [],
|
||||
}, listData)
|
||||
}
|
||||
|
||||
const getRemoteListData = socket => new Promise((resolve, reject) => {
|
||||
console.log('getRemoteListData')
|
||||
const handleError = reason => {
|
||||
reject(new Error(reason))
|
||||
}
|
||||
const handleSuccess = enData => {
|
||||
socket.removeListener('disconnect', handleError)
|
||||
socket.removeListener('list:sync', handleSuccess)
|
||||
console.log('getRemoteListData', 'handleSuccess')
|
||||
const data = JSON.parse(decryptMsg(socket.data.keyInfo, enData))
|
||||
if (!data) return reject(new Error('Get remote list data failed'))
|
||||
if (data.action != 'getData') return
|
||||
resolve(patchListData(data.data))
|
||||
}
|
||||
|
||||
socket.on('disconnect', handleError)
|
||||
socket.on('list:sync', handleSuccess)
|
||||
socket.emit('list:sync', encryptMsg(socket.data.keyInfo, JSON.stringify({ action: 'getData', data: 'all' })))
|
||||
})
|
||||
|
||||
const getLocalListData = () => new Promise((resolve, reject) => {
|
||||
const handleSuccess = ({ action, data }) => {
|
||||
if (action !== 'getData') return
|
||||
global.lx_event.sync.off(SYNC_EVENT_NAMES.sync_handle_list, handleSuccess)
|
||||
resolve(patchListData(data))
|
||||
}
|
||||
global.lx_event.sync.on(SYNC_EVENT_NAMES.sync_handle_list, handleSuccess)
|
||||
global.lx_event.sync.sync_list({
|
||||
action: 'getData',
|
||||
})
|
||||
})
|
||||
const getSyncMode = keyInfo => new Promise((resolve, reject) => {
|
||||
const handleSuccess = ({ action, data }) => {
|
||||
if (action !== 'selectMode') return
|
||||
global.lx_event.sync.off(SYNC_EVENT_NAMES.sync_handle_list, handleSuccess)
|
||||
resolve(data)
|
||||
}
|
||||
global.lx_event.sync.on(SYNC_EVENT_NAMES.sync_handle_list, handleSuccess)
|
||||
global.lx_event.sync.sync_list({
|
||||
action: 'selectMode',
|
||||
data: keyInfo,
|
||||
})
|
||||
})
|
||||
|
||||
const finishedSync = socket => {
|
||||
return socket.emit('list:sync', encryptMsg(socket.data.keyInfo, JSON.stringify({
|
||||
action: 'finished',
|
||||
})))
|
||||
}
|
||||
|
||||
const setLocalList = listData => {
|
||||
global.lx_event.sync.sync_list({
|
||||
action: 'setData',
|
||||
data: listData,
|
||||
})
|
||||
}
|
||||
const setRemotelList = async(socket, listData) => {
|
||||
if (!io) return
|
||||
const sockets = await io.fetchSockets()
|
||||
for (const socket of sockets) {
|
||||
// if (excludeIds.includes(socket.data.keyInfo.clientId)) continue
|
||||
socket.emit('list:sync', encryptMsg(socket.data.keyInfo, JSON.stringify({ action: 'setData', data: listData })))
|
||||
}
|
||||
}
|
||||
|
||||
let writeFilePromises = {}
|
||||
const updateSnapshot = (path, data) => {
|
||||
console.log('updateSnapshot', path)
|
||||
let writeFilePromise = writeFilePromises[path] || Promise.resolve()
|
||||
return writeFilePromise.then(() => {
|
||||
writeFilePromise = writeFilePromises[path] = fsPromises.writeFile(path, data)
|
||||
return writeFilePromise
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const createUserListDataObj = listData => {
|
||||
const userListDataObj = {}
|
||||
for (const list of listData.userList) userListDataObj[list.id] = list
|
||||
return userListDataObj
|
||||
}
|
||||
|
||||
const handleMergeList = (sourceList, targetList, addMusicLocationType) => {
|
||||
let newList
|
||||
switch (addMusicLocationType) {
|
||||
case 'top':
|
||||
newList = [...targetList.list, ...sourceList.list]
|
||||
break
|
||||
case 'bottom':
|
||||
default:
|
||||
newList = [...sourceList.list, ...targetList.list]
|
||||
break
|
||||
}
|
||||
const map = {}
|
||||
const ids = []
|
||||
switch (addMusicLocationType) {
|
||||
case 'top':
|
||||
newList = [...targetList.list, ...sourceList.list]
|
||||
for (let i = newList.length - 1; i > -1; i--) {
|
||||
const item = newList[i]
|
||||
if (map[item.songmid]) continue
|
||||
ids.unshift(item.songmid)
|
||||
map[item.songmid] = item
|
||||
}
|
||||
break
|
||||
case 'bottom':
|
||||
default:
|
||||
newList = [...sourceList.list, ...targetList.list]
|
||||
for (const item of newList) {
|
||||
if (map[item.songmid]) continue
|
||||
ids.push(item.songmid)
|
||||
map[item.songmid] = item
|
||||
}
|
||||
break
|
||||
}
|
||||
return {
|
||||
...sourceList,
|
||||
list: ids.map(id => map[id]),
|
||||
}
|
||||
}
|
||||
const mergeList = (sourceListData, targetListData) => {
|
||||
const addMusicLocationType = global.appSetting.list.addMusicLocationType
|
||||
const newListData = {}
|
||||
newListData.defaultList = handleMergeList(sourceListData.defaultList, targetListData.defaultList, addMusicLocationType)
|
||||
newListData.loveList = handleMergeList(sourceListData.loveList, targetListData.loveList, addMusicLocationType)
|
||||
|
||||
const userListDataObj = createUserListDataObj(sourceListData)
|
||||
newListData.userList = [...sourceListData.userList]
|
||||
|
||||
targetListData.userList.forEach((list, index) => {
|
||||
const targetUpdateTime = list?.locationUpdateTime ?? 0
|
||||
const sourceList = userListDataObj[list.id]
|
||||
if (sourceList) {
|
||||
sourceList.list = handleMergeList(sourceList, list, addMusicLocationType).list
|
||||
|
||||
const sourceUpdateTime = sourceList?.locationUpdateTime ?? 0
|
||||
if (targetUpdateTime >= sourceUpdateTime) return
|
||||
// 调整位置
|
||||
const [newList] = newListData.userList.splice(newListData.userList.findIndex(l => l.id == list.id), 1)
|
||||
newList.locationUpdateTime = targetUpdateTime
|
||||
newListData.userList.splice(index, 0, newList)
|
||||
} else {
|
||||
if (targetUpdateTime) {
|
||||
newListData.userList.splice(index, 0, list)
|
||||
} else {
|
||||
newListData.userList.push(list)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return newListData
|
||||
}
|
||||
const overwriteList = (sourceListData, targetListData) => {
|
||||
const newListData = {}
|
||||
newListData.defaultList = sourceListData.defaultList
|
||||
newListData.loveList = sourceListData.loveList
|
||||
|
||||
const userListDataObj = createUserListDataObj(sourceListData)
|
||||
newListData.userList = [...sourceListData.userList]
|
||||
|
||||
targetListData.userList.forEach((list, index) => {
|
||||
if (userListDataObj[list.id]) return
|
||||
if (list?.locationUpdateTime) {
|
||||
newListData.userList.splice(index, 0, list)
|
||||
} else {
|
||||
newListData.userList.push(list)
|
||||
}
|
||||
})
|
||||
|
||||
return newListData
|
||||
}
|
||||
|
||||
const handleMergeListData = async socket => {
|
||||
let isSelectingMode = false
|
||||
const handleDisconnect = () => {
|
||||
if (!isSelectingMode) return
|
||||
global.lx_event.sync.sync_list({
|
||||
action: 'closeSelectMode',
|
||||
})
|
||||
}
|
||||
socket.on('disconnect', handleDisconnect)
|
||||
isSelectingMode = true
|
||||
const mode = await getSyncMode(socket.data.keyInfo)
|
||||
isSelectingMode = false
|
||||
const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalListData()])
|
||||
console.log('handleMergeListData', 'remoteListData, localListData')
|
||||
let listData
|
||||
switch (mode) {
|
||||
case 'merge_local_remote':
|
||||
listData = mergeList(localListData, remoteListData)
|
||||
break
|
||||
case 'merge_remote_local':
|
||||
listData = mergeList(remoteListData, localListData)
|
||||
break
|
||||
case 'overwrite_local_remote':
|
||||
listData = overwriteList(localListData, remoteListData)
|
||||
break
|
||||
case 'overwrite_remote_local':
|
||||
listData = overwriteList(remoteListData, localListData)
|
||||
break
|
||||
case 'overwrite_local_remote_full':
|
||||
listData = localListData
|
||||
break
|
||||
case 'overwrite_remote_local_full':
|
||||
listData = remoteListData
|
||||
break
|
||||
case 'none': return
|
||||
case 'cancel':
|
||||
socket.disconnect(true)
|
||||
throw new Error('cancel')
|
||||
}
|
||||
return listData
|
||||
}
|
||||
|
||||
const handleSyncList = async socket => {
|
||||
const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalListData()])
|
||||
console.log('handleSyncList', 'remoteListData, localListData')
|
||||
const listData = {}
|
||||
if (localListData.defaultList.list.length || localListData.loveList.list.length || localListData.userList.length) {
|
||||
if (remoteListData.defaultList.list.length || remoteListData.loveList.list.length || remoteListData.userList.length) {
|
||||
const mergedList = await handleMergeListData(socket)
|
||||
console.log('handleMergeListData', 'mergedList')
|
||||
console.log(mergedList)
|
||||
if (!mergedList) return
|
||||
listData.defaultList = mergedList.defaultList
|
||||
listData.loveList = mergedList.loveList
|
||||
listData.userList = mergedList.userList
|
||||
setLocalList(mergedList)
|
||||
setRemotelList(socket, mergedList)
|
||||
} else {
|
||||
setRemotelList(socket, localListData)
|
||||
listData.defaultList = localListData.defaultList
|
||||
listData.loveList = localListData.loveList
|
||||
listData.userList = localListData.userList
|
||||
}
|
||||
} else {
|
||||
if (remoteListData.defaultList.list.length || remoteListData.loveList.list.length || remoteListData.userList.length) {
|
||||
setLocalList(remoteListData)
|
||||
listData.defaultList = remoteListData.defaultList
|
||||
listData.loveList = remoteListData.loveList
|
||||
listData.userList = remoteListData.userList
|
||||
} else {
|
||||
listData.defaultList = localListData.defaultList
|
||||
listData.loveList = localListData.loveList
|
||||
listData.userList = localListData.userList
|
||||
}
|
||||
}
|
||||
return updateSnapshot(socket.data.snapshotFilePath, JSON.stringify({
|
||||
defaultList: listData.defaultList,
|
||||
loveList: listData.loveList,
|
||||
userList: listData.userList,
|
||||
})).then(() => {
|
||||
socket.data.isCreatedSnapshot = true
|
||||
return listData
|
||||
})
|
||||
}
|
||||
|
||||
const mergeListDataFromSnapshot = (sourceList, targetList, snapshotList, addMusicLocationType) => {
|
||||
const removedListIds = new Set()
|
||||
const sourceListItemIds = new Set()
|
||||
const targetListItemIds = new Set()
|
||||
for (const m of sourceList.list) sourceListItemIds.add(m.songmid)
|
||||
for (const m of targetList.list) targetListItemIds.add(m.songmid)
|
||||
if (snapshotList) {
|
||||
for (const m of snapshotList.list) {
|
||||
if (!sourceListItemIds.has(m.songmid) || !targetListItemIds.has(m.songmid)) removedListIds.add(m.songmid)
|
||||
}
|
||||
}
|
||||
|
||||
let newList
|
||||
const map = {}
|
||||
const ids = []
|
||||
switch (addMusicLocationType) {
|
||||
case 'top':
|
||||
newList = [...targetList.list, ...sourceList.list]
|
||||
for (let i = newList.length - 1; i > -1; i--) {
|
||||
const item = newList[i]
|
||||
if (map[item.songmid] || removedListIds.has(item.songmid)) continue
|
||||
ids.unshift(item.songmid)
|
||||
map[item.songmid] = item
|
||||
}
|
||||
break
|
||||
case 'bottom':
|
||||
default:
|
||||
newList = [...sourceList.list, ...targetList.list]
|
||||
for (const item of newList) {
|
||||
if (map[item.songmid] || removedListIds.has(item.songmid)) continue
|
||||
ids.push(item.songmid)
|
||||
map[item.songmid] = item
|
||||
}
|
||||
break
|
||||
}
|
||||
return {
|
||||
...sourceList,
|
||||
list: ids.map(id => map[id]),
|
||||
}
|
||||
}
|
||||
const handleMergeListDataFromSnapshot = async(socket, snapshot) => {
|
||||
const addMusicLocationType = global.appSetting.list.addMusicLocationType
|
||||
const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalListData()])
|
||||
const newListData = {}
|
||||
newListData.defaultList = mergeListDataFromSnapshot(localListData.defaultList, remoteListData.defaultList, snapshot.defaultList, addMusicLocationType)
|
||||
newListData.loveList = mergeListDataFromSnapshot(localListData.loveList, remoteListData.loveList, snapshot.loveList, addMusicLocationType)
|
||||
const localUserListData = createUserListDataObj(localListData)
|
||||
const remoteUserListData = createUserListDataObj(remoteListData)
|
||||
const snapshotUserListData = createUserListDataObj(snapshot)
|
||||
const removedListIds = new Set()
|
||||
const localUserListIds = new Set()
|
||||
const remoteUserListIds = new Set()
|
||||
for (const l of localListData.userList) localUserListIds.add(l.id)
|
||||
for (const l of remoteListData.userList) remoteUserListIds.add(l.id)
|
||||
|
||||
for (const l of snapshot.userList) {
|
||||
if (!localUserListIds.has(l.id) || !remoteUserListIds.has(l.id)) removedListIds.add(l.id)
|
||||
}
|
||||
|
||||
let newUserList = []
|
||||
for (const list of localListData.userList) {
|
||||
if (removedListIds.has(list.id)) continue
|
||||
const remoteList = remoteUserListData[list.id]
|
||||
let newList
|
||||
if (remoteList) {
|
||||
newList = mergeListDataFromSnapshot(list, remoteList, snapshotUserListData[list.id], addMusicLocationType)
|
||||
} else {
|
||||
newList = { ...list }
|
||||
}
|
||||
newUserList.push(newList)
|
||||
}
|
||||
|
||||
remoteListData.userList.forEach((list, index) => {
|
||||
if (removedListIds.has(list.id)) return
|
||||
const remoteUpdateTime = list?.locationUpdateTime ?? 0
|
||||
if (localUserListData[list.id]) {
|
||||
const localUpdateTime = localUserListData[list.id]?.locationUpdateTime ?? 0
|
||||
if (localUpdateTime >= remoteUpdateTime) return
|
||||
// 调整位置
|
||||
const [newList] = newUserList.splice(newUserList.findIndex(l => l.id == list.id), 1)
|
||||
newList.locationUpdateTime = localUpdateTime
|
||||
newUserList.splice(index, 0, newList)
|
||||
} else {
|
||||
if (remoteUpdateTime) {
|
||||
newUserList.splice(index, 0, { ...list })
|
||||
} else {
|
||||
newUserList.push({ ...list })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
newListData.userList = newUserList
|
||||
setLocalList(newListData)
|
||||
setRemotelList(socket, newListData)
|
||||
return updateSnapshot(socket.data.snapshotFilePath, JSON.stringify({
|
||||
defaultList: newListData.defaultList,
|
||||
loveList: newListData.loveList,
|
||||
userList: newListData.userList,
|
||||
})).then(() => {
|
||||
socket.data.isCreatedSnapshot = true
|
||||
return newListData
|
||||
})
|
||||
}
|
||||
|
||||
const registerUpdateSnapshotTask = (socket, snapshot) => {
|
||||
if (!socket.data.isCreatedSnapshot) return
|
||||
const handleUpdateSnapshot = throttle(({ defaultList, loveList, userList }) => {
|
||||
if (defaultList != null) snapshot.defaultList = defaultList
|
||||
if (loveList != null) snapshot.loveList = loveList
|
||||
if (userList != null) snapshot.userList = userList
|
||||
updateSnapshot(socket.data.snapshotFilePath, JSON.stringify(snapshot))
|
||||
}, 2000)
|
||||
global.lx_event.common.on(COMMON_EVENT_NAME.saveMyList, handleUpdateSnapshot)
|
||||
socket.on('disconnect', () => {
|
||||
global.lx_event.common.off(COMMON_EVENT_NAME.saveMyList, handleUpdateSnapshot)
|
||||
})
|
||||
}
|
||||
|
||||
const syncList = async socket => {
|
||||
socket.data.snapshotFilePath = getSnapshotFilePath(socket.data.keyInfo)
|
||||
let fileData
|
||||
let isSyncRequired = false
|
||||
try {
|
||||
fileData = await fsPromises.readFile(socket.data.snapshotFilePath)
|
||||
fileData = JSON.parse(fileData)
|
||||
} catch (err) {
|
||||
if (err.code !== 'ENOENT') throw err
|
||||
isSyncRequired = true
|
||||
}
|
||||
console.log('isSyncRequired', isSyncRequired)
|
||||
if (isSyncRequired) return handleSyncList(socket)
|
||||
return handleMergeListDataFromSnapshot(socket, patchListData(fileData))
|
||||
}
|
||||
|
||||
const checkSyncQueue = async() => {
|
||||
if (!syncingId) return
|
||||
await wait()
|
||||
return checkSyncQueue()
|
||||
}
|
||||
exports.syncList = async(_io, socket) => {
|
||||
io = _io
|
||||
await checkSyncQueue()
|
||||
syncingId = socket.data.keyInfo.clientId
|
||||
return syncList(socket).then(newListData => {
|
||||
registerUpdateSnapshotTask(socket, { ...newListData })
|
||||
return finishedSync(socket)
|
||||
}).finally(() => {
|
||||
syncingId = null
|
||||
})
|
||||
}
|
||||
exports.removeSnapshot = keyInfo => {
|
||||
const filePath = getSnapshotFilePath(keyInfo)
|
||||
return fsPromises.unlink(filePath)
|
||||
}
|
||||
@@ -0,0 +1,492 @@
|
||||
import { promises as fsPromises } from 'fs'
|
||||
import { encryptMsg, decryptMsg, getSnapshotFilePath } from './utils'
|
||||
import { throttle } from '@common/utils'
|
||||
import { Server } from 'socket.io'
|
||||
import { sendCloseSelectMode, sendSelectMode } from '@main/modules/winMain'
|
||||
import { LIST_IDS } from '@common/constants'
|
||||
|
||||
// type ListInfoType = LX.List.UserListInfoFull | LX.List.MyDefaultListInfoFull | LX.List.MyLoveListInfoFull
|
||||
|
||||
let io: Server | null
|
||||
let syncingId: string | null = null
|
||||
const wait = async(time = 1000) => await new Promise((resolve, reject) => setTimeout(resolve, time))
|
||||
|
||||
const patchListData = (listData: Partial<LX.Sync.ListData>): LX.Sync.ListData => {
|
||||
return Object.assign({
|
||||
defaultList: [],
|
||||
loveList: [],
|
||||
userList: [],
|
||||
}, listData)
|
||||
}
|
||||
|
||||
const getRemoteListData = async(socket: LX.Sync.Socket): Promise<LX.Sync.ListData> => await new Promise((resolve, reject) => {
|
||||
console.log('getRemoteListData')
|
||||
const handleError = (reason: string) => {
|
||||
reject(new Error(reason))
|
||||
}
|
||||
const handleSuccess = (enData: string) => {
|
||||
socket.removeListener('disconnect', handleError)
|
||||
socket.removeListener('list:sync', handleSuccess)
|
||||
console.log('getRemoteListData', 'handleSuccess')
|
||||
const data: LX.Sync.Data | null = JSON.parse(decryptMsg(socket.data.keyInfo, enData))
|
||||
if (!data) return reject(new Error('Get remote list data failed'))
|
||||
if (data.action != 'getData') return
|
||||
resolve(patchListData(data.data))
|
||||
}
|
||||
|
||||
socket.on('disconnect', handleError)
|
||||
socket.on('list:sync', handleSuccess)
|
||||
socket.emit('list:sync', encryptMsg(socket.data.keyInfo, JSON.stringify({ action: 'getData', data: 'all' })))
|
||||
})
|
||||
|
||||
// const getAllLists = async() => {
|
||||
// const lists = []
|
||||
// lists.push(await getListMusics(defaultList.id).then(musics => ({ ...defaultList, list: toRaw(musics).map(m => toOldMusicInfo(m)) })))
|
||||
// lists.push(await getListMusics(loveList.id).then(musics => ({ ...loveList, list: toRaw(musics).map(m => toOldMusicInfo(m)) })))
|
||||
|
||||
// for await (const list of userLists) {
|
||||
// lists.push(await getListMusics(list.id).then(musics => ({ ...toRaw(list), list: toRaw(musics).map(m => toOldMusicInfo(m)) })))
|
||||
// }
|
||||
|
||||
// return lists
|
||||
// }
|
||||
|
||||
const getLocalListData = async(): Promise<LX.Sync.ListData> => {
|
||||
const lists: LX.Sync.ListData = {
|
||||
defaultList: await global.lx.worker.dbService.getListMusics(LIST_IDS.DEFAULT),
|
||||
loveList: await global.lx.worker.dbService.getListMusics(LIST_IDS.LOVE),
|
||||
userList: [],
|
||||
}
|
||||
|
||||
const userListInfos = await global.lx.worker.dbService.getAllUserList()
|
||||
for await (const list of userListInfos) {
|
||||
lists.userList.push(await global.lx.worker.dbService.getListMusics(list.id)
|
||||
.then(musics => ({ ...list, list: musics })))
|
||||
}
|
||||
|
||||
return lists
|
||||
}
|
||||
const getSyncMode = async(keyInfo: LX.Sync.KeyInfo): Promise<LX.Sync.Mode> => await new Promise((resolve, reject) => {
|
||||
let removeListener = sendSelectMode(keyInfo, (mode) => {
|
||||
removeListener()
|
||||
resolve(mode)
|
||||
})
|
||||
})
|
||||
|
||||
const finishedSync = (socket: LX.Sync.Socket) => {
|
||||
return socket.emit('list:sync', encryptMsg(socket.data.keyInfo, JSON.stringify({
|
||||
action: 'finished',
|
||||
})))
|
||||
}
|
||||
|
||||
|
||||
const setLocalList = (listData: LX.Sync.ListData) => {
|
||||
void global.lx.event_list.list_data_overwrite(listData, true)
|
||||
}
|
||||
const setRemotelList = async(socket: LX.Sync.Socket, listData: LX.Sync.ListData) => {
|
||||
if (!io) return
|
||||
|
||||
const sockets: LX.Sync.RemoteSocket[] = await io.fetchSockets()
|
||||
for (const socket of sockets) {
|
||||
// if (excludeIds.includes(socket.data.keyInfo.clientId)) continue
|
||||
socket.emit('list:sync', encryptMsg(socket.data.keyInfo, JSON.stringify({ action: 'setData', data: listData })))
|
||||
}
|
||||
}
|
||||
|
||||
const writeFilePromises: Map<string, Promise<void>> = new Map()
|
||||
const updateSnapshot = async(path: string, data: string) => {
|
||||
console.log('updateSnapshot', path)
|
||||
let writeFilePromise = writeFilePromises.get(path) ?? Promise.resolve()
|
||||
writeFilePromise = writeFilePromise.then(async() => {
|
||||
if (writeFilePromise !== writeFilePromises.get(path)) return
|
||||
return await fsPromises.writeFile(path, data)
|
||||
})
|
||||
writeFilePromises.set(path, writeFilePromise)
|
||||
return await writeFilePromise.finally(() => {
|
||||
if (writeFilePromise !== writeFilePromises.get(path)) return
|
||||
writeFilePromises.delete(path)
|
||||
})
|
||||
}
|
||||
|
||||
interface UserDataObj {
|
||||
[listId: string]: LX.List.UserListInfoFull
|
||||
}
|
||||
const createUserListDataObj = (listData: LX.Sync.ListData): UserDataObj => {
|
||||
const userListDataObj: UserDataObj = {}
|
||||
for (const list of listData.userList) userListDataObj[list.id] = list
|
||||
return userListDataObj
|
||||
}
|
||||
|
||||
const handleMergeList = (
|
||||
sourceList: LX.Music.MusicInfo[],
|
||||
targetList: LX.Music.MusicInfo[],
|
||||
addMusicLocationType: LX.AddMusicLocationType,
|
||||
): LX.Music.MusicInfo[] => {
|
||||
let newList
|
||||
switch (addMusicLocationType) {
|
||||
case 'top':
|
||||
newList = [...targetList, ...sourceList]
|
||||
break
|
||||
case 'bottom':
|
||||
default:
|
||||
newList = [...sourceList, ...targetList]
|
||||
break
|
||||
}
|
||||
const map: Map<string | number, LX.Music.MusicInfo> = new Map()
|
||||
const ids: Array<string | number> = []
|
||||
switch (addMusicLocationType) {
|
||||
case 'top':
|
||||
newList = [...targetList, ...sourceList]
|
||||
for (let i = newList.length - 1; i > -1; i--) {
|
||||
const item = newList[i]
|
||||
if (map.has(item.id)) continue
|
||||
ids.unshift(item.id)
|
||||
map.set(item.id, item)
|
||||
}
|
||||
break
|
||||
case 'bottom':
|
||||
default:
|
||||
newList = [...sourceList, ...targetList]
|
||||
for (const item of newList) {
|
||||
if (map.has(item.id)) continue
|
||||
ids.push(item.id)
|
||||
map.set(item.id, item)
|
||||
}
|
||||
break
|
||||
}
|
||||
return ids.map(id => map.get(id)) as LX.Music.MusicInfo[]
|
||||
}
|
||||
const mergeList = (sourceListData: LX.Sync.ListData, targetListData: LX.Sync.ListData): LX.Sync.ListData => {
|
||||
const addMusicLocationType = global.lx.appSetting['list.addMusicLocationType']
|
||||
const newListData: LX.Sync.ListData = {
|
||||
defaultList: [],
|
||||
loveList: [],
|
||||
userList: [],
|
||||
}
|
||||
newListData.defaultList = handleMergeList(sourceListData.defaultList, targetListData.defaultList, addMusicLocationType)
|
||||
newListData.loveList = handleMergeList(sourceListData.loveList, targetListData.loveList, addMusicLocationType)
|
||||
|
||||
const userListDataObj = createUserListDataObj(sourceListData)
|
||||
newListData.userList = [...sourceListData.userList]
|
||||
|
||||
targetListData.userList.forEach((list, index) => {
|
||||
const targetUpdateTime = list?.locationUpdateTime ?? 0
|
||||
const sourceList = userListDataObj[list.id]
|
||||
if (sourceList) {
|
||||
sourceList.list = handleMergeList(sourceList.list, list.list, addMusicLocationType)
|
||||
|
||||
const sourceUpdateTime = sourceList?.locationUpdateTime ?? 0
|
||||
if (targetUpdateTime >= sourceUpdateTime) return
|
||||
// 调整位置
|
||||
const [newList] = newListData.userList.splice(newListData.userList.findIndex(l => l.id == list.id), 1)
|
||||
newList.locationUpdateTime = targetUpdateTime
|
||||
newListData.userList.splice(index, 0, newList)
|
||||
} else {
|
||||
if (targetUpdateTime) {
|
||||
newListData.userList.splice(index, 0, list)
|
||||
} else {
|
||||
newListData.userList.push(list)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return newListData
|
||||
}
|
||||
const overwriteList = (sourceListData: LX.Sync.ListData, targetListData: LX.Sync.ListData): LX.Sync.ListData => {
|
||||
const newListData: LX.Sync.ListData = {
|
||||
defaultList: [],
|
||||
loveList: [],
|
||||
userList: [],
|
||||
}
|
||||
newListData.defaultList = sourceListData.defaultList
|
||||
newListData.loveList = sourceListData.loveList
|
||||
|
||||
const userListDataObj = createUserListDataObj(sourceListData)
|
||||
newListData.userList = [...sourceListData.userList]
|
||||
|
||||
targetListData.userList.forEach((list, index) => {
|
||||
if (userListDataObj[list.id]) return
|
||||
if (list?.locationUpdateTime) {
|
||||
newListData.userList.splice(index, 0, list)
|
||||
} else {
|
||||
newListData.userList.push(list)
|
||||
}
|
||||
})
|
||||
|
||||
return newListData
|
||||
}
|
||||
|
||||
const handleMergeListData = async(socket: LX.Sync.Socket): Promise<LX.Sync.ListData | null> => {
|
||||
let isSelectingMode = false
|
||||
const handleDisconnect = () => {
|
||||
if (!isSelectingMode) return
|
||||
sendCloseSelectMode()
|
||||
}
|
||||
socket.on('disconnect', handleDisconnect)
|
||||
isSelectingMode = true
|
||||
const mode: LX.Sync.Mode = await getSyncMode(socket.data.keyInfo)
|
||||
isSelectingMode = false
|
||||
const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalListData()])
|
||||
console.log('handleMergeListData', 'remoteListData, localListData')
|
||||
let listData: LX.Sync.ListData
|
||||
switch (mode) {
|
||||
case 'merge_local_remote':
|
||||
listData = mergeList(localListData, remoteListData)
|
||||
break
|
||||
case 'merge_remote_local':
|
||||
listData = mergeList(remoteListData, localListData)
|
||||
break
|
||||
case 'overwrite_local_remote':
|
||||
listData = overwriteList(localListData, remoteListData)
|
||||
break
|
||||
case 'overwrite_remote_local':
|
||||
listData = overwriteList(remoteListData, localListData)
|
||||
break
|
||||
case 'overwrite_local_remote_full':
|
||||
listData = localListData
|
||||
break
|
||||
case 'overwrite_remote_local_full':
|
||||
listData = remoteListData
|
||||
break
|
||||
case 'none': return null
|
||||
case 'cancel':
|
||||
socket.disconnect(true)
|
||||
throw new Error('cancel')
|
||||
}
|
||||
return listData
|
||||
}
|
||||
|
||||
const handleSyncList = async(socket: LX.Sync.Socket): Promise<LX.Sync.ListData | null> => {
|
||||
const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalListData()])
|
||||
console.log('handleSyncList', 'remoteListData, localListData')
|
||||
const listData: LX.Sync.ListData = {
|
||||
defaultList: [],
|
||||
loveList: [],
|
||||
userList: [],
|
||||
}
|
||||
if (localListData.defaultList.length || localListData.loveList.length || localListData.userList.length) {
|
||||
if (remoteListData.defaultList.length || remoteListData.loveList.length || remoteListData.userList.length) {
|
||||
const mergedList = await handleMergeListData(socket)
|
||||
console.log('handleMergeListData', 'mergedList')
|
||||
console.log(mergedList)
|
||||
if (!mergedList) return null
|
||||
listData.defaultList = mergedList.defaultList
|
||||
listData.loveList = mergedList.loveList
|
||||
listData.userList = mergedList.userList
|
||||
setLocalList(mergedList)
|
||||
void setRemotelList(socket, mergedList)
|
||||
} else {
|
||||
void setRemotelList(socket, localListData)
|
||||
listData.defaultList = localListData.defaultList
|
||||
listData.loveList = localListData.loveList
|
||||
listData.userList = localListData.userList
|
||||
}
|
||||
} else {
|
||||
if (remoteListData.defaultList.length || remoteListData.loveList.length || remoteListData.userList.length) {
|
||||
setLocalList(remoteListData)
|
||||
listData.defaultList = remoteListData.defaultList
|
||||
listData.loveList = remoteListData.loveList
|
||||
listData.userList = remoteListData.userList
|
||||
} else {
|
||||
listData.defaultList = localListData.defaultList
|
||||
listData.loveList = localListData.loveList
|
||||
listData.userList = localListData.userList
|
||||
}
|
||||
}
|
||||
return await updateSnapshot(socket.data.snapshotFilePath, JSON.stringify({
|
||||
defaultList: listData.defaultList,
|
||||
loveList: listData.loveList,
|
||||
userList: listData.userList,
|
||||
})).then(() => {
|
||||
socket.data.isCreatedSnapshot = true
|
||||
return listData
|
||||
})
|
||||
}
|
||||
|
||||
const mergeListDataFromSnapshot = (
|
||||
sourceList: LX.Music.MusicInfo[],
|
||||
targetList: LX.Music.MusicInfo[],
|
||||
snapshotList: LX.Music.MusicInfo[],
|
||||
addMusicLocationType: LX.AddMusicLocationType,
|
||||
): LX.Music.MusicInfo[] => {
|
||||
const removedListIds: Set<string | number> = new Set()
|
||||
const sourceListItemIds: Set<string | number> = new Set()
|
||||
const targetListItemIds: Set<string | number> = new Set()
|
||||
for (const m of sourceList) sourceListItemIds.add(m.id)
|
||||
for (const m of targetList) targetListItemIds.add(m.id)
|
||||
if (snapshotList) {
|
||||
for (const m of snapshotList) {
|
||||
if (!sourceListItemIds.has(m.id) || !targetListItemIds.has(m.id)) removedListIds.add(m.id)
|
||||
}
|
||||
}
|
||||
|
||||
let newList
|
||||
const map: Map<string | number, LX.Music.MusicInfo> = new Map()
|
||||
const ids = []
|
||||
switch (addMusicLocationType) {
|
||||
case 'top':
|
||||
newList = [...targetList, ...sourceList]
|
||||
for (let i = newList.length - 1; i > -1; i--) {
|
||||
const item = newList[i]
|
||||
if (map.has(item.id) || removedListIds.has(item.id)) continue
|
||||
ids.unshift(item.id)
|
||||
map.set(item.id, item)
|
||||
}
|
||||
break
|
||||
case 'bottom':
|
||||
default:
|
||||
newList = [...sourceList, ...targetList]
|
||||
for (const item of newList) {
|
||||
if (map.has(item.id) || removedListIds.has(item.id)) continue
|
||||
ids.push(item.id)
|
||||
map.set(item.id, item)
|
||||
}
|
||||
break
|
||||
}
|
||||
return ids.map(id => map.get(id)) as LX.Music.MusicInfo[]
|
||||
}
|
||||
const handleMergeListDataFromSnapshot = async(socket: LX.Sync.Socket, snapshot: LX.Sync.ListData): Promise<LX.Sync.ListData> => {
|
||||
const addMusicLocationType = global.lx.appSetting['list.addMusicLocationType']
|
||||
const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalListData()])
|
||||
const newListData: LX.Sync.ListData = {
|
||||
defaultList: [],
|
||||
loveList: [],
|
||||
userList: [],
|
||||
}
|
||||
newListData.defaultList = mergeListDataFromSnapshot(localListData.defaultList, remoteListData.defaultList, snapshot.defaultList, addMusicLocationType)
|
||||
newListData.loveList = mergeListDataFromSnapshot(localListData.loveList, remoteListData.loveList, snapshot.loveList, addMusicLocationType)
|
||||
const localUserListData = createUserListDataObj(localListData)
|
||||
const remoteUserListData = createUserListDataObj(remoteListData)
|
||||
const snapshotUserListData = createUserListDataObj(snapshot)
|
||||
const removedListIds: Set<string | number> = new Set()
|
||||
const localUserListIds: Set<string | number> = new Set()
|
||||
const remoteUserListIds: Set<string | number> = new Set()
|
||||
|
||||
for (const l of localListData.userList) localUserListIds.add(l.id)
|
||||
for (const l of remoteListData.userList) remoteUserListIds.add(l.id)
|
||||
|
||||
for (const l of snapshot.userList) {
|
||||
if (!localUserListIds.has(l.id) || !remoteUserListIds.has(l.id)) removedListIds.add(l.id)
|
||||
}
|
||||
|
||||
let newUserList: LX.List.UserListInfoFull[] = []
|
||||
for (const list of localListData.userList) {
|
||||
if (removedListIds.has(list.id)) continue
|
||||
const remoteList = remoteUserListData[list.id]
|
||||
let newList: LX.List.UserListInfoFull
|
||||
if (remoteList) {
|
||||
newList = { ...list, list: mergeListDataFromSnapshot(list.list, remoteList.list, snapshotUserListData[list.id].list, addMusicLocationType) }
|
||||
} else {
|
||||
newList = { ...list }
|
||||
}
|
||||
newUserList.push(newList)
|
||||
}
|
||||
|
||||
remoteListData.userList.forEach((list, index) => {
|
||||
if (removedListIds.has(list.id)) return
|
||||
const remoteUpdateTime = list?.locationUpdateTime ?? 0
|
||||
if (localUserListData[list.id]) {
|
||||
const localUpdateTime = localUserListData[list.id]?.locationUpdateTime ?? 0
|
||||
if (localUpdateTime >= remoteUpdateTime) return
|
||||
// 调整位置
|
||||
const [newList] = newUserList.splice(newUserList.findIndex(l => l.id == list.id), 1)
|
||||
newList.locationUpdateTime = localUpdateTime
|
||||
newUserList.splice(index, 0, newList)
|
||||
} else {
|
||||
if (remoteUpdateTime) {
|
||||
newUserList.splice(index, 0, { ...list })
|
||||
} else {
|
||||
newUserList.push({ ...list })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
newListData.userList = newUserList
|
||||
setLocalList(newListData)
|
||||
void setRemotelList(socket, newListData)
|
||||
return await updateSnapshot(socket.data.snapshotFilePath, JSON.stringify({
|
||||
defaultList: newListData.defaultList,
|
||||
loveList: newListData.loveList,
|
||||
userList: newListData.userList,
|
||||
})).then(() => {
|
||||
socket.data.isCreatedSnapshot = true
|
||||
return newListData
|
||||
})
|
||||
}
|
||||
|
||||
const registerUpdateSnapshotTask = (socket: LX.Sync.Socket, snapshot: LX.Sync.ListData) => {
|
||||
if (!socket.data.isCreatedSnapshot) return
|
||||
const handleUpdateSnapshot = throttle(() => {
|
||||
// TODO: 同步成功后再保存快照
|
||||
void getLocalListData().then(({ defaultList, loveList, userList }) => {
|
||||
if (defaultList != null) snapshot.defaultList = defaultList
|
||||
if (loveList != null) snapshot.loveList = loveList
|
||||
if (userList != null) snapshot.userList = userList
|
||||
void updateSnapshot(socket.data.snapshotFilePath, JSON.stringify(snapshot))
|
||||
})
|
||||
}, 2000)
|
||||
global.lx.event_list.on('list_changed', handleUpdateSnapshot)
|
||||
socket.on('disconnect', () => {
|
||||
global.lx.event_list.off('list_changed', handleUpdateSnapshot)
|
||||
})
|
||||
}
|
||||
|
||||
const syncList = async(socket: LX.Sync.Socket): Promise<LX.Sync.ListData | null> => {
|
||||
socket.data.snapshotFilePath = getSnapshotFilePath(socket.data.keyInfo)
|
||||
let fileData: any
|
||||
let isSyncRequired = false
|
||||
try {
|
||||
fileData = (await fsPromises.readFile(socket.data.snapshotFilePath)).toString()
|
||||
fileData = JSON.parse(fileData)
|
||||
} catch (error) {
|
||||
const err = error as NodeJS.ErrnoException
|
||||
if (err.code != 'ENOENT') throw err
|
||||
isSyncRequired = true
|
||||
}
|
||||
console.log('isSyncRequired', isSyncRequired)
|
||||
if (isSyncRequired) return await handleSyncList(socket)
|
||||
return await handleMergeListDataFromSnapshot(socket, patchListData(fileData))
|
||||
}
|
||||
|
||||
const checkSyncQueue = async(): Promise<void> => {
|
||||
if (!syncingId) return
|
||||
await wait()
|
||||
return await checkSyncQueue()
|
||||
}
|
||||
|
||||
// export {
|
||||
// syncList = async(_io: Server, socket: LX.Sync.Socket) => {
|
||||
// io = _io
|
||||
// await checkSyncQueue()
|
||||
// syncingId = socket.data.keyInfo.clientId
|
||||
// return await syncList(socket).then(newListData => {
|
||||
// registerUpdateSnapshotTask(socket, { ...newListData })
|
||||
// return finishedSync(socket)
|
||||
// }).finally(() => {
|
||||
// syncingId = null
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
const _syncList = async(_io: Server, socket: LX.Sync.Socket) => {
|
||||
io = _io
|
||||
await checkSyncQueue()
|
||||
syncingId = socket.data.keyInfo.clientId
|
||||
return await syncList(socket).then(newListData => {
|
||||
if (newListData) registerUpdateSnapshotTask(socket, { ...newListData })
|
||||
return finishedSync(socket)
|
||||
}).finally(() => {
|
||||
syncingId = null
|
||||
})
|
||||
}
|
||||
|
||||
const removeSnapshot = async(keyInfo: LX.Sync.KeyInfo) => {
|
||||
const filePath = getSnapshotFilePath(keyInfo)
|
||||
return await fsPromises.unlink(filePath)
|
||||
}
|
||||
|
||||
export {
|
||||
_syncList as syncList,
|
||||
removeSnapshot,
|
||||
}
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
const { app } = require('electron')
|
||||
const { networkInterfaces } = require('os')
|
||||
const { randomBytes, createCipheriv, createDecipheriv } = require('crypto')
|
||||
const path = require('path')
|
||||
const getStore = require('@common/store')
|
||||
const STORE_NAME = 'sync'
|
||||
|
||||
exports.getAddress = () => {
|
||||
const nets = networkInterfaces()
|
||||
const results = []
|
||||
// console.log(nets)
|
||||
|
||||
for (const name of Object.keys(nets)) {
|
||||
for (const net of nets[name]) {
|
||||
// Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses
|
||||
if (net.family === 'IPv4' && !net.internal) {
|
||||
results.push(net.address)
|
||||
}
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
let serverId
|
||||
exports.getServerId = () => {
|
||||
if (serverId) return serverId
|
||||
const store = getStore(STORE_NAME)
|
||||
serverId = store.get('serverId')
|
||||
if (!serverId) {
|
||||
serverId = randomBytes(4 * 4).toString('base64')
|
||||
store.set('serverId', serverId)
|
||||
}
|
||||
return serverId
|
||||
}
|
||||
|
||||
let keyInfos
|
||||
exports.createClientKeyInfo = deviceName => {
|
||||
const keyInfo = {
|
||||
clientId: randomBytes(4 * 4).toString('base64'),
|
||||
key: randomBytes(16).toString('base64'),
|
||||
iv: randomBytes(16).toString('base64'),
|
||||
deviceName,
|
||||
}
|
||||
const store = getStore(STORE_NAME)
|
||||
if (!keyInfos) keyInfos = store.get('keys') || {}
|
||||
if (Object.keys(keyInfos).length > 101) throw new Error('max keys')
|
||||
|
||||
keyInfos[keyInfo.clientId] = keyInfo
|
||||
store.set('keys', keyInfos)
|
||||
return keyInfo
|
||||
}
|
||||
exports.setClientKeyInfo = keyInfo => {
|
||||
keyInfos[keyInfo.clientId] = keyInfo
|
||||
const store = getStore(STORE_NAME)
|
||||
store.set('keys', keyInfos)
|
||||
}
|
||||
exports.getClientKeyInfo = clientId => {
|
||||
if (!keyInfos) {
|
||||
const store = getStore(STORE_NAME)
|
||||
keyInfos = store.get('keys') || {}
|
||||
}
|
||||
return keyInfos[clientId] || null
|
||||
}
|
||||
|
||||
exports.generateCode = () => {
|
||||
return Math.random().toString().substring(2, 8)
|
||||
}
|
||||
|
||||
exports.aesEncrypt = (buffer, key, iv) => {
|
||||
const cipher = createCipheriv('aes-128-cbc', Buffer.from(key, 'base64'), Buffer.from(iv, 'base64'))
|
||||
return Buffer.concat([cipher.update(buffer), cipher.final()]).toString('base64')
|
||||
}
|
||||
|
||||
exports.aesDecrypt = (text, key, iv) => {
|
||||
const decipher = createDecipheriv('aes-128-cbc', Buffer.from(key, 'base64'), Buffer.from(iv, 'base64'))
|
||||
return Buffer.concat([decipher.update(Buffer.from(text, 'base64')), decipher.final()]).toString()
|
||||
}
|
||||
|
||||
exports.encryptMsg = (keyInfo, msg) => {
|
||||
return msg
|
||||
// if (!keyInfo) return ''
|
||||
// return exports.aesEncrypt(msg, keyInfo.key, keyInfo.iv)
|
||||
}
|
||||
|
||||
exports.decryptMsg = (keyInfo, enMsg) => {
|
||||
return enMsg
|
||||
// if (!keyInfo) return ''
|
||||
// let msg = ''
|
||||
// try {
|
||||
// msg = exports.aesDecrypt(enMsg, keyInfo.key, keyInfo.iv)
|
||||
// } catch (err) {
|
||||
// console.log(err)
|
||||
// }
|
||||
// return msg
|
||||
}
|
||||
|
||||
exports.getSnapshotFilePath = keyInfo => {
|
||||
return path.join(app.getPath('userData'), `snapshot-${Buffer.from(keyInfo.clientId).toString('hex').substring(0, 10)}.json`)
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import { networkInterfaces } from 'os'
|
||||
import { randomBytes, createCipheriv, createDecipheriv } from 'crypto'
|
||||
import { join } from 'path'
|
||||
import getStore from '@main/utils/store'
|
||||
|
||||
const STORE_NAME = 'sync'
|
||||
|
||||
interface KeyInfos {
|
||||
[key: string]: LX.Sync.KeyInfo
|
||||
}
|
||||
|
||||
export const getAddress = (): string[] => {
|
||||
const nets = networkInterfaces()
|
||||
const results: string[] = []
|
||||
// console.log(nets)
|
||||
|
||||
for (const interfaceInfos of Object.values(nets)) {
|
||||
if (!interfaceInfos) continue
|
||||
// Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses
|
||||
for (const interfaceInfo of interfaceInfos) {
|
||||
if (interfaceInfo.family === 'IPv4' && !interfaceInfo.internal) {
|
||||
results.push(interfaceInfo.address)
|
||||
}
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
let serverId: string | undefined
|
||||
export const getServerId = (): string => {
|
||||
if (serverId) return serverId
|
||||
const store = getStore(STORE_NAME)
|
||||
serverId = store.get('serverId') as string | undefined
|
||||
if (!serverId) {
|
||||
serverId = randomBytes(4 * 4).toString('base64')
|
||||
store.set('serverId', serverId)
|
||||
}
|
||||
return serverId
|
||||
}
|
||||
|
||||
let keyInfos: KeyInfos
|
||||
export const createClientKeyInfo = (deviceName: string): LX.Sync.KeyInfo => {
|
||||
const keyInfo: LX.Sync.KeyInfo = {
|
||||
clientId: randomBytes(4 * 4).toString('base64'),
|
||||
key: randomBytes(16).toString('base64'),
|
||||
iv: randomBytes(16).toString('base64'),
|
||||
deviceName,
|
||||
}
|
||||
const store = getStore(STORE_NAME)
|
||||
if (!keyInfos) keyInfos = store.get('keys') as KeyInfos || {}
|
||||
if (Object.keys(keyInfos).length > 101) throw new Error('max keys')
|
||||
|
||||
keyInfos[keyInfo.clientId] = keyInfo
|
||||
store.set('keys', keyInfos)
|
||||
return keyInfo
|
||||
}
|
||||
export const setClientKeyInfo = (keyInfo: LX.Sync.KeyInfo) => {
|
||||
keyInfos[keyInfo.clientId] = keyInfo
|
||||
const store = getStore(STORE_NAME)
|
||||
store.set('keys', keyInfos)
|
||||
}
|
||||
export const getClientKeyInfo = (clientId: string): LX.Sync.KeyInfo | null => {
|
||||
if (!keyInfos) {
|
||||
const store = getStore(STORE_NAME)
|
||||
keyInfos = store.get('keys') as KeyInfos || {}
|
||||
}
|
||||
return keyInfos[clientId] ?? null
|
||||
}
|
||||
|
||||
export const generateCode = (): string => {
|
||||
return Math.random().toString().substring(2, 8)
|
||||
}
|
||||
|
||||
export const aesEncrypt = (buffer: string | Buffer, key: string, iv: string): string => {
|
||||
const cipher = createCipheriv('aes-128-cbc', Buffer.from(key, 'base64'), Buffer.from(iv, 'base64'))
|
||||
return Buffer.concat([cipher.update(buffer), cipher.final()]).toString('base64')
|
||||
}
|
||||
|
||||
export const aesDecrypt = (text: string, key: string, iv: string): string => {
|
||||
const decipher = createDecipheriv('aes-128-cbc', Buffer.from(key, 'base64'), Buffer.from(iv, 'base64'))
|
||||
return Buffer.concat([decipher.update(Buffer.from(text, 'base64')), decipher.final()]).toString()
|
||||
}
|
||||
|
||||
export const encryptMsg = (keyInfo: LX.Sync.KeyInfo, msg: string): string => {
|
||||
return msg
|
||||
// if (!keyInfo) return ''
|
||||
// return aesEncrypt(msg, keyInfo.key, keyInfo.iv)
|
||||
}
|
||||
|
||||
export const decryptMsg = (keyInfo: LX.Sync.KeyInfo, enMsg: string): string => {
|
||||
return enMsg
|
||||
// if (!keyInfo) return ''
|
||||
// let msg = ''
|
||||
// try {
|
||||
// msg = aesDecrypt(enMsg, keyInfo.key, keyInfo.iv)
|
||||
// } catch (err) {
|
||||
// console.log(err)
|
||||
// }
|
||||
// return msg
|
||||
}
|
||||
|
||||
export const getSnapshotFilePath = (keyInfo: LX.Sync.KeyInfo): string => {
|
||||
return join(global.lxDataPath, `snapshot-${Buffer.from(keyInfo.clientId).toString('hex').substring(0, 10)}.json`)
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
const { EventEmitter } = require('events')
|
||||
const TASKBAR_EVENT_NAME = require('./name')
|
||||
|
||||
class TaskBar extends EventEmitter {
|
||||
thumbarButtonClick(type) {
|
||||
this.emit(TASKBAR_EVENT_NAME.thumbarButtonClick, type)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TaskBar
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = {
|
||||
thumbarButtonClick: 'thumbarButtonClick',
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
const path = require('path')
|
||||
const Event = require('./event/event')
|
||||
const eventNames = require('./event/name')
|
||||
|
||||
exports.Event = Event
|
||||
exports.eventNames = eventNames
|
||||
|
||||
exports.setThumbnailClip = (region = { x: 0, y: 0, width: 0, height: 0 }) => {
|
||||
if (!global.modules.mainWindow) return
|
||||
return global.modules.mainWindow.setThumbnailClip(region)
|
||||
}
|
||||
|
||||
const getIconPath = name => {
|
||||
return path.join(global.__static, 'images/taskbar', name + '.png')
|
||||
}
|
||||
|
||||
const buttonsFlags = {
|
||||
empty: true,
|
||||
collect: false,
|
||||
play: false,
|
||||
next: true,
|
||||
prev: true,
|
||||
}
|
||||
const createButtons = ({ empty = false, collect = false, play = false, next = true, prev = true }) => {
|
||||
const buttons = [
|
||||
collect
|
||||
? {
|
||||
icon: getIconPath('collected'),
|
||||
click() {
|
||||
global.lx_event.taskbar.thumbarButtonClick('unCollect')
|
||||
},
|
||||
tooltip: '取消收藏',
|
||||
flags: ['nobackground'],
|
||||
}
|
||||
: {
|
||||
icon: getIconPath('collect'),
|
||||
click() {
|
||||
global.lx_event.taskbar.thumbarButtonClick('collect')
|
||||
},
|
||||
tooltip: '收藏',
|
||||
flags: ['nobackground'],
|
||||
},
|
||||
{
|
||||
icon: getIconPath('prev'),
|
||||
click() {
|
||||
global.lx_event.taskbar.thumbarButtonClick('prev')
|
||||
},
|
||||
tooltip: '上一曲',
|
||||
flags: prev ? ['nobackground'] : ['nobackground', 'disabled'],
|
||||
},
|
||||
play
|
||||
? {
|
||||
icon: getIconPath('pause'),
|
||||
click() {
|
||||
global.lx_event.taskbar.thumbarButtonClick('pause')
|
||||
},
|
||||
tooltip: '暂停',
|
||||
flags: ['nobackground'],
|
||||
}
|
||||
: {
|
||||
icon: getIconPath('play'),
|
||||
click() {
|
||||
global.lx_event.taskbar.thumbarButtonClick('play')
|
||||
},
|
||||
tooltip: '播放',
|
||||
flags: ['nobackground'],
|
||||
},
|
||||
{
|
||||
icon: getIconPath('next'),
|
||||
click() {
|
||||
global.lx_event.taskbar.thumbarButtonClick('next')
|
||||
},
|
||||
tooltip: '下一曲',
|
||||
flags: next ? ['nobackground'] : ['nobackground', 'disabled'],
|
||||
},
|
||||
]
|
||||
if (empty) {
|
||||
for (const button of buttons) {
|
||||
button.flags = ['nobackground', 'disabled']
|
||||
}
|
||||
}
|
||||
return buttons
|
||||
}
|
||||
exports.setThumbarButtons = ({ empty, collect, play, next, prev } = buttonsFlags) => {
|
||||
if (!global.modules.mainWindow) return
|
||||
buttonsFlags.empty = empty
|
||||
buttonsFlags.collect = collect
|
||||
buttonsFlags.play = play
|
||||
buttonsFlags.next = next
|
||||
buttonsFlags.prev = prev
|
||||
global.modules.mainWindow.setThumbarButtons(createButtons(buttonsFlags))
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
const { app, Tray, Menu, nativeImage } = require('electron')
|
||||
const { isWin } = require('@common/utils')
|
||||
const { tray: TRAY_EVENT_NAME, common: COMMON_EVENT_NAME, mainWindow: MAIN_WINDOW_NAME } = require('../events/_name')
|
||||
const path = require('path')
|
||||
let isEnableTray = null
|
||||
let themeId = null
|
||||
const themeList = [
|
||||
{
|
||||
id: 0,
|
||||
fileName: 'trayTemplate',
|
||||
isNative: true,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
fileName: 'tray_origin',
|
||||
isNative: false,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
fileName: 'tray_black',
|
||||
isNative: false,
|
||||
},
|
||||
]
|
||||
global.lx_event.common.on(COMMON_EVENT_NAME.configStatus, sourceName => {
|
||||
if (sourceName === TRAY_EVENT_NAME.name) return
|
||||
if (themeId !== global.appSetting.tray.themeId) {
|
||||
themeId = global.appSetting.tray.themeId
|
||||
setTrayImage(themeId)
|
||||
}
|
||||
if (isEnableTray !== global.appSetting.tray.isShow) {
|
||||
isEnableTray = global.appSetting.tray.isShow
|
||||
global.appSetting.tray.isShow ? createTray() : destroyTray()
|
||||
}
|
||||
createMenu(global.modules.tray)
|
||||
})
|
||||
|
||||
global.lx_event.mainWindow.on(MAIN_WINDOW_NAME.ready_to_show, () => {
|
||||
createMenu(global.modules.tray)
|
||||
})
|
||||
global.lx_event.mainWindow.on(MAIN_WINDOW_NAME.show, () => {
|
||||
createMenu(global.modules.tray)
|
||||
})
|
||||
if (!isWin) {
|
||||
global.lx_event.mainWindow.on(MAIN_WINDOW_NAME.focus, () => {
|
||||
createMenu(global.modules.tray)
|
||||
})
|
||||
global.lx_event.mainWindow.on(MAIN_WINDOW_NAME.blur, () => {
|
||||
createMenu(global.modules.tray)
|
||||
})
|
||||
}
|
||||
global.lx_event.mainWindow.on(MAIN_WINDOW_NAME.hide, () => {
|
||||
createMenu(global.modules.tray)
|
||||
})
|
||||
|
||||
const createTray = () => {
|
||||
if ((global.modules.tray && !global.modules.tray.isDestroyed()) || !global.appSetting.tray || !global.appSetting.tray.isShow) return
|
||||
|
||||
themeId = global.appSetting.tray.themeId
|
||||
let theme = themeList.find(item => item.id === themeId) || themeList[0]
|
||||
const iconPath = path.join(global.__static, 'images/tray', theme.fileName + '.png')
|
||||
|
||||
// 托盘
|
||||
global.modules.tray = new Tray(nativeImage.createFromPath(iconPath))
|
||||
|
||||
global.modules.tray.setToolTip('洛雪音乐助手')
|
||||
createMenu(global.modules.tray)
|
||||
global.modules.tray.setIgnoreDoubleClickEvents(true)
|
||||
global.modules.tray.on('click', () => {
|
||||
const mainWindow = global.modules.mainWindow
|
||||
if (!mainWindow) return
|
||||
mainWindow.isVisible()
|
||||
? mainWindow.focus()
|
||||
: mainWindow.show()
|
||||
})
|
||||
}
|
||||
|
||||
const destroyTray = () => {
|
||||
if (!global.modules.tray) return
|
||||
global.modules.tray.destroy()
|
||||
global.modules.tray = null
|
||||
}
|
||||
|
||||
const createMenu = tray => {
|
||||
if (!global.modules.tray) return
|
||||
let menu = []
|
||||
if (global.modules.mainWindow) {
|
||||
const isShow = global.modules.mainWindow.isVisible() && (isWin ? true : global.modules.mainWindow.isFocused())
|
||||
menu.push(isShow
|
||||
? {
|
||||
label: '隐藏主界面',
|
||||
click() {
|
||||
global.modules.mainWindow.hide()
|
||||
},
|
||||
}
|
||||
: {
|
||||
label: '显示主界面',
|
||||
click() {
|
||||
if (!global.modules.mainWindow) return
|
||||
if (!global.modules.mainWindow.isVisible()) {
|
||||
global.modules.mainWindow.show()
|
||||
}
|
||||
global.modules.mainWindow.restore()
|
||||
global.modules.mainWindow.focus()
|
||||
},
|
||||
})
|
||||
}
|
||||
menu.push(global.appSetting.desktopLyric.enable
|
||||
? {
|
||||
label: '关闭桌面歌词',
|
||||
click() {
|
||||
global.lx_core.setAppConfig({ desktopLyric: { enable: false } }, TRAY_EVENT_NAME.name)
|
||||
},
|
||||
}
|
||||
: {
|
||||
label: '开启桌面歌词',
|
||||
click() {
|
||||
global.lx_core.setAppConfig({ desktopLyric: { enable: true } }, TRAY_EVENT_NAME.name)
|
||||
},
|
||||
})
|
||||
menu.push(global.appSetting.desktopLyric.isLock
|
||||
? {
|
||||
label: '解锁桌面歌词',
|
||||
click() {
|
||||
global.lx_core.setAppConfig({ desktopLyric: { isLock: false } }, TRAY_EVENT_NAME.name)
|
||||
},
|
||||
}
|
||||
: {
|
||||
label: '锁定桌面歌词',
|
||||
click() {
|
||||
global.lx_core.setAppConfig({ desktopLyric: { isLock: true } }, TRAY_EVENT_NAME.name)
|
||||
},
|
||||
})
|
||||
menu.push(global.appSetting.desktopLyric.isAlwaysOnTop
|
||||
? {
|
||||
label: '取消置顶',
|
||||
click() {
|
||||
global.lx_core.setAppConfig({ desktopLyric: { isAlwaysOnTop: false } }, TRAY_EVENT_NAME.name)
|
||||
},
|
||||
}
|
||||
: {
|
||||
label: '置顶歌词',
|
||||
click() {
|
||||
global.lx_core.setAppConfig({ desktopLyric: { isAlwaysOnTop: true } }, TRAY_EVENT_NAME.name)
|
||||
},
|
||||
})
|
||||
menu.push({
|
||||
label: '退出',
|
||||
click() {
|
||||
global.isQuitting = true
|
||||
app.quit()
|
||||
},
|
||||
})
|
||||
const contextMenu = Menu.buildFromTemplate(menu)
|
||||
tray.setContextMenu(contextMenu)
|
||||
}
|
||||
|
||||
const setTrayImage = themeId => {
|
||||
if (!global.modules.tray) return
|
||||
let theme = themeList.find(item => item.id === themeId) || themeList[0]
|
||||
const iconPath = path.join(global.__static, 'images/tray', theme.fileName + '.png')
|
||||
global.modules.tray.setImage(nativeImage.createFromPath(iconPath))
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
import { Tray, Menu, nativeImage } from 'electron'
|
||||
import { isWin } from '@common/utils'
|
||||
import { join } from 'path'
|
||||
import {
|
||||
hideWindow as hideMainWindow,
|
||||
isExistWindow as isExistMainWindow,
|
||||
isShowWindow as isShowMainWindow,
|
||||
showWindow as showMainWindow,
|
||||
} from './winMain'
|
||||
import { quitApp } from '@main/app'
|
||||
|
||||
let tray: Electron.Tray | null
|
||||
let isEnableTray: boolean = false
|
||||
let themeId: number
|
||||
|
||||
const watchConfigKeys = [
|
||||
'desktopLyric.enable',
|
||||
'desktopLyric.isLock',
|
||||
'desktopLyric.isAlwaysOnTop',
|
||||
'tray.themeId',
|
||||
'tray.enable',
|
||||
] as const
|
||||
|
||||
const themeList = [
|
||||
{
|
||||
id: 0,
|
||||
fileName: 'trayTemplate',
|
||||
isNative: true,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
fileName: 'tray_origin',
|
||||
isNative: false,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
fileName: 'tray_black',
|
||||
isNative: false,
|
||||
},
|
||||
]
|
||||
|
||||
export const createTray = () => {
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
if ((tray && !tray.isDestroyed()) || !global.lx.appSetting['tray.enable']) return
|
||||
|
||||
themeId = global.lx.appSetting['tray.themeId']
|
||||
let theme = themeList.find(item => item.id === themeId) ?? themeList[0]
|
||||
const iconPath = join(global.staticPath, 'images/tray', theme.fileName + '.png')
|
||||
|
||||
// 托盘
|
||||
tray = new Tray(nativeImage.createFromPath(iconPath))
|
||||
|
||||
tray.setToolTip('洛雪音乐助手')
|
||||
createMenu()
|
||||
tray.setIgnoreDoubleClickEvents(true)
|
||||
tray.on('click', () => {
|
||||
showMainWindow()
|
||||
})
|
||||
}
|
||||
|
||||
export const destroyTray = () => {
|
||||
if (!tray) return
|
||||
tray.destroy()
|
||||
isEnableTray = false
|
||||
tray = null
|
||||
}
|
||||
|
||||
const handleUpdateConfig = (config: any) => {
|
||||
global.lx.event_app.update_config(config)
|
||||
}
|
||||
|
||||
export const createMenu = () => {
|
||||
if (!tray) return
|
||||
let menu = []
|
||||
if (isExistMainWindow()) {
|
||||
const isShow = isShowMainWindow()
|
||||
menu.push(isShow
|
||||
? {
|
||||
label: '隐藏主界面',
|
||||
click() {
|
||||
hideMainWindow()
|
||||
},
|
||||
}
|
||||
: {
|
||||
label: '显示主界面',
|
||||
click() {
|
||||
showMainWindow()
|
||||
},
|
||||
})
|
||||
}
|
||||
menu.push(global.lx.appSetting['desktopLyric.enable']
|
||||
? {
|
||||
label: '关闭桌面歌词',
|
||||
click() {
|
||||
handleUpdateConfig({ 'desktopLyric.enable': false })
|
||||
},
|
||||
}
|
||||
: {
|
||||
label: '开启桌面歌词',
|
||||
click() {
|
||||
handleUpdateConfig({ 'desktopLyric.enable': true })
|
||||
},
|
||||
})
|
||||
menu.push(global.lx.appSetting['desktopLyric.isLock']
|
||||
? {
|
||||
label: '解锁桌面歌词',
|
||||
click() {
|
||||
handleUpdateConfig({ 'desktopLyric.isLock': false })
|
||||
},
|
||||
}
|
||||
: {
|
||||
label: '锁定桌面歌词',
|
||||
click() {
|
||||
handleUpdateConfig({ 'desktopLyric.isLock': true })
|
||||
},
|
||||
})
|
||||
menu.push(global.lx.appSetting['desktopLyric.isAlwaysOnTop']
|
||||
? {
|
||||
label: '取消置顶',
|
||||
click() {
|
||||
handleUpdateConfig({ 'desktopLyric.isAlwaysOnTop': false })
|
||||
},
|
||||
}
|
||||
: {
|
||||
label: '置顶歌词',
|
||||
click() {
|
||||
handleUpdateConfig({ 'desktopLyric.isAlwaysOnTop': true })
|
||||
},
|
||||
})
|
||||
menu.push({
|
||||
label: '退出',
|
||||
click() {
|
||||
quitApp()
|
||||
},
|
||||
})
|
||||
const contextMenu = Menu.buildFromTemplate(menu)
|
||||
tray.setContextMenu(contextMenu)
|
||||
}
|
||||
|
||||
export const setTrayImage = (themeId: number) => {
|
||||
if (!tray) return
|
||||
let theme = themeList.find(item => item.id === themeId) ?? themeList[0]
|
||||
const iconPath = join(global.staticPath, 'images/tray', theme.fileName + '.png')
|
||||
tray.setImage(nativeImage.createFromPath(iconPath))
|
||||
}
|
||||
|
||||
const init = () => {
|
||||
if (themeId != global.lx.appSetting['tray.themeId']) {
|
||||
themeId = global.lx.appSetting['tray.themeId']
|
||||
setTrayImage(themeId)
|
||||
}
|
||||
if (isEnableTray !== global.lx.appSetting['tray.enable']) {
|
||||
isEnableTray = global.lx.appSetting['tray.enable']
|
||||
global.lx.appSetting['tray.enable'] ? createTray() : destroyTray()
|
||||
}
|
||||
createMenu()
|
||||
}
|
||||
|
||||
export default () => {
|
||||
global.lx.event_app.on('updated_config', (keys) => {
|
||||
if (!watchConfigKeys.some(key => keys.includes(key))) return
|
||||
init()
|
||||
})
|
||||
|
||||
global.lx.event_app.on('main_window_ready_to_show', () => {
|
||||
createMenu()
|
||||
})
|
||||
global.lx.event_app.on('main_window_show', () => {
|
||||
createMenu()
|
||||
})
|
||||
if (!isWin) {
|
||||
global.lx.event_app.on('main_window_focus', () => {
|
||||
createMenu()
|
||||
})
|
||||
global.lx.event_app.on('main_window_blur', () => {
|
||||
createMenu()
|
||||
})
|
||||
}
|
||||
global.lx.event_app.on('main_window_hide', () => {
|
||||
createMenu()
|
||||
})
|
||||
global.lx.event_app.on('main_window_close', () => {
|
||||
destroyTray()
|
||||
})
|
||||
|
||||
global.lx.event_app.on('app_inited', () => {
|
||||
init()
|
||||
})
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
const { EventEmitter } = require('events')
|
||||
const USER_API_EVENT_NAME = require('./name')
|
||||
|
||||
class UserApi extends EventEmitter {
|
||||
status(info) {
|
||||
this.emit(USER_API_EVENT_NAME.status, info)
|
||||
}
|
||||
|
||||
showUpdateAlert(info) {
|
||||
this.emit(USER_API_EVENT_NAME.showUpdateAlert, info)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UserApi
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
module.exports = {
|
||||
status: 'status',
|
||||
showUpdateAlert: 'showUpdateAlert',
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
const Event = require('./event/event')
|
||||
const eventNames = require('./event/name')
|
||||
const { closeWindow } = require('./main')
|
||||
const { getUserApis, importApi, removeApi, setAllowShowUpdateAlert: saveAllowShowUpdateAlert } = require('./utils')
|
||||
const { request, cancelRequest, getStatus, loadApi, setAllowShowUpdateAlert } = require('./rendererEvent/rendererEvent')
|
||||
|
||||
// const { getApiList, importApi, removeApi, setApi, getStatus, request, eventNames }
|
||||
let userApiId
|
||||
|
||||
exports.Event = Event
|
||||
exports.eventNames = eventNames
|
||||
|
||||
exports.getApiList = getUserApis
|
||||
exports.importApi = script => {
|
||||
return {
|
||||
apiInfo: importApi(script),
|
||||
apiList: getUserApis(),
|
||||
}
|
||||
}
|
||||
exports.request = request
|
||||
exports.cancelRequest = cancelRequest
|
||||
exports.getStatus = getStatus
|
||||
|
||||
exports.removeApi = async ids => {
|
||||
if (userApiId && ids.includes(userApiId)) {
|
||||
userApiId = null
|
||||
await closeWindow()
|
||||
}
|
||||
removeApi(ids)
|
||||
return getUserApis()
|
||||
}
|
||||
|
||||
exports.setApi = async id => {
|
||||
if (userApiId) {
|
||||
userApiId = null
|
||||
await closeWindow()
|
||||
}
|
||||
const apiList = getUserApis()
|
||||
if (!apiList.some(a => a.id === id)) return
|
||||
userApiId = id
|
||||
await loadApi(id)
|
||||
}
|
||||
|
||||
exports.setAllowShowUpdateAlert = (id, enable) => {
|
||||
saveAllowShowUpdateAlert(id, enable)
|
||||
setAllowShowUpdateAlert(id, enable)
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { closeWindow } from './main'
|
||||
import { getUserApis, importApi as handleImportApi, removeApi as handleRemoveApi, setAllowShowUpdateAlert as saveAllowShowUpdateAlert } from './utils'
|
||||
import { loadApi, setAllowShowUpdateAlert as setRendererEventAllowShowUpdateAlert, init } from './rendererEvent/rendererEvent'
|
||||
|
||||
let userApiId: string | null
|
||||
|
||||
export const getApiList = getUserApis
|
||||
|
||||
export const importApi = (script: string): LX.UserApi.ImportUserApi => {
|
||||
return {
|
||||
apiInfo: handleImportApi(script),
|
||||
apiList: getUserApis(),
|
||||
}
|
||||
}
|
||||
export const removeApi = async(ids: string[]): Promise<LX.UserApi.UserApiInfo[]> => {
|
||||
if (userApiId && ids.includes(userApiId)) {
|
||||
userApiId = null
|
||||
await closeWindow()
|
||||
}
|
||||
handleRemoveApi(ids)
|
||||
return getUserApis()
|
||||
}
|
||||
|
||||
export const setApi = async(id: string) => {
|
||||
if (userApiId) {
|
||||
userApiId = null
|
||||
await closeWindow()
|
||||
}
|
||||
const apiList = getUserApis()
|
||||
if (!apiList.some(a => a.id === id)) return
|
||||
userApiId = id
|
||||
await loadApi(id)
|
||||
}
|
||||
|
||||
export const setAllowShowUpdateAlert = (id: string, enable: boolean) => {
|
||||
saveAllowShowUpdateAlert(id, enable)
|
||||
setRendererEventAllowShowUpdateAlert(id, enable)
|
||||
}
|
||||
|
||||
|
||||
export * from './rendererEvent/rendererEvent'
|
||||
|
||||
export default () => {
|
||||
init()
|
||||
|
||||
global.lx.event_app.on('main_window_close', () => {
|
||||
void closeWindow()
|
||||
})
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
const { BrowserWindow } = require('electron')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const dir = global.isDev ? __userApi : path.join(__dirname, 'userApi')
|
||||
|
||||
const wait = time => new Promise(resolve => setTimeout(() => resolve(), time))
|
||||
|
||||
let html = ''
|
||||
|
||||
fs.readFile(path.join(dir, 'renderer/user-api.html'), 'utf8', (err, data) => {
|
||||
if (err) throw new Error('api html read failed, info: ' + err.message)
|
||||
html = data
|
||||
})
|
||||
|
||||
const denyEvents = [
|
||||
'will-navigate',
|
||||
'will-redirect',
|
||||
'will-attach-webview',
|
||||
'will-prevent-unload',
|
||||
'media-started-playing',
|
||||
]
|
||||
|
||||
const winEvent = win => {
|
||||
win.on('closed', () => {
|
||||
win = global.modules.userApiWindow = null
|
||||
})
|
||||
}
|
||||
|
||||
exports.createWindow = async userApi => {
|
||||
if (global.modules.userApiWindow) return
|
||||
while (true) {
|
||||
if (html) break
|
||||
await wait(100)
|
||||
}
|
||||
/**
|
||||
* Initial window options
|
||||
*/
|
||||
global.modules.userApiWindow = new BrowserWindow({
|
||||
enableRemoteModule: false,
|
||||
resizable: false,
|
||||
minimizable: false,
|
||||
maximizable: false,
|
||||
fullscreenable: false,
|
||||
show: false,
|
||||
webPreferences: {
|
||||
contextIsolation: true,
|
||||
// worldSafeExecuteJavaScript: true,
|
||||
nodeIntegration: false,
|
||||
nodeIntegrationInWorker: false,
|
||||
|
||||
spellcheck: false,
|
||||
autoplayPolicy: 'document-user-activation-required',
|
||||
enableWebSQL: false,
|
||||
disableDialogs: true,
|
||||
nativeWindowOpen: false,
|
||||
webgl: false,
|
||||
images: false,
|
||||
|
||||
preload: path.join(dir, 'renderer/preload.js'),
|
||||
},
|
||||
})
|
||||
|
||||
for (const eventName of denyEvents) {
|
||||
global.modules.userApiWindow.webContents.on(eventName, event => {
|
||||
event.preventDefault()
|
||||
})
|
||||
}
|
||||
global.modules.userApiWindow.webContents.session.setPermissionRequestHandler((webContents, permission, resolve) => {
|
||||
if (webContents === global.modules.mainWindow.webContents) return resolve(true)
|
||||
|
||||
resolve(false)
|
||||
})
|
||||
global.modules.userApiWindow.webContents.setWindowOpenHandler(() => {
|
||||
return { action: 'deny' }
|
||||
})
|
||||
|
||||
winEvent(global.modules.userApiWindow)
|
||||
|
||||
// console.log(html.replace('</body>', `<script>${userApi.script}</script></body>`))
|
||||
const randomNum = Math.random().toString().substring(2, 10)
|
||||
global.modules.userApiWindow.loadURL(
|
||||
'data:text/html;charset=UTF-8,' + encodeURIComponent(html
|
||||
.replace('<meta http-equiv="Content-Security-Policy" content="default-src \'none\'">',
|
||||
`<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'nonce-${randomNum}';">`)
|
||||
.replace('</body>', `<script nonce="${randomNum}">${userApi.script}</script></body>`)))
|
||||
|
||||
// global.modules.userApiWindow.loadFile(path.join(dir, 'renderer/user-api.html'))
|
||||
// global.modules.userApiWindow.webContents.openDevTools()
|
||||
}
|
||||
|
||||
exports.closeWindow = async() => {
|
||||
if (!global.modules.userApiWindow) return
|
||||
await Promise.all([
|
||||
global.modules.userApiWindow.webContents.session.clearAuthCache(),
|
||||
global.modules.userApiWindow.webContents.session.clearStorageData(),
|
||||
global.modules.userApiWindow.webContents.session.clearCache(),
|
||||
])
|
||||
global.modules.userApiWindow.destroy()
|
||||
global.modules.userApiWindow = null
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
import { mainSend } from '@common/mainIpc'
|
||||
import { BrowserWindow } from 'electron'
|
||||
import fs from 'fs'
|
||||
import { join } from 'path'
|
||||
import { openDevTools as handleOpenDevTools } from '@main/utils'
|
||||
import { encodePath, isWin } from '@common/utils'
|
||||
|
||||
let browserWindow: Electron.BrowserWindow | null = null
|
||||
|
||||
let html: string | null = null
|
||||
let dir: string | null = null
|
||||
|
||||
const denyEvents = [
|
||||
'will-navigate',
|
||||
'will-redirect',
|
||||
'will-attach-webview',
|
||||
'will-prevent-unload',
|
||||
'media-started-playing',
|
||||
] as const
|
||||
|
||||
const winEvent = () => {
|
||||
if (!browserWindow) return
|
||||
browserWindow.on('closed', () => {
|
||||
browserWindow = null
|
||||
})
|
||||
}
|
||||
|
||||
export const createWindow = async(userApi: LX.UserApi.UserApiInfo) => {
|
||||
await closeWindow()
|
||||
if (!dir) dir = global.isDev ? webpackUserApiPath : join(encodePath(isWin ? __dirname.replaceAll('\\', '/') : __dirname), 'userApi')
|
||||
|
||||
if (!html) {
|
||||
html = await fs.promises.readFile(join(dir, 'renderer/user-api.html'), 'utf8')
|
||||
}
|
||||
const preloadUrl = global.isDev
|
||||
? `${join(encodePath(isWin ? __dirname.replaceAll('\\', '/') : __dirname), '../dist/user-api-preload.js')}`
|
||||
: `${join(encodePath(isWin ? __dirname.replaceAll('\\', '/') : __dirname), 'user-api-preload.js')}`
|
||||
// console.log(preloadUrl)
|
||||
|
||||
/**
|
||||
* Initial window options
|
||||
*/
|
||||
browserWindow = new BrowserWindow({
|
||||
// enableRemoteModule: false,
|
||||
resizable: false,
|
||||
minimizable: false,
|
||||
maximizable: false,
|
||||
fullscreenable: false,
|
||||
show: false,
|
||||
webPreferences: {
|
||||
contextIsolation: true,
|
||||
// worldSafeExecuteJavaScript: true,
|
||||
nodeIntegration: false,
|
||||
nodeIntegrationInWorker: false,
|
||||
|
||||
spellcheck: false,
|
||||
autoplayPolicy: 'document-user-activation-required',
|
||||
enableWebSQL: false,
|
||||
disableDialogs: true,
|
||||
// nativeWindowOpen: false,
|
||||
webgl: false,
|
||||
images: false,
|
||||
|
||||
preload: preloadUrl,
|
||||
},
|
||||
})
|
||||
|
||||
for (const eventName of denyEvents) {
|
||||
// @ts-expect-error
|
||||
browserWindow.webContents.on(eventName, (event: Electron.Event) => {
|
||||
event.preventDefault()
|
||||
})
|
||||
}
|
||||
browserWindow.webContents.session.setPermissionRequestHandler((webContents, permission, resolve) => {
|
||||
if (webContents === browserWindow?.webContents) return resolve(true)
|
||||
|
||||
resolve(false)
|
||||
})
|
||||
browserWindow.webContents.setWindowOpenHandler(() => {
|
||||
return { action: 'deny' }
|
||||
})
|
||||
|
||||
winEvent()
|
||||
|
||||
// console.log(html.replace('</body>', `<script>${userApi.script}</script></body>`))
|
||||
const randomNum = Math.random().toString().substring(2, 10)
|
||||
await browserWindow.loadURL(
|
||||
'data:text/html;charset=UTF-8,' + encodeURIComponent(html
|
||||
.replace('<meta http-equiv="Content-Security-Policy" content="default-src \'none\'">',
|
||||
`<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'nonce-${randomNum}';">`)
|
||||
.replace('</body>', `<script nonce="${randomNum}">${userApi.script}</script></body>`)))
|
||||
|
||||
// global.modules.userApiWindow.loadFile(join(dir, 'renderer/user-api.html'))
|
||||
// global.modules.userApiWindow.webContents.openDevTools()
|
||||
}
|
||||
|
||||
export const closeWindow = async() => {
|
||||
if (!browserWindow) return
|
||||
await Promise.all([
|
||||
browserWindow.webContents.session.clearAuthCache(),
|
||||
browserWindow.webContents.session.clearStorageData(),
|
||||
browserWindow.webContents.session.clearCache(),
|
||||
])
|
||||
browserWindow.destroy()
|
||||
browserWindow = null
|
||||
}
|
||||
|
||||
export const sendEvent = <T = any>(name: string, params?: T) => {
|
||||
if (!browserWindow) return
|
||||
mainSend(browserWindow, name, params)
|
||||
}
|
||||
|
||||
export const openDevTools = () => {
|
||||
if (!browserWindow) return
|
||||
handleOpenDevTools(browserWindow.webContents)
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
const { mainOn, mainSend } = require('@common/ipc')
|
||||
|
||||
const USER_API_RENDERER_EVENT_NAME = require('../rendererEvent/name')
|
||||
const { createWindow } = require('../main')
|
||||
const { getUserApis } = require('../utils')
|
||||
const { openDevTools } = require('@main/utils')
|
||||
|
||||
let userApi
|
||||
let status = { status: true }
|
||||
const requestQueue = new Map()
|
||||
const timeouts = {}
|
||||
|
||||
const handleInit = (event, { status, message, data: apiInfo }) => {
|
||||
// console.log('inited')
|
||||
if (!status) {
|
||||
console.log('init failed:', message)
|
||||
global.lx_event.userApi.status(status = { status: false, apiInfo: userApi, message })
|
||||
return
|
||||
}
|
||||
global.lx_event.userApi.status(status = { status: true, apiInfo: { ...userApi, sources: apiInfo.sources } })
|
||||
}
|
||||
const handleResponse = (event, { status, data: { requestKey, result }, message }) => {
|
||||
const request = requestQueue.get(requestKey)
|
||||
if (!request) return
|
||||
requestQueue.delete(requestKey)
|
||||
clearTimeout(timeouts[requestKey])
|
||||
delete timeouts[requestKey]
|
||||
if (status) {
|
||||
request[0](result)
|
||||
} else {
|
||||
request[1](new Error(message))
|
||||
}
|
||||
}
|
||||
const handleOpenDevTools = () => {
|
||||
if (global.modules.userApiWindow) {
|
||||
openDevTools(global.modules.userApiWindow.webContents)
|
||||
}
|
||||
}
|
||||
const handleShowUpdateAlert = (event, { data }) => {
|
||||
if (!userApi.allowShowUpdateAlert) return
|
||||
global.lx_event.userApi.showUpdateAlert({
|
||||
name: userApi.name,
|
||||
description: userApi.description,
|
||||
log: data.log,
|
||||
updateUrl: data.updateUrl,
|
||||
})
|
||||
}
|
||||
mainOn(USER_API_RENDERER_EVENT_NAME.init, handleInit)
|
||||
mainOn(USER_API_RENDERER_EVENT_NAME.response, handleResponse)
|
||||
mainOn(USER_API_RENDERER_EVENT_NAME.openDevTools, handleOpenDevTools)
|
||||
mainOn(USER_API_RENDERER_EVENT_NAME.showUpdateAlert, handleShowUpdateAlert)
|
||||
|
||||
exports.loadApi = async apiId => {
|
||||
if (!apiId) return global.lx_event.userApi.status(status = { status: false, message: 'api id is null' })
|
||||
userApi = getUserApis().find(api => api.id == apiId)
|
||||
console.log('load api', userApi.name)
|
||||
await createWindow(userApi)
|
||||
// if (!userApi) return global.lx_event.userApi.status(status = { status: false, message: 'api script is not found' })
|
||||
// if (!global.modules.userApiWindow) {
|
||||
// global.lx_event.userApi.status(status = { status: false, message: 'user api runtime is not defined' })
|
||||
// throw new Error('user api window is not defined')
|
||||
// }
|
||||
|
||||
// // const path = require('path')
|
||||
// // // eslint-disable-next-line no-undef
|
||||
// // userApi.script = require('fs').readFileSync(path.join(global.isDev ? __userApi : __dirname, 'renderer/test-api.js')).toString()
|
||||
// console.log('load api', userApi.name)
|
||||
// mainSend(global.modules.userApiWindow, USER_API_RENDERER_EVENT_NAME.init, { userApi })
|
||||
}
|
||||
|
||||
exports.cancelRequest = requestKey => {
|
||||
if (!requestQueue.has(requestKey)) return
|
||||
const request = requestQueue.get(requestKey)
|
||||
request[1](new Error('Cancel request'))
|
||||
requestQueue.delete(requestKey)
|
||||
clearTimeout(timeouts[requestKey])
|
||||
delete timeouts[requestKey]
|
||||
}
|
||||
|
||||
exports.request = ({ requestKey, data }) => new Promise((resolve, reject) => {
|
||||
if (!userApi) return reject(new Error('user api is not load'))
|
||||
|
||||
// const requestKey = `request__${Math.random().toString().substring(2)}`
|
||||
|
||||
timeouts[requestKey] = setTimeout(() => {
|
||||
exports.cancelRequest(requestKey)
|
||||
}, 20000)
|
||||
|
||||
requestQueue.set(requestKey, [resolve, reject, data])
|
||||
mainSend(global.modules.userApiWindow, USER_API_RENDERER_EVENT_NAME.request, { requestKey, data })
|
||||
})
|
||||
|
||||
exports.getStatus = () => status
|
||||
|
||||
exports.setAllowShowUpdateAlert = (id, enable) => {
|
||||
if (!userApi || userApi.id != id) return
|
||||
userApi.allowShowUpdateAlert = enable
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
import { mainOn } from '@common/mainIpc'
|
||||
|
||||
import USER_API_RENDERER_EVENT_NAME from './name'
|
||||
import { createWindow, openDevTools, sendEvent } from '../main'
|
||||
import { getUserApis } from '../utils'
|
||||
import { sendShowUpdateAlert, sendStatusChange } from '@main/modules/winMain'
|
||||
|
||||
let userApi: LX.UserApi.UserApiInfo
|
||||
let apiStatus: LX.UserApi.UserApiStatus = { status: true }
|
||||
const requestQueue = new Map()
|
||||
const timeouts: Map<string, NodeJS.Timeout> = new Map()
|
||||
interface InitParams {
|
||||
params: {
|
||||
status: boolean
|
||||
message: string
|
||||
data: LX.UserApi.UserApiInfo
|
||||
}
|
||||
}
|
||||
interface ResponseParams {
|
||||
params: {
|
||||
status: boolean
|
||||
message: string
|
||||
data: {
|
||||
requestKey: string
|
||||
result: any
|
||||
}
|
||||
}
|
||||
}
|
||||
interface UpdateInfoParams {
|
||||
params: {
|
||||
data: {
|
||||
log: string
|
||||
updateUrl: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const init = () => {
|
||||
const handleInit = ({ params: { status, message, data: apiInfo } }: InitParams) => {
|
||||
// console.log('inited')
|
||||
// if (!status) {
|
||||
// console.log('init failed:', message)
|
||||
// global.lx_event.userApi.status(status = { status: true, apiInfo: { ...userApi, sources: apiInfo.sources } })
|
||||
// return
|
||||
// }
|
||||
apiStatus = status
|
||||
? { status: true, apiInfo: { ...userApi, sources: apiInfo.sources } }
|
||||
: { status: false, apiInfo: userApi, message }
|
||||
sendStatusChange(apiStatus)
|
||||
}
|
||||
const handleResponse = ({ params: { status, data: { requestKey, result }, message } }: ResponseParams) => {
|
||||
const request = requestQueue.get(requestKey)
|
||||
if (!request) return
|
||||
requestQueue.delete(requestKey)
|
||||
clearRequestTimeout(requestKey)
|
||||
if (status) {
|
||||
request[0](result)
|
||||
} else {
|
||||
request[1](new Error(message))
|
||||
}
|
||||
}
|
||||
const handleOpenDevTools = () => {
|
||||
openDevTools()
|
||||
}
|
||||
const handleShowUpdateAlert = ({ params: { data } }: UpdateInfoParams) => {
|
||||
if (!userApi.allowShowUpdateAlert) return
|
||||
sendShowUpdateAlert({
|
||||
name: userApi.name,
|
||||
description: userApi.description,
|
||||
log: data.log,
|
||||
updateUrl: data.updateUrl,
|
||||
})
|
||||
}
|
||||
mainOn(USER_API_RENDERER_EVENT_NAME.init, handleInit)
|
||||
mainOn(USER_API_RENDERER_EVENT_NAME.response, handleResponse)
|
||||
mainOn(USER_API_RENDERER_EVENT_NAME.openDevTools, handleOpenDevTools)
|
||||
mainOn(USER_API_RENDERER_EVENT_NAME.showUpdateAlert, handleShowUpdateAlert)
|
||||
}
|
||||
|
||||
export const clearRequestTimeout = (requestKey: string) => {
|
||||
const timeout = timeouts.get(requestKey)
|
||||
if (timeout) {
|
||||
clearTimeout(timeout)
|
||||
timeouts.delete(requestKey)
|
||||
}
|
||||
}
|
||||
|
||||
export const loadApi = async(apiId: string) => {
|
||||
if (!apiId) {
|
||||
apiStatus = { status: false, message: 'api id is null' }
|
||||
sendStatusChange(apiStatus)
|
||||
return
|
||||
}
|
||||
const targetApi = getUserApis().find(api => api.id == apiId)
|
||||
if (!targetApi) throw new Error('api not found')
|
||||
userApi = targetApi
|
||||
console.log('load api', userApi.name)
|
||||
await createWindow(userApi)
|
||||
// if (!userApi) return global.lx_event.userApi.status(status = { status: false, message: 'api script is not found' })
|
||||
// if (!global.modules.userApiWindow) {
|
||||
// global.lx_event.userApi.status(status = { status: false, message: 'user api runtime is not defined' })
|
||||
// throw new Error('user api window is not defined')
|
||||
// }
|
||||
|
||||
// // const path = require('path')
|
||||
// // // eslint-disable-next-line no-undef
|
||||
// // userApi.script = require('fs').readFileSync(join(global.isDev ? __userApi : __dirname, 'renderer/test-api.js')).toString()
|
||||
// console.log('load api', userApi.name)
|
||||
// mainSend(global.modules.userApiWindow, USER_API_RENDERER_EVENT_NAME.init, { userApi })
|
||||
}
|
||||
|
||||
export const cancelRequest = (requestKey: string) => {
|
||||
if (!requestQueue.has(requestKey)) return
|
||||
const request = requestQueue.get(requestKey)
|
||||
request[1](new Error('Cancel request'))
|
||||
requestQueue.delete(requestKey)
|
||||
clearRequestTimeout(requestKey)
|
||||
}
|
||||
|
||||
export const request = async({ requestKey, data }: LX.UserApi.UserApiRequestParams): Promise<any> => await new Promise((resolve, reject) => {
|
||||
if (!userApi) return reject(new Error('user api is not load'))
|
||||
|
||||
// const requestKey = `request__${Math.random().toString().substring(2)}`
|
||||
const timeout = timeouts.get(requestKey)
|
||||
if (timeout) {
|
||||
clearTimeout(timeout)
|
||||
timeouts.delete(requestKey)
|
||||
cancelRequest(requestKey)
|
||||
}
|
||||
|
||||
timeouts.set(requestKey, setTimeout(() => {
|
||||
cancelRequest(requestKey)
|
||||
}, 20000))
|
||||
|
||||
requestQueue.set(requestKey, [resolve, reject, data])
|
||||
sendRequest({ requestKey, data })
|
||||
})
|
||||
|
||||
export const getStatus = (): LX.UserApi.UserApiStatus => apiStatus
|
||||
|
||||
export const setAllowShowUpdateAlert = (id: string, enable: boolean) => {
|
||||
if (!userApi || userApi.id != id) return
|
||||
userApi.allowShowUpdateAlert = enable
|
||||
}
|
||||
|
||||
export const sendRequest = (reqData: { requestKey: string, data: any }) => {
|
||||
sendEvent(USER_API_RENDERER_EVENT_NAME.request, reqData)
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
const { userApis: defaultUserApis } = require('../config')
|
||||
const getStore = require('@common/store')
|
||||
import { userApis as defaultUserApis } from './config'
|
||||
import { STORE_NAMES } from '@common/constants'
|
||||
import getStore from '@main/utils/store'
|
||||
|
||||
let userApis
|
||||
let userApis: LX.UserApi.UserApiInfo[] | null
|
||||
|
||||
exports.getUserApis = () => {
|
||||
const electronStore_userApi = getStore('userApi')
|
||||
export const getUserApis = (): LX.UserApi.UserApiInfo[] => {
|
||||
const electronStore_userApi = getStore(STORE_NAMES.USER_API)
|
||||
if (userApis) return userApis
|
||||
userApis = electronStore_userApi.get('userApis')
|
||||
userApis = electronStore_userApi.get('userApis') as LX.UserApi.UserApiInfo[]
|
||||
if (!userApis) {
|
||||
userApis = defaultUserApis
|
||||
electronStore_userApi.set('userApis', userApis)
|
||||
@@ -17,7 +18,7 @@ exports.getUserApis = () => {
|
||||
return userApis
|
||||
}
|
||||
|
||||
exports.importApi = script => {
|
||||
export const importApi = (script: string): LX.UserApi.UserApiInfo => {
|
||||
let scriptInfo = script.split(/\r?\n/)
|
||||
let name = scriptInfo[1] || ''
|
||||
let description = scriptInfo[2] || ''
|
||||
@@ -32,24 +33,26 @@ exports.importApi = script => {
|
||||
script,
|
||||
allowShowUpdateAlert: true,
|
||||
}
|
||||
if (!userApis) userApis = []
|
||||
userApis.push(apiInfo)
|
||||
getStore('userApi').set('userApis', userApis)
|
||||
getStore(STORE_NAMES.USER_API).set('userApis', userApis)
|
||||
return apiInfo
|
||||
}
|
||||
|
||||
exports.removeApi = ids => {
|
||||
export const removeApi = (ids: string[]) => {
|
||||
if (!userApis) return
|
||||
for (let index = userApis.length - 1; index > -1; index--) {
|
||||
if (ids.includes(userApis[index].id)) {
|
||||
userApis.splice(index, 1)
|
||||
ids.splice(index, 1)
|
||||
}
|
||||
}
|
||||
getStore('userApi').set('userApis', userApis)
|
||||
getStore(STORE_NAMES.USER_API).set('userApis', userApis)
|
||||
}
|
||||
|
||||
exports.setAllowShowUpdateAlert = (id, enable) => {
|
||||
const targetApi = userApis.find(api => api.id == id)
|
||||
export const setAllowShowUpdateAlert = (id: string, enable: boolean) => {
|
||||
const targetApi = userApis?.find(api => api.id == id)
|
||||
if (!targetApi) return
|
||||
targetApi.allowShowUpdateAlert = enable
|
||||
getStore('userApi').set('userApis', userApis)
|
||||
getStore(STORE_NAMES.USER_API).set('userApis', userApis)
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { isLinux } from '@common/utils'
|
||||
import { closeWindow, createWindow, getBounds, isExistWindow, alwaysOnTopTools, setBounds, setIgnoreMouseEvents } from './main'
|
||||
import { sendConfigChange } from './rendererEvent'
|
||||
import { buildLyricConfig, getLyricWindowBounds, watchConfigKeys } from './utils'
|
||||
|
||||
let isLock: boolean
|
||||
let isEnable: boolean
|
||||
let isAlwaysOnTop: boolean
|
||||
let isAlwaysOnTopLoop: boolean
|
||||
let isLockScreen: boolean
|
||||
let isHoverHide: boolean
|
||||
|
||||
|
||||
export const setLrcConfig = (keys: Array<keyof LX.AppSetting>, setting: Partial<LX.AppSetting>) => {
|
||||
if (!watchConfigKeys.some(key => keys.includes(key))) return
|
||||
|
||||
if (isExistWindow()) {
|
||||
sendConfigChange(buildLyricConfig(setting))
|
||||
if (keys.includes('desktopLyric.isLock') && isLock != global.lx.appSetting['desktopLyric.isLock']) {
|
||||
isLock = global.lx.appSetting['desktopLyric.isLock']
|
||||
if (global.lx.appSetting['desktopLyric.isLock']) {
|
||||
setIgnoreMouseEvents(true, { forward: !isLinux && global.lx.appSetting['desktopLyric.isHoverHide'] })
|
||||
} else {
|
||||
setIgnoreMouseEvents(false, { forward: !isLinux && global.lx.appSetting['desktopLyric.isHoverHide'] })
|
||||
}
|
||||
}
|
||||
if (keys.includes('desktopLyric.isHoverHide') && isHoverHide != global.lx.appSetting['desktopLyric.isHoverHide']) {
|
||||
isHoverHide = global.lx.appSetting['desktopLyric.isHoverHide']
|
||||
if (!isLinux) {
|
||||
setIgnoreMouseEvents(global.lx.appSetting['desktopLyric.isLock'], { forward: global.lx.appSetting['desktopLyric.isHoverHide'] })
|
||||
}
|
||||
}
|
||||
if (keys.includes('desktopLyric.isAlwaysOnTop') && isAlwaysOnTop != global.lx.appSetting['desktopLyric.isAlwaysOnTop']) {
|
||||
isAlwaysOnTop = global.lx.appSetting['desktopLyric.isAlwaysOnTop']
|
||||
alwaysOnTopTools.setAlwaysOnTop(global.lx.appSetting['desktopLyric.isAlwaysOnTopLoop'])
|
||||
if (isAlwaysOnTop && global.lx.appSetting['desktopLyric.isAlwaysOnTopLoop']) {
|
||||
alwaysOnTopTools.startLoop()
|
||||
} else alwaysOnTopTools.clearLoop()
|
||||
}
|
||||
if (keys.includes('desktopLyric.isAlwaysOnTopLoop') && isAlwaysOnTopLoop != global.lx.appSetting['desktopLyric.isAlwaysOnTopLoop']) {
|
||||
isAlwaysOnTopLoop = global.lx.appSetting['desktopLyric.isAlwaysOnTopLoop']
|
||||
if (!global.lx.appSetting['desktopLyric.isAlwaysOnTop']) return
|
||||
if (isAlwaysOnTopLoop) {
|
||||
alwaysOnTopTools.startLoop()
|
||||
} else {
|
||||
alwaysOnTopTools.clearLoop()
|
||||
}
|
||||
}
|
||||
if (keys.includes('desktopLyric.isLockScreen') && isLockScreen != global.lx.appSetting['desktopLyric.isLockScreen']) {
|
||||
isLockScreen = global.lx.appSetting['desktopLyric.isLockScreen']
|
||||
if (global.lx.appSetting['desktopLyric.isLockScreen']) {
|
||||
setBounds(getLyricWindowBounds(getBounds(), {
|
||||
x: null,
|
||||
y: 0,
|
||||
w: global.lx.appSetting['desktopLyric.width'],
|
||||
h: global.lx.appSetting['desktopLyric.height'],
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
if (keys.includes('desktopLyric.enable') && isEnable != global.lx.appSetting['desktopLyric.enable']) {
|
||||
isEnable = global.lx.appSetting['desktopLyric.enable']
|
||||
if (global.lx.appSetting['desktopLyric.enable']) {
|
||||
createWindow()
|
||||
} else {
|
||||
alwaysOnTopTools.clearLoop()
|
||||
closeWindow()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
const { common: COMMON_EVENT_NAME, winLyric: WIN_LYRIC_EVENT_NAME, hotKey: HOT_KEY_EVENT_NAME, mainWindow: MAIN_WINDOW_EVENT_NAME } = require('../../events/_name')
|
||||
const { mainSend, NAMES: { winLyric: ipcWinLyricNames } } = require('../../../common/ipc')
|
||||
const { desktop_lyric } = require('../../../common/hotKey')
|
||||
const { getLyricWindowBounds } = require('./utils')
|
||||
const { isLinux } = require('@common/utils')
|
||||
|
||||
let isLock = null
|
||||
let isEnable = null
|
||||
let isAlwaysOnTop = null
|
||||
let isAlwaysOnTopLoop = null
|
||||
let isLockScreen = null
|
||||
let isHoverHide = null
|
||||
|
||||
const alwaysOnTopTools = {
|
||||
timeout: null,
|
||||
alwaysOnTop: false,
|
||||
setAlwaysOnTop(flag, isLoop) {
|
||||
this.alwaysOnTop = flag
|
||||
this.clearLoop()
|
||||
global.modules.lyricWindow.setAlwaysOnTop(flag, 'screen-saver')
|
||||
console.log(isLoop)
|
||||
if (flag && isLoop) this.startLoop()
|
||||
},
|
||||
startLoop() {
|
||||
if (!this.alwaysOnTop) return
|
||||
this.timeout = setInterval(() => {
|
||||
if (!global.modules.lyricWindow) return this.clearLoop()
|
||||
global.modules.lyricWindow.setAlwaysOnTop(true, 'screen-saver')
|
||||
}, 1000)
|
||||
},
|
||||
clearLoop() {
|
||||
if (!this.timeout) return
|
||||
clearInterval(this.timeout)
|
||||
this.timeout = null
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
const setLrcConfig = () => {
|
||||
let desktopLyric = global.appSetting.desktopLyric
|
||||
if (global.modules.lyricWindow) {
|
||||
mainSend(global.modules.lyricWindow, ipcWinLyricNames.set_lyric_config, {
|
||||
config: desktopLyric,
|
||||
languageId: global.appSetting.langId,
|
||||
isShowLyricTranslation: global.appSetting.player.isShowLyricTranslation,
|
||||
isShowLyricRoma: global.appSetting.player.isShowLyricRoma,
|
||||
isPlayLxlrc: global.appSetting.player.isPlayLxlrc,
|
||||
})
|
||||
if (isLock != desktopLyric.isLock) {
|
||||
isLock = desktopLyric.isLock
|
||||
if (desktopLyric.isLock) {
|
||||
global.modules.lyricWindow.setIgnoreMouseEvents(true, { forward: !isLinux && global.appSetting.desktopLyric.isHoverHide })
|
||||
} else {
|
||||
global.modules.lyricWindow.setIgnoreMouseEvents(false, { forward: !isLinux && global.appSetting.desktopLyric.isHoverHide })
|
||||
}
|
||||
}
|
||||
if (isHoverHide != desktopLyric.isHoverHide) {
|
||||
isHoverHide = desktopLyric.isHoverHide
|
||||
if (!isLinux) {
|
||||
global.modules.lyricWindow.setIgnoreMouseEvents(desktopLyric.isLock, { forward: global.appSetting.desktopLyric.isHoverHide })
|
||||
}
|
||||
}
|
||||
if (isAlwaysOnTop != desktopLyric.isAlwaysOnTop) {
|
||||
isAlwaysOnTop = desktopLyric.isAlwaysOnTop
|
||||
alwaysOnTopTools.setAlwaysOnTop(desktopLyric.isAlwaysOnTop, desktopLyric.isAlwaysOnTopLoop)
|
||||
}
|
||||
if (isAlwaysOnTopLoop != desktopLyric.isAlwaysOnTopLoop) {
|
||||
isAlwaysOnTopLoop = desktopLyric.isAlwaysOnTopLoop
|
||||
if (isAlwaysOnTopLoop) {
|
||||
alwaysOnTopTools.startLoop()
|
||||
} else {
|
||||
alwaysOnTopTools.clearLoop()
|
||||
}
|
||||
}
|
||||
if (isLockScreen != desktopLyric.isLockScreen) {
|
||||
isLockScreen = desktopLyric.isLockScreen
|
||||
if (desktopLyric.isLockScreen) {
|
||||
global.modules.lyricWindow.setBounds(getLyricWindowBounds(global.modules.lyricWindow.getBounds(), {
|
||||
x: null,
|
||||
y: null,
|
||||
w: desktopLyric.width,
|
||||
h: desktopLyric.height,
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isEnable != desktopLyric.enable) {
|
||||
isEnable = desktopLyric.enable
|
||||
if (desktopLyric.enable) {
|
||||
global.lx_event.winLyric.create()
|
||||
} else {
|
||||
alwaysOnTopTools.clearLoop()
|
||||
global.lx_event.winLyric.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
global.lx_event.common.on(COMMON_EVENT_NAME.configStatus, name => {
|
||||
if (WIN_LYRIC_EVENT_NAME.name === name) return
|
||||
setLrcConfig()
|
||||
})
|
||||
|
||||
global.lx_event.mainWindow.on(MAIN_WINDOW_EVENT_NAME.setLyricInfo, info => {
|
||||
if (!global.modules.lyricWindow) return
|
||||
mainSend(global.modules.lyricWindow, ipcWinLyricNames.set_lyric_info, info)
|
||||
})
|
||||
|
||||
global.lx_event.hotKey.on(HOT_KEY_EVENT_NAME.keyDown, ({ type, key }) => {
|
||||
let info = global.appHotKey.config.global.keys[key]
|
||||
if (!info || info.type != WIN_LYRIC_EVENT_NAME.name) return
|
||||
let desktopLyricSetting = JSON.parse(JSON.stringify(global.appSetting.desktopLyric))
|
||||
let settingKey
|
||||
switch (info.action) {
|
||||
case desktop_lyric.toggle_visible.action:
|
||||
settingKey = 'enable'
|
||||
break
|
||||
case desktop_lyric.toggle_lock.action:
|
||||
settingKey = 'isLock'
|
||||
break
|
||||
case desktop_lyric.toggle_always_top.action:
|
||||
settingKey = 'isAlwaysOnTop'
|
||||
break
|
||||
}
|
||||
desktopLyricSetting[settingKey] = !desktopLyricSetting[settingKey]
|
||||
|
||||
global.lx_core.setAppConfig({ desktopLyric: desktopLyricSetting }, null)
|
||||
})
|
||||
@@ -1,132 +0,0 @@
|
||||
const path = require('path')
|
||||
const { BrowserWindow } = require('electron')
|
||||
const { winLyric: WIN_LYRIC_EVENT_NAME } = require('../../events/_name')
|
||||
const { debounce, isLinux } = require('../../../common/utils')
|
||||
const { getLyricWindowBounds } = require('./utils')
|
||||
|
||||
require('./event')
|
||||
require('./rendererEvent')
|
||||
|
||||
global.lx_event.winLyric.on(WIN_LYRIC_EVENT_NAME.create, () => {
|
||||
createWindow()
|
||||
})
|
||||
global.lx_event.winLyric.on(WIN_LYRIC_EVENT_NAME.close, () => {
|
||||
closeWindow()
|
||||
})
|
||||
|
||||
let winURL = global.isDev ? 'http://localhost:9081/lyric.html' : `file://${path.join(__dirname, 'lyric.html')}`
|
||||
|
||||
const setLyricsConfig = debounce(config => {
|
||||
// if (x != null) bounds.x = x
|
||||
// if (y != null) bounds.y = y
|
||||
// if (width != null) bounds.width = width
|
||||
// if (height != null) bounds.height = height
|
||||
global.lx_core.setAppConfig({ desktopLyric: config }, WIN_LYRIC_EVENT_NAME.name)
|
||||
}, 500)
|
||||
|
||||
const winEvent = lyricWindow => {
|
||||
// let bounds
|
||||
// lyricWindow.on('close', event => {
|
||||
// if (global.isQuitting || !global.appSetting.tray.isShow || (!isWin && !global.isTrafficLightClose)) {
|
||||
// lyricWindow.setProgressBar(-1)
|
||||
// return
|
||||
// }
|
||||
|
||||
// if (global.isTrafficLightClose) global.isTrafficLightClose = false
|
||||
// event.preventDefault()
|
||||
// lyricWindow.hide()
|
||||
// })
|
||||
|
||||
lyricWindow.on('closed', () => {
|
||||
lyricWindow = global.modules.lyricWindow = null
|
||||
})
|
||||
|
||||
|
||||
lyricWindow.on('move', event => {
|
||||
// bounds = lyricWindow.getBounds()
|
||||
// console.log(bounds)
|
||||
setLyricsConfig(lyricWindow.getBounds())
|
||||
})
|
||||
|
||||
lyricWindow.on('resize', event => {
|
||||
// bounds = lyricWindow.getBounds()
|
||||
// console.log(bounds)
|
||||
setLyricsConfig(lyricWindow.getBounds())
|
||||
})
|
||||
|
||||
// lyricWindow.on('restore', () => {
|
||||
// lyricWindow.webContents.send('restore')
|
||||
// })
|
||||
// lyricWindow.on('focus', () => {
|
||||
// lyricWindow.webContents.send('focus')
|
||||
// })
|
||||
|
||||
lyricWindow.once('ready-to-show', () => {
|
||||
lyricWindow.show()
|
||||
if (global.appSetting.desktopLyric.isLock) {
|
||||
global.modules.lyricWindow.setIgnoreMouseEvents(true, { forward: !isLinux && global.appSetting.desktopLyric.isHoverHide })
|
||||
}
|
||||
// linux下每次重开时貌似要重新设置置顶
|
||||
if (isLinux && global.appSetting.desktopLyric.isAlwaysOnTop) {
|
||||
global.modules.lyricWindow.setAlwaysOnTop(global.appSetting.desktopLyric.isAlwaysOnTop, 'screen-saver')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const createWindow = () => {
|
||||
if (global.modules.lyricWindow) return
|
||||
if (!global.appSetting.desktopLyric.enable) return
|
||||
// const windowSizeInfo = getWindowSizeInfo(global.appSetting)
|
||||
let { x, y, width, height, isAlwaysOnTop } = global.appSetting.desktopLyric
|
||||
let { width: screenWidth, height: screenHeight } = global.envParams.workAreaSize
|
||||
if (x == null) {
|
||||
x = screenWidth - width
|
||||
y = screenHeight - height
|
||||
}
|
||||
if (global.appSetting.desktopLyric.isLockScreen) {
|
||||
let bounds = getLyricWindowBounds({ x, y, width, height }, { x: null, y: null, w: width, h: height })
|
||||
x = bounds.x
|
||||
y = bounds.y
|
||||
width = bounds.width
|
||||
height = bounds.height
|
||||
}
|
||||
/**
|
||||
* Initial window options
|
||||
*/
|
||||
global.modules.lyricWindow = new BrowserWindow({
|
||||
height,
|
||||
width,
|
||||
x,
|
||||
y,
|
||||
minWidth: 380,
|
||||
minHeight: 80,
|
||||
useContentSize: true,
|
||||
frame: false,
|
||||
transparent: true,
|
||||
enableRemoteModule: false,
|
||||
// icon: path.join(global.__static, isWin ? 'icons/256x256.ico' : 'icons/512x512.png'),
|
||||
resizable: false,
|
||||
minimizable: false,
|
||||
maximizable: false,
|
||||
fullscreenable: false,
|
||||
show: false,
|
||||
alwaysOnTop: isAlwaysOnTop,
|
||||
skipTaskbar: true,
|
||||
webPreferences: {
|
||||
contextIsolation: false,
|
||||
webSecurity: !global.isDev,
|
||||
nodeIntegration: true,
|
||||
spellcheck: false, // 禁用拼写检查器
|
||||
},
|
||||
})
|
||||
|
||||
global.modules.lyricWindow.loadURL(winURL)
|
||||
|
||||
winEvent(global.modules.lyricWindow)
|
||||
// mainWindow.webContents.openDevTools()
|
||||
}
|
||||
|
||||
const closeWindow = () => {
|
||||
if (!global.modules.lyricWindow) return
|
||||
global.modules.lyricWindow.close()
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { APP_EVENT_NAMES } from '@common/constants'
|
||||
import initRendererEvent, { sendMainWindowInitedEvent } from './rendererEvent'
|
||||
import { setLrcConfig } from './config'
|
||||
import { HOTKEY_DESKTOP_LYRIC } from '@common/hotKey'
|
||||
import { closeWindow, createWindow, isExistWindow } from './main'
|
||||
// import main from './main'
|
||||
// import { Event, EVENT_NAMES } from './event'
|
||||
|
||||
|
||||
export default () => {
|
||||
initRendererEvent()
|
||||
// global.lx.event_app.winLyric = new Event()
|
||||
// global.app_event.winMain.
|
||||
|
||||
global.lx.event_app.on('main_window_inited', () => {
|
||||
if (global.lx.appSetting['desktopLyric.enable']) {
|
||||
if (isExistWindow()) sendMainWindowInitedEvent()
|
||||
else createWindow()
|
||||
}
|
||||
})
|
||||
global.lx.event_app.on('updated_config', (keys, setting) => {
|
||||
setLrcConfig(keys, setting)
|
||||
})
|
||||
global.lx.event_app.on('main_window_close', () => {
|
||||
closeWindow()
|
||||
})
|
||||
|
||||
|
||||
// global.lx_event.mainWindow.on(MAIN_WINDOW_EVENT_NAME.setLyricInfo, info => {
|
||||
// if (!global.modules.lyricWindow) return
|
||||
// mainSend(global.modules.lyricWindow, ipcWinLyricNames.set_lyric_info, info)
|
||||
// })
|
||||
|
||||
global.lx.event_app.on('hot_key_down', ({ type, key }) => {
|
||||
let info = global.lx.hotKey.config.global.keys[key]
|
||||
if (!info || info.type != APP_EVENT_NAMES.winLyricName) return
|
||||
let newSetting: Partial<LX.AppSetting> = {}
|
||||
let settingKey: keyof LX.AppSetting
|
||||
switch (info.action) {
|
||||
case HOTKEY_DESKTOP_LYRIC.toggle_visible.action:
|
||||
settingKey = 'desktopLyric.enable'
|
||||
break
|
||||
case HOTKEY_DESKTOP_LYRIC.toggle_lock.action:
|
||||
settingKey = 'desktopLyric.isLock'
|
||||
break
|
||||
case HOTKEY_DESKTOP_LYRIC.toggle_always_top.action:
|
||||
settingKey = 'desktopLyric.isAlwaysOnTop'
|
||||
break
|
||||
default: return
|
||||
}
|
||||
newSetting[settingKey] = !global.lx.appSetting[settingKey]
|
||||
|
||||
global.lx.event_app.update_config(newSetting)
|
||||
})
|
||||
}
|
||||
export * from './main'
|
||||
export * from './rendererEvent'
|
||||
|
||||
// export {
|
||||
// EVENT_NAMES,
|
||||
// }
|
||||
@@ -0,0 +1,208 @@
|
||||
import { join } from 'path'
|
||||
import { BrowserWindow } from 'electron'
|
||||
import { debounce, encodePath, isLinux, isWin } from '@common/utils'
|
||||
import { getLyricWindowBounds } from './utils'
|
||||
import { mainSend } from '@common/mainIpc'
|
||||
|
||||
// require('./event')
|
||||
// require('./rendererEvent')
|
||||
|
||||
let browserWindow: Electron.BrowserWindow | null = null
|
||||
|
||||
|
||||
const setLyricsConfig = debounce((config: Partial<LX.AppSetting>) => {
|
||||
global.lx.event_app.update_config(config)
|
||||
}, 500)
|
||||
|
||||
const winEvent = () => {
|
||||
if (!browserWindow) return
|
||||
// let bounds
|
||||
// lyricWindow.on('close', event => {
|
||||
// if (global.isQuitting || !global.appSetting.tray.enable || (!isWin && !global.isTrafficLightClose)) {
|
||||
// lyricWindow.setProgressBar(-1)
|
||||
// return
|
||||
// }
|
||||
|
||||
// if (global.isTrafficLightClose) global.isTrafficLightClose = false
|
||||
// event.preventDefault()
|
||||
// lyricWindow.hide()
|
||||
// })
|
||||
|
||||
browserWindow.on('closed', () => {
|
||||
browserWindow = null
|
||||
})
|
||||
|
||||
browserWindow.on('move', () => {
|
||||
// bounds = browserWindow.getBounds()
|
||||
// console.log(bounds)
|
||||
const bounds = browserWindow!.getBounds()
|
||||
setLyricsConfig({
|
||||
'desktopLyric.x': bounds.x,
|
||||
'desktopLyric.y': bounds.y,
|
||||
'desktopLyric.width': bounds.width,
|
||||
'desktopLyric.height': bounds.height,
|
||||
})
|
||||
})
|
||||
|
||||
browserWindow.on('resize', () => {
|
||||
// bounds = browserWindow.getBounds()
|
||||
// console.log(bounds)
|
||||
const bounds = browserWindow!.getBounds()
|
||||
setLyricsConfig({
|
||||
'desktopLyric.x': bounds.x,
|
||||
'desktopLyric.y': bounds.y,
|
||||
'desktopLyric.width': bounds.width,
|
||||
'desktopLyric.height': bounds.height,
|
||||
})
|
||||
})
|
||||
|
||||
// browserWindow.on('restore', () => {
|
||||
// browserWindow.webContents.send('restore')
|
||||
// })
|
||||
// browserWindow.on('focus', () => {
|
||||
// browserWindow.webContents.send('focus')
|
||||
// })
|
||||
|
||||
browserWindow.once('ready-to-show', () => {
|
||||
showWindow()
|
||||
if (global.lx.appSetting['desktopLyric.isLock']) {
|
||||
browserWindow!.setIgnoreMouseEvents(true, { forward: !isLinux && global.lx.appSetting['desktopLyric.isHoverHide'] })
|
||||
}
|
||||
// linux下每次重开时貌似要重新设置置顶
|
||||
if (isLinux && global.lx.appSetting['desktopLyric.isAlwaysOnTop']) {
|
||||
browserWindow!.setAlwaysOnTop(global.lx.appSetting['desktopLyric.isAlwaysOnTop'], 'screen-saver')
|
||||
}
|
||||
if (global.lx.appSetting['desktopLyric.isAlwaysOnTop'] && global.lx.appSetting['desktopLyric.isAlwaysOnTopLoop']) alwaysOnTopTools.startLoop()
|
||||
})
|
||||
}
|
||||
|
||||
export const createWindow = () => {
|
||||
closeWindow()
|
||||
if (!global.envParams.workAreaSize) return
|
||||
let x = global.lx.appSetting['desktopLyric.x']
|
||||
let y = global.lx.appSetting['desktopLyric.y']
|
||||
let width = global.lx.appSetting['desktopLyric.width']
|
||||
let height = global.lx.appSetting['desktopLyric.height']
|
||||
let isAlwaysOnTop = global.lx.appSetting['desktopLyric.isAlwaysOnTop']
|
||||
let isLockScreen = global.lx.appSetting['desktopLyric.isLockScreen']
|
||||
let { width: screenWidth, height: screenHeight } = global.envParams.workAreaSize
|
||||
if (x == null || y == null) {
|
||||
x = screenWidth - width
|
||||
y = screenHeight - height
|
||||
}
|
||||
if (isLockScreen) {
|
||||
let bounds = getLyricWindowBounds({ x, y, width, height }, { x: null, y: 0, w: width, h: height })
|
||||
x = bounds.x
|
||||
y = bounds.y
|
||||
width = bounds.width
|
||||
height = bounds.height
|
||||
}
|
||||
|
||||
const { shouldUseDarkColors, theme } = global.lx.theme
|
||||
|
||||
/**
|
||||
* Initial window options
|
||||
*/
|
||||
browserWindow = new BrowserWindow({
|
||||
height,
|
||||
width,
|
||||
x,
|
||||
y,
|
||||
minWidth: 380,
|
||||
minHeight: 80,
|
||||
useContentSize: true,
|
||||
frame: false,
|
||||
transparent: true,
|
||||
// enableRemoteModule: false,
|
||||
// icon: join(global.__static, isWin ? 'icons/256x256.ico' : 'icons/512x512.png'),
|
||||
resizable: false,
|
||||
minimizable: false,
|
||||
maximizable: false,
|
||||
fullscreenable: false,
|
||||
show: false,
|
||||
alwaysOnTop: isAlwaysOnTop,
|
||||
skipTaskbar: true,
|
||||
webPreferences: {
|
||||
contextIsolation: false,
|
||||
webSecurity: !global.isDev,
|
||||
nodeIntegration: true,
|
||||
spellcheck: false, // 禁用拼写检查器
|
||||
},
|
||||
})
|
||||
|
||||
const winURL = global.isDev ? 'http://localhost:9081/lyric.html' : `file://${join(encodePath(isWin ? __dirname.replaceAll('\\', '/') : __dirname), 'lyric.html')}`
|
||||
void browserWindow.loadURL(winURL + `?dark=${shouldUseDarkColors}&theme=${encodeURIComponent(JSON.stringify(theme))}`)
|
||||
|
||||
winEvent()
|
||||
// browserWindow.webContents.openDevTools()
|
||||
}
|
||||
export const isExistWindow = (): boolean => !!browserWindow
|
||||
|
||||
export const closeWindow = () => {
|
||||
if (!browserWindow) return
|
||||
browserWindow.close()
|
||||
}
|
||||
|
||||
export const showWindow = () => {
|
||||
if (!browserWindow) return
|
||||
browserWindow.show()
|
||||
}
|
||||
|
||||
export const sendEvent = <T = any>(name: string, params?: T) => {
|
||||
if (!browserWindow) return
|
||||
mainSend(browserWindow, name, params)
|
||||
}
|
||||
|
||||
export const getBounds = (): Electron.Rectangle => {
|
||||
if (!browserWindow) throw new Error('window is not available')
|
||||
return browserWindow.getBounds()
|
||||
}
|
||||
|
||||
export const setBounds = (bounds: Electron.Rectangle) => {
|
||||
if (!browserWindow) return
|
||||
browserWindow.setBounds(bounds)
|
||||
}
|
||||
|
||||
|
||||
export const setIgnoreMouseEvents = (ignore: boolean, options?: Electron.IgnoreMouseEventsOptions) => {
|
||||
if (!browserWindow) return
|
||||
browserWindow.setIgnoreMouseEvents(ignore, options)
|
||||
}
|
||||
|
||||
export const setAlwaysOnTop = (flag: boolean, level?: 'normal' | 'floating' | 'torn-off-menu' | 'modal-panel' | 'main-menu' | 'status' | 'pop-up-menu' | 'screen-saver' | undefined, relativeLevel?: number | undefined) => {
|
||||
if (!browserWindow) return
|
||||
browserWindow.setAlwaysOnTop(flag, level, relativeLevel)
|
||||
}
|
||||
|
||||
export const getMainFrame = (): Electron.WebFrameMain | null => {
|
||||
if (!browserWindow) return null
|
||||
return browserWindow.webContents.mainFrame
|
||||
}
|
||||
|
||||
interface AlwaysOnTopTools {
|
||||
timeout: NodeJS.Timeout | null
|
||||
setAlwaysOnTop: (isLoop: boolean) => void
|
||||
startLoop: () => void
|
||||
clearLoop: () => void
|
||||
}
|
||||
export const alwaysOnTopTools: AlwaysOnTopTools = {
|
||||
timeout: null,
|
||||
setAlwaysOnTop(isLoop) {
|
||||
this.clearLoop()
|
||||
setAlwaysOnTop(global.lx.appSetting['desktopLyric.isAlwaysOnTop'], 'screen-saver')
|
||||
// console.log(isLoop)
|
||||
if (isLoop) this.startLoop()
|
||||
},
|
||||
startLoop() {
|
||||
this.clearLoop()
|
||||
this.timeout = setInterval(() => {
|
||||
if (!isExistWindow()) return this.clearLoop()
|
||||
setAlwaysOnTop(true, 'screen-saver')
|
||||
}, 1000)
|
||||
},
|
||||
clearLoop() {
|
||||
if (!this.timeout) return
|
||||
clearInterval(this.timeout)
|
||||
this.timeout = null
|
||||
},
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
const {
|
||||
mainOn,
|
||||
mainHandle,
|
||||
mainSend,
|
||||
NAMES: {
|
||||
mainWindow: ipcMainWindowNames,
|
||||
winLyric: ipcWinLyricNames,
|
||||
},
|
||||
} = require('../../../common/ipc')
|
||||
const { winLyric: WIN_LYRIC_EVENT_NAME } = require('../../events/_name')
|
||||
const { getLyricWindowBounds } = require('./utils')
|
||||
|
||||
mainOn(ipcWinLyricNames.get_lyric_info, (event, action) => {
|
||||
if (!global.modules.mainWindow) return
|
||||
mainSend(global.modules.mainWindow, ipcMainWindowNames.get_lyric_info, {
|
||||
name: ipcWinLyricNames.set_lyric_info,
|
||||
modal: 'lyricWindow',
|
||||
action,
|
||||
})
|
||||
})
|
||||
|
||||
mainOn(ipcWinLyricNames.set_lyric_config, (event, config) => {
|
||||
global.lx_core.setAppConfig({ desktopLyric: config }, WIN_LYRIC_EVENT_NAME.name)
|
||||
})
|
||||
|
||||
mainHandle(ipcWinLyricNames.get_lyric_config, async() => {
|
||||
return {
|
||||
config: global.appSetting.desktopLyric,
|
||||
languageId: global.appSetting.langId,
|
||||
isShowLyricTranslation: global.appSetting.player.isShowLyricTranslation,
|
||||
isShowLyricRoma: global.appSetting.player.isShowLyricRoma,
|
||||
isPlayLxlrc: global.appSetting.player.isPlayLxlrc,
|
||||
}
|
||||
})
|
||||
|
||||
mainOn(ipcWinLyricNames.set_win_bounds, (event, options) => {
|
||||
global.modules.lyricWindow.setBounds(getLyricWindowBounds(global.modules.lyricWindow.getBounds(), options))
|
||||
})
|
||||
@@ -0,0 +1,53 @@
|
||||
import { registerRendererEvents as common } from '@main/modules/commonRenderers/common'
|
||||
import { mainOn, mainHandle } from '@common/mainIpc'
|
||||
import { WIN_LYRIC_RENDERER_EVENT_NAME } from '@common/ipcNames'
|
||||
import { buildLyricConfig, getLyricWindowBounds } from './utils'
|
||||
import { sendNewDesktopLyricClient } from '@main/modules/winMain'
|
||||
import { getBounds, getMainFrame, sendEvent, setBounds } from './main'
|
||||
import { MessageChannelMain } from 'electron'
|
||||
|
||||
|
||||
export default () => {
|
||||
// mainOn(WIN_LYRIC_RENDERER_EVENT_NAME.get_lyric_info, ({ params: action }) => {
|
||||
// sendMainEvent(WIN_MAIN_RENDERER_EVENT_NAME.get_lyric_info, {
|
||||
// name: WIN_LYRIC_RENDERER_EVENT_NAME.set_lyric_info,
|
||||
// modal: 'lyricWindow',
|
||||
// action,
|
||||
// })
|
||||
// })
|
||||
common(sendEvent)
|
||||
|
||||
mainHandle<Partial<LX.AppSetting>>(WIN_LYRIC_RENDERER_EVENT_NAME.set_config, async({ params: config }) => {
|
||||
global.lx.event_app.update_config(config)
|
||||
})
|
||||
|
||||
mainHandle<LX.DesktopLyric.Config>(WIN_LYRIC_RENDERER_EVENT_NAME.get_config, async() => {
|
||||
return buildLyricConfig(global.lx.appSetting) as LX.DesktopLyric.Config
|
||||
})
|
||||
|
||||
mainOn<LX.DesktopLyric.NewBounds>(WIN_LYRIC_RENDERER_EVENT_NAME.set_win_bounds, ({ params: options }) => {
|
||||
setBounds(getLyricWindowBounds(getBounds(), options))
|
||||
})
|
||||
|
||||
mainOn(WIN_LYRIC_RENDERER_EVENT_NAME.request_main_window_channel, ({ event }) => {
|
||||
if (event.senderFrame !== getMainFrame()) return
|
||||
// Create a new channel ...
|
||||
const { port1, port2 } = new MessageChannelMain()
|
||||
// ... send one end to the worker ...
|
||||
sendNewDesktopLyricClient(port1)
|
||||
// ... and the other end to the main window.
|
||||
event.senderFrame.postMessage(WIN_LYRIC_RENDERER_EVENT_NAME.provide_main_window_channel, null, [port2])
|
||||
// Now the main window and the worker can communicate with each other
|
||||
// without going through the main process!
|
||||
console.log('request_main_window_channel')
|
||||
})
|
||||
}
|
||||
|
||||
export const sendConfigChange = (setting: Partial<LX.DesktopLyric.Config>) => {
|
||||
sendEvent(WIN_LYRIC_RENDERER_EVENT_NAME.on_config_change, setting)
|
||||
}
|
||||
|
||||
export const sendMainWindowInitedEvent = () => {
|
||||
sendEvent(WIN_LYRIC_RENDERER_EVENT_NAME.main_window_inited)
|
||||
}
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
// 设置窗口位置、大小
|
||||
let winX
|
||||
let winY
|
||||
let wasW
|
||||
let wasH
|
||||
let offset = 8
|
||||
let minWidth = 380
|
||||
let minHeight = 80
|
||||
exports.getLyricWindowBounds = (bounds, { x = 0, y = 0, w = 0, h = 0 }) => {
|
||||
if (w < minWidth) w = minWidth
|
||||
if (h < minHeight) h = minHeight
|
||||
|
||||
if (global.appSetting.desktopLyric.isLockScreen) {
|
||||
wasW = global.envParams.workAreaSize.width + offset
|
||||
wasH = global.envParams.workAreaSize.height + offset
|
||||
if (w > wasW + offset) w = wasW + offset
|
||||
if (h > wasH + offset) h = wasH + offset
|
||||
if (x == null) {
|
||||
if (bounds.x > wasW - w) {
|
||||
x = wasW - w - bounds.x
|
||||
} else if (bounds.x < -offset) {
|
||||
x = bounds.x + offset
|
||||
} else {
|
||||
x = 0
|
||||
}
|
||||
if (bounds.y > wasH - h) {
|
||||
y = wasH - h - bounds.y
|
||||
} else if (bounds.y < -offset) {
|
||||
y = bounds.y + offset
|
||||
} else {
|
||||
y = 0
|
||||
}
|
||||
}
|
||||
winX = bounds.x + x
|
||||
winY = bounds.y + y
|
||||
|
||||
if (x != 0) {
|
||||
if (winX < -offset) {
|
||||
winX = -offset
|
||||
} else if (winX + w > wasW) {
|
||||
winX = wasW - w
|
||||
}
|
||||
}
|
||||
if (y != 0) {
|
||||
if (winY < -offset) {
|
||||
winY = -offset
|
||||
} else if (winY + h > wasH) {
|
||||
winY = wasH - h
|
||||
}
|
||||
}
|
||||
|
||||
x = winX
|
||||
y = winY
|
||||
|
||||
if (x + w > wasW) w = wasW - x
|
||||
if (y + h > wasH) h = wasH - y
|
||||
} else {
|
||||
y += bounds.y
|
||||
x += bounds.x
|
||||
}
|
||||
|
||||
bounds.width = w
|
||||
bounds.height = h
|
||||
bounds.x = x
|
||||
bounds.y = y
|
||||
// console.log('util bounds', bounds)
|
||||
return bounds
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
// 设置窗口位置、大小
|
||||
let winX
|
||||
let winY
|
||||
let wasW
|
||||
let wasH
|
||||
let offset = 8
|
||||
let minWidth = 80
|
||||
let minHeight = 50
|
||||
|
||||
export const getLyricWindowBounds = (bounds: Electron.Rectangle, { x = 0, y = 0, w = 0, h = 0 }: LX.DesktopLyric.NewBounds): Electron.Rectangle => {
|
||||
if (w < minWidth) w = minWidth
|
||||
if (h < minHeight) h = minHeight
|
||||
|
||||
if (global.lx.appSetting['desktopLyric.isLockScreen']) {
|
||||
if (!global.envParams.workAreaSize) return bounds
|
||||
wasW = (global.envParams.workAreaSize.width ?? 0) + offset
|
||||
wasH = (global.envParams.workAreaSize.height ?? 0) + offset
|
||||
|
||||
if (w > wasW + offset) w = wasW + offset
|
||||
if (h > wasH + offset) h = wasH + offset
|
||||
if (x == null) {
|
||||
if (bounds.x > wasW - w) {
|
||||
x = wasW - w - bounds.x
|
||||
} else if (bounds.x < -offset) {
|
||||
x = bounds.x + offset
|
||||
} else {
|
||||
x = 0
|
||||
}
|
||||
if (bounds.y > wasH - h) {
|
||||
y = wasH - h - bounds.y
|
||||
} else if (bounds.y < -offset) {
|
||||
y = bounds.y + offset
|
||||
} else {
|
||||
y = 0
|
||||
}
|
||||
}
|
||||
winX = bounds.x + x
|
||||
winY = bounds.y + y
|
||||
|
||||
if (x != 0) {
|
||||
if (winX < -offset) {
|
||||
winX = -offset
|
||||
} else if (winX + w > wasW) {
|
||||
winX = wasW - w
|
||||
}
|
||||
}
|
||||
if (y != 0) {
|
||||
if (winY < -offset) {
|
||||
winY = -offset
|
||||
} else if (winY + h > wasH) {
|
||||
winY = wasH - h
|
||||
}
|
||||
}
|
||||
|
||||
x = winX
|
||||
y = winY
|
||||
|
||||
if (x + w > wasW) w = wasW - x
|
||||
if (y + h > wasH) h = wasH - y
|
||||
} else {
|
||||
if (x == null) {
|
||||
x = 0
|
||||
y = 0
|
||||
}
|
||||
y += bounds.y
|
||||
x += bounds.x
|
||||
}
|
||||
|
||||
bounds.width = w
|
||||
bounds.height = h
|
||||
bounds.x = x
|
||||
bounds.y = y
|
||||
// console.log('util bounds', bounds)
|
||||
return bounds
|
||||
}
|
||||
|
||||
|
||||
export const watchConfigKeys = [
|
||||
'desktopLyric.enable',
|
||||
'desktopLyric.isLock',
|
||||
'desktopLyric.isAlwaysOnTop',
|
||||
'desktopLyric.isAlwaysOnTopLoop',
|
||||
'desktopLyric.audioVisualization',
|
||||
'desktopLyric.width',
|
||||
'desktopLyric.height',
|
||||
'desktopLyric.x',
|
||||
'desktopLyric.y',
|
||||
'desktopLyric.isLockScreen',
|
||||
'desktopLyric.isDelayScroll',
|
||||
'desktopLyric.isHoverHide',
|
||||
'desktopLyric.direction',
|
||||
'desktopLyric.style.align',
|
||||
'desktopLyric.style.lyricUnplayColor',
|
||||
'desktopLyric.style.lyricPlayedColor',
|
||||
'desktopLyric.style.lyricShadowColor',
|
||||
'desktopLyric.style.font',
|
||||
'desktopLyric.style.fontSize',
|
||||
// 'desktopLyric.style.fontWeight',
|
||||
'desktopLyric.style.opacity',
|
||||
'desktopLyric.style.ellipsis',
|
||||
'desktopLyric.style.isZoomActiveLrc',
|
||||
'common.langId',
|
||||
'player.isShowLyricTranslation',
|
||||
'player.isShowLyricRoma',
|
||||
'player.isPlayLxlrc',
|
||||
] as const
|
||||
|
||||
export const buildLyricConfig = (appSetting: Partial<LX.AppSetting>): Partial<LX.DesktopLyric.Config> => {
|
||||
const setting: Partial<LX.DesktopLyric.Config> = {}
|
||||
for (const key of watchConfigKeys) {
|
||||
// @ts-expect-error
|
||||
if (key in appSetting) setting[key] = appSetting[key]
|
||||
}
|
||||
return setting
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
const { log, isWin } = require('../../common/utils')
|
||||
const { autoUpdater } = require('electron-updater')
|
||||
const { mainOn, mainSend, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
|
||||
import { autoUpdater } from 'electron-updater'
|
||||
import { log, isWin } from '@common/utils'
|
||||
import { mainOn } from '@common/mainIpc'
|
||||
import { isExistWindow, sendEvent } from './index'
|
||||
import { WIN_MAIN_RENDERER_EVENT_NAME } from '@common/ipcNames'
|
||||
|
||||
autoUpdater.logger = log
|
||||
// autoUpdater.autoDownload = false
|
||||
autoUpdater.logger.transports.file.level = 'info'
|
||||
|
||||
let isFirstCheckedUpdate = true
|
||||
|
||||
@@ -22,7 +23,7 @@ log.info('App starting...')
|
||||
// -------------------------------------------------------------------
|
||||
// let win
|
||||
|
||||
function sendStatusToWindow(text) {
|
||||
function sendStatusToWindow(text: string) {
|
||||
log.info(text)
|
||||
// ipcMain.send('message', text)
|
||||
}
|
||||
@@ -59,25 +60,69 @@ function sendStatusToWindow(text) {
|
||||
|
||||
// })
|
||||
|
||||
let waitEvent = []
|
||||
const handleSendEvent = action => {
|
||||
if (global.modules.mainWindow) {
|
||||
interface WaitEvent {
|
||||
type: string
|
||||
info: any
|
||||
}
|
||||
|
||||
let waitEvent: WaitEvent[] = []
|
||||
const handleSendEvent = (action: WaitEvent) => {
|
||||
if (isExistWindow()) {
|
||||
setTimeout(() => { // 延迟发送事件,过早发送可能渲染进程还没启动完成
|
||||
if (!global.modules.mainWindow) return
|
||||
mainSend(global.modules.mainWindow, action.type, action.info)
|
||||
sendEvent(action.type, action.info)
|
||||
}, 2000)
|
||||
} else {
|
||||
waitEvent.push(action)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = () => {
|
||||
export default () => {
|
||||
autoUpdater.on('checking-for-update', () => {
|
||||
sendStatusToWindow('Checking for update...')
|
||||
})
|
||||
autoUpdater.on('update-available', info => {
|
||||
sendStatusToWindow('Update available.')
|
||||
handleSendEvent({ type: WIN_MAIN_RENDERER_EVENT_NAME.update_available, info })
|
||||
})
|
||||
autoUpdater.on('update-not-available', info => {
|
||||
sendStatusToWindow('Update not available.')
|
||||
handleSendEvent({ type: WIN_MAIN_RENDERER_EVENT_NAME.update_not_available, info })
|
||||
})
|
||||
autoUpdater.on('error', err => {
|
||||
sendStatusToWindow('Error in auto-updater.')
|
||||
handleSendEvent({ type: WIN_MAIN_RENDERER_EVENT_NAME.update_error, info: err.message })
|
||||
})
|
||||
autoUpdater.on('download-progress', progressObj => {
|
||||
let log_message = `Download speed: ${progressObj.bytesPerSecond}`
|
||||
log_message = `${log_message} - Downloaded ${progressObj.percent}%`
|
||||
log_message = `${log_message} (progressObj.transferred/${progressObj.total})`
|
||||
sendStatusToWindow(log_message)
|
||||
handleSendEvent({ type: WIN_MAIN_RENDERER_EVENT_NAME.update_progress, info: progressObj })
|
||||
})
|
||||
autoUpdater.on('update-downloaded', info => {
|
||||
sendStatusToWindow('Update downloaded.')
|
||||
handleSendEvent({ type: WIN_MAIN_RENDERER_EVENT_NAME.update_downloaded, info })
|
||||
})
|
||||
|
||||
mainOn(WIN_MAIN_RENDERER_EVENT_NAME.update_check, () => {
|
||||
checkUpdate()
|
||||
})
|
||||
|
||||
mainOn(WIN_MAIN_RENDERER_EVENT_NAME.quit_update, () => {
|
||||
global.lx.isQuitting = true
|
||||
|
||||
setTimeout(() => {
|
||||
autoUpdater.quitAndInstall(true, true)
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
|
||||
export const checkUpdate = () => {
|
||||
if (!isFirstCheckedUpdate) {
|
||||
if (waitEvent.length) {
|
||||
waitEvent.forEach((event, index) => {
|
||||
setTimeout(() => { // 延迟发送事件,过早发送可能渲染进程还没启动完成
|
||||
if (!global.modules.mainWindow) return
|
||||
mainSend(global.modules.mainWindow, event.type, event.info)
|
||||
sendEvent(event.type, event.info)
|
||||
}, 2000 * (index + 1))
|
||||
})
|
||||
waitEvent = []
|
||||
@@ -86,45 +131,10 @@ module.exports = () => {
|
||||
}
|
||||
isFirstCheckedUpdate = false
|
||||
|
||||
autoUpdater.on('checking-for-update', () => {
|
||||
sendStatusToWindow('Checking for update...')
|
||||
})
|
||||
autoUpdater.on('update-available', info => {
|
||||
sendStatusToWindow('Update available.')
|
||||
handleSendEvent({ type: ipcMainWindowNames.update_available, info })
|
||||
})
|
||||
autoUpdater.on('update-not-available', info => {
|
||||
sendStatusToWindow('Update not available.')
|
||||
handleSendEvent({ type: ipcMainWindowNames.update_not_available, info })
|
||||
})
|
||||
autoUpdater.on('error', err => {
|
||||
sendStatusToWindow('Error in auto-updater.')
|
||||
handleSendEvent({ type: ipcMainWindowNames.update_error, info: err.message })
|
||||
})
|
||||
autoUpdater.on('download-progress', progressObj => {
|
||||
let log_message = 'Download speed: ' + progressObj.bytesPerSecond
|
||||
log_message = log_message + ' - Downloaded ' + progressObj.percent + '%'
|
||||
log_message = log_message + ' (' + progressObj.transferred + '/' + progressObj.total + ')'
|
||||
sendStatusToWindow(log_message)
|
||||
handleSendEvent({ type: ipcMainWindowNames.update_progress, info: progressObj })
|
||||
})
|
||||
autoUpdater.on('update-downloaded', info => {
|
||||
sendStatusToWindow('Update downloaded.')
|
||||
handleSendEvent({ type: ipcMainWindowNames.update_downloaded, info })
|
||||
})
|
||||
mainOn(ipcMainWindowNames.quit_update, () => {
|
||||
global.isQuitting = true
|
||||
|
||||
setTimeout(() => {
|
||||
autoUpdater.quitAndInstall(true, true)
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
// 由于集合安装包中不包含win arm版,这将会导致arm版更新失败
|
||||
if (isWin && process.arch.includes('arm')) {
|
||||
handleSendEvent({ type: ipcMainWindowNames.update_error, info: 'failed' })
|
||||
handleSendEvent({ type: WIN_MAIN_RENDERER_EVENT_NAME.update_error, info: 'failed' })
|
||||
} else {
|
||||
autoUpdater.checkForUpdates()
|
||||
void autoUpdater.checkForUpdates()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import initRendererEvent, { handleKeyDown, hotKeyConfigUpdate } from './rendererEvent'
|
||||
|
||||
import { APP_EVENT_NAMES } from '@common/constants'
|
||||
import { createWindow, minimize, toggleHide, toggleMinimize } from './main'
|
||||
import initUpdate from './autoUpdate'
|
||||
import { HOTKEY_COMMON } from '@common/hotKey'
|
||||
import { quitApp } from '@main/app'
|
||||
|
||||
export default () => {
|
||||
initRendererEvent()
|
||||
initUpdate()
|
||||
|
||||
global.lx.event_app.on('hot_key_down', ({ type, key }) => {
|
||||
let info = global.lx.hotKey.config.global.keys[key]
|
||||
if (info?.type != APP_EVENT_NAMES.winMainName) return
|
||||
switch (info.action) {
|
||||
case HOTKEY_COMMON.close.action:
|
||||
quitApp()
|
||||
break
|
||||
case HOTKEY_COMMON.hide_toggle.action:
|
||||
toggleHide()
|
||||
break
|
||||
case HOTKEY_COMMON.min.action:
|
||||
minimize()
|
||||
break
|
||||
case HOTKEY_COMMON.min_toggle.action:
|
||||
toggleMinimize()
|
||||
break
|
||||
default:
|
||||
handleKeyDown(type, key)
|
||||
break
|
||||
}
|
||||
})
|
||||
global.lx.event_app.on('hot_key_config_update', (config) => {
|
||||
hotKeyConfigUpdate(config)
|
||||
})
|
||||
|
||||
global.lx.event_app.on('app_inited', () => {
|
||||
createWindow()
|
||||
})
|
||||
}
|
||||
|
||||
export * from './main'
|
||||
export * from './rendererEvent'
|
||||
|
||||
@@ -0,0 +1,254 @@
|
||||
import { BrowserWindow, dialog } from 'electron'
|
||||
import { join } from 'path'
|
||||
import { createTaskBarButtons, getWindowSizeInfo } from './utils'
|
||||
import { encodePath, isLinux, isWin } from '@common/utils'
|
||||
import { openDevTools as handleOpenDevTools } from '@main/utils'
|
||||
import { mainSend } from '@common/mainIpc'
|
||||
import { sendFocus, sendTaskbarButtonClick } from './rendererEvent'
|
||||
|
||||
let browserWindow: Electron.BrowserWindow | null = null
|
||||
|
||||
const winEvent = () => {
|
||||
if (!browserWindow) return
|
||||
|
||||
browserWindow.on('close', event => {
|
||||
if (global.lx.isQuitting || !global.lx.appSetting['tray.enable'] || (!isWin && !global.lx.isTrafficLightClose)) {
|
||||
browserWindow!.setProgressBar(-1)
|
||||
global.lx.event_app.main_window_close()
|
||||
return
|
||||
}
|
||||
|
||||
if (global.lx.isTrafficLightClose) global.lx.isTrafficLightClose = false
|
||||
event.preventDefault()
|
||||
browserWindow!.hide()
|
||||
})
|
||||
|
||||
browserWindow.on('closed', () => {
|
||||
browserWindow = null
|
||||
})
|
||||
|
||||
// browserWindow.on('restore', () => {
|
||||
// browserWindow.webContents.send('restore')
|
||||
// })
|
||||
browserWindow.on('focus', () => {
|
||||
sendFocus()
|
||||
global.lx.event_app.main_window_focus()
|
||||
})
|
||||
|
||||
browserWindow.on('blur', () => {
|
||||
global.lx.event_app.main_window_blur()
|
||||
})
|
||||
|
||||
browserWindow.once('ready-to-show', () => {
|
||||
showWindow()
|
||||
setThumbarButtons()
|
||||
global.lx.event_app.main_window_ready_to_show()
|
||||
})
|
||||
|
||||
browserWindow.on('show', () => {
|
||||
global.lx.event_app.main_window_show()
|
||||
})
|
||||
browserWindow.on('hide', () => {
|
||||
global.lx.event_app.main_window_hide()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export const createWindow = () => {
|
||||
closeWindow()
|
||||
const windowSizeInfo = getWindowSizeInfo(global.lx.appSetting['common.windowSizeId'])
|
||||
|
||||
const { shouldUseDarkColors, theme } = global.lx.theme
|
||||
|
||||
/**
|
||||
* Initial window options
|
||||
*/
|
||||
const options: Electron.BrowserWindowConstructorOptions = {
|
||||
height: windowSizeInfo.height,
|
||||
useContentSize: true,
|
||||
width: windowSizeInfo.width,
|
||||
frame: false,
|
||||
transparent: !global.envParams.cmdParams.dt,
|
||||
// enableRemoteModule: false,
|
||||
// icon: join(global.__static, isWin ? 'icons/256x256.ico' : 'icons/512x512.png'),
|
||||
resizable: false,
|
||||
maximizable: false,
|
||||
fullscreenable: true,
|
||||
show: false,
|
||||
webPreferences: {
|
||||
nodeIntegrationInWorker: true,
|
||||
contextIsolation: false,
|
||||
webSecurity: !global.isDev,
|
||||
nodeIntegration: true,
|
||||
spellcheck: false, // 禁用拼写检查器
|
||||
},
|
||||
}
|
||||
if (global.envParams.cmdParams.dt) options.backgroundColor = theme.colors['--color-primary-light-1000']
|
||||
if (global.lx.appSetting['common.startInFullscreen']) {
|
||||
options.fullscreen = true
|
||||
if (isLinux) options.resizable = true
|
||||
}
|
||||
browserWindow = new BrowserWindow(options)
|
||||
|
||||
const winURL = global.isDev ? 'http://localhost:9080' : `file://${join(encodePath(isWin ? __dirname.replaceAll('\\', '/') : __dirname), 'index.html')}`
|
||||
void browserWindow.loadURL(winURL + `?dt=${!!global.envParams.cmdParams.dt}&dark=${shouldUseDarkColors}&theme=${encodeURIComponent(JSON.stringify(theme))}`)
|
||||
|
||||
winEvent()
|
||||
|
||||
if (global.envParams.cmdParams.odt) handleOpenDevTools(browserWindow.webContents)
|
||||
// browserWindow.webContents.openDevTools()
|
||||
}
|
||||
|
||||
export const isExistWindow = (): boolean => !!browserWindow
|
||||
export const isShowWindow = (): boolean => {
|
||||
if (!browserWindow) return false
|
||||
return browserWindow.isVisible() && (isWin ? true : browserWindow.isFocused())
|
||||
}
|
||||
|
||||
export const closeWindow = () => {
|
||||
if (!browserWindow) return
|
||||
browserWindow.close()
|
||||
}
|
||||
|
||||
export const sendEvent = <T = any>(name: string, params?: T) => {
|
||||
if (!browserWindow) return
|
||||
mainSend(browserWindow, name, params)
|
||||
}
|
||||
|
||||
export const showSelectDialog = async(options: Electron.OpenDialogOptions) => {
|
||||
if (!browserWindow) throw new Error('main window is undefined')
|
||||
return await dialog.showOpenDialog(browserWindow, options)
|
||||
}
|
||||
export const showDialog = ({ type, message, detail }: Electron.MessageBoxSyncOptions) => {
|
||||
if (!browserWindow) return
|
||||
dialog.showMessageBoxSync(browserWindow, {
|
||||
type,
|
||||
message,
|
||||
detail,
|
||||
})
|
||||
}
|
||||
export const showSaveDialog = async(options: Electron.SaveDialogOptions) => {
|
||||
if (!browserWindow) throw new Error('main window is undefined')
|
||||
return await dialog.showSaveDialog(browserWindow, options)
|
||||
}
|
||||
export const minimize = () => {
|
||||
if (!browserWindow) return
|
||||
browserWindow.minimize()
|
||||
}
|
||||
export const maximize = () => {
|
||||
if (!browserWindow) return
|
||||
browserWindow.maximize()
|
||||
}
|
||||
export const unmaximize = () => {
|
||||
if (!browserWindow) return
|
||||
browserWindow.unmaximize()
|
||||
}
|
||||
export const toggleHide = () => {
|
||||
if (!browserWindow) return
|
||||
browserWindow.isVisible()
|
||||
? browserWindow.hide()
|
||||
: browserWindow.show()
|
||||
}
|
||||
export const toggleMinimize = () => {
|
||||
if (!browserWindow) return
|
||||
if (browserWindow.isMinimized()) {
|
||||
if (!browserWindow.isVisible()) {
|
||||
browserWindow.show()
|
||||
}
|
||||
browserWindow.restore()
|
||||
browserWindow.focus()
|
||||
} else {
|
||||
browserWindow.minimize()
|
||||
}
|
||||
}
|
||||
export const showWindow = () => {
|
||||
if (!browserWindow) return
|
||||
if (browserWindow.isMinimized()) {
|
||||
browserWindow.restore()
|
||||
}
|
||||
if (browserWindow.isVisible()) {
|
||||
browserWindow.focus()
|
||||
} else {
|
||||
browserWindow.show()
|
||||
}
|
||||
}
|
||||
export const hideWindow = () => {
|
||||
if (!browserWindow) return
|
||||
browserWindow.hide()
|
||||
}
|
||||
export const setWindowBounds = (options: Partial<Electron.Rectangle>) => {
|
||||
if (!browserWindow) return
|
||||
browserWindow.setBounds(options)
|
||||
}
|
||||
export const setProgressBar = (progress: number, options?: Electron.ProgressBarOptions) => {
|
||||
if (!browserWindow) return
|
||||
browserWindow.setProgressBar(progress, options)
|
||||
}
|
||||
export const setIgnoreMouseEvents = (ignore: boolean, options?: Electron.IgnoreMouseEventsOptions) => {
|
||||
if (!browserWindow) return
|
||||
browserWindow.setIgnoreMouseEvents(ignore, options)
|
||||
}
|
||||
export const toggleDevTools = () => {
|
||||
if (!browserWindow) return
|
||||
if (browserWindow.webContents.isDevToolsOpened()) {
|
||||
browserWindow.webContents.closeDevTools()
|
||||
} else {
|
||||
handleOpenDevTools(browserWindow.webContents)
|
||||
}
|
||||
}
|
||||
|
||||
export const setFullScreen = (isFullscreen: boolean): boolean => {
|
||||
if (!browserWindow) return false
|
||||
if (isLinux) { // linux 需要先设置为可调整窗口大小才能全屏
|
||||
if (isFullscreen) {
|
||||
browserWindow.setResizable(isFullscreen)
|
||||
browserWindow.setFullScreen(isFullscreen)
|
||||
} else {
|
||||
browserWindow.setFullScreen(isFullscreen)
|
||||
browserWindow.setResizable(isFullscreen)
|
||||
}
|
||||
} else {
|
||||
browserWindow.setFullScreen(isFullscreen)
|
||||
}
|
||||
return isFullscreen
|
||||
}
|
||||
|
||||
const taskBarButtonFlags: LX.TaskBarButtonFlags = {
|
||||
empty: true,
|
||||
collect: false,
|
||||
play: false,
|
||||
next: true,
|
||||
prev: true,
|
||||
}
|
||||
export const setThumbarButtons = ({ empty, collect, play, next, prev }: LX.TaskBarButtonFlags = taskBarButtonFlags) => {
|
||||
if (!isWin || !browserWindow) return
|
||||
taskBarButtonFlags.empty = empty
|
||||
taskBarButtonFlags.collect = collect
|
||||
taskBarButtonFlags.play = play
|
||||
taskBarButtonFlags.next = next
|
||||
taskBarButtonFlags.prev = prev
|
||||
browserWindow.setThumbarButtons(createTaskBarButtons(taskBarButtonFlags, action => {
|
||||
sendTaskbarButtonClick(action)
|
||||
}))
|
||||
}
|
||||
|
||||
export const setThumbnailClip = (region: Electron.Rectangle) => {
|
||||
if (!browserWindow) return
|
||||
return browserWindow.setThumbnailClip(region)
|
||||
}
|
||||
|
||||
|
||||
export const clearCache = async() => {
|
||||
if (!browserWindow) throw new Error('main window is undefined')
|
||||
return await browserWindow.webContents.session.clearCache()
|
||||
}
|
||||
|
||||
export const getCacheSize = async() => {
|
||||
if (!browserWindow) throw new Error('main window is undefined')
|
||||
return await browserWindow.webContents.session.getCacheSize()
|
||||
}
|
||||
|
||||
export const getWebContents = (): Electron.WebContents => {
|
||||
if (!browserWindow) throw new Error('main window is undefined')
|
||||
return browserWindow.webContents
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
// const path = require('path')
|
||||
import { app } from 'electron'
|
||||
import { mainHandle, mainOn } from '@common/mainIpc'
|
||||
import { WIN_MAIN_RENDERER_EVENT_NAME } from '@common/ipcNames'
|
||||
// import { name as defaultName } from '../../../../../package.json'
|
||||
import {
|
||||
minimize,
|
||||
maximize,
|
||||
closeWindow,
|
||||
showWindow,
|
||||
setFullScreen,
|
||||
sendEvent,
|
||||
clearCache,
|
||||
getCacheSize,
|
||||
toggleDevTools,
|
||||
setWindowBounds,
|
||||
setProgressBar,
|
||||
setIgnoreMouseEvents,
|
||||
// setThumbnailClip,
|
||||
setThumbarButtons,
|
||||
toggleMinimize,
|
||||
toggleHide,
|
||||
showSelectDialog,
|
||||
showDialog,
|
||||
showSaveDialog,
|
||||
} from '@main/modules/winMain'
|
||||
import { quitApp } from '@main/app'
|
||||
import { getAllThemes, removeTheme, saveTheme } from '@main/utils'
|
||||
|
||||
export default () => {
|
||||
// 设置应用名称
|
||||
// mainOn(WIN_MAIN_RENDERER_EVENT_NAME.set_app_name, ({ params: name }) => {
|
||||
// if (name == null) {
|
||||
// app.setName(defaultName)
|
||||
// } else {
|
||||
// app.setName(name)
|
||||
// }
|
||||
// })
|
||||
mainOn(WIN_MAIN_RENDERER_EVENT_NAME.quit, () => {
|
||||
quitApp()
|
||||
})
|
||||
mainOn(WIN_MAIN_RENDERER_EVENT_NAME.min_toggle, () => {
|
||||
toggleMinimize()
|
||||
})
|
||||
mainOn(WIN_MAIN_RENDERER_EVENT_NAME.hide_toggle, () => {
|
||||
toggleHide()
|
||||
})
|
||||
mainOn(WIN_MAIN_RENDERER_EVENT_NAME.min, () => {
|
||||
minimize()
|
||||
})
|
||||
mainOn(WIN_MAIN_RENDERER_EVENT_NAME.max, () => {
|
||||
maximize()
|
||||
})
|
||||
mainOn(WIN_MAIN_RENDERER_EVENT_NAME.focus, () => {
|
||||
showWindow()
|
||||
})
|
||||
mainOn<boolean>(WIN_MAIN_RENDERER_EVENT_NAME.close, ({ params: isForce }) => {
|
||||
if (isForce) return app.exit(0)
|
||||
global.lx.isTrafficLightClose = true
|
||||
closeWindow()
|
||||
})
|
||||
// 全屏
|
||||
mainHandle<boolean, boolean>(WIN_MAIN_RENDERER_EVENT_NAME.fullscreen, async({ params: isFullscreen }) => {
|
||||
return setFullScreen(isFullscreen)
|
||||
})
|
||||
|
||||
// 选择目录
|
||||
mainHandle<Electron.OpenDialogOptions, Electron.OpenDialogReturnValue>(WIN_MAIN_RENDERER_EVENT_NAME.show_select_dialog, async({ params: options }) => {
|
||||
return await showSelectDialog(options)
|
||||
})
|
||||
// 显示弹窗信息
|
||||
mainOn<Electron.MessageBoxSyncOptions>(WIN_MAIN_RENDERER_EVENT_NAME.show_dialog, ({ params }) => {
|
||||
showDialog(params)
|
||||
})
|
||||
// 显示保存弹窗
|
||||
mainHandle<Electron.SaveDialogOptions, Electron.SaveDialogReturnValue>(WIN_MAIN_RENDERER_EVENT_NAME.show_save_dialog, async({ params }) => {
|
||||
return await showSaveDialog(params)
|
||||
})
|
||||
|
||||
|
||||
mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.clear_cache, async() => {
|
||||
return await clearCache()
|
||||
})
|
||||
|
||||
mainHandle<number>(WIN_MAIN_RENDERER_EVENT_NAME.get_cache_size, async() => {
|
||||
return await getCacheSize()
|
||||
})
|
||||
|
||||
mainOn(WIN_MAIN_RENDERER_EVENT_NAME.open_dev_tools, () => {
|
||||
toggleDevTools()
|
||||
})
|
||||
|
||||
mainOn<Partial<Electron.Rectangle>>(WIN_MAIN_RENDERER_EVENT_NAME.set_window_size, ({ params }) => {
|
||||
setWindowBounds(params)
|
||||
})
|
||||
|
||||
mainOn<LX.Player.ProgressBarOptions>(WIN_MAIN_RENDERER_EVENT_NAME.progress, ({ params }) => {
|
||||
// console.log(params)
|
||||
setProgressBar(params.progress, {
|
||||
mode: params.mode ?? 'normal',
|
||||
})
|
||||
})
|
||||
|
||||
mainOn<boolean>(WIN_MAIN_RENDERER_EVENT_NAME.set_ignore_mouse_events, ({ params: isIgnored }) => {
|
||||
isIgnored
|
||||
? setIgnoreMouseEvents(isIgnored, { forward: true })
|
||||
: setIgnoreMouseEvents(false)
|
||||
})
|
||||
|
||||
// mainHandle<Electron.Rectangle>(WIN_MAIN_RENDERER_EVENT_NAME.taskbar_set_thumbnail_clip, async({ params }) => {
|
||||
// return setThumbnailClip(params)
|
||||
// })
|
||||
|
||||
mainOn<LX.TaskBarButtonFlags>(WIN_MAIN_RENDERER_EVENT_NAME.player_action_set_buttons, ({ params }) => {
|
||||
setThumbarButtons(params)
|
||||
})
|
||||
|
||||
mainOn(WIN_MAIN_RENDERER_EVENT_NAME.inited, () => {
|
||||
global.lx.event_app.main_window_inited()
|
||||
})
|
||||
|
||||
mainHandle<{ themes: LX.Theme[], userThemes: LX.Theme[] }>(WIN_MAIN_RENDERER_EVENT_NAME.get_themes, async() => {
|
||||
return getAllThemes()
|
||||
})
|
||||
mainHandle<LX.Theme>(WIN_MAIN_RENDERER_EVENT_NAME.save_theme, async({ params: theme }) => {
|
||||
saveTheme(theme)
|
||||
})
|
||||
mainHandle<string>(WIN_MAIN_RENDERER_EVENT_NAME.remove_theme, async({ params: id }) => {
|
||||
removeTheme(id)
|
||||
})
|
||||
}
|
||||
|
||||
export const sendFocus = () => {
|
||||
sendEvent(WIN_MAIN_RENDERER_EVENT_NAME.focus)
|
||||
}
|
||||
|
||||
export const sendTaskbarButtonClick = (action: LX.Player.StatusButtonActions) => {
|
||||
sendEvent(WIN_MAIN_RENDERER_EVENT_NAME.player_action_on_button_click, action)
|
||||
}
|
||||
export const sendConfigChange = (setting: Partial<LX.AppSetting>) => {
|
||||
sendEvent(WIN_MAIN_RENDERER_EVENT_NAME.on_config_change, setting)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { STORE_NAMES } from '@common/constants'
|
||||
import { WIN_MAIN_RENDERER_EVENT_NAME } from '@common/ipcNames'
|
||||
import { mainOn, mainHandle } from '@common/mainIpc'
|
||||
import getStore from '@main/utils/store'
|
||||
|
||||
export default () => {
|
||||
mainHandle<string, any>(WIN_MAIN_RENDERER_EVENT_NAME.get_data, ({ params: path }) => {
|
||||
return getStore(STORE_NAMES.DATA).get(path) as any
|
||||
})
|
||||
|
||||
mainOn<{
|
||||
path: string
|
||||
data: any
|
||||
}>(WIN_MAIN_RENDERER_EVENT_NAME.save_data, ({ params: { path, data } }) => {
|
||||
getStore(STORE_NAMES.DATA).set(path, data)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { WIN_MAIN_RENDERER_EVENT_NAME } from '@common/ipcNames'
|
||||
import { mainHandle } from '@common/mainIpc'
|
||||
|
||||
|
||||
export default () => {
|
||||
mainHandle<LX.Download.ListItem[]>(WIN_MAIN_RENDERER_EVENT_NAME.download_list_get, async() => {
|
||||
return await global.lx.worker.dbService.getDownloadList()
|
||||
})
|
||||
mainHandle<LX.Download.saveDownloadMusicInfo>(WIN_MAIN_RENDERER_EVENT_NAME.download_list_add, async({ params: { list, addMusicLocationType } }) => {
|
||||
await global.lx.worker.dbService.downloadInfoSave(list, addMusicLocationType)
|
||||
})
|
||||
mainHandle<LX.Download.ListItem[]>(WIN_MAIN_RENDERER_EVENT_NAME.download_list_update, async({ params: list }) => {
|
||||
await global.lx.worker.dbService.downloadInfoUpdate(list)
|
||||
})
|
||||
mainHandle<string[]>(WIN_MAIN_RENDERER_EVENT_NAME.download_list_remove, async({ params: ids }) => {
|
||||
return await global.lx.worker.dbService.downloadInfoRemove(ids)
|
||||
})
|
||||
mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.download_list_clear, async() => {
|
||||
await global.lx.worker.dbService.downloadInfoClear()
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { WIN_MAIN_RENDERER_EVENT_NAME } from '@common/ipcNames'
|
||||
import { mainHandle } from '@common/mainIpc'
|
||||
import { sendEvent } from '../main'
|
||||
// import getStore from '@common/store'
|
||||
|
||||
|
||||
// const { registerHotkey, unRegisterHotkey } = require('../modules/hotKey/utils')
|
||||
|
||||
// mainHandle(ipcMainWindowNames.set_hot_key_config, async(event, { action, data }) => {
|
||||
// switch (action) {
|
||||
// case 'config':
|
||||
// global.lx_event.hotKey.saveConfig(data.data, MAIN_WINDOW_EVENT_NAME.source)
|
||||
// return
|
||||
// case 'register':
|
||||
// return registerHotkey(data)
|
||||
// case 'unregister':
|
||||
// return unRegisterHotkey(data)
|
||||
// }
|
||||
// })
|
||||
|
||||
export default () => {
|
||||
mainHandle<LX.HotKeyConfigAll>(WIN_MAIN_RENDERER_EVENT_NAME.get_hot_key, async() => {
|
||||
// const electronStore_hotKey = getStore('hotKey')
|
||||
return {
|
||||
local: global.lx.hotKey?.config.local,
|
||||
global: global.lx.hotKey?.config.global,
|
||||
}
|
||||
})
|
||||
|
||||
// global.lx.event_app.on(APP_EVENT_NAMES.hotKeyConfig, (config, source) => {
|
||||
// if (!global.modules.mainWindow || source === MAIN_WINDOW_EVENT_NAME.name) return
|
||||
// mainSend(global.modules.mainWindow, WIN_MAIN_RENDERER_EVENT_NAME.set_hot_key_config, { config, source })
|
||||
// })
|
||||
}
|
||||
|
||||
export const handleKeyDown = (type: string, key: string) => {
|
||||
sendEvent<LX.HotKeyEvent>(WIN_MAIN_RENDERER_EVENT_NAME.key_down, { type, key })
|
||||
}
|
||||
|
||||
export const hotKeyConfigUpdate = (config: LX.HotKeyConfigAll) => {
|
||||
sendEvent<LX.HotKeyConfigAll>(WIN_MAIN_RENDERER_EVENT_NAME.set_hot_key_config, config)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { registerRendererEvents as common } from '@main/modules/commonRenderers/common'
|
||||
import { registerRendererEvents as list } from '@main/modules/commonRenderers/list'
|
||||
import app, { sendConfigChange } from './app'
|
||||
import hotKey from './hotKey'
|
||||
import kw_decodeLyric from './kw_decodeLyric'
|
||||
import userApi from './userApi'
|
||||
import sync from './sync'
|
||||
import data from './data'
|
||||
import music from './music'
|
||||
import download from './download'
|
||||
import { sendEvent } from '../main'
|
||||
|
||||
export * from './app'
|
||||
export * from './hotKey'
|
||||
export * from './userApi'
|
||||
export * from './sync'
|
||||
export * from './process'
|
||||
|
||||
let isInitialized = false
|
||||
export default () => {
|
||||
if (isInitialized) return
|
||||
isInitialized = true
|
||||
|
||||
common(sendEvent)
|
||||
list(sendEvent)
|
||||
app()
|
||||
hotKey()
|
||||
kw_decodeLyric()
|
||||
userApi()
|
||||
sync()
|
||||
data()
|
||||
music()
|
||||
download()
|
||||
|
||||
global.lx.event_app.on('updated_config', (keys, setting) => {
|
||||
sendConfigChange(setting)
|
||||
})
|
||||
}
|
||||
|
||||
+19
-14
@@ -1,18 +1,21 @@
|
||||
const { inflate } = require('zlib')
|
||||
const iconv = require('iconv-lite')
|
||||
const { mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
|
||||
import { inflate } from 'zlib'
|
||||
import iconv from 'iconv-lite'
|
||||
import { mainHandle } from '@common/mainIpc'
|
||||
import { WIN_MAIN_RENDERER_EVENT_NAME } from '@common/ipcNames'
|
||||
|
||||
const handleInflate = data => new Promise((resolve, reject) => {
|
||||
inflate(data, (err, result) => {
|
||||
if (err) return reject(err)
|
||||
resolve(result)
|
||||
const handleInflate = async(data: Buffer) => {
|
||||
return await new Promise((resolve: (result: Buffer) => void, reject) => {
|
||||
inflate(data, (err, result) => {
|
||||
if (err) return reject(err)
|
||||
resolve(result)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const buf_key = Buffer.from('yeelion')
|
||||
const buf_key_len = buf_key.length
|
||||
|
||||
const decodeLyric = async(buf, isGetLyricx) => {
|
||||
const decodeLyric = async(buf: Buffer, isGetLyricx: boolean) => {
|
||||
// const info = buf.slice(0, index).toString()
|
||||
// if (!info.startsWith('tp=content')) return null
|
||||
// const isLyric = info.includes('\r\nlrcx=0\r\n')
|
||||
@@ -38,8 +41,10 @@ const decodeLyric = async(buf, isGetLyricx) => {
|
||||
return iconv.decode(Buffer.from(output), 'gb18030')
|
||||
}
|
||||
|
||||
mainHandle(ipcMainWindowNames.handle_kw_decode_lyric, async(event, { lrcBase64, isGetLyricx }) => {
|
||||
if (!global.modules.mainWindow) throw new Error('mainWindow is undefined')
|
||||
const lrc = await decodeLyric(Buffer.from(lrcBase64, 'base64'), isGetLyricx)
|
||||
return Buffer.from(lrc).toString('base64')
|
||||
})
|
||||
|
||||
export default () => {
|
||||
mainHandle<{ lrcBase64: string, isGetLyricx: boolean }, string>(WIN_MAIN_RENDERER_EVENT_NAME.handle_kw_decode_lyric, async({ params: { lrcBase64, isGetLyricx } }) => {
|
||||
const lrc = await decodeLyric(Buffer.from(lrcBase64, 'base64'), isGetLyricx)
|
||||
return Buffer.from(lrc).toString('base64')
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import { WIN_MAIN_RENDERER_EVENT_NAME } from '@common/ipcNames'
|
||||
import { mainHandle } from '@common/mainIpc'
|
||||
|
||||
|
||||
export default () => {
|
||||
// =========================歌词=========================
|
||||
mainHandle<string, LX.Player.LyricInfo>(WIN_MAIN_RENDERER_EVENT_NAME.get_palyer_lyric, async({ params: id }) => {
|
||||
// return (getStore(LRC_EDITED, true, false).get(id) as LX.Music.LyricInfo | undefined) ??
|
||||
// getStore(LRC_RAW, true, false).get(id, {}) as LX.Music.LyricInfo
|
||||
return await global.lx.worker.dbService.getPlayerLyric(id)
|
||||
})
|
||||
|
||||
// 原始歌词
|
||||
mainHandle<string, LX.Music.LyricInfo>(WIN_MAIN_RENDERER_EVENT_NAME.get_lyric_raw, async({ params: id }) => {
|
||||
return await global.lx.worker.dbService.getRawLyric(id)
|
||||
})
|
||||
mainHandle<LX.Music.LyricInfoSave>(WIN_MAIN_RENDERER_EVENT_NAME.save_lyric_raw, async({ params: { id, lyrics } }) => {
|
||||
await global.lx.worker.dbService.rawLyricAdd(id, lyrics)
|
||||
})
|
||||
mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.clear_lyric_raw, async() => {
|
||||
await global.lx.worker.dbService.rawLyricClear()
|
||||
})
|
||||
mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.get_lyric_raw_count, async() => {
|
||||
return await global.lx.worker.dbService.rawLyricCount()
|
||||
})
|
||||
|
||||
// 已编辑的歌词
|
||||
mainHandle<string, LX.Music.LyricInfo>(WIN_MAIN_RENDERER_EVENT_NAME.get_lyric_edited, async({ params: id }) => {
|
||||
return await global.lx.worker.dbService.getEditedLyric(id)
|
||||
})
|
||||
mainHandle<LX.Music.LyricInfoSave>(WIN_MAIN_RENDERER_EVENT_NAME.save_lyric_edited, async({ params: { id, lyrics } }) => {
|
||||
await global.lx.worker.dbService.editedLyricUpdateAddAndUpdate(id, lyrics)
|
||||
})
|
||||
mainHandle<string>(WIN_MAIN_RENDERER_EVENT_NAME.remove_lyric_edited, async({ params: id }) => {
|
||||
await global.lx.worker.dbService.editedLyricRemove([id])
|
||||
})
|
||||
mainHandle<string>(WIN_MAIN_RENDERER_EVENT_NAME.clear_lyric_edited, async() => {
|
||||
await global.lx.worker.dbService.editedLyricClear()
|
||||
})
|
||||
mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.get_lyric_edited_count, async() => {
|
||||
return await global.lx.worker.dbService.editedLyricCount()
|
||||
})
|
||||
|
||||
|
||||
// =========================歌曲URL=========================
|
||||
mainHandle<string, string>(WIN_MAIN_RENDERER_EVENT_NAME.get_music_url, async({ params: id }) => {
|
||||
return await global.lx.worker.dbService.getMusicUrl(id) ?? ''
|
||||
})
|
||||
mainHandle<LX.Music.MusicUrlInfo>(WIN_MAIN_RENDERER_EVENT_NAME.save_music_url, async({ params: { id, url } }) => {
|
||||
await global.lx.worker.dbService.musicUrlSave([{ id, url }])
|
||||
})
|
||||
mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.clear_music_url, async() => {
|
||||
await global.lx.worker.dbService.musicUrlClear()
|
||||
})
|
||||
mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.get_music_url_count, async() => {
|
||||
return await global.lx.worker.dbService.musicUrlCount()
|
||||
})
|
||||
|
||||
// =========================换源歌曲=========================
|
||||
mainHandle<string, LX.Music.MusicInfoOnline[]>(WIN_MAIN_RENDERER_EVENT_NAME.get_other_source, async({ params: id }) => {
|
||||
return await global.lx.worker.dbService.getMusicInfoOtherSource(id)
|
||||
})
|
||||
mainHandle<LX.Music.MusicInfoOtherSourceSave>(WIN_MAIN_RENDERER_EVENT_NAME.save_other_source, async({ params: { id, list } }) => {
|
||||
await global.lx.worker.dbService.musicInfoOtherSourceAdd(id, list)
|
||||
})
|
||||
mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.clear_other_source, async() => {
|
||||
await global.lx.worker.dbService.musicInfoOtherSourceClear()
|
||||
})
|
||||
mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.get_other_source_count, async() => {
|
||||
return await global.lx.worker.dbService.musicInfoOtherSourceCount()
|
||||
})
|
||||
|
||||
|
||||
// =========================我的列表=========================
|
||||
// mainHandle<boolean>(WIN_MAIN_RENDERER_EVENT_NAME.get_playlist, async({ params: isIgnoredError = false }) => {
|
||||
// const electronStore_list = getStore('playList', isIgnoredError, false)
|
||||
|
||||
// return {
|
||||
// defaultList: electronStore_list.get('defaultList'),
|
||||
// loveList: electronStore_list.get('loveList'),
|
||||
// tempList: electronStore_list.get('tempList'),
|
||||
// userList: electronStore_list.get('userList'),
|
||||
// downloadList: getStore('downloadList').get('list'),
|
||||
// }
|
||||
// })
|
||||
|
||||
// const handleSaveList = ({ defaultList, loveList, userList, tempList }: Partial<LX.List.MyAllList>) => {
|
||||
// let data: Partial<LX.List.MyAllList> = {}
|
||||
// if (defaultList != null) data.defaultList = defaultList
|
||||
// if (loveList != null) data.loveList = loveList
|
||||
// if (userList != null) data.userList = userList
|
||||
// if (tempList != null) data.tempList = tempList
|
||||
// getStore('playList').set(data)
|
||||
// }
|
||||
// mainOn<LX.List.ListSaveInfo>(WIN_MAIN_RENDERER_EVENT_NAME.save_playlist, ({ params }) => {
|
||||
// switch (params.type) {
|
||||
// case 'myList':
|
||||
// handleSaveList(params.data)
|
||||
// global.lx.event_app.save_my_list(params.data)
|
||||
// break
|
||||
// case 'downloadList':
|
||||
// getStore('downloadList').set('list', params.data)
|
||||
// break
|
||||
// }
|
||||
// })
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { WIN_MAIN_RENDERER_EVENT_NAME } from '@common/ipcNames'
|
||||
import { getWebContents } from '../main'
|
||||
|
||||
|
||||
// export default () => {
|
||||
|
||||
|
||||
// }
|
||||
|
||||
/**
|
||||
* 发送桌面歌词进程创建事件
|
||||
* @param port 端口
|
||||
*/
|
||||
export const sendNewDesktopLyricClient = (port: Electron.MessagePortMain) => {
|
||||
getWebContents().postMessage(WIN_MAIN_RENDERER_EVENT_NAME.process_new_desktop_lyric_client, null, [port])
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { mainHandle } from '@common/mainIpc'
|
||||
import { WIN_MAIN_RENDERER_EVENT_NAME } from '@common/ipcNames'
|
||||
import { startServer, stopServer, getStatus, generateCode } from '@main/modules/sync'
|
||||
import { sendEvent } from '../main'
|
||||
|
||||
let selectModeListenr: ((mode: LX.Sync.Mode) => void) | null = null
|
||||
|
||||
export default () => {
|
||||
mainHandle<LX.Sync.SyncServiceActions, any>(WIN_MAIN_RENDERER_EVENT_NAME.sync_action, async({ params: data }) => {
|
||||
switch (data.action) {
|
||||
case 'enable':
|
||||
return data.data.enable ? await startServer(parseInt(data.data.port)) : await stopServer()
|
||||
case 'get_status':
|
||||
return getStatus()
|
||||
case 'generate_code':
|
||||
return await generateCode()
|
||||
case 'select_mode':
|
||||
if (selectModeListenr) selectModeListenr(data.data)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export const sendSyncAction = (data: LX.Sync.SyncMainWindowActions) => {
|
||||
sendEvent(WIN_MAIN_RENDERER_EVENT_NAME.sync_action, data)
|
||||
}
|
||||
|
||||
export const sendStatus = (status: LX.Sync.Status) => {
|
||||
sendSyncAction({
|
||||
action: 'status',
|
||||
data: status,
|
||||
})
|
||||
}
|
||||
export const sendSelectMode = (keyInfo: LX.Sync.KeyInfo, listener: (mode: LX.Sync.Mode) => void) => {
|
||||
selectModeListenr = listener
|
||||
sendSyncAction({ action: 'select_mode', data: keyInfo })
|
||||
return () => {
|
||||
selectModeListenr = null
|
||||
}
|
||||
}
|
||||
export const sendCloseSelectMode = () => {
|
||||
sendSyncAction({ action: 'close_select_mode' })
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { WIN_MAIN_RENDERER_EVENT_NAME } from '@common/ipcNames'
|
||||
import { mainHandle } from '@common/mainIpc'
|
||||
import {
|
||||
getApiList,
|
||||
importApi,
|
||||
removeApi,
|
||||
setApi,
|
||||
getStatus,
|
||||
request,
|
||||
cancelRequest,
|
||||
setAllowShowUpdateAlert,
|
||||
} from '@main/modules/userApi'
|
||||
import { sendEvent } from '@main/modules/winMain/main'
|
||||
|
||||
export default () => {
|
||||
mainHandle<string, LX.UserApi.ImportUserApi>(WIN_MAIN_RENDERER_EVENT_NAME.import_user_api, async({ params: script }) => {
|
||||
return importApi(script)
|
||||
})
|
||||
|
||||
mainHandle<string[], LX.UserApi.UserApiInfo[]>(WIN_MAIN_RENDERER_EVENT_NAME.remove_user_api, async({ params: apiIds }) => {
|
||||
return await removeApi(apiIds)
|
||||
})
|
||||
|
||||
mainHandle<LX.UserApi.UserApiSetApiParams>(WIN_MAIN_RENDERER_EVENT_NAME.set_user_api, async({ params: apiId }) => {
|
||||
return await setApi(apiId)
|
||||
})
|
||||
|
||||
mainHandle<LX.UserApi.UserApiInfo[]>(WIN_MAIN_RENDERER_EVENT_NAME.get_user_api_list, async() => {
|
||||
return getApiList()
|
||||
})
|
||||
|
||||
mainHandle<LX.UserApi.UserApiStatus>(WIN_MAIN_RENDERER_EVENT_NAME.get_user_api_status, async() => {
|
||||
return getStatus()
|
||||
})
|
||||
|
||||
mainHandle<LX.UserApi.UserApiSetAllowUpdateAlertParams>(WIN_MAIN_RENDERER_EVENT_NAME.user_api_set_allow_update_alert, async({ params: { id, enable } }) => {
|
||||
return setAllowShowUpdateAlert(id, enable)
|
||||
})
|
||||
|
||||
mainHandle<LX.UserApi.UserApiRequestParams>(WIN_MAIN_RENDERER_EVENT_NAME.request_user_api, async({ params }) => {
|
||||
return await request(params)
|
||||
})
|
||||
mainHandle<LX.UserApi.UserApiRequestCancelParams>(WIN_MAIN_RENDERER_EVENT_NAME.request_user_api_cancel, async({ params: requestKey }) => {
|
||||
return cancelRequest(requestKey)
|
||||
})
|
||||
}
|
||||
|
||||
export const sendStatusChange = (status: LX.UserApi.UserApiStatus) => {
|
||||
sendEvent(WIN_MAIN_RENDERER_EVENT_NAME.user_api_status, status)
|
||||
}
|
||||
export const sendShowUpdateAlert = (info: LX.UserApi.UserApiUpdateInfo) => {
|
||||
sendEvent(WIN_MAIN_RENDERER_EVENT_NAME.user_api_show_update_alert, info)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
// import fs from 'fs'
|
||||
import { join } from 'path'
|
||||
import { WindowSize, windowSizeList } from '@common/config'
|
||||
import { nativeImage } from 'electron'
|
||||
|
||||
export const getWindowSizeInfo = (windowSizeId: number | string): WindowSize => {
|
||||
return windowSizeList.find(i => i.id == windowSizeId) ?? windowSizeList[0]
|
||||
}
|
||||
|
||||
const getIconPath = (name: string): Electron.NativeImage => {
|
||||
return nativeImage.createFromPath(join(global.staticPath, 'images/taskbar', name + '.png'))
|
||||
}
|
||||
|
||||
export const createTaskBarButtons = ({
|
||||
empty = false,
|
||||
collect = false,
|
||||
play = false,
|
||||
next = true,
|
||||
prev = true,
|
||||
}: LX.TaskBarButtonFlags, onClick: (action: LX.Player.StatusButtonActions) => void): Electron.ThumbarButton[] => {
|
||||
const buttons: Electron.ThumbarButton[] = [
|
||||
collect
|
||||
? {
|
||||
icon: getIconPath('collected'),
|
||||
click() {
|
||||
onClick('unCollect')
|
||||
},
|
||||
tooltip: '取消收藏',
|
||||
flags: ['nobackground'],
|
||||
}
|
||||
: {
|
||||
icon: getIconPath('collect'),
|
||||
click() {
|
||||
onClick('collect')
|
||||
},
|
||||
tooltip: '收藏',
|
||||
flags: ['nobackground'],
|
||||
},
|
||||
{
|
||||
icon: getIconPath('prev'),
|
||||
click() {
|
||||
onClick('prev')
|
||||
},
|
||||
tooltip: '上一曲',
|
||||
flags: prev ? ['nobackground'] : ['nobackground', 'disabled'],
|
||||
},
|
||||
play
|
||||
? {
|
||||
icon: getIconPath('pause'),
|
||||
click() {
|
||||
onClick('pause')
|
||||
},
|
||||
tooltip: '暂停',
|
||||
flags: ['nobackground'],
|
||||
}
|
||||
: {
|
||||
icon: getIconPath('play'),
|
||||
click() {
|
||||
onClick('play')
|
||||
},
|
||||
tooltip: '播放',
|
||||
flags: ['nobackground'],
|
||||
},
|
||||
{
|
||||
icon: getIconPath('next'),
|
||||
click() {
|
||||
onClick('next')
|
||||
},
|
||||
tooltip: '下一曲',
|
||||
flags: next ? ['nobackground'] : ['nobackground', 'disabled'],
|
||||
},
|
||||
]
|
||||
if (empty) {
|
||||
for (const button of buttons) {
|
||||
button.flags = ['nobackground', 'disabled']
|
||||
}
|
||||
}
|
||||
return buttons
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
const { mainOn, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
|
||||
const { app } = require('electron')
|
||||
const { name: defaultName } = require('../../../package.json')
|
||||
|
||||
|
||||
mainOn(ipcMainWindowNames.set_app_name, (event, params) => {
|
||||
if (params == null) {
|
||||
app.setName(defaultName)
|
||||
} else {
|
||||
app.setName(params.name)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
const { mainWindow: MAIN_WINDOW_EVENT_NAME } = require('../events/_name')
|
||||
const { NAMES: { mainWindow: ipcMainWindowNames }, mainHandle } = require('../../common/ipc')
|
||||
|
||||
|
||||
mainHandle(ipcMainWindowNames.set_app_setting, (event, config) => {
|
||||
if (!config) return
|
||||
global.lx_core.setAppConfig(config, MAIN_WINDOW_EVENT_NAME.name)
|
||||
return global.appSetting
|
||||
})
|
||||
|
||||
mainHandle(ipcMainWindowNames.get_setting, async() => ({ setting: global.appSetting, version: global.appSettingVersion }))
|
||||
@@ -1,11 +0,0 @@
|
||||
const { mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
|
||||
|
||||
mainHandle(ipcMainWindowNames.clear_cache, async(event, options) => {
|
||||
if (!global.modules.mainWindow) throw new Error('mainWindow is undefined')
|
||||
return global.modules.mainWindow.webContents.session.clearCache()
|
||||
})
|
||||
|
||||
mainHandle(ipcMainWindowNames.get_cache_size, async(event, options) => {
|
||||
if (!global.modules.mainWindow) throw new Error('mainWindow is undefined')
|
||||
return global.modules.mainWindow.webContents.session.getCacheSize()
|
||||
})
|
||||
@@ -1,8 +0,0 @@
|
||||
const { mainOn, NAMES: { mainWindow: ipcMainWindowNames }, mainHandle } = require('../../common/ipc')
|
||||
const getStore = require('@common/store')
|
||||
|
||||
|
||||
mainHandle(ipcMainWindowNames.get_data, async(event, path) => getStore('data').get(path))
|
||||
|
||||
|
||||
mainOn(ipcMainWindowNames.save_data, (event, { path, data }) => getStore('data').set(path, data))
|
||||
@@ -1,5 +0,0 @@
|
||||
const { mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
|
||||
const { app } = require('electron')
|
||||
|
||||
mainHandle(ipcMainWindowNames.get_data_path, async() => app.getPath('userData'))
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
const { mainHandle, mainOn, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
|
||||
|
||||
mainHandle(ipcMainWindowNames.get_env_params, async(event, options) => {
|
||||
return global.envParams
|
||||
})
|
||||
|
||||
mainOn(ipcMainWindowNames.clear_env_params_deeplink, () => {
|
||||
global.envParams.deeplink = null
|
||||
})
|
||||
@@ -1,42 +0,0 @@
|
||||
const { mainSend, NAMES: { mainWindow: ipcMainWindowNames }, mainOn, mainHandle } = require('../../common/ipc')
|
||||
const { mainWindow: MAIN_WINDOW_EVENT_NAME, hotKey: HOT_KEY_EVENT_NAME } = require('../events/_name')
|
||||
const getStore = require('@common/store')
|
||||
|
||||
|
||||
// const { registerHotkey, unRegisterHotkey } = require('../modules/hotKey/utils')
|
||||
|
||||
// mainHandle(ipcMainWindowNames.set_hot_key_config, async(event, { action, data }) => {
|
||||
// switch (action) {
|
||||
// case 'config':
|
||||
// global.lx_event.hotKey.saveConfig(data.data, MAIN_WINDOW_EVENT_NAME.source)
|
||||
// return
|
||||
// case 'register':
|
||||
// return registerHotkey(data)
|
||||
// case 'unregister':
|
||||
// return unRegisterHotkey(data)
|
||||
// }
|
||||
// })
|
||||
|
||||
mainHandle(ipcMainWindowNames.get_hot_key, async() => {
|
||||
const electronStore_hotKey = getStore('hotKey')
|
||||
return {
|
||||
local: electronStore_hotKey.get('local'),
|
||||
global: electronStore_hotKey.get('global'),
|
||||
}
|
||||
})
|
||||
|
||||
mainOn(ipcMainWindowNames.quit, () => global.lx_event.mainWindow.quit())
|
||||
mainOn(ipcMainWindowNames.min_toggle, () => global.lx_event.mainWindow.toggleMinimize())
|
||||
mainOn(ipcMainWindowNames.hide_toggle, () => global.lx_event.mainWindow.toggleHide())
|
||||
|
||||
|
||||
global.lx_event.hotKey.on(HOT_KEY_EVENT_NAME.config, (config, source) => {
|
||||
if (!global.modules.mainWindow || source === MAIN_WINDOW_EVENT_NAME.name) return
|
||||
mainSend(global.modules.mainWindow, ipcMainWindowNames.set_hot_key_config, { config, source })
|
||||
})
|
||||
|
||||
global.lx_event.hotKey.on(HOT_KEY_EVENT_NAME.keyDown, ({ type, key }) => {
|
||||
let info = global.appHotKey.config.global.keys[key]
|
||||
if (!info || info.type != MAIN_WINDOW_EVENT_NAME.name) return
|
||||
mainSend(global.modules.mainWindow, ipcMainWindowNames.key_down, { type, key })
|
||||
})
|
||||
@@ -1,34 +0,0 @@
|
||||
const { isWin } = require('@common/utils')
|
||||
// require('./request')
|
||||
// require('./appName')
|
||||
require('./progressBar')
|
||||
require('./trafficLight')
|
||||
require('./musicMeta')
|
||||
require('./selectDir')
|
||||
require('./setWindowSize')
|
||||
require('./showSaveDialog')
|
||||
require('./cache')
|
||||
require('./setIgnoreMouseEvent')
|
||||
require('./getEnvParams')
|
||||
require('./appSetting')
|
||||
require('./setLyricInfo')
|
||||
require('./hotKey')
|
||||
require('./getDataPath')
|
||||
require('./showDialog')
|
||||
require('./playList')
|
||||
require('./data')
|
||||
require('./lyric')
|
||||
require('./musicUrl')
|
||||
require('./systemFonts')
|
||||
require('./wait')
|
||||
require('./openDevtools')
|
||||
require('./nativeTheme')
|
||||
|
||||
if (isWin) require('./taskbar')
|
||||
|
||||
require('./kw_decodeLyric')
|
||||
|
||||
require('./userApi')
|
||||
require('./sync')
|
||||
require('./s2t')
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
const { mainOn, NAMES: { mainWindow: ipcMainWindowNames }, mainHandle } = require('../../common/ipc')
|
||||
const getStore = require('@common/store')
|
||||
|
||||
const LRC_RAW = 'lyrics'
|
||||
const LRC_EDITED = 'lyrics_edited'
|
||||
|
||||
mainHandle(ipcMainWindowNames.get_lyric, async(event, id) => {
|
||||
return getStore(LRC_EDITED, true, false).get(id) || getStore(LRC_RAW, true, false).get(id) || {}
|
||||
})
|
||||
|
||||
|
||||
// 原始歌词
|
||||
mainHandle(ipcMainWindowNames.get_lyric_raw, async(event, id) => getStore(LRC_RAW, true, false).get(id) || {})
|
||||
mainOn(ipcMainWindowNames.save_lyric_raw, (event, { id, lyrics }) => getStore(LRC_RAW, true, false).set(id, lyrics))
|
||||
mainOn(ipcMainWindowNames.clear_lyric_raw, () => getStore(LRC_RAW, true, false).clear())
|
||||
|
||||
// 已编辑的歌词
|
||||
mainHandle(ipcMainWindowNames.get_lyric_edited, async(event, id) => getStore(LRC_EDITED, true, false).get(id) || {})
|
||||
mainOn(ipcMainWindowNames.save_lyric_edited, (event, { id, lyrics }) => getStore(LRC_EDITED, true, false).set(id, lyrics))
|
||||
mainOn(ipcMainWindowNames.remove_lyric_edited, async(event, id) => getStore(LRC_EDITED, true, false).delete(id))
|
||||
@@ -1,6 +0,0 @@
|
||||
const { mainOn, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
|
||||
const { setMeta } = require('../utils/musicMeta')
|
||||
|
||||
mainOn(ipcMainWindowNames.set_music_meta, (event, { filePath, meta }) => {
|
||||
setMeta(filePath, meta)
|
||||
})
|
||||
@@ -1,10 +0,0 @@
|
||||
const { mainOn, NAMES: { mainWindow: ipcMainWindowNames }, mainHandle } = require('../../common/ipc')
|
||||
const getStore = require('@common/store')
|
||||
|
||||
|
||||
mainHandle(ipcMainWindowNames.get_music_url, async(event, id) => getStore('musicUrls', true, false).get(id) || '')
|
||||
|
||||
|
||||
mainOn(ipcMainWindowNames.save_music_url, (event, { id, url }) => getStore('musicUrls', true, false).set(id, url))
|
||||
|
||||
mainOn(ipcMainWindowNames.clear_music_url, () => getStore('musicUrls', true, false).clear())
|
||||
@@ -1,11 +0,0 @@
|
||||
const { nativeTheme } = require('electron')
|
||||
|
||||
const { NAMES: { mainWindow: ipcMainWindowNames }, mainSend } = require('@common/ipc')
|
||||
|
||||
nativeTheme.addListener('updated', (event) => {
|
||||
// console.log(event.sender.shouldUseDarkColors)
|
||||
if (!global.modules.mainWindow) return
|
||||
mainSend(global.modules.mainWindow, ipcMainWindowNames.system_theme_change, event.sender.shouldUseDarkColors)
|
||||
// console.log(nativeTheme.themeSource)
|
||||
})
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
const { mainOn, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
|
||||
const { openDevTools } = require('@main/utils')
|
||||
|
||||
mainOn(ipcMainWindowNames.open_dev_tools, event => {
|
||||
if (global.modules.mainWindow) {
|
||||
if (global.modules.mainWindow.isDevToolsOpened()) {
|
||||
global.modules.mainWindow.webContents.closeDevTools()
|
||||
} else {
|
||||
openDevTools(global.modules.mainWindow.webContents)
|
||||
}
|
||||
}
|
||||
})
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user