Skip to content

Commit 76d447d

Browse files
committed
Add Windows WI-FI state support
1 parent ab3caab commit 76d447d

File tree

4 files changed

+148
-10
lines changed

4 files changed

+148
-10
lines changed

common/settings/wifi_stub.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build !linux
1+
//go:build !linux && !windows
22

33
package settings
44

common/settings/wifi_windows.go

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
//go:build windows
2+
3+
package settings
4+
5+
import (
6+
"context"
7+
"fmt"
8+
"strings"
9+
"sync"
10+
"syscall"
11+
12+
"github.com/sagernet/sing-box/adapter"
13+
"github.com/sagernet/sing/common/winwlanapi"
14+
15+
"golang.org/x/sys/windows"
16+
)
17+
18+
type windowsWIFIMonitor struct {
19+
handle windows.Handle
20+
callback func(adapter.WIFIState)
21+
cancel context.CancelFunc
22+
lastState adapter.WIFIState
23+
mutex sync.Mutex
24+
}
25+
26+
func NewWIFIMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) {
27+
handle, err := winwlanapi.OpenHandle()
28+
if err != nil {
29+
return nil, err
30+
}
31+
32+
interfaces, err := winwlanapi.EnumInterfaces(handle)
33+
if err != nil {
34+
winwlanapi.CloseHandle(handle)
35+
return nil, err
36+
}
37+
if len(interfaces) == 0 {
38+
winwlanapi.CloseHandle(handle)
39+
return nil, fmt.Errorf("no wireless interfaces found")
40+
}
41+
42+
return &windowsWIFIMonitor{
43+
handle: handle,
44+
callback: callback,
45+
}, nil
46+
}
47+
48+
func (m *windowsWIFIMonitor) ReadWIFIState() adapter.WIFIState {
49+
interfaces, err := winwlanapi.EnumInterfaces(m.handle)
50+
if err != nil || len(interfaces) == 0 {
51+
return adapter.WIFIState{}
52+
}
53+
54+
for _, iface := range interfaces {
55+
if iface.InterfaceState != winwlanapi.InterfaceStateConnected {
56+
continue
57+
}
58+
59+
guid := iface.InterfaceGUID
60+
attrs, err := winwlanapi.QueryCurrentConnection(m.handle, &guid)
61+
if err != nil {
62+
continue
63+
}
64+
65+
ssidLength := attrs.AssociationAttributes.SSID.Length
66+
if ssidLength == 0 || ssidLength > winwlanapi.Dot11SSIDMaxLength {
67+
continue
68+
}
69+
70+
ssid := string(attrs.AssociationAttributes.SSID.SSID[:ssidLength])
71+
bssid := formatBSSID(attrs.AssociationAttributes.BSSID)
72+
73+
return adapter.WIFIState{
74+
SSID: strings.TrimSpace(ssid),
75+
BSSID: bssid,
76+
}
77+
}
78+
79+
return adapter.WIFIState{}
80+
}
81+
82+
func formatBSSID(mac winwlanapi.Dot11MacAddress) string {
83+
return fmt.Sprintf("%02X%02X%02X%02X%02X%02X",
84+
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5])
85+
}
86+
87+
func (m *windowsWIFIMonitor) Start() error {
88+
if m.callback == nil {
89+
return nil
90+
}
91+
92+
ctx, cancel := context.WithCancel(context.Background())
93+
m.cancel = cancel
94+
95+
m.lastState = m.ReadWIFIState()
96+
97+
callbackFunc := func(data *winwlanapi.NotificationData, callbackContext uintptr) uintptr {
98+
if data.NotificationSource != winwlanapi.NotificationSourceACM {
99+
return 0
100+
}
101+
switch data.NotificationCode {
102+
case winwlanapi.NotificationACMConnectionComplete,
103+
winwlanapi.NotificationACMDisconnected:
104+
m.checkAndNotify()
105+
}
106+
return 0
107+
}
108+
109+
callbackPointer := syscall.NewCallback(callbackFunc)
110+
111+
err := winwlanapi.RegisterNotification(m.handle, winwlanapi.NotificationSourceACM, callbackPointer, 0)
112+
if err != nil {
113+
cancel()
114+
return err
115+
}
116+
117+
go func() {
118+
<-ctx.Done()
119+
}()
120+
121+
m.callback(m.lastState)
122+
return nil
123+
}
124+
125+
func (m *windowsWIFIMonitor) checkAndNotify() {
126+
m.mutex.Lock()
127+
defer m.mutex.Unlock()
128+
129+
state := m.ReadWIFIState()
130+
if state != m.lastState {
131+
m.lastState = state
132+
if m.callback != nil {
133+
m.callback(state)
134+
}
135+
}
136+
}
137+
138+
func (m *windowsWIFIMonitor) Close() error {
139+
if m.cancel != nil {
140+
m.cancel()
141+
}
142+
winwlanapi.UnregisterNotification(m.handle)
143+
return winwlanapi.CloseHandle(m.handle)
144+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ require (
2929
github.com/sagernet/gomobile v0.1.8
3030
github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1
3131
github.com/sagernet/quic-go v0.57.1-sing-box-mod.1
32-
github.com/sagernet/sing v0.8.0-beta.6
32+
github.com/sagernet/sing v0.8.0-beta.6.0.20251207063731-56fd482ce1c6
3333
github.com/sagernet/sing-mux v0.3.3
3434
github.com/sagernet/sing-quic v0.6.0-beta.5
3535
github.com/sagernet/sing-shadowsocks v0.2.8

go.sum

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,6 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
139139
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
140140
github.com/prometheus-community/pro-bing v0.4.0 h1:YMbv+i08gQz97OZZBwLyvmmQEEzyfyrrjEaAchdy3R4=
141141
github.com/prometheus-community/pro-bing v0.4.0/go.mod h1:b7wRYZtCcPmt4Sz319BykUU241rWLe1VFXyiyWK/dH4=
142-
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
143-
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
144142
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
145143
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
146144
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
162160
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
163161
github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
164162
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
165-
github.com/sagernet/quic-go v0.55.0-sing-box-mod.2 h1:I79gW4Xl5ciVARHfnp122lDAMhC0AwUCU765Q8Kxdfo=
166-
github.com/sagernet/quic-go v0.55.0-sing-box-mod.2/go.mod h1:IE9naq7Kekj0rPAdWc0GLW1ENR7gAOQV9VRTDlKN8Bk=
167163
github.com/sagernet/quic-go v0.57.1-sing-box-mod.1 h1:6fhKbfA0b7L1CVekayV1g87uJFtMXFE0rFXR48SRrWI=
168164
github.com/sagernet/quic-go v0.57.1-sing-box-mod.1/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4=
169165
github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
170-
github.com/sagernet/sing v0.8.0-beta.6 h1:GXv1j1xWHihx6ptyOXh0yp4jUqJoNjCqD8d+AI9rnLU=
171-
github.com/sagernet/sing v0.8.0-beta.6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
166+
github.com/sagernet/sing v0.8.0-beta.6.0.20251207063731-56fd482ce1c6 h1:EYaDzllFzNYnzQ9xH/ieSAXct4wQ8pD45kgNMo7RPZc=
167+
github.com/sagernet/sing v0.8.0-beta.6.0.20251207063731-56fd482ce1c6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
172168
github.com/sagernet/sing-mux v0.3.3 h1:YFgt9plMWzH994BMZLmyKL37PdIVaIilwP0Jg+EcLfw=
173169
github.com/sagernet/sing-mux v0.3.3/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA=
174-
github.com/sagernet/sing-quic v0.6.0-beta.4 h1:2k/+Xrv/pjl7AYC7LD9tcB7y1lIgw04LjJjqTI8q5Xk=
175-
github.com/sagernet/sing-quic v0.6.0-beta.4/go.mod h1:FNvKPADzMZprwm7UQCcCGPhYifpb5rxoCOntOupJU+8=
176170
github.com/sagernet/sing-quic v0.6.0-beta.5 h1:kZfRLmsPxAgl0usZUgomDurLn7ZZ26lJWIpGow9ZWR4=
177171
github.com/sagernet/sing-quic v0.6.0-beta.5/go.mod h1:9D9GANrK33NjWCe1VkU5L5+8MxU39WrduBSmHuHz8GA=
178172
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=

0 commit comments

Comments
 (0)