2024-09-08 02:24:01 +09:00
|
|
|
package app
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io/fs"
|
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
|
2024-09-16 20:32:07 +09:00
|
|
|
"git.ophivana.moe/cat/fortify/acl"
|
2024-09-17 13:48:42 +09:00
|
|
|
"git.ophivana.moe/cat/fortify/internal"
|
2024-09-08 02:24:01 +09:00
|
|
|
"git.ophivana.moe/cat/fortify/internal/util"
|
2024-09-12 21:07:05 +09:00
|
|
|
"git.ophivana.moe/cat/fortify/internal/verbose"
|
2024-09-08 02:24:01 +09:00
|
|
|
)
|
|
|
|
|
2024-09-17 13:48:42 +09:00
|
|
|
const (
|
|
|
|
pulseServer = "PULSE_SERVER"
|
|
|
|
pulseCookie = "PULSE_COOKIE"
|
|
|
|
|
|
|
|
home = "HOME"
|
|
|
|
xdgConfigHome = "XDG_CONFIG_HOME"
|
|
|
|
)
|
|
|
|
|
2024-09-08 02:24:01 +09:00
|
|
|
func (a *App) SharePulse() {
|
2024-09-17 13:48:42 +09:00
|
|
|
a.setEnablement(internal.EnablePulse)
|
2024-09-08 02:24:01 +09:00
|
|
|
|
|
|
|
// ensure PulseAudio directory ACL (e.g. `/run/user/%d/pulse`)
|
2024-09-16 20:31:15 +09:00
|
|
|
pulse := path.Join(a.runtimePath, "pulse")
|
2024-09-08 02:24:01 +09:00
|
|
|
pulseS := path.Join(pulse, "native")
|
|
|
|
if s, err := os.Stat(pulse); err != nil {
|
|
|
|
if !errors.Is(err, fs.ErrNotExist) {
|
2024-09-17 13:48:42 +09:00
|
|
|
internal.Fatal("Error accessing PulseAudio directory:", err)
|
2024-09-08 02:24:01 +09:00
|
|
|
}
|
2024-09-17 13:48:42 +09:00
|
|
|
internal.Fatal(fmt.Sprintf("PulseAudio dir '%s' not found", pulse))
|
2024-09-08 02:24:01 +09:00
|
|
|
} else {
|
|
|
|
// add environment variable for new process
|
2024-09-17 13:48:42 +09:00
|
|
|
a.AppendEnv(pulseServer, "unix:"+pulseS)
|
2024-09-08 02:24:01 +09:00
|
|
|
if err = acl.UpdatePerm(pulse, a.UID(), acl.Execute); err != nil {
|
2024-09-17 13:48:42 +09:00
|
|
|
internal.Fatal("Error preparing PulseAudio:", err)
|
2024-09-08 02:24:01 +09:00
|
|
|
} else {
|
2024-09-17 13:48:42 +09:00
|
|
|
a.exit.RegisterRevertPath(pulse)
|
2024-09-08 02:24:01 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
// ensure PulseAudio socket permission (e.g. `/run/user/%d/pulse/native`)
|
|
|
|
if s, err = os.Stat(pulseS); err != nil {
|
|
|
|
if errors.Is(err, fs.ErrNotExist) {
|
2024-09-17 13:48:42 +09:00
|
|
|
internal.Fatal("PulseAudio directory found but socket does not exist")
|
2024-09-08 02:24:01 +09:00
|
|
|
}
|
2024-09-17 13:48:42 +09:00
|
|
|
internal.Fatal("Error accessing PulseAudio socket:", err)
|
2024-09-08 02:24:01 +09:00
|
|
|
} else {
|
|
|
|
if m := s.Mode(); m&0o006 != 0o006 {
|
2024-09-17 13:48:42 +09:00
|
|
|
internal.Fatal(fmt.Sprintf("Unexpected permissions on '%s':", pulseS), m)
|
2024-09-08 02:24:01 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Publish current user's pulse-cookie for target user
|
2024-09-17 13:48:42 +09:00
|
|
|
pulseCookieSource := discoverPulseCookie()
|
2024-09-16 20:31:15 +09:00
|
|
|
pulseCookieFinal := path.Join(a.sharePath, "pulse-cookie")
|
2024-09-17 13:48:42 +09:00
|
|
|
a.AppendEnv(pulseCookie, pulseCookieFinal)
|
2024-09-12 21:07:05 +09:00
|
|
|
verbose.Printf("Publishing PulseAudio cookie '%s' to '%s'\n", pulseCookieSource, pulseCookieFinal)
|
2024-09-08 02:24:01 +09:00
|
|
|
if err = util.CopyFile(pulseCookieFinal, pulseCookieSource); err != nil {
|
2024-09-17 13:48:42 +09:00
|
|
|
internal.Fatal("Error copying PulseAudio cookie:", err)
|
2024-09-08 02:24:01 +09:00
|
|
|
}
|
|
|
|
if err = acl.UpdatePerm(pulseCookieFinal, a.UID(), acl.Read); err != nil {
|
2024-09-17 13:48:42 +09:00
|
|
|
internal.Fatal("Error publishing PulseAudio cookie:", err)
|
2024-09-08 02:24:01 +09:00
|
|
|
} else {
|
2024-09-17 13:48:42 +09:00
|
|
|
a.exit.RegisterRevertPath(pulseCookieFinal)
|
2024-09-08 02:24:01 +09:00
|
|
|
}
|
|
|
|
|
2024-09-12 21:07:05 +09:00
|
|
|
verbose.Printf("PulseAudio dir '%s' configured\n", pulse)
|
2024-09-08 02:24:01 +09:00
|
|
|
}
|
|
|
|
}
|
2024-09-17 13:48:42 +09:00
|
|
|
|
|
|
|
// discoverPulseCookie try various standard methods to discover the current user's PulseAudio authentication cookie
|
|
|
|
func discoverPulseCookie() string {
|
|
|
|
if p, ok := os.LookupEnv(pulseCookie); ok {
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
|
|
|
|
if p, ok := os.LookupEnv(home); ok {
|
|
|
|
p = path.Join(p, ".pulse-cookie")
|
|
|
|
if s, err := os.Stat(p); err != nil {
|
|
|
|
if !errors.Is(err, fs.ErrNotExist) {
|
|
|
|
internal.Fatal("Error accessing PulseAudio cookie:", err)
|
|
|
|
// unreachable
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
} else if !s.IsDir() {
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if p, ok := os.LookupEnv(xdgConfigHome); ok {
|
|
|
|
p = path.Join(p, "pulse", "cookie")
|
|
|
|
if s, err := os.Stat(p); err != nil {
|
|
|
|
if !errors.Is(err, fs.ErrNotExist) {
|
|
|
|
internal.Fatal("Error accessing PulseAudio cookie:", err)
|
|
|
|
// unreachable
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
} else if !s.IsDir() {
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
internal.Fatal(fmt.Sprintf("Cannot locate PulseAudio cookie (tried $%s, $%s/pulse/cookie, $%s/.pulse-cookie)",
|
|
|
|
pulseCookie, xdgConfigHome, home))
|
|
|
|
return ""
|
|
|
|
}
|