diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cfac06b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/main +/audiochanger diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..64d4ab9 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module codeberg.org/tomkoid/audiochanger + +go 1.22.2 + +require ( + github.com/jfreymuth/pulse v0.1.1 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c83bcc3 --- /dev/null +++ b/go.sum @@ -0,0 +1,18 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/jfreymuth/pulse v0.1.1 h1:9WLNBNCijmtZ14ZJpatgJPu/NjwAl3TIKItSFnTh+9A= +github.com/jfreymuth/pulse v0.1.1/go.mod h1:cpYspI6YljhkUf1WLXLLDmeaaPFc3CnGLjDZf9dZ4no= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..672eb00 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,66 @@ +package config + +import ( + "errors" + "fmt" + "log" + "os" + + "github.com/pelletier/go-toml/v2" +) + +func configFileExists(configFilePath string) bool { + // if path exists + _, err := os.Stat(configFilePath) + if err != nil { + return false + } + + return true +} + +func readConfigFile(configFilePath string) (string, error) { + if !configFileExists(configFilePath) { + return "", errors.New("config file does not exist") + } else { + file, err := os.ReadFile(configFilePath) + if err != nil { + return "", err + } + + configStr := string(file) + + return configStr, nil + } +} + +func GetConfig() Config { + // default config + var config Config = Config{ + PollRateMs: 20, + Mpc: true, + PlayerCtl: true, + } + + userConfigDir, err := os.UserConfigDir() + if err != nil { + log.Fatal(err) + } + + configFilePath := userConfigDir + "/audiochanger/config.toml" + + configStr, err := readConfigFile(configFilePath) + if err != nil { + log.Println("using default config") + return config + } + + err = toml.Unmarshal([]byte(configStr), &config) + if err != nil { + log.Fatal(err) + } + + fmt.Printf("config: %+v\n", config) + + return config +} diff --git a/internal/config/model.go b/internal/config/model.go new file mode 100644 index 0000000..ed83d92 --- /dev/null +++ b/internal/config/model.go @@ -0,0 +1,7 @@ +package config + +type Config struct { + PollRateMs int `toml:"poll_rate_ms"` + Mpc bool `toml:"mpc"` + PlayerCtl bool `toml:"playerctl"` +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..f955736 --- /dev/null +++ b/main.go @@ -0,0 +1,105 @@ +package main + +import ( + "fmt" + "log" + "os" + "os/exec" + "os/signal" + "syscall" + "time" + + "github.com/jfreymuth/pulse" + + "codeberg.org/tomkoid/audiochanger/internal/config" +) + +func runCommand(name string, arg ...string) error { + cmd := exec.Command(name, arg...) + + if err := cmd.Run(); err != nil { + return err + } + + return nil +} + +func stopAudio(config *config.Config) { + log.Println("Stopping audio") + // if mpc binary exists on system + if _, err := exec.LookPath("mpc"); err == nil && config.Mpc { + err = runCommand("mpc", "pause") + if err != nil { + log.Println(err) + } + } + + if _, err := exec.LookPath("playerctl"); err == nil && config.PlayerCtl { + err = runCommand("playerctl", "pause") + if err != nil { + log.Println(err) + } + } +} + +func handleCleanup(pulseClient *pulse.Client) { + c := make(chan os.Signal) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + + <-c + + println("Shutting down...") + + // Run Cleanup + pulseClient.Close() + + os.Exit(1) +} + +func main() { + config := config.GetConfig() + + // Create a PulseAudio context + c, err := pulse.NewClient() + if err != nil { + log.Fatal(err) + } + + go handleCleanup(c) + + defer c.Close() + + // Get the default sink (audio source) + defaultSink, err := c.DefaultSink() + if err != nil { + log.Fatal(err) + } + + // Store the initial sink name + initialSinkName := defaultSink.Name() + + // Monitor the default sink for changes + for { + // Get the new default sink + defaultSink, err = c.DefaultSink() + if err != nil { + log.Fatal(err) + } + + // Check if the sink name has changed + if defaultSink.Name() != initialSinkName { + fmt.Printf( + "Audio source changed from %s to %s\n", + initialSinkName, + defaultSink.Name(), + ) + + // stop audio if mpc or playerctl is running + stopAudio(&config) + + initialSinkName = defaultSink.Name() + } + + time.Sleep(time.Duration(config.PollRateMs) * time.Millisecond) + } +}