1- const { app, BrowserWindow, ipcMain } = require ( 'electron' )
1+ const { app, BrowserWindow, ipcMain, Tray , Menu , nativeImage } = require ( 'electron' )
22const path = require ( 'path' )
33const net = require ( 'net' )
44const { spawn } = require ( 'child_process' )
@@ -21,14 +21,132 @@ const store = new Store({
2121} ) ;
2222
2323let mainWindow
24+ let tray = null
2425const forwardingServers = new Map ( )
2526const reverseSshProcesses = new Map ( )
2627const sshClients = new Map ( )
2728
29+ // 创建系统托盘
30+ const createTray = ( ) => {
31+ // 获取托盘图标路径
32+ const getTrayIconPath = ( ) => {
33+ if ( process . platform === 'win32' ) {
34+ return path . join ( __dirname , '../resources/icons/32x32.png' )
35+ } else if ( process . platform === 'darwin' ) {
36+ return path . join ( __dirname , '../resources/icons/16x16.png' )
37+ } else {
38+ return path . join ( __dirname , '../resources/icons/32x32.png' )
39+ }
40+ }
41+
42+ const iconPath = getTrayIconPath ( )
43+
44+ // 创建托盘图标
45+ tray = new Tray ( iconPath )
46+
47+ // 设置托盘提示文本
48+ tray . setToolTip ( 'Port Forwarder - 端口转发工具' )
49+
50+ // 创建托盘右键菜单
51+ const contextMenu = Menu . buildFromTemplate ( [
52+ {
53+ label : '显示主窗口' ,
54+ click : ( ) => {
55+ if ( mainWindow ) {
56+ if ( mainWindow . isMinimized ( ) ) {
57+ mainWindow . restore ( )
58+ }
59+ mainWindow . show ( )
60+ mainWindow . focus ( )
61+ }
62+ }
63+ } ,
64+ {
65+ label : '隐藏到托盘' ,
66+ click : ( ) => {
67+ if ( mainWindow ) {
68+ mainWindow . hide ( )
69+ }
70+ }
71+ } ,
72+ { type : 'separator' } ,
73+ {
74+ label : '转发规则管理' ,
75+ click : ( ) => {
76+ if ( mainWindow ) {
77+ mainWindow . show ( )
78+ mainWindow . focus ( )
79+ }
80+ }
81+ } ,
82+ { type : 'separator' } ,
83+ {
84+ label : '关于' ,
85+ click : ( ) => {
86+ if ( mainWindow ) {
87+ mainWindow . webContents . executeJavaScript ( `
88+ alert('Port Forwarder v${ require ( '../package.json' ) . version } \\n\\n一个简单易用的端口转发工具\\n支持本地转发和SSH反向转发');
89+ ` )
90+ mainWindow . show ( )
91+ mainWindow . focus ( )
92+ }
93+ }
94+ } ,
95+ {
96+ label : '退出程序' ,
97+ click : ( ) => {
98+ app . isQuiting = true
99+ app . quit ( )
100+ }
101+ }
102+ ] )
103+
104+ // 设置托盘菜单
105+ tray . setContextMenu ( contextMenu )
106+
107+ // 双击托盘图标显示主窗口
108+ tray . on ( 'double-click' , ( ) => {
109+ if ( mainWindow ) {
110+ if ( mainWindow . isVisible ( ) ) {
111+ mainWindow . hide ( )
112+ } else {
113+ mainWindow . show ( )
114+ mainWindow . focus ( )
115+ }
116+ }
117+ } )
118+
119+ // 单击托盘图标(Windows 下)
120+ tray . on ( 'click' , ( ) => {
121+ if ( process . platform === 'win32' ) {
122+ if ( mainWindow ) {
123+ if ( mainWindow . isVisible ( ) ) {
124+ mainWindow . hide ( )
125+ } else {
126+ mainWindow . show ( )
127+ mainWindow . focus ( )
128+ }
129+ }
130+ }
131+ } )
132+ }
133+
28134const createWindow = ( ) => {
135+ // 获取图标路径
136+ const getIconPath = ( ) => {
137+ if ( process . platform === 'win32' ) {
138+ return path . join ( __dirname , '../resources/icons/icon.ico' )
139+ } else if ( process . platform === 'darwin' ) {
140+ return path . join ( __dirname , '../resources/icons/icon.icns' )
141+ } else {
142+ return path . join ( __dirname , '../resources/icons/512x512.png' )
143+ }
144+ }
145+
29146 mainWindow = new BrowserWindow ( {
30147 width : 1200 ,
31148 height : 800 ,
149+ icon : getIconPath ( ) ,
32150 webPreferences : {
33151 contextIsolation : true ,
34152 nodeIntegration : true ,
@@ -38,6 +156,8 @@ const createWindow = () => {
38156 } ,
39157 show : false ,
40158 backgroundColor : '#fff' ,
159+ // 添加窗口标题
160+ title : 'Port Forwarder' ,
41161 } )
42162
43163 // 配置 session
@@ -54,6 +174,25 @@ const createWindow = () => {
54174 mainWindow . show ( )
55175 } )
56176
177+ // 拦截窗口关闭事件
178+ mainWindow . on ( 'close' , ( event ) => {
179+ if ( ! app . isQuiting ) {
180+ event . preventDefault ( )
181+ mainWindow . hide ( )
182+
183+ // 首次隐藏时显示提示(可选)
184+ if ( ! mainWindow . hasShownTrayNotification ) {
185+ tray . displayBalloon ( {
186+ iconType : 'info' ,
187+ title : 'Port Forwarder' ,
188+ content : '应用程序已最小化到系统托盘,点击托盘图标可以重新打开'
189+ } )
190+ mainWindow . hasShownTrayNotification = true
191+ }
192+ return false
193+ }
194+ } )
195+
57196 if ( process . env . VITE_DEV_SERVER_URL ) {
58197 mainWindow . loadURL ( process . env . VITE_DEV_SERVER_URL )
59198 if ( process . env . NODE_ENV === 'development' ) {
@@ -84,6 +223,10 @@ app.whenReady().then(() => {
84223 console . error ( '清理缓存出错:' , error ) ;
85224 }
86225
226+ // 创建系统托盘
227+ createTray ( ) ;
228+
229+ // 创建主窗口
87230 createWindow ( ) ;
88231
89232 const forwardings = store . get ( 'forwardings' ) ;
@@ -128,9 +271,13 @@ if (!gotTheLock) {
128271}
129272
130273app . on ( 'window-all-closed' , ( ) => {
131- if ( process . platform !== 'darwin' ) {
132- app . quit ( )
274+ // 在 Windows 和 Linux 下,当所有窗口关闭时不退出应用
275+ // 因为我们希望应用继续在托盘中运行
276+ if ( process . platform === 'darwin' ) {
277+ // macOS 下的标准行为:关闭窗口但保持应用运行
278+ return
133279 }
280+ // Windows 和 Linux 下也不退出,让应用在托盘中继续运行
134281} )
135282
136283app . on ( 'activate' , ( ) => {
@@ -603,8 +750,16 @@ app.on('child-process-gone', (event, details) => {
603750} ) ;
604751
605752// 在退出时进行清理
606- app . on ( 'before-quit' , ( ) => {
753+ app . on ( 'before-quit' , ( event ) => {
754+ app . isQuiting = true
755+
607756 try {
757+ // 销毁托盘图标
758+ if ( tray ) {
759+ tray . destroy ( )
760+ tray = null
761+ }
762+
608763 // 关闭所有转发服务
609764 for ( const [ id , server ] of forwardingServers . entries ( ) ) {
610765 try {
0 commit comments