forked from Elara6331/itd
Compare commits
16 Commits
Author | SHA1 | Date | |
---|---|---|---|
248beffa2f | |||
73a679d10b | |||
d475c6905e | |||
0e6e3848d7 | |||
ceff536e92 | |||
5e24e8aafa | |||
f4da64a8dd | |||
76320aa813 | |||
b64e6d27d4 | |||
|
f215e4fd90 | ||
|
1e8c9484d2 | ||
b6c47b7383 | |||
e97c1fef48 | |||
3f2bccc40c | |||
d80230b9d4 | |||
c81ac19dda |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
/itctl
|
||||
/itd
|
||||
/itgui
|
||||
/itgui-linux-*
|
||||
/version.txt
|
||||
dist/
|
||||
|
@@ -1,3 +0,0 @@
|
||||
[repos]
|
||||
origin = "ssh://git@192.168.100.62:2222/Arsen6331/itd.git"
|
||||
gitlab = "git@gitlab.com:moussaelianarsen/itd.git"
|
@@ -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
|
||||
|
@@ -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
|
||||
|
2
Makefile
2
Makefile
@@ -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
|
33
README.md
33
README.md
@@ -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).
|
||||
|
||||
[](https://ci.appveyor.com/project/moussaelianarsen/itd-7t6ko)
|
||||
[](https://ci.arsenm.dev/Arsen6331/itd)
|
||||
[](https://aur.archlinux.org/packages/itd-git/)
|
||||
[](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")
|
||||
```
|
||||
|
||||
---
|
||||
|
5
calls.go
5
calls.go
@@ -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
|
||||
}
|
||||
|
@@ -29,7 +29,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)
|
||||
}
|
||||
|
2
go.mod
2
go.mod
@@ -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
4
go.sum
@@ -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=
|
||||
|
@@ -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 {
|
5
itgui.desktop
Normal file
5
itgui.desktop
Normal file
@@ -0,0 +1,5 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Terminal=false
|
||||
Exec=/usr/bin/itgui
|
||||
Name=itgui
|
2
main.go
2
main.go
@@ -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")
|
||||
}
|
||||
|
7
maps.go
7
maps.go
@@ -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
270
mpris/mpris.go
Normal 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
84
mpris/mpris_test.go
Normal 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")
|
||||
}
|
||||
}
|
30
music.go
30
music.go
@@ -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")))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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
47
translit/translit_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user