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.
This commit is contained in:
Harvey Tindall 2021-05-17 15:56:23 +01:00
parent ea4f47a1b1
commit 49b3212c48
Signed by untrusted user: hrfee
GPG Key ID: BBC65952848FB1A2

174
main.go
View File

@ -7,6 +7,7 @@ import (
"net" "net"
"os" "os"
"os/signal" "os/signal"
"strconv"
"strings" "strings"
"time" "time"
@ -20,7 +21,8 @@ import (
const ( const (
SOCK = "/tmp/waybar-mpris.sock" SOCK = "/tmp/waybar-mpris.sock"
LOGFILE = "/tmp/waybar-mpris.log" 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 POLL = 1
) )
@ -37,7 +39,9 @@ var (
INTERPOLATE = false INTERPOLATE = false
REPLACE = false REPLACE = false
isSharing = false isSharing = false
isDataSharing = false
WRITER io.Writer = os.Stdout WRITER io.Writer = os.Stdout
SHAREWRITER, DATAWRITER io.Writer
) )
const ( const (
@ -49,8 +53,10 @@ const (
cList = "ls" cList = "ls"
cShare = "sh" cShare = "sh"
cPreShare = "ps" cPreShare = "ps"
cDataShare = "ds"
rSuccess = "sc" rSuccess = "sc"
rInvalidCommand = "iv" rInvalidCommand = "iv"
rFailed = "fa"
) )
func stringToCmd(str string) string { func stringToCmd(str string) string {
@ -69,14 +75,81 @@ func stringToCmd(str string) string {
return cList return cList
case "share": case "share":
return cShare return cShare
case "data-share":
return cDataShare
case "pre-share": case "pre-share":
return cPreShare return cPreShare
} }
return "" 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. // JSON returns json for waybar to consume.
func playerJSON(p *mpris2.Player) string { func playerJSON(p *player) string {
symbol := PLAY symbol := PLAY
out := "{\"class\": \"" out := "{\"class\": \""
if p.Playing { if p.Playing {
@ -87,10 +160,15 @@ func playerJSON(p *mpris2.Player) string {
} }
var pos string var pos string
if SHOW_POS { if SHOW_POS {
if !p.Duplicate {
pos = p.StringPosition() pos = p.StringPosition()
if pos != "" { if pos != "" {
pos = "(" + pos + ")" pos = "(" + pos + ")"
} }
} else {
pos = "(" + secondsToString(int(p.Position/1000000)) + "/" + secondsToString(p.Length) + ")"
}
} }
var items []string var items []string
order := strings.Split(ORDER, ":") order := strings.Split(ORDER, ":")
@ -159,7 +237,7 @@ type players struct {
func (pl *players) JSON() string { func (pl *players) JSON() string {
if len(pl.mpris2.List) != 0 { 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 "{}" return "{}"
} }
@ -217,18 +295,18 @@ func duplicateOutput() error {
for _, arg := range os.Args { for _, arg := range os.Args {
argString += arg + "|" argString += arg + "|"
} }
if string(buf[0:nr]) == argString {
conn.Close() conn.Close()
conn, err = net.Dial("unix", SOCK) conn, err = net.Dial("unix", SOCK)
if err != nil { if err != nil {
return err return err
} }
if string(buf[0:nr]) == argString {
// Tell other instance to share output in OUTFILE // Tell other instance to share output in OUTFILE
_, err := conn.Write([]byte(cShare)) _, err := conn.Write([]byte(cShare))
if err != nil { if err != nil {
log.Fatalf("Couldn't send command: %v", err) log.Fatalf("Couldn't send command: %v", err)
} }
buf := make([]byte, 2) buf = make([]byte, 2)
nr, err := conn.Read(buf) nr, err := conn.Read(buf)
if err != nil { if err != nil {
log.Fatalf("Couldn't read response: %v", err) 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 return nil
} }
@ -345,6 +472,25 @@ func listenForCommands(players *players) {
players.Toggle() players.Toggle()
case cList: case cList:
con.Write([]byte(players.mpris2.String())) 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: case cShare:
if !isSharing { if !isSharing {
f, err := os.OpenFile(OUTFILE, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666) 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 { if err != nil {
fmt.Fprintf(con, "Failed: %v", err) fmt.Fprintf(con, "Failed: %v", err)
} }
var out io.Writer = emptyEveryWrite{file: f} SHAREWRITER = emptyEveryWrite{file: f}
WRITER = io.MultiWriter(os.Stdout, out) if isDataSharing {
WRITER = io.MultiWriter(SHAREWRITER, DATAWRITER, os.Stdout)
} else {
WRITER = io.MultiWriter(SHAREWRITER, os.Stdout)
}
isSharing = true isSharing = true
} }
fmt.Fprint(con, rSuccess) 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 { type emptyEveryWrite struct {
file *os.File file *os.File
} }