278 lines
6.5 KiB
Go
278 lines
6.5 KiB
Go
//go:build windows
|
||
|
||
package main
|
||
|
||
import (
|
||
"fmt"
|
||
"log"
|
||
"os"
|
||
"runtime"
|
||
"sync"
|
||
"syscall"
|
||
"unsafe"
|
||
)
|
||
|
||
// Win32 常量
|
||
const (
|
||
WS_OVERLAPPEDWINDOW = 0x00CF0000
|
||
WS_CHILD = 0x40000000
|
||
WS_VISIBLE = 0x10000000
|
||
WS_VSCROLL = 0x00200000
|
||
WS_HSCROLL = 0x00100000
|
||
WS_EX_CLIENTEDGE = 0x00000200
|
||
|
||
ES_MULTILINE = 0x0004
|
||
ES_READONLY = 0x0800
|
||
ES_AUTOVSCROLL = 0x0040
|
||
ES_AUTOHSCROLL = 0x0080
|
||
|
||
WM_DESTROY = 0x0002
|
||
WM_SIZE = 0x0005
|
||
WM_SETFONT = 0x0030
|
||
WM_CTLCOLOREDIT = 0x0133
|
||
EM_SETSEL = 0x00B1
|
||
EM_REPLACESEL = 0x00C2
|
||
|
||
SW_SHOW = 5
|
||
IDC_ARROW = 32512
|
||
CW_USEDEFAULT = ^0x7FFFFFFF
|
||
FIXED_PITCH = 1
|
||
FF_MODERN = 48
|
||
DEFAULT_CHARSET = 1
|
||
FW_NORMAL = 400
|
||
MB_ICONERROR = 0x00000010
|
||
)
|
||
|
||
// 暗色主题 (BGR格式)
|
||
const (
|
||
colorBg = 0x001E1E1E
|
||
colorFg = 0x00D4D4D4
|
||
)
|
||
|
||
type tWNDCLASSEX struct {
|
||
CbSize uint32
|
||
Style uint32
|
||
LpfnWndProc uintptr
|
||
CbClsExtra int32
|
||
CbWndExtra int32
|
||
HInstance uintptr
|
||
HIcon uintptr
|
||
HCursor uintptr
|
||
HbrBackground uintptr
|
||
LpszMenuName *uint16
|
||
LpszClassName *uint16
|
||
HIconSm uintptr
|
||
}
|
||
|
||
type tRECT struct {
|
||
Left int32
|
||
Top int32
|
||
Right int32
|
||
Bottom int32
|
||
}
|
||
|
||
type tMSG struct {
|
||
HWnd uintptr
|
||
Message uint32
|
||
_ uint32
|
||
WParam uintptr
|
||
LParam uintptr
|
||
Time uint32
|
||
PtX int32
|
||
PtY int32
|
||
}
|
||
|
||
var (
|
||
modUser32 = syscall.NewLazyDLL("user32.dll")
|
||
modKernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||
modGdi32 = syscall.NewLazyDLL("gdi32.dll")
|
||
|
||
pGetModuleHandleW = modKernel32.NewProc("GetModuleHandleW")
|
||
pLoadCursorW = modUser32.NewProc("LoadCursorW")
|
||
pRegisterClassExW = modUser32.NewProc("RegisterClassExW")
|
||
pCreateWindowExW = modUser32.NewProc("CreateWindowExW")
|
||
pShowWindow = modUser32.NewProc("ShowWindow")
|
||
pUpdateWindow = modUser32.NewProc("UpdateWindow")
|
||
pGetMessageW = modUser32.NewProc("GetMessageW")
|
||
pTranslateMessage = modUser32.NewProc("TranslateMessage")
|
||
pDispatchMessageW = modUser32.NewProc("DispatchMessageW")
|
||
pDefWindowProcW = modUser32.NewProc("DefWindowProcW")
|
||
pPostQuitMessage = modUser32.NewProc("PostQuitMessage")
|
||
pSendMessageW = modUser32.NewProc("SendMessageW")
|
||
pGetClientRect = modUser32.NewProc("GetClientRect")
|
||
pMoveWindow = modUser32.NewProc("MoveWindow")
|
||
pMessageBoxW = modUser32.NewProc("MessageBoxW")
|
||
pCreateFontW = modGdi32.NewProc("CreateFontW")
|
||
pDeleteObject = modGdi32.NewProc("DeleteObject")
|
||
pCreateSolidBrush = modGdi32.NewProc("CreateSolidBrush")
|
||
pSetTextColor = modGdi32.NewProc("SetTextColor")
|
||
pSetBkColor = modGdi32.NewProc("SetBkColor")
|
||
)
|
||
|
||
var (
|
||
hWndMain uintptr
|
||
hWndEdit uintptr
|
||
hFont uintptr
|
||
hbrBg uintptr
|
||
guiReady bool
|
||
guiMu sync.Mutex
|
||
pendingLogs []string
|
||
)
|
||
|
||
// guiLogWriter 实现 io.Writer,将日志写入编辑控件
|
||
type guiLogWriter struct{}
|
||
|
||
func (w *guiLogWriter) Write(p []byte) (n int, err error) {
|
||
text := string(p)
|
||
|
||
guiMu.Lock()
|
||
if !guiReady || hWndEdit == 0 {
|
||
pendingLogs = append(pendingLogs, text)
|
||
guiMu.Unlock()
|
||
return len(p), nil
|
||
}
|
||
guiMu.Unlock()
|
||
|
||
appendText(text)
|
||
return len(p), nil
|
||
}
|
||
|
||
func appendText(text string) {
|
||
if hWndEdit == 0 {
|
||
return
|
||
}
|
||
// 光标移到末尾
|
||
pSendMessageW.Call(hWndEdit, EM_SETSEL, ^uintptr(0), ^uintptr(0))
|
||
// 插入文本
|
||
ptr, _ := syscall.UTF16PtrFromString(text)
|
||
pSendMessageW.Call(hWndEdit, EM_REPLACESEL, 1, uintptr(unsafe.Pointer(ptr)))
|
||
}
|
||
|
||
func flushPendingLogs() {
|
||
guiMu.Lock()
|
||
logs := pendingLogs
|
||
pendingLogs = nil
|
||
guiReady = true
|
||
guiMu.Unlock()
|
||
|
||
for _, text := range logs {
|
||
appendText(text)
|
||
}
|
||
}
|
||
|
||
func wndProc(hWnd uintptr, msg uint32, wParam, lParam uintptr) uintptr {
|
||
switch msg {
|
||
case WM_SIZE:
|
||
if hWndEdit != 0 {
|
||
var rect tRECT
|
||
pGetClientRect.Call(hWnd, uintptr(unsafe.Pointer(&rect)))
|
||
pMoveWindow.Call(hWndEdit, 0, 0, uintptr(rect.Right), uintptr(rect.Bottom), 1)
|
||
}
|
||
return 0
|
||
|
||
case WM_CTLCOLOREDIT:
|
||
pSetTextColor.Call(wParam, colorFg)
|
||
pSetBkColor.Call(wParam, colorBg)
|
||
return hbrBg
|
||
|
||
case WM_DESTROY:
|
||
if hFont != 0 {
|
||
pDeleteObject.Call(hFont)
|
||
}
|
||
if hbrBg != 0 {
|
||
pDeleteObject.Call(hbrBg)
|
||
}
|
||
pPostQuitMessage.Call(0)
|
||
return 0
|
||
}
|
||
|
||
ret, _, _ := pDefWindowProcW.Call(hWnd, uintptr(msg), wParam, lParam)
|
||
return ret
|
||
}
|
||
|
||
// runGUI 创建并运行GUI窗口,阻塞直到窗口关闭
|
||
func runGUI() {
|
||
runtime.LockOSThread()
|
||
defer runtime.UnlockOSThread()
|
||
|
||
hInstance, _, _ := pGetModuleHandleW.Call(0)
|
||
|
||
className, _ := syscall.UTF16PtrFromString("KfzLogWnd")
|
||
windowTitle, _ := syscall.UTF16PtrFromString("孔网商品定价 - 日志")
|
||
editClass, _ := syscall.UTF16PtrFromString("EDIT")
|
||
|
||
hCursor, _, _ := pLoadCursorW.Call(0, IDC_ARROW)
|
||
|
||
// 创建暗色背景画刷
|
||
hbrBg, _, _ = pCreateSolidBrush.Call(colorBg)
|
||
|
||
// 注册窗口类
|
||
wc := tWNDCLASSEX{
|
||
CbSize: uint32(unsafe.Sizeof(tWNDCLASSEX{})),
|
||
LpfnWndProc: syscall.NewCallback(wndProc),
|
||
HInstance: hInstance,
|
||
HCursor: hCursor,
|
||
HbrBackground: hbrBg,
|
||
LpszClassName: className,
|
||
}
|
||
pRegisterClassExW.Call(uintptr(unsafe.Pointer(&wc)))
|
||
|
||
// 创建主窗口
|
||
hWndMain, _, _ = pCreateWindowExW.Call(
|
||
0,
|
||
uintptr(unsafe.Pointer(className)),
|
||
uintptr(unsafe.Pointer(windowTitle)),
|
||
WS_OVERLAPPEDWINDOW,
|
||
100, 100, 900, 600,
|
||
0, 0, hInstance, 0,
|
||
)
|
||
|
||
// 创建编辑控件(多行、只读、滚动条)
|
||
hWndEdit, _, _ = pCreateWindowExW.Call(
|
||
WS_EX_CLIENTEDGE,
|
||
uintptr(unsafe.Pointer(editClass)),
|
||
0,
|
||
WS_CHILD|WS_VISIBLE|WS_VSCROLL|WS_HSCROLL|ES_MULTILINE|ES_READONLY|ES_AUTOVSCROLL|ES_AUTOHSCROLL,
|
||
0, 0, 0, 0,
|
||
hWndMain, 0, hInstance, 0,
|
||
)
|
||
|
||
// 设置等宽字体
|
||
fontName, _ := syscall.UTF16PtrFromString("Consolas")
|
||
hFont, _, _ = pCreateFontW.Call(
|
||
16, 0, 0, 0, FW_NORMAL, 0, 0, 0,
|
||
DEFAULT_CHARSET, 0, 0, 0,
|
||
FIXED_PITCH|FF_MODERN,
|
||
uintptr(unsafe.Pointer(fontName)),
|
||
)
|
||
pSendMessageW.Call(hWndEdit, WM_SETFONT, hFont, 1)
|
||
|
||
// 刷新缓冲日志
|
||
flushPendingLogs()
|
||
|
||
// 显示窗口
|
||
pShowWindow.Call(hWndMain, SW_SHOW)
|
||
pUpdateWindow.Call(hWndMain)
|
||
|
||
// 消息循环
|
||
var msg tMSG
|
||
for {
|
||
ret, _, _ := pGetMessageW.Call(uintptr(unsafe.Pointer(&msg)), 0, 0, 0)
|
||
if ret == 0 {
|
||
break
|
||
}
|
||
pTranslateMessage.Call(uintptr(unsafe.Pointer(&msg)))
|
||
pDispatchMessageW.Call(uintptr(unsafe.Pointer(&msg)))
|
||
}
|
||
}
|
||
|
||
// fatalExit 弹出错误对话框并退出
|
||
func fatalExit(format string, v ...interface{}) {
|
||
msg := fmt.Sprintf(format, v...)
|
||
log.Print(msg)
|
||
title, _ := syscall.UTF16PtrFromString("错误")
|
||
text, _ := syscall.UTF16PtrFromString(msg)
|
||
pMessageBoxW.Call(0, uintptr(unsafe.Pointer(text)), uintptr(unsafe.Pointer(title)), MB_ICONERROR)
|
||
os.Exit(1)
|
||
}
|