Merge pull request 'Move mpris implementation from infinitime library to itd, where it really belongs' (#41) from FloralExMachina/itd:master into master

Reviewed-on: https://gitea.arsenm.dev/Arsen6331/itd/pulls/41
This commit is contained in:
Elara 2022-11-22 02:24:51 +00:00
commit 2c733edeec
7 changed files with 304 additions and 26 deletions

View File

@ -7,11 +7,13 @@ import (
"github.com/godbus/dbus/v5" "github.com/godbus/dbus/v5"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"go.arsenm.dev/infinitime" "go.arsenm.dev/infinitime"
"go.arsenm.dev/itd/internal/utils"
) )
func initCallNotifs(ctx context.Context, dev *infinitime.Device) error { func initCallNotifs(ctx context.Context, dev *infinitime.Device) error {
// Connect to system bus. This connection is for method calls. // Connect to system bus. This connection is for method calls.
conn, err := newSystemBusConn(ctx) conn, err := utils.NewSystemBusConn(ctx)
if err != nil { if err != nil {
return err return err
} }
@ -29,7 +31,7 @@ func initCallNotifs(ctx context.Context, dev *infinitime.Device) error {
} }
// Connect to system bus. This connection is for monitoring. // Connect to system bus. This connection is for monitoring.
monitorConn, err := newSystemBusConn(ctx) monitorConn, err := utils.NewSystemBusConn(ctx)
if err != nil { if err != nil {
return err return err
} }

View File

@ -1,4 +1,4 @@
package main package utils
import ( import (
"context" "context"
@ -6,7 +6,7 @@ import (
"github.com/godbus/dbus/v5" "github.com/godbus/dbus/v5"
) )
func newSystemBusConn(ctx context.Context) (*dbus.Conn, error) { func NewSystemBusConn(ctx context.Context) (*dbus.Conn, error) {
// Connect to dbus session bus // Connect to dbus session bus
conn, err := dbus.SystemBusPrivate(dbus.WithContext(ctx)) conn, err := dbus.SystemBusPrivate(dbus.WithContext(ctx))
if err != nil { if err != nil {
@ -23,7 +23,7 @@ func newSystemBusConn(ctx context.Context) (*dbus.Conn, error) {
return conn, nil return conn, nil
} }
func newSessionBusConn(ctx context.Context) (*dbus.Conn, error) { func NewSessionBusConn(ctx context.Context) (*dbus.Conn, error) {
// Connect to dbus session bus // Connect to dbus session bus
conn, err := dbus.SessionBusPrivate(dbus.WithContext(ctx)) conn, err := dbus.SessionBusPrivate(dbus.WithContext(ctx))
if err != nil { if err != nil {

View File

@ -146,7 +146,7 @@ func main() {
} }
// Initialize music controls // Initialize music controls
err = initMusicCtrl(dev) err = initMusicCtrl(ctx, dev)
if err != nil { if err != nil {
log.Error().Err(err).Msg("Error initializing music control") log.Error().Err(err).Msg("Error initializing music control")
} }

View File

@ -7,6 +7,7 @@ import (
"github.com/godbus/dbus/v5" "github.com/godbus/dbus/v5"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"go.arsenm.dev/infinitime" "go.arsenm.dev/infinitime"
"go.arsenm.dev/itd/internal/utils"
) )
const ( const (
@ -19,7 +20,7 @@ const (
func initPureMaps(ctx context.Context, dev *infinitime.Device) error { func initPureMaps(ctx context.Context, dev *infinitime.Device) error {
// Connect to session bus. This connection is for method calls. // Connect to session bus. This connection is for method calls.
conn, err := newSessionBusConn(ctx) conn, err := utils.NewSessionBusConn(ctx)
if err != nil { if err != nil {
return err return err
} }
@ -30,7 +31,7 @@ func initPureMaps(ctx context.Context, dev *infinitime.Device) error {
} }
// Connect to session bus. This connection is for method calls. // Connect to session bus. This connection is for method calls.
monitorConn, err := newSessionBusConn(ctx) monitorConn, err := utils.NewSessionBusConn(ctx)
if err != nil { if err != nil {
return err return err
} }

View File

@ -19,29 +19,31 @@
package main package main
import ( import (
"context"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"go.arsenm.dev/infinitime" "go.arsenm.dev/infinitime"
"go.arsenm.dev/infinitime/pkg/player"
"go.arsenm.dev/itd/translit" "go.arsenm.dev/itd/translit"
"go.arsenm.dev/itd/pkg/mpris"
) )
func initMusicCtrl(dev *infinitime.Device) error { func initMusicCtrl(ctx context.Context, dev *infinitime.Device) error {
player.Init() mpris.Init(ctx)
maps := k.Strings("notifs.translit.use") maps := k.Strings("notifs.translit.use")
translit.Transliterators["custom"] = translit.Map(k.Strings("notifs.translit.custom")) translit.Transliterators["custom"] = translit.Map(k.Strings("notifs.translit.custom"))
player.OnChange(func(ct player.ChangeType, val string) { mpris.OnChange(func(ct mpris.ChangeType, val string) {
newVal := translit.Transliterate(val, maps...) newVal := translit.Transliterate(val, maps...)
if !firmwareUpdating { if !firmwareUpdating {
switch ct { switch ct {
case player.ChangeTypeStatus: case mpris.ChangeTypeStatus:
dev.Music.SetStatus(val == "Playing") dev.Music.SetStatus(val == "Playing")
case player.ChangeTypeTitle: case mpris.ChangeTypeTitle:
dev.Music.SetTrack(newVal) dev.Music.SetTrack(newVal)
case player.ChangeTypeAlbum: case mpris.ChangeTypeAlbum:
dev.Music.SetAlbum(newVal) dev.Music.SetAlbum(newVal)
case player.ChangeTypeArtist: case mpris.ChangeTypeArtist:
dev.Music.SetArtist(newVal) dev.Music.SetArtist(newVal)
} }
} }
@ -58,17 +60,17 @@ func initMusicCtrl(dev *infinitime.Device) error {
// Perform appropriate action based on event // Perform appropriate action based on event
switch musicEvt { switch musicEvt {
case infinitime.MusicEventPlay: case infinitime.MusicEventPlay:
player.Play() mpris.Play()
case infinitime.MusicEventPause: case infinitime.MusicEventPause:
player.Pause() mpris.Pause()
case infinitime.MusicEventNext: case infinitime.MusicEventNext:
player.Next() mpris.Next()
case infinitime.MusicEventPrev: case infinitime.MusicEventPrev:
player.Prev() mpris.Prev()
case infinitime.MusicEventVolUp: case infinitime.MusicEventVolUp:
player.VolUp(uint(k.Int("music.vol.interval"))) mpris.VolUp(uint(k.Int("music.vol.interval")))
case infinitime.MusicEventVolDown: case infinitime.MusicEventVolDown:
player.VolDown(uint(k.Int("music.vol.interval"))) mpris.VolDown(uint(k.Int("music.vol.interval")))
} }
} }
}() }()

View File

@ -26,11 +26,12 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"go.arsenm.dev/infinitime" "go.arsenm.dev/infinitime"
"go.arsenm.dev/itd/translit" "go.arsenm.dev/itd/translit"
"go.arsenm.dev/itd/internal/utils"
) )
func initNotifRelay(ctx context.Context, dev *infinitime.Device) error { func initNotifRelay(ctx context.Context, dev *infinitime.Device) error {
// Connect to dbus session bus // Connect to dbus session bus
bus, err := newSessionBusConn(ctx) bus, err := utils.NewSessionBusConn(ctx)
if err != nil { if err != nil {
return err return err
} }

272
pkg/mpris/mpris.go Normal file
View File

@ -0,0 +1,272 @@
package mpris
import (
"context"
"strings"
"sync"
"github.com/godbus/dbus/v5"
"go.arsenm.dev/itd/internal/utils"
)
var (
method, monitor *dbus.Conn
monitorCh chan *dbus.Message
onChangeOnce sync.Once
)
// Init makes required connections to DBus and
// initializes change monitoring channel
func Init(ctx context.Context) error {
// Connect to session bus for monitoring
monitorConn, err := utils.NewSessionBusConn(ctx)
if err != nil {
return err
}
// Add match rule for PropertiesChanged on media player
monitorConn.AddMatchSignal(
dbus.WithMatchObjectPath("/org/mpris/MediaPlayer2"),
dbus.WithMatchInterface("org.freedesktop.DBus.Properties"),
dbus.WithMatchMember("PropertiesChanged"),
)
monitorCh = make(chan *dbus.Message, 10)
monitorConn.Eavesdrop(monitorCh)
// Connect to session bus for method calls
methodConn, err := utils.NewSessionBusConn(ctx)
if err != nil {
return err
}
method, monitor = methodConn, monitorConn
return nil
}
// Exit closes all connections and channels
func Exit() {
close(monitorCh)
method.Close()
monitor.Close()
}
// Play uses MPRIS to play media
func Play() error {
player, err := getPlayerObj()
if err != nil {
return err
}
if player != nil {
call := player.Call("org.mpris.MediaPlayer2.Player.Play", 0)
if call.Err != nil {
return call.Err
}
}
return nil
}
// Pause uses MPRIS to pause media
func Pause() error {
player, err := getPlayerObj()
if err != nil {
return err
}
if player != nil {
call := player.Call("org.mpris.MediaPlayer2.Player.Pause", 0)
if call.Err != nil {
return call.Err
}
}
return nil
}
// Next uses MPRIS to skip to next media
func Next() error {
player, err := getPlayerObj()
if err != nil {
return err
}
if player != nil {
call := player.Call("org.mpris.MediaPlayer2.Player.Next", 0)
if call.Err != nil {
return call.Err
}
}
return nil
}
// Prev uses MPRIS to skip to previous media
func Prev() error {
player, err := getPlayerObj()
if err != nil {
return err
}
if player != nil {
call := player.Call("org.mpris.MediaPlayer2.Player.Previous", 0)
if call.Err != nil {
return call.Err
}
}
return nil
}
func VolUp(percent uint) error {
player, err := getPlayerObj()
if err != nil {
return err
}
if player != nil {
currentVal, err := player.GetProperty("org.mpris.MediaPlayer2.Player.Volume")
if err != nil {
return err
}
newVal := currentVal.Value().(float64) + (float64(percent) / 100)
err = player.SetProperty("org.mpris.MediaPlayer2.Player.Volume", newVal)
if err != nil {
return err
}
}
return nil
}
func VolDown(percent uint) error {
player, err := getPlayerObj()
if err != nil {
return err
}
if player != nil {
currentVal, err := player.GetProperty("org.mpris.MediaPlayer2.Player.Volume")
if err != nil {
return err
}
newVal := currentVal.Value().(float64) - (float64(percent) / 100)
err = player.SetProperty("org.mpris.MediaPlayer2.Player.Volume", newVal)
if err != nil {
return err
}
}
return nil
}
type ChangeType int
const (
ChangeTypeTitle ChangeType = iota
ChangeTypeArtist
ChangeTypeAlbum
ChangeTypeStatus
)
func (ct ChangeType) String() string {
switch ct {
case ChangeTypeTitle:
return "Title"
case ChangeTypeAlbum:
return "Album"
case ChangeTypeArtist:
return "Artist"
case ChangeTypeStatus:
return "Status"
}
return ""
}
// OnChange runs cb when a value changes
func OnChange(cb func(ChangeType, string)) {
go onChangeOnce.Do(func() {
// For every message on channel
for msg := range monitorCh {
// Parse PropertiesChanged
iface, changed, ok := parsePropertiesChanged(msg)
if !ok || iface != "org.mpris.MediaPlayer2.Player" {
continue
}
// For every property changed
for name, val := range changed {
// If metadata changed
if name == "Metadata" {
// Get fields
fields := val.Value().(map[string]dbus.Variant)
// For every field
for name, val := range fields {
// Handle each field appropriately
if strings.HasSuffix(name, "title") {
title := val.Value().(string)
if title == "" {
title = "Unknown " + ChangeTypeTitle.String()
}
cb(ChangeTypeTitle, title)
} else if strings.HasSuffix(name, "album") {
album := val.Value().(string)
if album == "" {
album = "Unknown " + ChangeTypeAlbum.String()
}
cb(ChangeTypeAlbum, album)
} else if strings.HasSuffix(name, "artist") {
var artists string
switch artistVal := val.Value().(type) {
case string:
artists = artistVal
case []string:
artists = strings.Join(artistVal, ", ")
}
if artists == "" {
artists = "Unknown " + ChangeTypeArtist.String()
}
cb(ChangeTypeArtist, artists)
}
}
} else if name == "PlaybackStatus" {
// Handle status change
cb(ChangeTypeStatus, val.Value().(string))
}
}
}
})
}
// getPlayerNames gets all DBus MPRIS player bus names
func getPlayerNames(conn *dbus.Conn) ([]string, error) {
var names []string
err := conn.BusObject().Call("org.freedesktop.DBus.ListNames", 0).Store(&names)
if err != nil {
return nil, err
}
var players []string
for _, name := range names {
if strings.HasPrefix(name, "org.mpris.MediaPlayer2") {
players = append(players, name)
}
}
return players, nil
}
// GetPlayerObj gets the object corresponding to the first
// bus name found in DBus
func getPlayerObj() (dbus.BusObject, error) {
players, err := getPlayerNames(method)
if err != nil {
return nil, err
}
if len(players) == 0 {
return nil, nil
}
return method.Object(players[0], "/org/mpris/MediaPlayer2"), nil
}
// parsePropertiesChanged parses a DBus PropertiesChanged signal
func parsePropertiesChanged(msg *dbus.Message) (iface string, changed map[string]dbus.Variant, ok bool) {
if len(msg.Body) != 3 {
return "", nil, false
}
iface, ok = msg.Body[0].(string)
if !ok {
return
}
changed, ok = msg.Body[1].(map[string]dbus.Variant)
if !ok {
return
}
return
}