mirror of
https://github.com/konyshe/GoodLink.git
synced 2026-08-31 01:13:22 +08:00
Remove main_cmd.go and main_ui.go files, simplifying the project structure. Update Makefile to reflect changes in build targets, removing UI-related entries. Refactor UI components in ui2 package to eliminate Windows-specific build constraints, enhancing cross-platform compatibility.
This commit is contained in:
@@ -12,16 +12,6 @@ GOBUILD=GO111MODULE=on \
|
||||
-X "go2.GitStatus=$(GitStatus)" \
|
||||
-X "go2.BuildTime=$(BuildTime)" \
|
||||
-w -s -buildid='
|
||||
GOBUILD_UI=CGO_ENABLED=0 GO111MODULE=on \
|
||||
GOPROXY="https://goproxy.cn,direct" \
|
||||
GOEXPERIMENT=jsonv2 \
|
||||
GOEXPERIMENT=loopvar \
|
||||
go build -trimpath -ldflags \
|
||||
'-H=windowsgui -X "go2.GitCommitLog=$(GitCommitLog)" \
|
||||
-X "go2.GitStatus=$(GitStatus)" \
|
||||
-X "go2.BuildTime=$(BuildTime)" \
|
||||
-w -s -buildid='
|
||||
|
||||
LINUX_PLATFORM_LIST = \
|
||||
linux-386-cmd \
|
||||
linux-amd64-cmd \
|
||||
@@ -40,7 +30,6 @@ DARWIN_PLATFORM_LIST = \
|
||||
darwin-arm64-cmd \
|
||||
|
||||
WINDOWS_PLATFORM_LIST = \
|
||||
windows-amd64-ui \
|
||||
windows-amd64-cmd \
|
||||
|
||||
WINDOWS_PLATFORM2_LIST = \
|
||||
@@ -56,59 +45,55 @@ windows: create_nac $(WINDOWS_PLATFORM_LIST) rm_nac strip
|
||||
linux: $(LINUX_PLATFORM_LIST) strip
|
||||
|
||||
linux-386-cmd:
|
||||
GOARCH=386 GOOS=linux $(GOBUILD) -tags "cmd" -o $(BINDIR)/$(NAME)-$@
|
||||
GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
linux-amd64-cmd:
|
||||
GOARCH=amd64 GOOS=linux $(GOBUILD) -tags "cmd" -o $(BINDIR)/$(NAME)-$@
|
||||
GOARCH=amd64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
linux-loong64-cmd:
|
||||
GOARCH=loong64 GOOS=linux $(GOBUILD) -tags "cmd" -o $(BINDIR)/$(NAME)-$@
|
||||
GOARCH=loong64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
linux-mips-cmd:
|
||||
GOARCH=mips GOOS=linux $(GOBUILD) -tags "cmd" -o $(BINDIR)/$(NAME)-$@
|
||||
GOARCH=mips GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
linux-mipsle-cmd:
|
||||
GOARCH=mipsle GOOS=linux $(GOBUILD) -tags "cmd" -o $(BINDIR)/$(NAME)-$@
|
||||
GOARCH=mipsle GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
linux-mips64-cmd:
|
||||
GOARCH=mips64 GOOS=linux $(GOBUILD) -tags "cmd" -o $(BINDIR)/$(NAME)-$@
|
||||
GOARCH=mips64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
linux-mips64le-cmd:
|
||||
GOARCH=mips64le GOOS=linux $(GOBUILD) -tags "cmd" -o $(BINDIR)/$(NAME)-$@
|
||||
GOARCH=mips64le GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
linux-riscv64-cmd:
|
||||
GOARCH=riscv64 GOOS=linux $(GOBUILD) -tags "cmd" -o $(BINDIR)/$(NAME)-$@
|
||||
GOARCH=riscv64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
linux-arm-cmd:
|
||||
GOARCH=arm GOOS=linux $(GOBUILD) -tags "cmd" -o $(BINDIR)/$(NAME)-$@
|
||||
GOARCH=arm GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
linux-armv6l-cmd:
|
||||
GOARCH=arm GOARM=6 GOOS=linux $(GOBUILD) -tags "cmd" -o $(BINDIR)/$(NAME)-$@
|
||||
GOARCH=arm GOARM=6 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
linux-arm64-cmd:
|
||||
GOARCH=arm64 GOOS=linux $(GOBUILD) -tags "cmd" -o $(BINDIR)/$(NAME)-$@
|
||||
GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
darwin-amd64-cmd:
|
||||
GOARCH=amd64 GOOS=darwin $(GOBUILD) -tags "cmd" -o $(BINDIR)/$(NAME)-$@
|
||||
GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
darwin-arm64-cmd:
|
||||
GOARCH=arm64 GOOS=darwin $(GOBUILD) -tags "cmd" -o $(BINDIR)/$(NAME)-$@
|
||||
GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
windows-amd64-cmd:
|
||||
GOARCH=amd64 GOOS=windows $(GOBUILD) -tags "cmd" -o $(BINDIR)/$(NAME)-$@.exe
|
||||
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||
|
||||
windows-arm64-cmd:
|
||||
GOARCH=arm64 GOOS=windows $(GOBUILD) -tags "cmd" -o $(BINDIR)/$(NAME)-$@.exe
|
||||
|
||||
windows-amd64-ui:
|
||||
mkdir -p $(BINDIR)
|
||||
GOARCH=amd64 GOOS=windows $(GOBUILD_UI) -o $(BINDIR)/$(NAME)-$@.exe
|
||||
GOARCH=arm64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||
|
||||
windows-386-cmd:
|
||||
GOARCH=386 GOOS=windows $(GOBUILD) -tags "cmd" -o $(BINDIR)/$(NAME)-$@.exe
|
||||
GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||
|
||||
windows-arm-cmd:
|
||||
GOARCH=arm GOOS=windows $(GOBUILD) -tags "cmd" -o $(BINDIR)/$(NAME)-$@.exe
|
||||
GOARCH=arm GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||
create_nac:
|
||||
rsrc -manifest nac.manifest -ico assert/favicon.ico -o nac.syso
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
//go:build !windows
|
||||
|
||||
package main
|
||||
|
||||
func hideUIConsole() {}
|
||||
@@ -0,0 +1,11 @@
|
||||
//go:build windows
|
||||
|
||||
package main
|
||||
|
||||
import "golang.org/x/sys/windows"
|
||||
|
||||
func hideUIConsole() {
|
||||
kernel32 := windows.NewLazySystemDLL("kernel32.dll")
|
||||
procFreeConsole := kernel32.NewProc("FreeConsole")
|
||||
_, _, _ = procFreeConsole.Call()
|
||||
}
|
||||
+55
-2
@@ -1,8 +1,7 @@
|
||||
//go:build cmd
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"flag"
|
||||
go2log "go2/log"
|
||||
go2pool "go2/pool"
|
||||
@@ -10,6 +9,7 @@ import (
|
||||
"goodlink/pro"
|
||||
_ "goodlink/pro"
|
||||
"goodlink/stun2"
|
||||
"goodlink/ui2"
|
||||
"goodlink/utils"
|
||||
goodlink_config "goodlink3/config"
|
||||
"log"
|
||||
@@ -23,6 +23,9 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
//go:embed assert/tray_idle.ico assert/tray_warning.ico assert/tray_danger.ico assert/tray_success.ico
|
||||
var trayIcons embed.FS
|
||||
|
||||
func main2() {
|
||||
log.Println("官方网址: https://gitee.com/konyshe/goodlink")
|
||||
|
||||
@@ -79,7 +82,57 @@ func main2() {
|
||||
pro.StopRemote()
|
||||
}
|
||||
|
||||
func runUI() {
|
||||
// 隐藏 UI 主进程控制台窗口(仅 Windows;从已有终端启动时只脱离本进程)
|
||||
hideUIConsole()
|
||||
|
||||
// 检查单实例,如果不是第一个实例则退出
|
||||
// 必须在创建任何UI资源之前检查,避免影响已运行的实例
|
||||
if !utils.CheckSingleInstance() {
|
||||
// 已有实例运行,直接退出
|
||||
return
|
||||
}
|
||||
|
||||
// 启动前清理遗留的cmd进程
|
||||
utils.CleanupOrphanedCmdProcesses()
|
||||
|
||||
goodlink_config.DeleteLocalConfig()
|
||||
|
||||
config.SetVersion(GetVersionFromAppConfig())
|
||||
|
||||
idle, _ := trayIcons.ReadFile("assert/tray_idle.ico")
|
||||
warning, _ := trayIcons.ReadFile("assert/tray_warning.ico")
|
||||
danger, _ := trayIcons.ReadFile("assert/tray_danger.ico")
|
||||
success, _ := trayIcons.ReadFile("assert/tray_success.ico")
|
||||
ui2.InitTrayIcons(idle, warning, danger, success)
|
||||
|
||||
ui2.Init()
|
||||
|
||||
uiURL, err := ui2.StartServer()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 监听显示窗口请求(二次启动时重新打开浏览器)
|
||||
go func() {
|
||||
for range utils.GetShowWindowChan() {
|
||||
ui2.OpenBrowser(uiURL)
|
||||
}
|
||||
}()
|
||||
|
||||
ui2.OpenBrowser(uiURL)
|
||||
|
||||
// Windows:主线程运行托盘消息循环;Linux/macOS:等待信号后退出
|
||||
ui2.RunLoop(uiURL)
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 无任何参数时启动 UI(Windows/Linux/macOS)
|
||||
if len(os.Args) <= 1 {
|
||||
runUI()
|
||||
return
|
||||
}
|
||||
|
||||
config.SetVersion(GetVersionFromAppConfig())
|
||||
config.Help()
|
||||
|
||||
-61
@@ -1,61 +0,0 @@
|
||||
//go:build windows && !cmd
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"goodlink/config"
|
||||
"goodlink/ui2"
|
||||
"goodlink/utils"
|
||||
goodlink_config "goodlink3/config"
|
||||
|
||||
"github.com/getlantern/systray"
|
||||
)
|
||||
|
||||
//go:embed assert/tray_idle.ico assert/tray_warning.ico assert/tray_danger.ico assert/tray_success.ico
|
||||
var trayIcons embed.FS
|
||||
|
||||
func main() {
|
||||
// 检查单实例,如果不是第一个实例则退出
|
||||
// 必须在创建任何UI资源之前检查,避免影响已运行的实例
|
||||
if !utils.CheckSingleInstance() {
|
||||
// 已有实例运行,直接退出
|
||||
return
|
||||
}
|
||||
|
||||
// 启动前清理遗留的cmd进程
|
||||
utils.CleanupOrphanedCmdProcesses()
|
||||
|
||||
goodlink_config.DeleteLocalConfig()
|
||||
|
||||
config.SetVersion(GetVersionFromAppConfig())
|
||||
|
||||
config.Help()
|
||||
|
||||
idle, _ := trayIcons.ReadFile("assert/tray_idle.ico")
|
||||
warning, _ := trayIcons.ReadFile("assert/tray_warning.ico")
|
||||
danger, _ := trayIcons.ReadFile("assert/tray_danger.ico")
|
||||
success, _ := trayIcons.ReadFile("assert/tray_success.ico")
|
||||
ui2.InitTrayIcons(idle, warning, danger, success)
|
||||
|
||||
ui2.Init()
|
||||
|
||||
uiURL, err := ui2.StartServer()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 监听显示窗口请求(二次启动时重新打开浏览器)
|
||||
go func() {
|
||||
for range utils.GetShowWindowChan() {
|
||||
ui2.OpenBrowser(uiURL)
|
||||
}
|
||||
}()
|
||||
|
||||
ui2.OpenBrowser(uiURL)
|
||||
|
||||
// 主线程运行托盘消息循环;退出时停止子进程
|
||||
systray.Run(func() {
|
||||
ui2.SetupTray(uiURL)
|
||||
}, ui2.OnTrayExit)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
//go:build darwin
|
||||
|
||||
package ui2
|
||||
|
||||
import "os/exec"
|
||||
|
||||
func OpenBrowser(url string) {
|
||||
_ = exec.Command("open", url).Start()
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
//go:build linux
|
||||
|
||||
package ui2
|
||||
|
||||
import "os/exec"
|
||||
|
||||
func OpenBrowser(url string) {
|
||||
_ = exec.Command("xdg-open", url).Start()
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
//go:build windows
|
||||
|
||||
package ui2
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func OpenBrowser(url string) {
|
||||
cmd := exec.Command("cmd", "/c", "start", "", url)
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
HideWindow: true,
|
||||
CreationFlags: 0x08000000, // CREATE_NO_WINDOW
|
||||
}
|
||||
_ = cmd.Start()
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:build windows
|
||||
|
||||
package ui2
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:build windows
|
||||
|
||||
package ui2
|
||||
|
||||
import (
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
//go:build !windows
|
||||
|
||||
package ui2
|
||||
|
||||
import "os/exec"
|
||||
|
||||
func hideChildWindow(cmd *exec.Cmd) {}
|
||||
@@ -0,0 +1,15 @@
|
||||
//go:build windows
|
||||
|
||||
package ui2
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func hideChildWindow(cmd *exec.Cmd) {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
HideWindow: true,
|
||||
CreationFlags: 0x08000000, // CREATE_NO_WINDOW
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:build windows
|
||||
|
||||
package ui2
|
||||
|
||||
import (
|
||||
@@ -8,8 +6,6 @@ import (
|
||||
"io/fs"
|
||||
"net"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -18,15 +14,6 @@ var webFS embed.FS
|
||||
|
||||
const defaultUIAddr = "127.0.0.1:16780"
|
||||
|
||||
func OpenBrowser(url string) {
|
||||
cmd := exec.Command("cmd", "/c", "start", "", url)
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
HideWindow: true,
|
||||
CreationFlags: 0x08000000, // CREATE_NO_WINDOW
|
||||
}
|
||||
_ = cmd.Start()
|
||||
}
|
||||
|
||||
func StartServer() (string, error) {
|
||||
ln, err := net.Listen("tcp", defaultUIAddr)
|
||||
if err != nil {
|
||||
|
||||
+2
-8
@@ -1,5 +1,3 @@
|
||||
//go:build windows
|
||||
|
||||
package ui2
|
||||
|
||||
import (
|
||||
@@ -13,7 +11,6 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"goodlink/pro"
|
||||
@@ -116,11 +113,8 @@ func startCmdProcess() error {
|
||||
m_cmd_mutex.Lock()
|
||||
m_cmd_process = exec.Command(cmdPath, args...)
|
||||
|
||||
// 隐藏子进程窗口
|
||||
m_cmd_process.SysProcAttr = &syscall.SysProcAttr{
|
||||
HideWindow: true,
|
||||
CreationFlags: 0x08000000, // CREATE_NO_WINDOW
|
||||
}
|
||||
// 隐藏子进程窗口(仅 Windows)
|
||||
hideChildWindow(m_cmd_process)
|
||||
|
||||
// 获取输出管道
|
||||
stdout, err := m_cmd_process.StdoutPipe()
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:build windows
|
||||
|
||||
package ui2
|
||||
|
||||
import (
|
||||
|
||||
@@ -54,6 +54,12 @@ func UpdateTrayIcon(state buttonState) {
|
||||
}
|
||||
}
|
||||
|
||||
func RunLoop(uiURL string) {
|
||||
systray.Run(func() {
|
||||
SetupTray(uiURL)
|
||||
}, OnTrayExit)
|
||||
}
|
||||
|
||||
func SetupTray(uiURL string) {
|
||||
stateMu.RLock()
|
||||
btn := currentButton
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
//go:build !windows
|
||||
|
||||
package ui2
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func InitTrayIcons(_, _, _, _ []byte) {}
|
||||
|
||||
func UpdateTrayIcon(_ buttonState) {}
|
||||
|
||||
func RunLoop(_ string) {
|
||||
ch := make(chan os.Signal, 1)
|
||||
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-ch
|
||||
StopCmdProcess()
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package utils
|
||||
|
||||
import "os"
|
||||
|
||||
// GetCmdExePath 返回当前可执行文件路径,UI 通过带参数 fork 自身走 CLI
|
||||
func GetCmdExePath() string {
|
||||
exePath, err := os.Executable()
|
||||
if err != nil {
|
||||
return os.Args[0]
|
||||
}
|
||||
return exePath
|
||||
}
|
||||
+7
-22
@@ -11,41 +11,28 @@ import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// 获取 cmd 版本可执行文件路径
|
||||
func GetCmdExePath() string {
|
||||
exePath, err := os.Executable()
|
||||
if err != nil {
|
||||
return "goodlink-windows-amd64-cmd.exe"
|
||||
}
|
||||
dir := filepath.Dir(exePath)
|
||||
return filepath.Join(dir, "goodlink-windows-amd64-cmd.exe")
|
||||
}
|
||||
|
||||
// killProcess 强制终止进程(使用 taskkill 确保终止)
|
||||
// KillProcess 强制终止进程(使用 taskkill 确保终止)
|
||||
func KillProcess(pid int) {
|
||||
// 先尝试用 taskkill /F /T 强制终止进程树
|
||||
if pid == os.Getpid() {
|
||||
return
|
||||
}
|
||||
cmd := exec.Command("taskkill", "/F", "/T", "/PID", fmt.Sprintf("%d", pid))
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
|
||||
cmd.Run()
|
||||
}
|
||||
|
||||
// CleanupOrphanedCmdProcesses 清理遗留的cmd进程(通过进程名查找并终止)
|
||||
// CleanupOrphanedCmdProcesses 清理遗留的工作进程(通过进程名查找并终止,跳过当前 PID)
|
||||
func CleanupOrphanedCmdProcesses() {
|
||||
selfPid := os.Getpid()
|
||||
cmdExeName := filepath.Base(GetCmdExePath())
|
||||
|
||||
// 使用 tasklist 查找所有匹配的进程
|
||||
cmd := exec.Command("tasklist", "/FI", fmt.Sprintf("IMAGENAME eq %s", cmdExeName), "/FO", "CSV", "/NH")
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
// 如果 tasklist 失败,尝试直接使用 taskkill 按进程名终止
|
||||
killCmd := exec.Command("taskkill", "/F", "/IM", cmdExeName)
|
||||
killCmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
|
||||
killCmd.Run()
|
||||
return
|
||||
}
|
||||
|
||||
// 解析输出,提取PID
|
||||
lines := strings.Split(string(output), "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
@@ -53,13 +40,11 @@ func CleanupOrphanedCmdProcesses() {
|
||||
continue
|
||||
}
|
||||
// CSV格式: "进程名","PID","会话名","会话#","内存使用"
|
||||
// 例如: "goodlink-windows-amd64-cmd.exe","1234","Console","1","12345 K"
|
||||
parts := strings.Split(line, ",")
|
||||
if len(parts) >= 2 {
|
||||
// 移除引号
|
||||
pidStr := strings.Trim(parts[1], "\"")
|
||||
var pid int
|
||||
if _, err := fmt.Sscanf(pidStr, "%d", &pid); err == nil {
|
||||
if _, err := fmt.Sscanf(pidStr, "%d", &pid); err == nil && pid != selfPid {
|
||||
KillProcess(pid)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
//go:build unix
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func KillProcess(pid int) {
|
||||
if pid == os.Getpid() {
|
||||
return
|
||||
}
|
||||
proc, err := os.FindProcess(pid)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_ = proc.Kill()
|
||||
}
|
||||
|
||||
func CleanupOrphanedCmdProcesses() {
|
||||
selfPid := os.Getpid()
|
||||
exePath := GetCmdExePath()
|
||||
|
||||
out, err := exec.Command("pgrep", "-f", exePath).Output()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, field := range strings.Fields(string(out)) {
|
||||
var pid int
|
||||
if _, err := fmt.Sscanf(field, "%d", &pid); err == nil && pid != selfPid {
|
||||
KillProcess(pid)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:build cmd
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
|
||||
+10
-88
@@ -1,104 +1,41 @@
|
||||
//go:build windows
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const (
|
||||
MUTEX_NAME = "Global\\GoodLinkSingleInstance"
|
||||
NOTIFY_FILE = "goodlink_notify.tmp"
|
||||
NOTIFY_CHECK_INTERVAL = 500 * time.Millisecond
|
||||
)
|
||||
|
||||
var (
|
||||
kernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||
procCreateMutex = kernel32.NewProc("CreateMutexW")
|
||||
procCloseHandle = kernel32.NewProc("CloseHandle")
|
||||
|
||||
mutexHandle uintptr
|
||||
showWindowChan chan struct{} // 用于通知主线程显示窗口的channel
|
||||
)
|
||||
|
||||
// CreateMutex 创建互斥锁
|
||||
func CreateMutex(name string) (uintptr, error) {
|
||||
namePtr, err := syscall.UTF16PtrFromString(name)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
ret, _, err := procCreateMutex.Call(
|
||||
0,
|
||||
0,
|
||||
uintptr(unsafe.Pointer(namePtr)),
|
||||
)
|
||||
if ret == 0 {
|
||||
return 0, fmt.Errorf("创建互斥锁失败: %v", err)
|
||||
}
|
||||
|
||||
// 检查是否已存在(ERROR_ALREADY_EXISTS = 183)
|
||||
if err.(syscall.Errno) == 183 {
|
||||
return ret, fmt.Errorf("实例已存在")
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// CloseHandle 关闭句柄
|
||||
func CloseHandle(handle uintptr) error {
|
||||
ret, _, _ := procCloseHandle.Call(handle)
|
||||
if ret == 0 {
|
||||
return fmt.Errorf("关闭句柄失败")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
var showWindowChan chan struct{} // 用于通知主线程显示窗口的channel
|
||||
|
||||
// NotifyExistingInstance 通知已存在的实例显示窗口
|
||||
func NotifyExistingInstance() error {
|
||||
// 创建通知文件
|
||||
exePath, err := os.Executable()
|
||||
notifyPath, err := notifyFilePath()
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取可执行文件路径失败: %v", err)
|
||||
return err
|
||||
}
|
||||
dir := filepath.Dir(exePath)
|
||||
notifyPath := filepath.Join(dir, NOTIFY_FILE)
|
||||
|
||||
// 写入通知文件
|
||||
err = os.WriteFile(notifyPath, []byte("SHOW_WINDOW"), 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("写入通知文件失败: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return os.WriteFile(notifyPath, []byte("SHOW_WINDOW"), 0644)
|
||||
}
|
||||
|
||||
// StartInstanceListener 启动实例监听器,监听来自其他实例的显示窗口请求
|
||||
func StartInstanceListener() {
|
||||
go func() {
|
||||
exePath, err := os.Executable()
|
||||
notifyPath, err := notifyFilePath()
|
||||
if err != nil {
|
||||
log.Printf("获取可执行文件路径失败: %v", err)
|
||||
log.Printf("获取通知文件路径失败: %v", err)
|
||||
return
|
||||
}
|
||||
dir := filepath.Dir(exePath)
|
||||
notifyPath := filepath.Join(dir, NOTIFY_FILE)
|
||||
|
||||
for {
|
||||
// 检查通知文件是否存在
|
||||
if _, err := os.Stat(notifyPath); err == nil {
|
||||
// 文件存在,读取内容
|
||||
data, err := os.ReadFile(notifyPath)
|
||||
if err == nil && string(data) == "SHOW_WINDOW" {
|
||||
// 删除通知文件
|
||||
os.Remove(notifyPath)
|
||||
|
||||
// 通过channel通知主线程显示窗口(非阻塞)
|
||||
@@ -120,25 +57,10 @@ func GetShowWindowChan() <-chan struct{} {
|
||||
return showWindowChan
|
||||
}
|
||||
|
||||
// CheckSingleInstance 检查是否为单实例,如果不是第一个实例则返回false
|
||||
func CheckSingleInstance() bool {
|
||||
handle, err := CreateMutex(MUTEX_NAME)
|
||||
func exeDir() (string, error) {
|
||||
exePath, err := os.Executable()
|
||||
if err != nil {
|
||||
// 实例已存在,通知第一个实例显示窗口
|
||||
if notifyErr := NotifyExistingInstance(); notifyErr != nil {
|
||||
log.Printf("通知已存在实例失败: %v", notifyErr)
|
||||
}
|
||||
return false
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 保存互斥锁句柄,程序退出时系统会自动释放
|
||||
mutexHandle = handle
|
||||
|
||||
// 初始化channel(带缓冲,避免阻塞)
|
||||
showWindowChan = make(chan struct{}, 1)
|
||||
|
||||
// 启动监听器,等待其他实例的请求
|
||||
StartInstanceListener()
|
||||
|
||||
return true
|
||||
return filepath.Dir(exePath), nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
//go:build unix
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var lockFile *os.File
|
||||
|
||||
func notifyFilePath() (string, error) {
|
||||
return filepath.Join(os.TempDir(), NOTIFY_FILE), nil
|
||||
}
|
||||
|
||||
// CheckSingleInstance 检查是否为单实例,如果不是第一个实例则返回false
|
||||
func CheckSingleInstance() bool {
|
||||
lockPath := filepath.Join(os.TempDir(), "goodlink.single.lock")
|
||||
f, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0644)
|
||||
if err != nil {
|
||||
log.Printf("创建单实例锁文件失败: %v", err)
|
||||
return true
|
||||
}
|
||||
|
||||
if err := unix.Flock(int(f.Fd()), unix.LOCK_EX|unix.LOCK_NB); err != nil {
|
||||
f.Close()
|
||||
if notifyErr := NotifyExistingInstance(); notifyErr != nil {
|
||||
log.Printf("通知已存在实例失败: %v", notifyErr)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
lockFile = f
|
||||
showWindowChan = make(chan struct{}, 1)
|
||||
StartInstanceListener()
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
//go:build windows
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const MUTEX_NAME = "Global\\GoodLinkSingleInstance"
|
||||
|
||||
var (
|
||||
kernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||
procCreateMutex = kernel32.NewProc("CreateMutexW")
|
||||
procCloseHandle = kernel32.NewProc("CloseHandle")
|
||||
|
||||
mutexHandle uintptr
|
||||
)
|
||||
|
||||
func notifyFilePath() (string, error) {
|
||||
dir, err := exeDir()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("获取可执行文件路径失败: %v", err)
|
||||
}
|
||||
return filepath.Join(dir, NOTIFY_FILE), nil
|
||||
}
|
||||
|
||||
func CreateMutex(name string) (uintptr, error) {
|
||||
namePtr, err := syscall.UTF16PtrFromString(name)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
ret, _, err := procCreateMutex.Call(
|
||||
0,
|
||||
0,
|
||||
uintptr(unsafe.Pointer(namePtr)),
|
||||
)
|
||||
if ret == 0 {
|
||||
return 0, fmt.Errorf("创建互斥锁失败: %v", err)
|
||||
}
|
||||
|
||||
// 检查是否已存在(ERROR_ALREADY_EXISTS = 183)
|
||||
if err.(syscall.Errno) == 183 {
|
||||
return ret, fmt.Errorf("实例已存在")
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func CloseHandle(handle uintptr) error {
|
||||
ret, _, _ := procCloseHandle.Call(handle)
|
||||
if ret == 0 {
|
||||
return fmt.Errorf("关闭句柄失败")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckSingleInstance 检查是否为单实例,如果不是第一个实例则返回false
|
||||
func CheckSingleInstance() bool {
|
||||
handle, err := CreateMutex(MUTEX_NAME)
|
||||
if err != nil {
|
||||
// 实例已存在,通知第一个实例显示窗口
|
||||
if notifyErr := NotifyExistingInstance(); notifyErr != nil {
|
||||
log.Printf("通知已存在实例失败: %v", notifyErr)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 保存互斥锁句柄,程序退出时系统会自动释放
|
||||
mutexHandle = handle
|
||||
|
||||
// 初始化channel(带缓冲,避免阻塞)
|
||||
showWindowChan = make(chan struct{}, 1)
|
||||
|
||||
// 启动监听器,等待其他实例的请求
|
||||
StartInstanceListener()
|
||||
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user