18 Commits

Author SHA1 Message Date
63976af404 OpenRC Script
All checks were successful
ci/woodpecker/pr/woodpecker Pipeline was successful
Added openrc script.
2023-01-03 06:13:17 +00:00
5f5c67f7cc Warn when Koanf read fails (#47)
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Figured out the problem in issue #32, the toml file syntax was invalid (I had `'` instead of `"` at some values), but there was nothing in the logs about it.

Moved the config reading (and watching) into the same function, which logs the error as a warning.

I wanted to try moving the whole `if` into a separate function, but I kept getting errors when I tried to extract the `path` from the `File`, so I have that attempt in a separate branch not in this pull request: 5a84bf8148

Reviewed-on: https://gitea.arsenm.dev/Arsen6331/itd/pulls/47
Co-authored-by: Hunman <sanyi.exe@gmail.com>
Co-committed-by: Hunman <sanyi.exe@gmail.com>
2023-01-02 09:05:23 +00:00
248beffa2f Add tests
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-08 01:16:00 -08:00
73a679d10b Move mpris out of pkg directory and run gofumpt
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-24 17:36:25 -08:00
d475c6905e Fix goreleaser aur config
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-24 16:24:45 -08:00
0e6e3848d7 Add archlinux package to goreleaser config
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-24 16:23:03 -08:00
ceff536e92 Add itgui.desktop to goreleaser and fix itgui permissions 2022-11-24 16:20:11 -08:00
5e24e8aafa Add itgui.desktop
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-24 16:18:26 -08:00
f4da64a8dd Prepare for itgui cross-compilation
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-24 16:16:25 -08:00
76320aa813 Switch version.txt target to use go generate
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-22 03:23:14 +00:00
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
22 changed files with 541 additions and 77 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
/itctl
/itd
/itgui
/itgui-linux-*
/version.txt
dist/

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

@@ -14,6 +14,8 @@ builds:
- amd64
- arm
- arm64
goarm:
- 7
- id: itctl
env:
- CGO_ENABLED=0
@@ -26,24 +28,33 @@ builds:
- amd64
- arm
- arm64
goarm:
- 7
archives:
- replacements:
386: i386
amd64: x86_64
arm64: aarch64
- name_template: >-
{{- .ProjectName }}-{{.Version}}-{{.Os}}-
{{- if eq .Arch "386" }}i386
{{- else if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "arm64" }}aarch64
{{- else }}{{.Arch}}
{{- end }}
files:
- LICENSE
- README.md
- itd.toml
- itd.service
- itgui.desktop
- itgui-linux-{{.Arch}}{{if eq .Arch "arm"}}-7{{end}}
nfpms:
- id: itd
file_name_template: '{{.PackageName}}-{{.Version}}-{{.Os}}-{{.Arch}}'
file_name_template: >-
{{- .PackageName }}-{{.Version}}-{{.Os}}-
{{- if eq .Arch "386" }}i386
{{- else if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "arm64" }}aarch64
{{- else }}{{.Arch}}
{{- end }}
description: "Companion daemon for the InfiniTime firmware on the PineTime smartwatch"
replacements:
386: i386
amd64: x86_64
arm64: aarch64
homepage: 'https://gitea.arsenm.dev/Arsen6331/itd'
maintainer: 'Arsen Musyaelyan <arsen@arsenm.dev>'
license: GPLv3
@@ -51,16 +62,22 @@ nfpms:
- apk
- deb
- rpm
- archlinux
dependencies:
- dbus
- bluez
- pulseaudio-utils
contents:
- src: itd.toml
dst: /etc/itd.toml
type: "config|noreplace"
- src: itd.service
dst: /usr/lib/systemd/user/itd.service
- src: itgui.desktop
dst: /usr/share/applications/itgui.desktop
- src: itgui-linux-{{.Arch}}{{if eq .Arch "arm"}}-7{{end}}
dst: /usr/bin/itgui
file_info:
mode: 0755
aurs:
- name: itd-bin
homepage: 'https://gitea.arsenm.dev/Arsen6331/itd'
@@ -79,11 +96,14 @@ aurs:
depends:
- dbus
- bluez
- libpulse
package: |-
# binaries
install -Dm755 "./itd" "${pkgdir}/usr/bin/itd"
install -Dm755 "./itctl" "${pkgdir}/usr/bin/itctl"
install -Dm755 ./itd "${pkgdir}/usr/bin/itd"
install -Dm755 ./itctl "${pkgdir}/usr/bin/itctl"
install -Dm755 ./itgui-linux-* "${pkgdir/usr/bin/itgui}"
# desktop files
install -Dm644 "./itgui.desktop" "${pkgdir}/usr/share/applications/itgui.desktop"
# service
install -Dm644 "./itd.service" ${pkgdir}/usr/lib/systemd/user/itd.service

View File

@@ -1,8 +1,20 @@
pipeline:
xgo-itgui:
image: arsen6331/fyne-xgo
environment:
- 'TARGETS=linux/amd64 linux/arm64 linux/386 linux/arm-7'
- 'OUT=itgui'
- 'PACK=./cmd/itgui'
commands:
- export SOURCE_DIR=$${CI_WORKSPACE} OUT_DIR=$${CI_WORKSPACE}
- /build.sh
when:
event: tag
release:
image: goreleaser/goreleaser
commands:
- goreleaser release
secrets: [ gitea_token, aur_key ]
when:
event: tag
event: tag

View File

@@ -25,6 +25,6 @@ uninstall:
rm $(CFG_PREFIX)/itd.toml
version.txt:
printf "r%s.%s" "$(shell git rev-list --count HEAD)" "$(shell git rev-parse --short HEAD)" > version.txt
go generate
.PHONY: all clean install uninstall

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).
[![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-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:
```
Control the itd daemon for InfiniTime smartwatches
NAME:
itctl - A new cli application
Usage:
itctl [flags]
itctl [command]
USAGE:
itctl [global options] command [command options] [arguments...]
Available Commands:
firmware Manage InfiniTime firmware
get Get information from InfiniTime
help Help about any command
notify Send notification to InfiniTime
set Set information on InfiniTime
COMMANDS:
help Display help screen for a command
resources, res Handle InfiniTime resource loading
filesystem, fs Perform filesystem operations on the PineTime
firmware, fw Manage InfiniTime firmware
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:
-h, --help help for itctl
-s, --socket-path string Path to itd socket
Use "itctl [command] --help" for more information about a command.
GLOBAL OPTIONS:
--socket-path value, -s value Path to itd socket (default: "/tmp/itd/socket")
```
---

View File

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

View File

@@ -16,6 +16,8 @@ import (
var cfgDir string
func init() {
etcPath := "/etc/itd.toml";
// Set up logger
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
@@ -29,7 +31,7 @@ func init() {
// If config dir is not readable
if _, err = os.ReadDir(cfgDir); err != nil {
// Create config dir with 700 permissions
err = os.MkdirAll(cfgDir, 0700)
err = os.MkdirAll(cfgDir, 0o700)
if err != nil {
panic(err)
}
@@ -51,15 +53,9 @@ func init() {
// Set config defaults
setCfgDefaults()
// Load config files
etcProvider := file.Provider("/etc/itd.toml")
cfgProvider := file.Provider(cfgPath)
k.Load(etcProvider, toml.Parser())
k.Load(cfgProvider, toml.Parser())
// Watch configs for changes
cfgWatch(etcProvider)
cfgWatch(cfgProvider)
// Load and watch config files
loadAndwatchCfgFile(etcPath)
loadAndwatchCfgFile(cfgPath)
// Load envireonment variables
k.Load(env.Provider("ITD_", "_", func(s string) string {
@@ -67,14 +63,22 @@ func init() {
}), nil)
}
func cfgWatch(provider *file.File) {
func loadAndwatchCfgFile(filename string) {
provider := file.Provider(filename)
if cfgError := k.Load(provider, toml.Parser()); cfgError != nil {
log.Warn().Str("filename", filename).Err(cfgError).Msg("Error while trying to read config file")
}
// Watch for changes and reload when detected
provider.Watch(func(_ interface{}, err error) {
if err != nil {
return
}
k.Load(provider, toml.Parser())
if cfgError := k.Load(provider, toml.Parser()); cfgError != nil {
log.Warn().Str("filename", filename).Err(cfgError).Msg("Error while trying to read config file")
}
})
}

2
go.mod
View File

@@ -15,7 +15,7 @@ require (
github.com/mozillazg/go-pinyin v0.19.0
github.com/rs/zerolog v1.26.1
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
golang.org/x/text v0.3.7
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.10 h1:+WgKGo8CQrlMTRJpGCFCyNddOhW801TKC2QijVV9QVg=
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-20221107042015-72b558707ee3/go.mod h1:K3NJ6fyPv5qqHUedB3MccKOE0whJMJZ80l/yTzzTrgc=
go.arsenm.dev/infinitime v0.0.0-20221119224612-0c369dc5df94 h1:b3kEsAfUyJN5781f0+K72v30MDrwusyPDh/1kPFCDNQ=
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/go.mod h1:goK9z735lfXmqlDxu9qN7FS8t0HJHN3PjyDtCToUY4w=
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 (
"context"
@@ -6,7 +6,7 @@ import (
"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
conn, err := dbus.SystemBusPrivate(dbus.WithContext(ctx))
if err != nil {
@@ -23,7 +23,7 @@ func newSystemBusConn(ctx context.Context) (*dbus.Conn, error) {
return conn, nil
}
func newSessionBusConn(ctx context.Context) (*dbus.Conn, error) {
func NewSessionBusConn(ctx context.Context) (*dbus.Conn, error) {
// Connect to dbus session bus
conn, err := dbus.SessionBusPrivate(dbus.WithContext(ctx))
if err != nil {

20
itd Normal file
View File

@@ -0,0 +1,20 @@
#!/sbin/openrc-run
name="itd"
description="Infinitime Daemon (itd)"
command="@bindir@/itd"
pidfile="@piddir@/${RC_SVCNAME}.pid"
command_user="user:group"
command_background="yes"
respawn_period=30
depend() {
after bluetooth
}
reload() {
checkconfig || return $?
ebegin "Reloading ${RC_SVCNAME}"
start-stop-daemon --signal HUP --pidfile "${pidfile}"
eend $?
}

5
itgui.desktop Normal file
View File

@@ -0,0 +1,5 @@
[Desktop Entry]
Type=Application
Terminal=false
Exec=/usr/bin/itgui
Name=itgui

View File

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

View File

@@ -7,6 +7,7 @@ import (
"github.com/godbus/dbus/v5"
"github.com/rs/zerolog/log"
"go.arsenm.dev/infinitime"
"go.arsenm.dev/itd/internal/utils"
)
const (
@@ -19,7 +20,7 @@ const (
func initPureMaps(ctx context.Context, dev *infinitime.Device) error {
// Connect to session bus. This connection is for method calls.
conn, err := newSessionBusConn(ctx)
conn, err := utils.NewSessionBusConn(ctx)
if err != nil {
return err
}
@@ -30,13 +31,13 @@ func initPureMaps(ctx context.Context, dev *infinitime.Device) error {
}
// Connect to session bus. This connection is for method calls.
monitorConn, err := newSessionBusConn(ctx)
monitorConn, err := utils.NewSessionBusConn(ctx)
if err != nil {
return err
}
// Define rules to listen for
var rules = []string{
rules := []string{
"type='signal',interface='io.github.rinigus.PureMaps.navigator'",
}
var flag uint = 0

270
mpris/mpris.go Normal file
View File

@@ -0,0 +1,270 @@
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
}

84
mpris/mpris_test.go Normal file
View File

@@ -0,0 +1,84 @@
package mpris
import (
"reflect"
"testing"
"github.com/godbus/dbus/v5"
)
// TestParsePropertiesChanged checks the parsePropertiesChanged function to
// make sure it correctly parses a DBus PropertiesChanged signal.
func TestParsePropertiesChanged(t *testing.T) {
// Create a DBus message
msg := &dbus.Message{
Body: []interface{}{
"com.example.Interface",
map[string]dbus.Variant{
"Property1": dbus.MakeVariant(true),
"Property2": dbus.MakeVariant("Hello, world!"),
},
[]string{},
},
}
// Parse the message
iface, changed, ok := parsePropertiesChanged(msg)
if !ok {
t.Error("Expected parsePropertiesChanged to return true, but got false")
}
// Check the parsed values
expectedIface := "com.example.Interface"
if iface != expectedIface {
t.Errorf("Expected iface to be %q, but got %q", expectedIface, iface)
}
expectedChanged := map[string]dbus.Variant{
"Property1": dbus.MakeVariant(true),
"Property2": dbus.MakeVariant("Hello, world!"),
}
if !reflect.DeepEqual(changed, expectedChanged) {
t.Errorf("Expected changed to be %v, but got %v", expectedChanged, changed)
}
// Test a message with an invalid number of arguments
msg = &dbus.Message{
Body: []interface{}{
"com.example.Interface",
},
}
_, _, ok = parsePropertiesChanged(msg)
if ok {
t.Error("Expected parsePropertiesChanged to return false, but got true")
}
// Test a message with an invalid first argument
msg = &dbus.Message{
Body: []interface{}{
123,
map[string]dbus.Variant{
"Property1": dbus.MakeVariant(true),
"Property2": dbus.MakeVariant("Hello, world!"),
},
[]string{},
},
}
_, _, ok = parsePropertiesChanged(msg)
if ok {
t.Error("Expected parsePropertiesChanged to return false, but got true")
}
// Test a message with an invalid second argument
msg = &dbus.Message{
Body: []interface{}{
"com.example.Interface",
123,
[]string{},
},
}
_, _, ok = parsePropertiesChanged(msg)
if ok {
t.Error("Expected parsePropertiesChanged to return false, but got true")
}
}

View File

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

View File

@@ -25,18 +25,19 @@ import (
"github.com/godbus/dbus/v5"
"github.com/rs/zerolog/log"
"go.arsenm.dev/infinitime"
"go.arsenm.dev/itd/internal/utils"
"go.arsenm.dev/itd/translit"
)
func initNotifRelay(ctx context.Context, dev *infinitime.Device) error {
// Connect to dbus session bus
bus, err := newSessionBusConn(ctx)
bus, err := utils.NewSessionBusConn(ctx)
if err != nil {
return err
}
// Define rules to listen for
var rules = []string{
rules := []string{
"type='method_call',member='Notify',path='/org/freedesktop/Notifications',interface='org.freedesktop.Notifications'",
}
var flag uint = 0

View File

@@ -43,7 +43,7 @@ var (
func startSocket(ctx context.Context, dev *infinitime.Device) error {
// Make socket directory if non-existant
err := os.MkdirAll(filepath.Dir(k.String("socket.path")), 0755)
err := os.MkdirAll(filepath.Dir(k.String("socket.path")), 0o755)
if err != nil {
return err
}

View File

@@ -35,8 +35,6 @@ func (ct *ChineseTranslit) Transliterate(s string) string {
// Reset temporary buffer
tmpBuf.Reset()
}
// Write character to output
outBuf.WriteRune(char)
}
}
// If buffer contains characters

47
translit/translit_test.go Normal file
View File

@@ -0,0 +1,47 @@
package translit
import "testing"
func TestTransliterate(t *testing.T) {
type testCase struct {
name string
input string
expected string
}
var cases = []testCase{
{"eASCII", "œª°«»", `oeao""`},
{"Scandinavian", "ÆæØøÅå", "AeaeOeoeAaaa"},
{"German", "äöüÄÖÜßẞ", "aeoeueAeOeUessSS"},
{"Hebrew", "אבגדהוזחטיכלמנסעפצקרשתףץךםן", "abgdhuzkhtyclmns'ptskrshthftschmn"},
{"Greek", "αάβγδεέζηήθιίϊΐκλμνξοόπρσςτυύϋΰφχψωώΑΆΒΓΔΕΈΖΗΉΘΙΊΪΚΛΜΝΞΟΌΠΡΣΤΥΎΫΦΧΨΩΏ", "aavgdeeziithiiiiklmnksooprsstyyyyfchpsooAABGDEEZIIThIIIKLMNKsOOPRSTYYYFChPsOO"},
{"Russian", "Ёё", "Йoйo"},
{"Ukranian", "ґєіїҐЄІЇ", "ghjeijiGhJeIJI"},
{"Arabic", "ابتثجحخدذرزسشصضطظعغفقكلمنهويىﺓآئإؤأء٠١٢٣٤٥٦٧٨٩", "abtthj75dthrzssh99'66'33'fqklmnhwya2222220123456789"},
{"Farsi", "پچژکگی\u200c؟٪؛،۱۲۳۴۵۶۷۸۹۰»«َُِّ", "pchzhkgy ?%;:1234567890<>eao"},
{"Polish", "Łł", "Ll"},
{"Lithuanian", "ąčęėįšųūž", "aceeisuuz"},
{"Estonian", "äÄöõÖÕüÜ", "aAooOOuU"},
{"Icelandic", "ÞþÐð", "ThthDd"},
{"Czech", "řěýáíéóúůďťň", "reyaieouudtn"},
{"French", "àâéèêëùüÿç", "aaeeeeuuyc"},
{"Romanian", "ăĂâÂîÎșȘțȚşŞţŢ„”", `aAaAiIsStTsStT""`},
{
"Emoji",
"😂🤣😊☺️😌😃😁😋😛😜🙃😎😶😩😕😏💜💖💗❤️💕💞💘💓💚💙💟❣️💔😱😮😯😝🤔😔😍😘😚😙👍👌🤞✌️🌄🌞🤗🌻🥱🙄🔫🥔😬✨🌌💀😅😢💯🔥😉😴💤",
`XDXD:):):):D:D:P:P;P(:8):#-_-:(:J<3<3<3<3<3<3<3<3<3<3<3<3!</3D::O:OxP',:-|:|:*:*:*:*:thumbsup::ok_hand::crossed_fingers::victory_hand::sunrise_over_mountains::sun_with_face::hugging_face::sunflower::yawning_face::face_with_rolling_eyes::gun::potato::E******8-X':D:'(:100::fire:;):zzz::zzz:`,
},
{"Korean", "\ucc2c\ubbf8\ub97c \uc637\uc744 \uc5bc\ub9c8\ub098 \ud48d\ubd80\ud558\uac8c \uccad\ucd98\uc774 \uc5ed\uc0ac\ub97c", "chanmireul oteul eolmana pungbuhage cheongchuni yeoksareul"},
{"Chinese", "\u81e8\u8cc7\u601d\u7531\u554f\u805e\u907f\u6c5a\u81f3\u5c0e\u524d\u99ac\u59cb\u4e00\u79fb\u3002", "lin zi si you wen wen bi wu zhi dao qian ma shi yi yi"},
{"Armenian", "\u0531\u0532\u0533\u0534\u0535\u0536\u0537\u0538\u0539\u053a\u053b\u053c\u053d\u053e\u053f\u0540\u0541\u0542\u0543\u0544\u0545\u0546\u0547\u0548\u0549\u054a\u054b\u054c\u054d\u054e\u054f\u0550\u0551\u0552\u0553\u0554\u0555\u0556\u0561\u0562\u0563\u0564\u0565\u0566\u0567\u0568\u0569\u056a\u056b\u056c\u056d\u056e\u056f\u0570\u0571\u0572\u0573\u0574\u0575\u0576\u0577\u0578\u0579\u057a\u057b\u057c\u057d\u057e\u057f\u0580\u0581\u0582\u0583\u0584\u0585\u0586\u0587", "ABGDEZEYTJILXCKHDzXCMYNShVoChPJRSVTRCPQOFabgdezeytjilxckhdzxcmynsochpjrsvtrcpqofev"},
}
for _, tCase := range cases {
t.Run(tCase.name, func(t *testing.T) {
out := Transliterate(tCase.input, tCase.name)
if out != tCase.expected {
t.Errorf("Expected %q, got %q", tCase.expected, out)
}
})
}
}