|
|
@@ -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)
|
|
|
+}
|