feat(windows): support store app CLI tools

resolves #1703
This commit is contained in:
Jan De Dobbeleer 2022-02-07 18:50:52 +01:00 committed by Jan De Dobbeleer
parent 632e495141
commit 1e797acf77
4 changed files with 104 additions and 1 deletions

View file

@ -397,7 +397,7 @@ func (env *ShellEnvironment) RunCommand(command string, args ...string) (string,
env.log(Error, "RunCommand", errorStr)
return output, cmdErr
}
output := strings.TrimSuffix(out.String(), "\n")
output := strings.TrimSpace(out.String())
env.log(Debug, "RunCommand", output)
return output, nil
}
@ -418,6 +418,10 @@ func (env *ShellEnvironment) HasCommand(command string) bool {
env.cmdCache.set(command, path)
return true
}
if path, err := env.LookWinAppPath(command); err == nil {
env.cmdCache.set(command, path)
return true
}
env.log(Error, "HasCommand", err.Error())
return false
}

View file

@ -119,3 +119,7 @@ func (env *ShellEnvironment) ConvertToLinuxPath(path string) string {
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")
}

View file

@ -116,6 +116,19 @@ func (env *ShellEnvironment) CachePath() string {
return env.Home()
}
func (env *ShellEnvironment) LookWinAppPath(file string) (string, error) {
winAppPath := env.Home() + `\AppData\Local\Microsoft\WindowsApps\`
command := file + ".exe"
isWinStoreApp := func() bool {
return env.HasFilesInDir(winAppPath, command)
}
if isWinStoreApp() {
commandFile := winAppPath + command
return readWinAppLink(commandFile)
}
return "", errors.New("no Windows Store App")
}
//
// Takes a registry path to a key like
// "HKLM\Software\Microsoft\Windows NT\CurrentVersion\EditionID"

View file

@ -3,10 +3,12 @@
package environment
import (
"errors"
"fmt"
"oh-my-posh/regex"
"strings"
"syscall"
"unicode/utf16"
"unsafe"
"golang.org/x/sys/windows"
@ -199,3 +201,83 @@ func getHKEYHandleFromAbbrString(abbr string) windows.Handle {
return 0
}
type REPARSE_DATA_BUFFER struct { // nolint: revive
ReparseTag uint32
ReparseDataLength uint16
Reserved uint16
DUMMYUNIONNAME byte
}
type GenericDataBuffer struct {
DataBuffer [1]uint8
}
type AppExecLinkReparseBuffer struct {
Version uint32
StringList [1]uint16
}
func (rb *AppExecLinkReparseBuffer) Path() string {
UTF16ToStringPosition := func(s []uint16) (string, int) {
for i, v := range s {
if v == 0 {
s = s[0:i]
return string(utf16.Decode(s)), i
}
}
return "", 0
}
s := (*[0xffff]uint16)(unsafe.Pointer(&rb.StringList[0]))[0:]
var link string
position := 0
for i := 0; i <= 2; i++ {
link, position = UTF16ToStringPosition(s)
position++
s = s[position:]
}
return link
}
// openSymlink calls CreateFile Windows API with FILE_FLAG_OPEN_REPARSE_POINT
// parameter, so that Windows does not follow symlink, if path is a symlink.
// openSymlink returns opened file handle.
func openSymlink(path string) (syscall.Handle, error) {
p, err := syscall.UTF16PtrFromString(path)
if err != nil {
return 0, err
}
attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS)
// Use FILE_FLAG_OPEN_REPARSE_POINT, otherwise CreateFile will follow symlink.
// See https://docs.microsoft.com/en-us/windows/desktop/FileIO/symbolic-link-effects-on-file-systems-functions#createfile-and-createfiletransacted
attrs |= syscall.FILE_FLAG_OPEN_REPARSE_POINT
h, err := syscall.CreateFile(p, 0, 0, nil, syscall.OPEN_EXISTING, attrs, 0)
if err != nil {
return 0, err
}
return h, nil
}
func readWinAppLink(path string) (string, error) {
h, err := openSymlink(path)
if err != nil {
return "", err
}
defer syscall.CloseHandle(h) // nolint: errcheck
rdbbuf := make([]byte, syscall.MAXIMUM_REPARSE_DATA_BUFFER_SIZE)
var bytesReturned uint32
err = syscall.DeviceIoControl(h, syscall.FSCTL_GET_REPARSE_POINT, nil, 0, &rdbbuf[0], uint32(len(rdbbuf)), &bytesReturned, nil)
if err != nil {
return "", err
}
rdb := (*REPARSE_DATA_BUFFER)(unsafe.Pointer(&rdbbuf[0]))
rb := (*GenericDataBuffer)(unsafe.Pointer(&rdb.DUMMYUNIONNAME))
appExecLink := (*AppExecLinkReparseBuffer)(unsafe.Pointer(&rb.DataBuffer))
if appExecLink.Version != 3 {
return " ", errors.New("unknown appexec link version")
}
link := appExecLink.Path()
return link, nil
}