Files
motioner/main.js
2025-09-11 22:17:19 +08:00

307 lines
8.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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秒更新一次
}