copy mpris implementation from infinitime library to itd, where it really belongs
moved dbus.go to an internal utils package added context function parameter to initMusicCtrl and updated main.go to pass it updated calls.go, maps.go, music.go, and notifs.go to use utils package for getting a dus connection
This commit is contained in:
parent
7ba643888c
commit
5699375b2a
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