aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHarvey Tindall <hrfee@protonmail.ch>2021-05-17 15:56:23 +0100
committerHarvey Tindall <hrfee@protonmail.ch>2021-05-17 16:10:14 +0100
commit49b3212c48194608edc4adef32f7fdaca47fd05e (patch)
tree679b1738cd7f26c9cffa9e4f7bb9ff4aa47b11a3
parentea4f47a1b14737599150519892ef0bb602b3f423 (diff)
downloadwaybar-mpris-49b3212c48194608edc4adef32f7fdaca47fd05e.tar.gz
waybar-mpris-49b3212c48194608edc4adef32f7fdaca47fd05e.zip
support multiple instances with different layouts
if specified layout for nth instance is different than 1st, the player data is shared instead of the waybar output.
-rw-r--r--main.go206
1 files changed, 183 insertions, 23 deletions
diff --git a/main.go b/main.go
index 8d34fd1..08f58c7 100644
--- a/main.go
+++ b/main.go
@@ -7,6 +7,7 @@ import (
"net"
"os"
"os/signal"
+ "strconv"
"strings"
"time"
@@ -18,10 +19,11 @@ import (
// Various paths and values to use elsewhere.
const (
- SOCK = "/tmp/waybar-mpris.sock"
- LOGFILE = "/tmp/waybar-mpris.log"
- OUTFILE = "/tmp/waybar-mpris.out"
- POLL = 1
+ SOCK = "/tmp/waybar-mpris.sock"
+ LOGFILE = "/tmp/waybar-mpris.log"
+ 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
)
// Mostly default values for flag options.
@@ -32,12 +34,14 @@ var (
ORDER = "SYMBOL:ARTIST:ALBUM:TITLE:POSITION"
AUTOFOCUS = false
// Available commands that can be sent to running instances.
- COMMANDS = []string{"player-next", "player-prev", "next", "prev", "toggle", "list"}
- SHOW_POS = false
- INTERPOLATE = false
- REPLACE = false
- isSharing = false
- WRITER io.Writer = os.Stdout
+ COMMANDS = []string{"player-next", "player-prev", "next", "prev", "toggle", "list"}
+ SHOW_POS = false
+ INTERPOLATE = false
+ REPLACE = false
+ isSharing = false
+ isDataSharing = false
+ WRITER io.Writer = os.Stdout
+ SHAREWRITER, DATAWRITER io.Writer
)
const (
@@ -49,8 +53,10 @@ const (
cList = "ls"
cShare = "sh"
cPreShare = "ps"
+ cDataShare = "ds"
rSuccess = "sc"
rInvalidCommand = "iv"
+ rFailed = "fa"
)
func stringToCmd(str string) string {
@@ -69,14 +75,81 @@ func stringToCmd(str string) string {
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 {
+func playerJSON(p *player) string {
symbol := PLAY
out := "{\"class\": \""
if p.Playing {
@@ -87,9 +160,14 @@ func playerJSON(p *mpris2.Player) string {
}
var pos string
if SHOW_POS {
- pos = p.StringPosition()
- if pos != "" {
- pos = "(" + pos + ")"
+ if !p.Duplicate {
+ pos = p.StringPosition()
+ if pos != "" {
+ pos = "(" + pos + ")"
+ }
+ } else {
+ pos = "(" + secondsToString(int(p.Position/1000000)) + "/" + secondsToString(p.Length) + ")"
+
}
}
var items []string
@@ -159,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 "{}"
}
@@ -217,18 +295,18 @@ func duplicateOutput() error {
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 {
- conn.Close()
- conn, err = net.Dial("unix", SOCK)
- if err != nil {
- return err
- }
// 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)
+ buf = make([]byte, 2)
nr, err := conn.Read(buf)
if err != nil {
log.Fatalf("Couldn't read response: %v", err)
@@ -282,6 +360,55 @@ func duplicateOutput() error {
}
}
}
+ } else {
+ _, err := conn.Write([]byte(cDataShare))
+ 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 {
+ 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
}
@@ -345,6 +472,25 @@ func listenForCommands(players *players) {
players.Toggle()
case cList:
con.Write([]byte(players.mpris2.String()))
+ 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 {
+ fmt.Fprintf(con, "Failed: %v", err)
+ }
+ 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)
@@ -352,8 +498,12 @@ func listenForCommands(players *players) {
if err != nil {
fmt.Fprintf(con, "Failed: %v", err)
}
- var out io.Writer = emptyEveryWrite{file: f}
- WRITER = io.MultiWriter(os.Stdout, out)
+ 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)
@@ -373,6 +523,16 @@ func listenForCommands(players *players) {
}
}
+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
}