From 8392f30388b04cd0ab8fd41bca9d7788249c1c1b Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Fri, 25 Apr 2025 21:32:14 -0500 Subject: base-call: split AT-specific call code into MMCallAt Signed-off-by: Dan Williams --- src/mm-call-at.c | 422 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 422 insertions(+) create mode 100644 src/mm-call-at.c (limited to 'src/mm-call-at.c') diff --git a/src/mm-call-at.c b/src/mm-call-at.c new file mode 100644 index 00000000..65cb3b7e --- /dev/null +++ b/src/mm-call-at.c @@ -0,0 +1,422 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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: + * + * Copyright (C) 2015 Riccardo Vangelisti + * Copyright (C) 2019 Aleksander Morgado + * Copyright (C) 2019 Purism SPC + */ + +#include +#include +#include +#include +#include +#include + +#include +#define _LIBMM_INSIDE_MM +#include + +#include "mm-call-at.h" +#include "mm-base-modem-at.h" +#include "mm-base-modem.h" +#include "mm-modem-helpers.h" +#include "mm-error-helpers.h" +#include "mm-log-object.h" +#include "mm-bind.h" + +G_DEFINE_TYPE (MMCallAt, mm_call_at, MM_TYPE_BASE_CALL) + +struct _MMCallAtPrivate { + /* The modem which owns this call */ + MMBaseModem *modem; +}; + +/*****************************************************************************/ +/* Start the CALL */ + +static gboolean +call_start_finish (MMBaseCall *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +call_start_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + const gchar *response = NULL; + + response = mm_base_modem_at_command_finish (modem, res, &error); + + /* check response for error */ + if (response && response[0]) + error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't start the call: Unhandled response '%s'", response); + + if (error) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +call_start (MMBaseCall *_self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMCallAt *self = MM_CALL_AT (_self); + GError *error = NULL; + GTask *task; + gchar *cmd; + MMIfacePortAt *port; + + task = g_task_new (self, NULL, callback, user_data); + + port = mm_base_modem_peek_best_at_port (self->priv->modem, &error); + if (!port) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + cmd = g_strdup_printf ("ATD%s;", mm_gdbus_call_get_number (MM_GDBUS_CALL (self))); + mm_base_modem_at_command_full (self->priv->modem, + port, + cmd, + 90, + FALSE, /* no cached */ + FALSE, /* no raw */ + cancellable, + (GAsyncReadyCallback)call_start_ready, + task); + g_free (cmd); +} + +/*****************************************************************************/ +/* Accept the call */ + +static gboolean +call_accept_finish (MMBaseCall *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +call_accept_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + const gchar *response; + + response = mm_base_modem_at_command_finish (modem, res, &error); + + /* check response for error */ + if (response && response[0]) + g_set_error (&error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't accept the call: Unhandled response '%s'", response); + + if (error) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +call_accept (MMBaseCall *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMCallAt *self = MM_CALL_AT (_self); + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + mm_base_modem_at_command (self->priv->modem, + "ATA", + 2, + FALSE, + (GAsyncReadyCallback)call_accept_ready, + task); +} + +/*****************************************************************************/ +/* Deflect the call */ + +static gboolean +call_deflect_finish (MMBaseCall *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +call_deflect_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + mm_base_modem_at_command_finish (modem, res, &error); + if (error) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +call_deflect (MMBaseCall *_self, + const gchar *number, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMCallAt *self = MM_CALL_AT (_self); + GTask *task; + gchar *cmd; + + task = g_task_new (self, NULL, callback, user_data); + + cmd = g_strdup_printf ("+CTFR=%s", number); + mm_base_modem_at_command (self->priv->modem, + cmd, + 20, + FALSE, + (GAsyncReadyCallback)call_deflect_ready, + task); + g_free (cmd); +} + +/*****************************************************************************/ +/* Hangup the call */ + +static gboolean +call_hangup_finish (MMBaseCall *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +chup_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + mm_base_modem_at_command_finish (modem, res, &error); + if (error) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +chup_fallback (GTask *task) +{ + MMCallAt *self; + + self = MM_CALL_AT (g_task_get_source_object (task)); + mm_base_modem_at_command (self->priv->modem, + "+CHUP", + 2, + FALSE, + (GAsyncReadyCallback)chup_ready, + task); +} + +static void +chld_hangup_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + MMBaseCall *self; + GError *error = NULL; + + self = g_task_get_source_object (task); + + mm_base_modem_at_command_finish (modem, res, &error); + if (error) { + mm_obj_warn (self, "couldn't hangup single call with call id '%u': %s", + mm_base_call_get_index (MM_BASE_CALL (self)), error->message); + g_error_free (error); + chup_fallback (task); + return; + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +call_hangup (MMBaseCall *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMCallAt *self = MM_CALL_AT (_self); + GTask *task; + guint index; + + task = g_task_new (self, NULL, callback, user_data); + + /* Try to hangup the single call id */ + index = mm_base_call_get_index (MM_BASE_CALL (self)); + if (index) { + gchar *cmd; + + cmd = g_strdup_printf ("+CHLD=1%u", index); + mm_base_modem_at_command (self->priv->modem, + cmd, + 2, + FALSE, + (GAsyncReadyCallback)chld_hangup_ready, + task); + g_free (cmd); + return; + } + + /* otherwise terminate all */ + chup_fallback (task); +} + +/*****************************************************************************/ +/* Send DTMF tone to call */ + +static gboolean +call_send_dtmf_finish (MMBaseCall *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +call_send_dtmf_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + MMBaseCall *self; + GError *error = NULL; + + self = g_task_get_source_object (task); + + mm_base_modem_at_command_finish (modem, res, &error); + if (error) { + mm_obj_dbg (self, "couldn't send dtmf: %s", error->message); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +call_send_dtmf (MMBaseCall *_self, + const gchar *dtmf, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMCallAt *self = MM_CALL_AT (_self); + GTask *task; + gchar *cmd; + + task = g_task_new (self, NULL, callback, user_data); + + cmd = g_strdup_printf ("AT+VTS=%c", dtmf[0]); + mm_base_modem_at_command (self->priv->modem, + cmd, + 3, + FALSE, + (GAsyncReadyCallback)call_send_dtmf_ready, + task); + + g_free (cmd); +} + +/*****************************************************************************/ + +MMBaseCall * +mm_call_at_new (MMBaseModem *modem, + GObject *bind_to, + MMCallDirection direction, + const gchar *number, + gboolean skip_incoming_timeout, + gboolean supports_dialing_to_ringing, + gboolean supports_ringing_to_active) +{ + MMBaseCall *call; + + call = MM_BASE_CALL (g_object_new (MM_TYPE_CALL_AT, + MM_BASE_CALL_MODEM, modem, + MM_BIND_TO, bind_to, + "direction", direction, + "number", number, + MM_BASE_CALL_SKIP_INCOMING_TIMEOUT, skip_incoming_timeout, + MM_BASE_CALL_SUPPORTS_DIALING_TO_RINGING, supports_dialing_to_ringing, + MM_BASE_CALL_SUPPORTS_RINGING_TO_ACTIVE, supports_ringing_to_active, + NULL)); + MM_CALL_AT (call)->priv->modem = g_object_ref (modem); + return call; +} + +static void +mm_call_at_init (MMCallAt *self) +{ + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_CALL_AT, MMCallAtPrivate); +} + +static void +dispose (GObject *object) +{ + MMCallAt *self = MM_CALL_AT (object); + + g_clear_object (&self->priv->modem); + + G_OBJECT_CLASS (mm_call_at_parent_class)->dispose (object); +} + +static void +mm_call_at_class_init (MMCallAtClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMBaseCallClass *base_call_class = MM_BASE_CALL_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMCallAtPrivate)); + + object_class->dispose = dispose; + + base_call_class->start = call_start; + base_call_class->start_finish = call_start_finish; + base_call_class->accept = call_accept; + base_call_class->accept_finish = call_accept_finish; + base_call_class->deflect = call_deflect; + base_call_class->deflect_finish = call_deflect_finish; + base_call_class->hangup = call_hangup; + base_call_class->hangup_finish = call_hangup_finish; + base_call_class->send_dtmf = call_send_dtmf; + base_call_class->send_dtmf_finish = call_send_dtmf_finish; +} -- cgit v1.2.3-70-g09d2 From b9c49c7b4dd93d3f66f27cc13110ba672f7862df Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Sat, 26 Apr 2025 06:21:20 -0500 Subject: base-call: remove usage of MMBaseModem Split out the things we actually use from MMBaseModem into separate properties that can be independently implemented (or just left NULL) for unit tests. Signed-off-by: Dan Williams --- src/mm-base-call.c | 61 ++++++++++++++++-------------------------------------- src/mm-base-call.h | 13 ++---------- src/mm-call-at.c | 8 +++---- src/mm-call-qmi.c | 43 ++++++++++++++++++++++++++++---------- src/mm-call-qmi.h | 2 ++ 5 files changed, 58 insertions(+), 69 deletions(-) (limited to 'src/mm-call-at.c') diff --git a/src/mm-base-call.c b/src/mm-base-call.c index e0158495..a59aca85 100644 --- a/src/mm-base-call.c +++ b/src/mm-base-call.c @@ -29,10 +29,7 @@ #include "mm-base-call.h" #include "mm-broadband-modem.h" #include "mm-auth-provider.h" -#include "mm-iface-modem.h" #include "mm-iface-modem-voice.h" -#include "mm-base-modem-at.h" -#include "mm-base-modem.h" #include "mm-log-object.h" #include "mm-modem-helpers.h" #include "mm-error-helpers.h" @@ -50,7 +47,7 @@ enum { PROP_PATH, PROP_CONNECTION, PROP_BIND_TO, - PROP_MODEM, + PROP_IFACE_MODEM_VOICE, PROP_SKIP_INCOMING_TIMEOUT, PROP_SUPPORTS_DIALING_TO_RINGING, PROP_SUPPORTS_RINGING_TO_ACTIVE, @@ -71,8 +68,8 @@ struct _MMBaseCallPrivate { /* The object this Call is bound to */ GObject *bind_to; - /* The modem which owns this call */ - MMBaseModem *modem; + /* The voice interface which owns this call */ + MMIfaceModemVoice *iface; /* The path where the call object is exported */ gchar *path; /* Features */ @@ -240,7 +237,7 @@ handle_start_auth_ready (MMAuthProvider *authp, mm_obj_info (ctx->self, "processing user request to start voice call..."); /* Disallow non-emergency calls when in emergency-only state */ - if (!mm_iface_modem_voice_authorize_outgoing_call (MM_IFACE_MODEM_VOICE (ctx->self->priv->modem), ctx->self, &error)) { + if (!mm_iface_modem_voice_authorize_outgoing_call (ctx->self->priv->iface, ctx->self, &error)) { mm_base_call_change_state (ctx->self, MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_UNKNOWN); mm_dbus_method_invocation_take_error (ctx->invocation, error); handle_start_context_free (ctx); @@ -535,7 +532,7 @@ handle_join_multiparty_auth_ready (MMAuthProvider *authp, /* This action is provided in the Call API, but implemented in the Modem.Voice interface * logic, because the action affects not only one call object, but all call objects that * are part of the multiparty call. */ - mm_iface_modem_voice_join_multiparty (MM_IFACE_MODEM_VOICE (ctx->self->priv->modem), + mm_iface_modem_voice_join_multiparty (ctx->self->priv->iface, ctx->self, (GAsyncReadyCallback)modem_voice_join_multiparty_ready, ctx); @@ -609,7 +606,7 @@ handle_leave_multiparty_auth_ready (MMAuthProvider *authp, /* This action is provided in the Call API, but implemented in the Modem.Voice interface * logic, because the action affects not only one call object, but all call objects that * are part of the multiparty call. */ - mm_iface_modem_voice_leave_multiparty (MM_IFACE_MODEM_VOICE (ctx->self->priv->modem), + mm_iface_modem_voice_leave_multiparty (ctx->self->priv->iface, ctx->self, (GAsyncReadyCallback)modem_voice_leave_multiparty_ready, ctx); @@ -1002,28 +999,6 @@ log_object_build_id (MMLogObject *_self) /*****************************************************************************/ -MMBaseCall * -mm_base_call_new (MMBaseModem *modem, - GObject *bind_to, - MMCallDirection direction, - const gchar *number, - gboolean skip_incoming_timeout, - gboolean supports_dialing_to_ringing, - gboolean supports_ringing_to_active) -{ - return MM_BASE_CALL (g_object_new (MM_TYPE_BASE_CALL, - MM_BASE_CALL_MODEM, modem, - MM_BIND_TO, bind_to, - "direction", direction, - "number", number, - MM_BASE_CALL_SKIP_INCOMING_TIMEOUT, skip_incoming_timeout, - MM_BASE_CALL_SUPPORTS_DIALING_TO_RINGING, supports_dialing_to_ringing, - MM_BASE_CALL_SUPPORTS_RINGING_TO_ACTIVE, supports_ringing_to_active, - NULL)); -} - -/*****************************************************************************/ - static void set_property (GObject *object, guint prop_id, @@ -1058,9 +1033,9 @@ set_property (GObject *object, self->priv->bind_to = g_value_dup_object (value); mm_bind_to (MM_BIND (self), MM_BASE_CALL_CONNECTION, self->priv->bind_to); break; - case PROP_MODEM: - g_clear_object (&self->priv->modem); - self->priv->modem = g_value_dup_object (value); + case PROP_IFACE_MODEM_VOICE: + g_clear_object (&self->priv->iface); + self->priv->iface = g_value_dup_object (value); break; case PROP_SKIP_INCOMING_TIMEOUT: self->priv->skip_incoming_timeout = g_value_get_boolean (value); @@ -1095,8 +1070,8 @@ get_property (GObject *object, case PROP_BIND_TO: g_value_set_object (value, self->priv->bind_to); break; - case PROP_MODEM: - g_value_set_object (value, self->priv->modem); + case PROP_IFACE_MODEM_VOICE: + g_value_set_object (value, self->priv->iface); break; case PROP_SKIP_INCOMING_TIMEOUT: g_value_set_boolean (value, self->priv->skip_incoming_timeout); @@ -1159,7 +1134,7 @@ dispose (GObject *object) g_clear_object (&self->priv->connection); } - g_clear_object (&self->priv->modem); + g_clear_object (&self->priv->iface); g_clear_object (&self->priv->bind_to); g_cancellable_cancel (self->priv->authp_cancellable); g_clear_object (&self->priv->authp_cancellable); @@ -1209,13 +1184,13 @@ mm_base_call_class_init (MMBaseCallClass *klass) g_object_class_override_property (object_class, PROP_BIND_TO, MM_BIND_TO); - properties[PROP_MODEM] = - g_param_spec_object (MM_BASE_CALL_MODEM, - "Modem", - "The Modem which owns this call", - MM_TYPE_BASE_MODEM, + properties[PROP_IFACE_MODEM_VOICE] = + g_param_spec_object (MM_BASE_CALL_IFACE_MODEM_VOICE, + "Modem Voice Interface", + "The Modem voice interface which owns this call", + MM_TYPE_IFACE_MODEM_VOICE, G_PARAM_READWRITE); - g_object_class_install_property (object_class, PROP_MODEM, properties[PROP_MODEM]); + g_object_class_install_property (object_class, PROP_IFACE_MODEM_VOICE, properties[PROP_IFACE_MODEM_VOICE]); properties[PROP_SKIP_INCOMING_TIMEOUT] = g_param_spec_boolean (MM_BASE_CALL_SKIP_INCOMING_TIMEOUT, diff --git a/src/mm-base-call.h b/src/mm-base-call.h index fc69bf2f..a7666145 100644 --- a/src/mm-base-call.h +++ b/src/mm-base-call.h @@ -23,8 +23,8 @@ #define _LIBMM_INSIDE_MM #include -#include "mm-base-modem.h" #include "mm-call-audio-format.h" +#include "mm-port.h" #define MM_TYPE_BASE_CALL (mm_base_call_get_type ()) #define MM_BASE_CALL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BASE_CALL, MMBaseCall)) @@ -39,7 +39,7 @@ typedef struct _MMBaseCallPrivate MMBaseCallPrivate; #define MM_BASE_CALL_PATH "call-path" #define MM_BASE_CALL_CONNECTION "call-connection" -#define MM_BASE_CALL_MODEM "call-modem" +#define MM_BASE_CALL_IFACE_MODEM_VOICE "call-iface-modem-voice" #define MM_BASE_CALL_SKIP_INCOMING_TIMEOUT "call-skip-incoming-timeout" #define MM_BASE_CALL_SUPPORTS_DIALING_TO_RINGING "call-supports-dialing-to-ringing" #define MM_BASE_CALL_SUPPORTS_RINGING_TO_ACTIVE "call-supports-ringing-to-active" @@ -99,15 +99,6 @@ struct _MMBaseCallClass { GType mm_base_call_get_type (void); G_DEFINE_AUTOPTR_CLEANUP_FUNC (MMBaseCall, g_object_unref) -/* This one can be overridden by plugins */ -MMBaseCall *mm_base_call_new (MMBaseModem *modem, - GObject *bind_to, - MMCallDirection direction, - const gchar *number, - gboolean skip_incoming_timeout, - gboolean supports_dialing_to_ringing, - gboolean supports_ringing_to_active); - void mm_base_call_export (MMBaseCall *self); void mm_base_call_unexport (MMBaseCall *self); diff --git a/src/mm-call-at.c b/src/mm-call-at.c index 65cb3b7e..88bcb349 100644 --- a/src/mm-call-at.c +++ b/src/mm-call-at.c @@ -370,10 +370,10 @@ mm_call_at_new (MMBaseModem *modem, MMBaseCall *call; call = MM_BASE_CALL (g_object_new (MM_TYPE_CALL_AT, - MM_BASE_CALL_MODEM, modem, - MM_BIND_TO, bind_to, - "direction", direction, - "number", number, + MM_BASE_CALL_IFACE_MODEM_VOICE, modem, + MM_BIND_TO, bind_to, + "direction", direction, + "number", number, MM_BASE_CALL_SKIP_INCOMING_TIMEOUT, skip_incoming_timeout, MM_BASE_CALL_SUPPORTS_DIALING_TO_RINGING, supports_dialing_to_ringing, MM_BASE_CALL_SUPPORTS_RINGING_TO_ACTIVE, supports_ringing_to_active, diff --git a/src/mm-call-qmi.c b/src/mm-call-qmi.c index 441be147..55bfa89a 100644 --- a/src/mm-call-qmi.c +++ b/src/mm-call-qmi.c @@ -35,6 +35,11 @@ G_DEFINE_TYPE (MMCallQmi, mm_call_qmi, MM_TYPE_BASE_CALL) +struct _MMCallQmiPrivate { + /* The modem which owns this call */ + MMBaseModem *modem; +}; + /*****************************************************************************/ static gboolean @@ -48,12 +53,7 @@ ensure_qmi_client (MMCallQmi *self, QmiClient *client; MMPortQmi *port; - g_object_get (self, - MM_BASE_CALL_MODEM, &modem, - NULL); - g_assert (MM_IS_BASE_MODEM (modem)); - - port = mm_broadband_modem_qmi_peek_port_qmi (MM_BROADBAND_MODEM_QMI (modem)); + port = mm_broadband_modem_qmi_peek_port_qmi (MM_BROADBAND_MODEM_QMI (self->priv->modem)); g_object_unref (modem); if (!port) { @@ -491,27 +491,48 @@ mm_call_qmi_new (MMBaseModem *modem, MMCallDirection direction, const gchar *number) { - return MM_BASE_CALL (g_object_new (MM_TYPE_CALL_QMI, - MM_BASE_CALL_MODEM, modem, - MM_BIND_TO, G_OBJECT (modem), - "direction", direction, - "number", number, + MMBaseCall *call; + + call = MM_BASE_CALL (g_object_new (MM_TYPE_CALL_QMI, + MM_BASE_CALL_IFACE_MODEM_VOICE, modem, + MM_BIND_TO, modem, + "direction", direction, + "number", number, MM_BASE_CALL_SKIP_INCOMING_TIMEOUT, TRUE, MM_BASE_CALL_SUPPORTS_DIALING_TO_RINGING, TRUE, MM_BASE_CALL_SUPPORTS_RINGING_TO_ACTIVE, TRUE, NULL)); + MM_CALL_QMI (call)->priv->modem = g_object_ref (modem); + return call; } static void mm_call_qmi_init (MMCallQmi *self) { + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_CALL_QMI, MMCallQmiPrivate); +} + +static void +dispose (GObject *object) +{ + MMCallQmi *self = MM_CALL_QMI (object); + + g_clear_object (&self->priv->modem); + + G_OBJECT_CLASS (mm_call_qmi_parent_class)->dispose (object); } static void mm_call_qmi_class_init (MMCallQmiClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS (klass); MMBaseCallClass *base_call_class = MM_BASE_CALL_CLASS (klass); + g_type_class_add_private (object_class, sizeof (MMCallQmiPrivate)); + + object_class->dispose = dispose; + base_call_class->start = call_start; base_call_class->start_finish = call_start_finish; base_call_class->accept = call_accept; diff --git a/src/mm-call-qmi.h b/src/mm-call-qmi.h index 78402539..34231bcc 100644 --- a/src/mm-call-qmi.h +++ b/src/mm-call-qmi.h @@ -33,9 +33,11 @@ typedef struct _MMCallQmi MMCallQmi; typedef struct _MMCallQmiClass MMCallQmiClass; +typedef struct _MMCallQmiPrivate MMCallQmiPrivate; struct _MMCallQmi { MMBaseCall parent; + MMCallQmiPrivate *priv; }; struct _MMCallQmiClass { -- cgit v1.2.3-70-g09d2 From 5448a85a2bae32b6c9dfb82ac148904bf20fc209 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Fri, 11 Apr 2025 20:43:17 -0500 Subject: call: allow setting DTMF tone duration at call creation time Instead of hard-coding a 500ms DTMF tone duration, let applications use a specific duration at call setup time. Signed-off-by: Dan Williams --- .../org.freedesktop.ModemManager1.Call.xml | 17 ++++- libmm-glib/mm-call-properties.c | 81 +++++++++++++++++++++- libmm-glib/mm-call-properties.h | 4 ++ src/mm-base-call.c | 14 ++++ src/mm-base-call.h | 9 +++ src/mm-broadband-modem-qmi.c | 6 +- src/mm-broadband-modem.c | 4 +- src/mm-call-at.c | 73 ++++++++++++++++--- src/mm-call-at.h | 1 + src/mm-call-qmi.c | 21 ++++-- src/mm-call-qmi.h | 3 +- src/mm-iface-modem-voice.c | 10 ++- src/mm-iface-modem-voice.h | 3 +- src/mm-modem-helpers.c | 16 +++++ src/mm-modem-helpers.h | 5 ++ src/plugins/cinterion/mm-shared-cinterion.c | 6 +- src/plugins/cinterion/mm-shared-cinterion.h | 3 +- src/plugins/huawei/mm-broadband-modem-huawei.c | 4 +- src/plugins/ublox/mm-broadband-modem-ublox.c | 4 +- 19 files changed, 249 insertions(+), 35 deletions(-) (limited to 'src/mm-call-at.c') diff --git a/introspection/org.freedesktop.ModemManager1.Call.xml b/introspection/org.freedesktop.ModemManager1.Call.xml index 3aa6a643..85db4ad2 100644 --- a/introspection/org.freedesktop.ModemManager1.Call.xml +++ b/introspection/org.freedesktop.ModemManager1.Call.xml @@ -105,7 +105,7 @@ SendDtmf: @dtmf: DTMF tone identifier [0-9A-D*#]. - Send a DTMF tone (Dual Tone Multi-Frequency) (only on supported modem). + Send a DTMF tone (Dual Tone Multi-Frequency) (only on supported modems). Applicable only if state is MM_CALL_STATE_ACTIVE. @@ -119,7 +119,7 @@ DtmfReceived: @dtmf: DTMF tone identifier [0-9A-D*#]. - Emitted when a DTMF tone is received (only on supported modem) + Emitted when a DTMF tone is received (only on supported modems) Since: 1.6 --> @@ -232,5 +232,18 @@ Since: 1.10 --> + + + diff --git a/libmm-glib/mm-call-properties.c b/libmm-glib/mm-call-properties.c index c5da4920..ff486aa5 100644 --- a/libmm-glib/mm-call-properties.c +++ b/libmm-glib/mm-call-properties.c @@ -44,10 +44,12 @@ G_DEFINE_TYPE (MMCallProperties, mm_call_properties, G_TYPE_OBJECT) -#define PROPERTY_NUMBER "number" +#define PROPERTY_NUMBER "number" +#define PROPERTY_DTMF_TONE_DURATION "dtmf-tone-duration" struct _MMCallPropertiesPrivate { gchar *number; + guint dtmf_tone_duration; }; /*****************************************************************************/ @@ -92,6 +94,43 @@ mm_call_properties_get_number (MMCallProperties *self) /*****************************************************************************/ +/** + * mm_call_properties_set_dtmf_tone_duration: + * @self: A #MMCallProperties. + * @duration_ms: The desired duration of DTMF tones in milliseconds. + * + * Sets the DTMF tone duration if supported by the modem. + * + * Since: 1.26 + */ +void +mm_call_properties_set_dtmf_tone_duration (MMCallProperties *self, + const guint duration_ms) +{ + g_return_if_fail (MM_IS_CALL_PROPERTIES (self)); + + self->priv->dtmf_tone_duration = duration_ms; +} + +/** + * mm_call_properties_get_dtmf_tone_duration: + * @self: A #MMCallProperties. + * + * Gets the desired DTMF tone duration in milliseconds. + * + * Returns: the DTMF tone duration in milliseconds. + * + * Since: 1.26 + */ +guint +mm_call_properties_get_dtmf_tone_duration (MMCallProperties *self) +{ + g_return_val_if_fail (MM_IS_CALL_PROPERTIES (self), 0); + + return self->priv->dtmf_tone_duration; +} + +/*****************************************************************************/ /* * mm_call_properties_get_dictionary: (skip) */ @@ -114,6 +153,12 @@ mm_call_properties_get_dictionary (MMCallProperties *self) PROPERTY_NUMBER, g_variant_new_string (self->priv->number)); + if (self->priv->dtmf_tone_duration) + g_variant_builder_add (&builder, + "{sv}", + PROPERTY_DTMF_TONE_DURATION, + g_variant_new_uint32 (self->priv->dtmf_tone_duration)); + return g_variant_ref_sink (g_variant_builder_end (&builder)); } @@ -126,6 +171,18 @@ consume_string (MMCallProperties *self, { if (g_str_equal (key, PROPERTY_NUMBER)) { mm_call_properties_set_number (self, value); + } else if (g_str_equal (key, PROPERTY_DTMF_TONE_DURATION)) { + guint num; + + if (!mm_get_uint_from_str (value, &num)) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_INVALID_ARGS, + "Failed to parse DTMF tone duration from value '%s'", + value); + return FALSE; + } + mm_call_properties_set_dtmf_tone_duration (self, num); } else { g_set_error (error, MM_CORE_ERROR, @@ -189,11 +246,29 @@ consume_variant (MMCallProperties *properties, GVariant *value, GError **error) { - if (g_str_equal (key, PROPERTY_NUMBER)) + if (g_str_equal (key, PROPERTY_NUMBER)) { + if (!g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) { + g_set_error_literal (error, + MM_CORE_ERROR, + MM_CORE_ERROR_INVALID_ARGS, + "Invalid properties dictionary; number not a string"); + return FALSE; + } mm_call_properties_set_number ( properties, g_variant_get_string (value, NULL)); - else { + } else if (g_str_equal (key, PROPERTY_DTMF_TONE_DURATION)) { + if (!g_variant_is_of_type (value, G_VARIANT_TYPE_UINT32)) { + g_set_error_literal (error, + MM_CORE_ERROR, + MM_CORE_ERROR_INVALID_ARGS, + "Invalid properties dictionary; dtmf-tone-duration not a uint32"); + return FALSE; + } + mm_call_properties_set_dtmf_tone_duration ( + properties, + g_variant_get_uint32 (value)); + } else { /* Set error */ g_set_error (error, MM_CORE_ERROR, diff --git a/libmm-glib/mm-call-properties.h b/libmm-glib/mm-call-properties.h index 12b4e330..1329ee3b 100644 --- a/libmm-glib/mm-call-properties.h +++ b/libmm-glib/mm-call-properties.h @@ -68,6 +68,10 @@ void mm_call_properties_set_number (MMCallProperties *self, const gchar *text); const gchar *mm_call_properties_get_number (MMCallProperties *self); +void mm_call_properties_set_dtmf_tone_duration (MMCallProperties *self, + const guint duration_ms); +guint mm_call_properties_get_dtmf_tone_duration (MMCallProperties *self); + /*****************************************************************************/ /* ModemManager/libmm-glib/mmcli specific methods */ diff --git a/src/mm-base-call.c b/src/mm-base-call.c index a59aca85..72982b1d 100644 --- a/src/mm-base-call.c +++ b/src/mm-base-call.c @@ -922,6 +922,20 @@ mm_base_call_set_multiparty (MMBaseCall *self, return mm_gdbus_call_set_multiparty (MM_GDBUS_CALL (self), multiparty); } +guint +mm_base_call_get_dtmf_tone_duration (MMBaseCall *self) +{ + return mm_dtmf_duration_normalize (mm_gdbus_call_get_dtmf_tone_duration (MM_GDBUS_CALL (self))); +} + +void +mm_base_call_set_dtmf_tone_duration (MMBaseCall *self, + guint duration_ms) +{ + return mm_gdbus_call_set_dtmf_tone_duration (MM_GDBUS_CALL (self), + mm_dtmf_duration_normalize (duration_ms)); +} + /*****************************************************************************/ /* Current call index, only applicable while the call is ongoing * See 3GPP TS 22.030 [27], subclause 6.5.5.1. diff --git a/src/mm-base-call.h b/src/mm-base-call.h index a7666145..fb738630 100644 --- a/src/mm-base-call.h +++ b/src/mm-base-call.h @@ -37,6 +37,10 @@ typedef struct _MMBaseCall MMBaseCall; typedef struct _MMBaseCallClass MMBaseCallClass; typedef struct _MMBaseCallPrivate MMBaseCallPrivate; +#define MM_CALL_DIRECTION "direction" +#define MM_CALL_NUMBER "number" +#define MM_CALL_DTMF_TONE_DURATION "dtmf-tone-duration" + #define MM_BASE_CALL_PATH "call-path" #define MM_BASE_CALL_CONNECTION "call-connection" #define MM_BASE_CALL_IFACE_MODEM_VOICE "call-iface-modem-voice" @@ -129,4 +133,9 @@ void mm_base_call_received_dtmf (MMBaseCall *self, void mm_base_call_incoming_refresh (MMBaseCall *self); +guint mm_base_call_get_dtmf_tone_duration (MMBaseCall *self); + +void mm_base_call_set_dtmf_tone_duration (MMBaseCall *self, + guint duration_ms); + #endif /* MM_BASE_CALL_H */ diff --git a/src/mm-broadband-modem-qmi.c b/src/mm-broadband-modem-qmi.c index 1b9cf04f..80cf157d 100644 --- a/src/mm-broadband-modem-qmi.c +++ b/src/mm-broadband-modem-qmi.c @@ -12190,11 +12190,13 @@ modem_voice_load_call_list (MMIfaceModemVoice *self, static MMBaseCall * modem_voice_create_call (MMIfaceModemVoice *self, MMCallDirection direction, - const gchar *number) + const gchar *number, + const guint dtmf_tone_duration) { return mm_call_qmi_new (MM_BASE_MODEM (self), direction, - number); + number, + dtmf_tone_duration); } /*****************************************************************************/ diff --git a/src/mm-broadband-modem.c b/src/mm-broadband-modem.c index 3d9cec56..27ac8f56 100644 --- a/src/mm-broadband-modem.c +++ b/src/mm-broadband-modem.c @@ -8899,7 +8899,8 @@ modem_voice_disable_unsolicited_events (MMIfaceModemVoice *self, static MMBaseCall * modem_voice_create_call (MMIfaceModemVoice *_self, MMCallDirection direction, - const gchar *number) + const gchar *number, + const guint dtmf_tone_duration) { MMBroadbandModem *self = MM_BROADBAND_MODEM (_self); @@ -8907,6 +8908,7 @@ modem_voice_create_call (MMIfaceModemVoice *_self, G_OBJECT (self), direction, number, + dtmf_tone_duration, /* If +CLCC is supported, we want no incoming timeout. * Also, we're able to support detailed call state updates without * additional vendor-specific commands. */ diff --git a/src/mm-call-at.c b/src/mm-call-at.c index 88bcb349..0a81b929 100644 --- a/src/mm-call-at.c +++ b/src/mm-call-at.c @@ -36,9 +36,18 @@ G_DEFINE_TYPE (MMCallAt, mm_call_at, MM_TYPE_BASE_CALL) +typedef enum { + FEATURE_SUPPORT_UNKNOWN, + FEATURE_NOT_SUPPORTED, + FEATURE_SUPPORTED, +} FeatureSupport; + struct _MMCallAtPrivate { /* The modem which owns this call */ MMBaseModem *modem; + + /* DTMF support */ + FeatureSupport vtd_supported; }; /*****************************************************************************/ @@ -333,27 +342,67 @@ call_send_dtmf_ready (MMBaseModem *modem, g_object_unref (task); } +static void +send_dtmf_digit (MMCallAt *self, + GTask *task, + const gchar dtmf_digit) +{ + g_autofree gchar *cmd = NULL; + + cmd = g_strdup_printf ("AT+VTS=%c", dtmf_digit); + mm_base_modem_at_command (self->priv->modem, + cmd, + 3, + FALSE, + (GAsyncReadyCallback)call_send_dtmf_ready, + task); +} + +static void +call_dtmf_vtd_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + MMCallAt *self; + g_autoptr(GError) error = NULL; + gchar dtmf_digit; + + self = g_task_get_source_object (task); + + mm_base_modem_at_command_finish (modem, res, &error); + self->priv->vtd_supported = error ? FEATURE_NOT_SUPPORTED : FEATURE_SUPPORTED; + + dtmf_digit = (gchar) GPOINTER_TO_UINT (g_task_get_task_data (task)); + send_dtmf_digit (self, task, dtmf_digit); +} + static void call_send_dtmf (MMBaseCall *_self, const gchar *dtmf, GAsyncReadyCallback callback, gpointer user_data) { - MMCallAt *self = MM_CALL_AT (_self); - GTask *task; - gchar *cmd; + MMCallAt *self = MM_CALL_AT (_self); + GTask *task; + g_autofree gchar *cmd = NULL; task = g_task_new (self, NULL, callback, user_data); - cmd = g_strdup_printf ("AT+VTS=%c", dtmf[0]); + if (self->priv->vtd_supported == FEATURE_NOT_SUPPORTED) { + send_dtmf_digit (self, task, dtmf[0]); + return; + } + + g_task_set_task_data (task, GUINT_TO_POINTER (dtmf[0]), NULL); + + /* Otherwise try to set duration */ + cmd = g_strdup_printf ("AT+VTD=%u", mm_base_call_get_dtmf_tone_duration (_self)); mm_base_modem_at_command (self->priv->modem, cmd, 3, FALSE, - (GAsyncReadyCallback)call_send_dtmf_ready, + (GAsyncReadyCallback)call_dtmf_vtd_ready, task); - - g_free (cmd); } /*****************************************************************************/ @@ -363,6 +412,7 @@ mm_call_at_new (MMBaseModem *modem, GObject *bind_to, MMCallDirection direction, const gchar *number, + const guint dtmf_tone_duration, gboolean skip_incoming_timeout, gboolean supports_dialing_to_ringing, gboolean supports_ringing_to_active) @@ -370,10 +420,11 @@ mm_call_at_new (MMBaseModem *modem, MMBaseCall *call; call = MM_BASE_CALL (g_object_new (MM_TYPE_CALL_AT, - MM_BASE_CALL_IFACE_MODEM_VOICE, modem, - MM_BIND_TO, bind_to, - "direction", direction, - "number", number, + MM_BASE_CALL_IFACE_MODEM_VOICE, modem, + MM_BIND_TO, bind_to, + MM_CALL_DIRECTION, direction, + MM_CALL_NUMBER, number, + MM_CALL_DTMF_TONE_DURATION, dtmf_tone_duration, MM_BASE_CALL_SKIP_INCOMING_TIMEOUT, skip_incoming_timeout, MM_BASE_CALL_SUPPORTS_DIALING_TO_RINGING, supports_dialing_to_ringing, MM_BASE_CALL_SUPPORTS_RINGING_TO_ACTIVE, supports_ringing_to_active, diff --git a/src/mm-call-at.h b/src/mm-call-at.h index e1ae336e..64aed048 100644 --- a/src/mm-call-at.h +++ b/src/mm-call-at.h @@ -53,6 +53,7 @@ MMBaseCall *mm_call_at_new (MMBaseModem *modem, GObject *bind_to, MMCallDirection direction, const gchar *number, + const guint dtmf_tone_duration, gboolean skip_incoming_timeout, gboolean supports_dialing_to_ringing, gboolean supports_ringing_to_active); diff --git a/src/mm-call-qmi.c b/src/mm-call-qmi.c index 55bfa89a..1a9b2dd7 100644 --- a/src/mm-call-qmi.c +++ b/src/mm-call-qmi.c @@ -402,9 +402,12 @@ voice_start_continuous_dtmf_ready (QmiClientVoice *client, GAsyncResult *res, GTask *task) { + MMCallQmi *self; g_autoptr(QmiMessageVoiceStartContinuousDtmfOutput) output = NULL; GError *error = NULL; + self = g_task_get_source_object (task); + output = qmi_client_voice_start_continuous_dtmf_finish (client, res, &error); if (!output) { g_prefix_error (&error, "QMI operation failed: "); @@ -420,8 +423,10 @@ voice_start_continuous_dtmf_ready (QmiClientVoice *client, return; } - /* Disable DTMF press after 500 ms */ - g_timeout_add (500, (GSourceFunc) voice_stop_continuous_dtmf, task); + /* Disable DTMF press after DTMF tone duration elapses */ + g_timeout_add (mm_base_call_get_dtmf_tone_duration (MM_BASE_CALL (self)), + (GSourceFunc) voice_stop_continuous_dtmf, + task); } static void @@ -489,15 +494,17 @@ call_send_dtmf (MMBaseCall *self, MMBaseCall * mm_call_qmi_new (MMBaseModem *modem, MMCallDirection direction, - const gchar *number) + const gchar *number, + const guint dtmf_tone_duration) { MMBaseCall *call; call = MM_BASE_CALL (g_object_new (MM_TYPE_CALL_QMI, - MM_BASE_CALL_IFACE_MODEM_VOICE, modem, - MM_BIND_TO, modem, - "direction", direction, - "number", number, + MM_BASE_CALL_IFACE_MODEM_VOICE, modem, + MM_BIND_TO, modem, + MM_CALL_DIRECTION, direction, + MM_CALL_NUMBER, number, + MM_CALL_DTMF_TONE_DURATION, dtmf_tone_duration, MM_BASE_CALL_SKIP_INCOMING_TIMEOUT, TRUE, MM_BASE_CALL_SUPPORTS_DIALING_TO_RINGING, TRUE, MM_BASE_CALL_SUPPORTS_RINGING_TO_ACTIVE, TRUE, diff --git a/src/mm-call-qmi.h b/src/mm-call-qmi.h index 34231bcc..f99e7e10 100644 --- a/src/mm-call-qmi.h +++ b/src/mm-call-qmi.h @@ -49,6 +49,7 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC (MMCallQmi, g_object_unref) MMBaseCall *mm_call_qmi_new (MMBaseModem *modem, MMCallDirection direction, - const gchar *number); + const gchar *number, + const guint dtmf_tone_duration); #endif /* MM_CALL_QMI_H */ diff --git a/src/mm-iface-modem-voice.c b/src/mm-iface-modem-voice.c index 2ce0ec6b..0bba2a34 100644 --- a/src/mm-iface-modem-voice.c +++ b/src/mm-iface-modem-voice.c @@ -161,7 +161,7 @@ create_incoming_call (MMIfaceModemVoice *self, g_assert (MM_IFACE_MODEM_VOICE_GET_IFACE (self)->create_call != NULL); - call = MM_IFACE_MODEM_VOICE_GET_IFACE (self)->create_call (self, MM_CALL_DIRECTION_INCOMING, number); + call = MM_IFACE_MODEM_VOICE_GET_IFACE (self)->create_call (self, MM_CALL_DIRECTION_INCOMING, number, 0); update_audio_settings_in_call (self, call); return call; } @@ -173,6 +173,7 @@ create_outgoing_call_from_properties (MMIfaceModemVoice *self, { MMBaseCall *call; const gchar *number; + guint dtmf_tone_duration; /* Don't create CALL from properties if either number is missing */ number = mm_call_properties_get_number (properties) ; @@ -184,9 +185,14 @@ create_outgoing_call_from_properties (MMIfaceModemVoice *self, return NULL; } + dtmf_tone_duration = mm_call_properties_get_dtmf_tone_duration (properties) ; + /* Create a call object as defined by the interface */ g_assert (MM_IFACE_MODEM_VOICE_GET_IFACE (self)->create_call != NULL); - call = MM_IFACE_MODEM_VOICE_GET_IFACE (self)->create_call (self, MM_CALL_DIRECTION_OUTGOING, number); + call = MM_IFACE_MODEM_VOICE_GET_IFACE (self)->create_call (self, + MM_CALL_DIRECTION_OUTGOING, + number, + dtmf_tone_duration); update_audio_settings_in_call (self, call); return call; } diff --git a/src/mm-iface-modem-voice.h b/src/mm-iface-modem-voice.h index 2d8b5160..716988d5 100644 --- a/src/mm-iface-modem-voice.h +++ b/src/mm-iface-modem-voice.h @@ -123,7 +123,8 @@ struct _MMIfaceModemVoiceInterface { /* Create call objects */ MMBaseCall * (* create_call) (MMIfaceModemVoice *self, MMCallDirection direction, - const gchar *number); + const gchar *number, + const guint dtmf_tone_duration); /* Hold and accept */ void (* hold_and_accept) (MMIfaceModemVoice *self, diff --git a/src/mm-modem-helpers.c b/src/mm-modem-helpers.c index a79ca956..0e258e61 100644 --- a/src/mm-modem-helpers.c +++ b/src/mm-modem-helpers.c @@ -5871,3 +5871,19 @@ mm_parse_cpin_response (const gchar *response, return MM_MODEM_LOCK_UNKNOWN; } + +/*****************************************************************************/ + +guint +mm_dtmf_duration_normalize (guint duration_ms) +{ + /* Default to 500ms */ + if (duration_ms == 0) + return 500; + + /* round to next highest 100ms */ + if (duration_ms % 100) + duration_ms = ((duration_ms + 100) / 100) * 100; + + return CLAMP (duration_ms, 100, 1000); +} diff --git a/src/mm-modem-helpers.h b/src/mm-modem-helpers.h index eb62a551..74626c90 100644 --- a/src/mm-modem-helpers.h +++ b/src/mm-modem-helpers.h @@ -643,4 +643,9 @@ guint mm_string_uint_map_lookup (const MMStringUintMap *map, MMModemLock mm_parse_cpin_response (const gchar *response, gboolean expect_cpin_prefix); +/*****************************************************************************/ + +/* Helper to clamp duration and round to next 100ms */ +guint mm_dtmf_duration_normalize (guint duration_ms); + #endif /* MM_MODEM_HELPERS_H */ diff --git a/src/plugins/cinterion/mm-shared-cinterion.c b/src/plugins/cinterion/mm-shared-cinterion.c index 60bbe6db..aa9b6208 100644 --- a/src/plugins/cinterion/mm-shared-cinterion.c +++ b/src/plugins/cinterion/mm-shared-cinterion.c @@ -998,7 +998,8 @@ mm_shared_cinterion_enable_location_gathering (MMIfaceModemLocation *self, MMBaseCall * mm_shared_cinterion_create_call (MMIfaceModemVoice *self, MMCallDirection direction, - const gchar *number) + const gchar *number, + const guint dtmf_tone_duration) { Private *priv; @@ -1010,6 +1011,7 @@ mm_shared_cinterion_create_call (MMIfaceModemVoice *self, G_OBJECT (self), direction, number, + dtmf_tone_duration, /* When SLCC is supported we have support for detailed * call list events via call list report URCs */ TRUE, /* incoming timeout not required */ @@ -1020,7 +1022,7 @@ mm_shared_cinterion_create_call (MMIfaceModemVoice *self, /* otherwise, run parent's generic base call logic */ g_assert (priv->iface_modem_voice_parent); g_assert (priv->iface_modem_voice_parent->create_call); - return priv->iface_modem_voice_parent->create_call (self, direction, number); + return priv->iface_modem_voice_parent->create_call (self, direction, number, dtmf_tone_duration); } /*****************************************************************************/ diff --git a/src/plugins/cinterion/mm-shared-cinterion.h b/src/plugins/cinterion/mm-shared-cinterion.h index f330e328..146ae96a 100644 --- a/src/plugins/cinterion/mm-shared-cinterion.h +++ b/src/plugins/cinterion/mm-shared-cinterion.h @@ -105,7 +105,8 @@ gboolean mm_shared_cinterion_disable_location_gathering_finish (MMI MMBaseCall *mm_shared_cinterion_create_call (MMIfaceModemVoice *self, MMCallDirection direction, - const gchar *number); + const gchar *number, + const guint dtmf_tone_duration); void mm_shared_cinterion_voice_check_support (MMIfaceModemVoice *self, GAsyncReadyCallback callback, diff --git a/src/plugins/huawei/mm-broadband-modem-huawei.c b/src/plugins/huawei/mm-broadband-modem-huawei.c index 76180a76..5de6c4d5 100644 --- a/src/plugins/huawei/mm-broadband-modem-huawei.c +++ b/src/plugins/huawei/mm-broadband-modem-huawei.c @@ -3879,12 +3879,14 @@ modem_voice_disable_unsolicited_events (MMIfaceModemVoice *self, static MMBaseCall * create_call (MMIfaceModemVoice *self, MMCallDirection direction, - const gchar *number) + const gchar *number, + const guint dtmf_tone_duration) { return mm_call_at_new (MM_BASE_MODEM (self), G_OBJECT (self), direction, number, + dtmf_tone_duration, TRUE, /* skip_incoming_timeout */ TRUE, /* supports_dialing_to_ringing */ TRUE); /* supports_ringing_to_active) */ diff --git a/src/plugins/ublox/mm-broadband-modem-ublox.c b/src/plugins/ublox/mm-broadband-modem-ublox.c index 4213480e..ab049024 100644 --- a/src/plugins/ublox/mm-broadband-modem-ublox.c +++ b/src/plugins/ublox/mm-broadband-modem-ublox.c @@ -1504,12 +1504,14 @@ modem_voice_setup_unsolicited_events (MMIfaceModemVoice *self, static MMBaseCall * create_call (MMIfaceModemVoice *self, MMCallDirection direction, - const gchar *number) + const gchar *number, + const guint dtmf_tone_duration) { return mm_call_at_new (MM_BASE_MODEM (self), G_OBJECT (self), direction, number, + dtmf_tone_duration, TRUE, /* skip_incoming_timeout */ TRUE, /* supports_dialing_to_ringing */ TRUE); /* supports_ringing_to_active */ -- cgit v1.2.3-70-g09d2 From ae3d37030a451eda2680af02cc63ba7bfbeeacbf Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Fri, 25 Apr 2025 07:24:38 -0500 Subject: base-call: handle multiple DTMF characters Send each character through the state machine one at a time. Some modems (various Quectel ones for example) can process multiple at the same time, and while that should be easily implemented using the new changes to send_dtmf() it's not done in any plugin yet. Signed-off-by: Dan Williams --- .../org.freedesktop.ModemManager1.Call.xml | 11 +++- src/mm-base-call.c | 77 ++++++++++++++++++---- src/mm-base-call.h | 3 +- src/mm-call-at.c | 7 +- src/mm-call-qmi.c | 6 +- 5 files changed, 81 insertions(+), 23 deletions(-) (limited to 'src/mm-call-at.c') diff --git a/introspection/org.freedesktop.ModemManager1.Call.xml b/introspection/org.freedesktop.ModemManager1.Call.xml index 41dc40f3..6f1fdf44 100644 --- a/introspection/org.freedesktop.ModemManager1.Call.xml +++ b/introspection/org.freedesktop.ModemManager1.Call.xml @@ -103,10 +103,15 @@