Compare commits

...

3 Commits

Author SHA1 Message Date
2da80044b3 Remove MPRIS controls (#7)
This is the follow up PR for moving Mpris controls to itd

Co-authored-by: razorkitty <razorkitty@null.net>
Reviewed-on: https://gitea.arsenm.dev/Arsen6331/infinitime/pulls/7
Co-authored-by: FloralExMachina <william@null.org.uk>
Co-committed-by: FloralExMachina <william@null.org.uk>
2022-11-22 22:53:35 +00:00
d20e8af00e Add local time characteristic (#4)
Co-authored-by: cybuzuma <cybuzuma@vnxs.de>
Co-committed-by: cybuzuma <cybuzuma@vnxs.de>
2022-11-21 16:51:49 +00:00
0c369dc5df Merge pull request 'replace pactl based volume control with mpris based volume control' (#6) from FloralExMachina/infinitime:master into master
Reviewed-on: https://gitea.arsenm.dev/Arsen6331/infinitime/pulls/6
2022-11-19 22:46:12 +00:00
3 changed files with 40 additions and 310 deletions

View File

@@ -33,6 +33,7 @@ const (
MotionValChar = "00030002-78fc-48fe-8e23-433b3a1942d0" MotionValChar = "00030002-78fc-48fe-8e23-433b3a1942d0"
FirmwareVerChar = "00002a26-0000-1000-8000-00805f9b34fb" FirmwareVerChar = "00002a26-0000-1000-8000-00805f9b34fb"
CurrentTimeChar = "00002a2b-0000-1000-8000-00805f9b34fb" CurrentTimeChar = "00002a2b-0000-1000-8000-00805f9b34fb"
LocalTimeChar = "00002a0f-0000-1000-8000-00805f9b34fb"
BatteryLvlChar = "00002a19-0000-1000-8000-00805f9b34fb" BatteryLvlChar = "00002a19-0000-1000-8000-00805f9b34fb"
HeartRateChar = "00002a37-0000-1000-8000-00805f9b34fb" HeartRateChar = "00002a37-0000-1000-8000-00805f9b34fb"
FSTransferChar = "adaf0200-4669-6c65-5472-616e73666572" FSTransferChar = "adaf0200-4669-6c65-5472-616e73666572"
@@ -47,6 +48,7 @@ var charNames = map[string]string{
MotionValChar: "Motion Values", MotionValChar: "Motion Values",
FirmwareVerChar: "Firmware Version", FirmwareVerChar: "Firmware Version",
CurrentTimeChar: "Current Time", CurrentTimeChar: "Current Time",
LocalTimeChar: "Local Time",
BatteryLvlChar: "Battery Level", BatteryLvlChar: "Battery Level",
HeartRateChar: "Heart Rate", HeartRateChar: "Heart Rate",
FSTransferChar: "Filesystem Transfer", FSTransferChar: "Filesystem Transfer",
@@ -66,6 +68,7 @@ type Device struct {
motionValChar *gatt.GattCharacteristic1 motionValChar *gatt.GattCharacteristic1
fwVersionChar *gatt.GattCharacteristic1 fwVersionChar *gatt.GattCharacteristic1
currentTimeChar *gatt.GattCharacteristic1 currentTimeChar *gatt.GattCharacteristic1
localTimeChar *gatt.GattCharacteristic1
battLevelChar *gatt.GattCharacteristic1 battLevelChar *gatt.GattCharacteristic1
heartRateChar *gatt.GattCharacteristic1 heartRateChar *gatt.GattCharacteristic1
fsVersionChar *gatt.GattCharacteristic1 fsVersionChar *gatt.GattCharacteristic1
@@ -421,6 +424,8 @@ func (i *Device) resolveChars() error {
i.fwVersionChar = char i.fwVersionChar = char
case CurrentTimeChar: case CurrentTimeChar:
i.currentTimeChar = char i.currentTimeChar = char
case LocalTimeChar:
i.localTimeChar = char
case BatteryLvlChar: case BatteryLvlChar:
i.battLevelChar = char i.battLevelChar = char
case HeartRateChar: case HeartRateChar:
@@ -708,7 +713,9 @@ func (i *Device) WatchMotion(ctx context.Context) (<-chan MotionValues, error) {
return out, nil return out, nil
} }
// SetTime sets the watch's time using the Current Time Service // SetTime sets the watch's
// * time using the Current Time Service's current time characteristic
// * timezone information using the CTS's local time characteristic
func (i *Device) SetTime(t time.Time) error { func (i *Device) SetTime(t time.Time) error {
if err := i.checkStatus(i.currentTimeChar, CurrentTimeChar); err != nil { if err := i.checkStatus(i.currentTimeChar, CurrentTimeChar); err != nil {
return err return err
@@ -723,7 +730,38 @@ func (i *Device) SetTime(t time.Time) error {
binary.Write(buf, binary.LittleEndian, uint8(t.Weekday())) binary.Write(buf, binary.LittleEndian, uint8(t.Weekday()))
binary.Write(buf, binary.LittleEndian, uint8((t.Nanosecond()/1000)/1e6*256)) binary.Write(buf, binary.LittleEndian, uint8((t.Nanosecond()/1000)/1e6*256))
binary.Write(buf, binary.LittleEndian, uint8(0b0001)) binary.Write(buf, binary.LittleEndian, uint8(0b0001))
return i.currentTimeChar.WriteValue(buf.Bytes(), nil) if err := i.currentTimeChar.WriteValue(buf.Bytes(), nil); err != nil {
return err
}
if err := i.checkStatus(i.localTimeChar, LocalTimeChar); err != nil {
// If the characteristic is unavailable,
// fail silently, as many people may be on
// older InfiniTime versions. A warning
// may be added later.
if _, ok := err.(ErrCharNotAvail); ok {
return nil
} else {
return err
}
}
_, offset := t.Zone()
dst := 0
// Local time expects two values: the timezone offset and the dst offset, both
// expressed in quarters of an hour.
// Timezone offset is to be constant over DST, with dst offset holding the offset != 0
// when DST is in effect.
// As there is no standard way in go to get the actual dst offset, we assume it to be 1h
// when DST is in effect
if t.IsDST() {
dst = 3600
offset -= 3600
}
bufTz := &bytes.Buffer{}
binary.Write(bufTz, binary.LittleEndian, uint8(offset / 3600 * 4))
binary.Write(bufTz, binary.LittleEndian, uint8(dst / 3600 * 4))
return i.localTimeChar.WriteValue(bufTz.Bytes(), nil)
} }
// Notify sends a notification to InfiniTime via // Notify sends a notification to InfiniTime via

View File

@@ -1,37 +0,0 @@
package utils
import "github.com/godbus/dbus/v5"
func NewSystemBusConn() (*dbus.Conn, error) {
// Connect to dbus session bus
conn, err := dbus.SystemBusPrivate()
if err != nil {
return nil, err
}
err = conn.Auth(nil)
if err != nil {
return nil, err
}
err = conn.Hello()
if err != nil {
return nil, err
}
return conn, nil
}
func NewSessionBusConn() (*dbus.Conn, error) {
// Connect to dbus session bus
conn, err := dbus.SessionBusPrivate()
if err != nil {
return nil, err
}
err = conn.Auth(nil)
if err != nil {
return nil, err
}
err = conn.Hello()
if err != nil {
return nil, err
}
return conn, nil
}

View File

@@ -1,271 +0,0 @@
package player
import (
"strings"
"sync"
"github.com/godbus/dbus/v5"
"go.arsenm.dev/infinitime/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() error {
// Connect to session bus for monitoring
monitorConn, err := utils.NewSessionBusConn()
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()
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
}