307 lines
8.1 KiB
JavaScript
307 lines
8.1 KiB
JavaScript
const { app, BrowserWindow, Tray, Menu, nativeImage, ipcMain } = require('electron')
|
||
const path = require('path')
|
||
const AutoLaunch = require('auto-launch')
|
||
const si = require('systeminformation')
|
||
|
||
// 保持对窗口对象的全局引用,如果不这样做,窗口将会在JavaScript垃圾回收时自动关闭
|
||
let mainWindow
|
||
let tray = null
|
||
|
||
// 创建开机启动管理器
|
||
let autoLauncher = new AutoLaunch({
|
||
name: 'Motioner',
|
||
path: process.execPath,
|
||
})
|
||
|
||
// 跟踪开机启动状态
|
||
let isAutoLaunchEnabled = false
|
||
|
||
// 存储GPU监控定时器
|
||
let gpuMonitorInterval = null
|
||
|
||
// 创建窗口的函数
|
||
function createWindow() {
|
||
// 创建浏览器窗口
|
||
mainWindow = new BrowserWindow({
|
||
width: 350,
|
||
height: 180,
|
||
webPreferences: {
|
||
nodeIntegration: true,
|
||
contextIsolation: false,
|
||
},
|
||
resizable: false,
|
||
// 创建无边框窗口
|
||
frame: false,
|
||
// 保持窗口始终在最前面
|
||
alwaysOnTop: true,
|
||
// 窗口透明
|
||
transparent: false,
|
||
backgroundMaterial: 'mica',
|
||
// 隐藏任务栏图标
|
||
skipTaskbar: true,
|
||
// 隐藏窗口切换时的显示
|
||
show: false,
|
||
// 隐藏窗口标题栏和任务栏显示
|
||
titleBarStyle: 'hidden',
|
||
// 隐藏窗口在Alt+Tab切换中显示
|
||
hiddenInMissionControl: true,
|
||
})
|
||
|
||
// 加载应用的index.html
|
||
mainWindow.loadFile('index.html')
|
||
|
||
// 处理最小化事件
|
||
ipcMain.on('window-minimize', () => {
|
||
mainWindow.hide()
|
||
})
|
||
|
||
// 处理关闭事件
|
||
ipcMain.on('window-close', () => {
|
||
app.quit()
|
||
})
|
||
|
||
// 处理开机启动设置事件
|
||
ipcMain.on('set-auto-launch', (event, enable) => {
|
||
if (enable) {
|
||
autoLauncher
|
||
.enable()
|
||
.then(() => {
|
||
console.log('开机启动已启用')
|
||
isAutoLaunchEnabled = true
|
||
// 重新创建托盘菜单以更新状态
|
||
createTray()
|
||
})
|
||
.catch(err => {
|
||
console.error('启用开机启动失败:', err)
|
||
})
|
||
} else {
|
||
autoLauncher
|
||
.disable()
|
||
.then(() => {
|
||
console.log('开机启动已禁用')
|
||
isAutoLaunchEnabled = false
|
||
// 重新创建托盘菜单以更新状态
|
||
createTray()
|
||
})
|
||
.catch(err => {
|
||
console.error('禁用开机启动失败:', err)
|
||
})
|
||
}
|
||
})
|
||
|
||
// 当窗口关闭时触发
|
||
mainWindow.on('closed', function () {
|
||
// 取消对窗口对象的引用,通常会存储窗口在数组中,这是删除相应元素的时候
|
||
mainWindow = null
|
||
// 清除GPU监控定时器
|
||
if (gpuMonitorInterval) {
|
||
clearInterval(gpuMonitorInterval)
|
||
gpuMonitorInterval = null
|
||
}
|
||
})
|
||
}
|
||
|
||
// 保存窗口原始位置和大小
|
||
let originalBounds = { width: 350, height: 150 }
|
||
|
||
// 当Electron完成初始化并准备创建浏览器窗口时调用此方法
|
||
app.whenReady().then(() => {
|
||
createWindow()
|
||
|
||
// 创建系统托盘
|
||
createTray()
|
||
|
||
// 启动GPU监控
|
||
startGpuMonitoring()
|
||
|
||
// 显示窗口
|
||
mainWindow.show()
|
||
|
||
// 监听窗口失焦事件
|
||
mainWindow.on('blur', () => {
|
||
// 2秒后缩小窗口
|
||
setTimeout(() => {
|
||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||
// 保存当前窗口位置和大小
|
||
const bounds = mainWindow.getBounds()
|
||
originalBounds = { ...bounds }
|
||
|
||
// 缩小窗口到小球大小,以顶部中心点为基准
|
||
const newX = Math.round(bounds.x + bounds.width / 2 - 20)
|
||
const newY = bounds.y
|
||
|
||
try {
|
||
mainWindow.setBounds({
|
||
x: newX,
|
||
y: newY,
|
||
width: 150,
|
||
height: 210,
|
||
})
|
||
// 移除backgroundMaterial带来的阴影
|
||
mainWindow.setBackgroundMaterial('none')
|
||
mainWindow.setOpacity(0.3)
|
||
} catch (error) {
|
||
console.error('设置窗口边界时出错:', error)
|
||
}
|
||
}
|
||
}, 1000)
|
||
|
||
// 通知渲染进程窗口已失焦
|
||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||
mainWindow.webContents.send('window-blurred')
|
||
}
|
||
})
|
||
|
||
// 监听窗口聚焦事件
|
||
mainWindow.on('focus', () => {
|
||
// 恢复窗口到原始大小和位置
|
||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||
try {
|
||
mainWindow.setBounds({
|
||
x: originalBounds.x,
|
||
y: originalBounds.y,
|
||
width: originalBounds.width,
|
||
height: originalBounds.height,
|
||
})
|
||
// 恢复backgroundMaterial带来的阴影
|
||
mainWindow.setBackgroundMaterial('mica')
|
||
mainWindow.setOpacity(1)
|
||
} catch (error) {
|
||
console.error('设置窗口边界时出错:', error)
|
||
}
|
||
}
|
||
|
||
// 通知渲染进程窗口已聚焦
|
||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||
mainWindow.webContents.send('window-focused')
|
||
}
|
||
})
|
||
|
||
app.on('activate', function () {
|
||
// 在macOS上,当单击dock图标并且没有其他窗口打开时,通常在应用程序中重新创建一个窗口
|
||
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
||
})
|
||
})
|
||
|
||
// 当所有窗口都关闭时退出应用
|
||
app.on('window-all-closed', function () {
|
||
// 在macOS上,应用程序及其菜单栏通常会保持活动状态,直到用户明确退出
|
||
if (process.platform !== 'darwin') app.quit()
|
||
})
|
||
|
||
// 创建系统托盘
|
||
function createTray() {
|
||
// 创建托盘图标
|
||
const iconPath = path.join(__dirname, 'assets/icon.png')
|
||
let icon
|
||
try {
|
||
icon = nativeImage.createFromPath(iconPath)
|
||
} catch (error) {
|
||
// 如果找不到图标文件,使用默认图标
|
||
icon = nativeImage.createEmpty()
|
||
}
|
||
|
||
tray = new Tray(icon)
|
||
|
||
// 创建上下文菜单
|
||
const contextMenu = Menu.buildFromTemplate([
|
||
{
|
||
label: '显示',
|
||
click: () => {
|
||
mainWindow.show()
|
||
},
|
||
},
|
||
{
|
||
label: '开机启动',
|
||
type: 'checkbox',
|
||
checked: isAutoLaunchEnabled,
|
||
click: () => {
|
||
if (isAutoLaunchEnabled) {
|
||
autoLauncher
|
||
.disable()
|
||
.then(() => {
|
||
console.log('开机启动已禁用')
|
||
isAutoLaunchEnabled = false
|
||
// 重新创建托盘菜单以更新状态
|
||
createTray()
|
||
})
|
||
.catch(err => {
|
||
console.error('禁用开机启动失败:', err)
|
||
})
|
||
} else {
|
||
autoLauncher
|
||
.enable()
|
||
.then(() => {
|
||
console.log('开机启动已启用')
|
||
isAutoLaunchEnabled = true
|
||
// 重新创建托盘菜单以更新状态
|
||
createTray()
|
||
})
|
||
.catch(err => {
|
||
console.error('启用开机启动失败:', err)
|
||
})
|
||
}
|
||
},
|
||
},
|
||
{
|
||
label: '退出',
|
||
click: () => {
|
||
app.quit()
|
||
},
|
||
},
|
||
])
|
||
|
||
tray.setContextMenu(contextMenu)
|
||
|
||
// 点击托盘图标显示窗口
|
||
tray.on('click', () => {
|
||
mainWindow.show()
|
||
})
|
||
}
|
||
|
||
// 启动GPU监控
|
||
function startGpuMonitoring() {
|
||
// 每5秒获取一次GPU信息,减少性能开销
|
||
gpuMonitorInterval = setInterval(async () => {
|
||
try {
|
||
// 尝试使用nvidia-smi命令获取GPU信息
|
||
const { exec } = require('child_process')
|
||
exec('nvidia-smi --query-gpu=utilization.gpu,memory.used,memory.total --format=csv,noheader,nounits', (error, stdout, stderr) => {
|
||
if (error) {
|
||
console.error('执行nvidia-smi时出错:', error)
|
||
return
|
||
}
|
||
|
||
if (stderr) {
|
||
console.error('nvidia-smi stderr:', stderr)
|
||
return
|
||
}
|
||
|
||
// 解析输出
|
||
const data = stdout
|
||
.trim()
|
||
.split(',')
|
||
.map(item => parseInt(item.trim()))
|
||
if (data.length >= 3) {
|
||
const gpuInfo = [
|
||
{
|
||
name: 'NVIDIA GPU',
|
||
utilizationGpu: data[0],
|
||
memoryUsed: data[1],
|
||
memoryTotal: data[2],
|
||
vram: data[2],
|
||
},
|
||
]
|
||
|
||
// 发送GPU信息到渲染进程
|
||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||
mainWindow.webContents.send('gpu-info', gpuInfo)
|
||
}
|
||
}
|
||
})
|
||
} catch (error) {
|
||
console.error('获取GPU信息时出错:', error)
|
||
}
|
||
}, 200) // 每5秒更新一次
|
||
}
|