Files
portmaster/process/xdgicons/humanInfo.go
2018-08-13 14:14:27 +02:00

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