Move mpris implementation from infinitime library to itd, where it really belongs #41
6
calls.go
6
calls.go
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
2
main.go
2
main.go
@ -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")
|
||||||
}
|
}
|
||||||
|
5
maps.go
5
maps.go
@ -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
|
||||||
}
|
}
|
||||||
|
30
music.go
30
music.go
@ -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")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -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
272
pkg/mpris/mpris.go
Normal 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 DBis 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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user