const { app, BrowserWindow, screen, Tray, nativeImage, Menu, ipcMain, clipboard, dialog, crashReporter, desktopCapturer, powerSaveBlocker, net } = 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 { setTimeout, setInterval } = require('timers'); const cancellationToken = new CancellationToken() app.allowRendererProcessReuse = false; let mainWindow = null; let childWindow = {} let isMaximized = false; let env = 'development'; //development production xy let regKey; let connection = null; let startNumber = 0; let buildStatus = false; //true 打包开发版本 false 本地开发 powerSaveBlocker.start('prevent-display-sleep') const id = powerSaveBlocker.start('prevent-display-sleep') powerSaveBlocker.stop(id) 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') { if (buildStatus) { this.loadURL('http://192.168.2.9:8827/'); } else { this.loadURL('http://localhost:3000'); } } else { this.loadURL('https://meeting-api.23544.com/') } // this.loadFile(path.resolve(__dirname, './dist/index.html')) this.once('ready-to-show', () => { this.show(); }); } } function quit() { app.quit() } let tray; // 检查网络状态 function checkNetworkStatus() { if (!net.isOnline()) { dialog.showErrorBox(`${env === 'xy' ? '湖北襄阳四中教研平台' : '智汇享'}-网络连接错误', '当前无网络连接,请检查您的网络设置。`); app.quit(); return false; } return true; } function createTray() { const iconPath = `${__dirname}/src/assets/${updateJs.getIcon(env)}.png`; const trayIcon = nativeImage.createFromPath(iconPath); tray = new Tray(trayIcon); tray.setToolTip(updateJs.getTitle(env)); tray.on('click', () => { mainWindow.webContents.send('isOpenWindows'); }); } function createWindow() { mainWindow = new AppWindow(); mainWindow.focus(); mainWindow.hookWindowMessage(278, function (e) { mainWindow.setEnabled(false);//窗口禁用 setTimeout(() => { mainWindow.setEnabled(true);//窗口启用 }, 100); return true; }) } const additionalData = { myKey: 'myValue' } app.on('ready', () => { // 检查网络状态 if (!checkNetworkStatus()) { return; } // const gotTheLock = true const gotTheLock = app.requestSingleInstanceLock(additionalData) if (gotTheLock) { app.getPath('crashDumps') crashReporter.start({ uploadToServer: false, ignoreSystemCrashHandler: false }) // if (!buildStatus) { // Object.defineProperty(app, 'isPackaged', { // get() { // return true // } // }) // autoUpdater.updateConfigPath = path.join('latest.yml') // } createWindow() 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 (input.key === 'F12') { mainWindow.webContents.openDevTools() } }); mainWindow.on('focus', () => { mainWindow.show() }); mainWindow.on('maximize', () => { mainWindow.show() }); // 监听移动 mainWindow.on('move', () => { // 如果是全屏自动恢复到上次窗口大小 if (isMaximized) { mainWindow.unmaximize() isMaximized = false; } if (mainWindow.isMaximized()) { isMaximized = true; } }); ipcMain.handle('startLoad', (event) => { if (startNumber === 0) { updateHandle() // 检查更新 setInterval(() => { autoUpdater.checkForUpdates() }, 1000 * 60 * 60) createTray() startNumber++ } }); // 更新 ipcMain.handle('updateHandle', () => { autoUpdater.checkForUpdates() }); // 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'); connection.off('ModifyNickName'); connection.off('JoinChannelCallback'); connection.off('ExitSharedScreen'); connection.off('SetSpeaker'); connection.off('ReceivedOperation'); connection.off('StopedSharedScreen'); } }); 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': await connection.invoke(str, data.roomNum, data.contentString) 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; case 'SetSpeakerCallback': // 发言人设置成功 await connection.invoke(str, data) break; case 'sendOper2User': // 扩展参数 await connection.invoke(str, data.uid, data.contentString) 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", (contentString) => { mainWindow.webContents.send('onSignalr', { key: 'Operation', contentString }) }); // 移出会议 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 }) }); // 修改用户名称 connection.on("ModifyNickName", (uid, nickName) => { mainWindow.webContents.send('onSignalr', { key: 'ModifyNickName', uid, nickName }) }); // 加入房间回调 connection.on("JoinChannelCallback", (isSuccess) => { mainWindow.webContents.send('onSignalr', { key: 'JoinChannelCallback', isSuccess, }) }); // 退出共享 connection.on("ExitSharedScreen", () => { mainWindow.webContents.send('onSignalr', { key: 'ExitSharedScreen' }) }); // 设置发言人 connection.on("SetSpeaker", (RoomManagerInputDTO) => { mainWindow.webContents.send('onSignalr', { key: 'SetSpeaker', RoomManagerInputDTO }) }); // 扩展参数 connection.on("ReceivedOperation", (contentString) => { mainWindow.webContents.send('onSignalr', { key: 'ReceivedOperation', contentString }) }); // 共享人取消共享屏幕 connection.on("StopedSharedScreen", (ScreenShareId) => { mainWindow.webContents.send('onSignalr', { key: 'StopedSharedScreen', ScreenShareId }) }); } }); // 放大缩小退出窗口 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(); mainWindow.setSkipTaskbar(false) mainWindow.setResizable(true) mainWindow.setAlwaysOnTop(false) break; } }); // 导出是否全屏 ipcMain.handle('getIsMaximized', () => { return mainWindow.isMaximized(); }); // 获取app路径 ipcMain.handle('getAppPath', () => { return app.getAppPath(); }); // 获取版本号 ipcMain.handle('getVersion', () => { return app.getVersion(); }); // 获取环境 ipcMain.handle('getEnv', () => { return env; }); // 获取窗口是否显示 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() } else if (data === '3') { // 打开弹窗 let message = JSON.stringify({ type: '3', }) sendUpdateMessage(message) } }); // 选择文件夹 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) => { if (config.width === 250) { const contextMenu = Menu.buildFromTemplate([ { label: '退出', click: () => quit(), }, ]); tray.setContextMenu(contextMenu); mainWindow.setSkipTaskbar(true) mainWindow.setResizable(false) mainWindow.setAlwaysOnTop(true, 'screen-saver') } else { const contextMenu = Menu.buildFromTemplate([ { label: '打开', click: () => mainWindow.webContents.send('isOpenWindows'), }, { label: '最小化到系统托盘', click: () => mainWindow.hide(), }, { label: '退出', click: () => quit(), }, ]); tray.setContextMenu(contextMenu); } // 设置最小窗口尺寸 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') { // 开发 if (buildStatus) { child.loadURL(`http://192.168.2.9:8827/#/${config.key}`); // child.loadURL(`file://${path.join(__dirname, './dist/index.html')}#/${config.key}`); } else { child.loadURL(config.url) } } else { // 测试 | 生产 child.loadURL(`https://meeting-api.23544.com/#/${config.key}`); // child.loadURL(`file://${path.join(__dirname, './dist/index.html')}#/${config.key}`); } child.hookWindowMessage(278, function (e) { child.setEnabled(false);//窗口禁用 setTimeout(() => { child.setEnabled(true);//窗口启用 }, 100); return true; }) 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 (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] = "" } } } 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 (config.bool) { 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.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(error) }) autoUpdater.on('checking-for-update', function () { sendUpdateMessage(message.checking) }) autoUpdater.on('update-available', function (info) { let messageStr = JSON.stringify({ type: '0' }) sendUpdateMessage(messageStr) mainWindow.webContents.send('changeLocalStorage', { isUpdate: true, }); }) autoUpdater.on('update-not-available', function (info) { mainWindow.webContents.send('changeLocalStorage', { isUpdate: false, }); }) // 更新下载进度事件 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); 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); } } }