mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-06-16 16:15:23 -04:00
feat(network): add saved WiFi state to settings (#2648)
This commit is contained in:
@@ -125,6 +125,8 @@ State updates are sent whenever network configuration changes:
|
||||
- `wifiConnected`: Whether associated with an access point
|
||||
- `wifiSSID`: Currently connected network name
|
||||
- `wifiIP`: Assigned IP address (empty until DHCP completes)
|
||||
- `savedWifiNetworks` (API v26+): Saved WiFi profiles exposed at SSID granularity. If a backend has multiple profiles for the same SSID, DMS merges them into one SSID-level entry. Clients talking to older servers should derive saved visible networks from `wifiNetworks` entries where `saved` is true.
|
||||
- `savedWifiNetworks[].outOfRange` (API v26+): Whether the saved profile is not currently visible in scan results. Fallback entries derived from `wifiNetworks` should be treated as visible (`outOfRange: false`).
|
||||
- `lastError`: Error message from last failed connection attempt
|
||||
|
||||
### network.credentials Service Events
|
||||
|
||||
@@ -67,6 +67,7 @@ type BackendState struct {
|
||||
WiFiBSSID string
|
||||
WiFiSignal uint8
|
||||
WiFiNetworks []WiFiNetwork
|
||||
SavedWiFiNetworks []WiFiNetwork
|
||||
WiFiDevices []WiFiDevice
|
||||
WiredConnections []WiredConnection
|
||||
VPNProfiles []VPNProfile
|
||||
|
||||
@@ -27,6 +27,19 @@ func TestHybridIwdNetworkdBackend_GetCurrentState_MergesState(t *testing.T) {
|
||||
wifi.state.WiFiBSSID = "00:11:22:33:44:55"
|
||||
wifi.state.WiFiSignal = 75
|
||||
wifi.state.WiFiDevice = "wlan0"
|
||||
wifi.state.SavedWiFiNetworks = []WiFiNetwork{
|
||||
{
|
||||
SSID: "TestNetwork",
|
||||
Saved: true,
|
||||
Autoconnect: true,
|
||||
Connected: true,
|
||||
},
|
||||
{
|
||||
SSID: "AwayNetwork",
|
||||
Saved: true,
|
||||
OutOfRange: true,
|
||||
},
|
||||
}
|
||||
|
||||
l3.state.WiFiIP = "192.168.1.100"
|
||||
l3.state.EthernetConnected = false
|
||||
@@ -42,6 +55,9 @@ func TestHybridIwdNetworkdBackend_GetCurrentState_MergesState(t *testing.T) {
|
||||
assert.True(t, state.WiFiConnected)
|
||||
assert.False(t, state.EthernetConnected)
|
||||
assert.Equal(t, StatusWiFi, state.NetworkStatus)
|
||||
assert.Len(t, state.SavedWiFiNetworks, 2)
|
||||
assert.Equal(t, "TestNetwork", state.SavedWiFiNetworks[0].SSID)
|
||||
assert.True(t, state.SavedWiFiNetworks[1].OutOfRange)
|
||||
}
|
||||
|
||||
func TestHybridIwdNetworkdBackend_GetCurrentState_EthernetPriority(t *testing.T) {
|
||||
|
||||
@@ -80,6 +80,10 @@ func (b *IWDBackend) Initialize() error {
|
||||
return fmt.Errorf("failed to discover iwd devices: %w", err)
|
||||
}
|
||||
|
||||
if err := b.updateSavedWiFiNetworks(); err != nil {
|
||||
log.Warnf("Failed to get initial saved WiFi networks: %v", err)
|
||||
}
|
||||
|
||||
if err := b.updateState(); err != nil {
|
||||
conn.Close()
|
||||
return fmt.Errorf("failed to get initial state: %w", err)
|
||||
@@ -145,6 +149,7 @@ func (b *IWDBackend) GetCurrentState() (*BackendState, error) {
|
||||
|
||||
state := *b.state
|
||||
state.WiFiNetworks = append([]WiFiNetwork(nil), b.state.WiFiNetworks...)
|
||||
state.SavedWiFiNetworks = append([]WiFiNetwork(nil), b.state.SavedWiFiNetworks...)
|
||||
state.WiredConnections = append([]WiredConnection(nil), b.state.WiredConnections...)
|
||||
state.WiFiDevices = b.getWiFiDevicesLocked()
|
||||
|
||||
|
||||
@@ -45,12 +45,42 @@ func (b *IWDBackend) StartMonitoring(onStateChange func()) error {
|
||||
}
|
||||
}
|
||||
|
||||
if err := b.conn.AddMatchSignal(
|
||||
dbus.WithMatchInterface(dbusPropertiesInterface),
|
||||
dbus.WithMatchMember("PropertiesChanged"),
|
||||
dbus.WithMatchArg(0, iwdKnownNetworkInterface),
|
||||
); err != nil {
|
||||
return fmt.Errorf("failed to add known network signal match: %w", err)
|
||||
}
|
||||
|
||||
if err := b.conn.AddMatchSignal(
|
||||
dbus.WithMatchInterface(dbusObjectManager),
|
||||
dbus.WithMatchMember("InterfacesAdded"),
|
||||
); err != nil {
|
||||
return fmt.Errorf("failed to add iwd interfaces-added signal match: %w", err)
|
||||
}
|
||||
|
||||
if err := b.conn.AddMatchSignal(
|
||||
dbus.WithMatchInterface(dbusObjectManager),
|
||||
dbus.WithMatchMember("InterfacesRemoved"),
|
||||
); err != nil {
|
||||
return fmt.Errorf("failed to add iwd interfaces-removed signal match: %w", err)
|
||||
}
|
||||
|
||||
b.sigWG.Add(1)
|
||||
go b.signalHandler(sigChan)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *IWDBackend) refreshWiFiNetworkState() bool {
|
||||
_, err := b.updateWiFiNetworks()
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
return b.updateSavedWiFiNetworks() == nil
|
||||
}
|
||||
|
||||
func (b *IWDBackend) signalHandler(sigChan chan *dbus.Signal) {
|
||||
defer b.sigWG.Done()
|
||||
|
||||
@@ -66,11 +96,36 @@ func (b *IWDBackend) signalHandler(sigChan chan *dbus.Signal) {
|
||||
return
|
||||
}
|
||||
|
||||
if sig.Name != dbusPropertiesInterface+".PropertiesChanged" {
|
||||
if sig.Name == dbusObjectManager+".InterfacesAdded" {
|
||||
if len(sig.Body) >= 2 {
|
||||
if interfaces, ok := sig.Body[1].(map[string]map[string]dbus.Variant); ok {
|
||||
if _, ok := interfaces[iwdKnownNetworkInterface]; ok {
|
||||
if b.refreshWiFiNetworkState() && b.onStateChange != nil {
|
||||
b.onStateChange()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if len(sig.Body) < 2 {
|
||||
if sig.Name == dbusObjectManager+".InterfacesRemoved" {
|
||||
if len(sig.Body) >= 2 {
|
||||
if interfaces, ok := sig.Body[1].([]string); ok {
|
||||
for _, iface := range interfaces {
|
||||
if iface == iwdKnownNetworkInterface {
|
||||
if b.refreshWiFiNetworkState() && b.onStateChange != nil {
|
||||
b.onStateChange()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if sig.Name != dbusPropertiesInterface+".PropertiesChanged" || len(sig.Body) < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -87,6 +142,9 @@ func (b *IWDBackend) signalHandler(sigChan chan *dbus.Signal) {
|
||||
stateChanged := false
|
||||
|
||||
switch iface {
|
||||
case iwdKnownNetworkInterface:
|
||||
stateChanged = b.refreshWiFiNetworkState()
|
||||
|
||||
case iwdDeviceInterface:
|
||||
if sig.Path == b.devicePath {
|
||||
if poweredVar, ok := changed["Powered"]; ok {
|
||||
@@ -105,13 +163,7 @@ func (b *IWDBackend) signalHandler(sigChan chan *dbus.Signal) {
|
||||
if sig.Path == b.stationPath {
|
||||
if scanningVar, ok := changed["Scanning"]; ok {
|
||||
if scanning, ok := scanningVar.Value().(bool); ok && !scanning {
|
||||
networks, err := b.updateWiFiNetworks()
|
||||
if err == nil {
|
||||
b.stateMutex.Lock()
|
||||
b.state.WiFiNetworks = networks
|
||||
b.stateMutex.Unlock()
|
||||
stateChanged = true
|
||||
}
|
||||
stateChanged = b.refreshWiFiNetworkState() || stateChanged
|
||||
|
||||
b.stateMutex.RLock()
|
||||
wifiConnected := b.state.WiFiConnected
|
||||
@@ -236,6 +288,7 @@ func (b *IWDBackend) signalHandler(sigChan chan *dbus.Signal) {
|
||||
}
|
||||
}
|
||||
|
||||
b.refreshWiFiNetworkState()
|
||||
stateChanged = true
|
||||
|
||||
if att != nil && isTarget {
|
||||
@@ -282,6 +335,7 @@ func (b *IWDBackend) signalHandler(sigChan chan *dbus.Signal) {
|
||||
b.state.NetworkStatus = StatusDisconnected
|
||||
}
|
||||
b.stateMutex.Unlock()
|
||||
b.refreshWiFiNetworkState()
|
||||
stateChanged = true
|
||||
}
|
||||
}
|
||||
@@ -342,6 +396,7 @@ func (b *IWDBackend) signalHandler(sigChan chan *dbus.Signal) {
|
||||
stateChanged = true
|
||||
}
|
||||
b.stateMutex.Unlock()
|
||||
b.refreshWiFiNetworkState()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -168,6 +169,92 @@ func TestIWDBackend_MapIwdDBusError(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIWDSavedWiFiProfilesFromManagedObjects(t *testing.T) {
|
||||
objects := map[dbus.ObjectPath]map[string]map[string]dbus.Variant{
|
||||
"/net/connman/iwd/known_network/1": {
|
||||
iwdKnownNetworkInterface: {
|
||||
"Name": dbus.MakeVariant("Home"),
|
||||
"AutoConnect": dbus.MakeVariant(false),
|
||||
"Hidden": dbus.MakeVariant(true),
|
||||
"Type": dbus.MakeVariant("psk"),
|
||||
},
|
||||
},
|
||||
"/net/connman/iwd/known_network/2": {
|
||||
iwdKnownNetworkInterface: {
|
||||
"Name": dbus.MakeVariant("Office"),
|
||||
"Type": dbus.MakeVariant("8021x"),
|
||||
},
|
||||
},
|
||||
"/net/connman/iwd/known_network/3": {
|
||||
iwdKnownNetworkInterface: {
|
||||
"Name": dbus.MakeVariant("Cafe"),
|
||||
"Type": dbus.MakeVariant("open"),
|
||||
},
|
||||
},
|
||||
"/net/connman/iwd/network/1": {
|
||||
iwdNetworkInterface: {
|
||||
"Name": dbus.MakeVariant("VisibleOnly"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
profiles := iwdSavedWiFiProfilesFromManagedObjects(objects)
|
||||
|
||||
assert.Len(t, profiles, 3)
|
||||
assert.False(t, profiles["Home"].Autoconnect)
|
||||
assert.True(t, profiles["Home"].Hidden)
|
||||
assert.True(t, profiles["Home"].Secured)
|
||||
assert.False(t, profiles["Home"].Enterprise)
|
||||
|
||||
assert.True(t, profiles["Office"].Autoconnect)
|
||||
assert.True(t, profiles["Office"].Secured)
|
||||
assert.True(t, profiles["Office"].Enterprise)
|
||||
|
||||
assert.True(t, profiles["Cafe"].Autoconnect)
|
||||
assert.False(t, profiles["Cafe"].Secured)
|
||||
assert.False(t, profiles["Cafe"].Enterprise)
|
||||
}
|
||||
|
||||
func TestIWDWiFiNetworksFromVisibleIncludesConnectedHiddenFallback(t *testing.T) {
|
||||
profiles := map[string]savedWiFiProfile{
|
||||
"Home": {
|
||||
Autoconnect: true,
|
||||
Secured: true,
|
||||
Hidden: true,
|
||||
Mode: "infrastructure",
|
||||
},
|
||||
}
|
||||
visible := []WiFiNetwork{
|
||||
{
|
||||
SSID: "Cafe",
|
||||
Signal: 42,
|
||||
Secured: false,
|
||||
},
|
||||
}
|
||||
|
||||
networks := iwdWiFiNetworksFromVisible(visible, profiles, "Home", true, 68)
|
||||
savedNetworks := savedWiFiNetworksFromProfiles(profiles, map[string]WiFiNetwork{
|
||||
networks[0].SSID: networks[0],
|
||||
networks[1].SSID: networks[1],
|
||||
}, "Home", true)
|
||||
|
||||
assert.Len(t, networks, 2)
|
||||
assert.Equal(t, "Cafe", networks[0].SSID)
|
||||
assert.False(t, networks[0].Connected)
|
||||
|
||||
assert.Equal(t, "Home", networks[1].SSID)
|
||||
assert.True(t, networks[1].Connected)
|
||||
assert.True(t, networks[1].Hidden)
|
||||
assert.True(t, networks[1].Saved)
|
||||
assert.True(t, networks[1].Autoconnect)
|
||||
assert.Equal(t, uint8(68), networks[1].Signal)
|
||||
|
||||
assert.Len(t, savedNetworks, 1)
|
||||
assert.Equal(t, "Home", savedNetworks[0].SSID)
|
||||
assert.True(t, savedNetworks[0].Connected)
|
||||
assert.False(t, savedNetworks[0].OutOfRange)
|
||||
}
|
||||
|
||||
func TestConnectAttempt_Finalization(t *testing.T) {
|
||||
backend, _ := NewIWDBackend()
|
||||
backend.state = &BackendState{}
|
||||
|
||||
@@ -164,22 +164,18 @@ func (b *IWDBackend) updateWiFiNetworks() ([]WiFiNetwork, error) {
|
||||
return nil, fmt.Errorf("failed to get networks: %w", err)
|
||||
}
|
||||
|
||||
knownNetworks, err := b.getKnownNetworks()
|
||||
savedProfiles, err := b.getIWDSavedWiFiProfiles()
|
||||
if err != nil {
|
||||
knownNetworks = make(map[string]bool)
|
||||
}
|
||||
|
||||
autoconnectMap, err := b.getAutoconnectSettings()
|
||||
if err != nil {
|
||||
autoconnectMap = make(map[string]bool)
|
||||
savedProfiles = make(map[string]savedWiFiProfile)
|
||||
}
|
||||
|
||||
b.stateMutex.RLock()
|
||||
currentSSID := b.state.WiFiSSID
|
||||
wifiConnected := b.state.WiFiConnected
|
||||
wifiSignal := b.state.WiFiSignal
|
||||
b.stateMutex.RUnlock()
|
||||
|
||||
networks := make([]WiFiNetwork, 0, len(orderedNetworks))
|
||||
visibleNetworks := make([]WiFiNetwork, 0, len(orderedNetworks))
|
||||
for _, netData := range orderedNetworks {
|
||||
if len(netData) < 2 {
|
||||
continue
|
||||
@@ -225,23 +221,26 @@ func (b *IWDBackend) updateWiFiNetworks() ([]WiFiNetwork, error) {
|
||||
|
||||
secured := netType != "open"
|
||||
|
||||
network := WiFiNetwork{
|
||||
SSID: name,
|
||||
Signal: signal,
|
||||
Secured: secured,
|
||||
Connected: wifiConnected && name == currentSSID,
|
||||
Saved: knownNetworks[name],
|
||||
Autoconnect: autoconnectMap[name],
|
||||
Enterprise: netType == "8021x",
|
||||
}
|
||||
|
||||
networks = append(networks, network)
|
||||
visibleNetworks = append(visibleNetworks, WiFiNetwork{
|
||||
SSID: name,
|
||||
Signal: signal,
|
||||
Secured: secured,
|
||||
Enterprise: netType == "8021x",
|
||||
})
|
||||
}
|
||||
|
||||
networks := iwdWiFiNetworksFromVisible(visibleNetworks, savedProfiles, currentSSID, wifiConnected, wifiSignal)
|
||||
visibleNetworkMap := make(map[string]WiFiNetwork, len(networks))
|
||||
for _, network := range networks {
|
||||
visibleNetworkMap[network.SSID] = network
|
||||
}
|
||||
savedNetworks := savedWiFiNetworksFromProfiles(savedProfiles, visibleNetworkMap, currentSSID, wifiConnected)
|
||||
|
||||
sortWiFiNetworks(networks)
|
||||
|
||||
b.stateMutex.Lock()
|
||||
b.state.WiFiNetworks = networks
|
||||
b.state.SavedWiFiNetworks = savedNetworks
|
||||
b.stateMutex.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
@@ -254,30 +253,129 @@ func (b *IWDBackend) updateWiFiNetworks() ([]WiFiNetwork, error) {
|
||||
return networks, nil
|
||||
}
|
||||
|
||||
func (b *IWDBackend) getKnownNetworks() (map[string]bool, error) {
|
||||
obj := b.conn.Object(iwdBusName, iwdObjectPath)
|
||||
|
||||
var objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant
|
||||
err := obj.Call(dbusObjectManager+".GetManagedObjects", 0).Store(&objects)
|
||||
func (b *IWDBackend) updateSavedWiFiNetworks() error {
|
||||
savedProfiles, err := b.getIWDSavedWiFiProfiles()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
known := make(map[string]bool)
|
||||
for _, interfaces := range objects {
|
||||
if knownProps, ok := interfaces[iwdKnownNetworkInterface]; ok {
|
||||
if nameVar, ok := knownProps["Name"]; ok {
|
||||
if name, ok := nameVar.Value().(string); ok {
|
||||
known[name] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
b.stateMutex.RLock()
|
||||
currentSSID := b.state.WiFiSSID
|
||||
wifiConnected := b.state.WiFiConnected
|
||||
wifiNetworks := append([]WiFiNetwork(nil), b.state.WiFiNetworks...)
|
||||
b.stateMutex.RUnlock()
|
||||
|
||||
return known, nil
|
||||
wifiNetworks, savedNetworks := refreshSavedWiFiState(wifiNetworks, savedProfiles, currentSSID, wifiConnected)
|
||||
|
||||
b.stateMutex.Lock()
|
||||
b.state.WiFiNetworks = wifiNetworks
|
||||
b.state.SavedWiFiNetworks = savedNetworks
|
||||
b.stateMutex.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *IWDBackend) getAutoconnectSettings() (map[string]bool, error) {
|
||||
func iwdWiFiNetworksFromVisible(visibleNetworks []WiFiNetwork, savedProfiles map[string]savedWiFiProfile, currentSSID string, wifiConnected bool, wifiSignal uint8) []WiFiNetwork {
|
||||
networks := make([]WiFiNetwork, 0, len(visibleNetworks)+1)
|
||||
seenSSIDs := make(map[string]struct{}, len(visibleNetworks)+1)
|
||||
|
||||
for _, network := range visibleNetworks {
|
||||
profile, saved := savedProfiles[network.SSID]
|
||||
network.Connected = wifiConnected && network.SSID == currentSSID
|
||||
network.Saved = saved
|
||||
network.Autoconnect = profile.Autoconnect
|
||||
network.Hidden = network.Hidden || profile.Hidden
|
||||
network.Secured = network.Secured || profile.Secured
|
||||
network.Enterprise = network.Enterprise || profile.Enterprise
|
||||
if network.Mode == "" {
|
||||
network.Mode = profile.Mode
|
||||
}
|
||||
networks = append(networks, network)
|
||||
seenSSIDs[network.SSID] = struct{}{}
|
||||
}
|
||||
|
||||
if wifiConnected && currentSSID != "" {
|
||||
if _, exists := seenSSIDs[currentSSID]; !exists {
|
||||
profile, saved := savedProfiles[currentSSID]
|
||||
secured := profile.Secured
|
||||
if !saved {
|
||||
secured = true
|
||||
}
|
||||
mode := profile.Mode
|
||||
if mode == "" {
|
||||
mode = "infrastructure"
|
||||
}
|
||||
|
||||
networks = append(networks, WiFiNetwork{
|
||||
SSID: currentSSID,
|
||||
Signal: wifiSignal,
|
||||
Secured: secured,
|
||||
Enterprise: profile.Enterprise,
|
||||
Connected: true,
|
||||
Saved: saved,
|
||||
Autoconnect: profile.Autoconnect,
|
||||
Hidden: true,
|
||||
Mode: mode,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return networks
|
||||
}
|
||||
|
||||
func iwdSavedWiFiProfilesFromManagedObjects(objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant) map[string]savedWiFiProfile {
|
||||
profiles := make(map[string]savedWiFiProfile)
|
||||
|
||||
for _, interfaces := range objects {
|
||||
knownProps, ok := interfaces[iwdKnownNetworkInterface]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
nameVar, ok := knownProps["Name"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
name, ok := nameVar.Value().(string)
|
||||
if !ok || name == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
profile := savedWiFiProfile{
|
||||
Autoconnect: true,
|
||||
Mode: "infrastructure",
|
||||
}
|
||||
if acVar, ok := knownProps["AutoConnect"]; ok {
|
||||
if autoconnect, ok := acVar.Value().(bool); ok {
|
||||
profile.Autoconnect = autoconnect
|
||||
}
|
||||
}
|
||||
if hiddenVar, ok := knownProps["Hidden"]; ok {
|
||||
if hidden, ok := hiddenVar.Value().(bool); ok {
|
||||
profile.Hidden = hidden
|
||||
}
|
||||
}
|
||||
if typeVar, ok := knownProps["Type"]; ok {
|
||||
if networkType, ok := typeVar.Value().(string); ok {
|
||||
profile.Secured = networkType != "" && networkType != "open"
|
||||
profile.Enterprise = networkType == "8021x"
|
||||
}
|
||||
}
|
||||
|
||||
if existing, ok := profiles[name]; ok {
|
||||
profile.Autoconnect = profile.Autoconnect || existing.Autoconnect
|
||||
profile.Hidden = profile.Hidden || existing.Hidden
|
||||
profile.Secured = profile.Secured || existing.Secured
|
||||
profile.Enterprise = profile.Enterprise || existing.Enterprise
|
||||
}
|
||||
|
||||
profiles[name] = profile
|
||||
}
|
||||
|
||||
return profiles
|
||||
}
|
||||
|
||||
func (b *IWDBackend) getIWDSavedWiFiProfiles() (map[string]savedWiFiProfile, error) {
|
||||
obj := b.conn.Object(iwdBusName, iwdObjectPath)
|
||||
|
||||
var objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant
|
||||
@@ -286,24 +384,7 @@ func (b *IWDBackend) getAutoconnectSettings() (map[string]bool, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
autoconnectMap := make(map[string]bool)
|
||||
for _, interfaces := range objects {
|
||||
if knownProps, ok := interfaces[iwdKnownNetworkInterface]; ok {
|
||||
if nameVar, ok := knownProps["Name"]; ok {
|
||||
if name, ok := nameVar.Value().(string); ok {
|
||||
autoconnect := true
|
||||
if acVar, ok := knownProps["AutoConnect"]; ok {
|
||||
if ac, ok := acVar.Value().(bool); ok {
|
||||
autoconnect = ac
|
||||
}
|
||||
}
|
||||
autoconnectMap[name] = autoconnect
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return autoconnectMap, nil
|
||||
return iwdSavedWiFiProfilesFromManagedObjects(objects), nil
|
||||
}
|
||||
|
||||
func (b *IWDBackend) GetWiFiNetworkDetails(ssid string) (*NetworkInfoResponse, error) {
|
||||
@@ -614,6 +695,8 @@ func (b *IWDBackend) ForgetWiFiNetwork(ssid string) error {
|
||||
b.stateMutex.Unlock()
|
||||
}
|
||||
|
||||
_, _ = b.updateWiFiNetworks()
|
||||
|
||||
if b.onStateChange != nil {
|
||||
b.onStateChange()
|
||||
}
|
||||
|
||||
@@ -222,6 +222,10 @@ func (b *NetworkManagerBackend) Initialize() error {
|
||||
log.Warnf("Failed to update WiFi state: %v", err)
|
||||
}
|
||||
|
||||
if err := b.updateSavedWiFiNetworks(); err != nil {
|
||||
log.Warnf("Failed to get initial saved WiFi networks: %v", err)
|
||||
}
|
||||
|
||||
if wifiEnabled {
|
||||
if _, err := b.updateWiFiNetworks(); err != nil {
|
||||
log.Warnf("Failed to get initial networks: %v", err)
|
||||
@@ -261,6 +265,7 @@ func (b *NetworkManagerBackend) GetCurrentState() (*BackendState, error) {
|
||||
|
||||
state := *b.state
|
||||
state.WiFiNetworks = append([]WiFiNetwork(nil), b.state.WiFiNetworks...)
|
||||
state.SavedWiFiNetworks = append([]WiFiNetwork(nil), b.state.SavedWiFiNetworks...)
|
||||
state.WiFiDevices = append([]WiFiDevice(nil), b.state.WiFiDevices...)
|
||||
state.WiredConnections = append([]WiredConnection(nil), b.state.WiredConnections...)
|
||||
state.EthernetDevices = append([]EthernetDevice(nil), b.state.EthernetDevices...)
|
||||
|
||||
@@ -5,6 +5,12 @@ import (
|
||||
"github.com/godbus/dbus/v5"
|
||||
)
|
||||
|
||||
const (
|
||||
dbusNMSettingsPath = "/org/freedesktop/NetworkManager/Settings"
|
||||
dbusNMSettingsInterface = "org.freedesktop.NetworkManager.Settings"
|
||||
dbusNMSettingsConnectionInterface = "org.freedesktop.NetworkManager.Settings.Connection"
|
||||
)
|
||||
|
||||
func (b *NetworkManagerBackend) startSignalPump() error {
|
||||
conn, err := dbus.ConnectSystemBus()
|
||||
if err != nil {
|
||||
@@ -27,8 +33,8 @@ func (b *NetworkManagerBackend) startSignalPump() error {
|
||||
}
|
||||
|
||||
if err := conn.AddMatchSignal(
|
||||
dbus.WithMatchObjectPath(dbus.ObjectPath("/org/freedesktop/NetworkManager/Settings")),
|
||||
dbus.WithMatchInterface("org.freedesktop.NetworkManager.Settings"),
|
||||
dbus.WithMatchObjectPath(dbus.ObjectPath(dbusNMSettingsPath)),
|
||||
dbus.WithMatchInterface(dbusNMSettingsInterface),
|
||||
dbus.WithMatchMember("NewConnection"),
|
||||
); err != nil {
|
||||
conn.RemoveMatchSignal(
|
||||
@@ -42,8 +48,8 @@ func (b *NetworkManagerBackend) startSignalPump() error {
|
||||
}
|
||||
|
||||
if err := conn.AddMatchSignal(
|
||||
dbus.WithMatchObjectPath(dbus.ObjectPath("/org/freedesktop/NetworkManager/Settings")),
|
||||
dbus.WithMatchInterface("org.freedesktop.NetworkManager.Settings"),
|
||||
dbus.WithMatchObjectPath(dbus.ObjectPath(dbusNMSettingsPath)),
|
||||
dbus.WithMatchInterface(dbusNMSettingsInterface),
|
||||
dbus.WithMatchMember("ConnectionRemoved"),
|
||||
); err != nil {
|
||||
conn.RemoveMatchSignal(
|
||||
@@ -52,8 +58,8 @@ func (b *NetworkManagerBackend) startSignalPump() error {
|
||||
dbus.WithMatchMember("PropertiesChanged"),
|
||||
)
|
||||
conn.RemoveMatchSignal(
|
||||
dbus.WithMatchObjectPath(dbus.ObjectPath("/org/freedesktop/NetworkManager/Settings")),
|
||||
dbus.WithMatchInterface("org.freedesktop.NetworkManager.Settings"),
|
||||
dbus.WithMatchObjectPath(dbus.ObjectPath(dbusNMSettingsPath)),
|
||||
dbus.WithMatchInterface(dbusNMSettingsInterface),
|
||||
dbus.WithMatchMember("NewConnection"),
|
||||
)
|
||||
conn.RemoveSignal(signals)
|
||||
@@ -61,6 +67,31 @@ func (b *NetworkManagerBackend) startSignalPump() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := conn.AddMatchSignal(
|
||||
dbus.WithMatchPathNamespace(dbus.ObjectPath(dbusNMSettingsPath)),
|
||||
dbus.WithMatchInterface(dbusNMSettingsConnectionInterface),
|
||||
dbus.WithMatchMember("Updated"),
|
||||
); err != nil {
|
||||
conn.RemoveMatchSignal(
|
||||
dbus.WithMatchObjectPath(dbus.ObjectPath(dbusNMPath)),
|
||||
dbus.WithMatchInterface(dbusPropsInterface),
|
||||
dbus.WithMatchMember("PropertiesChanged"),
|
||||
)
|
||||
conn.RemoveMatchSignal(
|
||||
dbus.WithMatchObjectPath(dbus.ObjectPath(dbusNMSettingsPath)),
|
||||
dbus.WithMatchInterface(dbusNMSettingsInterface),
|
||||
dbus.WithMatchMember("NewConnection"),
|
||||
)
|
||||
conn.RemoveMatchSignal(
|
||||
dbus.WithMatchObjectPath(dbus.ObjectPath(dbusNMSettingsPath)),
|
||||
dbus.WithMatchInterface(dbusNMSettingsInterface),
|
||||
dbus.WithMatchMember("ConnectionRemoved"),
|
||||
)
|
||||
conn.RemoveSignal(signals)
|
||||
conn.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
if err := conn.AddMatchSignal(
|
||||
dbus.WithMatchObjectPath(dbus.ObjectPath(dbusNMPath)),
|
||||
dbus.WithMatchInterface(dbusNMInterface),
|
||||
@@ -137,6 +168,32 @@ func (b *NetworkManagerBackend) stopSignalPump() {
|
||||
dbus.WithMatchMember("PropertiesChanged"),
|
||||
)
|
||||
|
||||
b.dbusConn.RemoveMatchSignal(
|
||||
dbus.WithMatchObjectPath(dbus.ObjectPath(dbusNMSettingsPath)),
|
||||
dbus.WithMatchInterface(dbusNMSettingsInterface),
|
||||
dbus.WithMatchMember("NewConnection"),
|
||||
)
|
||||
b.dbusConn.RemoveMatchSignal(
|
||||
dbus.WithMatchObjectPath(dbus.ObjectPath(dbusNMSettingsPath)),
|
||||
dbus.WithMatchInterface(dbusNMSettingsInterface),
|
||||
dbus.WithMatchMember("ConnectionRemoved"),
|
||||
)
|
||||
b.dbusConn.RemoveMatchSignal(
|
||||
dbus.WithMatchPathNamespace(dbus.ObjectPath(dbusNMSettingsPath)),
|
||||
dbus.WithMatchInterface(dbusNMSettingsConnectionInterface),
|
||||
dbus.WithMatchMember("Updated"),
|
||||
)
|
||||
b.dbusConn.RemoveMatchSignal(
|
||||
dbus.WithMatchObjectPath(dbus.ObjectPath(dbusNMPath)),
|
||||
dbus.WithMatchInterface(dbusNMInterface),
|
||||
dbus.WithMatchMember("DeviceAdded"),
|
||||
)
|
||||
b.dbusConn.RemoveMatchSignal(
|
||||
dbus.WithMatchObjectPath(dbus.ObjectPath(dbusNMPath)),
|
||||
dbus.WithMatchInterface(dbusNMInterface),
|
||||
dbus.WithMatchMember("DeviceRemoved"),
|
||||
)
|
||||
|
||||
for _, info := range b.wifiDevices {
|
||||
b.dbusConn.RemoveMatchSignal(
|
||||
dbus.WithMatchObjectPath(dbus.ObjectPath(info.device.GetPath())),
|
||||
@@ -164,9 +221,13 @@ func (b *NetworkManagerBackend) stopSignalPump() {
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) handleDBusSignal(sig *dbus.Signal) {
|
||||
if sig.Name == "org.freedesktop.NetworkManager.Settings.NewConnection" ||
|
||||
sig.Name == "org.freedesktop.NetworkManager.Settings.ConnectionRemoved" {
|
||||
if sig.Name == dbusNMSettingsInterface+".NewConnection" ||
|
||||
sig.Name == dbusNMSettingsInterface+".ConnectionRemoved" ||
|
||||
sig.Name == dbusNMSettingsConnectionInterface+".Updated" {
|
||||
b.ListVPNProfiles()
|
||||
if err := b.updateSavedWiFiNetworks(); err != nil {
|
||||
b.updateWiFiNetworks()
|
||||
}
|
||||
if b.onStateChange != nil {
|
||||
b.onStateChange()
|
||||
}
|
||||
|
||||
@@ -225,24 +225,14 @@ func (b *NetworkManagerBackend) GetWiFiQRCodeContent(ssid string) (string, error
|
||||
return "", fmt.Errorf("failed to identify security type of network `%s`", ssid)
|
||||
}
|
||||
|
||||
var securityType string
|
||||
switch keyMgmt {
|
||||
case "none":
|
||||
authAlg, _ := secSettings["auth-alg"].(string)
|
||||
switch authAlg {
|
||||
case "open":
|
||||
securityType = "nopass"
|
||||
default:
|
||||
securityType = "WEP"
|
||||
}
|
||||
return "", fmt.Errorf("QR code generation only supports WPA-PSK connections, `%s` is open or WEP", ssid)
|
||||
case "ieee8021x":
|
||||
securityType = "WEP"
|
||||
return "", fmt.Errorf("QR code generation only supports WPA-PSK connections, `%s` is enterprise", ssid)
|
||||
case "wpa-psk", "sae", "wpa-psk-sae":
|
||||
default:
|
||||
securityType = "WPA"
|
||||
}
|
||||
|
||||
if securityType != "WPA" {
|
||||
return "", fmt.Errorf("QR code generation only supports WPA connections, `%s` uses %s", ssid, securityType)
|
||||
return "", fmt.Errorf("QR code generation only supports WPA-PSK connections, `%s` uses %s", ssid, keyMgmt)
|
||||
}
|
||||
|
||||
var psk string
|
||||
@@ -276,7 +266,7 @@ func (b *NetworkManagerBackend) GetWiFiQRCodeContent(ssid string) (string, error
|
||||
return "", fmt.Errorf("failed to retrieve password for `%s`", ssid)
|
||||
}
|
||||
|
||||
return FormatWiFiQRString(securityType, ssid, psk), nil
|
||||
return FormatWiFiQRString("WPA", ssid, psk), nil
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) ConnectWiFi(req ConnectionRequest) error {
|
||||
@@ -405,6 +395,74 @@ func (b *NetworkManagerBackend) ForgetWiFiNetwork(ssid string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getSavedWiFiProfiles(connections []gonetworkmanager.Connection) map[string]savedWiFiProfile {
|
||||
profiles := make(map[string]savedWiFiProfile)
|
||||
|
||||
for _, conn := range connections {
|
||||
connSettings, err := conn.GetSettings()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
connMeta, ok := connSettings["connection"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
connType, ok := connMeta["type"].(string)
|
||||
if !ok || connType != "802-11-wireless" {
|
||||
continue
|
||||
}
|
||||
|
||||
wifiSettings, ok := connSettings["802-11-wireless"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
ssidBytes, ok := wifiSettings["ssid"].([]byte)
|
||||
if !ok || len(ssidBytes) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
ssid := string(ssidBytes)
|
||||
profile := savedWiFiProfile{
|
||||
Autoconnect: true,
|
||||
Mode: "infrastructure",
|
||||
}
|
||||
|
||||
if ac, ok := connMeta["autoconnect"].(bool); ok {
|
||||
profile.Autoconnect = ac
|
||||
}
|
||||
if hidden, ok := wifiSettings["hidden"].(bool); ok {
|
||||
profile.Hidden = hidden
|
||||
}
|
||||
if mode, ok := wifiSettings["mode"].(string); ok && mode != "" {
|
||||
profile.Mode = mode
|
||||
}
|
||||
if _, ok := connSettings["802-11-wireless-security"]; ok {
|
||||
profile.Secured = true
|
||||
}
|
||||
if _, ok := connSettings["802-1x"]; ok {
|
||||
profile.Enterprise = true
|
||||
profile.Secured = true
|
||||
}
|
||||
|
||||
if existing, ok := profiles[ssid]; ok {
|
||||
profile.Autoconnect = profile.Autoconnect || existing.Autoconnect
|
||||
profile.Hidden = profile.Hidden || existing.Hidden
|
||||
profile.Secured = profile.Secured || existing.Secured
|
||||
profile.Enterprise = profile.Enterprise || existing.Enterprise
|
||||
if profile.Mode == "" {
|
||||
profile.Mode = existing.Mode
|
||||
}
|
||||
}
|
||||
|
||||
profiles[ssid] = profile
|
||||
}
|
||||
|
||||
return profiles
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) IsConnectingTo(ssid string) bool {
|
||||
b.stateMutex.RLock()
|
||||
defer b.stateMutex.RUnlock()
|
||||
@@ -442,47 +500,7 @@ func (b *NetworkManagerBackend) updateWiFiNetworks() ([]WiFiNetwork, error) {
|
||||
return nil, fmt.Errorf("failed to get connections: %w", err)
|
||||
}
|
||||
|
||||
savedSSIDs := make(map[string]bool)
|
||||
autoconnectMap := make(map[string]bool)
|
||||
hiddenSSIDs := make(map[string]bool)
|
||||
for _, conn := range connections {
|
||||
connSettings, err := conn.GetSettings()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
connMeta, ok := connSettings["connection"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
connType, ok := connMeta["type"].(string)
|
||||
if !ok || connType != "802-11-wireless" {
|
||||
continue
|
||||
}
|
||||
|
||||
wifiSettings, ok := connSettings["802-11-wireless"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
ssidBytes, ok := wifiSettings["ssid"].([]byte)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
ssid := string(ssidBytes)
|
||||
savedSSIDs[ssid] = true
|
||||
autoconnect := true
|
||||
if ac, ok := connMeta["autoconnect"].(bool); ok {
|
||||
autoconnect = ac
|
||||
}
|
||||
autoconnectMap[ssid] = autoconnect
|
||||
|
||||
if hidden, ok := wifiSettings["hidden"].(bool); ok && hidden {
|
||||
hiddenSSIDs[ssid] = true
|
||||
}
|
||||
}
|
||||
savedProfiles := getSavedWiFiProfiles(connections)
|
||||
|
||||
b.stateMutex.RLock()
|
||||
currentSSID := b.state.WiFiSSID
|
||||
@@ -491,8 +509,8 @@ func (b *NetworkManagerBackend) updateWiFiNetworks() ([]WiFiNetwork, error) {
|
||||
wifiBSSID := b.state.WiFiBSSID
|
||||
b.stateMutex.RUnlock()
|
||||
|
||||
seenSSIDs := make(map[string]*WiFiNetwork)
|
||||
networks := []WiFiNetwork{}
|
||||
seenSSIDs := make(map[string]int)
|
||||
networks := make([]WiFiNetwork, 0, len(apPaths)+1)
|
||||
|
||||
for _, ap := range apPaths {
|
||||
ssid, err := ap.GetPropertySSID()
|
||||
@@ -500,7 +518,8 @@ func (b *NetworkManagerBackend) updateWiFiNetworks() ([]WiFiNetwork, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
if existing, exists := seenSSIDs[ssid]; exists {
|
||||
if existingIndex, exists := seenSSIDs[ssid]; exists {
|
||||
existing := &networks[existingIndex]
|
||||
strength, _ := ap.GetPropertyStrength()
|
||||
if strength > existing.Signal {
|
||||
existing.Signal = strength
|
||||
@@ -550,6 +569,7 @@ func (b *NetworkManagerBackend) updateWiFiNetworks() ([]WiFiNetwork, error) {
|
||||
}
|
||||
}
|
||||
|
||||
profile, saved := savedProfiles[ssid]
|
||||
network := WiFiNetwork{
|
||||
SSID: ssid,
|
||||
BSSID: bssid,
|
||||
@@ -557,45 +577,86 @@ func (b *NetworkManagerBackend) updateWiFiNetworks() ([]WiFiNetwork, error) {
|
||||
Secured: secured,
|
||||
Enterprise: enterprise,
|
||||
Connected: isConnected,
|
||||
Saved: savedSSIDs[ssid],
|
||||
Autoconnect: autoconnectMap[ssid],
|
||||
Hidden: hiddenSSIDs[ssid],
|
||||
Saved: saved,
|
||||
Autoconnect: profile.Autoconnect,
|
||||
Hidden: profile.Hidden,
|
||||
Frequency: freq,
|
||||
Mode: modeStr,
|
||||
Rate: rate,
|
||||
Channel: channel,
|
||||
}
|
||||
|
||||
seenSSIDs[ssid] = &network
|
||||
networks = append(networks, network)
|
||||
seenSSIDs[ssid] = len(networks) - 1
|
||||
}
|
||||
|
||||
if wifiConnected && currentSSID != "" {
|
||||
if _, exists := seenSSIDs[currentSSID]; !exists {
|
||||
profile, saved := savedProfiles[currentSSID]
|
||||
hiddenNetwork := WiFiNetwork{
|
||||
SSID: currentSSID,
|
||||
BSSID: wifiBSSID,
|
||||
Signal: wifiSignal,
|
||||
Secured: true,
|
||||
Connected: true,
|
||||
Saved: savedSSIDs[currentSSID],
|
||||
Autoconnect: autoconnectMap[currentSSID],
|
||||
Saved: saved,
|
||||
Autoconnect: profile.Autoconnect,
|
||||
Hidden: true,
|
||||
Mode: "infrastructure",
|
||||
}
|
||||
networks = append(networks, hiddenNetwork)
|
||||
seenSSIDs[currentSSID] = len(networks) - 1
|
||||
}
|
||||
}
|
||||
|
||||
visibleNetworks := wiFiNetworksBySSID(networks, true)
|
||||
savedNetworks := savedWiFiNetworksFromProfiles(savedProfiles, visibleNetworks, currentSSID, wifiConnected)
|
||||
|
||||
sortWiFiNetworks(networks)
|
||||
|
||||
b.stateMutex.Lock()
|
||||
b.state.WiFiNetworks = networks
|
||||
b.state.SavedWiFiNetworks = savedNetworks
|
||||
b.stateMutex.Unlock()
|
||||
|
||||
return networks, nil
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) updateSavedWiFiNetworks() error {
|
||||
s := b.settings
|
||||
if s == nil {
|
||||
var err error
|
||||
s, err = gonetworkmanager.NewSettings()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get settings: %w", err)
|
||||
}
|
||||
b.settings = s
|
||||
}
|
||||
|
||||
settingsMgr := s.(gonetworkmanager.Settings)
|
||||
connections, err := settingsMgr.ListConnections()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get connections: %w", err)
|
||||
}
|
||||
|
||||
savedProfiles := getSavedWiFiProfiles(connections)
|
||||
|
||||
b.stateMutex.RLock()
|
||||
currentSSID := b.state.WiFiSSID
|
||||
wifiConnected := b.state.WiFiConnected
|
||||
wifiNetworks := append([]WiFiNetwork(nil), b.state.WiFiNetworks...)
|
||||
b.stateMutex.RUnlock()
|
||||
|
||||
wifiNetworks, savedNetworks := refreshSavedWiFiState(wifiNetworks, savedProfiles, currentSSID, wifiConnected)
|
||||
|
||||
b.stateMutex.Lock()
|
||||
b.state.WiFiNetworks = wifiNetworks
|
||||
b.state.SavedWiFiNetworks = savedNetworks
|
||||
b.stateMutex.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) findConnection(ssid string) (gonetworkmanager.Connection, error) {
|
||||
s := b.settings
|
||||
if s == nil {
|
||||
@@ -975,49 +1036,14 @@ func (b *NetworkManagerBackend) updateAllWiFiDevices() {
|
||||
return
|
||||
}
|
||||
|
||||
savedSSIDs := make(map[string]bool)
|
||||
autoconnectMap := make(map[string]bool)
|
||||
hiddenSSIDs := make(map[string]bool)
|
||||
for _, conn := range connections {
|
||||
connSettings, err := conn.GetSettings()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
connMeta, ok := connSettings["connection"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
connType, ok := connMeta["type"].(string)
|
||||
if !ok || connType != "802-11-wireless" {
|
||||
continue
|
||||
}
|
||||
|
||||
wifiSettings, ok := connSettings["802-11-wireless"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
ssidBytes, ok := wifiSettings["ssid"].([]byte)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
ssid := string(ssidBytes)
|
||||
savedSSIDs[ssid] = true
|
||||
autoconnect := true
|
||||
if ac, ok := connMeta["autoconnect"].(bool); ok {
|
||||
autoconnect = ac
|
||||
}
|
||||
autoconnectMap[ssid] = autoconnect
|
||||
|
||||
if hidden, ok := wifiSettings["hidden"].(bool); ok && hidden {
|
||||
hiddenSSIDs[ssid] = true
|
||||
}
|
||||
}
|
||||
savedProfiles := getSavedWiFiProfiles(connections)
|
||||
|
||||
var devices []WiFiDevice
|
||||
visibleNetworks := make(map[string]WiFiNetwork)
|
||||
b.stateMutex.RLock()
|
||||
currentSSID := b.state.WiFiSSID
|
||||
wifiConnected := b.state.WiFiConnected
|
||||
b.stateMutex.RUnlock()
|
||||
|
||||
for name, devInfo := range b.wifiDevices {
|
||||
state, _ := devInfo.device.GetPropertyState()
|
||||
@@ -1050,14 +1076,16 @@ func (b *NetworkManagerBackend) updateAllWiFiDevices() {
|
||||
apPaths, err := devInfo.wireless.GetAccessPoints()
|
||||
var networks []WiFiNetwork
|
||||
if err == nil {
|
||||
seenSSIDs := make(map[string]*WiFiNetwork)
|
||||
seenSSIDs := make(map[string]int)
|
||||
networks = make([]WiFiNetwork, 0, len(apPaths)+1)
|
||||
for _, ap := range apPaths {
|
||||
apSSID, err := ap.GetPropertySSID()
|
||||
if err != nil || apSSID == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if existing, exists := seenSSIDs[apSSID]; exists {
|
||||
if existingIndex, exists := seenSSIDs[apSSID]; exists {
|
||||
existing := &networks[existingIndex]
|
||||
strength, _ := ap.GetPropertyStrength()
|
||||
if strength > existing.Signal {
|
||||
existing.Signal = strength
|
||||
@@ -1107,6 +1135,7 @@ func (b *NetworkManagerBackend) updateAllWiFiDevices() {
|
||||
}
|
||||
}
|
||||
|
||||
profile, saved := savedProfiles[apSSID]
|
||||
network := WiFiNetwork{
|
||||
SSID: apSSID,
|
||||
BSSID: apBSSID,
|
||||
@@ -1114,9 +1143,9 @@ func (b *NetworkManagerBackend) updateAllWiFiDevices() {
|
||||
Secured: secured,
|
||||
Enterprise: enterprise,
|
||||
Connected: isConnected,
|
||||
Saved: savedSSIDs[apSSID],
|
||||
Autoconnect: autoconnectMap[apSSID],
|
||||
Hidden: hiddenSSIDs[apSSID],
|
||||
Saved: saved,
|
||||
Autoconnect: profile.Autoconnect,
|
||||
Hidden: profile.Hidden,
|
||||
Frequency: freq,
|
||||
Mode: modeStr,
|
||||
Rate: rate,
|
||||
@@ -1124,25 +1153,31 @@ func (b *NetworkManagerBackend) updateAllWiFiDevices() {
|
||||
Device: name,
|
||||
}
|
||||
|
||||
seenSSIDs[apSSID] = &network
|
||||
networks = append(networks, network)
|
||||
seenSSIDs[apSSID] = len(networks) - 1
|
||||
if existing, ok := visibleNetworks[apSSID]; !ok || network.Signal > existing.Signal {
|
||||
visibleNetworks[apSSID] = network
|
||||
}
|
||||
}
|
||||
|
||||
if connected && ssid != "" {
|
||||
if _, exists := seenSSIDs[ssid]; !exists {
|
||||
profile, saved := savedProfiles[ssid]
|
||||
hiddenNetwork := WiFiNetwork{
|
||||
SSID: ssid,
|
||||
BSSID: bssid,
|
||||
Signal: signal,
|
||||
Secured: true,
|
||||
Connected: true,
|
||||
Saved: savedSSIDs[ssid],
|
||||
Autoconnect: autoconnectMap[ssid],
|
||||
Saved: saved,
|
||||
Autoconnect: profile.Autoconnect,
|
||||
Hidden: true,
|
||||
Mode: "infrastructure",
|
||||
Device: name,
|
||||
}
|
||||
networks = append(networks, hiddenNetwork)
|
||||
seenSSIDs[ssid] = len(networks) - 1
|
||||
visibleNetworks[ssid] = hiddenNetwork
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1168,6 +1203,7 @@ func (b *NetworkManagerBackend) updateAllWiFiDevices() {
|
||||
|
||||
b.stateMutex.Lock()
|
||||
b.state.WiFiDevices = devices
|
||||
b.state.SavedWiFiNetworks = savedWiFiNetworksFromProfiles(savedProfiles, visibleNetworks, currentSSID, wifiConnected)
|
||||
b.stateMutex.Unlock()
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"testing"
|
||||
|
||||
mock_gonetworkmanager "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/github.com/Wifx/gonetworkmanager/v2"
|
||||
"github.com/Wifx/gonetworkmanager/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -176,6 +177,54 @@ func TestNetworkManagerBackend_UpdateWiFiNetworks_NoDevice(t *testing.T) {
|
||||
assert.Contains(t, err.Error(), "no WiFi device available")
|
||||
}
|
||||
|
||||
func TestNetworkManagerBackend_UpdateSavedWiFiNetworksPreservesVisibleSavedNetworks(t *testing.T) {
|
||||
mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
|
||||
mockSettings := mock_gonetworkmanager.NewMockSettings(t)
|
||||
mockConn := mock_gonetworkmanager.NewMockConnection(t)
|
||||
|
||||
backend, err := NewNetworkManagerBackend(mockNM)
|
||||
assert.NoError(t, err)
|
||||
backend.settings = mockSettings
|
||||
|
||||
backend.stateMutex.Lock()
|
||||
backend.state.WiFiNetworks = []WiFiNetwork{
|
||||
{
|
||||
SSID: "Home",
|
||||
Signal: 76,
|
||||
},
|
||||
}
|
||||
backend.stateMutex.Unlock()
|
||||
|
||||
settings := gonetworkmanager.ConnectionSettings{
|
||||
"connection": {
|
||||
"type": "802-11-wireless",
|
||||
"autoconnect": true,
|
||||
},
|
||||
"802-11-wireless": {
|
||||
"ssid": []byte("Home"),
|
||||
},
|
||||
"802-11-wireless-security": {},
|
||||
}
|
||||
mockSettings.EXPECT().ListConnections().Return([]gonetworkmanager.Connection{mockConn}, nil)
|
||||
mockConn.EXPECT().GetSettings().Return(settings, nil)
|
||||
|
||||
err = backend.updateSavedWiFiNetworks()
|
||||
assert.NoError(t, err)
|
||||
|
||||
backend.stateMutex.RLock()
|
||||
savedNetworks := append([]WiFiNetwork(nil), backend.state.SavedWiFiNetworks...)
|
||||
wifiNetworks := append([]WiFiNetwork(nil), backend.state.WiFiNetworks...)
|
||||
backend.stateMutex.RUnlock()
|
||||
|
||||
assert.Len(t, wifiNetworks, 1)
|
||||
assert.True(t, wifiNetworks[0].Saved)
|
||||
assert.Len(t, savedNetworks, 1)
|
||||
assert.Equal(t, "Home", savedNetworks[0].SSID)
|
||||
assert.True(t, savedNetworks[0].Saved)
|
||||
assert.False(t, savedNetworks[0].OutOfRange)
|
||||
assert.Equal(t, uint8(76), savedNetworks[0].Signal)
|
||||
}
|
||||
|
||||
func TestNetworkManagerBackend_FindConnection_NoSettings(t *testing.T) {
|
||||
mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
|
||||
|
||||
|
||||
@@ -64,9 +64,10 @@ func NewManager() (*Manager, error) {
|
||||
m := &Manager{
|
||||
backend: backend,
|
||||
state: &NetworkState{
|
||||
NetworkStatus: StatusDisconnected,
|
||||
Preference: PreferenceAuto,
|
||||
WiFiNetworks: []WiFiNetwork{},
|
||||
NetworkStatus: StatusDisconnected,
|
||||
Preference: PreferenceAuto,
|
||||
WiFiNetworks: []WiFiNetwork{},
|
||||
SavedWiFiNetworks: []WiFiNetwork{},
|
||||
},
|
||||
stateMutex: sync.RWMutex{},
|
||||
|
||||
@@ -120,6 +121,7 @@ func (m *Manager) syncStateFromBackend() error {
|
||||
m.state.WiFiBSSID = backendState.WiFiBSSID
|
||||
m.state.WiFiSignal = backendState.WiFiSignal
|
||||
m.state.WiFiNetworks = backendState.WiFiNetworks
|
||||
m.state.SavedWiFiNetworks = backendState.SavedWiFiNetworks
|
||||
m.state.WiFiDevices = backendState.WiFiDevices
|
||||
m.state.WiredConnections = backendState.WiredConnections
|
||||
m.state.VPNProfiles = backendState.VPNProfiles
|
||||
@@ -156,6 +158,7 @@ func (m *Manager) snapshotState() NetworkState {
|
||||
defer m.stateMutex.RUnlock()
|
||||
s := *m.state
|
||||
s.WiFiNetworks = append([]WiFiNetwork(nil), m.state.WiFiNetworks...)
|
||||
s.SavedWiFiNetworks = append([]WiFiNetwork(nil), m.state.SavedWiFiNetworks...)
|
||||
s.WiFiDevices = append([]WiFiDevice(nil), m.state.WiFiDevices...)
|
||||
s.WiredConnections = append([]WiredConnection(nil), m.state.WiredConnections...)
|
||||
s.EthernetDevices = append([]EthernetDevice(nil), m.state.EthernetDevices...)
|
||||
@@ -211,6 +214,9 @@ func stateChangedMeaningfully(old, new *NetworkState) bool {
|
||||
if len(old.WiFiNetworks) != len(new.WiFiNetworks) {
|
||||
return true
|
||||
}
|
||||
if len(old.SavedWiFiNetworks) != len(new.SavedWiFiNetworks) {
|
||||
return true
|
||||
}
|
||||
if len(old.WiFiDevices) != len(new.WiFiDevices) {
|
||||
return true
|
||||
}
|
||||
@@ -238,6 +244,23 @@ func stateChangedMeaningfully(old, new *NetworkState) bool {
|
||||
}
|
||||
}
|
||||
|
||||
for i := range old.SavedWiFiNetworks {
|
||||
oldNet := &old.SavedWiFiNetworks[i]
|
||||
newNet := &new.SavedWiFiNetworks[i]
|
||||
if oldNet.SSID != newNet.SSID {
|
||||
return true
|
||||
}
|
||||
if oldNet.Connected != newNet.Connected {
|
||||
return true
|
||||
}
|
||||
if oldNet.Autoconnect != newNet.Autoconnect {
|
||||
return true
|
||||
}
|
||||
if oldNet.OutOfRange != newNet.OutOfRange {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for i := range old.WiredConnections {
|
||||
oldNet := &old.WiredConnections[i]
|
||||
newNet := &new.WiredConnections[i]
|
||||
|
||||
@@ -34,6 +34,7 @@ type WiFiNetwork struct {
|
||||
Saved bool `json:"saved"`
|
||||
Autoconnect bool `json:"autoconnect"`
|
||||
Hidden bool `json:"hidden"`
|
||||
OutOfRange bool `json:"outOfRange"`
|
||||
Frequency uint32 `json:"frequency"`
|
||||
Mode string `json:"mode"`
|
||||
Rate uint32 `json:"rate"`
|
||||
@@ -111,6 +112,7 @@ type NetworkState struct {
|
||||
WiFiBSSID string `json:"wifiBSSID"`
|
||||
WiFiSignal uint8 `json:"wifiSignal"`
|
||||
WiFiNetworks []WiFiNetwork `json:"wifiNetworks"`
|
||||
SavedWiFiNetworks []WiFiNetwork `json:"savedWifiNetworks"`
|
||||
WiFiDevices []WiFiDevice `json:"wifiDevices"`
|
||||
WiredConnections []WiredConnection `json:"wiredConnections"`
|
||||
VPNProfiles []VPNProfile `json:"vpnProfiles"`
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
package network
|
||||
|
||||
import "sort"
|
||||
|
||||
type savedWiFiProfile struct {
|
||||
Autoconnect bool
|
||||
Hidden bool
|
||||
Secured bool
|
||||
Enterprise bool
|
||||
Mode string
|
||||
}
|
||||
|
||||
// Saved WiFi state is keyed by SSID because the UI/API accepts SSID actions.
|
||||
// Multiple backend profiles for the same SSID are intentionally collapsed here.
|
||||
func mergeSavedProfilesIntoWiFiNetworks(networks []WiFiNetwork, profiles map[string]savedWiFiProfile, currentSSID string, wifiConnected bool) []WiFiNetwork {
|
||||
merged := make([]WiFiNetwork, len(networks))
|
||||
for i, network := range networks {
|
||||
profile, saved := profiles[network.SSID]
|
||||
network.Connected = wifiConnected && network.SSID == currentSSID
|
||||
network.Saved = saved
|
||||
if saved {
|
||||
network.Autoconnect = profile.Autoconnect
|
||||
network.Hidden = network.Hidden || profile.Hidden
|
||||
network.Secured = network.Secured || profile.Secured
|
||||
network.Enterprise = network.Enterprise || profile.Enterprise
|
||||
if network.Mode == "" {
|
||||
network.Mode = profile.Mode
|
||||
}
|
||||
} else {
|
||||
network.Autoconnect = false
|
||||
}
|
||||
merged[i] = network
|
||||
}
|
||||
return merged
|
||||
}
|
||||
|
||||
func wiFiNetworksBySSID(networks []WiFiNetwork, visibleOnly bool) map[string]WiFiNetwork {
|
||||
visible := make(map[string]WiFiNetwork, len(networks))
|
||||
for _, network := range networks {
|
||||
if visibleOnly && network.OutOfRange {
|
||||
continue
|
||||
}
|
||||
visible[network.SSID] = network
|
||||
}
|
||||
return visible
|
||||
}
|
||||
|
||||
func refreshSavedWiFiState(networks []WiFiNetwork, profiles map[string]savedWiFiProfile, currentSSID string, wifiConnected bool) ([]WiFiNetwork, []WiFiNetwork) {
|
||||
mergedNetworks := mergeSavedProfilesIntoWiFiNetworks(networks, profiles, currentSSID, wifiConnected)
|
||||
visibleNetworks := wiFiNetworksBySSID(mergedNetworks, true)
|
||||
savedNetworks := savedWiFiNetworksFromProfiles(profiles, visibleNetworks, currentSSID, wifiConnected)
|
||||
return mergedNetworks, savedNetworks
|
||||
}
|
||||
|
||||
func savedWiFiNetworksFromProfiles(profiles map[string]savedWiFiProfile, visible map[string]WiFiNetwork, currentSSID string, wifiConnected bool) []WiFiNetwork {
|
||||
networks := make([]WiFiNetwork, 0, len(profiles))
|
||||
for ssid, profile := range profiles {
|
||||
if network, ok := visible[ssid]; ok {
|
||||
network.Saved = true
|
||||
network.Autoconnect = profile.Autoconnect
|
||||
network.Hidden = network.Hidden || profile.Hidden
|
||||
network.Secured = network.Secured || profile.Secured
|
||||
network.Enterprise = network.Enterprise || profile.Enterprise
|
||||
network.OutOfRange = false
|
||||
if network.Mode == "" {
|
||||
network.Mode = profile.Mode
|
||||
}
|
||||
networks = append(networks, network)
|
||||
continue
|
||||
}
|
||||
|
||||
isConnected := wifiConnected && ssid == currentSSID
|
||||
networks = append(networks, WiFiNetwork{
|
||||
SSID: ssid,
|
||||
Secured: profile.Secured,
|
||||
Enterprise: profile.Enterprise,
|
||||
Connected: isConnected,
|
||||
Saved: true,
|
||||
Autoconnect: profile.Autoconnect,
|
||||
Hidden: profile.Hidden,
|
||||
OutOfRange: !isConnected,
|
||||
Mode: profile.Mode,
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(networks, func(i, j int) bool {
|
||||
if networks[i].Connected && !networks[j].Connected {
|
||||
return true
|
||||
}
|
||||
if !networks[i].Connected && networks[j].Connected {
|
||||
return false
|
||||
}
|
||||
if networks[i].OutOfRange != networks[j].OutOfRange {
|
||||
return !networks[i].OutOfRange
|
||||
}
|
||||
if networks[i].Signal != networks[j].Signal {
|
||||
return networks[i].Signal > networks[j].Signal
|
||||
}
|
||||
return networks[i].SSID < networks[j].SSID
|
||||
})
|
||||
|
||||
return networks
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMergeSavedProfilesIntoWiFiNetworks(t *testing.T) {
|
||||
networks := []WiFiNetwork{
|
||||
{
|
||||
SSID: "Home",
|
||||
Signal: 80,
|
||||
Secured: false,
|
||||
Autoconnect: false,
|
||||
},
|
||||
{
|
||||
SSID: "Cafe",
|
||||
Signal: 50,
|
||||
Secured: false,
|
||||
Autoconnect: true,
|
||||
},
|
||||
}
|
||||
profiles := map[string]savedWiFiProfile{
|
||||
"Home": {
|
||||
Autoconnect: true,
|
||||
Hidden: true,
|
||||
Secured: true,
|
||||
Mode: "infrastructure",
|
||||
},
|
||||
}
|
||||
|
||||
merged := mergeSavedProfilesIntoWiFiNetworks(networks, profiles, "Home", true)
|
||||
|
||||
assert.Len(t, merged, 2)
|
||||
assert.Equal(t, "Home", merged[0].SSID)
|
||||
assert.True(t, merged[0].Connected)
|
||||
assert.True(t, merged[0].Saved)
|
||||
assert.True(t, merged[0].Autoconnect)
|
||||
assert.True(t, merged[0].Hidden)
|
||||
assert.True(t, merged[0].Secured)
|
||||
assert.Equal(t, "infrastructure", merged[0].Mode)
|
||||
|
||||
assert.Equal(t, "Cafe", merged[1].SSID)
|
||||
assert.False(t, merged[1].Saved)
|
||||
assert.False(t, merged[1].Autoconnect)
|
||||
}
|
||||
|
||||
func TestSavedWiFiNetworksFromProfilesOutOfRangeWithoutVisibleNetworks(t *testing.T) {
|
||||
profiles := map[string]savedWiFiProfile{
|
||||
"Home": {
|
||||
Autoconnect: true,
|
||||
Secured: true,
|
||||
Mode: "infrastructure",
|
||||
},
|
||||
}
|
||||
|
||||
networks := savedWiFiNetworksFromProfiles(profiles, nil, "", false)
|
||||
|
||||
assert.Len(t, networks, 1)
|
||||
assert.Equal(t, "Home", networks[0].SSID)
|
||||
assert.True(t, networks[0].Saved)
|
||||
assert.True(t, networks[0].OutOfRange)
|
||||
assert.Equal(t, uint8(0), networks[0].Signal)
|
||||
}
|
||||
|
||||
func TestSavedWiFiNetworksFromProfilesKeepsConnectedCurrentNetworkInRange(t *testing.T) {
|
||||
profiles := map[string]savedWiFiProfile{
|
||||
"Home": {
|
||||
Autoconnect: true,
|
||||
Secured: true,
|
||||
},
|
||||
}
|
||||
|
||||
networks := savedWiFiNetworksFromProfiles(profiles, nil, "Home", true)
|
||||
|
||||
assert.Len(t, networks, 1)
|
||||
assert.Equal(t, "Home", networks[0].SSID)
|
||||
assert.True(t, networks[0].Connected)
|
||||
assert.False(t, networks[0].OutOfRange)
|
||||
}
|
||||
|
||||
func TestSavedWiFiNetworksFromProfilesIncludesOutOfRange(t *testing.T) {
|
||||
profiles := map[string]savedWiFiProfile{
|
||||
"Home": {
|
||||
Autoconnect: true,
|
||||
Hidden: true,
|
||||
Secured: true,
|
||||
Mode: "infrastructure",
|
||||
},
|
||||
"Office": {
|
||||
Autoconnect: false,
|
||||
Secured: true,
|
||||
Enterprise: true,
|
||||
Mode: "infrastructure",
|
||||
},
|
||||
}
|
||||
visible := map[string]WiFiNetwork{
|
||||
"Home": {
|
||||
SSID: "Home",
|
||||
Signal: 72,
|
||||
Secured: true,
|
||||
Connected: true,
|
||||
},
|
||||
}
|
||||
|
||||
networks := savedWiFiNetworksFromProfiles(profiles, visible, "Home", true)
|
||||
|
||||
assert.Len(t, networks, 2)
|
||||
assert.Equal(t, "Home", networks[0].SSID)
|
||||
assert.True(t, networks[0].Saved)
|
||||
assert.True(t, networks[0].Connected)
|
||||
assert.False(t, networks[0].OutOfRange)
|
||||
assert.True(t, networks[0].Hidden)
|
||||
assert.Equal(t, uint8(72), networks[0].Signal)
|
||||
|
||||
assert.Equal(t, "Office", networks[1].SSID)
|
||||
assert.True(t, networks[1].Saved)
|
||||
assert.False(t, networks[1].Autoconnect)
|
||||
assert.True(t, networks[1].Enterprise)
|
||||
assert.True(t, networks[1].OutOfRange)
|
||||
}
|
||||
|
||||
func TestWiFiNetworksBySSIDVisibleOnlySkipsOutOfRange(t *testing.T) {
|
||||
visible := wiFiNetworksBySSID([]WiFiNetwork{
|
||||
{SSID: "Home", Signal: 70},
|
||||
{SSID: "Office", Signal: 0, OutOfRange: true},
|
||||
}, true)
|
||||
|
||||
assert.Contains(t, visible, "Home")
|
||||
assert.NotContains(t, visible, "Office")
|
||||
}
|
||||
|
||||
func TestRefreshSavedWiFiStatePreservesVisibleSavedNetworks(t *testing.T) {
|
||||
networks := []WiFiNetwork{
|
||||
{
|
||||
SSID: "Home",
|
||||
Signal: 82,
|
||||
},
|
||||
}
|
||||
profiles := map[string]savedWiFiProfile{
|
||||
"Home": {
|
||||
Autoconnect: true,
|
||||
Secured: true,
|
||||
Mode: "infrastructure",
|
||||
},
|
||||
"Office": {
|
||||
Autoconnect: false,
|
||||
Secured: true,
|
||||
Mode: "infrastructure",
|
||||
},
|
||||
}
|
||||
|
||||
mergedNetworks, savedNetworks := refreshSavedWiFiState(networks, profiles, "", false)
|
||||
|
||||
assert.Len(t, mergedNetworks, 1)
|
||||
assert.Equal(t, "Home", mergedNetworks[0].SSID)
|
||||
assert.True(t, mergedNetworks[0].Saved)
|
||||
assert.True(t, mergedNetworks[0].Autoconnect)
|
||||
|
||||
assert.Len(t, savedNetworks, 2)
|
||||
assert.Equal(t, "Home", savedNetworks[0].SSID)
|
||||
assert.True(t, savedNetworks[0].Saved)
|
||||
assert.False(t, savedNetworks[0].OutOfRange)
|
||||
assert.Equal(t, uint8(82), savedNetworks[0].Signal)
|
||||
|
||||
assert.Equal(t, "Office", savedNetworks[1].SSID)
|
||||
assert.True(t, savedNetworks[1].Saved)
|
||||
assert.True(t, savedNetworks[1].OutOfRange)
|
||||
}
|
||||
@@ -38,7 +38,7 @@ import (
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
|
||||
)
|
||||
|
||||
const APIVersion = 25
|
||||
const APIVersion = 26
|
||||
|
||||
var CLIVersion = "dev"
|
||||
|
||||
|
||||
@@ -721,7 +721,7 @@ Rectangle {
|
||||
|
||||
DankActionButton {
|
||||
id: qrCodeButton
|
||||
visible: modelData.secured && modelData.saved
|
||||
visible: modelData.secured && modelData.saved && !(modelData.enterprise || false)
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: optionsButton.width + pinWifiRow.width + 3 * Theme.spacingM + Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Common
|
||||
import qs.Modules.Settings.Widgets
|
||||
@@ -16,6 +17,7 @@ Item {
|
||||
|
||||
Component.onCompleted: {
|
||||
NetworkService.addRef();
|
||||
Qt.callLater(() => NetworkService.refreshSavedWifiNetworks());
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
@@ -40,6 +42,7 @@ Item {
|
||||
id: root
|
||||
|
||||
property string expandedWifiSsid: ""
|
||||
property string expandedSavedWifiSsid: ""
|
||||
property int maxPinnedWifiNetworks: 3
|
||||
|
||||
function normalizePinList(value) {
|
||||
@@ -84,6 +87,79 @@ Item {
|
||||
settingKey: "networkWifi"
|
||||
tags: ["wifi", "wi-fi", "wireless", "network", "ssid", "adapter", "radio"]
|
||||
|
||||
function visibleWifiBySsid(ssid) {
|
||||
const networks = NetworkService.wifiNetworks || [];
|
||||
return networks.find(network => network.ssid === ssid) || null;
|
||||
}
|
||||
|
||||
function mergedSavedWifiNetworks() {
|
||||
const saved = NetworkService.savedWifiNetworks || [];
|
||||
const supportsSavedWifiState = DMSService.apiVersion >= NetworkService.savedWifiStateApiVersion;
|
||||
const result = [];
|
||||
const seen = new Set();
|
||||
|
||||
for (const network of saved) {
|
||||
if (!network?.ssid || seen.has(network.ssid))
|
||||
continue;
|
||||
const isOutOfRange = supportsSavedWifiState ? network.outOfRange === true : false;
|
||||
const visibleNetwork = !isOutOfRange ? visibleWifiBySsid(network.ssid) : null;
|
||||
if (visibleNetwork) {
|
||||
result.push(Object.assign({}, network, visibleNetwork, {
|
||||
saved: true,
|
||||
autoconnect: network.autoconnect ?? visibleNetwork.autoconnect,
|
||||
hidden: (network.hidden || false) || (visibleNetwork.hidden || false),
|
||||
outOfRange: false
|
||||
}));
|
||||
} else {
|
||||
result.push(Object.assign({}, network, {
|
||||
saved: true,
|
||||
outOfRange: isOutOfRange
|
||||
}));
|
||||
}
|
||||
seen.add(network.ssid);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function sortedSavedWifiNetworks() {
|
||||
const ssid = NetworkService.currentWifiSSID;
|
||||
const pinnedList = root.getPinnedWifiNetworks();
|
||||
let sorted = root.mergedSavedWifiNetworks();
|
||||
|
||||
sorted.sort((a, b) => {
|
||||
const aPinnedIndex = pinnedList.indexOf(a.ssid);
|
||||
const bPinnedIndex = pinnedList.indexOf(b.ssid);
|
||||
if (aPinnedIndex !== -1 || bPinnedIndex !== -1) {
|
||||
if (aPinnedIndex === -1)
|
||||
return 1;
|
||||
if (bPinnedIndex === -1)
|
||||
return -1;
|
||||
return aPinnedIndex - bPinnedIndex;
|
||||
}
|
||||
if (a.ssid === ssid)
|
||||
return -1;
|
||||
if (b.ssid === ssid)
|
||||
return 1;
|
||||
if ((a.outOfRange || false) !== (b.outOfRange || false))
|
||||
return (a.outOfRange || false) ? 1 : -1;
|
||||
if ((a.signal || 0) !== (b.signal || 0))
|
||||
return (b.signal || 0) - (a.signal || 0);
|
||||
return (a.ssid || "").localeCompare(b.ssid || "");
|
||||
});
|
||||
return sorted;
|
||||
}
|
||||
|
||||
function showForgetNetworkConfirm(ssid) {
|
||||
forgetNetworkConfirm.showWithOptions({
|
||||
title: I18n.tr("Forget Network"),
|
||||
message: I18n.tr("Forget \"%1\"?").arg(ssid),
|
||||
confirmText: I18n.tr("Forget"),
|
||||
confirmColor: Theme.error,
|
||||
onConfirm: () => NetworkService.forgetWifiNetwork(ssid)
|
||||
});
|
||||
}
|
||||
|
||||
Column {
|
||||
id: wifiSection
|
||||
|
||||
@@ -563,7 +639,7 @@ Item {
|
||||
DankActionButton {
|
||||
iconName: "qr_code"
|
||||
buttonSize: 28
|
||||
visible: modelData.secured && modelData.saved
|
||||
visible: modelData.secured && modelData.saved && !(modelData.enterprise || false)
|
||||
onClicked: {
|
||||
PopoutService.showWifiQRCodeModal(modelData.ssid);
|
||||
}
|
||||
@@ -584,13 +660,7 @@ Item {
|
||||
iconColor: Theme.error
|
||||
visible: modelData.saved || isConnected
|
||||
onClicked: {
|
||||
forgetNetworkConfirm.showWithOptions({
|
||||
title: I18n.tr("Forget Network"),
|
||||
message: I18n.tr("Forget \"%1\"?").arg(modelData.ssid),
|
||||
confirmText: I18n.tr("Forget"),
|
||||
confirmColor: Theme.error,
|
||||
onConfirm: () => NetworkService.forgetWifiNetwork(modelData.ssid)
|
||||
});
|
||||
root.showForgetNetworkConfirm(modelData.ssid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -756,6 +826,421 @@ Item {
|
||||
}
|
||||
}
|
||||
}
|
||||
SettingsCard {
|
||||
id: savedWifiCard
|
||||
|
||||
readonly property var savedNetworks: root.sortedSavedWifiNetworks()
|
||||
|
||||
width: parent.width
|
||||
title: I18n.tr("Saved Networks")
|
||||
iconName: "bookmark"
|
||||
settingKey: "networkSavedWifi"
|
||||
tags: ["wifi", "wi-fi", "wireless", "network", "saved", "known", "ssid", "autoconnect", "forget"]
|
||||
collapsible: true
|
||||
expanded: false
|
||||
visible: savedNetworks.length > 0
|
||||
|
||||
headerActions: [
|
||||
StyledText {
|
||||
text: savedWifiCard.savedNetworks.length
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
]
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 4
|
||||
|
||||
Repeater {
|
||||
model: savedWifiCard.expanded ? savedWifiCard.savedNetworks : []
|
||||
|
||||
delegate: Rectangle {
|
||||
id: savedWifiDelegate
|
||||
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
readonly property bool isConnected: modelData.ssid === NetworkService.currentWifiSSID
|
||||
readonly property bool isPinned: root.getPinnedWifiNetworks().includes(modelData.ssid)
|
||||
readonly property bool isOutOfRange: modelData.outOfRange || false
|
||||
readonly property bool isExpanded: !isOutOfRange && root.expandedSavedWifiSsid === modelData.ssid
|
||||
|
||||
width: parent.width
|
||||
height: isExpanded ? 56 + savedWifiExpandedContent.height : 56
|
||||
radius: Theme.cornerRadius
|
||||
color: savedWifiMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceLight
|
||||
border.width: isConnected ? 2 : 0
|
||||
border.color: Theme.primary
|
||||
clip: true
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 56
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: savedWifiActions.left
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: {
|
||||
if (isOutOfRange)
|
||||
return "wifi_off";
|
||||
const s = modelData.signal || 0;
|
||||
if (s >= 50)
|
||||
return "wifi";
|
||||
if (s >= 25)
|
||||
return "wifi_2_bar";
|
||||
return "wifi_1_bar";
|
||||
}
|
||||
size: 20
|
||||
color: isConnected ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
width: parent.width - 20 - Theme.spacingS
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
spacing: Theme.spacingXS
|
||||
width: parent.width
|
||||
|
||||
StyledText {
|
||||
text: modelData.ssid || I18n.tr("Unknown")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: isConnected ? Theme.primary : Theme.surfaceText
|
||||
font.weight: isConnected ? Font.Medium : Font.Normal
|
||||
elide: Text.ElideRight
|
||||
width: Math.max(0, parent.width - (savedWifiHiddenIcon.visible ? savedWifiHiddenIcon.width + Theme.spacingXS : 0))
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
id: savedWifiHiddenIcon
|
||||
name: "visibility_off"
|
||||
size: 14
|
||||
color: Theme.surfaceVariantText
|
||||
visible: modelData.hidden || false
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
const parts = [isConnected ? I18n.tr("Connected") : (modelData.secured ? I18n.tr("Secured") : I18n.tr("Open"))];
|
||||
parts.push(isOutOfRange ? I18n.tr("Unavailable") : (modelData.signal || 0) + "%");
|
||||
if (modelData.hidden || false)
|
||||
parts.push(I18n.tr("Hidden"));
|
||||
return parts.join(" • ");
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: isConnected ? Theme.primary : Theme.surfaceVariantText
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: savedWifiActions
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Rectangle {
|
||||
width: 28
|
||||
height: 28
|
||||
radius: 14
|
||||
color: savedWifiExpandBtn.containsMouse ? Theme.surfacePressed : "transparent"
|
||||
visible: !isOutOfRange
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: isExpanded ? "expand_less" : "expand_more"
|
||||
size: 18
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: savedWifiExpandBtn
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (isExpanded) {
|
||||
root.expandedSavedWifiSsid = "";
|
||||
} else {
|
||||
root.expandedSavedWifiSsid = modelData.ssid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
iconName: "qr_code"
|
||||
buttonSize: 28
|
||||
visible: modelData.secured && !(modelData.enterprise || false)
|
||||
onClicked: {
|
||||
PopoutService.showWifiQRCodeModal(modelData.ssid);
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
iconName: "push_pin"
|
||||
buttonSize: 28
|
||||
iconColor: isPinned ? Theme.primary : Theme.surfaceVariantText
|
||||
onClicked: {
|
||||
root.toggleWifiPin(modelData.ssid);
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: savedWifiMoreButton
|
||||
iconName: "more_horiz"
|
||||
buttonSize: 28
|
||||
onClicked: {
|
||||
if (savedWifiMenu.visible) {
|
||||
savedWifiMenu.close();
|
||||
return;
|
||||
}
|
||||
savedWifiMenu.popup(savedWifiMoreButton, -savedWifiMenu.width + savedWifiMoreButton.width, savedWifiMoreButton.height + Theme.spacingXS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: savedWifiMouseArea
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: savedWifiActions.width + Theme.spacingM
|
||||
hoverEnabled: true
|
||||
cursorShape: isOutOfRange ? Qt.ArrowCursor : Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (isOutOfRange)
|
||||
return;
|
||||
if (isExpanded) {
|
||||
root.expandedSavedWifiSsid = "";
|
||||
} else {
|
||||
root.expandedSavedWifiSsid = modelData.ssid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: savedWifiExpandedContent
|
||||
width: parent.width
|
||||
visible: isExpanded
|
||||
|
||||
Rectangle {
|
||||
width: parent.width - Theme.spacingM * 2
|
||||
height: 1
|
||||
x: Theme.spacingM
|
||||
color: Theme.outlineLight
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: savedWifiDetailsColumn.implicitHeight + Theme.spacingM * 2
|
||||
|
||||
Column {
|
||||
id: savedWifiDetailsColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Flow {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Repeater {
|
||||
model: {
|
||||
const fields = [];
|
||||
const net = modelData;
|
||||
if (!net)
|
||||
return fields;
|
||||
|
||||
fields.push({
|
||||
label: I18n.tr("Signal"),
|
||||
value: (net.signal || 0) + "%"
|
||||
});
|
||||
if (net.frequency)
|
||||
fields.push({
|
||||
label: I18n.tr("Frequency"),
|
||||
value: (net.frequency / 1000).toFixed(1) + " GHz"
|
||||
});
|
||||
if (net.channel)
|
||||
fields.push({
|
||||
label: I18n.tr("Channel"),
|
||||
value: String(net.channel)
|
||||
});
|
||||
if (net.rate)
|
||||
fields.push({
|
||||
label: I18n.tr("Rate"),
|
||||
value: net.rate + " Mbps"
|
||||
});
|
||||
if (net.mode)
|
||||
fields.push({
|
||||
label: I18n.tr("Mode"),
|
||||
value: net.mode
|
||||
});
|
||||
if (net.bssid)
|
||||
fields.push({
|
||||
label: I18n.tr("BSSID"),
|
||||
value: net.bssid
|
||||
});
|
||||
fields.push({
|
||||
label: I18n.tr("Security"),
|
||||
value: net.secured ? (net.enterprise ? I18n.tr("Enterprise") : I18n.tr("WPA/WPA2")) : I18n.tr("Open")
|
||||
});
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: savedWifiFieldContent.width + Theme.spacingM * 2
|
||||
height: 32
|
||||
radius: Theme.cornerRadius - 2
|
||||
color: Theme.surfaceContainerHigh
|
||||
border.width: 1
|
||||
border.color: Theme.outlineLight
|
||||
|
||||
Row {
|
||||
id: savedWifiFieldContent
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: modelData.label + ":"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData.value
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Menu {
|
||||
id: savedWifiMenu
|
||||
width: 170
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
|
||||
|
||||
background: Rectangle {
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
radius: Theme.cornerRadius
|
||||
border.width: 0
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: isConnected ? I18n.tr("Disconnect") : I18n.tr("Connect")
|
||||
height: isOutOfRange ? 0 : 32
|
||||
visible: !isOutOfRange
|
||||
|
||||
contentItem: StyledText {
|
||||
text: parent.text
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
leftPadding: Theme.spacingS
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: parent.hovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||
radius: Theme.cornerRadius / 2
|
||||
}
|
||||
|
||||
onTriggered: {
|
||||
if (isConnected) {
|
||||
NetworkService.disconnectWifi();
|
||||
return;
|
||||
}
|
||||
NetworkService.connectToWifi(modelData.ssid);
|
||||
}
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: modelData.autoconnect ? I18n.tr("Disable Autoconnect") : I18n.tr("Enable Autoconnect")
|
||||
height: DMSService.apiVersion > 13 ? 32 : 0
|
||||
visible: DMSService.apiVersion > 13
|
||||
|
||||
contentItem: StyledText {
|
||||
text: parent.text
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
leftPadding: Theme.spacingS
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: parent.hovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||
radius: Theme.cornerRadius / 2
|
||||
}
|
||||
|
||||
onTriggered: {
|
||||
NetworkService.setWifiAutoconnect(modelData.ssid, !(modelData.autoconnect || false));
|
||||
}
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: I18n.tr("Forget Network")
|
||||
height: 32
|
||||
|
||||
contentItem: StyledText {
|
||||
text: parent.text
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.error
|
||||
leftPadding: Theme.spacingS
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: parent.hovered ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.08) : "transparent"
|
||||
radius: Theme.cornerRadius / 2
|
||||
}
|
||||
|
||||
onTriggered: {
|
||||
root.showForgetNetworkConfirm(modelData.ssid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ Singleton {
|
||||
property bool changingPreference: false
|
||||
property string targetPreference: ""
|
||||
property var savedWifiNetworks: []
|
||||
readonly property int savedWifiStateApiVersion: 26
|
||||
property string connectionStatus: ""
|
||||
property string lastConnectionError: ""
|
||||
property bool passwordDialogShouldReopen: false
|
||||
@@ -309,17 +310,21 @@ Singleton {
|
||||
|
||||
if (state.wifiNetworks) {
|
||||
wifiNetworks = state.wifiNetworks;
|
||||
}
|
||||
|
||||
if (state.wifiNetworks || state.savedWifiNetworks) {
|
||||
const hasSavedWifiState = DMSService.apiVersion >= savedWifiStateApiVersion && Array.isArray(state.savedWifiNetworks);
|
||||
const sourceSavedNetworks = hasSavedWifiState ? state.savedWifiNetworks : (state.wifiNetworks || []).filter(network => network.saved);
|
||||
const saved = [];
|
||||
const mapping = {};
|
||||
for (const network of state.wifiNetworks) {
|
||||
if (network.saved) {
|
||||
saved.push({
|
||||
ssid: network.ssid,
|
||||
saved: true
|
||||
});
|
||||
for (const network of sourceSavedNetworks) {
|
||||
const normalized = Object.assign({}, network, {
|
||||
saved: true,
|
||||
outOfRange: hasSavedWifiState ? network.outOfRange === true : false
|
||||
});
|
||||
saved.push(normalized);
|
||||
if (network?.ssid)
|
||||
mapping[network.ssid] = network.ssid;
|
||||
}
|
||||
}
|
||||
savedConnections = saved;
|
||||
savedWifiNetworks = saved;
|
||||
@@ -596,6 +601,7 @@ Singleton {
|
||||
}
|
||||
wifiNetworks = updated;
|
||||
networksUpdated();
|
||||
Qt.callLater(() => refreshSavedWifiNetworks());
|
||||
}
|
||||
forgetSSID = "";
|
||||
});
|
||||
@@ -985,4 +991,11 @@ Singleton {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function refreshSavedWifiNetworks() {
|
||||
if (!networkAvailable)
|
||||
return;
|
||||
|
||||
getState();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,9 +142,11 @@ Singleton {
|
||||
|
||||
readonly property var savedConnections: wifiNetworks.filter(n => n.saved).map(n => ({
|
||||
"ssid": n.ssid,
|
||||
"saved": true
|
||||
"saved": true,
|
||||
"outOfRange": false
|
||||
}))
|
||||
readonly property var savedWifiNetworks: savedConnections
|
||||
readonly property int savedWifiStateApiVersion: 26
|
||||
readonly property var ssidToConnectionName: {
|
||||
const map = {};
|
||||
for (const n of wifiNetworks) {
|
||||
|
||||
@@ -54,6 +54,7 @@ Singleton {
|
||||
property bool changingPreference: activeService?.changingPreference ?? false
|
||||
property string targetPreference: activeService?.targetPreference ?? ""
|
||||
property var savedWifiNetworks: activeService?.savedWifiNetworks ?? []
|
||||
readonly property int savedWifiStateApiVersion: activeService?.savedWifiStateApiVersion ?? 26
|
||||
property string connectionStatus: activeService?.connectionStatus ?? ""
|
||||
property string lastConnectionError: activeService?.lastConnectionError ?? ""
|
||||
property bool passwordDialogShouldReopen: activeService?.passwordDialogShouldReopen ?? false
|
||||
@@ -180,6 +181,12 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
function refreshSavedWifiNetworks() {
|
||||
if (activeService && activeService.refreshSavedWifiNetworks) {
|
||||
activeService.refreshSavedWifiNetworks();
|
||||
}
|
||||
}
|
||||
|
||||
function connectToWifi(ssid, password = "", username = "", anonymousIdentity = "", domainSuffixMatch = "") {
|
||||
if (activeService && activeService.connectToWifi) {
|
||||
activeService.connectToWifi(ssid, password, username, anonymousIdentity, domainSuffixMatch);
|
||||
|
||||
@@ -8882,6 +8882,30 @@
|
||||
],
|
||||
"icon": "settings_ethernet"
|
||||
},
|
||||
{
|
||||
"section": "networkSavedWifi",
|
||||
"label": "Saved Networks",
|
||||
"tabIndex": 40,
|
||||
"category": "Network",
|
||||
"keywords": [
|
||||
"autoconnect",
|
||||
"connection",
|
||||
"connectivity",
|
||||
"ethernet",
|
||||
"forget",
|
||||
"internet",
|
||||
"known",
|
||||
"network",
|
||||
"networks",
|
||||
"online",
|
||||
"saved",
|
||||
"ssid",
|
||||
"wi-fi",
|
||||
"wifi",
|
||||
"wireless"
|
||||
],
|
||||
"icon": "bookmark"
|
||||
},
|
||||
{
|
||||
"section": "networkWifi",
|
||||
"label": "WiFi",
|
||||
|
||||
Reference in New Issue
Block a user