-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.go
More file actions
296 lines (248 loc) · 7.44 KB
/
Copy pathmain.go
File metadata and controls
296 lines (248 loc) · 7.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
package main
import (
"fmt"
"net"
"os"
"os/exec"
"path/filepath"
"runtime"
"time"
"github.com/rodrigocfd/windigo/ui"
"github.com/rodrigocfd/windigo/co"
"github.com/rodrigocfd/windigo/win"
)
var (
mainWnd *ui.Main
tabMain *ui.Tab
chkAuto *ui.CheckBox
btnBatch *ui.Button
btnClear *ui.Button
btnOpenLog *ui.Button
btnQuit *ui.Button
lstDrafts *ui.ListView
txtLog *ui.Edit
txtTemplate *ui.Edit
btnSaveTemplate *ui.Button
autoSendEnabled = false
logFile *os.File
)
func GetAutoSend() bool {
return autoSendEnabled
}
func SetAutoSend(b bool) {
autoSendEnabled = b
}
func main() {
runtime.LockOSThread()
// 1. Single-instance check (prevent duplicate processes and restore existing instance)
if hwnd, ok := win.FindWindow(win.ClassNameNone(), "Code-Shield Notifier"); ok {
hwnd.ShowWindow(co.SW_SHOW)
hwnd.ShowWindow(co.SW_RESTORE)
hwnd.SetForegroundWindow()
return
}
// Initialize log file
if homeDir, err := os.UserHomeDir(); err == nil {
logPath := filepath.Join(homeDir, ".code-shield-notifier.log")
if f, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err == nil {
logFile = f
}
}
LogMessage("==============================================")
LogMessage("Code-Shield Notifier starting...")
listener, err := net.Listen("tcp", "0.0.0.0:8081")
if err != nil {
errMsg := fmt.Sprintf("无法绑定端口 8081 (报错: %v)。\nCode-Shield 通知工具可能已经在运行中,或者端口被占用。\n\nFailed to bind port 8081. Is notifier already running?", err)
LogMessage("Error: " + errMsg)
win.HWND(0).MessageBox(errMsg, "启动失败 / Startup Failed", co.MB_OK|co.MB_ICONERROR)
if logFile != nil {
logFile.Close()
}
return
}
mainWnd = ui.NewMain(
ui.OptsMain().
Title("Code-Shield Notifier").
Size(640, 560),
)
setupControls()
setupEvents()
go StartHTTPServer(listener)
mainWnd.RunAsMain()
}
func setupControls() {
tabMain = ui.NewTab(mainWnd, ui.OptsTab().
Titles("常规态与日志", "邮件模版设置").
Position(10, 10).
Size(620, 540))
tabOverview := tabMain.Item(0).Child()
chkAuto = ui.NewCheckBox(tabOverview, ui.OptsCheckBox().
Text("自动发送").
Position(10, 10).
Size(80, 20))
btnBatch = ui.NewButton(tabOverview, ui.OptsButton().
Text("批量发送等待中的邮件").
Position(100, 8).
Width(150).Height(24))
btnClear = ui.NewButton(tabOverview, ui.OptsButton().
Text("清空全部记录").
Position(260, 8).
Width(100).Height(24))
btnOpenLog = ui.NewButton(tabOverview, ui.OptsButton().
Text("打开日志文件").
Position(370, 8).
Width(110).Height(24))
btnQuit = ui.NewButton(tabOverview, ui.OptsButton().
Text("退出程序").
Position(490, 8).
Width(100).Height(24))
lstDrafts = ui.NewListView(tabOverview, ui.OptsListView().
Position(10, 40).
Size(590, 310).
Column("状态", 60).
Column("收件人", 150).
Column("主题", 220).
Column("时间", 120))
txtLog = ui.NewEdit(tabOverview, ui.OptsEdit().
Position(10, 360).
Width(590).Height(140).
CtrlStyle(co.ES_MULTILINE | co.ES_READONLY | co.ES_AUTOVSCROLL | co.ES_NOHIDESEL).
WndStyle(co.WS_CHILD | co.WS_VISIBLE | co.WS_TABSTOP | co.WS_GROUP | co.WS_VSCROLL))
tabSettings := tabMain.Item(1).Child()
txtTemplate = ui.NewEdit(tabSettings, ui.OptsEdit().
Position(10, 10).
Width(590).Height(450).
CtrlStyle(co.ES_MULTILINE | co.ES_WANTRETURN | co.ES_AUTOVSCROLL | co.ES_NOHIDESEL).
WndStyle(co.WS_CHILD | co.WS_VISIBLE | co.WS_TABSTOP | co.WS_GROUP | co.WS_VSCROLL).
Text(LoadTemplate()))
btnSaveTemplate = ui.NewButton(tabSettings, ui.OptsButton().
Text("保存模版").
Position(490, 470).
Width(110).Height(26))
}
func setupEvents() {
var nid win.NOTIFYICONDATA
const WM_TRAYICON = co.WM_APP + 1
taskbarCreatedMsg, _ := win.RegisterWindowMessage("TaskbarCreated")
mainWnd.On().WmCreate(func(p ui.WmCreate) int {
chkAuto.SetCheck(GetAutoSend())
nid.SetCbSize()
nid.HWnd = mainWnd.Hwnd()
nid.UID = 100
nid.UFlags = co.NIF_MESSAGE | co.NIF_ICON | co.NIF_TIP
nid.UCallbackMessage = WM_TRAYICON
hInst, _ := win.GetModuleHandle("")
// Try to load custom icon (ID 1 generated by rsrc)
hIcon, err := hInst.LoadIcon(win.IconResId(1))
if err != nil || hIcon == 0 {
// Fallback to default application icon
hIcon, _ = win.HINSTANCE(0).LoadIcon(win.IconResId(uint16(co.IDI_APPLICATION)))
}
nid.HIcon = hIcon
nid.SetSzTip("Code-Shield Notifier (Running)")
win.Shell_NotifyIcon(co.NIM_ADD, &nid)
return 0
})
chkAuto.On().BnClicked(func() {
state := chkAuto.IsChecked()
SetAutoSend(state)
if state {
LogMessage("Auto-send enabled.")
} else {
LogMessage("Auto-send disabled.")
}
})
btnBatch.On().BnClicked(func() {
LogMessage("Checking Outlook Drafts for batch sending...")
go func() {
defer func() {
if r := recover(); r != nil {
LogMessage(fmt.Sprintf("Batch Send Panic: %v", r))
}
}()
sentCount, err := BatchSendOutlookDrafts()
if err != nil {
LogMessage(fmt.Sprintf("Batch send error: %v", err))
} else {
LogMessage(fmt.Sprintf("Batch send complete: Sent %d matching emails from Outlook Drafts.", sentCount))
}
}()
})
btnClear.On().BnClicked(func() {
if mainWnd != nil && mainWnd.Hwnd() != 0 {
mainWnd.UiThread(func() {
lstDrafts.DeleteAllItems()
})
}
LogMessage("Local GUI draft history cleared.")
})
btnOpenLog.On().BnClicked(func() {
if homeDir, err := os.UserHomeDir(); err == nil {
logPath := filepath.Join(homeDir, ".code-shield-notifier.log")
exec.Command("cmd", "/c", "start", "", logPath).Start()
}
})
btnQuit.On().BnClicked(func() {
mainWnd.Hwnd().DestroyWindow()
})
btnSaveTemplate.On().BnClicked(func() {
err := SaveTemplate(txtTemplate.Text())
if err != nil {
win.HWND(mainWnd.Hwnd()).MessageBox("保存失败: "+err.Error(), "错误", co.MB_OK|co.MB_ICONERROR)
} else {
win.HWND(mainWnd.Hwnd()).MessageBox("模版保存成功!\n注意:当前修改立即生效。", "保存成功", co.MB_OK|co.MB_ICONINFORMATION)
}
})
mainWnd.On().Wm(WM_TRAYICON, func(p ui.Wm) uintptr {
lParam := co.WM(p.LParam)
if lParam == co.WM_LBUTTONDBLCLK || lParam == co.WM_LBUTTONUP {
mainWnd.Hwnd().ShowWindow(co.SW_SHOW)
mainWnd.Hwnd().ShowWindow(co.SW_RESTORE)
mainWnd.Hwnd().SetForegroundWindow()
}
return 0
})
mainWnd.On().Wm(taskbarCreatedMsg, func(p ui.Wm) uintptr {
win.Shell_NotifyIcon(co.NIM_ADD, &nid)
return 0
})
mainWnd.On().Wm(co.WM_CLOSE, func(p ui.Wm) uintptr {
mainWnd.Hwnd().ShowWindow(co.SW_HIDE)
return 0 // intercept and don't destroy
})
mainWnd.On().WmDestroy(func() {
win.Shell_NotifyIcon(co.NIM_DELETE, &nid)
if logFile != nil {
logFile.Close()
}
})
}
func LogMessage(msg string) {
fmt.Println(msg)
if logFile != nil {
t := time.Now().Format("2006-01-02 15:04:05")
logFile.WriteString(fmt.Sprintf("[%s] %s\r\n", t, msg))
logFile.Sync()
}
if mainWnd == nil || mainWnd.Hwnd() == 0 {
return
}
mainWnd.UiThread(func() {
t := time.Now().Format("15:04:05")
current := txtLog.Text()
runes := []rune(current)
if len(runes) > 10000 {
// 清理的时候,只保留日志的最后 1000 个字符, 这样避免频繁截断
current = string(runes[:1000])
}
txtLog.SetText(fmt.Sprintf("[%s] %s\r\n%s", t, msg, current))
})
}
func AddDraftLogToView(status, to, subject, timeStr string) {
if mainWnd == nil || mainWnd.Hwnd() == 0 {
return
}
mainWnd.UiThread(func() {
lstDrafts.AddItem(status, to, subject, timeStr)
})
}