package network import ( "fmt" "sync" "time" "github.com/AvengeMedia/DankMaterialShell/core/internal/log" ) func NewManager() (*Manager, error) { detection, err := DetectNetworkStack() if err != nil { return nil, fmt.Errorf("failed to detect network stack: %w", err) } log.Infof("Network backend detection: %s", detection.ChosenReason) var backend Backend switch detection.Backend { case BackendNetworkManager: nm, err := NewNetworkManagerBackend() if err != nil { return nil, fmt.Errorf("failed to create NetworkManager backend: %w", err) } backend = nm case BackendIwd: iwd, err := NewIWDBackend() if err != nil { return nil, fmt.Errorf("failed to create iwd backend: %w", err) } backend = iwd case BackendNetworkd: if detection.HasIwd && !detection.HasNM { wifi, err := NewIWDBackend() if err != nil { return nil, fmt.Errorf("failed to create iwd backend: %w", err) } l3, err := NewSystemdNetworkdBackend() if err != nil { return nil, fmt.Errorf("failed to create networkd backend: %w", err) } hybrid, err := NewHybridIwdNetworkdBackend(wifi, l3) if err != nil { return nil, fmt.Errorf("failed to create hybrid backend: %w", err) } backend = hybrid } else { nd, err := NewSystemdNetworkdBackend() if err != nil { return nil, fmt.Errorf("failed to create networkd backend: %w", err) } backend = nd } default: return nil, fmt.Errorf("no supported network backend found: %s", detection.ChosenReason) } m := &Manager{ backend: backend, state: &NetworkState{ NetworkStatus: StatusDisconnected, Preference: PreferenceAuto, WiFiNetworks: []WiFiNetwork{}, }, stateMutex: sync.RWMutex{}, stopChan: make(chan struct{}), dirty: make(chan struct{}, 1), } broker := NewSubscriptionBroker(m.broadcastCredentialPrompt) if err := backend.SetPromptBroker(broker); err != nil { return nil, fmt.Errorf("failed to set prompt broker: %w", err) } if err := backend.Initialize(); err != nil { return nil, fmt.Errorf("failed to initialize backend: %w", err) } if err := m.syncStateFromBackend(); err != nil { return nil, fmt.Errorf("failed to sync initial state: %w", err) } m.notifierWg.Add(1) go m.notifier() if err := backend.StartMonitoring(m.onBackendStateChange); err != nil { m.Close() return nil, fmt.Errorf("failed to start monitoring: %w", err) } return m, nil } func (m *Manager) syncStateFromBackend() error { backendState, err := m.backend.GetCurrentState() if err != nil { return err } m.stateMutex.Lock() m.state.Backend = backendState.Backend m.state.NetworkStatus = backendState.NetworkStatus m.state.EthernetIP = backendState.EthernetIP m.state.EthernetDevice = backendState.EthernetDevice m.state.EthernetConnected = backendState.EthernetConnected m.state.EthernetConnectionUuid = backendState.EthernetConnectionUuid m.state.EthernetDevices = backendState.EthernetDevices m.state.WiFiIP = backendState.WiFiIP m.state.WiFiDevice = backendState.WiFiDevice m.state.WiFiConnected = backendState.WiFiConnected m.state.WiFiEnabled = backendState.WiFiEnabled m.state.WiFiSSID = backendState.WiFiSSID m.state.WiFiBSSID = backendState.WiFiBSSID m.state.WiFiSignal = backendState.WiFiSignal m.state.WiFiNetworks = backendState.WiFiNetworks m.state.WiFiDevices = backendState.WiFiDevices m.state.WiredConnections = backendState.WiredConnections m.state.VPNProfiles = backendState.VPNProfiles m.state.VPNActive = backendState.VPNActive m.state.IsConnecting = backendState.IsConnecting m.state.ConnectingSSID = backendState.ConnectingSSID m.state.ConnectingDevice = backendState.ConnectingDevice m.state.LastError = backendState.LastError m.stateMutex.Unlock() return nil } func (m *Manager) onBackendStateChange() { if err := m.syncStateFromBackend(); err != nil { log.Errorf("failed to sync state from backend: %v", err) } m.notifySubscribers() } func signalChangeSignificant(old, new uint8) bool { if old == 0 || new == 0 { return true } diff := int(new) - int(old) if diff < 0 { diff = -diff } return diff >= 5 } func (m *Manager) snapshotState() NetworkState { m.stateMutex.RLock() defer m.stateMutex.RUnlock() s := *m.state s.WiFiNetworks = append([]WiFiNetwork(nil), m.state.WiFiNetworks...) s.WiFiDevices = append([]WiFiDevice(nil), m.state.WiFiDevices...) s.WiredConnections = append([]WiredConnection(nil), m.state.WiredConnections...) s.EthernetDevices = append([]EthernetDevice(nil), m.state.EthernetDevices...) s.VPNProfiles = append([]VPNProfile(nil), m.state.VPNProfiles...) s.VPNActive = append([]VPNActive(nil), m.state.VPNActive...) return s } func stateChangedMeaningfully(old, new *NetworkState) bool { if old.NetworkStatus != new.NetworkStatus { return true } if old.Preference != new.Preference { return true } if old.EthernetConnected != new.EthernetConnected { return true } if old.EthernetIP != new.EthernetIP { return true } if old.WiFiConnected != new.WiFiConnected { return true } if old.WiFiEnabled != new.WiFiEnabled { return true } if old.WiFiSSID != new.WiFiSSID { return true } if old.WiFiBSSID != new.WiFiBSSID { return true } if old.WiFiIP != new.WiFiIP { return true } if !signalChangeSignificant(old.WiFiSignal, new.WiFiSignal) { if old.WiFiSignal != new.WiFiSignal { return false } } else if old.WiFiSignal != new.WiFiSignal { return true } if old.IsConnecting != new.IsConnecting { return true } if old.ConnectingSSID != new.ConnectingSSID { return true } if old.LastError != new.LastError { return true } if len(old.WiFiNetworks) != len(new.WiFiNetworks) { return true } if len(old.WiFiDevices) != len(new.WiFiDevices) { return true } if len(old.WiredConnections) != len(new.WiredConnections) { return true } if len(old.EthernetDevices) != len(new.EthernetDevices) { return true } for i := range old.WiFiNetworks { oldNet := &old.WiFiNetworks[i] newNet := &new.WiFiNetworks[i] if oldNet.SSID != newNet.SSID { return true } if oldNet.Connected != newNet.Connected { return true } if oldNet.Saved != newNet.Saved { return true } if oldNet.Autoconnect != newNet.Autoconnect { return true } } for i := range old.WiredConnections { oldNet := &old.WiredConnections[i] newNet := &new.WiredConnections[i] if oldNet.ID != newNet.ID { return true } if oldNet.IsActive != newNet.IsActive { return true } } for i := range old.EthernetDevices { oldDev := &old.EthernetDevices[i] newDev := &new.EthernetDevices[i] if oldDev.Name != newDev.Name { return true } if oldDev.Connected != newDev.Connected { return true } if oldDev.State != newDev.State { return true } if oldDev.IP != newDev.IP { return true } } // Check VPN profiles count if len(old.VPNProfiles) != len(new.VPNProfiles) { return true } // Check active VPN connections count or state if len(old.VPNActive) != len(new.VPNActive) { return true } // Check if any active VPN changed for i := range old.VPNActive { oldVPN := &old.VPNActive[i] newVPN := &new.VPNActive[i] if oldVPN.UUID != newVPN.UUID { return true } if oldVPN.State != newVPN.State { return true } } return false } func (m *Manager) GetState() NetworkState { return m.snapshotState() } func (m *Manager) Subscribe(id string) chan NetworkState { ch := make(chan NetworkState, 64) m.subscribers.Store(id, ch) return ch } func (m *Manager) Unsubscribe(id string) { if val, ok := m.subscribers.LoadAndDelete(id); ok { close(val) } } func (m *Manager) SubscribeCredentials(id string) chan CredentialPrompt { ch := make(chan CredentialPrompt, 16) m.credentialSubscribers.Store(id, ch) return ch } func (m *Manager) UnsubscribeCredentials(id string) { if ch, ok := m.credentialSubscribers.LoadAndDelete(id); ok { close(ch) } } func (m *Manager) broadcastCredentialPrompt(prompt CredentialPrompt) { m.credentialSubscribers.Range(func(key string, ch chan CredentialPrompt) bool { select { case ch <- prompt: default: } return true }) } func (m *Manager) notifier() { defer m.notifierWg.Done() const minGap = 100 * time.Millisecond timer := time.NewTimer(minGap) timer.Stop() var pending bool for { select { case <-m.stopChan: timer.Stop() return case <-m.dirty: if pending { continue } pending = true timer.Reset(minGap) case <-timer.C: if !pending { continue } currentState := m.snapshotState() if m.lastNotifiedState != nil && !stateChangedMeaningfully(m.lastNotifiedState, ¤tState) { pending = false continue } m.subscribers.Range(func(key string, ch chan NetworkState) bool { select { case ch <- currentState: default: } return true }) stateCopy := currentState m.lastNotifiedState = &stateCopy pending = false } } } func (m *Manager) notifySubscribers() { select { case m.dirty <- struct{}{}: default: } } func (m *Manager) SetPromptBroker(broker PromptBroker) error { return m.backend.SetPromptBroker(broker) } func (m *Manager) SubmitCredentials(token string, secrets map[string]string, save bool) error { return m.backend.SubmitCredentials(token, secrets, save) } func (m *Manager) CancelCredentials(token string) error { return m.backend.CancelCredentials(token) } func (m *Manager) GetPromptBroker() PromptBroker { return m.backend.GetPromptBroker() } func (m *Manager) Close() { close(m.stopChan) m.notifierWg.Wait() if m.backend != nil { m.backend.Close() } m.subscribers.Range(func(key string, ch chan NetworkState) bool { close(ch) m.subscribers.Delete(key) return true }) } func (m *Manager) ScanWiFi() error { return m.backend.ScanWiFi() } func (m *Manager) GetWiFiNetworks() []WiFiNetwork { m.stateMutex.RLock() defer m.stateMutex.RUnlock() networks := make([]WiFiNetwork, len(m.state.WiFiNetworks)) copy(networks, m.state.WiFiNetworks) return networks } func (m *Manager) GetNetworkInfo(ssid string) (*WiFiNetwork, error) { m.stateMutex.RLock() defer m.stateMutex.RUnlock() for _, network := range m.state.WiFiNetworks { if network.SSID == ssid { return &network, nil } } return nil, fmt.Errorf("network not found: %s", ssid) } func (m *Manager) GetNetworkInfoDetailed(ssid string) (*NetworkInfoResponse, error) { return m.backend.GetWiFiNetworkDetails(ssid) } func (m *Manager) ToggleWiFi() error { enabled, err := m.backend.GetWiFiEnabled() if err != nil { return fmt.Errorf("failed to get WiFi state: %w", err) } err = m.backend.SetWiFiEnabled(!enabled) if err != nil { return fmt.Errorf("failed to toggle WiFi: %w", err) } return nil } func (m *Manager) EnableWiFi() error { err := m.backend.SetWiFiEnabled(true) if err != nil { return fmt.Errorf("failed to enable WiFi: %w", err) } return nil } func (m *Manager) DisableWiFi() error { err := m.backend.SetWiFiEnabled(false) if err != nil { return fmt.Errorf("failed to disable WiFi: %w", err) } return nil } func (m *Manager) ConnectWiFi(req ConnectionRequest) error { return m.backend.ConnectWiFi(req) } func (m *Manager) DisconnectWiFi() error { return m.backend.DisconnectWiFi() } func (m *Manager) ForgetWiFiNetwork(ssid string) error { return m.backend.ForgetWiFiNetwork(ssid) } func (m *Manager) GetWiredConfigs() []WiredConnection { m.stateMutex.RLock() defer m.stateMutex.RUnlock() configs := make([]WiredConnection, len(m.state.WiredConnections)) copy(configs, m.state.WiredConnections) return configs } func (m *Manager) GetWiredNetworkInfoDetailed(uuid string) (*WiredNetworkInfoResponse, error) { return m.backend.GetWiredNetworkDetails(uuid) } func (m *Manager) ConnectEthernet() error { return m.backend.ConnectEthernet() } func (m *Manager) DisconnectEthernet() error { return m.backend.DisconnectEthernet() } func (m *Manager) DisconnectEthernetDevice(device string) error { return m.backend.DisconnectEthernetDevice(device) } func (m *Manager) GetEthernetDevices() []EthernetDevice { m.stateMutex.RLock() defer m.stateMutex.RUnlock() devices := make([]EthernetDevice, len(m.state.EthernetDevices)) copy(devices, m.state.EthernetDevices) return devices } func (m *Manager) activateConnection(uuid string) error { return m.backend.ActivateWiredConnection(uuid) } func (m *Manager) ListVPNProfiles() ([]VPNProfile, error) { return m.backend.ListVPNProfiles() } func (m *Manager) ListActiveVPN() ([]VPNActive, error) { return m.backend.ListActiveVPN() } func (m *Manager) ConnectVPN(uuidOrName string, singleActive bool) error { return m.backend.ConnectVPN(uuidOrName, singleActive) } func (m *Manager) DisconnectVPN(uuidOrName string) error { return m.backend.DisconnectVPN(uuidOrName) } func (m *Manager) DisconnectAllVPN() error { return m.backend.DisconnectAllVPN() } func (m *Manager) ClearVPNCredentials(uuidOrName string) error { return m.backend.ClearVPNCredentials(uuidOrName) } func (m *Manager) ListVPNPlugins() ([]VPNPlugin, error) { return m.backend.ListVPNPlugins() } func (m *Manager) ImportVPN(filePath string, name string) (*VPNImportResult, error) { return m.backend.ImportVPN(filePath, name) } func (m *Manager) GetVPNConfig(uuidOrName string) (*VPNConfig, error) { return m.backend.GetVPNConfig(uuidOrName) } func (m *Manager) UpdateVPNConfig(uuid string, updates map[string]any) error { return m.backend.UpdateVPNConfig(uuid, updates) } func (m *Manager) DeleteVPN(uuidOrName string) error { return m.backend.DeleteVPN(uuidOrName) } func (m *Manager) SetVPNCredentials(uuid, username, password string, save bool) error { return m.backend.SetVPNCredentials(uuid, username, password, save) } func (m *Manager) SetWiFiAutoconnect(ssid string, autoconnect bool) error { return m.backend.SetWiFiAutoconnect(ssid, autoconnect) } func (m *Manager) GetWiFiDevices() []WiFiDevice { m.stateMutex.RLock() defer m.stateMutex.RUnlock() devices := make([]WiFiDevice, len(m.state.WiFiDevices)) copy(devices, m.state.WiFiDevices) return devices } func (m *Manager) ScanWiFiDevice(device string) error { return m.backend.ScanWiFiDevice(device) } func (m *Manager) DisconnectWiFiDevice(device string) error { return m.backend.DisconnectWiFiDevice(device) }