220 lines
4.8 KiB
Go
220 lines
4.8 KiB
Go
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
|
|
|
|
package process
|
|
|
|
import (
|
|
"bufio"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/Safing/safing-core/log"
|
|
)
|
|
|
|
var (
|
|
humanInfoCache map[string]*humanInfo
|
|
humanInfoCacheNamesOnly map[string]*humanInfo
|
|
nextHumanInfoCacheUpdate time.Time
|
|
|
|
searchPathCache map[string]string
|
|
nextSearchPathCacheUpdate time.Time
|
|
|
|
humanInfoCacheLock sync.Mutex
|
|
)
|
|
|
|
type humanInfo struct {
|
|
Name string
|
|
IconName string
|
|
IconPath string
|
|
ExecCmd string
|
|
}
|
|
|
|
func (m *Process) GetHumanInfo() {
|
|
humanInfoCacheLock.Lock()
|
|
defer humanInfoCacheLock.Unlock()
|
|
|
|
// TODO: do some proper fuzzy matching to get these icons!
|
|
// take special care of:
|
|
// - flatpak
|
|
// - snapd
|
|
|
|
fuzzyMatch := false
|
|
info, ok := humanInfoCache[m.Path]
|
|
if !ok {
|
|
splitted := strings.Split(m.Path, "/")
|
|
nameOnly := splitted[len(splitted)-1]
|
|
info, ok = humanInfoCacheNamesOnly[nameOnly]
|
|
if ok {
|
|
fuzzyMatch = true
|
|
} else {
|
|
updateHumanInfoCache()
|
|
info, ok = humanInfoCache[m.Path]
|
|
if !ok {
|
|
fuzzyMatch = true
|
|
info, ok = humanInfoCacheNamesOnly[nameOnly]
|
|
if !ok {
|
|
if len(splitted) > 3 {
|
|
info, ok = humanInfoCacheNamesOnly[splitted[len(splitted)-2]]
|
|
if !ok {
|
|
return
|
|
}
|
|
} else {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if info.IconPath == "" && info.IconName != "" {
|
|
info.IconPath = getIconPathFromName(info.IconName)
|
|
}
|
|
|
|
if !fuzzyMatch {
|
|
m.Name = info.Name
|
|
}
|
|
if info.IconPath != "" {
|
|
m.Icon = "f:" + info.IconPath
|
|
}
|
|
|
|
}
|
|
|
|
func updateHumanInfoCache() {
|
|
if time.Now().Before(nextHumanInfoCacheUpdate) {
|
|
return
|
|
}
|
|
humanInfoCache = make(map[string]*humanInfo)
|
|
humanInfoCacheNamesOnly = make(map[string]*humanInfo)
|
|
log.Tracef("process: updating human info cache")
|
|
for _, starterDir := range starterLocations {
|
|
for _, dirEntry := range readDirNames(starterDir) {
|
|
|
|
// only look at .desktop files
|
|
if !strings.HasSuffix(dirEntry, ".desktop") {
|
|
continue
|
|
}
|
|
|
|
execCmd, name, iconPath := getStarterInfo(filepath.Join(starterDir, dirEntry))
|
|
|
|
if execCmd == "" {
|
|
continue
|
|
}
|
|
execPath := strings.SplitN(execCmd, " ", 2)[0]
|
|
|
|
var ok bool
|
|
if !strings.HasPrefix(execPath, "/") {
|
|
execPath, ok = searchPathCache[execPath]
|
|
if !ok {
|
|
updateSearchPathCache()
|
|
execPath, ok = searchPathCache[execPath]
|
|
if !ok {
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
new := humanInfo{
|
|
Name: name,
|
|
}
|
|
if strings.HasPrefix(iconPath, "/") {
|
|
new.IconPath = iconPath
|
|
} else {
|
|
new.IconName = iconPath
|
|
}
|
|
humanInfoCache[execPath] = &new
|
|
splitted := strings.Split(execPath, "/")
|
|
humanInfoCacheNamesOnly[splitted[len(splitted)-1]] = &new
|
|
// log.Tracef("process: new cache entry: %s - %s - %s - %s", execPath, name, new.IconName, new.IconPath)
|
|
|
|
}
|
|
}
|
|
nextHumanInfoCacheUpdate = time.Now().Add(10 * time.Second)
|
|
}
|
|
|
|
func getStarterInfo(filePath string) (execCmd, name, iconPath string) {
|
|
// open file
|
|
file, err := os.Open(filePath)
|
|
if err != nil {
|
|
log.Warningf("process: could not read %s: %s", filePath, err)
|
|
return
|
|
}
|
|
defer file.Close()
|
|
|
|
// file scanner
|
|
scanner := bufio.NewScanner(file)
|
|
scanner.Split(bufio.ScanLines)
|
|
|
|
// parse
|
|
seen := 0
|
|
scanner.Scan() // skip first line
|
|
for scanner.Scan() {
|
|
line := strings.SplitN(scanner.Text(), "=", 2)
|
|
if len(line) < 2 {
|
|
continue
|
|
}
|
|
switch {
|
|
case line[0] == "Name":
|
|
if name == "" {
|
|
name = line[1]
|
|
seen += 1
|
|
}
|
|
case line[0] == "Exec":
|
|
if execCmd == "" {
|
|
execCmd = line[1]
|
|
seen += 1
|
|
}
|
|
case line[0] == "Icon":
|
|
if iconPath == "" {
|
|
iconPath = line[1]
|
|
seen += 1
|
|
}
|
|
}
|
|
if seen >= 3 {
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func updateSearchPathCache() {
|
|
if time.Now().Before(nextSearchPathCacheUpdate) {
|
|
return
|
|
}
|
|
searchPathCache = make(map[string]string)
|
|
for _, searchEntry := range strings.Split(os.Getenv("PATH"), ":") {
|
|
names := readDirNames(searchEntry)
|
|
for _, name := range names {
|
|
// only save first occurence
|
|
if _, ok := searchPathCache[name]; !ok {
|
|
searchPathCache[name] = filepath.Join(searchEntry, name)
|
|
}
|
|
}
|
|
}
|
|
nextSearchPathCacheUpdate = time.Now().Add(10 * time.Second)
|
|
}
|
|
|
|
func getIconPathFromName(iconName string) string {
|
|
for _, location := range xdgIconLocations {
|
|
// skip everything that does not exist or we do not have access to.
|
|
if _, err := os.Stat(location); err != nil {
|
|
continue
|
|
}
|
|
// check for icon
|
|
for _, path := range xdgIconPaths {
|
|
possibleIcon := filepath.Join(location, path, iconName+".png")
|
|
if _, err := os.Stat(possibleIcon); err == nil {
|
|
return possibleIcon
|
|
}
|
|
}
|
|
}
|
|
for _, location := range iconLocations {
|
|
possibleIcon := filepath.Join(location, iconName+".png")
|
|
if _, err := os.Stat(possibleIcon); err == nil {
|
|
return possibleIcon
|
|
}
|
|
}
|
|
return ""
|
|
}
|