diff --git a/calls.go b/calls.go index 5b6076d..39631f0 100644 --- a/calls.go +++ b/calls.go @@ -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 +} \ No newline at end of file diff --git a/dbus.go b/dbus.go new file mode 100644 index 0000000..6d05b5a --- /dev/null +++ b/dbus.go @@ -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 +}