mirror of
https://github.com/konyshe/GoodLink.git
synced 2026-09-01 14:51:48 +08:00
404 lines
9.7 KiB
Go
404 lines
9.7 KiB
Go
//go:build windows
|
||
|
||
package ui2
|
||
|
||
import (
|
||
"bufio"
|
||
"encoding/json"
|
||
"fmt"
|
||
"go2"
|
||
"log"
|
||
"os"
|
||
"os/exec"
|
||
"path/filepath"
|
||
"strings"
|
||
"sync"
|
||
"syscall"
|
||
"time"
|
||
|
||
"goodlink/config"
|
||
"goodlink/pro"
|
||
"goodlink/utils"
|
||
|
||
_ "embed"
|
||
_ "net/http/pprof"
|
||
|
||
"fyne.io/fyne/v2"
|
||
"fyne.io/fyne/v2/theme"
|
||
"fyne.io/fyne/v2/widget"
|
||
)
|
||
|
||
var (
|
||
m_lock_start sync.Mutex
|
||
m_button_start *widget.Button
|
||
m_activity_start_button *widget.Activity
|
||
m_stats_start_button int
|
||
|
||
// 子进程管理
|
||
m_cmd_process *exec.Cmd
|
||
m_cmd_mutex sync.Mutex
|
||
|
||
// 自动重启控制
|
||
m_auto_restart_enabled bool
|
||
)
|
||
|
||
// UI组件接口,用于统一管理启用/禁用
|
||
type uiComponent interface {
|
||
Enable()
|
||
Disable()
|
||
}
|
||
|
||
// 所有需要控制的UI组件列表
|
||
var uiComponents = []uiComponent{}
|
||
|
||
func init() {
|
||
// 延迟初始化,在 GetMainUI 中设置
|
||
}
|
||
|
||
// setUIComponents 设置需要控制的UI组件列表
|
||
func setUIComponents(components []uiComponent) {
|
||
uiComponents = components
|
||
}
|
||
|
||
func disable_other(content string) {
|
||
for _, comp := range uiComponents {
|
||
comp.Disable()
|
||
}
|
||
m_activity_start_button.Start()
|
||
m_activity_start_button.Show()
|
||
m_stats_start_button = 1
|
||
}
|
||
|
||
func enable_other() {
|
||
for _, comp := range uiComponents {
|
||
comp.Enable()
|
||
}
|
||
m_activity_start_button.Stop()
|
||
m_activity_start_button.Hide()
|
||
}
|
||
|
||
// StopCmdProcess 停止子进程(供外部调用,如窗口关闭时)
|
||
func StopCmdProcess() {
|
||
m_cmd_mutex.Lock()
|
||
defer m_cmd_mutex.Unlock()
|
||
|
||
if m_cmd_process != nil && m_cmd_process.Process != nil {
|
||
utils.KillProcess(m_cmd_process.Process.Pid)
|
||
m_cmd_process = nil
|
||
}
|
||
|
||
// 清理所有遗留的cmd进程
|
||
utils.CleanupOrphanedCmdProcesses()
|
||
}
|
||
|
||
// parseStatusMessage 解析状态消息,返回状态值(connecting/connected/waiting)和是否成功解析
|
||
// 支持带时间戳前缀的日志格式,如 "2024/01/01 12:00:00 [GOODLINK_STATUS]connected"
|
||
func parseStatusMessage(line string) (string, bool) {
|
||
// 查找前缀在行中的位置(可能不在行首,因为有时间戳)
|
||
idx := strings.Index(line, pro.TagStatusPrefix)
|
||
if idx == -1 {
|
||
return "", false
|
||
}
|
||
// 提取状态值(去除前缀后的内容,可能包含空格)
|
||
status := strings.TrimSpace(line[idx+len(pro.TagStatusPrefix):])
|
||
if status == pro.TagStatusConnecting || status == pro.TagStatusConnected || status == pro.TagStatusRunning || status == pro.TagStatusConnectingNAT4 || status == pro.TagStatusVersionMismatch {
|
||
return status, true
|
||
}
|
||
return "", false
|
||
}
|
||
|
||
// 按钮状态类型
|
||
type buttonState struct {
|
||
text string
|
||
importance widget.Importance
|
||
icon fyne.Resource
|
||
}
|
||
|
||
// 预定义的按钮状态
|
||
var (
|
||
buttonStateIdle = buttonState{
|
||
text: "点击启动",
|
||
importance: widget.HighImportance,
|
||
icon: theme.MediaPlayIcon(),
|
||
}
|
||
buttonStateStarting = buttonState{
|
||
text: "启动中...",
|
||
importance: widget.WarningImportance,
|
||
icon: theme.MediaStopIcon(),
|
||
}
|
||
buttonStateConnecting = buttonState{
|
||
text: "连接中...",
|
||
importance: widget.WarningImportance,
|
||
icon: theme.MediaStopIcon(),
|
||
}
|
||
buttonStateConnectingNAT4 = buttonState{
|
||
text: "两端都是NAT4, 连接中...",
|
||
importance: widget.DangerImportance,
|
||
icon: theme.MediaStopIcon(),
|
||
}
|
||
buttonStateConnected = buttonState{
|
||
text: "连接成功, 点击停止",
|
||
importance: widget.SuccessImportance,
|
||
icon: theme.MediaStopIcon(),
|
||
}
|
||
buttonStateRunning = buttonState{
|
||
text: "启动成功, 点击停止",
|
||
importance: widget.SuccessImportance,
|
||
icon: theme.MediaStopIcon(),
|
||
}
|
||
)
|
||
|
||
// updateButtonState 更新启动按钮的状态
|
||
func updateButtonState(state buttonState) {
|
||
if m_button_start == nil {
|
||
return
|
||
}
|
||
m_button_start.SetText(state.text)
|
||
m_button_start.Importance = state.importance
|
||
m_button_start.SetIcon(state.icon)
|
||
m_button_start.Refresh()
|
||
}
|
||
|
||
// updateConnectionStatus 根据连接状态更新按钮(Local端直接映射,Remote端在连接成功后才切换为运行状态)
|
||
func updateConnectionStatus(status string) {
|
||
switch GetWorkType() {
|
||
case workTypeLocal:
|
||
switch status {
|
||
case pro.TagStatusConnecting:
|
||
updateButtonState(buttonStateConnecting)
|
||
case pro.TagStatusConnected:
|
||
updateButtonState(buttonStateConnected)
|
||
case pro.TagStatusConnectingNAT4:
|
||
updateButtonState(buttonStateConnectingNAT4)
|
||
case pro.TagStatusVersionMismatch:
|
||
// 版本不一致,禁用自动重启并停止进程
|
||
m_lock_start.Lock()
|
||
m_auto_restart_enabled = false
|
||
m_lock_start.Unlock()
|
||
go func() {
|
||
time.Sleep(500 * time.Millisecond) // 短暂延迟,确保日志已输出
|
||
StopCmdProcess()
|
||
}()
|
||
}
|
||
case workTypeRemote:
|
||
switch status {
|
||
case pro.TagStatusRunning:
|
||
updateButtonState(buttonStateRunning)
|
||
}
|
||
}
|
||
}
|
||
|
||
// startCmdProcess 启动cmd进程(提取的公共逻辑,用于初始启动和自动重启)
|
||
func startCmdProcess() error {
|
||
// 构建命令行参数
|
||
cmdPath := utils.GetCmdExePath()
|
||
|
||
// 检查 cmd 程序是否存在
|
||
if _, err := os.Stat(cmdPath); os.IsNotExist(err) {
|
||
UILogPrintF("未找到: %s", filepath.Base(cmdPath))
|
||
return fmt.Errorf("cmd程序不存在: %s", cmdPath)
|
||
}
|
||
|
||
// 构建命令行参数
|
||
workType := GetWorkType()
|
||
args := []string{"--fork", "--" + strings.ToLower(workType), "--key=" + m_validated_key.Text, "--local_config"}
|
||
|
||
// 创建子进程
|
||
m_cmd_mutex.Lock()
|
||
m_cmd_process = exec.Command(cmdPath, args...)
|
||
|
||
// 隐藏子进程窗口
|
||
m_cmd_process.SysProcAttr = &syscall.SysProcAttr{
|
||
HideWindow: true,
|
||
CreationFlags: 0x08000000, // CREATE_NO_WINDOW
|
||
}
|
||
|
||
// 获取输出管道
|
||
stdout, err := m_cmd_process.StdoutPipe()
|
||
if err != nil {
|
||
m_cmd_mutex.Unlock()
|
||
UILogPrintF("获取stdout失败: %v", err)
|
||
return err
|
||
}
|
||
stderr, err := m_cmd_process.StderrPipe()
|
||
if err != nil {
|
||
m_cmd_mutex.Unlock()
|
||
UILogPrintF("获取stderr失败: %v", err)
|
||
return err
|
||
}
|
||
|
||
// 启动子进程
|
||
if err := m_cmd_process.Start(); err != nil {
|
||
m_cmd_mutex.Unlock()
|
||
UILogPrintF("启动失败: %v", err)
|
||
return err
|
||
}
|
||
m_cmd_mutex.Unlock()
|
||
|
||
// 处理进程输出的通用函数
|
||
handleProcessOutput := func(scanner *bufio.Scanner) {
|
||
for scanner.Scan() {
|
||
line := scanner.Text()
|
||
// 检查是否是状态消息
|
||
if status, ok := parseStatusMessage(line); ok {
|
||
updateConnectionStatus(status)
|
||
} else {
|
||
UILogPrintF(line)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 读取 stdout 和 stderr
|
||
go handleProcessOutput(bufio.NewScanner(stdout))
|
||
go handleProcessOutput(bufio.NewScanner(stderr))
|
||
|
||
return nil
|
||
}
|
||
|
||
func start_button_click() {
|
||
m_lock_start.Lock()
|
||
defer m_lock_start.Unlock()
|
||
|
||
//先对需要填写的数据进行校验
|
||
switch m_stats_start_button {
|
||
case 0:
|
||
switch GetWorkType() {
|
||
case workTypeLocal:
|
||
case workTypeRemote:
|
||
}
|
||
|
||
// 保存配置文件, 下次启动加载
|
||
configByte, _ := json.Marshal(&config.ConfigInfo{
|
||
WorkType: GetWorkType(),
|
||
TunKey: m_validated_key.Text,
|
||
})
|
||
log.Println(string(configByte))
|
||
os.Remove("goodlink.json")
|
||
go2.FileAppend("goodlink.json", configByte)
|
||
}
|
||
|
||
switch m_stats_start_button {
|
||
case 0:
|
||
m_button_start.Disable()
|
||
// 强制刷新工作端侧按钮高亮,确保选中项明显显示
|
||
updateWorkTypeButtons(GetWorkType())
|
||
disable_other("正在启动...")
|
||
|
||
// 设置自动重启标志
|
||
m_auto_restart_enabled = true
|
||
|
||
// 启动进程
|
||
if err := startCmdProcess(); err != nil {
|
||
enable_other()
|
||
m_button_start.Enable()
|
||
m_stats_start_button = 0
|
||
m_auto_restart_enabled = false
|
||
return
|
||
}
|
||
|
||
// 更新按钮状态并等待进程结束
|
||
go waitForProcessAndHandleExit(false)
|
||
|
||
case 1:
|
||
m_button_start.Disable()
|
||
|
||
// 停止子进程(在 goroutine 中执行,避免阻塞 UI)
|
||
go func() {
|
||
// 设置自动重启标志为false,防止误触发重启
|
||
m_auto_restart_enabled = false
|
||
StopCmdProcess()
|
||
|
||
m_stats_start_button = 0
|
||
enable_other()
|
||
updateButtonState(buttonStateIdle)
|
||
m_button_start.Enable()
|
||
}()
|
||
}
|
||
}
|
||
|
||
// waitForProcessAndHandleExit 等待进程结束并处理退出逻辑
|
||
func waitForProcessAndHandleExit(isRestart bool) {
|
||
time.Sleep(time.Second * 1)
|
||
if m_stats_start_button != 1 {
|
||
if !isRestart {
|
||
m_activity_start_button.Stop()
|
||
m_activity_start_button.Hide()
|
||
}
|
||
return
|
||
}
|
||
m_button_start.Enable()
|
||
|
||
// 根据模式设置初始按钮状态
|
||
if GetWorkType() == workTypeLocal {
|
||
updateButtonState(buttonStateConnecting)
|
||
} else {
|
||
updateButtonState(buttonStateStarting)
|
||
}
|
||
if !isRestart {
|
||
m_activity_start_button.Stop()
|
||
m_activity_start_button.Hide()
|
||
}
|
||
|
||
// 等待子进程结束
|
||
m_cmd_mutex.Lock()
|
||
proc := m_cmd_process
|
||
m_cmd_mutex.Unlock()
|
||
|
||
if proc != nil {
|
||
proc.Wait()
|
||
}
|
||
|
||
// 检查是否为异常退出(需要自动重启)
|
||
m_lock_start.Lock()
|
||
isAbnormalExit := m_stats_start_button == 1 && m_auto_restart_enabled
|
||
m_lock_start.Unlock()
|
||
|
||
if isAbnormalExit {
|
||
// 异常退出,自动重启
|
||
autoRestartProcess()
|
||
} else {
|
||
// 正常停止,恢复 UI
|
||
m_stats_start_button = 0
|
||
updateButtonState(buttonStateIdle)
|
||
m_button_start.Enable()
|
||
enable_other()
|
||
}
|
||
}
|
||
|
||
// autoRestartProcess 自动重启进程(当进程异常退出时调用)
|
||
func autoRestartProcess() {
|
||
// 短暂延迟,避免频繁重启
|
||
time.Sleep(500 * time.Millisecond)
|
||
|
||
// 检查是否仍然需要重启(用户可能在此期间手动停止了)
|
||
m_lock_start.Lock()
|
||
if m_stats_start_button != 1 || !m_auto_restart_enabled {
|
||
m_lock_start.Unlock()
|
||
return
|
||
}
|
||
m_lock_start.Unlock()
|
||
|
||
UILogPrintF("检测到进程异常退出,正在自动重启...")
|
||
|
||
// 重启进程
|
||
if err := startCmdProcess(); err != nil {
|
||
UILogPrintF("自动重启失败: %v", err)
|
||
// 重启失败,恢复UI
|
||
m_stats_start_button = 0
|
||
updateButtonState(buttonStateIdle)
|
||
m_button_start.Enable()
|
||
enable_other()
|
||
return
|
||
}
|
||
|
||
// 根据模式设置按钮状态
|
||
if GetWorkType() == workTypeLocal {
|
||
updateButtonState(buttonStateConnecting)
|
||
} else {
|
||
updateButtonState(buttonStateStarting)
|
||
}
|
||
|
||
// 启动新的等待goroutine
|
||
go waitForProcessAndHandleExit(true)
|
||
}
|