diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c814ffb..2894cf2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,6 @@ on: branches: - main - dev - - 'feature/*' pull_request: jobs: @@ -21,3 +20,20 @@ jobs: go-version: 1.26.x - name: Test code run: go test ./... + + lint: + name: Run golangci-lint + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v7 + - name: Setup Go + uses: actions/setup-go@v6 + with: + go-version: 1.26.x + - name: Check formatting + run: go tool gofumpt -l -extra . + - name: lint + uses: golangci/golangci-lint-action@v9 + with: + version: v2.12 diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..e0948f0 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,79 @@ +version: "2" + +run: + # Set to 0 to disable + # Default: 0 + timeout: 5m + go: "1.26" + +issues: + # Maximum issues count per one linter. + # Set to 0 to disable. + # Default: 3 + max-same-issues: 50 + +linters: + enable: + - copyloopvar + - errorlint + - goconst + - importas + - intrange + - makezero + - misspell + - prealloc + - revive + - tparallel + - unparam + - unused + - wastedassign + + settings: + copyloopvar: + check-alias: true + + goconst: + find-duplicates: true + + misspell: + locale: UK + ignore-rules: + - Color + mode: default + + revive: + rules: + - name: comment-spacings + - name: error-naming + - name: error-return + - name: indent-error-flow + - name: package-comments + - name: range + - name: unhandled-error + - name: var-declaration + + unused: + exported-fields-are-used: false + parameters-are-used: false + local-variables-are-used: false + + exclusions: + presets: + - comments + - std-error-handling + - common-false-positives + - legacy + +formatters: + enable: + - gofumpt + - goimports + + settings: + gofumpt: + module-path: github.com/thecentinol/tuwi + extra-rules: true + + goimports: + local-prefixes: + - github.com/thecentinol/tuwi diff --git a/Makefile b/Makefile index 18d4794..2573e3a 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,8 @@ PREFIX ?= /usr BINDIR := $(PREFIX)/bin .PHONY: install-deps build install install-local uninstall \ - run-dev run-debug run unit-test fuzz test clean help + run-dev run-debug run unit-test fuzz test lint format-check \ + format-write clean help install-deps: @go mod download @@ -34,10 +35,19 @@ unit-test: @go test ./... -short fuzz: - go test -fuzz=FuzzDetermineSecurityType -fuzztime=30s -v ./internal/networkmanager/... + @go test -fuzz=FuzzDetermineSecurityType -fuzztime=30s -v ./internal/networkmanager/... test: unit-test +lint: + @golangci-lint run + +format-check: + @gofumpt -l -extra . + +format-write: + @gofumpt -l -extra -w . + clean: @go clean @rm -rf ./dist @@ -52,9 +62,12 @@ help: @echo " Local development:" @echo " make run-dev — Run from source code" @echo " make run-debug — Run with debug" - @echo " make test — Run full test suite" @echo " make unit-test — Run short unit tests" @echo " make fuzz — Run fuzz tests" + @echo " make test — Run full test suite" + @echo " make lint — Run linter" + @echo " make format-check — Check the codebases formatting" + @echo " make format-write — Fix formatting issues" @echo "" @echo " Builds:" @echo " make build — Compile the binary into repo root" diff --git a/go.mod b/go.mod index e3e5360..b78e7dd 100644 --- a/go.mod +++ b/go.mod @@ -24,6 +24,11 @@ require ( github.com/muesli/cancelreader v0.2.2 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + golang.org/x/mod v0.35.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.45.0 // indirect + golang.org/x/tools v0.44.0 // indirect + mvdan.cc/gofumpt v0.10.0 // indirect ) + +tool mvdan.cc/gofumpt diff --git a/go.sum b/go.sum index 4367abb..d8479b6 100644 --- a/go.sum +++ b/go.sum @@ -40,7 +40,13 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= +mvdan.cc/gofumpt v0.10.0 h1:yGGpRS2pBN2OQIi7b21IXknJna7faPkFaVfHLrN6Euo= +mvdan.cc/gofumpt v0.10.0/go.mod h1:sU2ElXHzOEmvoPqfutYG7uunlueR4K2T1JFml40SzP4= diff --git a/internal/dbus/client.go b/internal/dbus/client.go index a743385..8d3cbf7 100644 --- a/internal/dbus/client.go +++ b/internal/dbus/client.go @@ -2,6 +2,7 @@ package dbus import ( "fmt" + "github.com/godbus/dbus/v5" ) diff --git a/internal/events/error.go b/internal/events/error.go index c24c201..a3f015f 100644 --- a/internal/events/error.go +++ b/internal/events/error.go @@ -2,8 +2,10 @@ package events import tea "charm.land/bubbletea/v2" -type ShowErrorMsg struct{ Err error } -type DismissErrorMsg struct{} +type ( + ShowErrorMsg struct{ Err error } + DismissErrorMsg struct{} +) func ShowError(err error) tea.Cmd { return func() tea.Msg { diff --git a/internal/keybindings/keybindings.go b/internal/keybindings/keybindings.go index 3eabbf0..9108249 100644 --- a/internal/keybindings/keybindings.go +++ b/internal/keybindings/keybindings.go @@ -4,12 +4,27 @@ import ( "charm.land/bubbles/v2/key" ) +const ( + // ViewNames's + wifiSavedView = "wifi-saved" + wifiAvailableView = "wifi-available" + errModalView = "error-modal" + passwordView = "password-modal" + + // Groups + globalGroup = "global" + wifiGroup = "wifi" + tableGroup = "table" + errModalGroup = "error" + passwordGroup = "password" +) + func DefaultKeybindings() Keybindings { return Keybindings{ // Global FocusedWifiSaved: Binding{ ViewName: "", - Group: "global", + Group: globalGroup, Keys: []string{"1"}, HelpKey: "", HelpDescription: "", @@ -17,7 +32,7 @@ func DefaultKeybindings() Keybindings { }, FocusedWifiAvailable: Binding{ ViewName: "", - Group: "global", + Group: globalGroup, Keys: []string{"2"}, HelpKey: "", HelpDescription: "", @@ -25,7 +40,7 @@ func DefaultKeybindings() Keybindings { }, Quit: Binding{ ViewName: "", - Group: "global", + Group: globalGroup, Keys: []string{"q", "ctrl+c"}, HelpKey: "q", HelpDescription: "quit", @@ -34,48 +49,48 @@ func DefaultKeybindings() Keybindings { // WiFi WifiConnectSaved: Binding{ - ViewName: "wifi-saved", - Group: "wifi", + ViewName: wifiSavedView, + Group: wifiGroup, Keys: []string{"enter"}, HelpKey: "Enter", HelpDescription: "connect", Description: "Connect to a saved wifi connection", }, WifiDisconnect: Binding{ - ViewName: "wifi-saved", - Group: "wifi", + ViewName: wifiSavedView, + Group: wifiGroup, Keys: []string{"d"}, HelpKey: "d", HelpDescription: "diconnect", Description: "Disconnect from current wifi network", }, WifiForget: Binding{ - ViewName: "wifi-saved", - Group: "wifi", + ViewName: wifiSavedView, + Group: wifiGroup, Keys: []string{"f"}, HelpKey: "f", HelpDescription: "forget", Description: "Remove the selected wifi connection", }, WifiAutoConnect: Binding{ - ViewName: "wifi-saved", - Group: "wifi", + ViewName: wifiSavedView, + Group: wifiGroup, Keys: []string{"a"}, HelpKey: "a", HelpDescription: "toggle auto-connect", Description: "Toggle auto-connect state for the selected wifi connection", }, WifiScan: Binding{ - ViewName: "wifi-available", - Group: "wifi", + ViewName: wifiAvailableView, + Group: wifiGroup, Keys: []string{"s"}, HelpKey: "s", HelpDescription: "Scan", Description: "Scan for nearby wifi networks", }, WifiConnectAvailable: Binding{ - ViewName: "wifi-available", - Group: "wifi", + ViewName: wifiAvailableView, + Group: wifiGroup, Keys: []string{"enter"}, HelpKey: "enter", HelpDescription: "connect", @@ -85,28 +100,28 @@ func DefaultKeybindings() Keybindings { // table navigation // ViewName is left nil because multiple models will use a table. LineUp: Binding{ - Group: "table", + Group: tableGroup, Keys: []string{"k", "up"}, HelpKey: "↑", HelpDescription: "move up", Description: "Move up by one line", }, LineDown: Binding{ - Group: "table", + Group: tableGroup, Keys: []string{"j", "down"}, HelpKey: "", HelpDescription: "move down", Description: "Move down by one line", }, GotoTop: Binding{ - Group: "table", + Group: tableGroup, Keys: []string{"t"}, HelpKey: "t", HelpDescription: "move to top", Description: "Go to the top of the table", }, GotoBottom: Binding{ - Group: "table", + Group: tableGroup, Keys: []string{"b"}, HelpKey: "b", HelpDescription: "move to bottom", @@ -115,8 +130,8 @@ func DefaultKeybindings() Keybindings { // Error modal ErrorDismiss: Binding{ - ViewName: "error-modal", - Group: "error", + ViewName: errModalView, + Group: errModalGroup, Keys: []string{"esc", "enter"}, HelpKey: "esc", HelpDescription: "dismiss", @@ -125,24 +140,24 @@ func DefaultKeybindings() Keybindings { // Password modal PasswordVisibility: Binding{ - ViewName: "password-modal", - Group: "password", + ViewName: passwordView, + Group: passwordGroup, Keys: []string{"ctrl+t"}, HelpKey: "ctrl+t", HelpDescription: "hide/show password", Description: "Toggle the password inputs hidden state", }, PasswordSubmit: Binding{ - ViewName: "password-modal", - Group: "password", + ViewName: passwordView, + Group: passwordGroup, Keys: []string{"enter"}, HelpKey: "enter", HelpDescription: "submit", Description: "Submit password", }, PasswordCancel: Binding{ - ViewName: "password-modal", - Group: "password", + ViewName: passwordView, + Group: passwordGroup, Keys: []string{"esc"}, HelpKey: "esc", HelpDescription: "cancel", diff --git a/internal/networkmanager/active_connection.go b/internal/networkmanager/active_connection.go index 8d89412..a21ea24 100644 --- a/internal/networkmanager/active_connection.go +++ b/internal/networkmanager/active_connection.go @@ -2,6 +2,7 @@ package networkmanager import ( "fmt" + godbus "github.com/godbus/dbus/v5" "github.com/thecentinol/tuwi/internal/dbus" diff --git a/internal/networkmanager/connection_manager.go b/internal/networkmanager/connection_manager.go index cc38708..0778357 100644 --- a/internal/networkmanager/connection_manager.go +++ b/internal/networkmanager/connection_manager.go @@ -4,6 +4,7 @@ import ( "fmt" godbus "github.com/godbus/dbus/v5" + "github.com/thecentinol/tuwi/internal/dbus" ) @@ -23,18 +24,18 @@ func GetDevices(c *dbus.Client) ([]godbus.ObjectPath, error) { func baseSettings(network AccessPoint) map[string]map[string]godbus.Variant { hidden := len(network.SSID) == 0 settings := map[string]map[string]godbus.Variant{ - "connection": { + SettingsConnection: { "id": godbus.MakeVariant(string(network.SSID)), "type": godbus.MakeVariant("802-11-wireless"), }, - "802-11-wireless": { + SettingsWireless: { "ssid": godbus.MakeVariant(network.SSID), "hidden": godbus.MakeVariant(hidden), }, - "ipv4": { + SettingsIpv4: { "method": godbus.MakeVariant("auto"), }, - "ipv6": { + SettingsIpv6: { "method": godbus.MakeVariant("auto"), }, } @@ -60,8 +61,8 @@ func AddAndActivateConnection( ) error { settings := baseSettings(network) if securityType != "open" && securityType != "unknown" { - settings["802-11-wireless"]["security"] = godbus.MakeVariant("802-11-wireless-security") - settings["802-11-wireless-security"] = securitySettings(securityType, password) + settings[SettingsWireless]["security"] = godbus.MakeVariant(SettingsSecurity) + settings[SettingsSecurity] = securitySettings(securityType, password) } options := map[string]godbus.Variant{} diff --git a/internal/networkmanager/constants.go b/internal/networkmanager/constants.go index 4f95fc5..0aebb4c 100644 --- a/internal/networkmanager/constants.go +++ b/internal/networkmanager/constants.go @@ -1,5 +1,15 @@ package networkmanager +const ( + // the settings of a saved connection profile (type of map[string]map[string]godbus.Variant) + + SettingsConnection = "connection" // the connection block + SettingsWireless = "802-11-wireless" // the 802-11-wirless block + SettingsSecurity = "802-11-wireless-security" // the 802-11-wirless-security block + SettingsIpv4 = "ipv4" // the IPv4 block + SettingsIpv6 = "ipv6" // the IPv6 block +) + // Base Service + Object const ( BaseServiceName = "org.freedesktop.NetworkManager" diff --git a/internal/networkmanager/device.go b/internal/networkmanager/device.go index d7e55e2..fe45118 100644 --- a/internal/networkmanager/device.go +++ b/internal/networkmanager/device.go @@ -4,6 +4,7 @@ import ( "fmt" godbus "github.com/godbus/dbus/v5" + "github.com/thecentinol/tuwi/internal/dbus" ) diff --git a/internal/networkmanager/device_wireless.go b/internal/networkmanager/device_wireless.go index 58b92ac..830b8e9 100644 --- a/internal/networkmanager/device_wireless.go +++ b/internal/networkmanager/device_wireless.go @@ -1,14 +1,16 @@ package networkmanager import ( + "errors" "fmt" - godbus "github.com/godbus/dbus/v5" "time" + godbus "github.com/godbus/dbus/v5" + "github.com/thecentinol/tuwi/internal/dbus" ) -func RequestScan(c *dbus.Client, devicePath godbus.ObjectPath) error { +func RequestScan(c *dbus.Client, devicePath godbus.ObjectPath) (err error) { device := c.Conn.Object( BaseServiceName, devicePath, @@ -22,18 +24,25 @@ func RequestScan(c *dbus.Client, devicePath godbus.ObjectPath) error { ch := make(chan *godbus.Signal, 1) c.Conn.Signal(ch) // register the channel to receive all signal msgs - c.Conn.AddMatchSignal( + errAdd := c.Conn.AddMatchSignal( godbus.WithMatchInterface("org.freedesktop.DBus.Properties"), godbus.WithMatchMember("PropertiesChanged"), godbus.WithMatchObjectPath(devicePath), ) + if errAdd != nil { + return fmt.Errorf("RequestScan AddMatchSignal: %w", err) + } + defer func() { - c.Conn.RemoveMatchSignal( + errRm := c.Conn.RemoveMatchSignal( godbus.WithMatchInterface("org.freedesktop.DBus.Properties"), godbus.WithMatchMember("PropertiesChanged"), godbus.WithMatchObjectPath(devicePath), ) + if errRm != nil { + err = errors.Join(err, fmt.Errorf("RequestScan RemoveMatchSignal: %w", err)) + } c.Conn.RemoveSignal(ch) // deregister the channel }() @@ -154,7 +163,7 @@ func ActivateConnection( connectionPath godbus.ObjectPath, devicePath godbus.ObjectPath, apPath godbus.ObjectPath, -) (godbus.ObjectPath, error) { +) (_ godbus.ObjectPath, err error) { obj := c.Conn.Object(BaseServiceName, BaseObjPath) call := obj.Call( CmActivateConnection, @@ -179,18 +188,24 @@ func ActivateConnection( // https://networkmanager.dev/docs/libnm/latest/libnm-nm-dbus-interface.html#NMActiveConnectionState // for changes to the state of the connection so we can // act on the signal received. - c.Conn.AddMatchSignal( + errAdd := c.Conn.AddMatchSignal( godbus.WithMatchInterface("org.freedesktop.DBus.Properties"), godbus.WithMatchMember("PropertiesChanged"), godbus.WithMatchObjectPath(activeConnection), ) + if errAdd != nil { + return "", fmt.Errorf("ActivateConnection AddMatchSignal: %w", errAdd) + } defer func() { - c.Conn.RemoveMatchSignal( + errRm := c.Conn.RemoveMatchSignal( godbus.WithMatchInterface("org.freedesktop.DBus.Properties"), godbus.WithMatchMember("PropertiesChanged"), godbus.WithMatchObjectPath(activeConnection), ) + if errRm != nil { + err = errors.Join(err, fmt.Errorf("ActivateConnection RemoveMatchSignal: %w", err)) + } c.Conn.RemoveSignal(ch) }() @@ -252,7 +267,7 @@ func DeactivateConnection(c *dbus.Client, activeConnections []godbus.ObjectPath) return fmt.Errorf("DeactivateConnection: unexpected type for State property") } - if connType == "802-11-wireless" && connState == 2 { + if connType == SettingsWireless && connState == 2 { activeConnection = activePath break } diff --git a/internal/networkmanager/settings_connection.go b/internal/networkmanager/settings_connection.go index 5235ee8..414b075 100644 --- a/internal/networkmanager/settings_connection.go +++ b/internal/networkmanager/settings_connection.go @@ -2,6 +2,7 @@ package networkmanager import ( "fmt" + godbus "github.com/godbus/dbus/v5" "github.com/thecentinol/tuwi/internal/dbus" @@ -22,8 +23,8 @@ func GetSettings(c *dbus.Client, path godbus.ObjectPath) (*SavedConnection, erro // as per NM docs this is the response type from GetSettings: var settings map[string]map[string]godbus.Variant var uuid string - var autoConnect bool = true - var ssid string = "unknown" + autoConnect := true + ssid := "unknown" var bssids []string var mode string var band string @@ -45,7 +46,7 @@ func GetSettings(c *dbus.Client, path godbus.ObjectPath) (*SavedConnection, erro // filter out non-WiFi connections connBlock, ok := settings["connection"] - if !ok || connBlock["type"].Value().(string) != "802-11-wireless" { + if !ok || connBlock["type"].Value().(string) != SettingsWireless { return nil, nil } @@ -96,7 +97,7 @@ func GetSettings(c *dbus.Client, path godbus.ObjectPath) (*SavedConnection, erro macAddress = rawMac.Value().(string) } - securityBlock, ok := settings["802-11-wireless-security"] + securityBlock, ok := settings[SettingsSecurity] if ok { rawKeyMgmt, ok := securityBlock["key-mgmt"] if ok { diff --git a/internal/networkmanager/signals.go b/internal/networkmanager/signals.go index 29386fc..aec8143 100644 --- a/internal/networkmanager/signals.go +++ b/internal/networkmanager/signals.go @@ -2,6 +2,7 @@ package networkmanager import ( "fmt" + godbus "github.com/godbus/dbus/v5" "github.com/thecentinol/tuwi/internal/dbus" @@ -10,7 +11,7 @@ import ( func handleAccessPointAdded(c *dbus.Client, signal *godbus.Signal) (*AccessPoint, error) { body := signal.Body if len(body) != 1 { - return nil, fmt.Errorf("handleAccessPointAdded: signal body missing!") + return nil, fmt.Errorf("handleAccessPointAdded: signal body missing") } apPath := body[0].(godbus.ObjectPath) ap, err := GetAccessPointProperties(c, signal.Path, apPath) @@ -24,12 +25,12 @@ func handleAccessPointAdded(c *dbus.Client, signal *godbus.Signal) (*AccessPoint func handleAccessPointRemoved(signal *godbus.Signal) (godbus.ObjectPath, error) { body := signal.Body if len(body) != 1 { - return "", fmt.Errorf("handleAccessPointRemoved: signal body missing!") + return "", fmt.Errorf("handleAccessPointRemoved: signal body missing") } apPath, ok := body[0].(godbus.ObjectPath) if !ok { - return "", fmt.Errorf("handleAccessPointRemoved: failed to get AP path!") + return "", fmt.Errorf("handleAccessPointRemoved: failed to get AP path") } return apPath, nil } @@ -37,12 +38,12 @@ func handleAccessPointRemoved(signal *godbus.Signal) (godbus.ObjectPath, error) func handleNewConnection(signal *godbus.Signal) (godbus.ObjectPath, error) { body := signal.Body if len(body) != 1 { - return "", fmt.Errorf("handleNewConnection: signal body missing!") + return "", fmt.Errorf("handleNewConnection: signal body missing") } conPath, ok := body[0].(godbus.ObjectPath) if !ok { - return "", fmt.Errorf("handleNewConnection: failed to get connection path!") + return "", fmt.Errorf("handleNewConnection: failed to get connection path") } return conPath, nil @@ -51,12 +52,12 @@ func handleNewConnection(signal *godbus.Signal) (godbus.ObjectPath, error) { func handleConnectionRemoved(signal *godbus.Signal) (godbus.ObjectPath, error) { body := signal.Body if len(body) != 1 { - return "", fmt.Errorf("handleConnectionRemoved: signal body missing!") + return "", fmt.Errorf("handleConnectionRemoved: signal body missing") } conPath, ok := body[0].(godbus.ObjectPath) if !ok { - return "", fmt.Errorf("handleConnectionRemoved: failed to get connection path!") + return "", fmt.Errorf("handleConnectionRemoved: failed to get connection path") } return conPath, nil @@ -66,7 +67,7 @@ func handleConnectionRemoved(signal *godbus.Signal) (godbus.ObjectPath, error) { func handleActiveConnectionState(signal *godbus.Signal) (uint32, uint32, error) { body := signal.Body if len(body) != 2 { - return 0, 0, fmt.Errorf("handleActiveConnectionState: signal body missing!") + return 0, 0, fmt.Errorf("handleActiveConnectionState: signal body missing") } state, ok := body[0].(uint32) @@ -83,39 +84,58 @@ func handleActiveConnectionState(signal *godbus.Signal) (uint32, uint32, error) } type SignalCallbacks struct { - OnAccessPointAdded func(ap AccessPoint) - OnAccessPointRemoved func(apPath godbus.ObjectPath) + OnAccessPointAdded func(AccessPoint) + OnAccessPointRemoved func(godbus.ObjectPath) - OnNewConnection func(path godbus.ObjectPath) - OnConnectionRemoved func(path godbus.ObjectPath) - OnActiveConnStateChanged func(state, reason uint32) + OnNewConnection func(godbus.ObjectPath) + OnConnectionRemoved func(godbus.ObjectPath) + OnActiveConnStateChanged func(uint32, uint32) - OnError func(err error) + OnError func(error) } func ListenForSignals(c *dbus.Client, cb SignalCallbacks) { ch := make(chan *godbus.Signal, 10) c.Conn.Signal(ch) - c.Conn.AddMatchSignal( + errApAdded := c.Conn.AddMatchSignal( godbus.WithMatchInterface(baseWirelessServiceName), godbus.WithMatchMember("AccessPointAdded"), ) - c.Conn.AddMatchSignal( + if errApAdded != nil { + cb.OnError(fmt.Errorf("ListenForSignals: AccessPointAdded: %w", errApAdded)) + } + + errApRm := c.Conn.AddMatchSignal( godbus.WithMatchInterface(baseWirelessServiceName), godbus.WithMatchMember("AccessPointRemoved"), ) - c.Conn.AddMatchSignal( + if errApRm != nil { + cb.OnError(fmt.Errorf("ListenForSignals: AccessPointRemoved: %w", errApRm)) + } + + errNewConn := c.Conn.AddMatchSignal( godbus.WithMatchInterface(BaseServiceName+".Settings"), godbus.WithMatchMember("NewConnection"), ) - c.Conn.AddMatchSignal( + if errNewConn != nil { + cb.OnError(fmt.Errorf("ListenForSignals: NewConnection: %w", errNewConn)) + } + + errConnRm := c.Conn.AddMatchSignal( godbus.WithMatchInterface(BaseServiceName+".Settings"), godbus.WithMatchMember("ConnectionRemoved"), ) - c.Conn.AddMatchSignal( + if errConnRm != nil { + cb.OnError(fmt.Errorf("ListenForSignals: ConnectionRemoved: %w", errConnRm)) + } + + errStateChanged := c.Conn.AddMatchSignal( godbus.WithMatchInterface(baseActiveConnServiceName), godbus.WithMatchMember("StateChanged"), ) + if errStateChanged != nil { + cb.OnError(fmt.Errorf("ListenForSignals: StateChanged: %w", errStateChanged)) + } for sig := range ch { switch sig.Name { diff --git a/internal/ui/bluetooth/model.go b/internal/ui/bluetooth/model.go index 920d70d..9da3211 100644 --- a/internal/ui/bluetooth/model.go +++ b/internal/ui/bluetooth/model.go @@ -5,17 +5,17 @@ import ( ) type Model struct { - width int - height int - focused bool - // devices []BluetoothDevice // TODO: implement this - cursor int + width int //nolint:unused + height int //nolint:unused + focused bool //nolint:unused + cursor int //nolint:unused } func (m Model) Init() tea.Cmd { return nil } +//nolint:unused func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return nil, nil } diff --git a/internal/ui/components/border_content.go b/internal/ui/components/border_content.go index 3d6635a..05fb3b6 100644 --- a/internal/ui/components/border_content.go +++ b/internal/ui/components/border_content.go @@ -14,7 +14,7 @@ type BorderContent struct { func NewBorderContent( border, content color.Color, - width, height int, + height int, ) BorderContent { return BorderContent{ BorderStyle: lipgloss.NewStyle(). @@ -32,12 +32,12 @@ func NewBorderContent( func (b BorderContent) Render(label, content string, width int) string { var ( - border lipgloss.Border = b.BorderStyle.GetBorderStyle() - topBorderStyler func(strs ...string) string = lipgloss.NewStyle().Foreground(b.BorderStyle.GetBorderTopForeground()).Render - topLeft string = topBorderStyler(border.TopLeft) - topRight string = topBorderStyler(border.TopRight) + border = b.BorderStyle.GetBorderStyle() + topBorderStyler = lipgloss.NewStyle().Foreground(b.BorderStyle.GetBorderTopForeground()).Render + topLeft = topBorderStyler(border.TopLeft) + topRight = topBorderStyler(border.TopRight) - renderedLabel string = b.ContentStyle.Render(label) + renderedLabel = b.ContentStyle.Render(label) ) leftWidth := lipgloss.Width(topLeft) diff --git a/internal/ui/components/error_modal.go b/internal/ui/components/error_modal.go index fd7b52a..c7f8718 100644 --- a/internal/ui/components/error_modal.go +++ b/internal/ui/components/error_modal.go @@ -1,11 +1,11 @@ package components import ( - "charm.land/bubbles/v2/help" + "strings" + "charm.land/bubbles/v2/key" tea "charm.land/bubbletea/v2" "charm.land/lipgloss/v2" - "strings" "github.com/thecentinol/tuwi/internal/events" "github.com/thecentinol/tuwi/internal/keybindings" @@ -23,7 +23,6 @@ type ErrorModel struct { Y int keys keybindings.Keybindings - help help.Model Focused bool } @@ -59,8 +58,8 @@ func (e ErrorModel) View() tea.View { Width(e.Width - 2). Render(e.Text) - conatiner := NewBorderContent(e.theme.Error.Border, e.theme.Error.BorderContent, e.Width, e.Height-2) - return tea.NewView(conatiner.Render("Error:", wrapped, e.Width)) + container := NewBorderContent(e.theme.Error.Border, e.theme.Error.BorderContent, e.Height-2) + return tea.NewView(container.Render("Error:", wrapped, e.Width)) } // This is used in root Model's Update(). diff --git a/internal/ui/components/password_modal.go b/internal/ui/components/password_modal.go index 10d589f..eaddb82 100644 --- a/internal/ui/components/password_modal.go +++ b/internal/ui/components/password_modal.go @@ -1,7 +1,6 @@ package components import ( - "charm.land/bubbles/v2/help" "charm.land/bubbles/v2/key" "charm.land/bubbles/v2/textinput" tea "charm.land/bubbletea/v2" @@ -13,11 +12,9 @@ import ( ) type PasswordModel struct { - theme *theme.Theme - Input textinput.Model - password string - keys keybindings.Keybindings - help help.Model + theme *theme.Theme + Input textinput.Model + keys keybindings.Keybindings Content string @@ -89,7 +86,7 @@ func (p PasswordModel) Update(msg tea.Msg) (PasswordModel, tea.Cmd) { } func (p PasswordModel) View() tea.View { - container := NewBorderContent(p.theme.Password.Border, p.theme.Password.BorderContent, p.Width, 0) + container := NewBorderContent(p.theme.Password.Border, p.theme.Password.BorderContent, 0) input := lipgloss.JoinHorizontal( lipgloss.Left, diff --git a/internal/ui/root/model.go b/internal/ui/root/model.go index 4102f0a..97626bf 100644 --- a/internal/ui/root/model.go +++ b/internal/ui/root/model.go @@ -257,7 +257,7 @@ func (m Model) HelpView() string { if m.focus == FocusSaved { help = append(help, m.wifiSaved.HelpView()...) } - if m.focus == FocusAvailable && m.showPasswordModal == false { + if m.focus == FocusAvailable && !m.showPasswordModal { help = append(help, m.wifiAvailable.HelpView()...) } diff --git a/internal/ui/theme/rose_pine.go b/internal/ui/theme/rose_pine.go index 132f2c0..134c6b7 100644 --- a/internal/ui/theme/rose_pine.go +++ b/internal/ui/theme/rose_pine.go @@ -2,6 +2,7 @@ package theme import "charm.land/lipgloss/v2" +//nolint:goconst // Rose Pine Default and Moon intentionally share certain hex codes const ( base = "#191724" surface = "#1f1d2e" diff --git a/internal/ui/wifi/available_model.go b/internal/ui/wifi/available_model.go index 1247bf8..dfa6288 100644 --- a/internal/ui/wifi/available_model.go +++ b/internal/ui/wifi/available_model.go @@ -1,11 +1,11 @@ package wifi import ( - "charm.land/bubbles/v2/help" + "fmt" + "charm.land/bubbles/v2/key" "charm.land/bubbles/v2/table" tea "charm.land/bubbletea/v2" - "fmt" "github.com/thecentinol/tuwi/internal/dbus" "github.com/thecentinol/tuwi/internal/events" @@ -31,7 +31,6 @@ type AvailableModel struct { height int keys keybindings.Keybindings - help help.Model IsFocused bool } @@ -131,7 +130,6 @@ func (a AvailableModel) Update(msg tea.Msg) (AvailableModel, tea.Cmd) { msg.Network, msg.Password, ) - if err != nil { return a, events.ShowError(err) } @@ -176,7 +174,7 @@ func (a AvailableModel) View() tea.View { borderContent = a.theme.BorderContentFocused } - title := comp.NewBorderContent(borderColor, borderContent, a.width, a.height) + title := comp.NewBorderContent(borderColor, borderContent, a.height) return tea.NewView(title.Render("[2]-Available networks", a.Table.View().Content, a.width)) } diff --git a/internal/ui/wifi/rows.go b/internal/ui/wifi/rows.go index d7a4465..b4406d2 100644 --- a/internal/ui/wifi/rows.go +++ b/internal/ui/wifi/rows.go @@ -1,9 +1,10 @@ package wifi import ( - "charm.land/bubbles/v2/table" "strconv" + "charm.land/bubbles/v2/table" + nm "github.com/thecentinol/tuwi/internal/networkmanager" wifidomain "github.com/thecentinol/tuwi/internal/wifi" ) diff --git a/internal/ui/wifi/saved_model.go b/internal/ui/wifi/saved_model.go index 4c8be79..44660b0 100644 --- a/internal/ui/wifi/saved_model.go +++ b/internal/ui/wifi/saved_model.go @@ -1,11 +1,11 @@ package wifi import ( - "charm.land/bubbles/v2/help" + "fmt" + "charm.land/bubbles/v2/key" "charm.land/bubbles/v2/table" tea "charm.land/bubbletea/v2" - "fmt" "github.com/thecentinol/tuwi/internal/dbus" "github.com/thecentinol/tuwi/internal/events" @@ -27,7 +27,6 @@ type SavedModel struct { height int keys keybindings.Keybindings - help help.Model IsFocused bool } @@ -70,7 +69,7 @@ func (s SavedModel) Init() tea.Cmd { } func (s SavedModel) Update(msg tea.Msg) (SavedModel, tea.Cmd) { - var cmds []tea.Cmd + cmds := make([]tea.Cmd, 0, 1) var cmd tea.Cmd switch msg := msg.(type) { @@ -152,7 +151,7 @@ func (s SavedModel) View() tea.View { borderContent = s.theme.BorderContentFocused } - title := comp.NewBorderContent(borderColor, borderContent, s.width, s.height) + title := comp.NewBorderContent(borderColor, borderContent, s.height) return tea.NewView(title.Render("[1]-Saved connections", s.Table.View().Content, s.width)) } diff --git a/internal/wifi/connection.go b/internal/wifi/connection.go index 8dc2aa6..e006d7a 100644 --- a/internal/wifi/connection.go +++ b/internal/wifi/connection.go @@ -2,6 +2,7 @@ package wifi import ( "fmt" + godbus "github.com/godbus/dbus/v5" "github.com/thecentinol/tuwi/internal/dbus" @@ -19,7 +20,6 @@ func ConnectSaved(client *dbus.Client, nc nm.NearbyConnection) (godbus.ObjectPat nc.AP.DevicePath, nc.AP.APPath, ) - if err != nil { return "", fmt.Errorf("ConnectSaved: %w", err) } @@ -41,7 +41,6 @@ func ConnectSecured( password, securityType, ) - if err != nil { return fmt.Errorf("ConnectSecured: %w", err) } @@ -62,7 +61,6 @@ func ConnectOpen( "", securityType, ) - if err != nil { return fmt.Errorf("ConnectOpen: %w", err) } diff --git a/internal/wifi/saved.go b/internal/wifi/saved.go index 5a376fb..8beda9f 100644 --- a/internal/wifi/saved.go +++ b/internal/wifi/saved.go @@ -2,6 +2,7 @@ package wifi import ( "fmt" + godbus "github.com/godbus/dbus/v5" "github.com/thecentinol/tuwi/internal/dbus" @@ -97,7 +98,7 @@ func DisplaySavedConnections( saved []nm.SavedConnection, available []nm.AccessPoint, ) []nm.NearbyConnection { - var nearbyConnections []nm.NearbyConnection + nearbyConnections := make([]nm.NearbyConnection, 0, len(saved)) visibleAPsByBSSID := make(map[string]*nm.AccessPoint) visibleAPsByConnection := make(map[string]*nm.AccessPoint) @@ -125,7 +126,6 @@ func DisplaySavedConnections( break } } - } if sc.SSID != "" { if ap, found := visibleAPsByConnection[sc.SSID]; found { diff --git a/internal/wifi/state.go b/internal/wifi/state.go index a813fde..a328293 100644 --- a/internal/wifi/state.go +++ b/internal/wifi/state.go @@ -1,9 +1,10 @@ package wifi import ( - godbus "github.com/godbus/dbus/v5" "slices" + godbus "github.com/godbus/dbus/v5" + nm "github.com/thecentinol/tuwi/internal/networkmanager" ) diff --git a/internal/wifi/utils.go b/internal/wifi/utils.go index 872871e..a2f9791 100644 --- a/internal/wifi/utils.go +++ b/internal/wifi/utils.go @@ -6,6 +6,22 @@ import ( nm "github.com/thecentinol/tuwi/internal/networkmanager" ) +const ( + secTypeOpen = "open" + secTypeUnknown = "unknown" + + ssidHidden = "" + + freq24GHz = "2.4GHz" + freq5GHz = "5GHz" + freq6GHz = "6GHz" + freqUnknown = "unknown" //nolint:goconst + + statusConnected = "Connected" + statusNearby = "Nearby" + statusUnreachable = "Unreachable" +) + func DetermineSecurityType(flags, wpaFlags, rsnFlags uint32) string { var secType string switch { @@ -30,31 +46,31 @@ func DetermineSecurityType(flags, wpaFlags, rsnFlags uint32) string { case flags&nm.NmApFlagsPrivacy != 0: secType = "wep" case flags&nm.NmApFlagsPrivacy == 0 && wpaFlags == 0 && rsnFlags == 0: - secType = "open" + secType = secTypeOpen default: - secType = "unknown" + secType = secTypeUnknown } return secType } func FormatSSID(ssid []byte) string { if len(ssid) == 0 { - return "" + return ssidHidden } return string(ssid) } func DetermineFrequency(hertz uint32) string { if hertz >= 2401 && hertz <= 2495 { - return "2.4GHz" + return freq24GHz } if hertz >= 5150 && hertz <= 5895 { - return "5GHz" + return freq5GHz } if hertz >= 5945 && hertz <= 7125 { - return "6GHz" + return freq6GHz } - return "unknown" + return freqUnknown } func DetermineChannel(freq uint32) uint32 { @@ -62,16 +78,12 @@ func DetermineChannel(freq uint32) uint32 { if freq >= 2412 && freq <= 2472 { channel = (freq - 2407) / 5 - } else if freq == 2484 { channel = 14 - } else if freq >= 5160 && freq <= 5855 { channel = (freq - 5000) / 5 - } else if freq == 5935 { channel = 2 - } else if freq >= 5955 && freq <= 7115 { channel = (freq - 5950) / 5 } @@ -93,15 +105,15 @@ func FormatStrength(strength uint8) string { } // used for saved WiFi networks -func DetermineStatus(isNearby bool, isActive bool) string { +func DetermineStatus(isNearby, isActive bool) string { switch { case isActive: - return "Connected" + return statusConnected case isNearby: - return "Nearby" + return statusNearby default: - return "Unreachable" + return statusUnreachable } } diff --git a/internal/wifi/utils_test.go b/internal/wifi/utils_test.go index 5a8870b..6a52aa9 100644 --- a/internal/wifi/utils_test.go +++ b/internal/wifi/utils_test.go @@ -25,8 +25,8 @@ func TestDetermineSecurityType(t *testing.T) { {name: "OWE Network", flags: 0, wpa: 0, rsn: nm.NmSecMgmtOwe, want: "OWE"}, {name: "OWE Transition Network", flags: 0, wpa: 0, rsn: nm.NmSecMgmtOweTm, want: "OWE-TM"}, {name: "WPA2_WPA3 Network", flags: 0, wpa: nm.NmSecMgmtPsk, rsn: nm.NmSecMgmtSae, want: "WPA2/WPA3"}, - {name: "Open Network with WPS", flags: nm.NmApFlagsWps, wpa: 0, rsn: 0, want: "open"}, - {name: "Open Network no flags", flags: nm.NmApFlagsNone, wpa: 0, rsn: 0, want: "open"}, + {name: "Open Network with WPS", flags: nm.NmApFlagsWps, wpa: 0, rsn: 0, want: secTypeOpen}, + {name: "Open Network no flags", flags: nm.NmApFlagsNone, wpa: 0, rsn: 0, want: secTypeOpen}, } for _, tc := range tests { @@ -40,6 +40,7 @@ func TestDetermineSecurityType(t *testing.T) { }) } } + func FuzzDetermineSecurityType(f *testing.F) { f.Add(uint32(0), uint32(0), uint32(0)) f.Add(uint32(0), uint32(0), uint32(nm.NmSecMgmtSae)) @@ -70,8 +71,8 @@ func TestFormatSSID(t *testing.T) { bytes []byte wantStr string }{ - {name: "Hidden Network - nil", bytes: nil, wantStr: ""}, - {name: "Hidden Network - empty", bytes: []byte{}, wantStr: ""}, + {name: "Hidden Network - nil", bytes: nil, wantStr: ssidHidden}, + {name: "Hidden Network - empty", bytes: []byte{}, wantStr: ssidHidden}, {name: "Visible Network", bytes: []byte("HomeWifi"), wantStr: "HomeWifi"}, {name: "Visible 1 character ssid", bytes: []byte{65}, wantStr: "A"}, @@ -106,22 +107,22 @@ func TestDetermineFrequency(t *testing.T) { hertz uint32 want string }{ - {name: "Unknown Freq with 0 MHz", hertz: uint32(0), want: "unknown"}, - {name: "Uknown Freq with 9999 MHz", hertz: uint32(9999), want: "unknown"}, - {name: "Uknown Freq with 3999 MHz", hertz: uint32(9999), want: "unknown"}, - {name: "Uknown Freq with 7126 MHz", hertz: uint32(9999), want: "unknown"}, - - {name: "2.4GHz Freq lower bound", hertz: uint32(2401), want: "2.4GHz"}, - {name: "2.4GHz Freq mid range", hertz: uint32(2442), want: "2.4GHz"}, - {name: "2.4GHz Freq higher bound", hertz: uint32(2495), want: "2.4GHz"}, - - {name: "5GHz Freq lower bound", hertz: uint32(5150), want: "5GHz"}, - {name: "5GHz Freq mid range", hertz: uint32(5500), want: "5GHz"}, - {name: "5GHz Freq higher bound", hertz: uint32(5850), want: "5GHz"}, - - {name: "6GHz Freq lower bound", hertz: uint32(5945), want: "6GHz"}, - {name: "6GHz Freq mid range", hertz: uint32(6525), want: "6GHz"}, - {name: "6GHz Freq higher bound", hertz: uint32(7125), want: "6GHz"}, + {name: "Unknown Freq with 0 MHz", hertz: uint32(0), want: secTypeUnknown}, + {name: "Uknown Freq with 9999 MHz", hertz: uint32(9999), want: secTypeUnknown}, + {name: "Uknown Freq with 3999 MHz", hertz: uint32(9999), want: secTypeUnknown}, + {name: "Uknown Freq with 7126 MHz", hertz: uint32(9999), want: secTypeUnknown}, + + {name: "2.4GHz Freq lower bound", hertz: uint32(2401), want: freq24GHz}, + {name: "2.4GHz Freq mid range", hertz: uint32(2442), want: freq24GHz}, + {name: "2.4GHz Freq higher bound", hertz: uint32(2495), want: freq24GHz}, + + {name: "5GHz Freq lower bound", hertz: uint32(5150), want: freq5GHz}, + {name: "5GHz Freq mid range", hertz: uint32(5500), want: freq5GHz}, + {name: "5GHz Freq higher bound", hertz: uint32(5850), want: freq5GHz}, + + {name: "6GHz Freq lower bound", hertz: uint32(5945), want: freq6GHz}, + {name: "6GHz Freq mid range", hertz: uint32(6525), want: freq6GHz}, + {name: "6GHz Freq higher bound", hertz: uint32(7125), want: freq6GHz}, } for _, tc := range tests { @@ -146,7 +147,7 @@ func FuzzDetermineFrequency(f *testing.F) { f.Fuzz(func(t *testing.T, hertz uint32) { result := DetermineFrequency(hertz) - if result != "2.4GHz" && result != "5GHz" && result != "6GHz" && result != "unknown" { + if result != freq24GHz && result != freq5GHz && result != freq6GHz && result != freqUnknown { t.Errorf("invalid string: %s", result) } }) @@ -205,10 +206,10 @@ func TestDetermineStatus(t *testing.T) { activeBool bool want string }{ - {"Nearby and Active", true, true, "Connected"}, - {"Active only", false, true, "Connected"}, - {"Nearby only", true, false, "Nearby"}, - {"Not nearby or active", false, false, "Unreachable"}, + {"Nearby and Active", true, true, statusConnected}, + {"Active only", false, true, statusConnected}, + {"Nearby only", true, false, statusNearby}, + {"Not nearby or active", false, false, statusUnreachable}, } for _, tc := range tests { diff --git a/main.go b/main.go index 8d97e4c..4006a93 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,10 @@ package main import ( + "log" + tea "charm.land/bubbletea/v2" godbus "github.com/godbus/dbus/v5" - "log" "github.com/thecentinol/tuwi/internal/dbus" "github.com/thecentinol/tuwi/internal/events"