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

This commit is contained in:
Arsen Musayelyan 2021-11-24 11:36:36 -08:00
parent 75327286ef
commit 0b5d777077
2 changed files with 104 additions and 144 deletions

211
calls.go
View File

@ -1,176 +1,99 @@
package main
import (
"bufio"
"encoding/json"
"fmt"
"os/exec"
"strconv"
"strings"
"github.com/godbus/dbus/v5"
"github.com/rs/zerolog/log"
"go.arsenm.dev/infinitime"
)
func initCallNotifs(dev *infinitime.Device) error {
// 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
// because go-bluetooth seems to monopolize the system bus connection
// 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()
// Connect to dbus session monitorConn
monitorConn, err := newSystemBusConn()
if err != nil {
return err
}
// Create new scanner for command output
scanner := bufio.NewScanner(stdout)
conn, err := newSystemBusConn()
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() {
// For each line in output
for scanner.Scan() {
// Get line as string
text := scanner.Text()
for x := range c {
callPath := x.Body[0].(dbus.ObjectPath)
callObj := conn.Object("org.freedesktop.ModemManager1", callPath)
// If line starts with "#", it is part of
// the field format, skip it.
if strings.HasPrefix(text, "#") {
phoneNum, err := getPhoneNum(conn, callObj)
if err != nil {
log.Fatal().Err(err).Send()
}
resCh, err := dev.NotifyCall(phoneNum)
if err != nil {
continue
}
// Split line into fields. The order is as follows:
// type timestamp serial sender destination path interface member
fields := strings.Fields(text)
// Field 7 is Member. Make sure it is "CallAdded".
if fields[7] == "CallAdded" {
// Get Modem ID from modem path
modemID := parseModemID(fields[5])
// Get call ID of current call
callID, err := getCurrentCallID(modemID)
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")
go func() {
// Wait for PineTime response
res := <-resCh
switch res {
case infinitime.CallStatusAccepted:
// Attempt to accept call
err = acceptCall(conn, callObj)
if err != nil {
log.Warn().Err(err).Msg("Error accepting call")
}
}()
}
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
}
func parseModemID(modemPath string) int {
// Split path by "/"
splitPath := strings.Split(modemPath, "/")
// 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()
func getPhoneNum(conn *dbus.Conn, callObj dbus.BusObject) (string, error) {
var out string
err := callObj.StoreProperty("org.freedesktop.ModemManager1.Call.Number", &out)
if err != nil {
return "", err
}
// Split output into fields
num := strings.Fields(string(numData))
// Return last field
return num[len(num)-1], nil
return out, nil
}
func acceptCall(callID int) error {
// Create dbus-send command
cmd := exec.Command("dbus-send",
"--dest=org.freedesktop.ModemManager1",
"--print-reply",
"--system",
"--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 acceptCall(conn *dbus.Conn, callObj dbus.BusObject) error {
call := callObj.Call("org.freedesktop.ModemManager1.Call.Accept", 0)
if call.Err != nil {
return call.Err
}
return nil
}
func declineCall(callID int) error {
// Create dbus-send command
cmd := exec.Command("dbus-send",
"--dest=org.freedesktop.ModemManager1",
"--print-reply",
"--system",
"--type=method_call",
fmt.Sprintf("/org/freedesktop/ModemManager1/Call/%d", callID),
"org.freedesktop.ModemManager1.Call.Hangup",
)
// Run command and return errpr
return cmd.Run()
}
func declineCall(conn *dbus.Conn, callObj dbus.BusObject) error {
call := callObj.Call("org.freedesktop.ModemManager1.Call.Hangup", 0)
if call.Err != nil {
return call.Err
}
return nil
}

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
}