aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYaroslav de la Peña Smirnov <yps@yaroslavps.com>2022-11-20 02:42:19 +0300
committerYaroslav de la Peña Smirnov <yps@yaroslavps.com>2022-11-20 02:50:44 +0300
commit387fd15bf231e1cf38eaa7ad1543aaf911a4325e (patch)
tree23a226a534896a4afcf23d779902a5078c740673
parent485ec0ec0af80a0d63c10e94aebfc59b16aab46b (diff)
downloadwaybar-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.md32
-rw-r--r--main.go222
2 files changed, 130 insertions, 124 deletions
diff --git a/README.md b/README.md
index 87eca47..0a8712c 100644
--- a/README.md
+++ b/README.md
@@ -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.
diff --git a/main.go b/main.go
index 206ea44..4593ca2 100644
--- a/main.go
+++ b/main.go
@@ -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, "&", "&amp;") + "\\n"
- if artist != "" {
- out += "by " + strings.ReplaceAll(artist, "&", "&amp;") + "\\n"
+
+ out := strings.ReplaceAll(string(buf), `"`, `\"`)
+ out = strings.ReplaceAll(out, "&", "&amp;")
+
+ 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, "&", "&amp;") + "\\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)
}
}
}