592 lines
15 KiB
Go
592 lines
15 KiB
Go
//go:build windows
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
"unsafe"
|
|
)
|
|
|
|
const (
|
|
windowTitle = "D-InSAR 数据包恢复工具"
|
|
|
|
wmDestroy = 0x0002
|
|
wmCommand = 0x0111
|
|
wmClose = 0x0010
|
|
wmSetFont = 0x0030
|
|
|
|
bnClicked = 0
|
|
|
|
wsOverlappedWindow = 0x00CF0000
|
|
wsVisible = 0x10000000
|
|
wsChild = 0x40000000
|
|
wsTabStop = 0x00010000
|
|
wsBorder = 0x00800000
|
|
wsVScroll = 0x00200000
|
|
wsHScroll = 0x00100000
|
|
esLeft = 0x0000
|
|
esMultiline = 0x0004
|
|
esAutoVScroll = 0x0040
|
|
esAutoHScroll = 0x0080
|
|
esReadOnly = 0x0800
|
|
bsPushButton = 0x00000000
|
|
bsAutoCheckBox = 0x00000003
|
|
|
|
swShow = 5
|
|
cwUseDefault = 0x80000000
|
|
|
|
gwlUserData = -21
|
|
|
|
idInputEdit = 1001
|
|
idInputBrowse = 1002
|
|
idOutputEdit = 1003
|
|
idOutputBrowse = 1004
|
|
idDryRun = 1005
|
|
idOverwrite = 1006
|
|
idLimitEdit = 1007
|
|
idStart = 1008
|
|
idLogEdit = 1009
|
|
idCheck = 1010
|
|
|
|
bmGetCheck = 0x00F0
|
|
bstChecked = 1
|
|
|
|
maxPathBuffer = 4096
|
|
)
|
|
|
|
var (
|
|
user32 = syscall.NewLazyDLL("user32.dll")
|
|
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
|
gdi32 = syscall.NewLazyDLL("gdi32.dll")
|
|
shell32 = syscall.NewLazyDLL("shell32.dll")
|
|
ole32 = syscall.NewLazyDLL("ole32.dll")
|
|
|
|
procRegisterClassExW = user32.NewProc("RegisterClassExW")
|
|
procCreateWindowExW = user32.NewProc("CreateWindowExW")
|
|
procDefWindowProcW = user32.NewProc("DefWindowProcW")
|
|
procDestroyWindow = user32.NewProc("DestroyWindow")
|
|
procDispatchMessageW = user32.NewProc("DispatchMessageW")
|
|
procGetMessageW = user32.NewProc("GetMessageW")
|
|
procPostQuitMessage = user32.NewProc("PostQuitMessage")
|
|
procSendMessageW = user32.NewProc("SendMessageW")
|
|
procShowWindow = user32.NewProc("ShowWindow")
|
|
procUpdateWindow = user32.NewProc("UpdateWindow")
|
|
procSetWindowTextW = user32.NewProc("SetWindowTextW")
|
|
procGetWindowTextW = user32.NewProc("GetWindowTextW")
|
|
procGetWindowTextLenW = user32.NewProc("GetWindowTextLengthW")
|
|
procEnableWindow = user32.NewProc("EnableWindow")
|
|
procSetWindowLongPtrW = user32.NewProc("SetWindowLongPtrW")
|
|
procGetWindowLongPtrW = user32.NewProc("GetWindowLongPtrW")
|
|
procMessageBoxW = user32.NewProc("MessageBoxW")
|
|
|
|
procGetModuleHandleW = kernel32.NewProc("GetModuleHandleW")
|
|
|
|
procCreateFontW = gdi32.NewProc("CreateFontW")
|
|
|
|
procSHBrowseForFolderW = shell32.NewProc("SHBrowseForFolderW")
|
|
procSHGetPathFromIDListW = shell32.NewProc("SHGetPathFromIDListW")
|
|
procCoTaskMemFree = ole32.NewProc("CoTaskMemFree")
|
|
procOleInitialize = ole32.NewProc("OleInitialize")
|
|
procOleUninitialize = ole32.NewProc("OleUninitialize")
|
|
)
|
|
|
|
type point struct {
|
|
x int32
|
|
y int32
|
|
}
|
|
|
|
type msg struct {
|
|
hwnd uintptr
|
|
message uint32
|
|
wParam uintptr
|
|
lParam uintptr
|
|
time uint32
|
|
pt point
|
|
}
|
|
|
|
type wndClassEx 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 browseInfo struct {
|
|
hwndOwner uintptr
|
|
pidlRoot uintptr
|
|
pszDisplayName *uint16
|
|
lpszTitle *uint16
|
|
ulFlags uint32
|
|
lpfn uintptr
|
|
lParam uintptr
|
|
iImage int32
|
|
}
|
|
|
|
type guiApp struct {
|
|
hwnd uintptr
|
|
font uintptr
|
|
inputEdit uintptr
|
|
outputEdit uintptr
|
|
dryRun uintptr
|
|
overwrite uintptr
|
|
limitEdit uintptr
|
|
startButton uintptr
|
|
checkButton uintptr
|
|
logEdit uintptr
|
|
mu sync.Mutex
|
|
logText string
|
|
running bool
|
|
}
|
|
|
|
func runGUI() error {
|
|
runtime.LockOSThread()
|
|
defer runtime.UnlockOSThread()
|
|
procOleInitialize.Call(0)
|
|
defer procOleUninitialize.Call()
|
|
|
|
instance, _, _ := procGetModuleHandleW.Call(0)
|
|
className, _ := syscall.UTF16PtrFromString("DinsarRestoreWindow")
|
|
title, _ := syscall.UTF16PtrFromString(windowTitle)
|
|
|
|
wc := wndClassEx{
|
|
cbSize: uint32(unsafe.Sizeof(wndClassEx{})),
|
|
lpfnWndProc: syscall.NewCallback(windowProc),
|
|
hInstance: instance,
|
|
hbrBackground: 5 + 1,
|
|
lpszClassName: className,
|
|
}
|
|
if atom, _, err := procRegisterClassExW.Call(uintptr(unsafe.Pointer(&wc))); atom == 0 {
|
|
return fmt.Errorf("RegisterClassExW failed: %v", err)
|
|
}
|
|
|
|
app := &guiApp{}
|
|
hwnd, _, err := procCreateWindowExW.Call(
|
|
0,
|
|
uintptr(unsafe.Pointer(className)),
|
|
uintptr(unsafe.Pointer(title)),
|
|
wsOverlappedWindow|wsVisible,
|
|
cwUseDefault,
|
|
cwUseDefault,
|
|
1040,
|
|
720,
|
|
0,
|
|
0,
|
|
instance,
|
|
uintptr(unsafe.Pointer(app)),
|
|
)
|
|
if hwnd == 0 {
|
|
return fmt.Errorf("CreateWindowExW failed: %v", err)
|
|
}
|
|
app.hwnd = hwnd
|
|
|
|
procShowWindow.Call(hwnd, swShow)
|
|
procUpdateWindow.Call(hwnd)
|
|
|
|
var m msg
|
|
for {
|
|
ret, _, _ := procGetMessageW.Call(uintptr(unsafe.Pointer(&m)), 0, 0, 0)
|
|
if int32(ret) <= 0 {
|
|
break
|
|
}
|
|
procDispatchMessageW.Call(uintptr(unsafe.Pointer(&m)))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func windowProc(hwnd uintptr, msg uint32, wParam uintptr, lParam uintptr) uintptr {
|
|
app := appFromWindow(hwnd)
|
|
switch msg {
|
|
case wmClose:
|
|
if app != nil && app.isRunning() {
|
|
messageBox(hwnd, "任务正在运行,请等待结束。", windowTitle)
|
|
return 0
|
|
}
|
|
procDestroyWindow.Call(hwnd)
|
|
return 0
|
|
case wmDestroy:
|
|
procPostQuitMessage.Call(0)
|
|
return 0
|
|
case wmCommand:
|
|
id := int(wParam & 0xffff)
|
|
notify := int((wParam >> 16) & 0xffff)
|
|
if notify == bnClicked && app != nil {
|
|
switch id {
|
|
case idInputBrowse:
|
|
if selected := chooseDirectory(hwnd, "选择 BundleRoot 输入目录"); selected != "" {
|
|
setWindowText(app.inputEdit, selected)
|
|
}
|
|
case idOutputBrowse:
|
|
if selected := chooseDirectory(hwnd, "选择 OutputRoot 输出目录"); selected != "" {
|
|
setWindowText(app.outputEdit, selected)
|
|
}
|
|
case idStart:
|
|
app.startRestore()
|
|
case idCheck:
|
|
app.startCheck()
|
|
}
|
|
}
|
|
return 0
|
|
case 0x0081: // WM_NCCREATE
|
|
cs := (*createStruct)(unsafe.Pointer(lParam))
|
|
procSetWindowLongPtrW.Call(hwnd, winIndex(gwlUserData), cs.createParams)
|
|
return 1
|
|
case 0x0001: // WM_CREATE
|
|
app = appFromWindow(hwnd)
|
|
if app != nil {
|
|
app.hwnd = hwnd
|
|
app.createControls()
|
|
}
|
|
return 0
|
|
}
|
|
ret, _, _ := procDefWindowProcW.Call(hwnd, uintptr(msg), wParam, lParam)
|
|
return ret
|
|
}
|
|
|
|
type createStruct struct {
|
|
createParams uintptr
|
|
instance uintptr
|
|
menu uintptr
|
|
parent uintptr
|
|
cy int32
|
|
cx int32
|
|
y int32
|
|
x int32
|
|
style int32
|
|
name *uint16
|
|
class *uint16
|
|
exStyle uint32
|
|
}
|
|
|
|
func appFromWindow(hwnd uintptr) *guiApp {
|
|
if hwnd == 0 {
|
|
return nil
|
|
}
|
|
ptr, _, _ := procGetWindowLongPtrW.Call(hwnd, winIndex(gwlUserData))
|
|
if ptr == 0 {
|
|
return nil
|
|
}
|
|
return (*guiApp)(unsafe.Pointer(ptr))
|
|
}
|
|
|
|
func (a *guiApp) createControls() {
|
|
a.font = createFont("Microsoft YaHei UI", 20)
|
|
|
|
createLabel(a.hwnd, "输入目录 BundleRoot", 24, 28, 210, 30, a.font)
|
|
a.inputEdit = createEdit(a.hwnd, "", idInputEdit, 240, 24, 640, 34, false, a.font)
|
|
createButton(a.hwnd, "选择...", idInputBrowse, 895, 24, 100, 34, a.font)
|
|
|
|
createLabel(a.hwnd, "输出目录 OutputRoot", 24, 78, 210, 30, a.font)
|
|
a.outputEdit = createEdit(a.hwnd, "", idOutputEdit, 240, 74, 640, 34, false, a.font)
|
|
createButton(a.hwnd, "选择...", idOutputBrowse, 895, 74, 100, 34, a.font)
|
|
|
|
a.dryRun = createCheckBox(a.hwnd, "Dry run 只打印计划", idDryRun, 240, 128, 230, 32, a.font)
|
|
a.overwrite = createCheckBox(a.hwnd, "覆盖已有 Task", idOverwrite, 500, 128, 190, 32, a.font)
|
|
createLabel(a.hwnd, "Limit", 720, 130, 60, 30, a.font)
|
|
a.limitEdit = createEdit(a.hwnd, "", idLimitEdit, 780, 126, 100, 34, false, a.font)
|
|
|
|
a.checkButton = createButton(a.hwnd, "数据自检", idCheck, 760, 174, 105, 38, a.font)
|
|
a.startButton = createButton(a.hwnd, "开始恢复", idStart, 890, 174, 105, 38, a.font)
|
|
createLabel(a.hwnd, "运行日志", 24, 226, 140, 30, a.font)
|
|
a.logEdit = createEdit(a.hwnd, "", idLogEdit, 24, 262, 970, 365, true, a.font)
|
|
|
|
a.appendLog("请选择输入目录后可先数据自检;选择输出目录后可开始恢复。\r\n")
|
|
}
|
|
|
|
func (a *guiApp) startRestore() {
|
|
if a.isRunning() {
|
|
return
|
|
}
|
|
|
|
input := strings.TrimSpace(getWindowText(a.inputEdit))
|
|
output := strings.TrimSpace(getWindowText(a.outputEdit))
|
|
if input == "" {
|
|
messageBox(a.hwnd, "请选择输入目录 BundleRoot。", windowTitle)
|
|
return
|
|
}
|
|
if output == "" {
|
|
messageBox(a.hwnd, "请选择输出目录 OutputRoot。", windowTitle)
|
|
return
|
|
}
|
|
limit, ok := a.parseLimit()
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
cfg := config{
|
|
inputRoot: input,
|
|
outputRoot: output,
|
|
skipExisting: true,
|
|
overwrite: isChecked(a.overwrite),
|
|
limit: limit,
|
|
dryRun: isChecked(a.dryRun),
|
|
logWriter: guiLogWriter{app: a},
|
|
}
|
|
if cfg.overwrite {
|
|
cfg.skipExisting = false
|
|
}
|
|
|
|
a.setRunning(true)
|
|
a.setLog("")
|
|
a.appendLog("开始执行...\r\n")
|
|
go func() {
|
|
result, err := run(cfg)
|
|
if err != nil {
|
|
a.appendLog("错误: " + err.Error() + "\r\n")
|
|
messageBox(a.hwnd, "执行失败:\r\n"+err.Error(), windowTitle)
|
|
} else {
|
|
a.appendLog(fmt.Sprintf("完成: restored=%d skipped=%d failed=%d warnings=%d\r\n",
|
|
result.report.Restored,
|
|
result.report.Skipped,
|
|
result.report.Failed,
|
|
len(result.report.Warnings),
|
|
))
|
|
messageBox(a.hwnd, "执行完成。", windowTitle)
|
|
}
|
|
a.setRunning(false)
|
|
}()
|
|
}
|
|
|
|
func (a *guiApp) startCheck() {
|
|
if a.isRunning() {
|
|
return
|
|
}
|
|
|
|
input := strings.TrimSpace(getWindowText(a.inputEdit))
|
|
if input == "" {
|
|
messageBox(a.hwnd, "请选择输入目录 BundleRoot。", windowTitle)
|
|
return
|
|
}
|
|
limit, ok := a.parseLimit()
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
cfg := config{
|
|
inputRoot: input,
|
|
limit: limit,
|
|
checkOnly: true,
|
|
logWriter: guiLogWriter{app: a},
|
|
}
|
|
|
|
a.setRunning(true)
|
|
a.setLog("")
|
|
a.appendLog("开始数据自检...\r\n")
|
|
go func() {
|
|
result, err := runCheck(cfg)
|
|
if err != nil {
|
|
a.appendLog("错误: " + err.Error() + "\r\n")
|
|
messageBox(a.hwnd, "自检失败:\r\n"+err.Error(), windowTitle)
|
|
} else if result.report.Failed > 0 {
|
|
a.appendLog(fmt.Sprintf("自检完成: total=%d checked=%d valid=%d failed=%d warnings=%d\r\n",
|
|
result.report.TotalPairs,
|
|
result.report.Checked,
|
|
result.report.Valid,
|
|
result.report.Failed,
|
|
len(result.report.Warnings),
|
|
))
|
|
messageBox(a.hwnd, fmt.Sprintf("自检完成,但发现 %d 个失败项。请查看日志。", result.report.Failed), windowTitle)
|
|
} else {
|
|
a.appendLog(fmt.Sprintf("自检通过: total=%d checked=%d valid=%d warnings=%d\r\n",
|
|
result.report.TotalPairs,
|
|
result.report.Checked,
|
|
result.report.Valid,
|
|
len(result.report.Warnings),
|
|
))
|
|
messageBox(a.hwnd, "自检通过。", windowTitle)
|
|
}
|
|
a.setRunning(false)
|
|
}()
|
|
}
|
|
|
|
func (a *guiApp) parseLimit() (int, bool) {
|
|
limitText := strings.TrimSpace(getWindowText(a.limitEdit))
|
|
if limitText == "" {
|
|
return 0, true
|
|
}
|
|
parsed, err := strconv.Atoi(limitText)
|
|
if err != nil || parsed < 0 {
|
|
messageBox(a.hwnd, "Limit 必须是大于等于 0 的整数。", windowTitle)
|
|
return 0, false
|
|
}
|
|
return parsed, true
|
|
}
|
|
|
|
func (a *guiApp) isRunning() bool {
|
|
a.mu.Lock()
|
|
defer a.mu.Unlock()
|
|
return a.running
|
|
}
|
|
|
|
func (a *guiApp) setRunning(running bool) {
|
|
a.mu.Lock()
|
|
a.running = running
|
|
a.mu.Unlock()
|
|
enable := uintptr(1)
|
|
if running {
|
|
enable = 0
|
|
}
|
|
procEnableWindow.Call(a.startButton, enable)
|
|
procEnableWindow.Call(a.checkButton, enable)
|
|
procEnableWindow.Call(a.inputEdit, enable)
|
|
procEnableWindow.Call(a.outputEdit, enable)
|
|
procEnableWindow.Call(a.dryRun, enable)
|
|
procEnableWindow.Call(a.overwrite, enable)
|
|
procEnableWindow.Call(a.limitEdit, enable)
|
|
}
|
|
|
|
func (a *guiApp) setLog(text string) {
|
|
a.mu.Lock()
|
|
a.logText = text
|
|
a.mu.Unlock()
|
|
setWindowText(a.logEdit, text)
|
|
}
|
|
|
|
func (a *guiApp) appendLog(text string) {
|
|
a.mu.Lock()
|
|
a.logText += text
|
|
fullText := a.logText
|
|
a.mu.Unlock()
|
|
setWindowText(a.logEdit, fullText)
|
|
}
|
|
|
|
type guiLogWriter struct {
|
|
app *guiApp
|
|
}
|
|
|
|
func (w guiLogWriter) Write(p []byte) (int, error) {
|
|
if w.app != nil {
|
|
w.app.appendLog(strings.ReplaceAll(string(p), "\n", "\r\n"))
|
|
}
|
|
return len(p), nil
|
|
}
|
|
|
|
func createLabel(parent uintptr, text string, x, y, width, height int32, font uintptr) uintptr {
|
|
return createControl("STATIC", text, wsChild|wsVisible, 0, parent, 0, x, y, width, height, font)
|
|
}
|
|
|
|
func createEdit(parent uintptr, text string, id int, x, y, width, height int32, multiline bool, font uintptr) uintptr {
|
|
style := uint32(wsChild | wsVisible | wsBorder | wsTabStop | esLeft | esAutoHScroll)
|
|
if multiline {
|
|
style = wsChild | wsVisible | wsBorder | wsVScroll | wsHScroll | esLeft | esMultiline | esAutoVScroll | esAutoHScroll | esReadOnly
|
|
}
|
|
return createControl("EDIT", text, style, 0, parent, id, x, y, width, height, font)
|
|
}
|
|
|
|
func createButton(parent uintptr, text string, id int, x, y, width, height int32, font uintptr) uintptr {
|
|
return createControl("BUTTON", text, wsChild|wsVisible|wsTabStop|bsPushButton, 0, parent, id, x, y, width, height, font)
|
|
}
|
|
|
|
func createCheckBox(parent uintptr, text string, id int, x, y, width, height int32, font uintptr) uintptr {
|
|
return createControl("BUTTON", text, wsChild|wsVisible|wsTabStop|bsAutoCheckBox, 0, parent, id, x, y, width, height, font)
|
|
}
|
|
|
|
func createControl(className string, text string, style uint32, exStyle uint32, parent uintptr, id int, x, y, width, height int32, font uintptr) uintptr {
|
|
classPtr, _ := syscall.UTF16PtrFromString(className)
|
|
textPtr, _ := syscall.UTF16PtrFromString(text)
|
|
hwnd, _, _ := procCreateWindowExW.Call(
|
|
uintptr(exStyle),
|
|
uintptr(unsafe.Pointer(classPtr)),
|
|
uintptr(unsafe.Pointer(textPtr)),
|
|
uintptr(style),
|
|
uintptr(x),
|
|
uintptr(y),
|
|
uintptr(width),
|
|
uintptr(height),
|
|
parent,
|
|
uintptr(id),
|
|
0,
|
|
0,
|
|
)
|
|
if hwnd != 0 && font != 0 {
|
|
procSendMessageW.Call(hwnd, wmSetFont, font, 1)
|
|
}
|
|
return hwnd
|
|
}
|
|
|
|
func createFont(name string, size int32) uintptr {
|
|
namePtr, _ := syscall.UTF16PtrFromString(name)
|
|
font, _, _ := procCreateFontW.Call(
|
|
uintptr(-size),
|
|
0,
|
|
0,
|
|
0,
|
|
400,
|
|
0,
|
|
0,
|
|
0,
|
|
1,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
uintptr(unsafe.Pointer(namePtr)),
|
|
)
|
|
return font
|
|
}
|
|
|
|
func setWindowText(hwnd uintptr, text string) {
|
|
ptr, _ := syscall.UTF16PtrFromString(text)
|
|
procSetWindowTextW.Call(hwnd, uintptr(unsafe.Pointer(ptr)))
|
|
}
|
|
|
|
func getWindowText(hwnd uintptr) string {
|
|
length, _, _ := procGetWindowTextLenW.Call(hwnd)
|
|
buffer := make([]uint16, int(length)+1)
|
|
procGetWindowTextW.Call(hwnd, uintptr(unsafe.Pointer(&buffer[0])), uintptr(len(buffer)))
|
|
return syscall.UTF16ToString(buffer)
|
|
}
|
|
|
|
func isChecked(hwnd uintptr) bool {
|
|
ret, _, _ := procSendMessageW.Call(hwnd, bmGetCheck, 0, 0)
|
|
return ret == bstChecked
|
|
}
|
|
|
|
func messageBox(hwnd uintptr, text string, title string) {
|
|
textPtr, _ := syscall.UTF16PtrFromString(text)
|
|
titlePtr, _ := syscall.UTF16PtrFromString(title)
|
|
procMessageBoxW.Call(hwnd, uintptr(unsafe.Pointer(textPtr)), uintptr(unsafe.Pointer(titlePtr)), 0)
|
|
}
|
|
|
|
func chooseDirectory(hwnd uintptr, title string) string {
|
|
titlePtr, _ := syscall.UTF16PtrFromString(title)
|
|
displayName := make([]uint16, maxPathBuffer)
|
|
bi := browseInfo{
|
|
hwndOwner: hwnd,
|
|
pszDisplayName: &displayName[0],
|
|
lpszTitle: titlePtr,
|
|
ulFlags: 0x00000001 | 0x00000010 | 0x00000040,
|
|
}
|
|
pidl, _, _ := procSHBrowseForFolderW.Call(uintptr(unsafe.Pointer(&bi)))
|
|
if pidl == 0 {
|
|
return ""
|
|
}
|
|
defer procCoTaskMemFree.Call(pidl)
|
|
|
|
pathBuffer := make([]uint16, maxPathBuffer)
|
|
ret, _, _ := procSHGetPathFromIDListW.Call(pidl, uintptr(unsafe.Pointer(&pathBuffer[0])))
|
|
if ret == 0 {
|
|
return ""
|
|
}
|
|
return syscall.UTF16ToString(pathBuffer)
|
|
}
|
|
|
|
func winIndex(value int32) uintptr {
|
|
return uintptr(value)
|
|
}
|
|
|
|
var _ io.Writer = guiLogWriter{}
|