aboutsummaryrefslogtreecommitdiff
path: root/main.go
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 /main.go
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.
Diffstat (limited to 'main.go')
-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
}