feat(connection): new segment

BREAKING CHANGE: this will need a manual migration from the wifi
segment to the new connection segment.
This commit is contained in:
LNK LEO 2022-09-12 18:55:57 +08:00 committed by Jan De Dobbeleer
parent f60b1715bd
commit 4b6b128d74
23 changed files with 562 additions and 484 deletions

View file

@ -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:

View file

@ -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) {

View file

@ -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{},

View file

@ -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() {

View file

@ -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{}
}

View file

@ -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{}
}

View file

@ -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
}

View file

@ -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 (

View file

@ -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=

View file

@ -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 {

View file

@ -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
}

View file

@ -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) {

View file

@ -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
}

View file

@ -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))
}
}
}

View file

@ -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,

View file

@ -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
}

View file

@ -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)
}
}
}

View file

@ -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": {

View file

@ -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

View file

@ -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 <inlineCode>\|</inlineCode> . The first to resolve is shown (**default value** is <inlineCode>wifi\|ethernet</inlineCode>). Possible values:<ul><li>`wifi`</li><li>`ethernet`</li><li>`bluetooth`</li><li>`cellular`</li></ul> |
## 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

View file

@ -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` | <ul><li>`always`: the segment is always displayed</li><li>`files`: the segment is only displayed when `*.go` or `go.mod` files are present (**default**)</li></ul> |
| `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` | <ul><li>`always`: the segment is always displayed</li><li>`files`: the segment is only displayed when `*.go` or `go.mod` files are present (**default**)</li></ul> |
| `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])

View file

@ -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

View file

@ -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",