2020-08-21 13:54:32 +00:00
package main
import (
"encoding/json"
"fmt"
2020-08-31 19:37:23 +00:00
"io"
2020-08-22 18:01:51 +00:00
"log"
"net"
"os"
2020-09-06 12:13:36 +00:00
"os/signal"
2020-08-22 18:01:51 +00:00
"strings"
2020-08-26 21:10:36 +00:00
"time"
2020-08-25 13:40:56 +00:00
"github.com/godbus/dbus/v5"
2020-09-28 22:48:51 +00:00
"github.com/hpcloud/tail"
2021-01-08 00:54:34 +00:00
mpris2 "github.com/hrfee/mpris2client"
2020-08-25 13:40:56 +00:00
flag "github.com/spf13/pflag"
2020-08-21 13:54:32 +00:00
)
2020-12-15 20:44:03 +00:00
// Various paths and values to use elsewhere.
2020-08-22 13:39:23 +00:00
const (
2021-01-07 16:10:20 +00:00
SOCK = "/tmp/waybar-mpris.sock"
LOGFILE = "/tmp/waybar-mpris.log"
OUTFILE = "/tmp/waybar-mpris.out"
POLL = 1
2020-08-22 13:39:23 +00:00
)
2020-12-15 20:44:03 +00:00
// Mostly default values for flag options.
2020-08-22 14:40:24 +00:00
var (
2020-12-15 20:44:03 +00:00
PLAY = "▶"
PAUSE = ""
SEP = " - "
ORDER = "SYMBOL:ARTIST:ALBUM:TITLE:POSITION"
AUTOFOCUS = false
// Available commands that can be sent to running instances.
2020-09-28 22:48:51 +00:00
COMMANDS = [ ] string { "player-next" , "player-prev" , "next" , "prev" , "toggle" , "list" }
SHOW_POS = false
INTERPOLATE = false
REPLACE = false
WRITER io . Writer = os . Stdout
2020-08-22 14:40:24 +00:00
)
2020-12-15 20:44:03 +00:00
// JSON returns json for waybar to consume.
2021-01-07 16:10:20 +00:00
func playerJSON ( p * mpris2 . Player ) string {
2020-08-22 14:40:24 +00:00
data := map [ string ] string { }
symbol := PLAY
data [ "class" ] = "paused"
2021-01-07 16:10:20 +00:00
if p . Playing {
2020-08-22 14:40:24 +00:00
symbol = PAUSE
data [ "class" ] = "playing"
}
2020-08-26 21:10:36 +00:00
var pos string
if SHOW_POS {
2021-01-07 16:10:20 +00:00
pos = p . StringPosition ( )
2020-08-26 21:10:36 +00:00
if pos != "" {
pos = "(" + pos + ")"
}
}
2020-08-22 13:39:23 +00:00
var items [ ] string
2020-08-22 14:40:24 +00:00
order := strings . Split ( ORDER , ":" )
for _ , v := range order {
2020-12-15 20:44:03 +00:00
switch v {
case "SYMBOL" :
2020-08-22 14:40:24 +00:00
items = append ( items , symbol )
2020-12-15 20:44:03 +00:00
case "ARTIST" :
2021-01-07 16:10:20 +00:00
if p . Artist != "" {
items = append ( items , p . Artist )
2020-08-22 14:40:24 +00:00
}
2020-12-15 20:44:03 +00:00
case "ALBUM" :
2021-01-07 16:10:20 +00:00
if p . Album != "" {
items = append ( items , p . Album )
2020-08-22 14:40:24 +00:00
}
2020-12-15 20:44:03 +00:00
case "TITLE" :
2021-01-07 16:10:20 +00:00
if p . Title != "" {
items = append ( items , p . Title )
2020-08-22 14:40:24 +00:00
}
2020-12-15 20:44:03 +00:00
case "POSITION" :
if pos != "" && SHOW_POS {
2020-08-26 21:10:36 +00:00
items = append ( items , pos )
}
2020-08-22 13:39:23 +00:00
}
}
if len ( items ) == 0 {
return "{}"
}
2020-08-22 14:40:24 +00:00
text := ""
for i , v := range items {
right := ""
2020-08-26 21:10:36 +00:00
if ( v == symbol || v == pos ) && i != len ( items ) - 1 {
2020-08-22 14:40:24 +00:00
right = " "
2020-08-26 21:10:36 +00:00
} else if i != len ( items ) - 1 && items [ i + 1 ] != symbol && items [ i + 1 ] != pos {
2020-08-22 14:40:24 +00:00
right = SEP
} else {
right = " "
}
text += v + right
}
2020-08-21 13:54:32 +00:00
data [ "tooltip" ] = fmt . Sprintf (
2020-08-22 13:39:23 +00:00
"%s\nby %s\n" ,
2021-01-07 16:10:20 +00:00
strings . ReplaceAll ( p . Title , "&" , "&" ) ,
strings . ReplaceAll ( p . Artist , "&" , "&" ) )
if p . Album != "" {
data [ "tooltip" ] += "from " + strings . ReplaceAll ( p . Album , "&" , "&" ) + "\n"
2020-08-22 13:39:23 +00:00
}
2021-01-07 16:10:20 +00:00
data [ "tooltip" ] += "(" + p . Name + ")"
2020-08-22 14:40:24 +00:00
data [ "text" ] = text
out , err := json . Marshal ( data )
2020-08-22 13:39:23 +00:00
if err != nil {
return "{}"
}
2020-08-22 14:40:24 +00:00
return string ( out )
2020-08-21 13:54:32 +00:00
}
2021-01-07 16:10:20 +00:00
type players struct {
mpris2 * mpris2 . Mpris2
2020-08-22 13:39:23 +00:00
}
2021-01-07 16:10:20 +00:00
func ( pl * players ) JSON ( ) string {
if len ( pl . mpris2 . List ) != 0 {
return playerJSON ( pl . mpris2 . List [ pl . mpris2 . Current ] )
2020-08-22 13:39:23 +00:00
}
return "{}"
2020-08-21 13:54:32 +00:00
}
2021-01-07 16:10:20 +00:00
func ( pl * players ) Next ( ) { pl . mpris2 . List [ pl . mpris2 . Current ] . Next ( ) }
2020-08-22 18:01:51 +00:00
2021-01-07 16:10:20 +00:00
func ( pl * players ) Prev ( ) { pl . mpris2 . List [ pl . mpris2 . Current ] . Previous ( ) }
2020-08-22 18:01:51 +00:00
2021-01-07 16:10:20 +00:00
func ( pl * players ) Toggle ( ) { pl . mpris2 . List [ pl . mpris2 . Current ] . Toggle ( ) }
2020-08-22 18:01:51 +00:00
2020-08-21 13:54:32 +00:00
func main ( ) {
2020-08-31 19:37:23 +00:00
logfile , err := os . OpenFile ( LOGFILE , os . O_CREATE | os . O_APPEND | os . O_RDWR , 0666 )
if err != nil {
log . Fatalf ( "Couldn't open %s for writing: %s" , LOGFILE , err )
}
mw := io . MultiWriter ( logfile , os . Stdout )
log . SetOutput ( mw )
2020-08-22 14:40:24 +00:00
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." )
2020-08-22 18:01:51 +00:00
flag . BoolVar ( & AUTOFOCUS , "autofocus" , AUTOFOCUS , "Auto switch to currently playing music players." )
2020-08-26 21:10:36 +00:00
flag . BoolVar ( & SHOW_POS , "position" , SHOW_POS , "Show current position between brackets, e.g (04:50/05:00)" )
2020-09-14 15:14:30 +00:00
flag . BoolVar ( & INTERPOLATE , "interpolate" , INTERPOLATE , "Interpolate track position (helpful for players that don't update regularly, e.g mpDris2)" )
2020-09-28 22:48:51 +00:00
flag . BoolVar ( & REPLACE , "replace" , REPLACE , "replace existing waybar-mpris if found. When false, new instance will clone the original instances output." )
2020-08-22 18:01:51 +00:00
var command string
flag . StringVar ( & command , "send" , "" , "send command to already runnning waybar-mpris instance. (options: " + strings . Join ( COMMANDS , "/" ) + ")" )
2020-09-28 22:48:51 +00:00
2020-08-22 14:40:24 +00:00
flag . Parse ( )
2020-11-16 21:59:31 +00:00
os . Stderr = logfile
2020-08-22 14:40:24 +00:00
2020-08-22 18:01:51 +00:00
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." )
2020-08-27 19:26:01 +00:00
if command == "list" {
buf := make ( [ ] byte , 512 )
nr , err := conn . Read ( buf )
if err != nil {
2020-09-28 22:48:51 +00:00
log . Fatalln ( "Couldn't read response." )
2020-08-27 19:26:01 +00:00
}
response := string ( buf [ 0 : nr ] )
fmt . Println ( "Response:" )
fmt . Printf ( response )
}
2020-08-31 19:14:14 +00:00
os . Exit ( 0 )
}
// fmt.Println("New array", players)
// Start command listener
if _ , err := os . Stat ( SOCK ) ; err == nil {
2020-09-28 22:48:51 +00:00
if REPLACE {
fmt . Printf ( "Socket %s already exists, this could mean waybar-mpris is already running.\nStarting this instance will overwrite the file, possibly stopping other instances from accepting commands.\n" , SOCK )
var input string
ignoreChoice := false
fmt . Printf ( "Continue? [y/n]: " )
go func ( ) {
fmt . Scanln ( & input )
if strings . Contains ( input , "y" ) && ! ignoreChoice {
os . Remove ( SOCK )
}
} ( )
time . Sleep ( 5 * time . Second )
if input == "" {
fmt . Printf ( "\nRemoving due to lack of input.\n" )
ignoreChoice = true
// os.Remove(SOCK)
2020-08-31 19:14:14 +00:00
}
2020-09-28 22:48:51 +00:00
} else if conn , err := net . Dial ( "unix" , SOCK ) ; err == nil {
// When waybar-mpris is already running, we attach to its output instead of launching a whole new instance.
2020-11-26 19:11:38 +00:00
// Print to stderr to avoid errors from waybar
os . Stderr . WriteString ( "waybar-mpris is already running. This instance will clone its output." )
2020-09-28 22:48:51 +00:00
if err != nil {
log . Fatalln ( "Couldn't dial:" , err )
}
_ , err = conn . Write ( [ ] byte ( "share" ) )
if err != nil {
log . Fatalln ( "Couldn't send command" )
}
buf := make ( [ ] byte , 512 )
nr , err := conn . Read ( buf )
if err != nil {
log . Fatalln ( "Couldn't read response." )
}
if string ( buf [ 0 : nr ] ) == "success" {
t , err := tail . TailFile ( OUTFILE , tail . Config {
Follow : true ,
MustExist : true ,
Logger : tail . DiscardingLogger ,
} )
if err == nil {
for line := range t . Lines {
fmt . Println ( line . Text )
}
}
}
} else {
2020-08-31 19:14:14 +00:00
os . Remove ( SOCK )
2020-09-28 22:48:51 +00:00
os . Remove ( OUTFILE )
2020-08-22 18:01:51 +00:00
}
2020-08-31 19:14:14 +00:00
}
2021-01-07 16:10:20 +00:00
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 := ""
2020-08-31 19:14:14 +00:00
go func ( ) {
listener , err := net . Listen ( "unix" , SOCK )
2020-09-06 12:13:36 +00:00
c := make ( chan os . Signal , 1 )
signal . Notify ( c , os . Interrupt )
go func ( ) {
<- c
2020-09-28 22:48:51 +00:00
os . Remove ( OUTFILE )
2020-09-06 12:13:36 +00:00
os . Remove ( SOCK )
os . Exit ( 1 )
} ( )
2020-08-31 19:14:14 +00:00
if err != nil {
2020-09-28 22:48:51 +00:00
log . Fatalf ( "Couldn't establish socket connection at %s (error %s)\n" , SOCK , err )
2020-08-22 18:01:51 +00:00
}
2020-08-31 19:14:14 +00:00
defer func ( ) {
listener . Close ( )
2020-08-22 18:01:51 +00:00
os . Remove ( SOCK )
2020-08-31 19:14:14 +00:00
} ( )
for {
con , err := listener . Accept ( )
2020-08-22 18:01:51 +00:00
if err != nil {
2020-08-31 19:14:14 +00:00
log . Println ( "Couldn't accept:" , err )
continue
2020-08-22 18:01:51 +00:00
}
2020-08-31 19:14:14 +00:00
buf := make ( [ ] byte , 512 )
nr , err := con . Read ( buf )
if err != nil {
log . Println ( "Couldn't read:" , err )
continue
}
command := string ( buf [ 0 : nr ] )
2020-09-28 22:48:51 +00:00
switch command {
case "player-next" :
2021-01-07 16:10:20 +00:00
length := len ( players . mpris2 . List )
2020-08-31 19:14:14 +00:00
if length != 1 {
2021-01-07 16:10:20 +00:00
if players . mpris2 . Current < uint ( length - 1 ) {
players . mpris2 . Current ++
2020-08-31 19:14:14 +00:00
} else {
2021-01-07 16:10:20 +00:00
players . mpris2 . Current = 0
2020-08-22 13:39:23 +00:00
}
2021-01-07 16:10:20 +00:00
players . mpris2 . Refresh ( )
2020-08-31 19:14:14 +00:00
}
2020-09-28 22:48:51 +00:00
case "player-prev" :
2021-01-07 16:10:20 +00:00
length := len ( players . mpris2 . List )
2020-08-31 19:14:14 +00:00
if length != 1 {
2021-01-07 16:10:20 +00:00
if players . mpris2 . Current != 0 {
players . mpris2 . Current --
2020-08-31 19:14:14 +00:00
} else {
2021-01-07 16:10:20 +00:00
players . mpris2 . Current = uint ( length - 1 )
2020-08-27 19:26:01 +00:00
}
2021-01-07 16:10:20 +00:00
players . mpris2 . Refresh ( )
2020-08-31 19:14:14 +00:00
}
2020-09-28 22:48:51 +00:00
case "next" :
2020-08-31 19:14:14 +00:00
players . Next ( )
2020-09-28 22:48:51 +00:00
case "prev" :
2020-08-31 19:14:14 +00:00
players . Prev ( )
2020-09-28 22:48:51 +00:00
case "toggle" :
2020-08-31 19:14:14 +00:00
players . Toggle ( )
2020-09-28 22:48:51 +00:00
case "list" :
2021-01-07 16:10:20 +00:00
con . Write ( [ ] byte ( players . mpris2 . String ( ) ) )
2020-09-28 22:48:51 +00:00
case "share" :
out , err := os . Create ( OUTFILE )
if err != nil {
con . Write ( [ ] byte ( fmt . Sprintf ( "Failed: %s" , err ) ) )
}
WRITER = io . MultiWriter ( os . Stdout , out )
con . Write ( [ ] byte ( "success" ) )
default :
2020-08-31 19:14:14 +00:00
fmt . Println ( "Invalid command" )
2020-08-21 14:44:59 +00:00
}
2020-08-31 19:14:14 +00:00
}
} ( )
2021-01-07 16:10:20 +00:00
go players . mpris2 . Listen ( )
2020-08-31 19:14:14 +00:00
if SHOW_POS {
go func ( ) {
for {
2020-09-14 15:14:30 +00:00
time . Sleep ( POLL * time . Second )
2021-01-07 16:10:20 +00:00
if len ( players . mpris2 . List ) != 0 {
if players . mpris2 . List [ players . mpris2 . Current ] . Playing {
2020-09-28 22:48:51 +00:00
go fmt . Fprintln ( WRITER , players . JSON ( ) )
2020-09-06 12:13:36 +00:00
}
2020-08-26 21:10:36 +00:00
}
2020-08-31 19:14:14 +00:00
}
} ( )
}
2021-01-07 16:10:20 +00:00
fmt . Fprintln ( WRITER , players . JSON ( ) )
for v := range players . mpris2 . Messages {
if v . Name == "refresh" {
2020-08-31 19:14:14 +00:00
if AUTOFOCUS {
2021-01-07 16:10:20 +00:00
players . mpris2 . Sort ( )
2020-08-31 19:14:14 +00:00
}
if l := players . JSON ( ) ; l != lastLine {
lastLine = l
2020-09-28 22:48:51 +00:00
fmt . Fprintln ( WRITER , l )
2020-08-22 14:40:24 +00:00
}
2020-08-21 14:44:59 +00:00
}
2020-08-21 13:54:32 +00:00
}
2021-01-07 16:10:20 +00:00
players . mpris2 . Refresh ( )
2020-08-21 13:54:32 +00:00
}