const { app, BrowserWindow, screen, Tray, nativeImage, Menu, ipcMain, clipboard, dialog, desktopCapturer, } = require('electron'); const path = require('node:path') const updateJs = require('./src/utils/package/update') const fs = require('fs'); const Registry = require('winreg'); const { autoUpdater, CancellationToken } = require('electron-updater'); const signalR = require('@microsoft/signalr'); const cancellationToken = new CancellationToken() app.allowRendererProcessReuse = false; let mainWindow = null; let childWindow = {} let isMaximized = false; let env; let regKey; let connection = null; class AppWindow extends BrowserWindow { constructor(config) { const basicConfig = { webPreferences: { contextIsolation: false, nodeIntegration: true, enableRemoteModule: true, nodeIntegrationInWorker: true, allowMediaDevices: true, preload: path.join(__dirname, 'preload.js') }, show: false, frame: false, backgroundColor: '#00000000', transparent: true, }; const finalConfig = { ...basicConfig, ...config }; super(finalConfig); if (env === 'development') { // 开发 this.loadURL('http://localhost:3000'); } else { // 测试 | 生产 this.loadFile(path.resolve(__dirname, './dist/index.html')); } this.once('ready-to-show', () => { this.show(); }); } } function quit() { app.quit() } function createTray() { const iconPath = `${__dirname}/src/assets/icon.png`; const trayIcon = nativeImage.createFromPath(iconPath); const tray = new Tray(trayIcon); const contextMenu = Menu.buildFromTemplate([ { label: '打开', click: () => { mainWindow.webContents.send('isOpenWindows'); }, // icon: iconPath, }, { label: '最小化到系统托盘', click: () => { mainWindow.hide(); }, // icon: iconPath, }, { label: '退出', click: async () => { quit() }, // icon: iconPath, }, ]); tray.setToolTip(updateJs.getTitle(env)); tray.setContextMenu(contextMenu); tray.on('click', () => { mainWindow.webContents.send('isOpenWindows'); }); } function createWindow() { mainWindow = new AppWindow(); mainWindow.focus(); } const additionalData = { myKey: 'myValue' } app.on('ready', () => { const gotTheLock = app.requestSingleInstanceLock(additionalData) if (gotTheLock) { env = process.argv.find((arg) => arg.startsWith('--env='))?.split('=')[1]; if (env === 'development') { Object.defineProperty(app, 'isPackaged', { get() { return true } }) autoUpdater.updateConfigPath = path.join('latest.yml') } createWindow() updateHandle() // 检查更新 setInterval(() => { updateHandle() // 每一小时检查更新 }, 1000 * 60 * 60) createTray() regKey = new Registry({ hive: Registry.HKCU, key: '\\Software\\ZhiHuiXiang' }); // 获取当前脚本所在目录的绝对路径 const currentDirectory = __dirname; // 获取安装父目录 const parentDirectory = path.resolve(currentDirectory, '..'); const customFolderPath = path.join(parentDirectory, 'Downloads'); if (!fs.existsSync(customFolderPath)) { // 如果不存在,则创建文件夹 fs.mkdirSync(customFolderPath); } // 监听f12打开控制台 mainWindow.webContents.on('before-input-event', (event, input) => { // if (env === 'development') { if (input.key === 'F12') { mainWindow.webContents.openDevTools() } // } }); // 监听移动 mainWindow.on('move', () => { // 如果是全屏自动恢复到上次窗口大小 if (isMaximized) { mainWindow.unmaximize() isMaximized = false; } if (mainWindow.isMaximized()) { isMaximized = true; } }); // socket ipcMain.handle('startSignalr', (event, user) => { startSignalr(user) }); ipcMain.handle('offSignalr', (event) => { if (connection) { connection.off('ReceiveMessage'); connection.off('Operation'); connection.off('ForceExitRoom'); connection.off('AllLeave'); connection.off('ShowUser'); connection.off('RefreshView'); connection.off('UserJoined'); connection.off('UserLeave'); connection.off('OperAllMicr'); connection.off('OperMicr'); connection.off('OperCamera'); connection.off('ManagerRefresh'); connection.off('ApplyToSpeak'); connection.off('Watch'); connection.off('DriverList'); connection.off('SetDriver'); connection.off('ShowDriverList'); } }); ipcMain.handle('onStop', (event) => { if (connection) { mainWindow.webContents.send('changeLocalStorage', { isSignalr: false, }); connection.off('Invitation'); connection.off('ForceLogout'); connection.stop() connection = "" } }); ipcMain.handle('onInvoke', async (event, str, data) => { switch (str) { case 'sendChannelMsg': await connection.invoke(str, data.roomNum, data.msg) break; case 'sendOper': // 4:屏幕共享 await connection.invoke(str, data.roomNum, data.type) break; case 'getDrivers': // 获取某个人的设备列表 await connection.invoke(str, data.uid) break; case 'sendDrivers': // 发送设备列表给某个人 await connection.invoke(str, data.uid, data.driversJsonString) break; case 'setDrivers': // 设置某个人的设备列表 await connection.invoke(str, data.uid, data.driversJsonString) break; case 'joinChannel': // 设置某个人的设备列表 await connection.invoke(str, data.roomNum, data.enableMicr, data.enableCamera, data.isRoomManager || false) break; case 'levelChannel': // 设置某个人的设备列表 await connection.invoke(str, data.roomNum) break; } }); ipcMain.handle('onOtherSignalr', (event) => { if (connection) { // 邀请 connection.on("Invitation", (roomNum, roomName, InviterName) => { mainWindow.webContents.send('onOtherSignalr', { key: 'Invitation', roomNum, roomName, InviterName }); }); // 退出 connection.on("ForceLogout", (msg) => { mainWindow.webContents.send('onOtherSignalr', { key: 'ForceLogout', msg }); }); } }); ipcMain.handle('onSignalr', (event) => { if (connection) { // 聊天 connection.on("ReceiveMessage", (uid, userName, message, timestamp) => { mainWindow.webContents.send('onSignalr', { key: 'ReceiveMessage', uid, message, userName, timestamp }) }); // 扩展操作 connection.on("Operation", (type) => { mainWindow.webContents.send('onSignalr', { key: 'Operation', type }) }); // 移出会议 connection.on("ForceExitRoom", () => { mainWindow.webContents.send('onSignalr', { key: 'ForceExitRoom', }) }); // 全员离开房间 connection.on("AllLeave", () => { mainWindow.webContents.send('onSignalr', { key: 'AllLeave', }) }); // 全员看他 connection.on("ShowUser", (uid, uname, operUid, operUserName) => { mainWindow.webContents.send('onSignalr', { key: 'ShowUser', uid, uname, operUid, operUserName, }) }); // 更新视图模式 connection.on("RefreshView", (type) => { mainWindow.webContents.send('onSignalr', { key: 'RefreshView', type }) }); // 用户加入频道回调 connection.on("UserJoined", (user) => { mainWindow.webContents.send('onSignalr', { key: 'UserJoined', user, }) }); // 用户退出频道回调 connection.on("UserLeave", (uid) => { mainWindow.webContents.send('onSignalr', { key: 'UserLeave', uid, }) }); // 所有用户开闭麦 connection.on("OperAllMicr", (enableMicr, uid) => { mainWindow.webContents.send('onSignalr', { key: 'OperAllMicr', enableMicr, uid }) }); // 用户关闭开启麦克风 connection.on("OperMicr", (user, operUid) => { mainWindow.webContents.send('onSignalr', { key: 'OperMicr', user, operUid }) }); // 用户开启关闭摄像头 connection.on("OperCamera", (user, operUid) => { mainWindow.webContents.send('onSignalr', { key: 'OperCamera', user, operUid }) }); // 发言人用户信息刷新 connection.on("ManagerRefresh", (user, uid) => { mainWindow.webContents.send('onSignalr', { key: 'ManagerRefresh', user, uid }) }); // 申请发言 connection.on("ApplyToSpeak", (uid, uname) => { mainWindow.webContents.send('onSignalr', { key: 'ApplyToSpeak', uid, uname }) }); // 管理员查看随机用户 connection.on("Watch", (watchUids) => { mainWindow.webContents.send('onSignalr', { key: 'Watch', watchUids }) }); // 设备列表 connection.on("DriverList", (callerUid) => { mainWindow.webContents.send('onSignalr', { key: 'DriverList', callerUid }) }); // 设置设备 connection.on("SaveDriver", (driver) => { mainWindow.webContents.send('onSignalr', { key: 'SaveDriver', driver }) }); // 显示设备列表 connection.on("ShowDriverList", (driversJsonString) => { mainWindow.webContents.send('onSignalr', { key: 'ShowDriverList', driversJsonString }) }); } }); // 放大缩小退出窗口 ipcMain.handle('setViewStatus', async (event, status) => { switch (status) { case 'quit': await mainWindow.webContents.send('onQuit'); break; case 'maximize': mainWindow.maximize() break; case 'unmaximize': mainWindow.unmaximize() break; case 'minimize': mainWindow.minimize() break; case 'hide': mainWindow.hide() break; case 'show': mainWindow.show() mainWindow.focus(); break; } }); // 导出是否全屏 ipcMain.handle('getIsMaximized', () => { return mainWindow.isMaximized(); }); // 获取app路径 ipcMain.handle('getAppPath', () => { return app.getAppPath(); }); // 获取版本号 ipcMain.handle('getVersion', () => { return app.getVersion(); }); // 获取窗口是否显示 ipcMain.handle('isVisible', () => { return mainWindow.isVisible(); }); // 获取共享屏幕列表 ipcMain.handle('getSources', async () => { return await desktopCapturer.getSources({ types: ['screen'] }); }); // 复制文字 ipcMain.handle('setWriteText', (event, text) => { clipboard.writeText(text) }); // 退出 ipcMain.handle('quit', async (event, bool) => { if (bool) { quit() } else { await mainWindow.webContents.send('quitAndInstall'); } }); // 加入房间通知 ipcMain.handle('joinNotification', (event, user) => { mainWindow.show() mainWindow.focus(); }); // 通知下载包 ipcMain.handle('updateDownload', (event, data) => { if (data === '0') { // 取消下载 cancleDownloadUpdate() } else if (data === '1') { // 开始下载 downloadUpdate() } else if (data === '2') { // 下载完成 点击安装 quitAndInstall() } }); // 选择文件夹 ipcMain.handle('selectFilePath', async (event, data) => { const result = await dialog.showOpenDialog({ properties: ['openDirectory'] }); if (result.canceled) { } else { switch (data.key) { case 'shareFilesPath': case 'recordingFilesPath': mainWindow.webContents.send('onFilePath', result.filePaths[0] + '\\', data.key); break; default: mainWindow.webContents.send('downFile', { downFilePaths: result.filePaths[0] + '\\', fileName: data.fileName, filePath: data.filePath, }); break; } } }); // 获取桌面大小 ipcMain.handle('getWindowSize', (event, config) => { const primaryDisplay = screen.getPrimaryDisplay() const { width, height } = primaryDisplay.workAreaSize return { width, height } }); // 设置桌面应用基础属性 ipcMain.handle('setMainWindowSize', (event, config) => { // 设置最小窗口尺寸 mainWindow.setMinimumSize(config.width, config.height); // 设置最大尺寸 const primaryDisplay = screen.getPrimaryDisplay() const { width, height } = primaryDisplay.workAreaSize if (config.key === 'login') { mainWindow.setMaximumSize(config.width, config.height); } else { mainWindow.setMaximumSize(width, height); } // 设置窗口尺寸 mainWindow.setSize(config.width, config.height) // 设置窗口位置使其居中于当前屏幕 mainWindowCenter() }); // 写入注册表 ipcMain.handle('setRegistry', (event, uuid) => { regKey.create((err) => { if (err) { return; } // 设置键和值 regKey.set('uuid', Registry.REG_SZ, uuid, (err) => { if (err) { return; } }); }); }); // 读取注册表 ipcMain.handle('getRegistry', async () => { return new Promise((resolve, reject) => { regKey.get('uuid', (err, item) => { resolve(item) }) }); }); // 创建子窗口 ipcMain.handle('createChildWindow', (event, config) => { const child = new BrowserWindow({ parent: mainWindow, webPreferences: { contextIsolation: false, nodeIntegration: true, enableRemoteModule: true, nodeIntegrationInWorker: true, allowMediaDevices: true, preload: path.join(__dirname, 'preload.js') }, show: false, frame: false, backgroundColor: '#00000000', transparent: true, width: config.width, height: config.height, }) if (env === 'development') { // 开发 child.loadURL(config.url) } else { // 测试 | 生产 child.loadURL(`file://${path.join(__dirname, './dist/index.html')}#/${config.key}`); } childWindow[config.key] = child child.once('ready-to-show', () => { if (config.show) { childWindow[config.key].show() } childWindow[config.key].setAlwaysOnTop(true, 'screen-saver') childWindow[config.key].setSkipTaskbar(true) windowOperation(config) }) child.webContents.on('before-input-event', (event, input) => { // if (env === 'development') { if (input.key === 'F12') { child.webContents.openDevTools() } // } }); }); // 关闭子窗口 ipcMain.handle('closeChildWindow', (event, key) => { if (key === 'shareScreenWindow') { for (const k in childWindow) { if (childWindow[k]) { childWindow[k].close() childWindow[k] = "" } } mainWindow.setSkipTaskbar(false) mainWindow.setResizable(true) mainWindow.setAlwaysOnTop(false) } else { childWindow[key].close() childWindow[key] = "" } }); // 设置子窗口 ipcMain.handle('setChildWindow', (event, config) => { switch (config.key) { case 'shareScreenWindow': childWindow[config.key].setBounds({ width: config.width }) break; case 'chatSmallWindow': childWindow[config.key].setBounds({ height: config.height }) break; case 'noticeWindow': childWindow[config.key].setBounds({ width: config.width, height: config.height }) break; default: mainWindow.setMinimumSize(250, config.height); mainWindow.setMaximumSize(250, config.height); mainWindow.setSize(250, config.height) break; } }); // 隐藏显示子窗口 ipcMain.handle('setChildWindowShow', (event, config) => { if (config.key === 'noticeWindow') { if (config.bool) { childWindow[config.key].show() } else { if (childWindow[config.key].isVisible()) { childWindow[config.key].hide() } } } else { if (childWindow[config.key].isVisible()) { childWindow[config.key].hide() } else { childWindow[config.key].show() } } }); // 定位主窗口 ipcMain.handle('setPosition', (event, data) => { const display = screen.getDisplayMatching({ ...mainWindow.getBounds() }); const { width, height } = display.size switch (data) { case 'right': x = width - mainWindow.getSize()[0]; mainWindow.setPosition(x - 40, 40); break; default: break; } }); // 窗口通信 ipcMain.handle('windowHandleMessage', (event, data) => { if (childWindow[data.key]) { childWindow[data.key].webContents.send('windowHandleMessageCallBack', data) } }); } }); // 检测更新,在你想要检查更新的时候执行,renderer事件触发后的操作自行编写 function updateHandle() { autoUpdater.checkForUpdates() // autoUpdater.checkForUpdatesAndNotify().catch(); const message = { error: '检查更新出错', checking: '正在检查更新……', updateAva: '检测到新版本,正在下载……', updateNotAva: '已经是最新版本,不用更新' } autoUpdater.setFeedURL(updateJs.getUpdateUrl(env)) autoUpdater.autoDownload = false // 不自动下载安装包 autoUpdater.autoInstallOnAppQuit = false // 不自动安装 autoUpdater.on('error', function (error) { sendUpdateMessage(message.error) }) autoUpdater.on('checking-for-update', function () { sendUpdateMessage(message.checking) }) autoUpdater.on('update-available', function (info) { let messageStr = JSON.stringify({ type: '0' }) setTimeout(() => { sendUpdateMessage(messageStr) }, 5000) }) autoUpdater.on('update-not-available', function (info) { }) // 更新下载进度事件 autoUpdater.on('download-progress', function (progressObj) { let message = JSON.stringify({ type: '1', value: progressObj.percent }) sendUpdateMessage(message) }) autoUpdater.on('update-downloaded', function (event, releaseNotes, releaseName, releaseDate, updateUrl, quitAndUpdate) { let message = JSON.stringify({ type: '2', }) sendUpdateMessage(message) }) } // 通过main进程发送事件给renderer进程,提示更新信息 // type: 0 检测到需要更新(打开窗口) 1 正在下载更新包 function sendUpdateMessage(text) { mainWindow.webContents.send('update', text) } // 下载最新的包 function downloadUpdate() { autoUpdater.downloadUpdate(cancellationToken) } // 取消下载 function cancleDownloadUpdate() { autoUpdater.downloadUpdate(cancellationToken) // stop download cancellationToken.cancel() } // 完成下载立即安装 function quitAndInstall() { autoUpdater.quitAndInstall(); } function windowOperation(config) { const child = childWindow[config.key]; const display = screen.getDisplayMatching({ ...child.getBounds() }); const { width, height } = display.size let x, y; child.setResizable(false) switch (config.key) { case 'shareScreenWindow': x = Math.round((display.workArea.width - child.getSize()[0]) / 2); child.setPosition(x, 0); mainWindow.setSkipTaskbar(true) mainWindow.setResizable(false) mainWindow.setAlwaysOnTop(true, 'screen-saver') break; case 'chatSmallWindow': y = height - child.getSize()[1]; child.setPosition(40, y - 200); break; case 'noticeWindow': x = width - child.getSize()[0]; y = height - child.getSize()[1]; child.setPosition(x, y - 80); break; } } // 主窗口居中 function mainWindowCenter() { const display = screen.getDisplayMatching({ ...mainWindow.getBounds() }); const x = Math.round((display.workArea.width - mainWindow.getSize()[0]) / 2); const y = Math.round((display.workArea.height - mainWindow.getSize()[1]) / 2); mainWindow.setPosition(x, y); } const startSignalr = async (user) => { connection = new signalR.HubConnectionBuilder() .withUrl(`${env === 'development' ? 'http://192.168.2.9:5192' : 'https://meeting-api.23544.com/pc'}/session-manage`, { skipNegotiation: true, transport: signalR.HttpTransportType.WebSockets, accessTokenFactory: () => user.token }) .withAutomaticReconnect([0, 3000, 3000, 3000]) .build(); mainWindow.webContents.send('changeLocalStorage', { isSignalr: true, reconnect: true, }); connection.onreconnected(async () => { mainWindow.webContents.send('changeLocalStorage', { reconnect: true, }); }); connection.onreconnecting(async () => { onStart() mainWindow.webContents.send('changeLocalStorage', { reconnect: false, }); }); connection.start(); } const onStart = async () => { if (connection) { if (connection.state === signalR.HubConnectionState.Disconnected) { connection.start().then(() => { mainWindow.webContents.send('changeLocalStorage', { reconnect: true, }); }).catch((err) => { }); } if (connection.state !== signalR.HubConnectionState.Connected) { setTimeout(onStart, 3000); } } }