diff --git a/README.md b/README.md index 8258968..87f376a 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ a waybar component/utility for displaying and controlling MPRIS2 compliant media MPRIS2 is widely supported, so this component should work with: * Chrome/Chromium +* Firefox (Potentially, with `media.hardwaremediakeys.enabled = true` in about:config) * Other browsers (with kde plasma integration installed) * VLC * Spotify diff --git a/go.mod b/go.mod index b6cdd07..da66dc9 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,10 @@ module git.hrfee.pw/hrfee/waybar-mpris go 1.15 +replace git.hrfee.pw/hrfee/waybar-mpris/mpris2client => ./mpris2client + require ( + git.hrfee.pw/hrfee/waybar-mpris/mpris2client v0.0.0-00010101000000-000000000000 github.com/godbus/dbus/v5 v5.0.3 github.com/hpcloud/tail v1.0.0 github.com/spf13/pflag v1.0.5 diff --git a/main.go b/main.go index 60fa589..ab103d6 100644 --- a/main.go +++ b/main.go @@ -4,55 +4,25 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "log" "net" "os" "os/signal" - "sort" - "strconv" "strings" "time" + mpris2 "git.hrfee.pw/hrfee/waybar-mpris/mpris2client" "github.com/godbus/dbus/v5" "github.com/hpcloud/tail" flag "github.com/spf13/pflag" ) -var knownPlayers = map[string]string{ - "plasma-browser-integration": "Browser", - "noson": "Noson", -} - -var knownBrowsers = map[string]string{ - "mozilla": "Firefox", - "chrome": "Chrome", - "chromium": "Chromium", -} - -// Player represents an active music player. -type Player struct { - player dbus.BusObject - fullName, name, title, artist, album string - position int64 - pid uint32 - playing, stopped bool - metadata map[string]dbus.Variant - conn *dbus.Conn -} - // Various paths and values to use elsewhere. const ( - INTERFACE = "org.mpris.MediaPlayer2" - PATH = "/org/mpris/MediaPlayer2" - // For the NameOwnerChanged signal. - MATCH_NOC = "type='signal',path='/org/freedesktop/DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'" - // For the PropertiesChanged signal. It doesn't match exactly (couldn't get that to work) so we check it manually. - MATCH_PC = "type='signal',path='/org/mpris/MediaPlayer2',interface='org.freedesktop.DBus.Properties'" - 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" + POLL = 1 ) // Mostly default values for flag options. @@ -70,144 +40,18 @@ var ( WRITER io.Writer = os.Stdout ) -// NewPlayer returns a new player object. -func NewPlayer(conn *dbus.Conn, name string) (p *Player) { - playerName := strings.ReplaceAll(name, INTERFACE+".", "") - var pid uint32 - conn.BusObject().Call("org.freedesktop.DBus.GetConnectionUnixProcessID", 0, name).Store(&pid) - for key, val := range knownPlayers { - if strings.Contains(name, key) { - playerName = val - break - } - } - if playerName == "Browser" { - file, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/cmdline", pid)) - if err == nil { - cmd := string(file) - for key, val := range knownBrowsers { - if strings.Contains(cmd, key) { - playerName = val - break - } - } - } - } - p = &Player{ - player: conn.Object(name, PATH), - conn: conn, - name: playerName, - fullName: name, - pid: pid, - } - p.Refresh() - return -} - -// Refresh grabs playback info. -func (p *Player) Refresh() (err error) { - val, err := p.player.GetProperty(INTERFACE + ".Player.PlaybackStatus") - if err != nil { - p.playing = false - p.stopped = false - p.metadata = map[string]dbus.Variant{} - p.title = "" - p.artist = "" - p.album = "" - return - } - strVal := val.String() - if strings.Contains(strVal, "Playing") { - p.playing = true - p.stopped = false - } else if strings.Contains(strVal, "Paused") { - p.playing = false - p.stopped = false - } else { - p.playing = false - p.stopped = true - } - 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 = 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 µsToString(µs int64) string { - seconds := int(µs / 1e6) - minutes := int(seconds / 60) - seconds -= minutes * 60 - return fmt.Sprintf("%02d:%02d", minutes, seconds) -} - -// Position figures out the track position in MM:SS, interpolating the value if necessary. -func (p *Player) Position() string { - // position is in microseconds so we prob need int64 to be safe - v := p.metadata["mpris:length"].Value() - var l int64 - if v != nil { - l = v.(int64) - } else { - return "" - } - length := µsToString(l) - if length == "" { - return "" - } - pos, err := p.player.GetProperty(INTERFACE + ".Player.Position") - if err != nil { - return "" - } - position := µsToString(pos.Value().(int64)) - if position == "" { - return "" - } - if INTERPOLATE && position == µsToString(p.position) { - np := p.position + int64(POLL*1000000) - position = µsToString(np) - } - p.position = pos.Value().(int64) - return position + "/" + length -} - // JSON returns json for waybar to consume. -func (p *Player) JSON() string { +func playerJSON(p *mpris2.Player) string { data := map[string]string{} symbol := PLAY data["class"] = "paused" - if p.playing { + if p.Playing { symbol = PAUSE data["class"] = "playing" } var pos string if SHOW_POS { - pos = p.Position() + pos = p.StringPosition() if pos != "" { pos = "(" + pos + ")" } @@ -219,16 +63,16 @@ func (p *Player) JSON() string { case "SYMBOL": items = append(items, symbol) case "ARTIST": - if p.artist != "" { - items = append(items, p.artist) + if p.Artist != "" { + items = append(items, p.Artist) } case "ALBUM": - if p.album != "" { - items = append(items, p.album) + if p.Album != "" { + items = append(items, p.Album) } case "TITLE": - if p.title != "" { - items = append(items, p.title) + if p.Title != "" { + items = append(items, p.Title) } case "POSITION": if pos != "" && SHOW_POS { @@ -254,12 +98,12 @@ func (p *Player) JSON() string { data["tooltip"] = fmt.Sprintf( "%s\nby %s\n", - strings.ReplaceAll(p.title, "&", "&"), - strings.ReplaceAll(p.artist, "&", "&")) - if p.album != "" { - data["tooltip"] += "from " + strings.ReplaceAll(p.album, "&", "&") + "\n" + strings.ReplaceAll(p.Title, "&", "&"), + strings.ReplaceAll(p.Artist, "&", "&")) + if p.Album != "" { + data["tooltip"] += "from " + strings.ReplaceAll(p.Album, "&", "&") + "\n" } - data["tooltip"] += "(" + p.name + ")" + data["tooltip"] += "(" + p.Name + ")" data["text"] = text out, err := json.Marshal(data) if err != nil { @@ -268,115 +112,22 @@ func (p *Player) JSON() string { return string(out) } -type availablePlayers struct { - list playerArray - current uint - conn *dbus.Conn +type players struct { + mpris2 *mpris2.Mpris2 } -type playerArray []*Player - -func (ls playerArray) Len() int { - return len(ls) -} - -func (ls playerArray) 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 order - return states[0] > states[1] -} - -func (ls playerArray) Swap(i, j int) { - ls[i], ls[j] = ls[j], ls[i] -} - -func (pl *availablePlayers) Remove(fullName string) { - currentName := pl.list[pl.current].fullName - var i int - found := false - for ind, p := range pl.list { - if p.fullName == fullName { - i = ind - found = true - break - } - } - if !found { - return - } - pl.list[0], pl.list[i] = pl.list[i], pl.list[0] - pl.list = pl.list[1:] - found = false - for ind, p := range pl.list { - if p.fullName == currentName { - pl.current = uint(ind) - found = true - break - } - } - if !found { - pl.current = 0 - pl.Refresh() - fmt.Fprintln(WRITER, pl.JSON()) - } -} - -func (pl *availablePlayers) 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 { - // Don't add playerctld, it just duplicates other players - if strings.HasPrefix(name, INTERFACE) && !strings.Contains(name, "playerctld") { - pl.New(name) - } - } - return nil -} - -func (pl *availablePlayers) New(name string) { - pl.list = append(pl.list, NewPlayer(pl.conn, name)) - if AUTOFOCUS { - pl.current = uint(len(pl.list) - 1) - } -} - -func (pl *availablePlayers) Sort() { - sort.Sort(pl.list) - pl.current = 0 -} - -func (pl *availablePlayers) Refresh() { - for i := range pl.list { - pl.list[i].Refresh() - } -} - -func (pl *availablePlayers) JSON() string { - if len(pl.list) != 0 { - return pl.list[pl.current].JSON() +func (pl *players) JSON() string { + if len(pl.mpris2.List) != 0 { + return playerJSON(pl.mpris2.List[pl.mpris2.Current]) } return "{}" } -func (pl *availablePlayers) Next() { - pl.list[pl.current].player.Call(INTERFACE+".Player.Next", 0) -} +func (pl *players) Next() { pl.mpris2.List[pl.mpris2.Current].Next() } -func (pl *availablePlayers) Prev() { - pl.list[pl.current].player.Call(INTERFACE+".Player.Previous", 0) -} +func (pl *players) Prev() { pl.mpris2.List[pl.mpris2.Current].Previous() } -func (pl *availablePlayers) Toggle() { - pl.list[pl.current].player.Call(INTERFACE+".Player.PlayPause", 0) -} +func (pl *players) Toggle() { pl.mpris2.List[pl.mpris2.Current].Toggle() } func main() { logfile, err := os.OpenFile(LOGFILE, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666) @@ -421,18 +172,6 @@ func main() { } os.Exit(0) } - conn, err := dbus.SessionBus() - if err != nil { - log.Fatalln("Error connecting to DBus:", err) - } - players := &availablePlayers{ - conn: conn, - } - players.Reload() - players.Sort() - players.Refresh() - fmt.Fprintln(WRITER, players.JSON()) - lastLine := "" // fmt.Println("New array", players) // Start command listener if _, err := os.Stat(SOCK); err == nil { @@ -486,6 +225,16 @@ func main() { os.Remove(OUTFILE) } } + conn, err := dbus.SessionBus() + if err != nil { + log.Fatalln("Error connecting to DBus:", err) + } + players := &players{ + mpris2: mpris2.NewMpris2(conn, INTERPOLATE, POLL, AUTOFOCUS), + } + players.mpris2.Reload() + players.mpris2.Sort() + lastLine := "" go func() { listener, err := net.Listen("unix", SOCK) c := make(chan os.Signal, 1) @@ -518,26 +267,24 @@ func main() { command := string(buf[0:nr]) switch command { case "player-next": - length := len(players.list) + length := len(players.mpris2.List) if length != 1 { - if players.current < uint(length-1) { - players.current++ + if players.mpris2.Current < uint(length-1) { + players.mpris2.Current++ } else { - players.current = 0 + players.mpris2.Current = 0 } - players.Refresh() - fmt.Fprintln(WRITER, players.JSON()) + players.mpris2.Refresh() } case "player-prev": - length := len(players.list) + length := len(players.mpris2.List) if length != 1 { - if players.current != 0 { - players.current-- + if players.mpris2.Current != 0 { + players.mpris2.Current-- } else { - players.current = uint(length - 1) + players.mpris2.Current = uint(length - 1) } - players.Refresh() - fmt.Fprintln(WRITER, players.JSON()) + players.mpris2.Refresh() } case "next": players.Next() @@ -546,21 +293,7 @@ func main() { case "toggle": players.Toggle() case "list": - resp := "" - pad := 0 - i := len(players.list) - for i != 0 { - i /= 10 - pad++ - } - for i, p := range players.list { - symbol := "" - if uint(i) == players.current { - symbol = "*" - } - resp += fmt.Sprintf("%0"+strconv.Itoa(pad)+"d%s: Name: %s; Playing: %t; PID: %d\n", i, symbol, p.fullName, p.playing, p.pid) - } - con.Write([]byte(resp)) + con.Write([]byte(players.mpris2.String())) case "share": out, err := os.Create(OUTFILE) if err != nil { @@ -573,45 +306,24 @@ func main() { } } }() - - conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, MATCH_NOC) - conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, MATCH_PC) + go players.mpris2.Listen() if SHOW_POS { go func() { for { time.Sleep(POLL * time.Second) - if len(players.list) != 0 { - if players.list[players.current].playing { + if len(players.mpris2.List) != 0 { + if players.mpris2.List[players.mpris2.Current].Playing { go fmt.Fprintln(WRITER, players.JSON()) } } } }() } - 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: - var pid uint32 - conn.BusObject().Call("org.freedesktop.DBus.GetConnectionUnixProcessID", 0, name).Store(&pid) - // Ignore playerctld again - if strings.Contains(name, INTERFACE) && !strings.Contains(name, "playerctld") { - 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() + fmt.Fprintln(WRITER, players.JSON()) + for v := range players.mpris2.Messages { + if v.Name == "refresh" { if AUTOFOCUS { - players.Sort() + players.mpris2.Sort() } if l := players.JSON(); l != lastLine { lastLine = l @@ -619,4 +331,5 @@ func main() { } } } + players.mpris2.Refresh() } diff --git a/mpris2client/go.mod b/mpris2client/go.mod new file mode 100644 index 0000000..75c202d --- /dev/null +++ b/mpris2client/go.mod @@ -0,0 +1,5 @@ +module git.hrfee.pw/hrfee/waybar-mpris/mpris2client + +go 1.15 + +require github.com/godbus/dbus/v5 v5.0.3 // indirect diff --git a/mpris2client/go.sum b/mpris2client/go.sum new file mode 100644 index 0000000..4eefd05 --- /dev/null +++ b/mpris2client/go.sum @@ -0,0 +1,3 @@ +github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4= +github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME= +github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= diff --git a/mpris2client/mpris2.go b/mpris2client/mpris2.go new file mode 100644 index 0000000..8565bd5 --- /dev/null +++ b/mpris2client/mpris2.go @@ -0,0 +1,355 @@ +package mpris2client + +import ( + "fmt" + "io/ioutil" + "sort" + "strconv" + "strings" + + "github.com/godbus/dbus/v5" +) + +// Various paths and values to use elsewhere. +const ( + INTERFACE = "org.mpris.MediaPlayer2" + PATH = "/org/mpris/MediaPlayer2" + // For the NameOwnerChanged signal. + MATCH_NOC = "type='signal',path='/org/freedesktop/DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'" + // For the PropertiesChanged signal. It doesn't match exactly (couldn't get that to work) so we check it manually. + MATCH_PC = "type='signal',path='/org/mpris/MediaPlayer2',interface='org.freedesktop.DBus.Properties'" + Refresh = "refresh" +) + +var knownPlayers = map[string]string{ + "plasma-browser-integration": "Browser", + "noson": "Noson", +} + +var knownBrowsers = map[string]string{ + "mozilla": "Firefox", + "chrome": "Chrome", + "chromium": "Chromium", +} + +// Player represents an active media player. +type Player struct { + Player dbus.BusObject + FullName, Name, Title, Artist, AlbumArtist, Album string + Position int64 + pid uint32 + Playing, Stopped bool + metadata map[string]dbus.Variant + conn *dbus.Conn + poll int + interpolate bool +} + +// NewPlayer returns a new player object. +func NewPlayer(conn *dbus.Conn, name string, interpolate bool, poll int) (p *Player) { + playerName := strings.ReplaceAll(name, INTERFACE+".", "") + var pid uint32 + conn.BusObject().Call("org.freedesktop.DBus.GetConnectionUnixProcessID", 0, name).Store(&pid) + for key, val := range knownPlayers { + if strings.Contains(name, key) { + playerName = val + break + } + } + if playerName == "Browser" { + file, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/cmdline", pid)) + if err == nil { + cmd := string(file) + for key, val := range knownBrowsers { + if strings.Contains(cmd, key) { + playerName = val + break + } + } + } + } + p = &Player{ + Player: conn.Object(name, PATH), + conn: conn, + Name: playerName, + FullName: name, + pid: pid, + interpolate: interpolate, + poll: poll, + } + p.Refresh() + return +} + +func (p *Player) String() string { + return fmt.Sprintf("Name: %s; Playing: %t; PID: %d", p.FullName, p.Playing, p.pid) +} + +// Refresh grabs playback info. +func (p *Player) Refresh() (err error) { + val, err := p.Player.GetProperty(INTERFACE + ".Player.PlaybackStatus") + if err != nil { + p.Playing = false + p.Stopped = false + p.metadata = map[string]dbus.Variant{} + p.Title = "" + p.Artist = "" + p.AlbumArtist = "" + p.Album = "" + return + } + strVal := val.String() + if strings.Contains(strVal, "Playing") { + p.Playing = true + p.Stopped = false + } else if strings.Contains(strVal, "Paused") { + p.Playing = false + p.Stopped = false + } else { + p.Playing = false + p.Stopped = true + } + metadata, err := p.Player.GetProperty(INTERFACE + ".Player.Metadata") + if err != nil { + p.metadata = map[string]dbus.Variant{} + p.Title = "" + p.Artist = "" + p.AlbumArtist = "" + p.Album = "" + return + } + 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 albumArtist := p.metadata["xesam:albumArtist"].Value().(type) { + case []string: + p.AlbumArtist = strings.Join(albumArtist, ", ") + case string: + p.AlbumArtist = albumArtist + default: + p.AlbumArtist = "" + } + 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 µsToString(µs int64) string { + seconds := int(µs / 1e6) + minutes := int(seconds / 60) + seconds -= minutes * 60 + return fmt.Sprintf("%02d:%02d", minutes, seconds) +} + +// StringPosition figures out the track position in MM:SS/MM:SS, interpolating the value if necessary. +func (p *Player) StringPosition() string { + // position is in microseconds so we prob need int64 to be safe + v := p.metadata["mpris:length"].Value() + var l int64 + if v != nil { + l = v.(int64) + } else { + return "" + } + length := µsToString(l) + if length == "" { + return "" + } + pos, err := p.Player.GetProperty(INTERFACE + ".Player.Position") + if err != nil { + return "" + } + position := µsToString(pos.Value().(int64)) + if position == "" { + return "" + } + if p.interpolate && position == µsToString(p.Position) { + np := p.Position + int64(p.poll*1e6) + position = µsToString(np) + } + p.Position = pos.Value().(int64) + return position + "/" + length +} + +// Next requests the next track. +func (p *Player) Next() { p.Player.Call(INTERFACE+".Player.Next", 0) } + +// Previous requests the previous track. +func (p *Player) Previous() { p.Player.Call(INTERFACE+".Player.Previous", 0) } + +// Toggle requests play/pause +func (p *Player) Toggle() { p.Player.Call(INTERFACE+".Player.PlayPause", 0) } + +type Message struct { + Name, Value string +} + +type PlayerArray []*Player + +func (ls PlayerArray) Len() int { + return len(ls) +} + +func (ls PlayerArray) 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 order + return states[0] > states[1] +} + +func (ls PlayerArray) Swap(i, j int) { + ls[i], ls[j] = ls[j], ls[i] +} + +type Mpris2 struct { + List PlayerArray + Current uint + conn *dbus.Conn + Messages chan Message + interpolate bool + poll int + autofocus bool +} + +func NewMpris2(conn *dbus.Conn, interpolate bool, poll int, autofocus bool) *Mpris2 { + return &Mpris2{ + List: PlayerArray{}, + Current: 0, + conn: conn, + Messages: make(chan Message), + interpolate: interpolate, + poll: poll, + } +} + +// Listen should be run as a Goroutine. When players become available or are removed, an mpris2.Message is sent on mpris2.Mpris2.Messages with Name "add"/"remove" and Value as the player name. When a players state changes, a message is sent on mpris2.Mpris2.Messages with Name "refresh". +func (pl *Mpris2) Listen() { + c := make(chan *dbus.Signal, 10) + pl.conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, MATCH_NOC) + pl.conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, MATCH_PC) + pl.conn.Signal(c) + for v := range c { + if strings.Contains(v.Name, "NameOwnerChanged") { + switch name := v.Body[0].(type) { + case string: + var pid uint32 + pl.conn.BusObject().Call("org.freedesktop.DBus.GetConnectionUnixProcessID", 0, name).Store(&pid) + // Ignore playerctld again + if strings.Contains(name, INTERFACE) && !strings.Contains(name, "playerctld") { + if pid == 0 { + pl.Remove(name) + pl.Messages <- Message{Name: "remove", Value: name} + } else { + pl.New(name) + pl.Messages <- Message{Name: "add", Value: name} + } + } + } + } else if strings.Contains(v.Name, "PropertiesChanged") && strings.Contains(v.Body[0].(string), INTERFACE+".Player") { + pl.Refresh() + } + } +} + +func (pl *Mpris2) Remove(fullName string) { + currentName := pl.List[pl.Current].FullName + var i int + found := false + for ind, p := range pl.List { + if p.FullName == fullName { + i = ind + found = true + break + } + } + if !found { + return + } + pl.List[0], pl.List[i] = pl.List[i], pl.List[0] + pl.List = pl.List[1:] + found = false + for ind, p := range pl.List { + if p.FullName == currentName { + pl.Current = uint(ind) + found = true + break + } + } + if !found { + pl.Current = 0 + pl.Refresh() + //fmt.Fprintln(WRITER, pl.JSON()) + } +} + +func (pl *Mpris2) 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 { + // Don't add playerctld, it just duplicates other players + if strings.HasPrefix(name, INTERFACE) && !strings.Contains(name, "playerctld") { + pl.New(name) + } + } + return nil +} + +func (pl *Mpris2) String() string { + resp := "" + pad := 0 + i := len(pl.List) + for i != 0 { + i /= 10 + pad++ + } + for i, p := range pl.List { + symbol := "" + if uint(i) == pl.Current { + symbol = "*" + } + resp += fmt.Sprintf("%0"+strconv.Itoa(pad)+"d", i) + symbol + ": " + p.String() + "\n" + } + return resp +} + +func (pl *Mpris2) New(name string) { + pl.List = append(pl.List, NewPlayer(pl.conn, name, pl.interpolate, pl.poll)) + if pl.autofocus { + pl.Current = uint(len(pl.List) - 1) + } +} + +func (pl *Mpris2) Sort() { + sort.Sort(pl.List) + pl.Current = 0 +} + +func (pl *Mpris2) Refresh() { + for i := range pl.List { + pl.List[i].Refresh() + } + pl.Messages <- Message{Name: "refresh", Value: ""} +} diff --git a/waybar-mpris b/waybar-mpris index 7429a6b..075703f 100755 Binary files a/waybar-mpris and b/waybar-mpris differ