mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2024-12-27 11:59:40 -08:00
188 lines
4.9 KiB
Go
188 lines
4.9 KiB
Go
// +build windows
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
"syscall"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
// WindowsProcess is an implementation of Process for Windows.
|
|
type WindowsProcess struct {
|
|
pid int
|
|
ppid int
|
|
exe string
|
|
}
|
|
|
|
// getImagePid returns the
|
|
func getImagePid(imageName string) ([]int, error) {
|
|
processes, err := processes()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var pids []int
|
|
for i := 0; i < len(processes); i++ {
|
|
if strings.ToLower(processes[i].exe) == imageName {
|
|
pids = append(pids, processes[i].pid)
|
|
}
|
|
}
|
|
return pids, nil
|
|
}
|
|
|
|
// getWindowTitle returns the title of a window linked to a process name
|
|
func getWindowTitle(imageName, windowTitleRegex string) (string, error) {
|
|
processPid, err := getImagePid(imageName)
|
|
if err != nil {
|
|
return "", nil
|
|
}
|
|
|
|
// is a spotify process running?
|
|
// no: returns an empty string
|
|
if len(processPid) == 0 {
|
|
return "", nil
|
|
}
|
|
|
|
// returns the first window of the first pid
|
|
_, windowTitle, err := GetWindowTitle(processPid[0], windowTitleRegex)
|
|
if err != nil {
|
|
return "", nil
|
|
}
|
|
return windowTitle, nil
|
|
}
|
|
|
|
func newWindowsProcess(e *windows.ProcessEntry32) *WindowsProcess {
|
|
// Find when the string ends for decoding
|
|
end := 0
|
|
for {
|
|
if e.ExeFile[end] == 0 {
|
|
break
|
|
}
|
|
end++
|
|
}
|
|
|
|
return &WindowsProcess{
|
|
pid: int(e.ProcessID),
|
|
ppid: int(e.ParentProcessID),
|
|
exe: syscall.UTF16ToString(e.ExeFile[:end]),
|
|
}
|
|
}
|
|
|
|
// Processes returns a snapshot of all the processes
|
|
// Taken and adapted from https://github.com/mitchellh/go-ps
|
|
func processes() ([]WindowsProcess, error) {
|
|
// get process table snapshot
|
|
handle, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0)
|
|
if err != nil {
|
|
return nil, syscall.GetLastError()
|
|
}
|
|
defer func() {
|
|
_ = windows.CloseHandle(handle)
|
|
}()
|
|
|
|
// get process infor by looping through the snapshot
|
|
var entry windows.ProcessEntry32
|
|
entry.Size = uint32(unsafe.Sizeof(entry))
|
|
err = windows.Process32First(handle, &entry)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error retrieving process info")
|
|
}
|
|
|
|
results := make([]WindowsProcess, 0, 50)
|
|
for {
|
|
results = append(results, *newWindowsProcess(&entry))
|
|
err := windows.Process32Next(handle, &entry)
|
|
if err != nil {
|
|
if err == syscall.ERROR_NO_MORE_FILES {
|
|
break
|
|
}
|
|
return nil, fmt.Errorf("Fail to syscall Process32Next: %v", err)
|
|
}
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
// win32 specific code
|
|
|
|
// win32 dll load and function definitions
|
|
var (
|
|
user32 = syscall.NewLazyDLL("user32.dll")
|
|
procEnumWindows = user32.NewProc("EnumWindows")
|
|
procGetWindowTextW = user32.NewProc("GetWindowTextW")
|
|
procGetWindowThreadProcessID = user32.NewProc("GetWindowThreadProcessId")
|
|
)
|
|
|
|
// EnumWindows call EnumWindows from user32 and returns all active windows
|
|
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumwindows
|
|
func EnumWindows(enumFunc, lparam uintptr) (err error) {
|
|
r1, _, e1 := syscall.Syscall(procEnumWindows.Addr(), 2, enumFunc, lparam, 0)
|
|
if r1 == 0 {
|
|
if e1 != 0 {
|
|
err = error(e1)
|
|
} else {
|
|
err = syscall.EINVAL
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// GetWindowText returns the title and text of a window from a window handle
|
|
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowtextw
|
|
func GetWindowText(hwnd syscall.Handle, str *uint16, maxCount int32) (length int32, err error) {
|
|
r0, _, e1 := syscall.Syscall(procGetWindowTextW.Addr(), 3, uintptr(hwnd), uintptr(unsafe.Pointer(str)), uintptr(maxCount))
|
|
length = int32(r0)
|
|
if length == 0 {
|
|
if e1 != 0 {
|
|
err = error(e1)
|
|
} else {
|
|
err = syscall.EINVAL
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// GetWindowTitle searches for a window attached to the pid
|
|
func GetWindowTitle(pid int, windowTitleRegex string) (syscall.Handle, string, error) {
|
|
var hwnd syscall.Handle
|
|
var title string
|
|
compiledRegex, err := regexp.Compile(windowTitleRegex)
|
|
if err != nil {
|
|
return 0, "", fmt.Errorf("Error while compiling the regex '%s'", windowTitleRegex)
|
|
}
|
|
// callback fro EnumWindows
|
|
cb := syscall.NewCallback(func(h syscall.Handle, p uintptr) uintptr {
|
|
var prcsID int = 0
|
|
// get pid
|
|
_, _, _ = procGetWindowThreadProcessID.Call(uintptr(h), uintptr(unsafe.Pointer(&prcsID)))
|
|
// check if pid matches spotify pid
|
|
if prcsID == pid {
|
|
b := make([]uint16, 200)
|
|
_, err := GetWindowText(h, &b[0], int32(len(b)))
|
|
if err != nil {
|
|
// ignore the error
|
|
return 1 // continue enumeration
|
|
}
|
|
title = syscall.UTF16ToString(b)
|
|
if compiledRegex.MatchString(title) {
|
|
// will cause EnumWindows to return 0 (error)
|
|
// but we don't want to enumerate all windows since we got what we want
|
|
hwnd = h
|
|
return 0
|
|
}
|
|
}
|
|
|
|
return 1 // continue enumeration
|
|
})
|
|
// Enumerates all top-level windows on the screen
|
|
// The error is not checked because if EnumWindows is stopped bofere enumerating all windows
|
|
// it returns 0(error occurred) instead of 1(success)
|
|
// In our case, title will equal "" or the title of the window anyway
|
|
_ = EnumWindows(cb, 0)
|
|
return hwnd, title, nil
|
|
}
|