diff --git a/common/settings/wifi_stub.go b/common/settings/wifi_stub.go index 98db500fd..fd39af9e6 100644 --- a/common/settings/wifi_stub.go +++ b/common/settings/wifi_stub.go @@ -1,4 +1,4 @@ -//go:build !linux +//go:build !linux && !windows package settings diff --git a/common/settings/wifi_windows.go b/common/settings/wifi_windows.go new file mode 100644 index 000000000..91b0d479c --- /dev/null +++ b/common/settings/wifi_windows.go @@ -0,0 +1,144 @@ +//go:build windows + +package settings + +import ( + "context" + "fmt" + "strings" + "sync" + "syscall" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing/common/winwlanapi" + + "golang.org/x/sys/windows" +) + +type windowsWIFIMonitor struct { + handle windows.Handle + callback func(adapter.WIFIState) + cancel context.CancelFunc + lastState adapter.WIFIState + mutex sync.Mutex +} + +func NewWIFIMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) { + handle, err := winwlanapi.OpenHandle() + if err != nil { + return nil, err + } + + interfaces, err := winwlanapi.EnumInterfaces(handle) + if err != nil { + winwlanapi.CloseHandle(handle) + return nil, err + } + if len(interfaces) == 0 { + winwlanapi.CloseHandle(handle) + return nil, fmt.Errorf("no wireless interfaces found") + } + + return &windowsWIFIMonitor{ + handle: handle, + callback: callback, + }, nil +} + +func (m *windowsWIFIMonitor) ReadWIFIState() adapter.WIFIState { + interfaces, err := winwlanapi.EnumInterfaces(m.handle) + if err != nil || len(interfaces) == 0 { + return adapter.WIFIState{} + } + + for _, iface := range interfaces { + if iface.InterfaceState != winwlanapi.InterfaceStateConnected { + continue + } + + guid := iface.InterfaceGUID + attrs, err := winwlanapi.QueryCurrentConnection(m.handle, &guid) + if err != nil { + continue + } + + ssidLength := attrs.AssociationAttributes.SSID.Length + if ssidLength == 0 || ssidLength > winwlanapi.Dot11SSIDMaxLength { + continue + } + + ssid := string(attrs.AssociationAttributes.SSID.SSID[:ssidLength]) + bssid := formatBSSID(attrs.AssociationAttributes.BSSID) + + return adapter.WIFIState{ + SSID: strings.TrimSpace(ssid), + BSSID: bssid, + } + } + + return adapter.WIFIState{} +} + +func formatBSSID(mac winwlanapi.Dot11MacAddress) string { + return fmt.Sprintf("%02X%02X%02X%02X%02X%02X", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]) +} + +func (m *windowsWIFIMonitor) Start() error { + if m.callback == nil { + return nil + } + + ctx, cancel := context.WithCancel(context.Background()) + m.cancel = cancel + + m.lastState = m.ReadWIFIState() + + callbackFunc := func(data *winwlanapi.NotificationData, callbackContext uintptr) uintptr { + if data.NotificationSource != winwlanapi.NotificationSourceACM { + return 0 + } + switch data.NotificationCode { + case winwlanapi.NotificationACMConnectionComplete, + winwlanapi.NotificationACMDisconnected: + m.checkAndNotify() + } + return 0 + } + + callbackPointer := syscall.NewCallback(callbackFunc) + + err := winwlanapi.RegisterNotification(m.handle, winwlanapi.NotificationSourceACM, callbackPointer, 0) + if err != nil { + cancel() + return err + } + + go func() { + <-ctx.Done() + }() + + m.callback(m.lastState) + return nil +} + +func (m *windowsWIFIMonitor) checkAndNotify() { + m.mutex.Lock() + defer m.mutex.Unlock() + + state := m.ReadWIFIState() + if state != m.lastState { + m.lastState = state + if m.callback != nil { + m.callback(state) + } + } +} + +func (m *windowsWIFIMonitor) Close() error { + if m.cancel != nil { + m.cancel() + } + winwlanapi.UnregisterNotification(m.handle) + return winwlanapi.CloseHandle(m.handle) +} diff --git a/go.mod b/go.mod index eb6f0ca22..ebbdf620e 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/sagernet/gomobile v0.1.8 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 github.com/sagernet/quic-go v0.57.1-sing-box-mod.1 - github.com/sagernet/sing v0.8.0-beta.6 + github.com/sagernet/sing v0.8.0-beta.6.0.20251207063731-56fd482ce1c6 github.com/sagernet/sing-mux v0.3.3 github.com/sagernet/sing-quic v0.6.0-beta.5 github.com/sagernet/sing-shadowsocks v0.2.8 diff --git a/go.sum b/go.sum index 1686e1478..10f056846 100644 --- a/go.sum +++ b/go.sum @@ -139,8 +139,6 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus-community/pro-bing v0.4.0 h1:YMbv+i08gQz97OZZBwLyvmmQEEzyfyrrjEaAchdy3R4= github.com/prometheus-community/pro-bing v0.4.0/go.mod h1:b7wRYZtCcPmt4Sz319BykUU241rWLe1VFXyiyWK/dH4= -github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= -github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -162,17 +160,13 @@ github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZN github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I= github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= -github.com/sagernet/quic-go v0.55.0-sing-box-mod.2 h1:I79gW4Xl5ciVARHfnp122lDAMhC0AwUCU765Q8Kxdfo= -github.com/sagernet/quic-go v0.55.0-sing-box-mod.2/go.mod h1:IE9naq7Kekj0rPAdWc0GLW1ENR7gAOQV9VRTDlKN8Bk= github.com/sagernet/quic-go v0.57.1-sing-box-mod.1 h1:6fhKbfA0b7L1CVekayV1g87uJFtMXFE0rFXR48SRrWI= github.com/sagernet/quic-go v0.57.1-sing-box-mod.1/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4= github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/sagernet/sing v0.8.0-beta.6 h1:GXv1j1xWHihx6ptyOXh0yp4jUqJoNjCqD8d+AI9rnLU= -github.com/sagernet/sing v0.8.0-beta.6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.8.0-beta.6.0.20251207063731-56fd482ce1c6 h1:EYaDzllFzNYnzQ9xH/ieSAXct4wQ8pD45kgNMo7RPZc= +github.com/sagernet/sing v0.8.0-beta.6.0.20251207063731-56fd482ce1c6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.3 h1:YFgt9plMWzH994BMZLmyKL37PdIVaIilwP0Jg+EcLfw= github.com/sagernet/sing-mux v0.3.3/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA= -github.com/sagernet/sing-quic v0.6.0-beta.4 h1:2k/+Xrv/pjl7AYC7LD9tcB7y1lIgw04LjJjqTI8q5Xk= -github.com/sagernet/sing-quic v0.6.0-beta.4/go.mod h1:FNvKPADzMZprwm7UQCcCGPhYifpb5rxoCOntOupJU+8= github.com/sagernet/sing-quic v0.6.0-beta.5 h1:kZfRLmsPxAgl0usZUgomDurLn7ZZ26lJWIpGow9ZWR4= github.com/sagernet/sing-quic v0.6.0-beta.5/go.mod h1:9D9GANrK33NjWCe1VkU5L5+8MxU39WrduBSmHuHz8GA= github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=