Skip to content

Commit 52d0050

Browse files
LuojiongLuojiong
authored andcommitted
✨ add mini menu and Desktop Icon
1 parent e3ec42c commit 52d0050

File tree

17 files changed

+253
-5
lines changed

17 files changed

+253
-5
lines changed

TRAY_FEATURES.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# 系统托盘功能说明
2+
3+
## 🎯 功能概述
4+
5+
Port Forwarder 现在支持系统托盘功能,允许应用程序在后台运行而不占用任务栏空间。
6+
7+
## ✨ 主要特性
8+
9+
### 1. **最小化到托盘**
10+
- 点击窗口关闭按钮(X)时,应用不会退出
11+
- 而是最小化到系统托盘,继续在后台运行
12+
- 首次最小化时会显示气泡提示
13+
14+
### 2. **托盘图标交互**
15+
- **单击托盘图标**(Windows):显示/隐藏主窗口
16+
- **双击托盘图标**:显示/隐藏主窗口
17+
- **右键托盘图标**:显示上下文菜单
18+
19+
### 3. **托盘右键菜单**
20+
- 📖 **显示主窗口**:恢复并显示应用主界面
21+
- 🔽 **隐藏到托盘**:手动隐藏窗口到托盘
22+
- ⚙️ **转发规则管理**:直接打开主界面管理转发规则
23+
- ℹ️ **关于**:显示应用程序版本信息
24+
- 🚪 **退出程序**:完全退出应用程序
25+
26+
## 🔧 使用方法
27+
28+
### 开始使用
29+
1. 启动 Port Forwarder 应用
30+
2. 系统托盘会自动显示应用图标
31+
3. 正常使用应用程序的所有功能
32+
33+
### 最小化到托盘
34+
**方法1:** 点击窗口右上角的关闭按钮(X)
35+
**方法2:** 右键托盘图标 → 选择"隐藏到托盘"
36+
37+
### 从托盘恢复窗口
38+
**方法1:** 单击托盘图标(Windows)
39+
**方法2:** 双击托盘图标
40+
**方法3:** 右键托盘图标 → 选择"显示主窗口"
41+
42+
### 完全退出应用
43+
**方法1:** 右键托盘图标 → 选择"退出程序"
44+
**方法2:** 在主窗口中使用 Alt+F4(Windows)
45+
46+
## 🎨 图标说明
47+
48+
- **托盘图标**:32x32 像素 PNG 格式(Windows/Linux)
49+
- **macOS 托盘图标**:16x16 像素 PNG 格式
50+
- **提示文本**:悬停托盘图标显示"Port Forwarder - 端口转发工具"
51+
52+
## ⚡ 优势
53+
54+
1. **后台运行**:转发规则继续工作,即使窗口关闭
55+
2. **快速访问**:一键恢复窗口,不需要重新启动
56+
3. **节省空间**:不占用任务栏位置
57+
4. **系统集成**:完美融入 Windows 系统托盘
58+
59+
## 🛠️ 技术实现
60+
61+
- 使用 Electron Tray API
62+
- 跨平台支持(Windows、macOS、Linux)
63+
- 自动图标适配不同平台要求
64+
- 智能窗口状态管理
65+
- 优雅的应用生命周期处理
66+
67+
## 📝 注意事项
68+
69+
- 转发规则在托盘运行时保持活跃
70+
- 应用程序数据和设置会自动保存
71+
- 托盘功能支持所有现有的端口转发功能
72+
- 建议将应用设置为开机自启动以便常驻使用

electron/main.js

Lines changed: 159 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const { app, BrowserWindow, ipcMain } = require('electron')
1+
const { app, BrowserWindow, ipcMain, Tray, Menu, nativeImage } = require('electron')
22
const path = require('path')
33
const net = require('net')
44
const { spawn } = require('child_process')
@@ -21,14 +21,132 @@ const store = new Store({
2121
});
2222

2323
let mainWindow
24+
let tray = null
2425
const forwardingServers = new Map()
2526
const reverseSshProcesses = new Map()
2627
const 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+
28134
const 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

130273
app.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

136283
app.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 {

package.json

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "electron-port-forwarder",
33
"private": true,
4-
"version": "1.0.2",
4+
"version": "1.0.3",
55
"main": "dist-electron/main.js",
66
"description": "A port forwarding tool built with Electron",
77
"author": {
@@ -53,7 +53,25 @@
5353
"buildResources": "build",
5454
"output": "release/${version}"
5555
},
56+
"files": [
57+
"dist/**/*",
58+
"dist-electron/**/*",
59+
"node_modules/**/*"
60+
],
61+
"extraResources": [
62+
{
63+
"from": "src/assets/icons",
64+
"to": "icons",
65+
"filter": ["**/*"]
66+
},
67+
{
68+
"from": "build",
69+
"to": ".",
70+
"filter": ["16x16.png", "32x32.png"]
71+
}
72+
],
5673
"win": {
74+
"icon": "build/icon.ico",
5775
"target": [
5876
{
5977
"target": "nsis",
@@ -66,6 +84,7 @@
6684
"requestedExecutionLevel": "asInvoker"
6785
},
6886
"linux": {
87+
"icon": "build/icon.png",
6988
"target": [
7089
{
7190
"target": "AppImage",
@@ -92,6 +111,7 @@
92111
"category": "Network"
93112
},
94113
"mac": {
114+
"icon": "build/icon.icns",
95115
"target": [
96116
{
97117
"target": "dmg",

resources/icons/16x16.png

800 Bytes
Loading

resources/icons/32x32.png

2.19 KB
Loading

src/assets/icons/1024x1024.png

1.09 MB
Loading

src/assets/icons/128x128.png

17.4 KB
Loading

src/assets/icons/16x16.png

800 Bytes
Loading

src/assets/icons/24x24.png

1.42 KB
Loading

src/assets/icons/256x256.png

61.6 KB
Loading

0 commit comments

Comments
 (0)