mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-02 03:28:28 -04:00
dc75f1f01d
* Add hotspot contract layer with capability-gated dispatch. Establish the interface and manager plumbing for hotspot support so backends can opt in without expanding the core Backend contract. Only backends that implement HotspotBackend get hotspot state propagated; others are forced to unsupported regardless of what they self-report. * Add IPC handlers and API docs for hotspot actions. Wire up configure/ start/ stop hotspot through the request router so the QML service layer can drive hotspot operations. Bump API version to 27 and document the capability-gating contract clients should follow. * Implement NetworkManager hotspot backend. Implement HotspotBackend on NetworkManagerBackend with DMS-owned profile management, AP capability detection, and band validation. Device resolution is deferred to StartHotspot when no device is specified, so profiles survive hardware changes. * Isolate client Wi-Fi state from AP-mode connections. Filter AP-mode profiles and access points out of all client Wi-Fi paths so the DMS hotspot (and user-created APs) never appear as saved networks, visible networks, or connected state. This protects existing client behavior before hotspot controls are exposed in the UI. * Prefer idle radios for automatic hotspot device selection. * Add hotspot properties and methods to QML service layer. Expose hotspot state and actions through DMSNetworkService, NetworkService, and LegacyNetworkService. Capability is gated on API version and backend-reported support, not backend name checks, and stays stable when Wi-Fi radio is disabled. * Add hotspot controls to Settings and Control Center. Settings shows a hotspot setup card as a sibling in the Wi-Fi tab with SSID, password, device, band, save, and start/ stop controls. Control Center shows a compact row that toggles a configured hotspot or routes to Settings for initial setup. Both stay visible when Wi-Fi is disabled, explaining the requirement instead of hiding. * Add translator context to hotspot strings.
815 lines
20 KiB
Go
815 lines
20 KiB
Go
package network
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
|
"github.com/yeqown/go-qrcode/v2"
|
|
"github.com/yeqown/go-qrcode/writer/standard"
|
|
)
|
|
|
|
// ErrNoNetworkBackend is returned when no supported network management daemon
|
|
// (NetworkManager, iwd, systemd-networkd, ConnMan, wpa_supplicant) is present
|
|
// on the system.
|
|
var ErrNoNetworkBackend = errors.New("no supported network backend found")
|
|
|
|
// ErrHotspotNotSupported is returned when the active backend does not implement
|
|
// hotspot operations.
|
|
var ErrHotspotNotSupported = errors.New("hotspot not supported by active network backend")
|
|
|
|
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
|
|
}
|
|
|
|
case BackendWpaSupplicant:
|
|
wpa, err := NewWpaSupplicantBackend()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create wpa_supplicant backend: %w", err)
|
|
}
|
|
backend = wpa
|
|
|
|
default:
|
|
return nil, fmt.Errorf("%w: %s", ErrNoNetworkBackend, detection.ChosenReason)
|
|
}
|
|
|
|
m := &Manager{
|
|
backend: backend,
|
|
state: &NetworkState{
|
|
NetworkStatus: StatusDisconnected,
|
|
Preference: PreferenceAuto,
|
|
WiFiNetworks: []WiFiNetwork{},
|
|
SavedWiFiNetworks: []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) hotspotBackend() (HotspotBackend, bool) {
|
|
backend, ok := m.backend.(HotspotBackend)
|
|
return backend, ok
|
|
}
|
|
|
|
func (m *Manager) syncStateFromBackend() error {
|
|
backendState, err := m.backend.GetCurrentState()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, hotspotSupported := m.hotspotBackend()
|
|
|
|
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.SavedWiFiNetworks = backendState.SavedWiFiNetworks
|
|
m.state.WiFiDevices = backendState.WiFiDevices
|
|
m.state.HotspotSupported = hotspotSupported
|
|
if hotspotSupported {
|
|
m.state.HotspotAvailable = backendState.HotspotAvailable
|
|
m.state.HotspotConfigured = backendState.HotspotConfigured
|
|
m.state.HotspotEnabled = backendState.HotspotEnabled
|
|
m.state.HotspotActivating = backendState.HotspotActivating
|
|
m.state.HotspotSecured = backendState.HotspotSecured
|
|
m.state.HotspotSSID = backendState.HotspotSSID
|
|
m.state.HotspotDevice = backendState.HotspotDevice
|
|
m.state.HotspotBand = backendState.HotspotBand
|
|
m.state.HotspotLastError = backendState.HotspotLastError
|
|
} else {
|
|
m.state.HotspotAvailable = false
|
|
m.state.HotspotConfigured = false
|
|
m.state.HotspotEnabled = false
|
|
m.state.HotspotActivating = false
|
|
m.state.HotspotSecured = false
|
|
m.state.HotspotSSID = ""
|
|
m.state.HotspotDevice = ""
|
|
m.state.HotspotBand = ""
|
|
m.state.HotspotLastError = ""
|
|
}
|
|
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.state.VPNError = backendState.VPNError
|
|
m.state.VPNErrorUuid = backendState.VPNErrorUuid
|
|
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.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...)
|
|
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 old.HotspotSupported != new.HotspotSupported {
|
|
return true
|
|
}
|
|
if old.HotspotAvailable != new.HotspotAvailable {
|
|
return true
|
|
}
|
|
if old.HotspotConfigured != new.HotspotConfigured {
|
|
return true
|
|
}
|
|
if old.HotspotEnabled != new.HotspotEnabled {
|
|
return true
|
|
}
|
|
if old.HotspotActivating != new.HotspotActivating {
|
|
return true
|
|
}
|
|
if old.HotspotSecured != new.HotspotSecured {
|
|
return true
|
|
}
|
|
if old.HotspotLastError != new.HotspotLastError {
|
|
return true
|
|
}
|
|
if old.HotspotSSID != new.HotspotSSID {
|
|
return true
|
|
}
|
|
if old.HotspotDevice != new.HotspotDevice {
|
|
return true
|
|
}
|
|
if old.HotspotBand != new.HotspotBand {
|
|
return true
|
|
}
|
|
if old.WiFiSignal != new.WiFiSignal && signalChangeSignificant(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 old.VPNError != new.VPNError || old.VPNErrorUuid != new.VPNErrorUuid {
|
|
return true
|
|
}
|
|
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
|
|
}
|
|
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.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
|
|
}
|
|
}
|
|
|
|
// Per-device signal strength is intentionally not compared: it churns
|
|
// constantly and the primary device's signal is already debounced above.
|
|
for i := range old.WiFiDevices {
|
|
oldDev := &old.WiFiDevices[i]
|
|
newDev := &new.WiFiDevices[i]
|
|
if oldDev.Name != newDev.Name {
|
|
return true
|
|
}
|
|
if oldDev.State != newDev.State {
|
|
return true
|
|
}
|
|
if oldDev.Connected != newDev.Connected {
|
|
return true
|
|
}
|
|
if oldDev.APCapable != newDev.APCapable {
|
|
return true
|
|
}
|
|
if oldDev.SSID != newDev.SSID {
|
|
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) GetWiFiQRContent(ssid string) (string, error) {
|
|
return m.backend.GetWiFiQRCodeContent(ssid)
|
|
}
|
|
|
|
func (m *Manager) GetNetworkQRCode(ssid string) ([2]string, error) {
|
|
content, err := m.backend.GetWiFiQRCodeContent(ssid)
|
|
if err != nil {
|
|
return [2]string{}, err
|
|
}
|
|
|
|
qrc, err := qrcode.New(content)
|
|
if err != nil {
|
|
return [2]string{}, fmt.Errorf("failed to create QR code for `%s`: %w", ssid, err)
|
|
}
|
|
|
|
pathThemed, pathNormal := qrCodePaths(ssid)
|
|
|
|
wThemed, err := standard.New(
|
|
pathThemed,
|
|
standard.WithBuiltinImageEncoder(standard.PNG_FORMAT),
|
|
standard.WithBgTransparent(),
|
|
standard.WithFgColorRGBHex("#ffffff"),
|
|
)
|
|
if err != nil {
|
|
return [2]string{}, fmt.Errorf("failed to create QR code writer: %w", err)
|
|
}
|
|
if err := qrc.Save(wThemed); err != nil {
|
|
return [2]string{}, fmt.Errorf("failed to save QR code for `%s`: %w", ssid, err)
|
|
}
|
|
|
|
wNormal, err := standard.New(pathNormal, standard.WithBuiltinImageEncoder(standard.PNG_FORMAT))
|
|
if err != nil {
|
|
return [2]string{}, fmt.Errorf("failed to create QR code writer: %w", err)
|
|
}
|
|
if err := qrc.Save(wNormal); err != nil {
|
|
return [2]string{}, fmt.Errorf("failed to save QR code for `%s`: %w", ssid, err)
|
|
}
|
|
|
|
return [2]string{pathThemed, pathNormal}, nil
|
|
}
|
|
|
|
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) ConfigureHotspot(req HotspotRequest) error {
|
|
backend, ok := m.hotspotBackend()
|
|
if !ok {
|
|
return ErrHotspotNotSupported
|
|
}
|
|
|
|
if err := backend.ConfigureHotspot(req); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := m.syncStateFromBackend(); err != nil {
|
|
return err
|
|
}
|
|
m.notifySubscribers()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *Manager) StartHotspot() error {
|
|
backend, ok := m.hotspotBackend()
|
|
if !ok {
|
|
return ErrHotspotNotSupported
|
|
}
|
|
|
|
if err := backend.StartHotspot(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := m.syncStateFromBackend(); err != nil {
|
|
return err
|
|
}
|
|
m.notifySubscribers()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *Manager) StopHotspot() error {
|
|
backend, ok := m.hotspotBackend()
|
|
if !ok {
|
|
return ErrHotspotNotSupported
|
|
}
|
|
|
|
if err := backend.StopHotspot(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := m.syncStateFromBackend(); err != nil {
|
|
return err
|
|
}
|
|
m.notifySubscribers()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *Manager) GetHotspotSecrets() (string, error) {
|
|
backend, ok := m.hotspotBackend()
|
|
if !ok {
|
|
return "", ErrHotspotNotSupported
|
|
}
|
|
|
|
return backend.GetHotspotSecrets()
|
|
}
|
|
|
|
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)
|
|
}
|