Switch calls to use dbus library and add helpers for private connections

This commit is contained in:
Elara 2021-11-24 11:36:36 -08:00
parent 22709439c4
commit 715a53a91c
2 changed files with 104 additions and 144 deletions

211
calls.go
View File

@ -1,176 +1,99 @@
package main package main
import ( import (
"bufio" "github.com/godbus/dbus/v5"
"encoding/json"
"fmt"
"os/exec"
"strconv"
"strings"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"go.arsenm.dev/infinitime" "go.arsenm.dev/infinitime"
) )
func initCallNotifs(dev *infinitime.Device) error { func initCallNotifs(dev *infinitime.Device) error {
// Define rule to filter dbus messages // Define rule to filter dbus messages
rule := "type='signal',sender='org.freedesktop.ModemManager1',interface='org.freedesktop.ModemManager1.Modem.Voice',member='CallAdded'" //rule := "type='signal',sender='org.freedesktop.ModemManager1',interface='org.freedesktop.ModemManager1.Modem.Voice',member='CallAdded'"
// Use dbus-monitor command with profiling output as a workaround // Connect to dbus session monitorConn
// because go-bluetooth seems to monopolize the system bus connection monitorConn, err := newSystemBusConn()
// which makes monitoring show only bluez-related messages.
cmd := exec.Command("dbus-monitor", "--system", "--profile", rule)
// Get command output pipe
stdout, err := cmd.StdoutPipe()
if err != nil {
return err
}
// Run command asynchronously
err = cmd.Start()
if err != nil { if err != nil {
return err return err
} }
// Create new scanner for command output conn, err := newSystemBusConn()
scanner := bufio.NewScanner(stdout) if err != nil {
return err
}
err = monitorConn.AddMatchSignal(
dbus.WithMatchSender("org.freedesktop.ModemManager1"),
dbus.WithMatchInterface("org.freedesktop.ModemManager1.Modem.Voice"),
dbus.WithMatchMember("CallAdded"),
)
if err != nil {
return err
}
c := make(chan *dbus.Message, 10)
monitorConn.Eavesdrop(c)
go func() { go func() {
// For each line in output for x := range c {
for scanner.Scan() { callPath := x.Body[0].(dbus.ObjectPath)
// Get line as string callObj := conn.Object("org.freedesktop.ModemManager1", callPath)
text := scanner.Text()
// If line starts with "#", it is part of phoneNum, err := getPhoneNum(conn, callObj)
// the field format, skip it. if err != nil {
if strings.HasPrefix(text, "#") { log.Fatal().Err(err).Send()
}
resCh, err := dev.NotifyCall(phoneNum)
if err != nil {
continue continue
} }
// Split line into fields. The order is as follows: go func() {
// type timestamp serial sender destination path interface member // Wait for PineTime response
fields := strings.Fields(text) res := <-resCh
// Field 7 is Member. Make sure it is "CallAdded". switch res {
if fields[7] == "CallAdded" { case infinitime.CallStatusAccepted:
// Get Modem ID from modem path // Attempt to accept call
modemID := parseModemID(fields[5]) err = acceptCall(conn, callObj)
// Get call ID of current call if err != nil {
callID, err := getCurrentCallID(modemID) log.Warn().Err(err).Msg("Error accepting call")
if err != nil {
continue
}
// Get phone number of current call
phoneNum, err := getPhoneNum(callID)
if err != nil {
continue
}
// Send call notification to PineTime
resCh, err := dev.NotifyCall(phoneNum)
if err != nil {
continue
}
go func() {
// Wait for PineTime response
res := <-resCh
switch res {
case infinitime.CallStatusAccepted:
// Attempt to accept call
err = acceptCall(callID)
if err != nil {
log.Warn().Err(err).Msg("Error accepting call")
}
case infinitime.CallStatusDeclined:
// Attempt to decline call
err = declineCall(callID)
if err != nil {
log.Warn().Err(err).Msg("Error declining call")
}
case infinitime.CallStatusMuted:
// Warn about unimplemented muting
log.Warn().Msg("Muting calls is not implemented")
} }
}() case infinitime.CallStatusDeclined:
} // Attempt to decline call
err = declineCall(conn, callObj)
if err != nil {
log.Warn().Err(err).Msg("Error declining call")
}
case infinitime.CallStatusMuted:
// Warn about unimplemented muting
log.Warn().Msg("Muting calls is not implemented")
}
}()
} }
}() }()
return nil return nil
} }
func parseModemID(modemPath string) int { func getPhoneNum(conn *dbus.Conn, callObj dbus.BusObject) (string, error) {
// Split path by "/" var out string
splitPath := strings.Split(modemPath, "/") err := callObj.StoreProperty("org.freedesktop.ModemManager1.Call.Number", &out)
// Get last element and convert to integer
id, _ := strconv.Atoi(splitPath[len(splitPath)-1])
return id
}
func getCurrentCallID(modemID int) (int, error) {
// Create mmcli command
cmd := exec.Command("mmcli", "--voice-list-calls", "-m", fmt.Sprint(modemID), "-J")
// Run command and get output
data, err := cmd.Output()
if err != nil {
return 0, err
}
var calls map[string][]string
// Decode JSON from command output
err = json.Unmarshal(data, &calls)
if err != nil {
return 0, err
}
// Get first call in output
firstCall := calls["modem.voice.call"][0]
// Split path by "/"
splitCall := strings.Split(firstCall, "/")
// Return last element converted to integer
return strconv.Atoi(splitCall[len(splitCall)-1])
}
func getPhoneNum(callID int) (string, error) {
// Create dbus-send command
cmd := exec.Command("dbus-send",
"--dest=org.freedesktop.ModemManager1",
"--system",
"--print-reply=literal",
"--type=method_call",
fmt.Sprintf("/org/freedesktop/ModemManager1/Call/%d", callID),
"org.freedesktop.DBus.Properties.Get",
"string:org.freedesktop.ModemManager1.Call",
"string:Number",
)
// Run command and get output
numData, err := cmd.Output()
if err != nil { if err != nil {
return "", err return "", err
} }
// Split output into fields return out, nil
num := strings.Fields(string(numData))
// Return last field
return num[len(num)-1], nil
} }
func acceptCall(callID int) error { func acceptCall(conn *dbus.Conn, callObj dbus.BusObject) error {
// Create dbus-send command call := callObj.Call("org.freedesktop.ModemManager1.Call.Accept", 0)
cmd := exec.Command("dbus-send", if call.Err != nil {
"--dest=org.freedesktop.ModemManager1", return call.Err
"--print-reply", }
"--system", return nil
"--type=method_call",
fmt.Sprintf("/org/freedesktop/ModemManager1/Call/%d", callID),
"org.freedesktop.ModemManager1.Call.Accept",
)
// Run command and return errpr
return cmd.Run()
} }
func declineCall(callID int) error { func declineCall(conn *dbus.Conn, callObj dbus.BusObject) error {
// Create dbus-send command call := callObj.Call("org.freedesktop.ModemManager1.Call.Hangup", 0)
cmd := exec.Command("dbus-send", if call.Err != nil {
"--dest=org.freedesktop.ModemManager1", return call.Err
"--print-reply", }
"--system", return nil
"--type=method_call", }
fmt.Sprintf("/org/freedesktop/ModemManager1/Call/%d", callID),
"org.freedesktop.ModemManager1.Call.Hangup",
)
// Run command and return errpr
return cmd.Run()
}

37
dbus.go Normal file
View File

@ -0,0 +1,37 @@
package main
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
}