1713 lines
64 KiB
Diff
1713 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
|
||
|
|