Compare commits

..

6 Commits

Author SHA1 Message Date
49b3212c48 support multiple instances with different layouts
All checks were successful
continuous-integration/drone/push Build is passing
if specified layout for nth instance is different than 1st, the player data is
    shared instead of the waybar output.
2021-05-17 16:10:14 +01:00
ea4f47a1b1 only share output when args are identical; use fixed length socket
commands

fixed length commands were originally a guess for an issue I was having,
but it's cleaner this way anyway.
2021-05-17 13:44:24 +01:00
51c7a983a0 add PLAYER to --order, remove binary from repo
All checks were successful
continuous-integration/drone/push Build is passing
also added more prominent download links to the readme.
2021-05-16 23:28:23 +01:00
3aec82c40c fix upload.py args for new version
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-24 18:03:03 +01:00
a2164b0173 build JSON string instead of json.Marshal-ing
Some checks failed
continuous-integration/drone/push Build is failing
not that this was a problem, but the output is simple enough to
just create it by hand.
2021-04-24 16:37:44 +01:00
c235dd60dd overwrite previous content when sharing output
this way /tmp/waybar-mpris.out won't get filled up.
2021-04-24 16:02:39 +01:00
7 changed files with 460 additions and 176 deletions

View File

@@ -14,7 +14,7 @@ steps:
- ./goreleaser.sh --snapshot --skip-publish --rm-dist
- wget https://builds.hrfee.pw/upload.py
- pip3 install requests
- bash -c 'python3 upload.py https://builds2.hrfee.pw hrfee waybar-mpris ./dist/*.tar.gz'
- bash -c 'python3 upload.py https://builds2.hrfee.pw hrfee waybar-mpris --upload ./dist/*.tar.gz'
environment:
BUILDRONE_KEY:
from_secret: BUILDRONE_KEY

1
.gitignore vendored
View File

@@ -1 +1,2 @@
./main
waybar-mpris

View File

