f9c7ffa9b6
PulseAudio is used for handling all audio on postmarketOS. This also involves Bluetooth audio such as A2DP, HSP and HFP audio. In the case of HFP/HSP, the HF and AG can interact with each other through AT commands defined in the Bluetooth HFP 1.8 spec. This set of patches implements HFP support to allow Bluetooth devices to accept/reject/hangup calls, dial numbers, DTMF tone generation, query signal strength, roaming status, service status, AG battery level, call status, etc. More details in the upstream MR: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/693 Available in edge for testing this merge request with a broader user base. Not intended for backporting to stable branches. [ci:skip-build]: already built successfully in CI
1712 lines
64 KiB
Diff
1712 lines
64 KiB
Diff
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, ®istration);
|
|
+
|
|
+ /* 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
|
|
|