aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Williams <dan@ioncontrol.co>2025-04-24 08:50:10 -0500
committerDan Williams <dan@ioncontrol.co>2025-05-30 07:59:59 -0500
commit2726f8e8a28a802a880bdbf974f522ef02c27bea (patch)
tree08200def8570328b350e5e0e689227a8697dd29d
parent5448a85a2bae32b6c9dfb82ac148904bf20fc209 (diff)
base-call: convert DTMF tone handling to a state machine
Generalize the timeout/stop logic that QMI requires into the base call. We'll use this to implement the pause character (,) generically. Signed-off-by: Dan Williams <dan@ioncontrol.co>
-rw-r--r--src/mm-base-call.c270
-rw-r--r--src/mm-base-call.h24
-rw-r--r--src/mm-call-qmi.c169
3 files changed, 371 insertions, 92 deletions
diff --git a/src/mm-base-call.c b/src/mm-base-call.c
index 72982b1d..3f4e081a 100644
--- a/src/mm-base-call.c
+++ b/src/mm-base-call.c
@@ -89,6 +89,9 @@ struct _MMBaseCallPrivate {
* 'terminated' is coming asynchronously (e.g. via in-call state
* update notifications) */
GCancellable *start_cancellable;
+
+ /* DTMF support */
+ GQueue *dtmf_queue;
};
/*****************************************************************************/
@@ -728,6 +731,239 @@ handle_hangup (MMBaseCall *self,
/*****************************************************************************/
/* Send dtmf (DBus call handling) */
+typedef enum {
+ DTMF_STEP_FIRST,
+ DTMF_STEP_START,
+ DTMF_STEP_TIMEOUT,
+ DTMF_STEP_STOP,
+ DTMF_STEP_LAST,
+} DtmfStep;
+
+typedef struct {
+ DtmfStep step;
+ GError *saved_error;
+ guint8 call_id;
+ gchar *dtmf;
+ guint timeout_id;
+} SendDtmfContext;
+
+static void
+send_dtmf_context_clear_timeout (SendDtmfContext *ctx)
+{
+ if (ctx->timeout_id) {
+ g_source_remove (ctx->timeout_id);
+ ctx->timeout_id = 0;
+ }
+}
+
+static void
+send_dtmf_context_free (SendDtmfContext *ctx)
+{
+ send_dtmf_context_clear_timeout (ctx);
+ g_free (ctx->dtmf);
+ g_assert (!ctx->saved_error);
+ g_slice_free (SendDtmfContext, ctx);
+}
+
+static void send_dtmf_task_step_next (GTask *task);
+
+static void
+stop_dtmf_ignore_ready (MMBaseCall *self,
+ GAsyncResult *res,
+ gpointer unused)
+{
+ /* Ignore the result and error */
+ MM_BASE_CALL_GET_CLASS (self)->stop_dtmf_finish (self, res, NULL);
+}
+
+static void
+send_dtmf_task_cancel (GTask *task)
+{
+ MMBaseCall *self;
+ SendDtmfContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+ self = g_task_get_source_object (task);
+
+ send_dtmf_context_clear_timeout (ctx);
+ if (ctx->step > DTMF_STEP_FIRST && ctx->step < DTMF_STEP_STOP) {
+ if (MM_BASE_CALL_GET_CLASS (self)->stop_dtmf) {
+ MM_BASE_CALL_GET_CLASS (self)->stop_dtmf (self,
+ (GAsyncReadyCallback)stop_dtmf_ignore_ready,
+ NULL);
+ }
+ }
+ g_assert (ctx->step != DTMF_STEP_LAST);
+ ctx->step = DTMF_STEP_LAST;
+ send_dtmf_task_step_next (task);
+}
+
+static void
+stop_dtmf_ready (MMBaseCall *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ SendDtmfContext *ctx;
+ GError *error = NULL;
+ gboolean success;
+
+ ctx = g_task_get_task_data (task);
+
+ success = MM_BASE_CALL_GET_CLASS (self)->stop_dtmf_finish (self, res, &error);
+ if (ctx->step == DTMF_STEP_STOP) {
+ if (!success) {
+ g_propagate_error (&ctx->saved_error, error);
+ ctx->step = DTMF_STEP_LAST;
+ } else
+ ctx->step++;
+
+ send_dtmf_task_step_next (task);
+ }
+
+ /* Balance stop_dtmf() */
+ g_object_unref (task);
+}
+
+static gboolean
+dtmf_timeout (GTask *task)
+{
+ SendDtmfContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+
+ send_dtmf_context_clear_timeout (ctx);
+ ctx->step++;
+ send_dtmf_task_step_next (task);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+send_dtmf_ready (MMBaseCall *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ SendDtmfContext *ctx;
+ GError *error = NULL;
+ gboolean success;
+
+ ctx = g_task_get_task_data (task);
+
+ success = MM_BASE_CALL_GET_CLASS (self)->send_dtmf_finish (self, res, &error);
+ if (ctx->step == DTMF_STEP_START) {
+ if (!success) {
+ g_propagate_error (&ctx->saved_error, error);
+ ctx->step = DTMF_STEP_LAST;
+ } else
+ ctx->step++;
+
+ send_dtmf_task_step_next (task);
+ }
+
+ /* Balance send_dtmf() */
+ g_object_unref (task);
+}
+
+static void
+send_dtmf_task_step_next (GTask *task)
+{
+ SendDtmfContext *ctx;
+ gboolean need_stop;
+ MMBaseCall *self;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ need_stop = MM_BASE_CALL_GET_CLASS (self)->stop_dtmf &&
+ MM_BASE_CALL_GET_CLASS (self)->stop_dtmf_finish;
+
+ switch (ctx->step) {
+ case DTMF_STEP_FIRST:
+ ctx->step++;
+ /* Fall through */
+ case DTMF_STEP_START:
+ MM_BASE_CALL_GET_CLASS (self)->send_dtmf (self,
+ ctx->dtmf,
+ (GAsyncReadyCallback)send_dtmf_ready,
+ g_object_ref (task));
+ break;
+ case DTMF_STEP_TIMEOUT:
+ if (need_stop) {
+ /* Disable DTMF press after DTMF tone duration elapses */
+ ctx->timeout_id = g_timeout_add (mm_base_call_get_dtmf_tone_duration (self),
+ (GSourceFunc) dtmf_timeout,
+ task);
+ return;
+ }
+ /* Fall through */
+ case DTMF_STEP_STOP:
+ if (need_stop) {
+ send_dtmf_context_clear_timeout (ctx);
+ MM_BASE_CALL_GET_CLASS (self)->stop_dtmf (self,
+ (GAsyncReadyCallback)stop_dtmf_ready,
+ g_object_ref (task));
+ return;
+ }
+ /* Fall through */
+ case DTMF_STEP_LAST:
+ send_dtmf_context_clear_timeout (ctx);
+ if (ctx->saved_error)
+ g_task_return_error (task, g_steal_pointer (&ctx->saved_error));
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+
+ /* Start the next tone if any are queued */
+ g_queue_remove (self->priv->dtmf_queue, task);
+ task = g_queue_peek_head (self->priv->dtmf_queue);
+ if (task)
+ send_dtmf_task_step_next (task);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static gboolean
+send_dtmf_task_finish (MMBaseCall *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static GTask *
+send_dtmf_task_new (MMBaseCall *self,
+ const gchar *dtmf,
+ GAsyncReadyCallback callback,
+ gpointer user_data,
+ GError **error)
+{
+ GTask *task;
+ SendDtmfContext *ctx;
+ guint8 call_id;
+
+ call_id = mm_base_call_get_index (self);
+ if (call_id == 0) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_INVALID_ARGS,
+ "Invalid call index");
+ return NULL;
+ }
+
+ task = g_task_new (self, NULL, callback, user_data);
+ ctx = g_slice_new0 (SendDtmfContext);
+ ctx->call_id = call_id;
+ ctx->dtmf = g_strdup (dtmf);
+ g_task_set_task_data (task, ctx, (GDestroyNotify) send_dtmf_context_free);
+
+ return task;
+}
+
+/*****************************************************************************/
+/* Send DTMF D-Bus request handling */
+
typedef struct {
MMBaseCall *self;
GDBusMethodInvocation *invocation;
@@ -750,7 +986,7 @@ handle_send_dtmf_ready (MMBaseCall *self,
{
GError *error = NULL;
- if (!MM_BASE_CALL_GET_CLASS (self)->send_dtmf_finish (self, res, &error)) {
+ if (!send_dtmf_task_finish (self, res, &error)) {
mm_dbus_method_invocation_take_error (ctx->invocation, error);
} else {
mm_gdbus_call_complete_send_dtmf (MM_GDBUS_CALL (ctx->self), ctx->invocation);
@@ -764,8 +1000,9 @@ handle_send_dtmf_auth_ready (MMAuthProvider *authp,
GAsyncResult *res,
HandleSendDtmfContext *ctx)
{
- MMCallState state;
- GError *error = NULL;
+ MMCallState state;
+ GError *error = NULL;
+ GTask *task;
if (!mm_auth_provider_authorize_finish (authp, res, &error)) {
mm_dbus_method_invocation_take_error (ctx->invocation, error);
@@ -775,7 +1012,7 @@ handle_send_dtmf_auth_ready (MMAuthProvider *authp,
state = mm_gdbus_call_get_state (MM_GDBUS_CALL (ctx->self));
- /* Check if we do support doing it */
+ /* Check if we do support doing DTMF at all */
if (!MM_BASE_CALL_GET_CLASS (ctx->self)->send_dtmf ||
!MM_BASE_CALL_GET_CLASS (ctx->self)->send_dtmf_finish) {
mm_dbus_method_invocation_return_error_literal (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
@@ -793,9 +1030,20 @@ handle_send_dtmf_auth_ready (MMAuthProvider *authp,
}
mm_obj_info (ctx->self, "processing user request to send DTMF...");
- MM_BASE_CALL_GET_CLASS (ctx->self)->send_dtmf (ctx->self, ctx->dtmf,
- (GAsyncReadyCallback)handle_send_dtmf_ready,
- ctx);
+ task = send_dtmf_task_new (ctx->self,
+ ctx->dtmf,
+ (GAsyncReadyCallback)handle_send_dtmf_ready,
+ ctx,
+ &error);
+ if (!task) {
+ mm_dbus_method_invocation_take_error (ctx->invocation, error);
+ handle_send_dtmf_context_free (ctx);
+ return;
+ }
+
+ g_queue_push_tail (ctx->self->priv->dtmf_queue, task);
+ if (g_queue_get_length (ctx->self->priv->dtmf_queue) == 1)
+ send_dtmf_task_step_next (task);
}
static gboolean
@@ -1116,6 +1364,8 @@ mm_base_call_init (MMBaseCall *self)
/* Setup authorization provider */
self->priv->authp = mm_auth_provider_get ();
self->priv->authp_cancellable = g_cancellable_new ();
+
+ self->priv->dtmf_queue = g_queue_new ();
}
static void
@@ -1123,6 +1373,9 @@ finalize (GObject *object)
{
MMBaseCall *self = MM_BASE_CALL (object);
+ g_assert (g_queue_get_length (self->priv->dtmf_queue) == 0);
+ g_queue_free (g_steal_pointer (&self->priv->dtmf_queue));
+
g_assert (!self->priv->start_cancellable);
g_free (self->priv->path);
@@ -1153,6 +1406,9 @@ dispose (GObject *object)
g_cancellable_cancel (self->priv->authp_cancellable);
g_clear_object (&self->priv->authp_cancellable);
+ g_queue_foreach (self->priv->dtmf_queue, (GFunc) send_dtmf_task_cancel, NULL);
+ g_queue_clear (self->priv->dtmf_queue);
+
G_OBJECT_CLASS (mm_base_call_parent_class)->dispose (object);
}
diff --git a/src/mm-base-call.h b/src/mm-base-call.h
index fb738630..2e45ca5f 100644
--- a/src/mm-base-call.h
+++ b/src/mm-base-call.h
@@ -90,14 +90,22 @@ struct _MMBaseCallClass {
GAsyncResult *res,
GError **error);
- /* Send a DTMF tone */
- void (* send_dtmf) (MMBaseCall *self,
- const gchar *dtmf,
- GAsyncReadyCallback callback,
- gpointer user_data);
- gboolean (* send_dtmf_finish) (MMBaseCall *self,
- GAsyncResult *res,
- GError **error);
+ /* DTMF tone handling */
+ void (* send_dtmf) (MMBaseCall *self,
+ const gchar *dtmf,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ gboolean (* send_dtmf_finish) (MMBaseCall *self,
+ GAsyncResult *res,
+ GError **error);
+
+ void (* stop_dtmf) (MMBaseCall *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ gboolean (* stop_dtmf_finish) (MMBaseCall *self,
+ GAsyncResult *res,
+ GError **error);
+
};
GType mm_base_call_get_type (void);
diff --git a/src/mm-call-qmi.c b/src/mm-call-qmi.c
index 1a9b2dd7..b1b6746d 100644
--- a/src/mm-call-qmi.c
+++ b/src/mm-call-qmi.c
@@ -331,22 +331,41 @@ call_hangup (MMBaseCall *self,
}
/*****************************************************************************/
-/* Send DTMF character */
+/* DTMF handling */
-typedef struct {
- QmiClient *client;
- guint8 call_id;
-} SendDtmfContext;
-
-static void
-send_dtmf_context_free (SendDtmfContext *ctx)
+static gboolean
+get_client_and_call_id (MMCallQmi *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data,
+ QmiClient **client,
+ guint *call_id)
{
- g_clear_object (&ctx->client);
- g_slice_free (SendDtmfContext, ctx);
+ g_return_val_if_fail (client, FALSE);
+ g_return_val_if_fail (call_id, FALSE);
+
+ /* Ensure Voice client */
+ if (!ensure_qmi_client (self,
+ QMI_SERVICE_VOICE, client,
+ callback, user_data))
+ return FALSE;
+
+ *call_id = mm_base_call_get_index (MM_BASE_CALL (self));
+ if (*call_id == 0) {
+ g_task_report_new_error (self,
+ callback,
+ user_data,
+ (gpointer) __func__,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_INVALID_ARGS,
+ "Invalid call index");
+ return FALSE;
+ }
+
+ return TRUE;
}
static gboolean
-call_send_dtmf_finish (MMBaseCall *self,
+call_stop_dtmf_finish (MMBaseCall *call,
GAsyncResult *res,
GError **error)
{
@@ -366,35 +385,59 @@ voice_stop_continuous_dtmf_ready (QmiClientVoice *client,
g_prefix_error (&error, "QMI operation failed: ");
g_task_return_error (task, error);
} else if (!qmi_message_voice_stop_continuous_dtmf_output_get_result (output, &error)) {
- g_prefix_error (&error, "Couldn't send DTMF character: ");
+ g_prefix_error (&error, "Couldn't stop DTMF character: ");
g_task_return_error (task, error);
} else {
g_task_return_boolean (task, TRUE);
}
-
g_object_unref (task);
}
-static gboolean
-voice_stop_continuous_dtmf (GTask *task)
+static void
+call_stop_dtmf (MMBaseCall *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
{
- SendDtmfContext *ctx;
- GError *error = NULL;
- g_autoptr (QmiMessageVoiceStopContinuousDtmfInput) input = NULL;
+ MMCallQmi *self = MM_CALL_QMI (_self);
+ GTask *task;
+ QmiClient *client = NULL;
+ guint call_id = 0;
+ GError *error = NULL;
+ g_autoptr (QmiMessageVoiceStopContinuousDtmfInput) input = NULL;
+
+ if (!get_client_and_call_id (self,
+ callback,
+ user_data,
+ &client,
+ &call_id))
+ return;
- ctx = g_task_get_task_data (task);
+ task = g_task_new (self, NULL, callback, user_data);
input = qmi_message_voice_stop_continuous_dtmf_input_new ();
- qmi_message_voice_stop_continuous_dtmf_input_set_data (input, ctx->call_id, &error);
+ if (!qmi_message_voice_stop_continuous_dtmf_input_set_data (input,
+ call_id,
+ &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
- qmi_client_voice_stop_continuous_dtmf (QMI_CLIENT_VOICE (ctx->client),
+ /* Stop sending DTMF tone */
+ qmi_client_voice_stop_continuous_dtmf (QMI_CLIENT_VOICE (client),
input,
5,
NULL,
(GAsyncReadyCallback) voice_stop_continuous_dtmf_ready,
task);
+}
- return G_SOURCE_REMOVE;
+static gboolean
+call_send_dtmf_finish (MMBaseCall *call,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
}
static void
@@ -402,85 +445,55 @@ 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);
+ g_autoptr (QmiMessageVoiceStartContinuousDtmfOutput) output = NULL;
+ GError *error = NULL;
output = qmi_client_voice_start_continuous_dtmf_finish (client, res, &error);
if (!output) {
g_prefix_error (&error, "QMI operation failed: ");
g_task_return_error (task, error);
- g_object_unref (task);
- return;
- }
-
- if (!qmi_message_voice_start_continuous_dtmf_output_get_result (output, &error)) {
+ } else if (!qmi_message_voice_start_continuous_dtmf_output_get_result (output, &error)) {
g_prefix_error (&error, "Couldn't send DTMF character: ");
g_task_return_error (task, error);
- g_object_unref (task);
- return;
+ } else {
+ g_task_return_boolean (task, TRUE);
}
-
- /* 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);
+ g_object_unref (task);
}
static void
-call_send_dtmf (MMBaseCall *self,
+call_send_dtmf (MMBaseCall *_self,
const gchar *dtmf,
GAsyncReadyCallback callback,
gpointer user_data)
{
- GTask *task;
- GError *error = NULL;
- SendDtmfContext *ctx;
- QmiClient *client = NULL;
- guint8 call_id;
- g_autoptr (QmiMessageVoiceStartContinuousDtmfInput) input = NULL;
-
- /* Ensure Voice client */
- if (!ensure_qmi_client (MM_CALL_QMI (self),
- QMI_SERVICE_VOICE, &client,
- callback, user_data))
+ MMCallQmi *self = MM_CALL_QMI (_self);
+ GTask *task;
+ QmiClient *client = NULL;
+ guint call_id = 0;
+ GError *error = NULL;
+ g_autoptr (QmiMessageVoiceStartContinuousDtmfInput) input = NULL;
+
+ if (!get_client_and_call_id (self,
+ callback,
+ user_data,
+ &client,
+ &call_id))
return;
task = g_task_new (self, NULL, callback, user_data);
- call_id = mm_base_call_get_index (self);
- if (call_id == 0) {
- g_task_return_new_error (task,
- MM_CORE_ERROR,
- MM_CORE_ERROR_INVALID_ARGS,
- "Invalid call index");
- g_object_unref (task);
- return;
- }
-
- ctx = g_slice_new0 (SendDtmfContext);
- ctx->client = g_object_ref (client);
- ctx->call_id = call_id;
-
- g_task_set_task_data (task, ctx, (GDestroyNotify) send_dtmf_context_free);
-
- /* Send DTMF character as ASCII number */
input = qmi_message_voice_start_continuous_dtmf_input_new ();
- qmi_message_voice_start_continuous_dtmf_input_set_data (input,
- call_id,
- (guint8) dtmf[0],
- &error);
- if (error) {
- g_task_return_new_error (task,
- MM_CORE_ERROR,
- MM_CORE_ERROR_INVALID_ARGS,
- "DTMF data build failed");
+ if (!qmi_message_voice_start_continuous_dtmf_input_set_data (input,
+ call_id,
+ dtmf[0],
+ &error)) {
+ g_task_return_error (task, error);
g_object_unref (task);
return;
}
+ /* Send DTMF character as ASCII number */
qmi_client_voice_start_continuous_dtmf (QMI_CLIENT_VOICE (client),
input,
5,
@@ -548,4 +561,6 @@ mm_call_qmi_class_init (MMCallQmiClass *klass)
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;
+ base_call_class->stop_dtmf = call_stop_dtmf;
+ base_call_class->stop_dtmf_finish = call_stop_dtmf_finish;
}