fix halt when player is removed

rewritten without goroutines or checking if processes exist and it seems
to work much better now.
This commit is contained in:
Harvey Tindall 2020-08-22 14:39:23 +01:00
parent 3cf3f910ca
commit 5020e1c929
Signed by: hrfee
GPG Key ID: BBC65952848FB1A2
3 changed files with 207 additions and 99 deletions

4
go.mod
View File

@ -2,6 +2,4 @@ module github.com/hrfee/waybar-mpris
go 1.15 go 1.15
require ( require github.com/godbus/dbus/v5 v5.0.3
github.com/godbus/dbus/v5 v5.0.3
)

1
go.sum
View File

@ -1,4 +1,3 @@
github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4= github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4=
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME= 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/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=

275
main.go
View File

@ -3,104 +3,221 @@ package main
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/godbus/dbus/v5"
"sort" "sort"
"strings" "strings"
// "time"
"github.com/godbus/dbus/v5"
) )
var knownPlayers = map[string]string{
"plasma-browser-integration": "Browser",
"noson": "Noson",
}
type Player struct { type Player struct {
player dbus.BusObject player dbus.BusObject
fullName, name, title, artist, album string
playing, stopped bool playing, stopped bool
playerName, title, artist, album string
metadata map[string]dbus.Variant metadata map[string]dbus.Variant
conn *dbus.Conn conn *dbus.Conn
} }
func NewPlayer(conn *dbus.Conn, name string) *Player { const (
p := &Player{ INTERFACE = "org.mpris.MediaPlayer2"
player: conn.Object(name, "/org/mpris/MediaPlayer2"), PATH = "/org/mpris/MediaPlayer2"
PLAY = "▶"
PAUSE = ""
// NameOwnerChanged
MATCH_NOC = "type='signal',path='/org/freedesktop/DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'"
// PropertiesChanged
MATCH_PC = "type='signal',path='/org/mpris/MediaPlayer2',interface='org.freedesktop.DBus.Properties'"
)
// NewPlayer returns a new player object.
func NewPlayer(conn *dbus.Conn, name string) (p *Player) {
playerName := strings.ReplaceAll(name, INTERFACE+".", "")
for key, val := range knownPlayers {
if strings.Contains(name, key) {
playerName = val
break
}
}
p = &Player{
player: conn.Object(name, PATH),
conn: conn, conn: conn,
playerName: strings.ReplaceAll(name, "org.mpris.MediaPlayer2.", ""), name: playerName,
fullName: name,
} }
p.Refresh() p.Refresh()
return p return
} }
func (p *Player) Refresh() { // Refresh grabs playback info.
val, err := p.player.GetProperty("org.mpris.MediaPlayer2.Player.PlaybackStatus") func (p *Player) Refresh() (err error) {
val, err := p.player.GetProperty(INTERFACE + ".Player.PlaybackStatus")
if err != nil { if err != nil {
panic(err) p.playing = false
p.stopped = false
p.metadata = map[string]dbus.Variant{}
p.title = ""
p.artist = ""
p.album = ""
return
} }
if strings.Contains(val.String(), "Playing") { strVal := val.String()
if strings.Contains(strVal, "Playing") {
p.playing = true p.playing = true
p.stopped = false p.stopped = false
} else if strings.Contains(val.String(), "Paused") { } else if strings.Contains(strVal, "Paused") {
p.playing = false p.playing = false
p.stopped = false p.stopped = false
} else { } else {
p.playing = false p.playing = false
p.stopped = true p.stopped = true
} }
md, err := p.player.GetProperty("org.mpris.MediaPlayer2.Player.Metadata") metadata, err := p.player.GetProperty(INTERFACE + ".Player.Metadata")
if err != nil { if err != nil {
p.metadata = map[string]dbus.Variant{}
p.title = ""
p.artist = ""
p.album = ""
return return
} }
p.metadata = md.Value().(map[string]dbus.Variant) p.metadata = metadata.Value().(map[string]dbus.Variant)
p.artist = strings.Join(p.metadata["xesam:artist"].Value().([]string), ", ") switch artist := p.metadata["xesam:artist"].Value().(type) {
p.title = p.metadata["xesam:title"].Value().(string) case []string:
p.album = p.metadata["xesam:album"].Value().(string) p.artist = strings.Join(artist, ", ")
case string:
p.artist = artist
default:
p.artist = ""
}
switch title := p.metadata["xesam:title"].Value().(type) {
case string:
p.title = title
default:
p.title = ""
}
switch album := p.metadata["xesam:album"].Value().(type) {
case string:
p.album = album
default:
p.album = ""
}
return nil
} }
func (p *Player) JSON() string { func (p *Player) JSON() string {
var items []string
for _, v := range []string{p.artist, p.album, p.title} {
if v != "" {
items = append(items, v)
}
}
if len(items) == 0 {
return "{}"
}
data := map[string]string{} data := map[string]string{}
data["tooltip"] = fmt.Sprintf( data["tooltip"] = fmt.Sprintf(
"%s\nby %s\nfrom %s\n(%s)", "%s\nby %s\n",
p.title, p.title,
p.artist, p.artist)
p.album, if p.album != "" {
p.playerName) data["tooltip"] += "from " + p.album + "\n"
var symbol string }
if p.playing { data["tooltip"] += "(" + p.name + ")"
data["class"] = "playing" symbol := PLAY
symbol = "" data["class"] = "paused"
} else { if p.playing {
data["class"] = "paused" symbol = PAUSE
symbol = "▶" data["class"] = "playing"
}
data["text"] = symbol + " " + strings.Join(items, " - ")
text, err := json.Marshal(data)
if err != nil {
return "{}"
} }
data["text"] = fmt.Sprintf(
"%s %s - %s - %s",
symbol,
p.artist,
p.album,
p.title)
text, _ := json.Marshal(data)
return string(text) return string(text)
} }
type Players []*Player type PlayerList struct {
list List
func (s Players) Len() int { conn *dbus.Conn
return len(s)
} }
func (s Players) Less(i, j int) bool { type List []*Player
s[i].Refresh()
s[j].Refresh() func (ls List) Len() int {
var states [2]int return len(ls)
if s[i].playing { }
states[0] = 1
func (ls List) Less(i, j int) bool {
var states [2]uint8
for i, p := range []bool{ls[i].playing, ls[j].playing} {
if p {
states[i] = 1
} }
if s[j].playing {
states[1] = 1
} }
// reverse // Reverse order
return states[0] > states[1] return states[0] > states[1]
} }
func (s Players) Swap(i, j int) { func (ls List) Swap(i, j int) {
s[i], s[j] = s[j], s[i] ls[i], ls[j] = ls[j], ls[i]
}
// Doesn't retain order since sorting if constantly done anyway
func (pl *PlayerList) Remove(fullName string) {
var i int
found := false
for ind, p := range pl.list {
if p.fullName == fullName {
i = ind
found = true
break
}
}
if found {
pl.list[0], pl.list[i] = pl.list[i], pl.list[0]
pl.list = pl.list[1:]
}
// ls[len(ls)-1], ls[i] = ls[i], ls[len(ls)-1]
// ls = ls[:len(ls)-1]
}
func (pl *PlayerList) Reload() error {
var buses []string
err := pl.conn.BusObject().Call("org.freedesktop.DBus.ListNames", 0).Store(&buses)
if err != nil {
return err
}
for _, name := range buses {
if strings.HasPrefix(name, INTERFACE) {
pl.New(name)
}
}
return nil
}
func (pl *PlayerList) New(name string) {
pl.list = append(pl.list, NewPlayer(pl.conn, name))
}
func (pl *PlayerList) Sort() {
sort.Sort(pl.list)
}
func (pl *PlayerList) Refresh() {
for i := range pl.list {
pl.list[i].Refresh()
}
}
func (pl *PlayerList) JSON() string {
if len(pl.list) != 0 {
return pl.list[0].JSON()
}
return "{}"
} }
func main() { func main() {
@ -108,44 +225,38 @@ func main() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
var players Players players := &PlayerList{
getPlayers := func() { conn: conn,
var fd []string
err = conn.BusObject().Call("org.freedesktop.DBus.ListNames", 0).Store(&fd)
if err != nil {
panic(err)
} }
for _, name := range fd { players.Reload()
if strings.HasPrefix(name, "org.mpris.MediaPlayer2") { players.Sort()
players = append(players, NewPlayer(conn, name)) fmt.Println("New array", players)
} conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, MATCH_NOC)
} conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, MATCH_PC)
sort.Sort(players)
}
getPlayers()
go func() {
conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
"type='signal',path='/org/freedesktop/DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'")
c := make(chan *dbus.Signal, 10) c := make(chan *dbus.Signal, 10)
conn.Signal(c) conn.Signal(c)
for v := range c { for v := range c {
// fmt.Printf("SIGNAL: Sender %s, Path %s, Name %s, Body %s\n", v.Sender, v.Path, v.Name, v.Body)
if strings.Contains(v.Name, "NameOwnerChanged") {
switch name := v.Body[0].(type) { switch name := v.Body[0].(type) {
case string: case string:
if strings.Contains(name, "org.mpris.MediaPlayer2") && name != "org.mpris.MediaPlayer2.Player" { var pid uint32
players = append(players, NewPlayer(conn, name)) conn.BusObject().Call("org.freedesktop.DBus.GetConnectionUnixProcessID", 0, name).Store(&pid)
sort.Sort(players) if strings.Contains(name, INTERFACE) {
if pid == 0 {
fmt.Println("Removing", name)
players.Remove(name)
} else {
fmt.Println("Adding", name)
players.New(name)
} }
} }
} }
}() } else if strings.Contains(v.Name, "PropertiesChanged") && strings.Contains(v.Body[0].(string), INTERFACE+".Player") {
conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, players.Refresh()
"type='signal',path='/org/mpris/MediaPlayer2',interface='org.freedesktop.DBus.Properties'") players.Sort()
c := make(chan *dbus.Signal, 10) fmt.Println(players.JSON())
conn.Signal(c) }
fmt.Println(players[0].JSON()) fmt.Println("New array", players)
for range c {
sort.Sort(players)
players[0].Refresh()
fmt.Println(players[0].JSON())
} }
} }