diff --git a/README.md b/README.md index e7363e0..2208e3c 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,62 @@ ## waybar-mpris +

+ bar gif +

-a custom waybar component for displaying info from MPRIS2 players. It automatically focuses on currently playing music players, and can easily be customized. +a waybar component/utility for displaying and controlling MPRIS2 compliant media players individually, inspired by [waybar-media](https://github.com/yurihs/waybar-media). + +MPRIS2 is widely supported, so this component should work with: +* Chrome/Chromium +* Other browsers (with kde plasma integration installed) +* VLC +* Spotify +* Noson +* mpd (with [mpDris2](https://github.com/eonpatapon/mpDris2)) +* Most other music/media players ## Install `go get github.com/hrfee/waybar-mpris` -or just grab the binary from here. +or just grab the `waybar-mpris` binary from here and place it in your PATH. ## Usage -When running, the program will pipe out json in waybar's format. Make a custom component in your configuration and set `return-type` to `json`, and `exec` to the path to the program. +When running, the program will pipe out json in waybar's format. Add something like this to your waybar `config.json`: +``` json +"custom/waybar-mpris": { + "return-type": "json", + "exec": "waybar-mpris", + "on-click": "waybar-mpris --send toggle", + // This option will switch between players on right click. + "on-click-right": "waybar-mpris --send player-next", + // The options below will switch the selected player on scroll + // "on-scroll-up": "waybar-mpris --send player-next", + // "on-scroll-down": "waybar-mpris --send player-prev", + // The options below will go to next/previous track on scroll + // "on-scroll-up": "waybar-mpris --send next", + // "on-scroll-down": "waybar-mpris --send prev", + "escape": true, +}, ``` -Usage of ./waybar-mpris: + + +``` +Usage of waybar-mpris: + --autofocus Auto switch to currently playing music players. --order string Element order. (default "SYMBOL:ARTIST:ALBUM:TITLE") - --pause string Pause symbol/text to use. (default "") + --pause string Pause symbol/text to use. (default "\uf8e3") --play string Play symbol/text to use. (default "▶") + --send string send command to already runnning waybar-mpris instance. (options: player-next/player-prev/next/prev/toggle) --separator string Separator string to use between artist, album, and title. (default " - ") ``` + * Modify the order of components with `--order`. `SYMBOL` is the play/paused icon or text, other options are self explanatory. * `--play/--pause` specify the symbols or text to display when music is paused/playing respectively. -* --separator specifies a string to separate the artist, album and title text. +* `--separator` specifies a string to separate the artist, album and title text. +* `--autofocus` makes waybar-mpris automatically focus on currently playing music players. +* `--send` sends commands to an already running waybar-mpris instance via a unix socket. Commands: + * `player-next`: Switch to displaying and controlling next available player. + * `player-prev`: Same as `player-next`, but for the previous player. + * `next/prev`: Next/previous track on the selected player. + * `toggle`: Play/pause. + * *Note: you can also bind these commands to keys in your sway/other wm config.* diff --git a/images/bar.gif b/images/bar.gif new file mode 100644 index 0000000..efc093e Binary files /dev/null and b/images/bar.gif differ diff --git a/images/bar.mkv b/images/bar.mkv new file mode 100644 index 0000000..3085bfd Binary files /dev/null and b/images/bar.mkv differ diff --git a/images/cropped.gif b/images/cropped.gif new file mode 100644 index 0000000..03b519c Binary files /dev/null and b/images/cropped.gif differ diff --git a/images/cropped.mkv b/images/cropped.mkv new file mode 100644 index 0000000..6d0c3e9 Binary files /dev/null and b/images/cropped.mkv differ diff --git a/images/cropped.png b/images/cropped.png new file mode 100644 index 0000000..e6d7361 Binary files /dev/null and b/images/cropped.png differ diff --git a/images/palette.png b/images/palette.png new file mode 100644 index 0000000..4b6927e Binary files /dev/null and b/images/palette.png differ diff --git a/main.go b/main.go index a6f7688..e018f15 100644 --- a/main.go +++ b/main.go @@ -3,11 +3,13 @@ package main import ( "encoding/json" "fmt" - "sort" - "strings" - "github.com/godbus/dbus/v5" flag "github.com/spf13/pflag" + "log" + "net" + "os" + "sort" + "strings" ) var knownPlayers = map[string]string{ @@ -30,13 +32,16 @@ const ( 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'" + SOCK = "/tmp/waybar-mpris.sock" ) var ( - PLAY = "▶" - PAUSE = "" - SEP = " - " - ORDER = "SYMBOL:ARTIST:ALBUM:TITLE" + PLAY = "▶" + PAUSE = "" + SEP = " - " + ORDER = "SYMBOL:ARTIST:ALBUM:TITLE" + AUTOFOCUS = false + COMMANDS = []string{"player-next", "player-prev", "next", "prev", "toggle"} ) // NewPlayer returns a new player object. @@ -135,7 +140,7 @@ func (p *Player) JSON() string { items = append(items, p.album) } } else if v == "TITLE" { - if p.album != "" { + if p.title != "" { items = append(items, p.title) } } @@ -173,8 +178,9 @@ func (p *Player) JSON() string { } type PlayerList struct { - list List - conn *dbus.Conn + list List + current uint + conn *dbus.Conn } type List []*Player @@ -200,6 +206,7 @@ func (ls List) Swap(i, j int) { // Doesn't retain order since sorting if constantly done anyway func (pl *PlayerList) Remove(fullName string) { + currentName := pl.list[pl.current].fullName var i int found := false for ind, p := range pl.list { @@ -212,6 +219,19 @@ func (pl *PlayerList) Remove(fullName string) { if found { 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.Println(pl.JSON()) + } } // ls[len(ls)-1], ls[i] = ls[i], ls[len(ls)-1] // ls = ls[:len(ls)-1] @@ -233,10 +253,14 @@ func (pl *PlayerList) Reload() error { func (pl *PlayerList) New(name string) { pl.list = append(pl.list, NewPlayer(pl.conn, name)) + if AUTOFOCUS { + pl.current = uint(len(pl.list) - 1) + } } func (pl *PlayerList) Sort() { sort.Sort(pl.list) + pl.current = 0 } func (pl *PlayerList) Refresh() { @@ -247,60 +271,135 @@ func (pl *PlayerList) Refresh() { func (pl *PlayerList) JSON() string { if len(pl.list) != 0 { - return pl.list[0].JSON() + return pl.list[pl.current].JSON() } return "{}" } +func (pl *PlayerList) Next() { + pl.list[pl.current].player.Call(INTERFACE+".Player.Next", 0) +} + +func (pl *PlayerList) Prev() { + pl.list[pl.current].player.Call(INTERFACE+".Player.Previous", 0) +} + +func (pl *PlayerList) Toggle() { + pl.list[pl.current].player.Call(INTERFACE+".Player.PlayPause", 0) +} + func main() { flag.StringVar(&PLAY, "play", PLAY, "Play symbol/text to use.") flag.StringVar(&PAUSE, "pause", PAUSE, "Pause symbol/text to use.") flag.StringVar(&SEP, "separator", SEP, "Separator string to use between artist, album, and title.") flag.StringVar(&ORDER, "order", ORDER, "Element order.") + flag.BoolVar(&AUTOFOCUS, "autofocus", AUTOFOCUS, "Auto switch to currently playing music players.") + var command string + flag.StringVar(&command, "send", "", "send command to already runnning waybar-mpris instance. (options: "+strings.Join(COMMANDS, "/")+")") flag.Parse() - conn, err := dbus.SessionBus() - if err != nil { - panic(err) - } - players := &PlayerList{ - conn: conn, - } - players.Reload() - players.Sort() - players.Refresh() - fmt.Println(players.JSON()) - lastLine := "" - // 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: - 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) + if command != "" { + conn, err := net.Dial("unix", SOCK) + if err != nil { + log.Fatalln("Couldn't dial:", err) + } + _, err = conn.Write([]byte(command)) + if err != nil { + log.Fatalln("Couldn't send command") + } + fmt.Println("Sent.") + } else { + conn, err := dbus.SessionBus() + if err != nil { + panic(err) + } + players := &PlayerList{ + conn: conn, + } + players.Reload() + players.Sort() + players.Refresh() + fmt.Println(players.JSON()) + lastLine := "" + // fmt.Println("New array", players) + go func() { + os.Remove(SOCK) + listener, err := net.Listen("unix", SOCK) + if err != nil { + log.Fatalln("Couldn't establish socket connection at", SOCK) + } + defer listener.Close() + for { + con, err := listener.Accept() + if err != nil { + log.Println("Couldn't accept:", err) + continue + } + buf := make([]byte, 512) + nr, err := con.Read(buf) + if err != nil { + log.Println("Couldn't read:", err) + continue + } + command := string(buf[0:nr]) + if command == "player-next" { + if players.current < uint(len(players.list)-1) { + players.current += 1 } else { - // fmt.Println("Adding", name) - players.New(name) + players.current = 0 } + players.Refresh() + fmt.Println(players.JSON()) + } else if command == "player-prev" { + if players.current != 0 { + players.current -= 1 + } else { + players.current = uint(len(players.list) - 1) + } + players.Refresh() + fmt.Println(players.JSON()) + } else if command == "next" { + players.Next() + } else if command == "prev" { + players.Prev() + } else if command == "toggle" { + players.Toggle() + } else { + fmt.Println("Invalid command") } } - } else if strings.Contains(v.Name, "PropertiesChanged") && strings.Contains(v.Body[0].(string), INTERFACE+".Player") { - players.Refresh() - players.Sort() - if l := players.JSON(); l != lastLine { - lastLine = l - fmt.Println(l) + }() + 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: + 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() + if AUTOFOCUS { + players.Sort() + } + if l := players.JSON(); l != lastLine { + lastLine = l + fmt.Println(l) + } } } - // fmt.Println("New array", players) } } diff --git a/waybar-mpris b/waybar-mpris index 31c553d..1492d17 100755 Binary files a/waybar-mpris and b/waybar-mpris differ