pmaports/temp/pulseaudio/0005-bluetooth-add-ModemManager-backend.patch

1713 lines
64 KiB
Diff
Raw Normal View History

From 67fc83f344dc0a308aad5619621edff3307f54d6 Mon Sep 17 00:00:00 2001
From: Dylan Van Assche <me@dylanvanassche.be>
Date: Thu, 7 Apr 2022 20:30:24 +0200
Subject: [PATCH 05/26] bluetooth: add ModemManager backend
ModemManager is an alternative to oFono for interacting with
modems. Add a ModemManager backend to better support Bluetooth HFP 1.6
with cellular support such as making and rejecting calls, retrieving
cellular status and in-band ringing.
---
.gitlab-ci.yml | 3 +-
meson.build | 2 +
src/modules/bluetooth/bluez5-util.h | 7 +
src/modules/bluetooth/meson.build | 4 +-
src/modules/bluetooth/modemmanager.c | 1498 ++++++++++++++++++++++++++
src/modules/bluetooth/modemmanager.h | 98 ++
6 files changed, 1610 insertions(+), 2 deletions(-)
create mode 100644 src/modules/bluetooth/modemmanager.c
create mode 100644 src/modules/bluetooth/modemmanager.h
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index e6b36da87..f2e20dd64 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -19,7 +19,7 @@ variables:
# CI runs, for example when adding new packages to FDO_DISTRIBUTION_PACKAGES.
# The tag is an arbitrary string that identifies the exact container
# contents.
- FDO_DISTRIBUTION_TAG: '2021-11-03-00'
+ FDO_DISTRIBUTION_TAG: '2022-04-20-00'
FDO_DISTRIBUTION_VERSION: '20.04'
FDO_UPSTREAM_REPO: 'pulseaudio/pulseaudio'
UBUNTU_IMAGE: "$CI_REGISTRY_IMAGE/ubuntu/$FDO_DISTRIBUTION_VERSION:$FDO_DISTRIBUTION_TAG"
@@ -81,6 +81,7 @@ build-container:
libxml2-utils
libxtst-dev
m4
+ modemmanager-dev
ninja-build
pkg-config
python3-setuptools
diff --git a/meson.build b/meson.build
index 3ae7ac69a..063390c7f 100644
--- a/meson.build
+++ b/meson.build
@@ -773,6 +773,8 @@ if get_option('daemon')
endif
endif
+ modemmanager_dep = dependency('ModemManager', version : '>= 1.10.0', required : get_option('bluez5-native-headset'))
+
jack_dep = dependency('jack', version : '>= 0.117.0', required : get_option('jack'))
if jack_dep.found()
cdata.set('HAVE_JACK', 1)
diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h
index a24a0b823..f69176425 100644
--- a/src/modules/bluetooth/bluez5-util.h
+++ b/src/modules/bluetooth/bluez5-util.h
@@ -64,6 +64,7 @@ typedef struct pa_bluetooth_adapter pa_bluetooth_adapter;
typedef struct pa_bluetooth_discovery pa_bluetooth_discovery;
typedef struct pa_bluetooth_backend pa_bluetooth_backend;
typedef struct pa_upower_backend pa_upower_backend;
+typedef struct pa_modemmanager_backend pa_modemmanager_backend;
typedef enum pa_bluetooth_hook {
PA_BLUETOOTH_HOOK_ADAPTER_UUIDS_CHANGED, /* Call data: pa_bluetooth_adapter */
@@ -71,6 +72,12 @@ typedef enum pa_bluetooth_hook {
PA_BLUETOOTH_HOOK_DEVICE_UNLINK, /* Call data: pa_bluetooth_device */
PA_BLUETOOTH_HOOK_DEVICE_BATTERY_LEVEL_CHANGED, /* Call data: pa_bluetooth_device */
PA_BLUETOOTH_HOOK_HOST_BATTERY_LEVEL_CHANGED, /* Call data: pa_upower_backend */
+ PA_BLUETOOTH_HOOK_HOST_CALLS_CHANGED, /* Call data: pa_modemmanager_transport */
+ PA_BLUETOOTH_HOOK_HOST_SIGNAL_STRENGTH_CHANGED, /* Call data: pa_modemmanager_transport */
+ PA_BLUETOOTH_HOOK_HOST_HAS_SERVICE_CHANGED, /* Call data: pa_modemmanager_transport */
+ PA_BLUETOOTH_HOOK_HOST_IS_ROAMING_CHANGED, /* Call data: pa_modemmanager_transport */
+ PA_BLUETOOTH_HOOK_HOST_OPERATION_FAILED, /* Call data: pa_modemmanager_transport */
+ PA_BLUETOOTH_HOOK_HOST_OPERATION_SUCCEED, /* Call data: pa_modemmanager_transport */
PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED, /* Call data: pa_bluetooth_transport */
PA_BLUETOOTH_HOOK_TRANSPORT_SOURCE_VOLUME_CHANGED, /* Call data: pa_bluetooth_transport */
PA_BLUETOOTH_HOOK_TRANSPORT_SINK_VOLUME_CHANGED, /* Call data: pa_bluetooth_transport */
diff --git a/src/modules/bluetooth/meson.build b/src/modules/bluetooth/meson.build
index 0703d5086..fdcc8a521 100644
--- a/src/modules/bluetooth/meson.build
+++ b/src/modules/bluetooth/meson.build
@@ -18,6 +18,8 @@ if get_option('bluez5-native-headset')
libbluez5_util_sources += [ 'backend-native.c' ]
libbluez5_util_sources += [ 'upower.c' ]
libbluez5_util_headers += [ 'upower.h' ]
+ libbluez5_util_sources += [ 'modemmanager.c' ]
+ libbluez5_util_headers += [ 'modemmanager.h' ]
endif
if get_option('bluez5-ofono-headset')
@@ -37,7 +39,7 @@ libbluez5_util = shared_library('bluez5-util',
c_args : [pa_c_args, server_c_args],
link_args : [nodelete_link_args],
include_directories : [configinc, topinc],
- dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep, bluez_dep, dbus_dep, sbc_dep, libintl_dep, bluez5_gst_dep, bluez5_gstapp_dep, libm_dep],
+ dependencies : [libpulse_dep, libpulsecommon_dep, libpulsecore_dep, bluez_dep, dbus_dep, sbc_dep, libintl_dep, bluez5_gst_dep, bluez5_gstapp_dep, libm_dep, modemmanager_dep],
install : true,
install_rpath : privlibdir,
install_dir : modlibexecdir,
diff --git a/src/modules/bluetooth/modemmanager.c b/src/modules/bluetooth/modemmanager.c
new file mode 100644
index 000000000..60c3fabec
--- /dev/null
+++ b/src/modules/bluetooth/modemmanager.c
@@ -0,0 +1,1498 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2022 Dylan Van Assche <me@dylanvanassche.be>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <math.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/dbus-shared.h>
+#include <pulsecore/log.h>
+#include <pulse/timeval.h>
+#include <pulse/rtclock.h>
+#include <pulsecore/strbuf.h>
+
+#include "modemmanager.h"
+
+static pa_dbus_pending* send_and_add_to_pending(pa_modemmanager_backend *backend, DBusMessage *m,
+ DBusPendingCallNotifyFunction func, void *call_data) {
+
+ pa_dbus_pending *p;
+ DBusPendingCall *call;
+
+ pa_assert(backend);
+ pa_assert(m);
+
+ pa_assert_se(dbus_connection_send_with_reply(pa_dbus_connection_get(backend->connection), m, &call, -1));
+
+ p = pa_dbus_pending_new(pa_dbus_connection_get(backend->connection), m, call, backend, call_data);
+ PA_LLIST_PREPEND(pa_dbus_pending, backend->pending, p);
+ dbus_pending_call_set_notify(call, func, p, NULL);
+
+ return p;
+}
+
+static const char *check_variant_property(DBusMessageIter *i) {
+ const char *key;
+
+ pa_assert(i);
+
+ if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) {
+ pa_log_error("Property name not a string.");
+ return NULL;
+ }
+
+ dbus_message_iter_get_basic(i, &key);
+
+ if (!dbus_message_iter_next(i)) {
+ pa_log_error("Property value missing");
+ return NULL;
+ }
+
+ if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_VARIANT) {
+ pa_log_error("Property value not a variant.");
+ return NULL;
+ }
+
+ return key;
+}
+
+static void create_call_reply(DBusPendingCall *pending, void *userdata) {
+ pa_dbus_pending *p;
+ pa_modemmanager_backend *b;
+ DBusMessage *r;
+
+ pa_assert(pending);
+ pa_assert_se(p = userdata);
+ pa_assert_se(b = p->context_data);
+ pa_assert_se(r = dbus_pending_call_steal_reply(pending));
+
+ if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) {
+ pa_log_warn("ModemManager D-Bus Voice not available");
+ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery,
+ PA_BLUETOOTH_HOOK_HOST_OPERATION_FAILED), b);
+ goto finish;
+ }
+
+ if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
+ pa_log_error("CreateCall() failed: %s: %s", dbus_message_get_error_name(r), pa_dbus_get_error_message(r));
+ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery,
+ PA_BLUETOOTH_HOOK_HOST_OPERATION_FAILED), b);
+ goto finish;
+ }
+
+ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery,
+ PA_BLUETOOTH_HOOK_HOST_OPERATION_SUCCEED), b);
+
+finish:
+ dbus_message_unref(r);
+
+ PA_LLIST_REMOVE(pa_dbus_pending, b->pending, p);
+ pa_dbus_pending_free(p);
+}
+
+static void call_hangup_reply(DBusPendingCall *pending, void *userdata) {
+ pa_dbus_pending *p;
+ pa_modemmanager_backend *b;
+ DBusMessage *r;
+
+ pa_assert(pending);
+ pa_assert_se(p = userdata);
+ pa_assert_se(b = p->context_data);
+ pa_assert_se(r = dbus_pending_call_steal_reply(pending));
+
+ if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) {
+ pa_log_warn("ModemManager D-Bus Call not available");
+ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery,
+ PA_BLUETOOTH_HOOK_HOST_OPERATION_FAILED), b);
+ goto finish;
+ }
+
+ if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
+ pa_log_error("Hangup() failed: %s: %s", dbus_message_get_error_name(r), pa_dbus_get_error_message(r));
+ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery,
+ PA_BLUETOOTH_HOOK_HOST_OPERATION_FAILED), b);
+ goto finish;
+ }
+
+ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery,
+ PA_BLUETOOTH_HOOK_HOST_OPERATION_SUCCEED), b);
+
+finish:
+ dbus_message_unref(r);
+
+ PA_LLIST_REMOVE(pa_dbus_pending, b->pending, p);
+ pa_dbus_pending_free(p);
+}
+
+static void call_accept_reply(DBusPendingCall *pending, void *userdata) {
+ pa_dbus_pending *p;
+ pa_modemmanager_backend *b;
+ DBusMessage *r;
+
+ pa_assert(pending);
+ pa_assert_se(p = userdata);
+ pa_assert_se(b = p->context_data);
+ pa_assert_se(r = dbus_pending_call_steal_reply(pending));
+
+ if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) {
+ pa_log_warn("ModemManager D-Bus Call not available");
+ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery,
+ PA_BLUETOOTH_HOOK_HOST_OPERATION_FAILED), b);
+ goto finish;
+ }
+
+ if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
+ pa_log_error("Accept() failed: %s: %s", dbus_message_get_error_name(r), pa_dbus_get_error_message(r));
+ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery,
+ PA_BLUETOOTH_HOOK_HOST_OPERATION_FAILED), b);
+ goto finish;
+ }
+
+ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery,
+ PA_BLUETOOTH_HOOK_HOST_OPERATION_SUCCEED), b);
+
+finish:
+ dbus_message_unref(r);
+
+ PA_LLIST_REMOVE(pa_dbus_pending, b->pending, p);
+ pa_dbus_pending_free(p);
+}
+
+static void send_dtmf_reply(DBusPendingCall *pending, void *userdata) {
+ pa_dbus_pending *p;
+ pa_modemmanager_backend *b;
+ DBusMessage *r;
+
+ pa_assert(pending);
+ pa_assert_se(p = userdata);
+ pa_assert_se(b = p->context_data);
+ pa_assert_se(r = dbus_pending_call_steal_reply(pending));
+
+ if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) {
+ pa_log_warn("ModemManager D-Bus Call not available");
+ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery,
+ PA_BLUETOOTH_HOOK_HOST_OPERATION_FAILED), b);
+ goto finish;
+ }
+
+ if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
+ pa_log_error("SendDtmf() failed: %s: %s", dbus_message_get_error_name(r), pa_dbus_get_error_message(r));
+ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery,
+ PA_BLUETOOTH_HOOK_HOST_OPERATION_FAILED), b);
+ goto finish;
+ }
+
+ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery,
+ PA_BLUETOOTH_HOOK_HOST_OPERATION_SUCCEED), b);
+
+finish:
+ dbus_message_unref(r);
+
+ PA_LLIST_REMOVE(pa_dbus_pending, b->pending, p);
+ pa_dbus_pending_free(p);
+}
+
+static void parse_state(pa_modemmanager_backend *b, DBusMessageIter *i) {
+ MMModemState state;
+ bool has_service;
+
+ pa_assert(i);
+
+ /*
+ * ModemManager emits the State property as UINT32 or INT32.
+ * Cast to unsigned int if needed.
+ */
+ pa_assert(dbus_message_iter_get_arg_type(i) == DBUS_TYPE_UINT32
+ || dbus_message_iter_get_arg_type(i) == DBUS_TYPE_INT32);
+
+ if (dbus_message_iter_get_arg_type(i) == DBUS_TYPE_INT32) {
+ int s;
+
+ dbus_message_iter_get_basic(i, &s);
+ state = (unsigned int) s;
+ } else
+ dbus_message_iter_get_basic(i, &state);
+
+ /* All states from REGISTERED and higher indicate that the modem has service */
+ if (state >= MM_MODEM_STATE_REGISTERED)
+ has_service = true;
+ else
+ has_service = false;
+
+ pa_log_debug("Network has service: %s", pa_yes_no(has_service));
+
+ if (b->modem->network_has_service != has_service) {
+ b->modem->network_has_service = has_service;
+ pa_log_debug("AG service status updated: %s", pa_yes_no(b->modem->network_has_service));
+ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery,
+ PA_BLUETOOTH_HOOK_HOST_HAS_SERVICE_CHANGED), b);
+ }
+}
+
+static void parse_registration(pa_modemmanager_backend *b, DBusMessageIter *i) {
+ MMModem3gppRegistrationState registration;
+ bool is_roaming;
+
+ pa_assert(i);
+ pa_assert(dbus_message_iter_get_arg_type(i) == DBUS_TYPE_UINT32);
+
+ dbus_message_iter_get_basic(i, &registration);
+
+ /* All states containing ROAMING indicate that the modem is roaming */
+ if (registration == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING
+ || registration == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING_CSFB_NOT_PREFERRED
+ || registration == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING_SMS_ONLY) {
+ is_roaming = true;
+ } else
+ is_roaming = false;
+
+ pa_log_debug("Network is roaming: %s", pa_yes_no(is_roaming));
+
+ if (b->modem->network_is_roaming != is_roaming) {
+ b->modem->network_is_roaming = is_roaming;
+ pa_log_debug("AG roaming status updated: %s", pa_yes_no(b->modem->network_is_roaming));
+ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery,
+ PA_BLUETOOTH_HOOK_HOST_IS_ROAMING_CHANGED), b);
+ }
+}
+
+static void parse_signal_quality(pa_modemmanager_backend *b, DBusMessageIter *i) {
+ unsigned int percentage, signal_strength;
+ DBusMessageIter structIter;
+
+ pa_assert(i);
+ pa_assert(dbus_message_iter_get_arg_type(i) == DBUS_TYPE_STRUCT);
+
+ dbus_message_iter_recurse(i, &structIter);
+
+ if (dbus_message_iter_get_arg_type(&structIter) == DBUS_TYPE_UINT32) {
+ dbus_message_iter_get_basic(&structIter, &percentage);
+ signal_strength = (unsigned int) round(percentage / 20.0);
+
+ pa_log_debug("Network signal strength: %d/100", percentage);
+
+ if (b->modem->network_signal_strength != signal_strength) {
+ b->modem->network_signal_strength = signal_strength;
+ pa_log_debug("AG signal strength updated (%d/5)", b->modem->network_signal_strength);
+ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery,
+ PA_BLUETOOTH_HOOK_HOST_SIGNAL_STRENGTH_CHANGED), b);
+ }
+ }
+}
+
+static void parse_operator_name(pa_modemmanager_backend *b, DBusMessageIter *i) {
+ char *name;
+
+ pa_assert(i);
+ pa_assert(dbus_message_iter_get_arg_type(i) == DBUS_TYPE_STRING);
+
+ dbus_message_iter_get_basic(i, &name);
+ if (b->modem->network_operator_name)
+ pa_xfree(b->modem->network_operator_name);
+ b->modem->network_operator_name = pa_xstrdup(name);
+
+ pa_log_debug("Network operator name: %s", b->modem->network_operator_name);
+}
+
+static void parse_operator_code(pa_modemmanager_backend *b, DBusMessageIter *i) {
+ char *code;
+
+ pa_assert(i);
+ pa_assert(dbus_message_iter_get_arg_type(i) == DBUS_TYPE_STRING);
+
+ dbus_message_iter_get_basic(i, &code);
+ if (b->modem->network_operator_code)
+ pa_xfree(b->modem->network_operator_code);
+ b->modem->network_operator_code = pa_xstrdup(code);
+
+ pa_log_debug("Network operator code: %s", b->modem->network_operator_code);
+}
+
+static void parse_own_number(pa_modemmanager_backend *b, DBusMessageIter *i) {
+ DBusMessageIter element_i;
+ char *number;
+
+ pa_assert(i);
+ pa_assert(dbus_message_iter_get_arg_type(i) == DBUS_TYPE_ARRAY);
+
+ dbus_message_iter_recurse(i, &element_i);
+ while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_STRING) {
+ dbus_message_iter_get_basic(&element_i, &number);
+ if (b->modem->modem_own_number)
+ pa_xfree(b->modem->modem_own_number);
+ b->modem->modem_own_number = pa_xstrdup(number);
+ pa_log_debug("Modem own number: %s", b->modem->modem_own_number);
+
+ /* Bluetooth HFP cannot deal with multiple SIMs and numbers */
+ break;
+
+ dbus_message_iter_next(&element_i);
+ }
+}
+
+static void parse_call_number(pa_modemmanager_backend *b, DBusMessageIter *i, const char *call_object) {
+ char *number;
+ call_status_t *call_state;
+
+ pa_assert(i);
+ pa_assert(dbus_message_iter_get_arg_type(i) == DBUS_TYPE_STRING);
+
+ dbus_message_iter_get_basic(i, &number);
+
+ call_state = pa_hashmap_get(b->calls, call_object);
+ if (!call_state) {
+ return;
+ }
+
+ if (call_state->number)
+ pa_xfree(call_state->number);
+ call_state->number = pa_xstrdup(number);
+
+ pa_log_debug("Call number %s", call_state->number);
+}
+
+static void parse_call_state(pa_modemmanager_backend *b, DBusMessageIter *i, const char *call_object) {
+ MMCallState state;
+ call_status_t *call_state;
+
+ pa_assert(i);
+ pa_assert(dbus_message_iter_get_arg_type(i) == DBUS_TYPE_INT32);
+
+ dbus_message_iter_get_basic(i, &state);
+
+ call_state = pa_hashmap_get(b->calls, call_object);
+ if (!call_state) {
+ return;
+ }
+
+ switch(state) {
+ case MM_CALL_STATE_UNKNOWN:
+ pa_log_warn("Call state unknown");
+ call_state->status = PA_MODEMMANAGER_CALL_STATE_INVALID;
+ break;
+ case MM_CALL_STATE_ACTIVE:
+ pa_log_debug("Call active");
+ call_state->status = PA_MODEMMANAGER_CALL_STATE_ACTIVE;
+ break;
+ /* FIXME: implement three-way calling */
+ case MM_CALL_STATE_HELD:
+ pa_log_warn("Three-way calling is not implemented!");
+ call_state->status = PA_MODEMMANAGER_CALL_STATE_INVALID;
+ break;
+ case MM_CALL_STATE_DIALING:
+ pa_log_debug("Call dailing");
+ call_state->status = PA_MODEMMANAGER_CALL_STATE_DIALING;
+ break;
+ case MM_CALL_STATE_RINGING_OUT:
+ case MM_CALL_STATE_RINGING_IN:
+ pa_log_debug("Call ringing");
+ call_state->status = PA_MODEMMANAGER_CALL_STATE_RINGING;
+ break;
+ /* FIXME: implement three-way calling */
+ case MM_CALL_STATE_WAITING:
+ pa_log_warn("Three-way calling is not implemented!");
+ call_state->status = PA_MODEMMANAGER_CALL_STATE_INVALID;
+ return;
+ case MM_CALL_STATE_TERMINATED:
+ pa_log_debug("Call terminated");
+ call_state->status = PA_MODEMMANAGER_CALL_STATE_TERMINATED;
+ break;
+ default:
+ pa_assert_not_reached();
+ }
+}
+
+static void parse_call_direction(pa_modemmanager_backend *b, DBusMessageIter *i, const char *call_object) {
+ MMCallDirection direction;
+ call_status_t *call_state;
+
+ pa_assert(i);
+ pa_assert(dbus_message_iter_get_arg_type(i) == DBUS_TYPE_INT32);
+
+ dbus_message_iter_get_basic(i, &direction);
+
+ call_state = pa_hashmap_get(b->calls, call_object);
+ if (!call_state) {
+ return;
+ }
+
+ switch(direction) {
+ case MM_CALL_DIRECTION_UNKNOWN:
+ pa_log_warn("Unknown call direction");
+ return;
+ case MM_CALL_DIRECTION_INCOMING:
+ call_state->is_incoming = true;
+ break;
+ case MM_CALL_DIRECTION_OUTGOING:
+ call_state->is_incoming = false;
+ break;
+ default:
+ pa_assert_not_reached();
+ }
+
+ pa_log_debug("Call direction updated. Is incoming? %s", pa_yes_no(call_state->is_incoming));
+}
+
+static void parse_manufacturer(pa_modemmanager_backend *b, DBusMessageIter *i) {
+ char *manufacturer;
+
+ pa_assert(i);
+ pa_assert(dbus_message_iter_get_arg_type(i) == DBUS_TYPE_STRING);
+
+ dbus_message_iter_get_basic(i, &manufacturer);
+ if (b->modem->modem_manufacturer)
+ pa_xfree(b->modem->modem_manufacturer);
+ b->modem->modem_manufacturer = pa_xstrdup(manufacturer);
+
+ pa_log_debug("Modem manufacturer: %s", b->modem->modem_manufacturer);
+}
+
+static void parse_model(pa_modemmanager_backend *b, DBusMessageIter *i) {
+ char *model;
+
+ pa_assert(i);
+ pa_assert(dbus_message_iter_get_arg_type(i) == DBUS_TYPE_STRING);
+
+ dbus_message_iter_get_basic(i, &model);
+ if (b->modem->modem_model)
+ pa_xfree(b->modem->modem_model);
+ b->modem->modem_model = pa_xstrdup(model);
+
+ pa_log_debug("Modem model: %s", b->modem->modem_model);
+}
+
+static void parse_revision(pa_modemmanager_backend *b, DBusMessageIter *i) {
+ char *revision;
+
+ pa_assert(i);
+ pa_assert(dbus_message_iter_get_arg_type(i) == DBUS_TYPE_STRING);
+
+ dbus_message_iter_get_basic(i, &revision);
+ if (b->modem->modem_revision)
+ pa_xfree(b->modem->modem_revision);
+ b->modem->modem_revision = pa_xstrdup(revision);
+
+ pa_log_debug("Modem revision: %s", b->modem->modem_revision);
+}
+
+static void parse_imei(pa_modemmanager_backend *b, DBusMessageIter *i) {
+ char *imei;
+
+ pa_assert(i);
+ pa_assert(dbus_message_iter_get_arg_type(i) == DBUS_TYPE_STRING);
+
+ dbus_message_iter_get_basic(i, &imei);
+ if (b->modem->modem_imei)
+ pa_xfree(b->modem->modem_imei);
+ b->modem->modem_imei = pa_xstrdup(imei);
+
+ pa_log_debug("Modem IMEI: %s", b->modem->modem_imei);
+}
+
+static void get_modem_properties_reply(DBusPendingCall *pending, void *userdata) {
+ pa_dbus_pending *p;
+ pa_modemmanager_backend *b;
+ DBusMessage *r;
+ DBusMessageIter arg_i, element_i;
+
+ pa_assert(pending);
+ pa_assert_se(p = userdata);
+ pa_assert_se(b = p->context_data);
+ pa_assert_se(r = dbus_pending_call_steal_reply(pending));
+
+ if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) {
+ pa_log_warn("ModemManager D-Bus Modem not available");
+ goto finish;
+ }
+
+ if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
+ pa_log_error("GetAll() failed: %s: %s", dbus_message_get_error_name(r), pa_dbus_get_error_message(r));
+ goto finish;
+ }
+
+ if (!dbus_message_iter_init(r, &arg_i) || !pa_streq(dbus_message_get_signature(r), "a{sv}")) {
+ pa_log_error("Invalid reply signature for GetAll()");
+ goto finish;
+ }
+
+ dbus_message_iter_recurse(&arg_i, &element_i);
+
+ while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
+ DBusMessageIter dict_i, variant_i;
+ const char *key;
+
+ dbus_message_iter_recurse(&element_i, &dict_i);
+
+ /* Retrieve property name */
+ key = check_variant_property(&dict_i);
+ if (key == NULL) {
+ pa_log_error("Received invalid property!");
+ break;
+ }
+
+ /* Retrieve and parse property value */
+ dbus_message_iter_recurse(&dict_i, &variant_i);
+
+ if(pa_streq(key, MM_MODEM_PROPERTY_OWNNUMBERS))
+ parse_own_number(b, &variant_i);
+ else if(pa_streq(key, MM_MODEM_PROPERTY_EQUIPMENTIDENTIFIER))
+ parse_imei(b, &variant_i);
+ else if(pa_streq(key, MM_MODEM_PROPERTY_MANUFACTURER))
+ parse_manufacturer(b, &variant_i);
+ else if(pa_streq(key, MM_MODEM_PROPERTY_MODEL))
+ parse_model(b, &variant_i);
+ else if(pa_streq(key, MM_MODEM_PROPERTY_REVISION))
+ parse_revision(b, &variant_i);
+ else if(pa_streq(key, MM_MODEM_PROPERTY_SIGNALQUALITY))
+ parse_signal_quality(b, &variant_i);
+ else if(pa_streq(key, MM_MODEM_PROPERTY_STATE))
+ parse_state(b, &variant_i);
+
+ dbus_message_iter_next(&element_i);
+ }
+
+finish:
+ dbus_message_unref(r);
+
+ PA_LLIST_REMOVE(pa_dbus_pending, b->pending, p);
+ pa_dbus_pending_free(p);
+}
+
+static void get_modem_3gpp_properties_reply(DBusPendingCall *pending, void *userdata) {
+ pa_dbus_pending *p;
+ pa_modemmanager_backend *b;
+ DBusMessage *r;
+ DBusMessageIter arg_i, element_i;
+
+ pa_assert(pending);
+ pa_assert_se(p = userdata);
+ pa_assert_se(b = p->context_data);
+ pa_assert_se(r = dbus_pending_call_steal_reply(pending));
+
+ if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) {
+ pa_log_warn("ModemManager D-Bus Modem 3GPP not available");
+ goto finish;
+ }
+
+ if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
+ pa_log_error("GetAll() failed: %s: %s", dbus_message_get_error_name(r), pa_dbus_get_error_message(r));
+ goto finish;
+ }
+
+ if (!dbus_message_iter_init(r, &arg_i) || !pa_streq(dbus_message_get_signature(r), "a{sv}")) {
+ pa_log_error("Invalid reply signature for GetAll()");
+ goto finish;
+ }
+
+ dbus_message_iter_recurse(&arg_i, &element_i);
+
+ while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
+ DBusMessageIter dict_i, variant_i;
+ const char *key;
+
+ dbus_message_iter_recurse(&element_i, &dict_i);
+
+ /* Retrieve property name */
+ key = check_variant_property(&dict_i);
+ if (key == NULL) {
+ pa_log_error("Received invalid property!");
+ break;
+ }
+
+ /* Retrieve and parse property value */
+ dbus_message_iter_recurse(&dict_i, &variant_i);
+
+ if(pa_streq(key, MM_MODEM_MODEM3GPP_PROPERTY_REGISTRATIONSTATE))
+ parse_registration(b, &variant_i);
+ else if(pa_streq(key, MM_MODEM_MODEM3GPP_PROPERTY_OPERATORCODE))
+ parse_operator_code(b, &variant_i);
+ else if(pa_streq(key, MM_MODEM_MODEM3GPP_PROPERTY_OPERATORNAME))
+ parse_operator_name(b, &variant_i);
+
+ dbus_message_iter_next(&element_i);
+ }
+
+finish:
+ dbus_message_unref(r);
+
+ PA_LLIST_REMOVE(pa_dbus_pending, b->pending, p);
+ pa_dbus_pending_free(p);
+}
+
+static void get_call_properties_reply(DBusPendingCall *pending, void *userdata) {
+ pa_dbus_pending *p;
+ pa_modemmanager_backend *b;
+ DBusMessage *r;
+ DBusMessageIter arg_i, element_i;
+ char *call_object;
+
+ pa_assert(pending);
+ pa_assert_se(p = userdata);
+ pa_assert_se(b = p->context_data);
+ pa_assert_se(call_object = p->call_data);
+ pa_assert_se(r = dbus_pending_call_steal_reply(pending));
+
+ if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) {
+ pa_log_warn("ModemManager D-Bus Call not available");
+ goto finish;
+ }
+
+ if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
+ pa_log_error("GetAll() failed: %s: %s", dbus_message_get_error_name(r), pa_dbus_get_error_message(r));
+ goto finish;
+ }
+
+ if (!dbus_message_iter_init(r, &arg_i) || !pa_streq(dbus_message_get_signature(r), "a{sv}")) {
+ pa_log_error("Invalid reply signature for GetAll()");
+ goto finish;
+ }
+
+ dbus_message_iter_recurse(&arg_i, &element_i);
+
+ while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
+ DBusMessageIter dict_i, variant_i;
+ const char *key;
+
+ dbus_message_iter_recurse(&element_i, &dict_i);
+
+ /* Retrieve property name */
+ key = check_variant_property(&dict_i);
+ if (key == NULL) {
+ pa_log_error("Received invalid property!");
+ break;
+ }
+
+ /* Retrieve and parse property value */
+ dbus_message_iter_recurse(&dict_i, &variant_i);
+
+ if(pa_streq(key, MM_CALL_PROPERTY_NUMBER))
+ parse_call_number(b, &variant_i, call_object);
+ else if(pa_streq(key, MM_CALL_PROPERTY_STATE))
+ parse_call_state(b, &variant_i, call_object);
+ else if(pa_streq(key, MM_CALL_PROPERTY_DIRECTION))
+ parse_call_direction(b, &variant_i, call_object);
+
+ dbus_message_iter_next(&element_i);
+ }
+
+ /* Notify change for call */
+ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery,
+ PA_BLUETOOTH_HOOK_HOST_CALLS_CHANGED), b);
+
+finish:
+ dbus_message_unref(r);
+
+ PA_LLIST_REMOVE(pa_dbus_pending, b->pending, p);
+ pa_dbus_pending_free(p);
+
+ pa_xfree(call_object);
+}
+
+static void parse_calls(pa_modemmanager_backend *b, DBusMessageIter *i) {
+ const char *mm_call_interface = MM_DBUS_INTERFACE_CALL;
+ const char *call_object;
+ DBusMessageIter element_i;
+ DBusMessage *m;
+
+ pa_assert(i);
+ pa_assert(dbus_message_iter_get_arg_type(i) == DBUS_TYPE_ARRAY);
+
+ /* Iterate over array of dictonaries */
+ dbus_message_iter_recurse(i, &element_i);
+
+ while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_OBJECT_PATH) {
+ dbus_message_iter_get_basic(&element_i, &call_object);
+
+ if (pa_hashmap_put(b->calls, pa_xstrdup(call_object), pa_xnew0(call_status_t, 1)) != 0)
+ pa_log_error("Call %s already exist", call_object);
+
+ /* Retrieve call properties */
+ pa_assert_se(m = dbus_message_new_method_call(MM_DBUS_SERVICE, call_object, DBUS_INTERFACE_PROPERTIES, DBUS_INTERFACE_PROPERTIES_METHOD_GETALL));
+ pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &mm_call_interface, DBUS_TYPE_INVALID));
+ send_and_add_to_pending(b, m, get_call_properties_reply, pa_xstrdup(call_object));
+
+ // FIXME: Implement three-way calling. Only the first call is used.
+ return;
+
+ dbus_message_iter_next(&element_i);
+ }
+}
+
+static void get_modem_voice_properties_reply(DBusPendingCall *pending, void *userdata) {
+ pa_dbus_pending *p;
+ pa_modemmanager_backend *b;
+ DBusMessage *r;
+ DBusMessageIter arg_i, element_i;
+
+ pa_assert(pending);
+ pa_assert_se(p = userdata);
+ pa_assert_se(b = p->context_data);
+ pa_assert_se(r = dbus_pending_call_steal_reply(pending));
+
+ if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) {
+ pa_log_warn("ModemManager D-Bus Modem Voice not available");
+ goto finish;
+ }
+
+ if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
+ pa_log_error("GetAll() failed: %s: %s", dbus_message_get_error_name(r), pa_dbus_get_error_message(r));
+ goto finish;
+ }
+
+ if (!dbus_message_iter_init(r, &arg_i) || !pa_streq(dbus_message_get_signature(r), "a{sv}")) {
+ pa_log_error("Invalid reply signature for GetAll()");
+ goto finish;
+ }
+
+ dbus_message_iter_recurse(&arg_i, &element_i);
+
+ while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
+ DBusMessageIter dict_i, variant_i;
+ const char *key;
+
+ dbus_message_iter_recurse(&element_i, &dict_i);
+
+ /* Retrieve property name */
+ key = check_variant_property(&dict_i);
+ if (key == NULL) {
+ pa_log_error("Received invalid property!");
+ break;
+ }
+
+ /* Retrieve and parse property value */
+ dbus_message_iter_recurse(&dict_i, &variant_i);
+
+ if(pa_streq(key, MM_MODEM_VOICE_PROPERTY_CALLS))
+ parse_calls(b, &variant_i);
+
+ dbus_message_iter_next(&element_i);
+ }
+
+finish:
+ dbus_message_unref(r);
+
+ PA_LLIST_REMOVE(pa_dbus_pending, b->pending, p);
+ pa_dbus_pending_free(p);
+}
+
+static void parse_interfaces_and_properties(pa_modemmanager_backend *b, DBusMessageIter *dict_i) {
+ DBusMessage *m;
+ DBusMessageIter element_i;
+ const char *path;
+ const char *mm_modem_interface = MM_DBUS_INTERFACE_MODEM;
+ const char *mm_modem_3gpp_interface = MM_DBUS_INTERFACE_MODEM_MODEM3GPP;
+ const char *mm_modem_voice_interface = MM_DBUS_INTERFACE_MODEM_VOICE;
+
+ pa_assert(dbus_message_iter_get_arg_type(dict_i) == DBUS_TYPE_OBJECT_PATH);
+ dbus_message_iter_get_basic(dict_i, &path);
+
+ pa_assert_se(dbus_message_iter_next(dict_i));
+ pa_assert(dbus_message_iter_get_arg_type(dict_i) == DBUS_TYPE_ARRAY);
+
+ dbus_message_iter_recurse(dict_i, &element_i);
+
+ while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
+ DBusMessageIter dict_i;
+ const char *interface;
+
+ dbus_message_iter_recurse(&element_i, &dict_i);
+
+ pa_assert(dbus_message_iter_get_arg_type(&dict_i) == DBUS_TYPE_STRING);
+ dbus_message_iter_get_basic(&dict_i, &interface);
+
+ pa_assert_se(dbus_message_iter_next(&dict_i));
+ pa_assert(dbus_message_iter_get_arg_type(&dict_i) == DBUS_TYPE_ARRAY);
+
+ /* ModemManager Modem inteface */
+ if (pa_streq(interface, MM_DBUS_INTERFACE_MODEM)) {
+ pa_log_debug("Modem Interface %s found", path);
+ b->mm_modem = pa_xstrdup(path);
+
+ /* Retrieve all modem properties */
+ pa_assert_se(m = dbus_message_new_method_call(MM_DBUS_SERVICE, b->mm_modem, DBUS_INTERFACE_PROPERTIES, DBUS_INTERFACE_PROPERTIES_METHOD_GETALL));
+ pa_assert_se(dbus_message_append_args(m,
+ DBUS_TYPE_STRING, &mm_modem_interface,
+ DBUS_TYPE_INVALID));
+ send_and_add_to_pending(b, m, get_modem_properties_reply, NULL);
+ /* ModemManager Modem 3GPP interface */
+ } else if (pa_streq(interface, MM_DBUS_INTERFACE_MODEM_MODEM3GPP)) {
+ pa_log_debug("Modem 3GPP Interface %s found", path);
+ b->mm_modem_3gpp = pa_xstrdup(path);
+
+ /* Retrieve all network properties */
+ pa_assert_se(m = dbus_message_new_method_call(MM_DBUS_SERVICE, b->mm_modem_3gpp, DBUS_INTERFACE_PROPERTIES, DBUS_INTERFACE_PROPERTIES_METHOD_GETALL));
+ pa_assert_se(dbus_message_append_args(m,
+ DBUS_TYPE_STRING, &mm_modem_3gpp_interface,
+ DBUS_TYPE_INVALID));
+ send_and_add_to_pending(b, m, get_modem_3gpp_properties_reply, NULL);
+ /* ModemManager Modem Voice interface */
+ } else if (pa_streq(interface, MM_DBUS_INTERFACE_MODEM_VOICE)) {
+ pa_log_debug("Modem Voice Interface %s found", path);
+ b->mm_modem_voice = pa_xstrdup(path);
+
+ /* Retrieve current call list and properties */
+ pa_assert_se(m = dbus_message_new_method_call(MM_DBUS_SERVICE, b->mm_modem_voice, DBUS_INTERFACE_PROPERTIES, DBUS_INTERFACE_PROPERTIES_METHOD_GETALL));
+ pa_assert_se(dbus_message_append_args(m,
+ DBUS_TYPE_STRING, &mm_modem_voice_interface,
+ DBUS_TYPE_INVALID));
+ send_and_add_to_pending(b, m, get_modem_voice_properties_reply, NULL);
+ } else
+ pa_log_debug("Unknown interface %s found, skipping", interface);
+
+ dbus_message_iter_next(&element_i);
+ }
+
+ return;
+}
+
+static void invalidate_modem_properties(pa_modemmanager_backend *b) {
+ if (b->modem) {
+ if (b->modem->modem_manufacturer) {
+ pa_xfree(b->modem->modem_manufacturer);
+ b->modem->modem_manufacturer = NULL;
+ }
+ if (b->modem->modem_model) {
+ pa_xfree(b->modem->modem_model);
+ b->modem->modem_model = NULL;
+ }
+ if (b->modem->modem_revision) {
+ pa_xfree(b->modem->modem_revision);
+ b->modem->modem_revision = NULL;
+ }
+ if (b->modem->modem_imei) {
+ pa_xfree(b->modem->modem_imei);
+ b->modem->modem_imei = NULL;
+ }
+ if (b->modem->modem_own_number) {
+ pa_xfree(b->modem->modem_own_number);
+ b->modem->modem_own_number = NULL;
+ }
+ }
+}
+
+static void invalidate_modem_3gpp_properties(pa_modemmanager_backend *b) {
+ if (b->modem) {
+ if (b->modem->network_operator_name) {
+ pa_xfree(b->modem->network_operator_name);
+ b->modem->network_operator_name = NULL;
+ }
+ if (b->modem->network_operator_code) {
+ pa_xfree(b->modem->network_operator_code);
+ b->modem->network_operator_code = NULL;
+ }
+ b->modem->network_has_service = false;
+ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery,
+ PA_BLUETOOTH_HOOK_HOST_HAS_SERVICE_CHANGED), b);
+ b->modem->network_is_roaming = false;
+ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery,
+ PA_BLUETOOTH_HOOK_HOST_IS_ROAMING_CHANGED), b);
+ b->modem->network_signal_strength = 0;
+ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery,
+ PA_BLUETOOTH_HOOK_HOST_SIGNAL_STRENGTH_CHANGED), b);
+ }
+}
+
+static void invalidate_modem_voice_properties(pa_modemmanager_backend *b) {
+ call_status_t *c = NULL;
+ void *state = NULL;
+
+ /* Remove all call objects */
+ if (b->calls) {
+ while ((c = pa_hashmap_iterate(b->calls, &state, NULL))) {
+ if (!c)
+ continue;
+
+ if (c->number)
+ pa_xfree(c->number);
+ pa_hashmap_remove_and_free(b->calls, c);
+ }
+
+ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery,
+ PA_BLUETOOTH_HOOK_HOST_CALLS_CHANGED), b);
+ }
+}
+
+static void get_managed_objects_reply(DBusPendingCall *pending, void *userdata) {
+ pa_dbus_pending *p;
+ pa_modemmanager_backend *b;
+ DBusMessage *r;
+ DBusMessageIter arg_i, element_i;
+
+ pa_assert(pending);
+ pa_assert_se(p = userdata);
+ pa_assert_se(b = p->context_data);
+ pa_assert_se(r = dbus_pending_call_steal_reply(pending));
+
+ if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) {
+ pa_log_warn("ModemManager D-Bus ObjectManager not available");
+ goto finish;
+ }
+
+ if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
+ pa_log_error("GetManagedObjects() failed: %s: %s", dbus_message_get_error_name(r), pa_dbus_get_error_message(r));
+ goto finish;
+ }
+
+ if (!dbus_message_iter_init(r, &arg_i) || !pa_streq(dbus_message_get_signature(r), "a{oa{sa{sv}}}")) {
+ pa_log_error("Invalid reply signature for GetManagedObjects()");
+ goto finish;
+ }
+
+ dbus_message_iter_recurse(&arg_i, &element_i);
+
+ while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
+ DBusMessageIter dict_i;
+
+ dbus_message_iter_recurse(&element_i, &dict_i);
+ parse_interfaces_and_properties(b, &dict_i);
+ dbus_message_iter_next(&element_i);
+ }
+
+finish:
+ dbus_message_unref(r);
+
+ PA_LLIST_REMOVE(pa_dbus_pending, b->pending, p);
+ pa_dbus_pending_free(p);
+}
+
+static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *data) {
+ DBusError err;
+ DBusMessage *m2;
+ const char *mm_call_interface = MM_DBUS_INTERFACE_CALL;
+ pa_modemmanager_backend *b = data;
+ const char *path, *interface, *member;
+
+ pa_assert(bus);
+ pa_assert(m);
+ pa_assert(b);
+
+ dbus_error_init(&err);
+
+ path = dbus_message_get_path(m);
+ interface = dbus_message_get_interface(m);
+ member = dbus_message_get_member(m);
+
+ pa_log_debug("dbus: path=%s, interface=%s, member=%s", path, interface, member);
+
+ /* ModemManager D-Bus status change */
+ if (dbus_message_is_signal(m, DBUS_INTERFACE_DBUS, DBUS_INTERFACE_DBUS_SIGNAL_NAMEOWNERCHANGED)) {
+ const char *name, *old_owner, *new_owner;
+
+ if (!dbus_message_get_args(m, &err,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_STRING, &old_owner,
+ DBUS_TYPE_STRING, &new_owner,
+ DBUS_TYPE_INVALID)) {
+ pa_log_error("Failed to parse " DBUS_INTERFACE_DBUS ".NameOwnerChanged: %s", err.message);
+ goto fail;
+ }
+
+ if (pa_streq(name, MM_DBUS_SERVICE)) {
+
+ /* ModemManager disappeared from D-Bus */
+ if (old_owner && *old_owner) {
+ pa_log_debug("ModemManager disappeared from D-Bus");
+ if (b->mm_bus_id)
+ pa_xfree(b->mm_bus_id);
+ b->mm_bus_id = NULL;
+ if (b->mm_modem)
+ pa_xfree(b->mm_modem);
+ b->mm_modem = NULL;
+ if (b->mm_modem_3gpp)
+ pa_xfree(b->mm_modem_3gpp);
+ b->mm_modem_3gpp = NULL;
+ if (b->mm_modem_voice)
+ pa_xfree(b->mm_modem_voice);
+ b->mm_modem_voice = NULL;
+
+ /* Invalidate all properties since ModemManager is unavailable */
+ invalidate_modem_properties(b);
+ invalidate_modem_3gpp_properties(b);
+ invalidate_modem_voice_properties(b);
+ }
+
+ /* ModemManager appeared on D-Bus, properties are refreshed through the InterfacesAdded signal callback */
+ if (new_owner && *new_owner) {
+ pa_log_debug("ModemManager appeared on D-Bus");
+ b->mm_bus_id = pa_xstrdup(dbus_message_get_sender(m));
+ if (b->modem)
+ pa_xfree(b->modem);
+ b->modem = pa_xnew0(modem_status_t, 1);
+ }
+ }
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ }
+ /* Modem added, refresh managed objects */
+ else if (dbus_message_is_signal(m, DBUS_INTERFACE_OBJECT_MANAGER, DBUS_INTERFACE_OBJECT_MANAGER_SIGNAL_INTERFACESADDED)) {
+ DBusMessageIter arg_i;
+
+ if (!dbus_message_iter_init(m, &arg_i) || !pa_streq(dbus_message_get_signature(m), "oa{sa{sv}}")) {
+ pa_log_error("Invalid signature found in InterfacesAdded");
+ goto fail;
+ }
+
+ parse_interfaces_and_properties(b, &arg_i);
+ }
+ /* Modem removed, refresh managed objects */
+ else if (dbus_message_is_signal(m, DBUS_INTERFACE_OBJECT_MANAGER, DBUS_INTERFACE_OBJECT_MANAGER_SIGNAL_INTERFACESREMOVED)) {
+ const char *p;
+ DBusMessageIter arg_i;
+ DBusMessageIter element_i;
+
+ if (!dbus_message_iter_init(m, &arg_i) || !pa_streq(dbus_message_get_signature(m), "oas")) {
+ pa_log_error("Invalid signature found in InterfacesRemoved");
+ goto fail;
+ }
+
+ dbus_message_iter_get_basic(&arg_i, &p);
+
+ pa_assert_se(dbus_message_iter_next(&arg_i));
+ pa_assert(dbus_message_iter_get_arg_type(&arg_i) == DBUS_TYPE_ARRAY);
+
+ dbus_message_iter_recurse(&arg_i, &element_i);
+
+ while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_STRING) {
+ const char *iface;
+
+ dbus_message_iter_get_basic(&element_i, &iface);
+
+ /* ModemManager Modem interface removed */
+ if (pa_streq(iface, MM_DBUS_INTERFACE_MODEM)) {
+ if (b->mm_modem) {
+ pa_xfree(b->mm_modem);
+ b->mm_modem = NULL;
+ }
+ invalidate_modem_properties(b);
+ pa_log_debug("Modem Interface removed");
+ }
+
+ /* ModemManager Modem 3GPP interface removed */
+ if (pa_streq(iface, MM_DBUS_INTERFACE_MODEM_MODEM3GPP)) {
+ if (b->mm_modem_3gpp) {
+ pa_xfree(b->mm_modem_3gpp);
+ b->mm_modem_3gpp = NULL;
+ }
+ invalidate_modem_3gpp_properties(b);
+ pa_log_debug("Modem 3GPP Interface removed");
+ }
+
+ /* ModemManager Modem Voice interface removed */
+ if (pa_streq(iface, MM_DBUS_INTERFACE_MODEM_VOICE)) {
+ if (b->mm_modem_voice) {
+ pa_xfree(b->mm_modem_voice);
+ b->mm_modem_voice = NULL;
+ }
+ invalidate_modem_voice_properties(b);
+ pa_log_debug("Modem Voice Interface removed");
+ }
+
+ dbus_message_iter_next(&element_i);
+ }
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ }
+ /* Update properties if updated by ModemManager */
+ else if (dbus_message_is_signal(m, DBUS_INTERFACE_PROPERTIES, DBUS_INTERFACE_PROPERTIES_SIGNAL_PROPERTIESCHANGED)) {
+ DBusMessageIter arg_i, element_i;
+
+ if (!dbus_message_iter_init(m, &arg_i) || !pa_streq(dbus_message_get_signature(m), "sa{sv}as")) {
+ pa_log_error("Invalid signature found in PropertiesChanged");
+ goto fail;
+ }
+
+ /* Skip interface name */
+ pa_assert_se(dbus_message_iter_next(&arg_i));
+ pa_assert(dbus_message_iter_get_arg_type(&arg_i) == DBUS_TYPE_ARRAY);
+
+ dbus_message_iter_recurse(&arg_i, &element_i);
+
+ while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
+ DBusMessageIter dict_i, variant_i;
+ const char *key;
+
+ dbus_message_iter_recurse(&element_i, &dict_i);
+
+ /* Retrieve property name and parse value */
+ key = check_variant_property(&dict_i);
+ if (key == NULL) {
+ pa_log_error("Received invalid property!");
+ break;
+ }
+ dbus_message_iter_recurse(&dict_i, &variant_i);
+
+ /*
+ * ModemManager Modem interface
+ * See https://www.freedesktop.org/software/ModemManager/api/latest/gdbus-org.freedesktop.ModemManager1.Modem.html
+ */
+ if(b->mm_modem && pa_streq(path, b->mm_modem)) {
+ pa_log_debug("ModemManager Modem property updated: %s", key);
+
+ if(pa_streq(key, MM_MODEM_PROPERTY_OWNNUMBERS))
+ parse_own_number(b, &variant_i);
+ else if(pa_streq(key, MM_MODEM_PROPERTY_EQUIPMENTIDENTIFIER))
+ parse_imei(b, &variant_i);
+ else if(pa_streq(key, MM_MODEM_PROPERTY_MANUFACTURER))
+ parse_manufacturer(b, &variant_i);
+ else if(pa_streq(key, MM_MODEM_PROPERTY_MODEL))
+ parse_model(b, &variant_i);
+ else if(pa_streq(key, MM_MODEM_PROPERTY_REVISION))
+ parse_revision(b, &variant_i);
+ else if(pa_streq(key, MM_MODEM_PROPERTY_SIGNALQUALITY))
+ parse_signal_quality(b, &variant_i);
+ else if(pa_streq(key, MM_MODEM_PROPERTY_STATE))
+ parse_state(b, &variant_i);
+ }
+
+ /*
+ * ModemManager Modem 3GPP interface
+ * See https://www.freedesktop.org/software/ModemManager/api/latest/gdbus-org.freedesktop.ModemManager1.Modem.Modem3gpp.html
+ */
+ if(b->mm_modem_3gpp && pa_streq(path, b->mm_modem_3gpp)) {
+ pa_log_debug("ModemManager Modem 3GPP property updated: %s", key);
+
+ if(pa_streq(key, MM_MODEM_MODEM3GPP_PROPERTY_REGISTRATIONSTATE))
+ parse_registration(b, &variant_i);
+ else if(pa_streq(key, MM_MODEM_MODEM3GPP_PROPERTY_OPERATORCODE))
+ parse_operator_code(b, &variant_i);
+ else if(pa_streq(key, MM_MODEM_MODEM3GPP_PROPERTY_OPERATORNAME))
+ parse_operator_name(b, &variant_i);
+ }
+
+ dbus_message_iter_next(&element_i);
+ }
+ }
+ /* Call started */
+ else if (dbus_message_is_signal(m, MM_DBUS_INTERFACE_MODEM_VOICE, MM_MODEM_VOICE_SIGNAL_CALLADDED)) {
+ DBusMessageIter arg_i;
+ char *call_object;
+
+ if (!dbus_message_iter_init(m, &arg_i) || !pa_streq(dbus_message_get_signature(m), "o")) {
+ pa_log_error("Invalid signature found in CallAdded");
+ goto fail;
+ }
+
+ /* Create call state */
+ dbus_message_iter_get_basic(&arg_i, &call_object);
+ pa_log_debug("Call added: %s", call_object);
+
+ if (pa_hashmap_put(b->calls, pa_xstrdup(call_object), pa_xnew0(call_status_t, 1)) != 0)
+ pa_log_error("Call %s already exist", call_object);
+
+ /* Retrieve call properties */
+ pa_assert_se(m2 = dbus_message_new_method_call(MM_DBUS_SERVICE, call_object, DBUS_INTERFACE_PROPERTIES, DBUS_INTERFACE_PROPERTIES_METHOD_GETALL));
+ pa_assert_se(dbus_message_append_args(m2, DBUS_TYPE_STRING, &mm_call_interface, DBUS_TYPE_INVALID));
+ send_and_add_to_pending(b, m2, get_call_properties_reply, pa_xstrdup(call_object));
+ }
+ /* Call rejected or ended */
+ else if (dbus_message_is_signal(m, MM_DBUS_INTERFACE_MODEM_VOICE, MM_MODEM_VOICE_SIGNAL_CALLDELETED)) {
+ DBusMessageIter arg_i;
+ char *call_object;
+
+ if (!dbus_message_iter_init(m, &arg_i) || !pa_streq(dbus_message_get_signature(m), "o")) {
+ pa_log_error("Invalid signature found in CallDeleted");
+ goto fail;
+ }
+
+ /* Remove call state */
+ dbus_message_iter_get_basic(&arg_i, &call_object);
+ pa_log_debug("Call removed: %s", call_object);
+ pa_hashmap_remove_and_free(b->calls, call_object);
+
+ /* Notify about call deletion */
+ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery,
+ PA_BLUETOOTH_HOOK_HOST_CALLS_CHANGED), b);
+ }
+ /* Call state updated */
+ else if (dbus_message_is_signal(m, MM_DBUS_INTERFACE_CALL, MM_CALL_SIGNAL_STATECHANGED)) {
+ DBusMessageIter arg_i;
+
+ pa_log_debug("Call %s: state updated", path);
+
+ if (!dbus_message_iter_init(m, &arg_i) || !pa_streq(dbus_message_get_signature(m), "iiu")) {
+ pa_log_error("Invalid signature found in StateChanged");
+ goto fail;
+ }
+
+ /* Skip old call state */
+ pa_assert_se(dbus_message_iter_next(&arg_i));
+
+ /* Parse new call state */
+ pa_assert(dbus_message_iter_get_arg_type(&arg_i) == DBUS_TYPE_INT32);
+ parse_call_state(b, &arg_i, path);
+
+ /* Skip call state change reason */
+ pa_assert_se(dbus_message_iter_next(&arg_i));
+
+ /* Notify change for call */
+ pa_hook_fire(pa_bluetooth_discovery_hook(b->discovery,
+ PA_BLUETOOTH_HOOK_HOST_CALLS_CHANGED), b);
+ }
+
+fail:
+ dbus_error_free(&err);
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+/* Accept an incoming call */
+void pa_modemmanager_accept_call(pa_modemmanager_backend *backend, char *call) {
+ DBusMessage *m;
+ call_status_t *call_state;
+
+ /* Check if call is incoming ringing */
+ call_state = pa_hashmap_get(backend->calls, call);
+ if (!call_state || call_state->status != PA_MODEMMANAGER_CALL_STATE_RINGING || !call_state->is_incoming) {
+ pa_log_error("Call is not ringing and/or incoming, unable to accept call");
+ pa_hook_fire(pa_bluetooth_discovery_hook(backend->discovery,
+ PA_BLUETOOTH_HOOK_HOST_OPERATION_FAILED), backend);
+ return;
+ }
+
+ /* Accept call */
+ pa_assert_se(m = dbus_message_new_method_call(MM_DBUS_SERVICE, call, MM_DBUS_INTERFACE_CALL, MM_CALL_METHOD_ACCEPT));
+ send_and_add_to_pending(backend, m, call_accept_reply, NULL);
+}
+
+/* End a call */
+void pa_modemmanager_end_call(pa_modemmanager_backend *backend, char *call) {
+ DBusMessage *m;
+ call_status_t *call_state;
+
+ /* Check if call is active or incoming ringing */
+ call_state = pa_hashmap_get(backend->calls, call);
+ if (!call_state || (call_state->status != PA_MODEMMANAGER_CALL_STATE_ACTIVE
+ && (call_state->status != PA_MODEMMANAGER_CALL_STATE_RINGING && call_state->is_incoming))) {
+ pa_log_error("Call is not active or incoming ringing, unable to hangup call");
+ pa_hook_fire(pa_bluetooth_discovery_hook(backend->discovery,
+ PA_BLUETOOTH_HOOK_HOST_OPERATION_FAILED), backend);
+ return;
+ }
+
+ /* End call */
+ pa_assert_se(m = dbus_message_new_method_call(MM_DBUS_SERVICE, call, MM_DBUS_INTERFACE_CALL, MM_CALL_METHOD_HANGUP));
+ send_and_add_to_pending(backend, m, call_hangup_reply, NULL);
+}
+
+/* Create a new call by dialing the provided number */
+void pa_modemmanager_start_call(pa_modemmanager_backend *backend, char *number) {
+ DBusMessage *m;
+ DBusMessageIter i, d;
+ char *call_number;
+
+ /* Create call for filtered number */
+ pa_assert_se(m = dbus_message_new_method_call(MM_DBUS_SERVICE, backend->mm_modem, MM_DBUS_INTERFACE_MODEM_VOICE, MM_MODEM_VOICE_METHOD_CREATECALL));
+ dbus_message_iter_init_append(m, &i);
+ dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING
+ DBUS_TYPE_VARIANT_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
+ &d);
+ call_number = pa_xstrdup(number);
+ pa_dbus_append_basic_variant_dict_entry(&d, "number", DBUS_TYPE_STRING, &call_number);
+ dbus_message_iter_close_container(&i, &d);
+ send_and_add_to_pending(backend, m, create_call_reply, NULL);
+}
+
+/* Send a DTMF tone to a given call */
+void pa_modemmanager_send_dtmf(pa_modemmanager_backend *backend, char *call, char *character) {
+ DBusMessage *m;
+ char *dtmf_char = pa_xstrdup(character);
+ call_status_t *call_state;
+
+ /* Check if call is active */
+ call_state = pa_hashmap_get(backend->calls, call);
+ if (call_state && (call_state->status != PA_MODEMMANAGER_CALL_STATE_ACTIVE)) {
+ pa_log_error("Call is not active, unable to send DTMF tone");
+ pa_hook_fire(pa_bluetooth_discovery_hook(backend->discovery,
+ PA_BLUETOOTH_HOOK_HOST_OPERATION_FAILED), backend);
+ return;
+ }
+
+ /* Let ModemManager generate DTMF tone */
+ pa_log_debug("Generating DTMF tone: '%s'", dtmf_char);
+ m = dbus_message_new_method_call(MM_DBUS_SERVICE, call, MM_DBUS_INTERFACE_CALL, MM_CALL_METHOD_SENDDTMF);
+ pa_assert_se(dbus_message_append_args(m,
+ DBUS_TYPE_STRING, &dtmf_char,
+ DBUS_TYPE_INVALID));
+ send_and_add_to_pending(backend, m, send_dtmf_reply, NULL);
+}
+
+/* Get network operator name */
+char *pa_modemmanager_get_operator_name(const pa_modemmanager_backend *backend) {
+ return backend->modem->network_operator_name;
+}
+
+/* Get network operator code */
+char *pa_modemmanager_get_operator_code(const pa_modemmanager_backend *backend) {
+ return backend->modem->network_operator_code;
+}
+
+/* Get service status */
+bool pa_modemmanager_has_service(const pa_modemmanager_backend *backend) {
+ return backend->modem->network_has_service;
+}
+
+/* Get roaming status */
+bool pa_modemmanager_is_roaming(const pa_modemmanager_backend *backend) {
+ return backend->modem->network_is_roaming;
+}
+
+/* Get signal strength percentage */
+unsigned int pa_modemmanager_get_signal_strength(const pa_modemmanager_backend *backend) {
+ return backend->modem->network_signal_strength;
+}
+
+/* Get all calls */
+pa_hashmap *pa_modemmanager_get_calls(const pa_modemmanager_backend *backend) {
+ return backend->calls;
+}
+
+/* Get active call */
+char *pa_modemmanager_get_active_call_key(const pa_modemmanager_backend *backend) {
+ pa_hashmap *calls;
+ call_status_t *c;
+ const void *key;
+ void *state = NULL;
+
+ calls = pa_modemmanager_get_calls(backend);
+ while ((c = pa_hashmap_iterate(calls, &state, &key))) {
+ if (key == NULL)
+ break;
+ /* FIXME: support three-way calling */
+ return (char *) key;
+ }
+
+ /* No active calls */
+ return NULL;
+}
+
+/* Get modem model */
+char *pa_modemmanager_get_modem_model(const pa_modemmanager_backend *backend) {
+ return backend->modem->modem_model;
+}
+
+/* Get modem manufacturer */
+char *pa_modemmanager_get_modem_manufacturer(const pa_modemmanager_backend *backend) {
+ return backend->modem->modem_manufacturer;
+}
+
+/* Get modem revision */
+char *pa_modemmanager_get_modem_revision(const pa_modemmanager_backend *backend) {
+ return backend->modem->modem_revision;
+}
+
+/* Get modem IMEI */
+char *pa_modemmanager_get_modem_imei(const pa_modemmanager_backend *backend) {
+ return backend->modem->modem_imei;
+}
+
+/* Get modem's own number */
+char *pa_modemmanager_get_own_number(const pa_modemmanager_backend *backend) {
+ return backend->modem->modem_own_number;
+}
+
+/* Check if modem is present */
+bool pa_modemmanager_has_modem(const pa_modemmanager_backend *backend) {
+ if (!backend->mm_modem)
+ return false;
+
+ if (!backend->mm_modem_3gpp)
+ return false;
+
+ if (!backend->mm_modem_voice)
+ return false;
+
+ if(!backend->modem)
+ return false;
+
+ return true;
+}
+
+pa_modemmanager_backend *pa_modemmanager_backend_new(pa_core *c, pa_bluetooth_discovery *d) {
+ pa_modemmanager_backend *backend;
+ DBusError err;
+ DBusMessage *m;
+
+ pa_log_debug("Native backend enabled ModemManager modem & call status reporting");
+
+ backend = pa_xnew0(pa_modemmanager_backend, 1);
+ backend->core = c;
+ backend->discovery = d;
+ backend->calls = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+ backend->modem = pa_xnew0(modem_status_t, 1);
+
+ /* Get DBus connection */
+ dbus_error_init(&err);
+ if (!(backend->connection = pa_dbus_bus_get(c, DBUS_BUS_SYSTEM, &err))) {
+ pa_log("Failed to get D-Bus connection: %s", err.message);
+ dbus_error_free(&err);
+ pa_xfree(backend);
+ return NULL;
+ }
+
+ /* Add filter callback for DBus connection */
+ if (!dbus_connection_add_filter(pa_dbus_connection_get(backend->connection), filter_cb, backend, NULL)) {
+ pa_log_error("Failed to add filter function");
+ pa_dbus_connection_unref(backend->connection);
+ pa_xfree(backend);
+ return NULL;
+ }
+
+ /* Register for call & modem changes from ModemManager */
+ if (pa_dbus_add_matches(pa_dbus_connection_get(backend->connection), &err,
+ "type='signal',sender='" DBUS_SERVICE_DBUS "',interface='" DBUS_INTERFACE_DBUS "',member='" DBUS_INTERFACE_DBUS_SIGNAL_NAMEOWNERCHANGED "',"
+ "arg0='" MM_DBUS_SERVICE "'",
+ "type='signal',sender='" MM_DBUS_SERVICE "',interface='" DBUS_INTERFACE_OBJECT_MANAGER "',member='" DBUS_INTERFACE_OBJECT_MANAGER_SIGNAL_INTERFACESADDED "'",
+ "type='signal',sender='" MM_DBUS_SERVICE "',interface='" DBUS_INTERFACE_OBJECT_MANAGER "',member='" DBUS_INTERFACE_OBJECT_MANAGER_SIGNAL_INTERFACESREMOVED "'",
+ "type='signal',sender='" MM_DBUS_SERVICE "',interface='" DBUS_INTERFACE_PROPERTIES "',member='" DBUS_INTERFACE_PROPERTIES_SIGNAL_PROPERTIESCHANGED "'",
+ "type='signal',sender='" MM_DBUS_SERVICE "',interface='" MM_DBUS_INTERFACE_MODEM_VOICE "',member='" MM_MODEM_VOICE_SIGNAL_CALLADDED "'",
+ "type='signal',sender='" MM_DBUS_SERVICE "',interface='" MM_DBUS_INTERFACE_MODEM_VOICE "',member='" MM_MODEM_VOICE_SIGNAL_CALLDELETED "'",
+ "type='signal',sender='" MM_DBUS_SERVICE "',interface='" MM_DBUS_INTERFACE_CALL "',member='" MM_CALL_SIGNAL_STATECHANGED "'",
+ NULL) < 0) {
+ pa_log("Failed to add ModemManager D-Bus matches: %s", err.message);
+ dbus_connection_remove_filter(pa_dbus_connection_get(backend->connection), filter_cb, backend);
+ pa_dbus_connection_unref(backend->connection);
+ pa_xfree(backend);
+ return NULL;
+ }
+
+ /* Initialize list of modems by requesting it from ModemManager */
+ pa_assert_se(m = dbus_message_new_method_call(MM_DBUS_SERVICE, MM_DBUS_PATH, DBUS_INTERFACE_OBJECT_MANAGER, DBUS_INTERFACE_OBJECT_MANAGER_METHOD_GETMANAGEDOBJECTS));
+ send_and_add_to_pending(backend, m, get_managed_objects_reply, NULL);
+
+ return backend;
+}
+
+void pa_modemmanager_backend_free(pa_modemmanager_backend *backend) {
+ pa_assert(backend);
+
+ pa_dbus_free_pending_list(&backend->pending);
+
+ pa_dbus_connection_unref(backend->connection);
+
+ invalidate_modem_properties(backend);
+ invalidate_modem_3gpp_properties(backend);
+ invalidate_modem_voice_properties(backend);
+
+ pa_xfree(backend);
+}
+
diff --git a/src/modules/bluetooth/modemmanager.h b/src/modules/bluetooth/modemmanager.h
new file mode 100644
index 000000000..9a1d82219
--- /dev/null
+++ b/src/modules/bluetooth/modemmanager.h
@@ -0,0 +1,98 @@
+#pragma once
+
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2022 Dylan Van Assche <me@dylanvanassche.be>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "bluez5-util.h"
+#include "ModemManager.h"
+
+#define DBUS_INTERFACE_OBJECT_MANAGER "org.freedesktop.DBus.ObjectManager"
+#define DBUS_INTERFACE_OBJECT_MANAGER_METHOD_GETMANAGEDOBJECTS "GetManagedObjects"
+#define DBUS_INTERFACE_OBJECT_MANAGER_SIGNAL_INTERFACESADDED "InterfacesAdded"
+#define DBUS_INTERFACE_OBJECT_MANAGER_SIGNAL_INTERFACESREMOVED "InterfacesRemoved"
+#define DBUS_INTERFACE_DBUS_SIGNAL_NAMEOWNERCHANGED "NameOwnerChanged"
+#define DBUS_INTERFACE_PROPERTIES_METHOD_GETALL "GetAll"
+#define DBUS_INTERFACE_PROPERTIES_SIGNAL_PROPERTIESCHANGED "PropertiesChanged"
+
+#define MAX_NUMBER_LENGTH 30
+
+#define STR_VALUE(x) STR(x)
+#define STR(x) #x
+
+typedef enum pa_modemmanager_call_state {
+ PA_MODEMMANAGER_CALL_STATE_INVALID = 0,
+ PA_MODEMMANAGER_CALL_STATE_ACTIVE = 1,
+ PA_MODEMMANAGER_CALL_STATE_RINGING = 2,
+ PA_MODEMMANAGER_CALL_STATE_DIALING = 3,
+ PA_MODEMMANAGER_CALL_STATE_TERMINATED = 4
+ /* FIXME: implement three-way calling */
+} pa_modemmanager_call_state_t;
+
+typedef struct call_status {
+ bool is_incoming;
+ pa_modemmanager_call_state_t status;
+ char *number;
+} call_status_t;
+
+typedef struct modem_status {
+ char *network_operator_name;
+ char *network_operator_code;
+ unsigned int network_signal_strength;
+ bool network_has_service;
+ bool network_is_roaming;
+ char *modem_own_number;
+ char *modem_manufacturer;
+ char *modem_model;
+ char *modem_revision;
+ char *modem_imei;
+} modem_status_t;
+
+struct pa_modemmanager_backend {
+ pa_core *core;
+ pa_dbus_connection *connection;
+ pa_bluetooth_discovery *discovery;
+ char *mm_bus_id;
+ char *mm_modem;
+ char *mm_modem_3gpp;
+ char *mm_modem_voice;
+ pa_hashmap *calls;
+ modem_status_t *modem;
+
+ PA_LLIST_HEAD(pa_dbus_pending, pending);
+};
+
+pa_modemmanager_backend *pa_modemmanager_backend_new(pa_core *c, pa_bluetooth_discovery *d);
+void pa_modemmanager_backend_free(pa_modemmanager_backend *backend);
+bool pa_modemmanager_has_modem(const pa_modemmanager_backend *backend);
+void pa_modemmanager_accept_call(pa_modemmanager_backend *backend, char *call);
+void pa_modemmanager_end_call(pa_modemmanager_backend *backend, char *call);
+void pa_modemmanager_start_call(pa_modemmanager_backend *backend, char *number);
+void pa_modemmanager_send_dtmf(pa_modemmanager_backend *backend, char *call, char *character);
+pa_hashmap *pa_modemmanager_get_calls(const pa_modemmanager_backend *backend);
+char *pa_modemmanager_get_active_call_key(const pa_modemmanager_backend *backend);
+char *pa_modemmanager_get_operator_name(const pa_modemmanager_backend *backend);
+char *pa_modemmanager_get_operator_code(const pa_modemmanager_backend *backend);
+bool pa_modemmanager_has_service(const pa_modemmanager_backend *backend);
+bool pa_modemmanager_is_roaming(const pa_modemmanager_backend *backend);
+unsigned int pa_modemmanager_get_signal_strength(const pa_modemmanager_backend *backend);
+char *pa_modemmanager_get_modem_model(const pa_modemmanager_backend *backend);
+char *pa_modemmanager_get_modem_manufacturer(const pa_modemmanager_backend *backend);
+char *pa_modemmanager_get_modem_revision(const pa_modemmanager_backend *backend);
+char *pa_modemmanager_get_modem_imei(const pa_modemmanager_backend *backend);
+char *pa_modemmanager_get_own_number(const pa_modemmanager_backend *backend);
--
2.35.1