Files
cthulhu/internal/atspi/flatpak_process.go

101 lines
2.8 KiB
Go

// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
// SPDX-License-Identifier: GPL-3.0-or-later
package atspi
import (
"fmt"
"io"
"os"
"strings"
"syscall"
)
type flatpakInstance struct {
application, instance string
}
// Flatpak gives its D-Bus proxy and application the same /.flatpak-info.
// Compare a running instance, never just an application ID: two instances of
// the same application must not acquire one another's foreground window.
func readFlatpakInstance(procRoot string, pid int) flatpakInstance {
if pid <= 0 {
return flatpakInstance{}
}
file, err := os.OpenFile(fmt.Sprintf("%s/%d/root/.flatpak-info", procRoot, pid), os.O_RDONLY|syscall.O_NONBLOCK, 0)
if err != nil {
return flatpakInstance{}
}
defer file.Close()
const limit = 64 * 1024
stat, err := file.Stat()
if err != nil || !stat.Mode().IsRegular() || stat.Size() > limit {
return flatpakInstance{}
}
data, err := io.ReadAll(io.LimitReader(file, limit+1))
if err != nil || len(data) > limit {
return flatpakInstance{}
}
var result flatpakInstance
var section string
for _, line := range strings.Split(string(data), "\n") {
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, ";") {
continue
}
if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
section = line
continue
}
key, value, ok := strings.Cut(line, "=")
if !ok {
return flatpakInstance{}
}
key, value = strings.TrimSpace(key), strings.TrimSpace(value)
if section == "[Application]" && key == "name" {
if result.application != "" {
return flatpakInstance{}
}
result.application = value
}
if section == "[Instance]" && key == "instance-id" {
if result.instance != "" {
return flatpakInstance{}
}
result.instance = value
}
}
if result.application == "" || result.instance == "" {
return flatpakInstance{}
}
return result
}
func sameFlatpakInstance(procRoot string, first, second int) bool {
identity := readFlatpakInstance(procRoot, first)
return identity != (flatpakInstance{}) && identity == readFlatpakInstance(procRoot, second)
}
// Owned by the GLib thread and cleared with the foreground client. This only
// resolves process identity; fresh window state and geometry still decide
// whether an accessible can own focus.
func (adapter *Adapter) foregroundProcessID(pid int) int {
client := adapter.Foreground().Client
if client.Window == 0 || client.PID <= 0 || pid <= 0 || pid == client.PID {
return pid
}
matched, known := adapter.foregroundProcesses[pid]
if !known {
matched = sameFlatpakInstance("/proc", client.PID, pid)
if adapter.foregroundProcesses == nil || len(adapter.foregroundProcesses) >= 128 {
adapter.foregroundProcesses = make(map[int]bool)
}
adapter.foregroundProcesses[pid] = matched
}
if matched {
return client.PID
}
return pid
}