forked from hrfee/waybar-mpris
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:
parent
ea4f47a1b1
commit
49b3212c48
174
main.go
174
main.go
@ -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
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user