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 <dan@ioncontrol.co>
This commit is contained in:
Dan Williams
2025-05-30 07:59:58 -05:00
parent 28b1954654
commit 5448a85a2b
19 changed files with 253 additions and 39 deletions
@@ -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 <link linkend="MM-CALL-STATE-ACTIVE:CAPS"><constant>MM_CALL_STATE_ACTIVE</constant></link>.
@@ -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
-->
<property name="AudioFormat" type="a{sv}" access="read" />
<!--
DtmfToneDuration:
The length of DTMF tones, in milliseconds. Valid range is 100ms to
1000ms and is rounded up to the next 100ms if not evenly divisble
by 100. Set the duration by passing this property to
<link linkend="gdbus-method-org-freedesktop-ModemManager1-Voice.CreateCall">CreateCall()</link>
at call creation time.
Since: 1.26
-->
<property name="DtmfToneDuration" type="u" access="read" />
</interface>
</node>
+78 -3
View File
@@ -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,
+4
View File
@@ -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 */
+14
View File
@@ -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.
+9
View File
@@ -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 */
+4 -2
View File
@@ -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);
}
/*****************************************************************************/
+3 -1
View File
@@ -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. */
+66 -15
View File
@@ -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;
};
/*****************************************************************************/
@@ -334,26 +343,66 @@ call_send_dtmf_ready (MMBaseModem *modem,
}
static void
call_send_dtmf (MMBaseCall *_self,
const gchar *dtmf,
GAsyncReadyCallback callback,
gpointer user_data)
send_dtmf_digit (MMCallAt *self,
GTask *task,
const gchar dtmf_digit)
{
MMCallAt *self = MM_CALL_AT (_self);
GTask *task;
gchar *cmd;
g_autofree gchar *cmd = NULL;
task = g_task_new (self, NULL, callback, user_data);
cmd = g_strdup_printf ("AT+VTS=%c", dtmf[0]);
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);
}
g_free (cmd);
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;
g_autofree gchar *cmd = NULL;
task = g_task_new (self, NULL, callback, user_data);
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_dtmf_vtd_ready,
task);
}
/*****************************************************************************/
@@ -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,
+1
View File
@@ -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);
+14 -7
View File
@@ -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,
+2 -1
View File
@@ -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 */
+8 -2
View File
@@ -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;
}
+2 -1
View File
@@ -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,
+16
View File
@@ -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);
}
+5
View File
@@ -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 */
+4 -2
View File
@@ -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);
}
/*****************************************************************************/
+2 -1
View File
@@ -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,
@@ -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) */
+3 -1
View File
@@ -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 */