diff options
-rw-r--r-- | go.mod | 4 | ||||
-rw-r--r-- | go.sum | 1 | ||||
-rw-r--r-- | main.go | 289 |
3 files changed, 201 insertions, 93 deletions
@@ -2,6 +2,4 @@ module github.com/hrfee/waybar-mpris go 1.15 -require ( - github.com/godbus/dbus/v5 v5.0.3 -) +require github.com/godbus/dbus/v5 v5.0.3 @@ -1,4 +1,3 @@ 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/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -3,104 +3,221 @@ package main import ( "encoding/json" "fmt" - "github.com/godbus/dbus/v5" "sort" "strings" - // "time" + + "github.com/godbus/dbus/v5" ) +var knownPlayers = map[string]string{ + "plasma-browser-integration": "Browser", + "noson": "Noson", +} + type Player struct { - player dbus.BusObject - playing, stopped bool - playerName, title, artist, album string - metadata map[string]dbus.Variant - conn *dbus.Conn + player dbus.BusObject + fullName, name, title, artist, album string + playing, stopped bool + metadata map[string]dbus.Variant + conn *dbus.Conn } -func NewPlayer(conn *dbus.Conn, name string) *Player { - p := &Player{ - player: conn.Object(name, "/org/mpris/MediaPlayer2"), - conn: conn, - playerName: strings.ReplaceAll(name, "org.mpris.MediaPlayer2.", ""), +const ( + INTERFACE = "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, + name: playerName, + fullName: name, } p.Refresh() - return p + return } -func (p *Player) Refresh() { - val, err := p.player.GetProperty("org.mpris.MediaPlayer2.Player.PlaybackStatus") +// Refresh grabs playback info. +func (p *Player) Refresh() (err error) { + val, err := p.player.GetProperty(INTERFACE + ".Player.PlaybackStatus") 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.stopped = false - } else if strings.Contains(val.String(), "Paused") { + } else if strings.Contains(strVal, "Paused") { p.playing = false p.stopped = false } else { p.playing = false p.stopped = true } - md, err := p.player.GetProperty("org.mpris.MediaPlayer2.Player.Metadata") + metadata, err := p.player.GetProperty(INTERFACE + ".Player.Metadata") if err != nil { + p.metadata = map[string]dbus.Variant{} + p.title = "" + p.artist = "" + p.album = "" return } - p.metadata = md.Value().(map[string]dbus.Variant) - p.artist = strings.Join(p.metadata["xesam:artist"].Value().([]string), ", ") - p.title = p.metadata["xesam:title"].Value().(string) - p.album = p.metadata["xesam:album"].Value().(string) + p.metadata = metadata.Value().(map[string]dbus.Variant) + switch artist := p.metadata["xesam:artist"].Value().(type) { + case []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 { + 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["tooltip"] = fmt.Sprintf( - "%s\nby %s\nfrom %s\n(%s)", + "%s\nby %s\n", p.title, - p.artist, - p.album, - p.playerName) - var symbol string + p.artist) + if p.album != "" { + data["tooltip"] += "from " + p.album + "\n" + } + data["tooltip"] += "(" + p.name + ")" + symbol := PLAY + data["class"] = "paused" if p.playing { + symbol = PAUSE data["class"] = "playing" - symbol = "" - } else { - data["class"] = "paused" - symbol = "▶" } - data["text"] = fmt.Sprintf( - "%s %s - %s - %s", - symbol, - p.artist, - p.album, - p.title) - - text, _ := json.Marshal(data) + data["text"] = symbol + " " + strings.Join(items, " - ") + text, err := json.Marshal(data) + if err != nil { + return "{}" + } return string(text) } -type Players []*Player +type PlayerList struct { + list List + conn *dbus.Conn +} + +type List []*Player -func (s Players) Len() int { - return len(s) +func (ls List) Len() int { + return len(ls) } -func (s Players) Less(i, j int) bool { - s[i].Refresh() - s[j].Refresh() - var states [2]int - if s[i].playing { - states[0] = 1 - } - if s[j].playing { - states[1] = 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 + } } - // reverse + // Reverse order return states[0] > states[1] } -func (s Players) Swap(i, j int) { - s[i], s[j] = s[j], s[i] +func (ls List) Swap(i, j int) { + 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() { @@ -108,44 +225,38 @@ func main() { if err != nil { panic(err) } - var players Players - getPlayers := func() { - var fd []string - err = conn.BusObject().Call("org.freedesktop.DBus.ListNames", 0).Store(&fd) - if err != nil { - panic(err) - } - for _, name := range fd { - if strings.HasPrefix(name, "org.mpris.MediaPlayer2") { - players = append(players, NewPlayer(conn, name)) - } - } - 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) - conn.Signal(c) - for v := range c { + players := &PlayerList{ + conn: conn, + } + players.Reload() + players.Sort() + 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) + c := make(chan *dbus.Signal, 10) + conn.Signal(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) { case string: - if strings.Contains(name, "org.mpris.MediaPlayer2") && name != "org.mpris.MediaPlayer2.Player" { - players = append(players, NewPlayer(conn, name)) - sort.Sort(players) + var pid uint32 + conn.BusObject().Call("org.freedesktop.DBus.GetConnectionUnixProcessID", 0, name).Store(&pid) + 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") { + players.Refresh() + players.Sort() + fmt.Println(players.JSON()) } - }() - conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, - "type='signal',path='/org/mpris/MediaPlayer2',interface='org.freedesktop.DBus.Properties'") - c := make(chan *dbus.Signal, 10) - conn.Signal(c) - fmt.Println(players[0].JSON()) - for range c { - sort.Sort(players) - players[0].Refresh() - fmt.Println(players[0].JSON()) + fmt.Println("New array", players) } } |