diff options
| author | Yaroslav de la Peña Smirnov <yps@yaroslavps.com> | 2022-11-20 02:42:19 +0300 | 
|---|---|---|
| committer | Yaroslav de la Peña Smirnov <yps@yaroslavps.com> | 2022-11-20 02:50:44 +0300 | 
| commit | 387fd15bf231e1cf38eaa7ad1543aaf911a4325e (patch) | |
| tree | 23a226a534896a4afcf23d779902a5078c740673 | |
| parent | 485ec0ec0af80a0d63c10e94aebfc59b16aab46b (diff) | |
| download | waybar-mpris-387fd15bf231e1cf38eaa7ad1543aaf911a4325e.tar.gz waybar-mpris-387fd15bf231e1cf38eaa7ad1543aaf911a4325e.zip  | |
Make waybar-mpris more flexible customizable
Instead of customizing by indicating just the order and using flags for
other customization options (e.g. separator), read a C-like format
string with tokens/verbs that are replaced with their respective
information (e.g. `%a` for artist).
Also made it possible to customize the tooltip. The principle is the
same as with the module's text.
| -rw-r--r-- | README.md | 32 | ||||
| -rw-r--r-- | main.go | 222 | 
2 files changed, 130 insertions, 124 deletions
@@ -57,21 +57,29 @@ When running, the program will pipe out json in waybar's format. Add something l  ``` -Usage of waybar-mpris: -      --autofocus          Auto switch to currently playing music players. -      --interpolate        Interpolate track position (helpful for players that don't update regularly, e.g mpDris2) -      --order string       Element order. (default "SYMBOL:ARTIST:ALBUM:TITLE:POSITION") -      --pause string       Pause symbol/text to use. (default "\uf8e3") -      --play string        Play symbol/text to use. (default "▶") -      --position           Show current position between brackets, e.g (04:50/05:00) -      --replace            Replace any running instances -      --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 " - ") +Usage of ./waybar-mpris: +      --autofocus               Auto switch to currently playing music players. +      --interpolate             Interpolate track position (helpful for players that don't update regularly, e.g mpDris2) +      --pause string            Pause symbol/text to use. (default "\uf8e3") +      --play string             Play symbol/text to use. (default "▶") +      --replace                 Replace existing waybar-mpris if found. When false, new instance will clone the original instances output. +      --send string             send command to already runnning waybar-mpris instance. (options: player-next/player-prev/next/prev/toggle/list) +      --text-format string      Format of the waybar module text. (default "%i %a - %t (%p/%d)") +      --tooltip-format string   Format of the waybar module tooltip. (default "%t by %a from %A\n(%P)")  ``` -* Modify the order of components with `--order`. `SYMBOL` is the play/paused icon or text, `POSITION` is the track position (if enabled), other options are self explanatory. +* `--text-format` specifies in what format to display the module's text in +  waybar; the accepted tokens are as follows: +  - `%i` play/pause icon +  - `%a` track artist +  - `%A` album +  - `%t` track title +  - `%p` current position +  - `%l` track length +  - `%P` current media player +* `--tooltip-format` same as `text-format` but for the tooltip; same rules +  apply.  * `--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.  * `--autofocus` makes waybar-mpris automatically focus on currently playing music players.  * `--position` enables the display of the track position.  * `--interpolate` increments the track position every second. This is useful for players (e.g mpDris2) that don't regularly update the position. @@ -1,6 +1,7 @@  package main  import ( +	"encoding/json"  	"fmt"  	"io"  	"log" @@ -28,20 +29,32 @@ const (  // Mostly default values for flag options.  var ( -	PLAY      = "▶" -	PAUSE     = "" -	SEP       = " - " -	ORDER     = "SYMBOL:ARTIST:ALBUM:TITLE:POSITION" -	AUTOFOCUS = false +	playIcon      = "▶" +	pauseIcon     = "" +	textFormat    = "%i %a - %t (%p/%d)" +	tooltipFormat = "%t by %a from %A\n(%P)" +	autofocus     = false  	// Available commands that can be sent to running instances. -	COMMANDS                          = []string{"player-next", "player-prev", "next", "prev", "toggle", "list"} -	SHOW_POS                          = false -	INTERPOLATE                       = false -	REPLACE                           = false +	commands                          = []string{"player-next", "player-prev", "next", "prev", "toggle", "list"} +	showPos                           = false +	interpolate                       = false +	replace                           = false  	isSharing                         = false  	isDataSharing                     = false -	WRITER                  io.Writer = os.Stdout -	SHAREWRITER, DATAWRITER io.Writer +	writer                  io.Writer = os.Stdout +	shareWriter, dataWriter io.Writer +) + +// Format tokens `%<character>` +const ( +	tokIcon     = 'i' +	tokArtist   = 'a' +	tokAlbum    = 'A' +	tokTitle    = 't' +	tokPosition = 'p' +	tokLength   = 'd' +	tokPlayer   = 'P' +	tokPercent  = '%'  )  const ( @@ -142,97 +155,78 @@ type player struct {  	Duplicate bool  } +// This represents the output in JSON format that waybar is expecting +type waybarData struct { +	Class   string `json:"class"` +	Text    string `json:"text"` +	Tooltip string `json:"tooltip"` +} +  func secondsToString(seconds int) string {  	minutes := int(seconds / 60)  	seconds -= int(minutes * 60)  	return fmt.Sprintf("%02d:%02d", minutes, seconds)  } -// JSON returns json for waybar to consume. -func playerJSON(p *player) string { -	artist := strings.ReplaceAll(p.Artist, "\"", "\\\"") -	album := strings.ReplaceAll(p.Album, "\"", "\\\"") -	title := strings.ReplaceAll(p.Title, "\"", "\\\"") -	name := strings.ReplaceAll(p.Name, "\"", "\\\"") -	symbol := PLAY -	out := "{\"class\": \"" -	if p.Playing { -		symbol = PAUSE -		out += "playing" -	} else { -		out += "paused" -	} -	var pos string -	if SHOW_POS { -		if !p.Duplicate { -			pos = p.StringPosition() -			if pos != "" { -				pos = "(" + pos + ")" -			} -		} else { -			pos = "(" + secondsToString(int(p.Position/1000000)) + "/" + secondsToString(p.Length) + ")" - -		} -	} -	var items []string -	order := strings.Split(ORDER, ":") -	for _, v := range order { -		switch v { -		case "SYMBOL": -			items = append(items, symbol) -		case "ARTIST": -			if artist != "" { -				items = append(items, artist) +func formatOutput(p *player, icon string, fstr string) string { +	var buf []byte +	for i := 0; i < len(fstr); i++ { +		if fstr[i] == '%' { +			i++ +			if i >= len(fstr) { +				break  			} -		case "ALBUM": -			if album != "" { -				items = append(items, album) +			switch fstr[i] { +			case tokIcon: +				buf = append(buf, []byte(icon)...) +			case tokArtist: +				buf = append(buf, []byte(p.Artist)...) +			case tokAlbum: +				buf = append(buf, []byte(p.Album)...) +			case tokTitle: +				buf = append(buf, []byte(p.Title)...) +			case tokPosition: +				position := secondsToString(int(p.Position / 1000000)) +				buf = append(buf, []byte(position)...) +			case tokLength: +				length := secondsToString(p.Length) +				buf = append(buf, []byte(length)...) +			case tokPlayer: +				buf = append(buf, []byte(p.Name)...) +			default: +				buf = append(buf, fstr[i])  			} -		case "TITLE": -			if title != "" { -				items = append(items, title) -			} -		case "POSITION": -			if pos != "" && SHOW_POS { -				items = append(items, pos) -			} -		case "PLAYER": -			if name != "" { -				items = append(items, name) -			} -		} -	} -	if len(items) == 0 { -		return "{}" -	} -	text := "" -	for i, v := range items { -		right := "" -		if (v == symbol || v == pos) && i != len(items)-1 { -			right = " " -		} else if i != len(items)-1 && items[i+1] != symbol && items[i+1] != pos { -			right = SEP  		} else { -			right = " " +			buf = append(buf, fstr[i])  		} -		text += v + right  	} -	out += "\",\"text\":\"" + text + "\"," -	out += "\"tooltip\":\"" + strings.ReplaceAll(title, "&", "&") + "\\n" -	if artist != "" { -		out += "by " + strings.ReplaceAll(artist, "&", "&") + "\\n" + +	out := strings.ReplaceAll(string(buf), `"`, `\"`) +	out = strings.ReplaceAll(out, "&", "&") + +	return out +} + +// Returns JSON for waybar to consume. +func playerJSON(p *player) string { +	data := waybarData{} + +	icon := playIcon +	if p.Playing { +		icon = pauseIcon +		data.Class = "playing" +	} else { +		data.Class = "paused"  	} -	if album != "" { -		out += "from " + strings.ReplaceAll(album, "&", "&") + "\\n" + +	data.Text = formatOutput(p, icon, textFormat) +	data.Tooltip = formatOutput(p, icon, tooltipFormat) + +	out, err := json.Marshal(data) +	if err != nil { +		return "{}"  	} -	out += "(" + name + ")\"}" -	return out -	// return fmt.Sprintf("{\"class\":\"%s\",\"text\":\"%s\",\"tooltip\":\"%s\"}", data["class"], data["text"], data["tooltip"]) -	// out, err := json.Marshal(data) -	// if err != nil { -	// 	return "{}" -	// } -	// return string(out) +	return string(out)  }  type players struct { @@ -407,7 +401,7 @@ func duplicateOutput() error {  						}  						str := string(l)  						fromData(p, str) -						fmt.Fprintln(WRITER, playerJSON(p)) +						fmt.Fprintln(writer, playerJSON(p))  						f.Seek(0, 0)  					}  				} @@ -483,14 +477,14 @@ func listenForCommands(players *players) {  				if err != nil {  					fmt.Fprintf(con, "Failed: %v", err)  				} -				DATAWRITER = dataWrite{ +				dataWriter = dataWrite{  					emptyEveryWrite{file: f},  					players,  				}  				if isSharing { -					WRITER = io.MultiWriter(os.Stdout, SHAREWRITER, DATAWRITER) +					writer = io.MultiWriter(os.Stdout, shareWriter, dataWriter)  				} else { -					WRITER = io.MultiWriter(os.Stdout, DATAWRITER) +					writer = io.MultiWriter(os.Stdout, dataWriter)  				}  				isDataSharing = true  			} @@ -502,11 +496,11 @@ func listenForCommands(players *players) {  				if err != nil {  					fmt.Fprintf(con, "Failed: %v", err)  				} -				SHAREWRITER = emptyEveryWrite{file: f} +				shareWriter = emptyEveryWrite{file: f}  				if isDataSharing { -					WRITER = io.MultiWriter(SHAREWRITER, DATAWRITER, os.Stdout) +					writer = io.MultiWriter(shareWriter, dataWriter, os.Stdout)  				} else { -					WRITER = io.MultiWriter(SHAREWRITER, os.Stdout) +					writer = io.MultiWriter(shareWriter, os.Stdout)  				}  				isSharing = true  			} @@ -565,16 +559,20 @@ func main() {  	}  	mw := io.MultiWriter(logfile, os.Stdout)  	log.SetOutput(mw) -	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. An extra \"PLAYER\" element is also available.") -	flag.BoolVar(&AUTOFOCUS, "autofocus", AUTOFOCUS, "Auto switch to currently playing music players.") -	flag.BoolVar(&SHOW_POS, "position", SHOW_POS, "Show current position between brackets, e.g (04:50/05:00)") -	flag.BoolVar(&INTERPOLATE, "interpolate", INTERPOLATE, "Interpolate track position (helpful for players that don't update regularly, e.g mpDris2)") -	flag.BoolVar(&REPLACE, "replace", REPLACE, "replace existing waybar-mpris if found. When false, new instance will clone the original instances output.") +	flag.StringVar(&playIcon, "play", playIcon, "Play symbol/text to use.") +	flag.StringVar(&pauseIcon, "pause", pauseIcon, "Pause symbol/text to use.") +	flag.StringVar(&textFormat, "text-format", textFormat, +		"Format of the waybar module text.") +	flag.StringVar(&tooltipFormat, "tooltip-format", tooltipFormat, +		"Format of the waybar module tooltip.") +	flag.BoolVar(&autofocus, "autofocus", autofocus, +		"Auto switch to currently playing music players.") +	flag.BoolVar(&interpolate, "interpolate", interpolate, +		"Interpolate track position (helpful for players that don't update regularly, e.g mpDris2)") +	flag.BoolVar(&replace, "replace", replace, +		"Replace existing waybar-mpris if found. When false, new instance will clone the original instances output.")  	var command string -	flag.StringVar(&command, "send", "", "send command to already runnning waybar-mpris instance. (options: "+strings.Join(COMMANDS, "/")+")") +	flag.StringVar(&command, "send", "", "send command to already runnning waybar-mpris instance. (options: "+strings.Join(commands, "/")+")")  	flag.Parse()  	os.Stderr = logfile @@ -585,7 +583,7 @@ func main() {  	// fmt.Println("New array", players)  	// Start command listener  	if _, err := os.Stat(SOCK); err == nil { -		if REPLACE { +		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 @@ -614,34 +612,34 @@ func main() {  		log.Fatalln("Error connecting to DBus:", err)  	}  	players := &players{ -		mpris2: mpris2.NewMpris2(conn, INTERPOLATE, POLL, AUTOFOCUS), +		mpris2: mpris2.NewMpris2(conn, interpolate, POLL, autofocus),  	}  	players.mpris2.Reload()  	players.mpris2.Sort()  	lastLine := ""  	go listenForCommands(players)  	go players.mpris2.Listen() -	if SHOW_POS { +	if showPos {  		go func() {  			for {  				time.Sleep(POLL * time.Second)  				if len(players.mpris2.List) != 0 {  					if players.mpris2.List[players.mpris2.Current].Playing { -						go fmt.Fprintln(WRITER, players.JSON()) +						go fmt.Fprintln(writer, players.JSON())  					}  				}  			}  		}()  	} -	fmt.Fprintln(WRITER, players.JSON()) +	fmt.Fprintln(writer, players.JSON())  	for v := range players.mpris2.Messages {  		if v.Name == "refresh" { -			if AUTOFOCUS { +			if autofocus {  				players.mpris2.Sort()  			}  			if l := players.JSON(); l != lastLine {  				lastLine = l -				fmt.Fprintln(WRITER, l) +				fmt.Fprintln(writer, l)  			}  		}  	}  | 
