8 Commits

Author SHA1 Message Date
b64e6d27d4 Merge pull request 'Move mpris implementation from infinitime library to itd, where it really belongs' (#41) from FloralExMachina/itd:master into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: https://gitea.arsenm.dev/Arsen6331/itd/pulls/41
2022-11-22 02:24:51 +00:00
razorkitty
f215e4fd90 fixed type in comment about DBus
All checks were successful
ci/woodpecker/pr/woodpecker Pipeline was successful
2022-11-22 02:23:03 +00:00
razorkitty
1e8c9484d2 copy mpris implementation from infinitime library to itd, where it really belongs
All checks were successful
ci/woodpecker/pr/woodpecker Pipeline was successful
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
2022-11-21 19:59:54 +00:00
b6c47b7383 Remove gitm config as it's no longer needed
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-19 15:38:23 -08:00
e97c1fef48 Remove pactl dependencies (Arsen6331/infinitime#6)
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-19 15:29:10 -08:00
3f2bccc40c Merge pull request 'update itctl usage screen to current output' (#39) from mashuptwice/itd:master into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: https://gitea.arsenm.dev/Arsen6331/itd/pulls/39
2022-11-19 04:04:31 +00:00
d80230b9d4 update itctl usage screen to current output
All checks were successful
ci/woodpecker/pr/woodpecker Pipeline was successful
2022-11-19 03:55:22 +00:00
c81ac19dda Switch badge to self-hosted CI
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-18 07:50:21 +00:00
12 changed files with 324 additions and 50 deletions

View File

@@ -1,3 +0,0 @@
[repos]
origin = "ssh://git@192.168.100.62:2222/Arsen6331/itd.git"
gitlab = "git@gitlab.com:moussaelianarsen/itd.git"

View File

@@ -54,7 +54,6 @@ nfpms:
dependencies: dependencies:
- dbus - dbus
- bluez - bluez
- pulseaudio-utils
contents: contents:
- src: itd.toml - src: itd.toml
dst: /etc/itd.toml dst: /etc/itd.toml
@@ -79,7 +78,6 @@ aurs:
depends: depends:
- dbus - dbus
- bluez - bluez
- libpulse
package: |- package: |-
# binaries # binaries
install -Dm755 "./itd" "${pkgdir}/usr/bin/itd" install -Dm755 "./itd" "${pkgdir}/usr/bin/itd"

View File

@@ -3,7 +3,7 @@
`itd` is a daemon that uses my infinitime [library](https://go.arsenm.dev/infinitime) to interact with the [PineTime](https://www.pine64.org/pinetime/) running [InfiniTime](https://infinitime.io). `itd` is a daemon that uses my infinitime [library](https://go.arsenm.dev/infinitime) to interact with the [PineTime](https://www.pine64.org/pinetime/) running [InfiniTime](https://infinitime.io).
[![Build status](https://ci.appveyor.com/api/projects/status/01qpwa2bn7c7fdi2?svg=true)](https://ci.appveyor.com/project/moussaelianarsen/itd-7t6ko) [![status-badge](https://ci.arsenm.dev/api/badges/Arsen6331/itd/status.svg)](https://ci.arsenm.dev/Arsen6331/itd)
[![itd-git AUR package](https://img.shields.io/aur/version/itd-git?label=itd-git&logo=archlinux)](https://aur.archlinux.org/packages/itd-git/) [![itd-git AUR package](https://img.shields.io/aur/version/itd-git?label=itd-git&logo=archlinux)](https://aur.archlinux.org/packages/itd-git/)
[![itd-bin AUR package](https://img.shields.io/aur/version/itd-bin?label=itd-bin&logo=archlinux)](https://aur.archlinux.org/packages/itd-bin/) [![itd-bin AUR package](https://img.shields.io/aur/version/itd-bin?label=itd-bin&logo=archlinux)](https://aur.archlinux.org/packages/itd-bin/)
@@ -112,24 +112,25 @@ This daemon comes with a binary called `itctl` which uses the socket to control
This is the `itctl` usage screen: This is the `itctl` usage screen:
``` ```
Control the itd daemon for InfiniTime smartwatches NAME:
itctl - A new cli application
Usage: USAGE:
itctl [flags] itctl [global options] command [command options] [arguments...]
itctl [command]
Available Commands: COMMANDS:
firmware Manage InfiniTime firmware help Display help screen for a command
get Get information from InfiniTime resources, res Handle InfiniTime resource loading
help Help about any command filesystem, fs Perform filesystem operations on the PineTime
notify Send notification to InfiniTime firmware, fw Manage InfiniTime firmware
set Set information on InfiniTime get Get information from InfiniTime
notify Send notification to InfiniTime
set Set information on InfiniTime
update, upd Update information on InfiniTime
watch Watch a value for changes
Flags: GLOBAL OPTIONS:
-h, --help help for itctl --socket-path value, -s value Path to itd socket (default: "/tmp/itd/socket")
-s, --socket-path string Path to itd socket
Use "itctl [command] --help" for more information about a command.
``` ```
--- ---

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
} }

2
go.mod
View File

@@ -15,7 +15,7 @@ require (
github.com/mozillazg/go-pinyin v0.19.0 github.com/mozillazg/go-pinyin v0.19.0
github.com/rs/zerolog v1.26.1 github.com/rs/zerolog v1.26.1
github.com/urfave/cli/v2 v2.4.0 github.com/urfave/cli/v2 v2.4.0
go.arsenm.dev/infinitime v0.0.0-20221107042015-72b558707ee3 go.arsenm.dev/infinitime v0.0.0-20221119224612-0c369dc5df94
go.arsenm.dev/lrpc v0.0.0-20220513001344-3bcc01fdb6a0 go.arsenm.dev/lrpc v0.0.0-20220513001344-3bcc01fdb6a0
golang.org/x/text v0.3.7 golang.org/x/text v0.3.7
modernc.org/sqlite v1.17.2 modernc.org/sqlite v1.17.2

4
go.sum
View File

@@ -422,8 +422,8 @@ github.com/yuin/goldmark v1.3.8/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.10 h1:+WgKGo8CQrlMTRJpGCFCyNddOhW801TKC2QijVV9QVg= github.com/yuin/goldmark v1.4.10 h1:+WgKGo8CQrlMTRJpGCFCyNddOhW801TKC2QijVV9QVg=
github.com/yuin/goldmark v1.4.10/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg= github.com/yuin/goldmark v1.4.10/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg=
go.arsenm.dev/infinitime v0.0.0-20221107042015-72b558707ee3 h1:BfZkb41Gq6h9gy5Cg5jDd5hEk9kI27/h+EX0KN3qZv8= go.arsenm.dev/infinitime v0.0.0-20221119224612-0c369dc5df94 h1:b3kEsAfUyJN5781f0+K72v30MDrwusyPDh/1kPFCDNQ=
go.arsenm.dev/infinitime v0.0.0-20221107042015-72b558707ee3/go.mod h1:K3NJ6fyPv5qqHUedB3MccKOE0whJMJZ80l/yTzzTrgc= go.arsenm.dev/infinitime v0.0.0-20221119224612-0c369dc5df94/go.mod h1:K3NJ6fyPv5qqHUedB3MccKOE0whJMJZ80l/yTzzTrgc=
go.arsenm.dev/lrpc v0.0.0-20220513001344-3bcc01fdb6a0 h1:1K96g1eww+77GeGchwMhd0NTrs7Mk/Hc3M3ItW5NbG4= go.arsenm.dev/lrpc v0.0.0-20220513001344-3bcc01fdb6a0 h1:1K96g1eww+77GeGchwMhd0NTrs7Mk/Hc3M3ItW5NbG4=
go.arsenm.dev/lrpc v0.0.0-20220513001344-3bcc01fdb6a0/go.mod h1:goK9z735lfXmqlDxu9qN7FS8t0HJHN3PjyDtCToUY4w= go.arsenm.dev/lrpc v0.0.0-20220513001344-3bcc01fdb6a0/go.mod h1:goK9z735lfXmqlDxu9qN7FS8t0HJHN3PjyDtCToUY4w=
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=

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
}