diff --git a/src/.golangci.yml b/src/.golangci.yml index 4be42a19..3f3bcc0a 100644 --- a/src/.golangci.yml +++ b/src/.golangci.yml @@ -5,7 +5,6 @@ linters: disable-all: true enable: - bodyclose - - deadcode - depguard - dupl - errcheck @@ -28,12 +27,10 @@ linters: - rowserrcheck - exportloopref - staticcheck - - structcheck - typecheck - unconvert - unparam - unused - - varcheck - whitespace - lll linters-settings: diff --git a/src/color/colors_unix.go b/src/color/colors_unix.go index 1e40dd5d..56d5728a 100644 --- a/src/color/colors_unix.go +++ b/src/color/colors_unix.go @@ -3,12 +3,11 @@ package color import ( - "errors" "oh-my-posh/environment" ) func GetAccentColor(env environment.Environment) (*RGB, error) { - return nil, errors.New("not implemented") + return nil, &environment.NotImplemented{} } func (d *DefaultColors) SetAccentColor(env environment.Environment, defaultColor string) { diff --git a/src/engine/segment.go b/src/engine/segment.go index aaf01b12..37b00b1a 100644 --- a/src/engine/segment.go +++ b/src/engine/segment.go @@ -96,6 +96,8 @@ const ( CMAKE SegmentType = "cmake" // CMD writes the output of a shell command CMD SegmentType = "command" + // CONNECTION writes a connection's information + CONNECTION SegmentType = "connection" // CRYSTAL writes the active crystal version CRYSTAL SegmentType = "crystal" // DART writes the active dart version @@ -192,8 +194,6 @@ const ( UI5TOOLING SegmentType = "ui5tooling" // WAKATIME writes tracked time spend in dev editors WAKATIME SegmentType = "wakatime" - // WIFI writes details about the current WIFI connection - WIFI SegmentType = "wifi" // WINREG queries the Windows registry. WINREG SegmentType = "winreg" // WITHINGS queries the Withings API. @@ -277,6 +277,7 @@ func (segment *Segment) mapSegmentWithWriter(env environment.Environment) error CF: &segments.Cf{}, CFTARGET: &segments.CfTarget{}, CMD: &segments.Cmd{}, + CONNECTION: &segments.Connection{}, CRYSTAL: &segments.Crystal{}, CMAKE: &segments.Cmake{}, DART: &segments.Dart{}, @@ -326,7 +327,6 @@ func (segment *Segment) mapSegmentWithWriter(env environment.Environment) error TIME: &segments.Time{}, UI5TOOLING: &segments.UI5Tooling{}, WAKATIME: &segments.Wakatime{}, - WIFI: &segments.Wifi{}, WINREG: &segments.WindowsRegistry{}, WITHINGS: &segments.Withings{}, YTM: &segments.Ytm{}, diff --git a/src/environment/shell.go b/src/environment/shell.go index b1d2763f..ac7d43bb 100644 --- a/src/environment/shell.go +++ b/src/environment/shell.go @@ -98,20 +98,27 @@ type WindowsRegistryValue struct { String string } -type WifiType string +type NotImplemented struct{} -type WifiInfo struct { - SSID string - Interface string - RadioType WifiType - PhysType WifiType - Authentication WifiType - Cipher WifiType - Channel int - ReceiveRate int - TransmitRate int - Signal int - Error string +func (n *NotImplemented) Error() string { + return "not implemented" +} + +type ConnectionType string + +const ( + ETHERNET ConnectionType = "ethernet" + WIFI ConnectionType = "wifi" + CELLULAR ConnectionType = "cellular" + BLUETOOTH ConnectionType = "bluetooth" +) + +type Connection struct { + Name string + Type ConnectionType + TransmitRate uint64 + ReceiveRate uint64 + SSID string // Wi-Fi only } type TemplateCache struct { @@ -182,7 +189,7 @@ type Environment interface { InWSLSharedDrive() bool ConvertToLinuxPath(path string) string ConvertToWindowsPath(path string) string - WifiNetwork() (*WifiInfo, error) + Connection(connectionType ConnectionType) (*Connection, error) TemplateCache() *TemplateCache LoadTemplateCache() Log(logType LogType, funcName, message string) @@ -222,6 +229,7 @@ type ShellEnvironment struct { fileCache *fileCache tmplCache *TemplateCache logBuilder strings.Builder + networks []*Connection } func (env *ShellEnvironment) Init() { diff --git a/src/environment/shell_unix.go b/src/environment/shell_unix.go index d0836907..7a7a68bc 100644 --- a/src/environment/shell_unix.go +++ b/src/environment/shell_unix.go @@ -23,7 +23,7 @@ func (env *ShellEnvironment) Home() string { } func (env *ShellEnvironment) QueryWindowTitles(processName, windowTitleRegex string) (string, error) { - return "", errors.New("not implemented") + return "", &NotImplemented{} } func (env *ShellEnvironment) IsWsl() bool { @@ -94,7 +94,7 @@ func (env *ShellEnvironment) CachePath() string { } func (env *ShellEnvironment) WindowsRegistryKeyValue(path string) (*WindowsRegistryValue, error) { - return nil, errors.New("not implemented") + return nil, &NotImplemented{} } func (env *ShellEnvironment) InWSLSharedDrive() bool { @@ -120,10 +120,6 @@ func (env *ShellEnvironment) ConvertToLinuxPath(path string) string { return path } -func (env *ShellEnvironment) WifiNetwork() (*WifiInfo, error) { - return nil, errors.New("not implemented") -} - func (env *ShellEnvironment) LookWinAppPath(file string) (string, error) { return "", errors.New("not relevant") } @@ -160,3 +156,11 @@ func (env *ShellEnvironment) DirIsWritable(path string) bool { return true } + +func (env *ShellEnvironment) Connection(connectionType ConnectionType) (*Connection, error) { + // added to disable the linting error, we can implement this later + if len(env.networks) == 0 { + return nil, &NotImplemented{} + } + return nil, &NotImplemented{} +} diff --git a/src/environment/shell_windows.go b/src/environment/shell_windows.go index 6dc746b0..9afa5ff0 100644 --- a/src/environment/shell_windows.go +++ b/src/environment/shell_windows.go @@ -8,8 +8,6 @@ import ( "strings" "syscall" "time" - "unicode/utf16" - "unsafe" "github.com/Azure/go-ansiterm/winterm" "golang.org/x/sys/windows" @@ -235,248 +233,6 @@ func (env *ShellEnvironment) ConvertToLinuxPath(path string) string { return path } -var ( - hapi = syscall.NewLazyDLL("wlanapi.dll") - hWlanOpenHandle = hapi.NewProc("WlanOpenHandle") - hWlanCloseHandle = hapi.NewProc("WlanCloseHandle") - hWlanEnumInterfaces = hapi.NewProc("WlanEnumInterfaces") - hWlanQueryInterface = hapi.NewProc("WlanQueryInterface") -) - -const ( - FHSS WifiType = "FHSS" - DSSS WifiType = "DSSS" - IR WifiType = "IR" - A WifiType = "802.11a" - HRDSSS WifiType = "HRDSSS" - G WifiType = "802.11g" - N WifiType = "802.11n" - AC WifiType = "802.11ac" - - Infrastructure WifiType = "Infrastructure" - Independent WifiType = "Independent" - Any WifiType = "Any" - - OpenSystem WifiType = "802.11 Open System" - SharedKey WifiType = "802.11 Shared Key" - WPA WifiType = "WPA" - WPAPSK WifiType = "WPA PSK" - WPANone WifiType = "WPA NONE" - WPA2 WifiType = "WPA2" - WPA2PSK WifiType = "WPA2 PSK" - Disabled WifiType = "disabled" - - None WifiType = "None" - WEP40 WifiType = "WEP40" - TKIP WifiType = "TKIP" - CCMP WifiType = "CCMP" - WEP104 WifiType = "WEP104" - WEP WifiType = "WEP" -) - -func (env *ShellEnvironment) WifiNetwork() (*WifiInfo, error) { - env.Trace(time.Now(), "WifiNetwork") - // Open handle - var pdwNegotiatedVersion uint32 - var phClientHandle uint32 - e, _, err := hWlanOpenHandle.Call(uintptr(uint32(2)), uintptr(unsafe.Pointer(nil)), uintptr(unsafe.Pointer(&pdwNegotiatedVersion)), uintptr(unsafe.Pointer(&phClientHandle))) - if e != 0 { - return nil, err - } - - // defer closing handle - defer func() { - _, _, _ = hWlanCloseHandle.Call(uintptr(phClientHandle), uintptr(unsafe.Pointer(nil))) - }() - - // list interfaces - var interfaceList *WLAN_INTERFACE_INFO_LIST - e, _, err = hWlanEnumInterfaces.Call(uintptr(phClientHandle), uintptr(unsafe.Pointer(nil)), uintptr(unsafe.Pointer(&interfaceList))) - if e != 0 { - return nil, err - } - - // use first interface that is connected - numberOfInterfaces := int(interfaceList.dwNumberOfItems) - infoSize := unsafe.Sizeof(interfaceList.InterfaceInfo[0]) - for i := 0; i < numberOfInterfaces; i++ { - network := (*WLAN_INTERFACE_INFO)(unsafe.Pointer(uintptr(unsafe.Pointer(&interfaceList.InterfaceInfo[0])) + uintptr(i)*infoSize)) - if network.isState != 1 { - continue - } - return env.parseNetworkInterface(network, phClientHandle) - } - return nil, errors.New("Not connected") -} - -func (env *ShellEnvironment) parseNetworkInterface(network *WLAN_INTERFACE_INFO, clientHandle uint32) (*WifiInfo, error) { - info := WifiInfo{} - info.Interface = strings.TrimRight(string(utf16.Decode(network.strInterfaceDescription[:])), "\x00") - - // Query wifi connection state - var dataSize uint16 - var wlanAttr *WLAN_CONNECTION_ATTRIBUTES - e, _, err := hWlanQueryInterface.Call(uintptr(clientHandle), - uintptr(unsafe.Pointer(&network.InterfaceGuid)), - uintptr(7), // wlan_intf_opcode_current_connection - uintptr(unsafe.Pointer(nil)), - uintptr(unsafe.Pointer(&dataSize)), - uintptr(unsafe.Pointer(&wlanAttr)), - uintptr(unsafe.Pointer(nil))) - if e != 0 { - env.Log(Error, "parseNetworkInterface", "wlan_intf_opcode_current_connection error") - return &info, err - } - - // SSID - ssid := wlanAttr.wlanAssociationAttributes.dot11Ssid - if ssid.uSSIDLength > 0 { - info.SSID = string(ssid.ucSSID[0:ssid.uSSIDLength]) - } - - // see https://docs.microsoft.com/en-us/windows/win32/nativewifi/dot11-phy-type - switch wlanAttr.wlanAssociationAttributes.dot11PhyType { - case 1: - info.PhysType = FHSS - case 2: - info.PhysType = DSSS - case 3: - info.PhysType = IR - case 4: - info.PhysType = A - case 5: - info.PhysType = HRDSSS - case 6: - info.PhysType = G - case 7: - info.PhysType = N - case 8: - info.PhysType = AC - default: - info.PhysType = UNKNOWN - } - - // see https://docs.microsoft.com/en-us/windows/win32/nativewifi/dot11-bss-type - switch wlanAttr.wlanAssociationAttributes.dot11BssType { - case 1: - info.RadioType = Infrastructure - case 2: - info.RadioType = Independent - default: - info.RadioType = Any - } - - info.Signal = int(wlanAttr.wlanAssociationAttributes.wlanSignalQuality) - info.TransmitRate = int(wlanAttr.wlanAssociationAttributes.ulTxRate) / 1024 - info.ReceiveRate = int(wlanAttr.wlanAssociationAttributes.ulRxRate) / 1024 - - // Query wifi channel - dataSize = 0 - var channel *uint32 - e, _, err = hWlanQueryInterface.Call(uintptr(clientHandle), - uintptr(unsafe.Pointer(&network.InterfaceGuid)), - uintptr(8), // wlan_intf_opcode_channel_number - uintptr(unsafe.Pointer(nil)), - uintptr(unsafe.Pointer(&dataSize)), - uintptr(unsafe.Pointer(&channel)), - uintptr(unsafe.Pointer(nil))) - if e != 0 { - env.Log(Error, "parseNetworkInterface", "wlan_intf_opcode_channel_number error") - return &info, err - } - info.Channel = int(*channel) - - if wlanAttr.wlanSecurityAttributes.bSecurityEnabled <= 0 { - info.Authentication = Disabled - return &info, nil - } - - // see https://docs.microsoft.com/en-us/windows/win32/nativewifi/dot11-auth-algorithm - switch wlanAttr.wlanSecurityAttributes.dot11AuthAlgorithm { - case 1: - info.Authentication = OpenSystem - case 2: - info.Authentication = SharedKey - case 3: - info.Authentication = WPA - case 4: - info.Authentication = WPAPSK - case 5: - info.Authentication = WPANone - case 6: - info.Authentication = WPA2 - case 7: - info.Authentication = WPA2PSK - default: - info.Authentication = UNKNOWN - } - - // see https://docs.microsoft.com/en-us/windows/win32/nativewifi/dot11-cipher-algorithm - switch wlanAttr.wlanSecurityAttributes.dot11CipherAlgorithm { - case 0: - info.Cipher = None - case 0x1: - info.Cipher = WEP40 - case 0x2: - info.Cipher = TKIP - case 0x4: - info.Cipher = CCMP - case 0x5: - info.Cipher = WEP104 - case 0x100: - info.Cipher = WPA - case 0x101: - info.Cipher = WEP - default: - info.Cipher = UNKNOWN - } - - return &info, nil -} - -type WLAN_INTERFACE_INFO_LIST struct { //nolint: revive - dwNumberOfItems uint32 - dwIndex uint32 //nolint: unused - InterfaceInfo [1]WLAN_INTERFACE_INFO -} - -type WLAN_INTERFACE_INFO struct { //nolint: revive - InterfaceGuid syscall.GUID //nolint: revive - strInterfaceDescription [256]uint16 - isState uint32 -} - -type WLAN_CONNECTION_ATTRIBUTES struct { //nolint: revive - isState uint32 //nolint: unused - wlanConnectionMode uint32 //nolint: unused - strProfileName [256]uint16 //nolint: unused - wlanAssociationAttributes WLAN_ASSOCIATION_ATTRIBUTES - wlanSecurityAttributes WLAN_SECURITY_ATTRIBUTES -} - -type WLAN_ASSOCIATION_ATTRIBUTES struct { //nolint: revive - dot11Ssid DOT11_SSID - dot11BssType uint32 - dot11Bssid [6]uint8 //nolint: unused - dot11PhyType uint32 - uDot11PhyIndex uint32 //nolint: unused - wlanSignalQuality uint32 - ulRxRate uint32 - ulTxRate uint32 -} - -type WLAN_SECURITY_ATTRIBUTES struct { //nolint: revive - bSecurityEnabled uint32 - bOneXEnabled uint32 //nolint: unused - dot11AuthAlgorithm uint32 - dot11CipherAlgorithm uint32 -} - -type DOT11_SSID struct { //nolint: revive - uSSIDLength uint32 - ucSSID [32]uint8 -} - func (env *ShellEnvironment) DirIsWritable(path string) bool { defer env.Trace(time.Now(), "DirIsWritable") info, err := os.Stat(path) @@ -498,3 +254,20 @@ func (env *ShellEnvironment) DirIsWritable(path string) bool { return true } + +func (env *ShellEnvironment) Connection(connectionType ConnectionType) (*Connection, error) { + if env.networks == nil { + networks := env.getConnections() + if len(networks) == 0 { + return nil, errors.New("No connections found") + } + env.networks = networks + } + for _, network := range env.networks { + if network.Type == connectionType { + return network, nil + } + } + env.Log(Error, "network", fmt.Sprintf("Network type '%s' not found", connectionType)) + return nil, &NotImplemented{} +} diff --git a/src/environment/windows_win32.go b/src/environment/windows_win32.go index 41acfbdf..be0c7b5c 100644 --- a/src/environment/windows_win32.go +++ b/src/environment/windows_win32.go @@ -24,6 +24,9 @@ var ( psapi = syscall.NewLazyDLL("psapi.dll") getModuleBaseNameA = psapi.NewProc("GetModuleBaseNameA") + + iphlpapi = syscall.NewLazyDLL("iphlpapi.dll") + hGetIfTable2 = iphlpapi.NewProc("GetIfTable2") ) // enumWindows call enumWindows from user32 and returns all active windows @@ -189,7 +192,242 @@ func readWinAppLink(path string) (string, error) { rb := (*GenericDataBuffer)(unsafe.Pointer(&rdb.DUMMYUNIONNAME)) appExecLink := (*AppExecLinkReparseBuffer)(unsafe.Pointer(&rb.DataBuffer)) if appExecLink.Version != 3 { - return " ", errors.New("unknown AppExecLink version") + return "", errors.New("unknown AppExecLink version") } return appExecLink.Path() } + +// networks + +func (env *ShellEnvironment) getConnections() []*Connection { + var pIFTable2 *MIN_IF_TABLE2 + _, _, _ = hGetIfTable2.Call(uintptr(unsafe.Pointer(&pIFTable2))) + + SSIDs, _ := env.getAllWifiSSID() + networks := make([]*Connection, 0) + + for i := 0; i < int(pIFTable2.NumEntries); i++ { + networkInterface := pIFTable2.Table[i] + alias := strings.TrimRight(syscall.UTF16ToString(networkInterface.Alias[:]), "\x00") + description := strings.TrimRight(syscall.UTF16ToString(networkInterface.Description[:]), "\x00") + + if networkInterface.OperStatus != 1 || // not connected or functional + !networkInterface.InterfaceAndOperStatusFlags.HardwareInterface || // rule out software interfaces + strings.HasPrefix(alias, "Local Area Connection") || // not relevant + strings.Index(alias, "-") >= 3 { // rule out parts of Ethernet filter interfaces + // e.g. : "Ethernet-WFP Native MAC Layer LightWeight Filter-0000" + continue + } + + var connectionType ConnectionType + switch networkInterface.Type { + case 6: + connectionType = ETHERNET + case 71: + connectionType = WIFI + case 237, 234, 244: + connectionType = CELLULAR + } + + if networkInterface.PhysicalMediumType == 10 { + connectionType = BLUETOOTH + } + + // skip connections which aren't relevant + if len(connectionType) == 0 { + continue + } + + network := &Connection{ + Type: connectionType, + Name: description, // we want a relatable name, alias isn't that + TransmitRate: networkInterface.TransmitLinkSpeed, + ReceiveRate: networkInterface.ReceiveLinkSpeed, + } + + if SSID, OK := SSIDs[network.Name]; OK { + network.SSID = SSID + } + + networks = append(networks, network) + } + return networks +} + +type MIN_IF_TABLE2 struct { //nolint: revive + NumEntries uint64 + Table [256]MIB_IF_ROW2 +} + +const ( + IF_MAX_STRING_SIZE uint64 = 256 //nolint: revive + IF_MAX_PHYS_ADDRESS_LENGTH uint64 = 32 //nolint: revive +) + +type MIB_IF_ROW2 struct { //nolint: revive + InterfaceLuid uint64 + InterfaceIndex uint32 + InterfaceGUID windows.GUID + Alias [IF_MAX_STRING_SIZE + 1]uint16 + Description [IF_MAX_STRING_SIZE + 1]uint16 + PhysicalAddressLength uint32 + PhysicalAddress [IF_MAX_PHYS_ADDRESS_LENGTH]uint8 + PermanentPhysicalAddress [IF_MAX_PHYS_ADDRESS_LENGTH]uint8 + + Mtu uint32 + Type uint32 + TunnelType uint32 + MediaType uint32 + PhysicalMediumType uint32 + AccessType uint32 + DirectionType uint32 + + InterfaceAndOperStatusFlags struct { + HardwareInterface bool + FilterInterface bool + ConnectorPresent bool + NotAuthenticated bool + NotMediaConnected bool + Paused bool + LowPower bool + EndPointInterface bool + } + + OperStatus uint32 + AdminStatus uint32 + MediaConnectState uint32 + NetworkGUID windows.GUID + ConnectionType uint32 + + TransmitLinkSpeed uint64 + ReceiveLinkSpeed uint64 + + InOctets uint64 + InUcastPkts uint64 + InNUcastPkts uint64 + InDiscards uint64 + InErrors uint64 + InUnknownProtos uint64 + InUcastOctets uint64 + InMulticastOctets uint64 + InBroadcastOctets uint64 + OutOctets uint64 + OutUcastPkts uint64 + OutNUcastPkts uint64 + OutDiscards uint64 + OutErrors uint64 + OutUcastOctets uint64 + OutMulticastOctets uint64 + OutBroadcastOctets uint64 + OutQLen uint64 +} + +func (env *ShellEnvironment) getAllWifiSSID() (map[string]string, error) { + var pdwNegotiatedVersion uint32 + var phClientHandle uint32 + e, _, err := hWlanOpenHandle.Call(uintptr(uint32(2)), uintptr(unsafe.Pointer(nil)), uintptr(unsafe.Pointer(&pdwNegotiatedVersion)), uintptr(unsafe.Pointer(&phClientHandle))) + if e != 0 { + env.Log(Error, "getAllWifiSSID", err.Error()) + return nil, err + } + + // defer closing handle + defer func() { + _, _, _ = hWlanCloseHandle.Call(uintptr(phClientHandle), uintptr(unsafe.Pointer(nil))) + }() + + ssid := make(map[string]string) + // list interfaces + var interfaceList *WLAN_INTERFACE_INFO_LIST + e, _, err = hWlanEnumInterfaces.Call(uintptr(phClientHandle), uintptr(unsafe.Pointer(nil)), uintptr(unsafe.Pointer(&interfaceList))) + if e != 0 { + env.Log(Error, "getAllWifiSSID", err.Error()) + return nil, err + } + + // use first interface that is connected + numberOfInterfaces := int(interfaceList.dwNumberOfItems) + infoSize := unsafe.Sizeof(interfaceList.InterfaceInfo[0]) + for i := 0; i < numberOfInterfaces; i++ { + network := (*WLAN_INTERFACE_INFO)(unsafe.Pointer(uintptr(unsafe.Pointer(&interfaceList.InterfaceInfo[0])) + uintptr(i)*infoSize)) + if network.isState == 1 { + wifiInterface := strings.TrimRight(string(utf16.Decode(network.strInterfaceDescription[:])), "\x00") + ssid[wifiInterface] = env.getWiFiSSID(network, phClientHandle) + } + } + return ssid, nil +} + +var ( + wlanapi = syscall.NewLazyDLL("wlanapi.dll") + hWlanOpenHandle = wlanapi.NewProc("WlanOpenHandle") + hWlanCloseHandle = wlanapi.NewProc("WlanCloseHandle") + hWlanEnumInterfaces = wlanapi.NewProc("WlanEnumInterfaces") + hWlanQueryInterface = wlanapi.NewProc("WlanQueryInterface") +) + +func (env *ShellEnvironment) getWiFiSSID(network *WLAN_INTERFACE_INFO, clientHandle uint32) string { + // Query wifi connection state + var dataSize uint16 + var wlanAttr *WLAN_CONNECTION_ATTRIBUTES + e, _, _ := hWlanQueryInterface.Call(uintptr(clientHandle), + uintptr(unsafe.Pointer(&network.InterfaceGuid)), + uintptr(7), // wlan_intf_opcode_current_connection + uintptr(unsafe.Pointer(nil)), + uintptr(unsafe.Pointer(&dataSize)), + uintptr(unsafe.Pointer(&wlanAttr)), + uintptr(unsafe.Pointer(nil))) + if e != 0 { + env.Log(Error, "parseWlanInterface", "wlan_intf_opcode_current_connection error") + return "" + } + + ssid := wlanAttr.wlanAssociationAttributes.dot11Ssid + if ssid.uSSIDLength <= 0 { + return "" + } + return string(ssid.ucSSID[0:ssid.uSSIDLength]) +} + +type WLAN_INTERFACE_INFO_LIST struct { //nolint: revive + dwNumberOfItems uint32 + dwIndex uint32 //nolint: unused + InterfaceInfo [256]WLAN_INTERFACE_INFO +} + +type WLAN_INTERFACE_INFO struct { //nolint: revive + InterfaceGuid syscall.GUID //nolint: revive + strInterfaceDescription [256]uint16 + isState uint32 +} + +type WLAN_CONNECTION_ATTRIBUTES struct { //nolint: revive + isState uint32 //nolint: unused + wlanConnectionMode uint32 //nolint: unused + strProfileName [256]uint16 //nolint: unused + wlanAssociationAttributes WLAN_ASSOCIATION_ATTRIBUTES + wlanSecurityAttributes WLAN_SECURITY_ATTRIBUTES //nolint: unused +} + +type WLAN_ASSOCIATION_ATTRIBUTES struct { //nolint: revive + dot11Ssid DOT11_SSID + dot11BssType uint32 //nolint: unused + dot11Bssid [6]uint8 //nolint: unused + dot11PhyType uint32 //nolint: unused + uDot11PhyIndex uint32 //nolint: unused + wlanSignalQuality uint32 //nolint: unused + ulRxRate uint32 //nolint: unused + ulTxRate uint32 //nolint: unused +} + +type WLAN_SECURITY_ATTRIBUTES struct { //nolint: revive + bSecurityEnabled uint32 //nolint: unused + bOneXEnabled uint32 //nolint: unused + dot11AuthAlgorithm uint32 //nolint: unused + dot11CipherAlgorithm uint32 //nolint: unused +} + +type DOT11_SSID struct { //nolint: revive + uSSIDLength uint32 + ucSSID [32]uint8 +} diff --git a/src/go.mod b/src/go.mod index 34387476..6956a20c 100644 --- a/src/go.mod +++ b/src/go.mod @@ -38,7 +38,7 @@ require ( github.com/hashicorp/hcl/v2 v2.14.0 github.com/mattn/go-runewidth v0.0.13 github.com/spf13/cobra v1.5.0 - golang.org/x/mod v0.5.1 + golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 gopkg.in/yaml.v3 v3.0.1 ) @@ -60,7 +60,6 @@ require ( github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/tklauser/numcpus v0.5.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect ) require ( diff --git a/src/go.sum b/src/go.sum index 35653704..c38d49e6 100644 --- a/src/go.sum +++ b/src/go.sum @@ -222,8 +222,8 @@ golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 h1:Lj6HJGCSn5AjxRAH2+r35Mir4icalbqku+CLUtjnvXY= golang.org/x/image v0.0.0-20220902085622-e7cb96979f69/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY= -golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38= -golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= @@ -265,8 +265,6 @@ golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/src/mock/environment.go b/src/mock/environment.go index 344ca870..41f1cfe3 100644 --- a/src/mock/environment.go +++ b/src/mock/environment.go @@ -208,9 +208,9 @@ func (env *MockedEnvironment) ConvertToLinuxPath(path string) string { return args.String(0) } -func (env *MockedEnvironment) WifiNetwork() (*environment.WifiInfo, error) { - args := env.Called() - return args.Get(0).(*environment.WifiInfo), args.Error(1) +func (env *MockedEnvironment) Connection(connectionType environment.ConnectionType) (*environment.Connection, error) { + args := env.Called(connectionType) + return args.Get(0).(*environment.Connection), args.Error(1) } func (env *MockedEnvironment) TemplateCache() *environment.TemplateCache { diff --git a/src/properties/properties.go b/src/properties/properties.go index f79d6fe8..deb25f93 100644 --- a/src/properties/properties.go +++ b/src/properties/properties.go @@ -60,15 +60,7 @@ func (m Map) GetString(property Property, defaultValue string) string { if !found { return defaultValue } - return ParseString(val, defaultValue) -} - -func ParseString(value interface{}, defaultValue string) string { - stringValue, ok := value.(string) - if !ok { - return defaultValue - } - return stringValue + return fmt.Sprint(val) } func (m Map) GetColor(property Property, defaultValue string) string { @@ -76,7 +68,7 @@ func (m Map) GetColor(property Property, defaultValue string) string { if !found { return defaultValue } - colorString := ParseString(val, defaultValue) + colorString := fmt.Sprint(val) if color.IsAnsiColorName(colorString) { return colorString } diff --git a/src/properties/properties_test.go b/src/properties/properties_test.go index ced71751..409423c9 100644 --- a/src/properties/properties_test.go +++ b/src/properties/properties_test.go @@ -28,7 +28,7 @@ func TestGetStringNoEntry(t *testing.T) { func TestGetStringNoTextEntry(t *testing.T) { var properties = Map{Foo: true} value := properties.GetString(Foo, expected) - assert.Equal(t, expected, value) + assert.Equal(t, "true", value) } func TestGetHexColor(t *testing.T) { diff --git a/src/segments/connection.go b/src/segments/connection.go new file mode 100644 index 00000000..4891b1f3 --- /dev/null +++ b/src/segments/connection.go @@ -0,0 +1,41 @@ +package segments + +import ( + "oh-my-posh/environment" + "oh-my-posh/properties" + "strings" +) + +type Connection struct { + props properties.Properties + env environment.Environment + + environment.Connection +} + +const ( + Type properties.Property = "type" +) + +func (c *Connection) Template() string { + return " {{ if eq .Type \"wifi\"}}\uf1eb{{ else if eq .Type \"ethernet\"}}\uf6ff{{ end }} " +} + +func (c *Connection) Enabled() bool { + types := c.props.GetString(Type, "wifi|ethernet") + connectionTypes := strings.Split(types, "|") + for _, connectionType := range connectionTypes { + network, err := c.env.Connection(environment.ConnectionType(connectionType)) + if err != nil { + continue + } + c.Connection = *network + return true + } + return false +} + +func (c *Connection) Init(props properties.Properties, env environment.Environment) { + c.props = props + c.env = env +} diff --git a/src/segments/connection_test.go b/src/segments/connection_test.go new file mode 100644 index 00000000..e365fc5a --- /dev/null +++ b/src/segments/connection_test.go @@ -0,0 +1,105 @@ +package segments + +import ( + "fmt" + "oh-my-posh/environment" + "oh-my-posh/mock" + "oh-my-posh/properties" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestConnection(t *testing.T) { + type connectionResponse struct { + Connection *environment.Connection + Error error + } + cases := []struct { + Case string + ExpectedString string + ExpectedEnabled bool + ConnectionType string + Connections []*connectionResponse + }{ + { + Case: "WiFi only, enabled", + ExpectedString: "\uf1eb", + ExpectedEnabled: true, + ConnectionType: "wifi", + Connections: []*connectionResponse{ + { + Connection: &environment.Connection{ + Name: "WiFi", + Type: "wifi", + }, + }, + }, + }, + { + Case: "WiFi only, disabled", + ConnectionType: "wifi", + Connections: []*connectionResponse{ + { + Connection: &environment.Connection{ + Type: environment.WIFI, + }, + Error: fmt.Errorf("no connection"), + }, + }, + }, + { + Case: "WiFi and Ethernet, enabled", + ConnectionType: "wifi|ethernet", + ExpectedString: "\uf6ff", + ExpectedEnabled: true, + Connections: []*connectionResponse{ + { + Connection: &environment.Connection{ + Type: environment.WIFI, + }, + Error: fmt.Errorf("no connection"), + }, + { + Connection: &environment.Connection{ + Type: environment.ETHERNET, + }, + }, + }, + }, + { + Case: "WiFi and Ethernet, disabled", + ConnectionType: "wifi|ethernet", + Connections: []*connectionResponse{ + { + Connection: &environment.Connection{ + Type: environment.WIFI, + }, + Error: fmt.Errorf("no connection"), + }, + { + Connection: &environment.Connection{ + Type: environment.ETHERNET, + }, + Error: fmt.Errorf("no connection"), + }, + }, + }, + } + for _, tc := range cases { + env := &mock.MockedEnvironment{} + for _, con := range tc.Connections { + env.On("Connection", con.Connection.Type).Return(con.Connection, con.Error) + } + c := &Connection{ + env: env, + props: &properties.Map{ + Type: tc.ConnectionType, + }, + } + assert.Equal(t, tc.ExpectedEnabled, c.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case)) + if tc.ExpectedEnabled { + assert.Equal(t, tc.ExpectedString, renderTemplate(env, c.Template(), c), fmt.Sprintf("Failed in case: %s", tc.Case)) + } + } +} diff --git a/src/segments/spotify_windows_test.go b/src/segments/spotify_windows_test.go index 670b0f25..a100d766 100644 --- a/src/segments/spotify_windows_test.go +++ b/src/segments/spotify_windows_test.go @@ -3,7 +3,7 @@ package segments import ( - "errors" + "oh-my-posh/environment" "oh-my-posh/mock" "oh-my-posh/properties" "testing" @@ -40,7 +40,7 @@ func TestSpotifyWindowsNative(t *testing.T) { for _, tc := range cases { env := new(mock.MockedEnvironment) env.On("QueryWindowTitles", "spotify.exe", `^(Spotify.*)|(.*\s-\s.*)$`).Return(tc.Title, tc.Error) - env.On("QueryWindowTitles", "msedge.exe", `^(Spotify.*)`).Return("", errors.New("not implemented")) + env.On("QueryWindowTitles", "msedge.exe", `^(Spotify.*)`).Return("", &environment.NotImplemented{}) s := &Spotify{ env: env, props: properties.Map{}, @@ -74,7 +74,7 @@ func TestSpotifyWindowsPWA(t *testing.T) { } for _, tc := range cases { env := new(mock.MockedEnvironment) - env.On("QueryWindowTitles", "spotify.exe", "^(Spotify.*)|(.*\\s-\\s.*)$").Return("", errors.New("not implemented")) + env.On("QueryWindowTitles", "spotify.exe", "^(Spotify.*)|(.*\\s-\\s.*)$").Return("", &environment.NotImplemented{}) env.On("QueryWindowTitles", "msedge.exe", "^(Spotify.*)").Return(tc.Title, tc.Error) s := &Spotify{ env: env, diff --git a/src/segments/wifi.go b/src/segments/wifi.go deleted file mode 100644 index 284508d8..00000000 --- a/src/segments/wifi.go +++ /dev/null @@ -1,46 +0,0 @@ -package segments - -import ( - "oh-my-posh/environment" - "oh-my-posh/properties" -) - -type Wifi struct { - props properties.Properties - env environment.Environment - - Error string - - environment.WifiInfo -} - -const ( - defaultTemplate = " {{ if .Error }}{{ .Error }}{{ else }}\uFAA8 {{ .SSID }} {{ .Signal }}% {{ .ReceiveRate }}Mbps{{ end }} " -) - -func (w *Wifi) Template() string { - return defaultTemplate -} - -func (w *Wifi) Enabled() bool { - // This segment only supports Windows/WSL for now - if w.env.Platform() != environment.WINDOWS && !w.env.IsWsl() { - return false - } - wifiInfo, err := w.env.WifiNetwork() - displayError := w.props.GetBool(properties.DisplayError, false) - if err != nil && displayError { - w.Error = err.Error() - return true - } - if err != nil || wifiInfo == nil { - return false - } - w.WifiInfo = *wifiInfo - return true -} - -func (w *Wifi) Init(props properties.Properties, env environment.Environment) { - w.props = props - w.env = env -} diff --git a/src/segments/wifi_test.go b/src/segments/wifi_test.go deleted file mode 100644 index 0aeb353b..00000000 --- a/src/segments/wifi_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package segments - -import ( - "errors" - "oh-my-posh/environment" - "oh-my-posh/mock" - "oh-my-posh/properties" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestWiFiSegment(t *testing.T) { - cases := []struct { - Case string - ExpectedString string - ExpectedEnabled bool - Network *environment.WifiInfo - WifiError error - DisplayError bool - }{ - { - Case: "No error and nil network", - }, - { - Case: "Error and nil network", - WifiError: errors.New("oh noes"), - }, - { - Case: "Display error and nil network", - WifiError: errors.New("oh noes"), - ExpectedString: "oh noes", - DisplayError: true, - ExpectedEnabled: true, - }, - { - Case: "Display wifi state", - ExpectedString: "pretty fly for a wifi", - ExpectedEnabled: true, - Network: &environment.WifiInfo{ - SSID: "pretty fly for a wifi", - }, - }, - } - - for _, tc := range cases { - env := new(mock.MockedEnvironment) - env.On("Platform").Return(environment.WINDOWS) - env.On("IsWsl").Return(false) - env.On("WifiNetwork").Return(tc.Network, tc.WifiError) - - w := &Wifi{ - env: env, - props: properties.Map{ - properties.DisplayError: tc.DisplayError, - }, - } - - assert.Equal(t, tc.ExpectedEnabled, w.Enabled(), tc.Case) - if tc.Network != nil || tc.DisplayError { - assert.Equal(t, tc.ExpectedString, renderTemplate(env, "{{ if .Error }}{{ .Error }}{{ else }}{{ .SSID }}{{ end }}", w), tc.Case) - } - } -} diff --git a/themes/schema.json b/themes/schema.json index aad434bd..4ba053cf 100644 --- a/themes/schema.json +++ b/themes/schema.json @@ -225,6 +225,7 @@ "angular", "battery", "command", + "connection", "crystal", "cds", "cf", @@ -274,7 +275,6 @@ "terraform", "ui5tooling", "wakatime", - "wifi", "winreg", "withings", "ytm" @@ -575,6 +575,55 @@ } } }, + { + "if": { + "properties": { + "type": { + "const": "connection" + } + } + }, + "then": { + "title": "Connection Segment", + "description": "https://ohmyposh.dev/docs/segments/connection", + "properties": { + "properties": { + "properties": { + "type": { + "type": "string", + "title": "Connection type", + "description": "The connection type to display", + "enum": [ + "ethernet", + "wifi", + "cellular", + "bluetooth" + ], + "default": "wifi|ethernet" + }, + "unit": { + "type": "string", + "title": "Transfer speed unit", + "enum": [ + "none", + "b", + "bps", + "K", + "Kbps", + "M", + "Mbps", + "G", + "Gbps", + "T", + "Tbps" + ], + "default": "none" + } + } + } + } + } + }, { "if": { "properties": { @@ -2164,19 +2213,6 @@ } } }, - { - "if": { - "properties": { - "type": { - "const": "wifi" - } - } - }, - "then": { - "title": "WiFi Segment", - "description": "https://ohmyposh.dev/docs/segments/wifi" - } - }, { "if": { "properties": { diff --git a/website/docs/contributing/segment.mdx b/website/docs/contributing/segment.mdx index 3a82431f..eeb02928 100644 --- a/website/docs/contributing/segment.mdx +++ b/website/docs/contributing/segment.mdx @@ -85,7 +85,7 @@ go build -o $GOPATH/bin/oh-my-posh ## Add the documentation -Create a new `markdown` file underneath the [`docs/docs/segments`][docs] folder called `new.md`. +Create a new `markdown` file underneath the [`website/docs/segments`][docs] folder called `new.md`. Use the following template as a guide. ````markdown diff --git a/website/docs/segments/connection.mdx b/website/docs/segments/connection.mdx new file mode 100644 index 00000000..291a6a32 --- /dev/null +++ b/website/docs/segments/connection.mdx @@ -0,0 +1,51 @@ +--- +id: connection +title: Connection +sidebar_label: Connection +--- + +## Connection + +Show details about the currently connected network. + +:::info +Currently only supports Windows. Pull requests for Darwin and Linux support are welcome :) +::: + +## Sample Configuration + +```json +{ + "type": "connection", + "style": "powerline", + "background": "#8822ee", + "foreground": "#222222", + "powerline_symbol": "\uE0B0" +} +``` + +## Properties + +| Name | Type | Description | +| ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `type` | `string` | the type of connection to display. Can be a single value or multiple joined by \| . The first to resolve is shown (**default value** is wifi\|ethernet). Possible values: | + +## Template ([info][templates]) + +:::note default template + +```template +{{ if eq .Type \"wifi\"}}\uf1eb{{ else if eq .Type \"ethernet\"}}\uf6ff{{ end }} +``` + +::: + +### Properties + +| Name | Type | Description | +| ------- | -------- | ------------------------------------------------------- | +| `.Type` | `string` | the connection type type. Single values of `type` above | +| `.Name` | `string` | the name of the connection | +| `.SSID` | `string` | the SSID of the current wifi network | + +[templates]: /docs/configuration/templates diff --git a/website/docs/segments/golang.mdx b/website/docs/segments/golang.mdx index 94151836..a2e13e9c 100644 --- a/website/docs/segments/golang.mdx +++ b/website/docs/segments/golang.mdx @@ -23,14 +23,14 @@ Display the currently active golang version. ## Properties -| Name | Type | Description | -| ---------------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `home_enabled` | `boolean` | display the segment in the HOME folder or not - defaults to `false` | -| `fetch_version` | `boolean` | display the golang version - defaults to `true` | -| `missing_command_text` | `string` | text to display when the command is missing - defaults to empty | -| `display_mode` | `string` | | -| `version_url_template` | `string` | a go [text/template][go-text-template] [template][templates] that creates the URL of the version info / release notes | -| `parse_mod_file` | `boolean`: parse the go.mod file instead of calling `go version` | +| Name | Type | Description | +| ---------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `home_enabled` | `boolean` | display the segment in the HOME folder or not - defaults to `false` | +| `fetch_version` | `boolean` | display the golang version - defaults to `true` | +| `missing_command_text` | `string` | text to display when the command is missing - defaults to empty | +| `display_mode` | `string` | | +| `version_url_template` | `string` | a go [text/template][go-text-template] [template][templates] that creates the URL of the version info / release notes | +| `parse_mod_file` | `boolean` | parse the go.mod file instead of calling `go version` | ## Template ([info][templates]) diff --git a/website/docs/segments/wifi.mdx b/website/docs/segments/wifi.mdx deleted file mode 100644 index 2844b664..00000000 --- a/website/docs/segments/wifi.mdx +++ /dev/null @@ -1,53 +0,0 @@ ---- -id: wifi -title: WiFi -sidebar_label: WiFi ---- - -## What - -Show details about the currently connected WiFi network. - -:::info -Currently only supports Windows and WSL. Pull requests for Darwin and Linux support are welcome :) -::: - -## Sample Configuration - -```json -{ - "type": "wifi", - "style": "powerline", - "background": "#8822ee", - "foreground": "#222222", - "background_templates": [ - "{{ if (lt .Signal 60) }}#DDDD11{{ else if (lt .Signal 90) }}#DD6611{{ else }}#11CC11{{ end }}" - ], - "powerline_symbol": "\uE0B0", - "template": "\uFAA8 {{ .SSID }} {{ .Signal }}% {{ .ReceiveRate }}Mbps" -} -``` - -## Template ([info][templates]) - -:::note default template - -```template -{{ if .Error }}{{ .Error }}{{ else }}\uFAA8 {{ .SSID }} {{ .Signal }}% {{ .ReceiveRate }}Mbps{{ end }} -``` - -::: - -### Properties - -| Name | Type | Description | -| ----------------- | -------- | --------------------------------------------------------------------- | -| `.SSID` | `string` | the SSID of the current wifi network | -| `.RadioType` | `string` | the radio type - _e.g. 802.11ac, 802.11ax, 802.11n, etc._ | -| `.Authentication` | `string` | the authentication type - _e.g. WPA2-Personal, WPA2-Enterprise, etc._ | -| `.Channel` | `int` | the current channel number | -| `.ReceiveRate` | `int` | the receive rate (Mbps) | -| `.TransmitRate` | `int` | the transmit rate (Mbps) | -| `.Signal` | `int` | the signal strength (%) | - -[templates]: /docs/configuration/templates diff --git a/website/sidebars.js b/website/sidebars.js index 7f36303c..6cf179c8 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -58,10 +58,11 @@ module.exports = { "segments/battery", "segments/brewfather", "segments/cds", - "segments/command", "segments/cf", "segments/cftarget", "segments/cmake", + "segments/command", + "segments/connection", "segments/crystal", "segments/dart", "segments/deno", @@ -110,7 +111,6 @@ module.exports = { "segments/time", "segments/ui5tooling", "segments/wakatime", - "segments/wifi", "segments/withings", "segments/winreg", "segments/ytm",