@@ -3,6 +3,9 @@
<img src="images/cropped.gif" style="width: 100%;" alt="bar gif"></img>
</p>
##### Downloads:
##### [binary](https://builds2.hrfee.pw/view/hrfee/waybar-mpris) | [aur](https://aur.archlinux.org/packages/waybar-mpris-git/)
a waybar component/utility for displaying and controlling MPRIS2 compliant media players individually, inspired by [waybar-media](https://github.com/yurihs/waybar-media).
MPRIS2 is widely supported, so this component should work with:
@@ -20,7 +23,7 @@ Available on the AUR as [waybar-mpris-git](https://aur.archlinux.org/packages/wa
`go get git.hrfee.pw/hrfee/waybar-mpris` will compile from source and install.
You can also download a precompiled binaries from [here](https://builds2.hrfee.pw/view/hrfee/waybar-mpris).
You can also download a precompiled binary from [here](https://builds2.hrfee.pw/view/hrfee/waybar-mpris).
## Issues
Stick them on [mpris2client](https://github.com/hrfee/mpris2client) or the [og](https://github.com/hrfee/waybar-mpris) repository (both on github) if you can't make an account here.

5
go.mod
View File

@@ -3,12 +3,9 @@ module git.hrfee.pw/hrfee/waybar-mpris
go 1.15
require (
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/fsnotify/fsnotify v1.4.9
github.com/godbus/dbus/v5 v5.0.3
github.com/hpcloud/tail v1.0.0
github.com/hrfee/mpris2client v0.0.5
github.com/spf13/pflag v1.0.5
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 // indirect
gopkg.in/fsnotify.v1 v1.4.7 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
)

18
go.sum
View File

@@ -2,28 +2,10 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hrfee/mpris2client v0.0.0-20210108004725-d2a36745cc4a h1:3H1awMdF3eJBCg0QPpDtNW+ujQPvUCuLsqlMRoODMA4=
github.com/hrfee/mpris2client v0.0.0-20210108004725-d2a36745cc4a/go.mod h1:tVpzzAlsljmQevNA4mJwUy1onUUiaQcRAa4gdl37okY=
github.com/hrfee/mpris2client v0.0.0-20210115213010-244c4cd6a569 h1:aCHzdXajnRUeaaW9FHzunrjGcRqmyFedwO0ODJRAdBk=
github.com/hrfee/mpris2client v0.0.0-20210115213010-244c4cd6a569/go.mod h1:tVpzzAlsljmQevNA4mJwUy1onUUiaQcRAa4gdl37okY=
github.com/hrfee/mpris2client v0.0.2 h1:AqsDoJCKyKjSGwVk3sJ5fjx+7M0tsSueZbVUIDxS1xI=
github.com/hrfee/mpris2client v0.0.2/go.mod h1:tVpzzAlsljmQevNA4mJwUy1onUUiaQcRAa4gdl37okY=
github.com/hrfee/mpris2client v0.0.4 h1:2WnCkTWGBr1eg3qHFjxDo7hIeCeQWh3jJ0hFJSts9Ks=
github.com/hrfee/mpris2client v0.0.4/go.mod h1:tVpzzAlsljmQevNA4mJwUy1onUUiaQcRAa4gdl37okY=
github.com/hrfee/mpris2client v0.0.5 h1:h3gcb8Q68i6SfcEgOxL9cXcGee9ekvxXltrUxGwRLDU=
github.com/hrfee/mpris2client v0.0.5/go.mod h1:tVpzzAlsljmQevNA4mJwUy1onUUiaQcRAa4gdl37okY=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200928205150-006507a75852 h1:sXxgOAXy8JwHhZnPuItAlUtwIlxrlEqi28mKhUR+zZY=
golang.org/x/sys v0.0.0-20200928205150-006507a75852/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201116194326-cc9327a14d48 h1:AYCWBZhgIw6XobZ5CibNJr0Rc4ZofGGKvWa1vcx2IGk=
golang.org/x/sys v0.0.0-20201116194326-cc9327a14d48/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 h1:nVuTkr9L6Bq62qpUqKo/RnZCFfzDBL0bYo6w9OJUqZY=
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=

511
main.go
View File

@@ -1,18 +1,18 @@
package main
import (
"encoding/json"
"fmt"
"io"
"log"
"net"
"os"
"os/signal"
"strconv"
"strings"
"time"
"github.com/fsnotify/fsnotify"
"github.com/godbus/dbus/v5"
"github.com/hpcloud/tail"
mpris2 "github.com/hrfee/mpris2client"
flag "github.com/spf13/pflag"
)
@@ -21,7 +21,8 @@ import (
const (
SOCK = "/tmp/waybar-mpris.sock"
LOGFILE = "/tmp/waybar-mpris.log"
OUTFILE = "/tmp/waybar-mpris.out"
OUTFILE = "/tmp/waybar-mpris.out" // Used for sharing waybar output when args are the same.
DATAFILE = "/tmp/waybar-mpris.data.out" // Used for sharing "\n"-separated player data between instances when args are different.
POLL = 1
)
@@ -37,24 +38,137 @@ var (
SHOW_POS = false
INTERPOLATE = false
REPLACE = false
isSharing = false
isDataSharing = false
WRITER io.Writer = os.Stdout
SHAREWRITER, DATAWRITER io.Writer
)
const (
cPlayerNext = "pn"
cPlayerPrev = "pp"
cNext = "mn"
cPrev = "mp"
cToggle = "mt"
cList = "ls"
cShare = "sh"
cPreShare = "ps"
cDataShare = "ds"
rSuccess = "sc"
rInvalidCommand = "iv"
rFailed = "fa"
)
func stringToCmd(str string) string {
switch str {
case "player-next":
return cPlayerNext
case "player-prev":
return cPlayerPrev
case "next":
return cNext
case "prev":
return cPrev
case "toggle":
return cToggle
case "list":
return cList
case "share":
return cShare
case "data-share":
return cDataShare
case "pre-share":
return cPreShare
}
return ""
}
// length-µS\nposition-µS\nplaying (0 or 1)\nartist\nalbum\ntitle\nplayer\n
func fromData(p *player, cmd string) {
p.Duplicate = true
values := make([]string, 7)
prev := 0
current := 0
for i := range cmd {
if current == len(values) {
break
}
if cmd[i] == '\n' {
values[current] = cmd[prev:i]
prev = i + 1
current++
}
}
l, err := strconv.ParseInt(values[0], 10, 64)
if err != nil {
l = -1
}
p.Length = int(l) / 1000000
pos, err := strconv.ParseInt(values[1], 10, 64)
if err != nil {
pos = -1
}
p.Position = pos
if values[2] == "1" {
p.Playing = true
} else {
p.Playing = false
}
p.Artist = values[3]
p.Album = values[4]
p.Title = values[5]
p.Name = values[6]
}
func toData(p *player) (cmd string) {
cmd += strconv.FormatInt(int64(p.Length*1000000), 10) + "\n"
cmd += strconv.FormatInt(p.Position, 10) + "\n"
if p.Playing {
cmd += "1"
} else {
cmd += "0"
}
cmd += "\n"
cmd += p.Artist + "\n"
cmd += p.Album + "\n"
cmd += p.Title + "\n"
cmd += p.Name + "\n"
return
}
type player struct {
*mpris2.Player
Duplicate bool
}
func secondsToString(seconds int) string {
minutes := int(seconds / 60)
seconds -= int(minutes * 60)
return fmt.Sprintf("%02d:%02d", minutes, seconds)
}
// JSON returns json for waybar to consume.
func playerJSON(p *mpris2.Player) string {
data := map[string]string{}
func playerJSON(p *player) string {
symbol := PLAY
data["class"] = "paused"
out := "{\"class\": \""
if p.Playing {
symbol = PAUSE
data["class"] = "playing"
out += "playing"
} else {
out += "paused"
}
var pos string
if SHOW_POS {
if !p.Duplicate {
pos = p.StringPosition()
if pos != "" {
pos = "(" + pos + ")"
}
} else {
pos = "(" + secondsToString(int(p.Position/1000000)) + "/" + secondsToString(p.Length) + ")"
}
}
var items []string
order := strings.Split(ORDER, ":")
@@ -78,6 +192,10 @@ func playerJSON(p *mpris2.Player) string {
if pos != "" && SHOW_POS {
items = append(items, pos)
}
case "PLAYER":
if p.Name != "" {
items = append(items, p.Name)
}
}
}
if len(items) == 0 {
@@ -95,21 +213,22 @@ func playerJSON(p *mpris2.Player) string {
}
text += v + right
}
data["tooltip"] = fmt.Sprintf(
"%s\nby %s\n",
strings.ReplaceAll(p.Title, "&", "&amp;"),
strings.ReplaceAll(p.Artist, "&", "&amp;"))
out += "\",\"text\":\"" + text + "\","
out += "\"tooltip\":\"" + strings.ReplaceAll(p.Title, "&", "&amp;") + "\\n"
if p.Artist != "" {
out += "by " + strings.ReplaceAll(p.Artist, "&", "&amp;") + "\\n"
}
if p.Album != "" {
data["tooltip"] += "from " + strings.ReplaceAll(p.Album, "&", "&amp;") + "\n"
out += "from " + strings.ReplaceAll(p.Album, "&", "&amp;") + "\\n"
}
data["tooltip"] += "(" + p.Name + ")"
data["text"] = text
out, err := json.Marshal(data)
if err != nil {
return "{}"
}
return string(out)
out += "(" + p.Name + ")\"}"
return out
// return fmt.Sprintf("{\"class\":\"%s\",\"text\":\"%s\",\"tooltip\":\"%s\"}", data["class"], data["text"], data["tooltip"])
// out, err := json.Marshal(data)
// if err != nil {
// return "{}"
// }
// return string(out)
}
type players struct {
@@ -118,7 +237,7 @@ type players struct {
func (pl *players) JSON() string {
if len(pl.mpris2.List) != 0 {
return playerJSON(pl.mpris2.List[pl.mpris2.Current])
return playerJSON(&player{pl.mpris2.List[pl.mpris2.Current], false})
}
return "{}"
}
@@ -129,38 +248,18 @@ func (pl *players) Prev() { pl.mpris2.List[pl.mpris2.Current].Previous() }
func (pl *players) Toggle() { pl.mpris2.List[pl.mpris2.Current].Toggle() }
func main() {
logfile, err := os.OpenFile(LOGFILE, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
if err != nil {
log.Fatalf("Couldn't open %s for writing: %s", LOGFILE, err)
}
mw := io.MultiWriter(logfile, os.Stdout)
log.SetOutput(mw)
flag.StringVar(&PLAY, "play", PLAY, "Play symbol/text to use.")
flag.StringVar(&PAUSE, "pause", PAUSE, "Pause symbol/text to use.")
flag.StringVar(&SEP, "separator", SEP, "Separator string to use between artist, album, and title.")
flag.StringVar(&ORDER, "order", ORDER, "Element order.")
flag.BoolVar(&AUTOFOCUS, "autofocus", AUTOFOCUS, "Auto switch to currently playing music players.")
flag.BoolVar(&SHOW_POS, "position", SHOW_POS, "Show current position between brackets, e.g (04:50/05:00)")
flag.BoolVar(&INTERPOLATE, "interpolate", INTERPOLATE, "Interpolate track position (helpful for players that don't update regularly, e.g mpDris2)")
flag.BoolVar(&REPLACE, "replace", REPLACE, "replace existing waybar-mpris if found. When false, new instance will clone the original instances output.")
var command string
flag.StringVar(&command, "send", "", "send command to already runnning waybar-mpris instance. (options: "+strings.Join(COMMANDS, "/")+")")
flag.Parse()
os.Stderr = logfile
if command != "" {
func execCommand(cmd string) {
conn, err := net.Dial("unix", SOCK)
if err != nil {
log.Fatalln("Couldn't dial:", err)
}
_, err = conn.Write([]byte(command))
shortCmd := stringToCmd(cmd)
_, err = conn.Write([]byte(shortCmd))
if err != nil {
log.Fatalln("Couldn't send command")
}
fmt.Println("Sent.")
if command == "list" {
if cmd == "list" {
buf := make([]byte, 512)
nr, err := conn.Read(buf)
if err != nil {
@@ -171,71 +270,150 @@ func main() {
fmt.Printf(response)
}
os.Exit(0)
}
// fmt.Println("New array", players)
// Start command listener
if _, err := os.Stat(SOCK); err == nil {
if REPLACE {
fmt.Printf("Socket %s already exists, this could mean waybar-mpris is already running.\nStarting this instance will overwrite the file, possibly stopping other instances from accepting commands.\n", SOCK)
var input string
ignoreChoice := false
fmt.Printf("Continue? [y/n]: ")
go func() {
fmt.Scanln(&input)
if strings.Contains(input, "y") && !ignoreChoice {
os.Remove(SOCK)
}
}()
time.Sleep(5 * time.Second)
if input == "" {
fmt.Printf("\nRemoving due to lack of input.\n")
ignoreChoice = true
// os.Remove(SOCK)
}
} else if conn, err := net.Dial("unix", SOCK); err == nil {
// When waybar-mpris is already running, we attach to its output instead of launching a whole new instance.
}
func duplicateOutput() error {
// Print to stderr to avoid errors from waybar
os.Stderr.WriteString("waybar-mpris is already running. This instance will clone its output.")
conn, err := net.Dial("unix", SOCK)
if err != nil {
log.Fatalln("Couldn't dial:", err)
return err
}
_, err = conn.Write([]byte("share"))
_, err = conn.Write([]byte(cPreShare))
if err != nil {
log.Fatalln("Couldn't send command")
log.Fatalf("Couldn't send command: %v", err)
return err
}
buf := make([]byte, 512)
nr, err := conn.Read(buf)
if err != nil {
log.Fatalln("Couldn't read response.")
log.Fatalf("Couldn't read response: %v", err)
return err
}
argString := ""
for _, arg := range os.Args {
argString += arg + "|"
}
conn.Close()
conn, err = net.Dial("unix", SOCK)
if err != nil {
return err
}
if string(buf[0:nr]) == argString {
// Tell other instance to share output in OUTFILE
_, err := conn.Write([]byte(cShare))
if err != nil {
log.Fatalf("Couldn't send command: %v", err)
}
buf = make([]byte, 2)
nr, err := conn.Read(buf)
if err != nil {
log.Fatalf("Couldn't read response: %v", err)
}
if resp := string(buf[0:nr]); resp == rSuccess {
// t, err := tail.TailFile(OUTFILE, tail.Config{
// Follow: true,
// MustExist: true,
// Logger: tail.DiscardingLogger,
// })
// if err == nil {
// for line := range t.Lines {
// fmt.Println(line.Text)
// }
// }
f, err := os.Open(OUTFILE)
if err != nil {
log.Fatalf("Failed to open \"%s\": %v", OUTFILE, err)
}
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatalf("Failed to start watcher: %v", err)
}
defer watcher.Close()
err = watcher.Add(OUTFILE)
if err != nil {
log.Fatalf("Failed to watch file: %v", err)
}
for {
select {
case event, ok := <-watcher.Events:
if !ok {
log.Printf("Watcher failed: %v", err)
return err
}
if event.Op&fsnotify.Write == fsnotify.Write {
l, err := io.ReadAll(f)
if err != nil {
log.Printf("Failed to read file: %v", err)
return err
}
str := string(l)
// Trim extra newline is necessary
if str[len(str)-2:] == "\n\n" {
fmt.Print(str[:len(str)-1])
} else {
fmt.Print(str)
}
f.Seek(0, 0)
}
if string(buf[0:nr]) == "success" {
t, err := tail.TailFile(OUTFILE, tail.Config{
Follow: true,
MustExist: true,
Logger: tail.DiscardingLogger,
})
if err == nil {
for line := range t.Lines {
fmt.Println(line.Text)
}
}
}
} else {
os.Remove(SOCK)
os.Remove(OUTFILE)
}
}
conn, err := dbus.SessionBus()
_, err := conn.Write([]byte(cDataShare))
if err != nil {
log.Fatalln("Error connecting to DBus:", err)
log.Fatalf("Couldn't send command: %v", err)
}
players := &players{
mpris2: mpris2.NewMpris2(conn, INTERPOLATE, POLL, AUTOFOCUS),
buf = make([]byte, 2)
nr, err := conn.Read(buf)
if err != nil {
log.Fatalf("Couldn't read response: %v", err)
}
players.mpris2.Reload()
players.mpris2.Sort()
lastLine := ""
go func() {
if resp := string(buf[0:nr]); resp == rSuccess {
f, err := os.Open(DATAFILE)
if err != nil {
log.Fatalf("Failed to open \"%s\": %v", DATAFILE, err)
}
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatalf("Failed to start watcher: %v", err)
}
defer watcher.Close()
err = watcher.Add(DATAFILE)
if err != nil {
log.Fatalf("Failed to watch file: %v", err)
}
p := &player{
&mpris2.Player{},
true,
}
for {
select {
case event, ok := <-watcher.Events:
if !ok {
log.Printf("Watcher failed: %v", err)
return err
}
if event.Op&fsnotify.Write == fsnotify.Write {
l, err := io.ReadAll(f)
if err != nil {
log.Printf("Failed to read file: %v", err)
return err
}
str := string(l)
fromData(p, str)
fmt.Fprintln(WRITER, playerJSON(p))
f.Seek(0, 0)
}
}
}
}
}
return nil
}
func listenForCommands(players *players) {
listener, err := net.Listen("unix", SOCK)
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
@@ -258,7 +436,7 @@ func main() {
log.Println("Couldn't accept:", err)
continue
}
buf := make([]byte, 512)
buf := make([]byte, 2)
nr, err := con.Read(buf)
if err != nil {
log.Println("Couldn't read:", err)
@@ -266,7 +444,7 @@ func main() {
}
command := string(buf[0:nr])
switch command {
case "player-next":
case cPlayerNext:
length := len(players.mpris2.List)
if length != 1 {
if players.mpris2.Current < uint(length-1) {
@@ -276,7 +454,7 @@ func main() {
}
players.mpris2.Refresh()
}
case "player-prev":
case cPlayerPrev:
length := len(players.mpris2.List)
if length != 1 {
if players.mpris2.Current != 0 {
@@ -286,26 +464,149 @@ func main() {
}
players.mpris2.Refresh()
}
case "next":
case cNext:
players.Next()
case "prev":
case cPrev:
players.Prev()
case "toggle":
case cToggle:
players.Toggle()
case "list":
case cList:
con.Write([]byte(players.mpris2.String()))
case "share":
out, err := os.Create(OUTFILE)
case cDataShare:
if !isDataSharing {
f, err := os.OpenFile(DATAFILE, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
defer f.Close()
if err != nil {
con.Write([]byte(fmt.Sprintf("Failed: %s", err)))
fmt.Fprintf(con, "Failed: %v", err)
}
WRITER = io.MultiWriter(os.Stdout, out)
con.Write([]byte("success"))
DATAWRITER = dataWrite{
emptyEveryWrite{file: f},
players,
}
if isSharing {
WRITER = io.MultiWriter(SHAREWRITER, DATAWRITER, os.Stdout)
} else {
WRITER = io.MultiWriter(DATAWRITER, os.Stdout)
}
isDataSharing = true
}
fmt.Fprint(con, rSuccess)
case cShare:
if !isSharing {
f, err := os.OpenFile(OUTFILE, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
defer f.Close()
if err != nil {
fmt.Fprintf(con, "Failed: %v", err)
}
SHAREWRITER = emptyEveryWrite{file: f}
if isDataSharing {
WRITER = io.MultiWriter(SHAREWRITER, DATAWRITER, os.Stdout)
} else {
WRITER = io.MultiWriter(SHAREWRITER, os.Stdout)
}
isSharing = true
}
fmt.Fprint(con, rSuccess)
/* Prior to sharing, the first instance sends its os.Args.
If the second instances args are different, the first sends the raw data (artist, album, etc.)
If they are the same, the first instance just sends its output and the second prints it. */
case cPreShare:
out := ""
for _, arg := range os.Args {
out += arg + "|"
}
con.Write([]byte(out))
default:
fmt.Println("Invalid command")
}
con.Close()
}
}
type dataWrite struct {
emptyEveryWrite
Players *players
}
func (w dataWrite) Write(p []byte) (n int, err error) {
line := toData(&player{w.Players.mpris2.List[w.Players.mpris2.Current], true})
return w.emptyEveryWrite.Write([]byte(line))
}
type emptyEveryWrite struct {
file *os.File
}
func (w emptyEveryWrite) Write(p []byte) (n int, err error) {
offset, err := w.file.Seek(0, 0)
if err != nil {
return 0, err
}
return w.file.WriteAt(p, offset)
}
func main() {
logfile, err := os.OpenFile(LOGFILE, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
if err != nil {
log.Fatalf("Couldn't open %s for writing: %s", LOGFILE, err)
}
mw := io.MultiWriter(logfile, os.Stdout)
log.SetOutput(mw)
flag.StringVar(&PLAY, "play", PLAY, "Play symbol/text to use.")
flag.StringVar(&PAUSE, "pause", PAUSE, "Pause symbol/text to use.")
flag.StringVar(&SEP, "separator", SEP, "Separator string to use between artist, album, and title.")
flag.StringVar(&ORDER, "order", ORDER, "Element order. An extra \"PLAYER\" element is also available.")
flag.BoolVar(&AUTOFOCUS, "autofocus", AUTOFOCUS, "Auto switch to currently playing music players.")
flag.BoolVar(&SHOW_POS, "position", SHOW_POS, "Show current position between brackets, e.g (04:50/05:00)")
flag.BoolVar(&INTERPOLATE, "interpolate", INTERPOLATE, "Interpolate track position (helpful for players that don't update regularly, e.g mpDris2)")
flag.BoolVar(&REPLACE, "replace", REPLACE, "replace existing waybar-mpris if found. When false, new instance will clone the original instances output.")
var command string
flag.StringVar(&command, "send", "", "send command to already runnning waybar-mpris instance. (options: "+strings.Join(COMMANDS, "/")+")")
flag.Parse()
os.Stderr = logfile
if command != "" {
execCommand(command)
}
// fmt.Println("New array", players)
// Start command listener
if _, err := os.Stat(SOCK); err == nil {
if REPLACE {
fmt.Printf("Socket %s already exists, this could mean waybar-mpris is already running.\nStarting this instance will overwrite the file, possibly stopping other instances from accepting commands.\n", SOCK)
var input string
ignoreChoice := false
fmt.Printf("Continue? [y/n]: ")
go func() {
fmt.Scanln(&input)
if strings.Contains(input, "y") && !ignoreChoice {
os.Remove(SOCK)
}
}()
time.Sleep(5 * time.Second)
if input == "" {
fmt.Printf("\nRemoving due to lack of input.\n")
ignoreChoice = true
// os.Remove(SOCK)
}
// When waybar-mpris is already running, we attach to its output instead of launching a whole new instance.
} else if err := duplicateOutput(); err != nil {
os.Stdout.WriteString("Couldn't dial socket, deleting instead: " + err.Error())
os.Remove(SOCK)
os.Remove(OUTFILE)
}
}
conn, err := dbus.SessionBus()
if err != nil {
log.Fatalln("Error connecting to DBus:", err)
}
players := &players{
mpris2: mpris2.NewMpris2(conn, INTERPOLATE, POLL, AUTOFOCUS),
}
players.mpris2.Reload()
players.mpris2.Sort()
lastLine := ""
go listenForCommands(players)
go players.mpris2.Listen()
if SHOW_POS {
go func() {

Binary file not shown.