mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-05-15 00:32:47 -04:00
app picker: extend App Picker to integrate with mime overrides
- Adds "DMS Opener" as an option (dms-open.desktop) - Add mime type GO utils - Add rememberance to App Picker modal
This commit is contained in:
@@ -0,0 +1,311 @@
|
||||
package desktop
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
aliasMap map[string]string
|
||||
subclassMap map[string][]string
|
||||
aliasLoaded time.Time
|
||||
aliasReloadMu sync.Mutex
|
||||
|
||||
mimeCacheMap map[string][]string
|
||||
mimeCacheLoaded time.Time
|
||||
mimeCacheReloadMu sync.Mutex
|
||||
)
|
||||
|
||||
const aliasTTL = 60 * time.Second
|
||||
const mimeCacheTTL = 10 * time.Second
|
||||
|
||||
func mimeDataDirs() []string {
|
||||
var dirs []string
|
||||
seen := make(map[string]bool)
|
||||
|
||||
add := func(p string) {
|
||||
if p == "" || seen[p] {
|
||||
return
|
||||
}
|
||||
seen[p] = true
|
||||
dirs = append(dirs, p)
|
||||
}
|
||||
|
||||
add(filepath.Join(utils.XDGDataHome(), "mime"))
|
||||
|
||||
if env := os.Getenv("XDG_DATA_DIRS"); env != "" {
|
||||
for d := range strings.SplitSeq(env, ":") {
|
||||
add(filepath.Join(strings.TrimSpace(d), "mime"))
|
||||
}
|
||||
} else {
|
||||
add("/usr/local/share/mime")
|
||||
add("/usr/share/mime")
|
||||
}
|
||||
|
||||
return dirs
|
||||
}
|
||||
|
||||
func loadAliasTables() {
|
||||
aliases := make(map[string]string)
|
||||
subclasses := make(map[string][]string)
|
||||
|
||||
for _, dir := range mimeDataDirs() {
|
||||
readKV(filepath.Join(dir, "aliases"), func(k, v string) {
|
||||
if _, ok := aliases[k]; !ok {
|
||||
aliases[k] = v
|
||||
}
|
||||
})
|
||||
readKV(filepath.Join(dir, "subclasses"), func(k, v string) {
|
||||
subclasses[k] = append(subclasses[k], v)
|
||||
})
|
||||
}
|
||||
|
||||
aliasMap = aliases
|
||||
subclassMap = subclasses
|
||||
aliasLoaded = time.Now()
|
||||
}
|
||||
|
||||
func readKV(path string, fn func(k, v string)) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
scanner := bufio.NewScanner(bytes.NewReader(data))
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if line == "" || line[0] == '#' {
|
||||
continue
|
||||
}
|
||||
sp := strings.IndexByte(line, ' ')
|
||||
if sp <= 0 {
|
||||
continue
|
||||
}
|
||||
fn(strings.TrimSpace(line[:sp]), strings.TrimSpace(line[sp+1:]))
|
||||
}
|
||||
}
|
||||
|
||||
func ensureAliasTables() {
|
||||
aliasReloadMu.Lock()
|
||||
defer aliasReloadMu.Unlock()
|
||||
|
||||
if aliasMap == nil || time.Since(aliasLoaded) > aliasTTL {
|
||||
loadAliasTables()
|
||||
}
|
||||
}
|
||||
|
||||
func loadMimeCache() {
|
||||
merged := make(map[string][]string)
|
||||
seen := make(map[string]map[string]bool)
|
||||
|
||||
for _, dir := range applicationDirs() {
|
||||
data, err := os.ReadFile(filepath.Join(dir, "mimeinfo.cache"))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
groups := parseGroups(data)
|
||||
g := groups["MIME Cache"]
|
||||
if g == nil {
|
||||
continue
|
||||
}
|
||||
for mime, val := range g.keys {
|
||||
ids := splitList(val)
|
||||
if len(ids) == 0 {
|
||||
continue
|
||||
}
|
||||
if seen[mime] == nil {
|
||||
seen[mime] = make(map[string]bool)
|
||||
}
|
||||
for _, id := range ids {
|
||||
if seen[mime][id] {
|
||||
continue
|
||||
}
|
||||
seen[mime][id] = true
|
||||
merged[mime] = append(merged[mime], id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mimeCacheMap = merged
|
||||
mimeCacheLoaded = time.Now()
|
||||
}
|
||||
|
||||
func ensureMimeCache() {
|
||||
mimeCacheReloadMu.Lock()
|
||||
defer mimeCacheReloadMu.Unlock()
|
||||
|
||||
if mimeCacheMap == nil || time.Since(mimeCacheLoaded) > mimeCacheTTL {
|
||||
loadMimeCache()
|
||||
}
|
||||
}
|
||||
|
||||
func cacheAppsForMime(mimeType string) []string {
|
||||
ensureMimeCache()
|
||||
return mimeCacheMap[mimeType]
|
||||
}
|
||||
|
||||
func StripMimeParams(mimeType string) string {
|
||||
if semi := strings.IndexByte(mimeType, ';'); semi >= 0 {
|
||||
mimeType = mimeType[:semi]
|
||||
}
|
||||
return strings.TrimSpace(mimeType)
|
||||
}
|
||||
|
||||
func canonicalMime(mimeType string) string {
|
||||
ensureAliasTables()
|
||||
mimeType = StripMimeParams(mimeType)
|
||||
if target, ok := aliasMap[mimeType]; ok {
|
||||
return target
|
||||
}
|
||||
return mimeType
|
||||
}
|
||||
|
||||
func mimeChain(mimeType string) []string {
|
||||
ensureAliasTables()
|
||||
|
||||
root := canonicalMime(mimeType)
|
||||
visited := map[string]bool{root: true}
|
||||
chain := []string{root}
|
||||
|
||||
queue := []string{root}
|
||||
for len(queue) > 0 {
|
||||
cur := queue[0]
|
||||
queue = queue[1:]
|
||||
for _, parent := range subclassMap[cur] {
|
||||
if visited[parent] {
|
||||
continue
|
||||
}
|
||||
visited[parent] = true
|
||||
chain = append(chain, parent)
|
||||
queue = append(queue, parent)
|
||||
}
|
||||
}
|
||||
|
||||
return chain
|
||||
}
|
||||
|
||||
func entrySupportsMime(entry *Entry, chain []string) bool {
|
||||
for _, m := range entry.MimeTypes {
|
||||
canonical := canonicalMime(m)
|
||||
if slices.Contains(chain, canonical) || slices.Contains(chain, m) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func GetDefault(mimeType string) string {
|
||||
merged := mergedAssociations()
|
||||
chain := mimeChain(mimeType)
|
||||
|
||||
for _, m := range chain {
|
||||
if id, ok := merged.Defaults[m]; ok {
|
||||
if !slices.Contains(merged.Removed[m], id) {
|
||||
return id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, m := range chain {
|
||||
for _, id := range merged.Added[m] {
|
||||
if !slices.Contains(merged.Removed[m], id) {
|
||||
return id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, m := range chain {
|
||||
for _, id := range cacheAppsForMime(m) {
|
||||
if !slices.Contains(merged.Removed[m], id) {
|
||||
return id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, m := range chain {
|
||||
for _, entry := range AllEntries() {
|
||||
if entry.Hidden || entry.NoDisplay {
|
||||
continue
|
||||
}
|
||||
if slices.Contains(merged.Removed[m], entry.ID) {
|
||||
continue
|
||||
}
|
||||
if entrySupportsMime(entry, []string{m}) {
|
||||
return entry.ID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func SetDefault(mimeType, desktopID string) error {
|
||||
return setDefaultAssociation(mimeType, desktopID)
|
||||
}
|
||||
|
||||
func SetDefaults(mimeTypes []string, desktopID string) error {
|
||||
return setDefaultAssociations(mimeTypes, desktopID)
|
||||
}
|
||||
|
||||
func AppsForMime(mimeType string) []string {
|
||||
merged := mergedAssociations()
|
||||
chain := mimeChain(mimeType)
|
||||
removed := make(map[string]bool)
|
||||
for _, m := range chain {
|
||||
for _, id := range merged.Removed[m] {
|
||||
removed[id] = true
|
||||
}
|
||||
}
|
||||
|
||||
seen := make(map[string]bool)
|
||||
var out []string
|
||||
|
||||
add := func(id string) {
|
||||
if id == "" || removed[id] || seen[id] {
|
||||
return
|
||||
}
|
||||
seen[id] = true
|
||||
out = append(out, id)
|
||||
}
|
||||
|
||||
for _, m := range chain {
|
||||
if id := merged.Defaults[m]; id != "" {
|
||||
add(id)
|
||||
}
|
||||
for _, id := range merged.Added[m] {
|
||||
add(id)
|
||||
}
|
||||
}
|
||||
|
||||
for _, m := range chain {
|
||||
for _, id := range cacheAppsForMime(m) {
|
||||
add(id)
|
||||
}
|
||||
}
|
||||
|
||||
for _, entry := range AllEntries() {
|
||||
if entry.Hidden {
|
||||
continue
|
||||
}
|
||||
if entrySupportsMime(entry, chain) {
|
||||
add(entry.ID)
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func QueryDefaults(mimeTypes []string) map[string]string {
|
||||
out := make(map[string]string, len(mimeTypes))
|
||||
for _, m := range mimeTypes {
|
||||
out[m] = GetDefault(m)
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user