Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • marin/ModemManager
  • mobile-broadband/ModemManager
  • lkundrak/ModemManager
  • t-8ch/ModemManager
  • rah/ModemManager
  • benchan/ModemManager
  • claudep/ModemManager
  • ebassi/ModemManager
  • dcbw/ModemManager
  • alfonsosanchezbeato/ModemManager
  • xingnifeng/ModemManager
  • paulbartell/ModemManager
  • MiloCasagrande/ModemManager
  • afett/ModemManager
  • svenschwermer/ModemManager
  • cancanxinxin/ModemManager
  • mjablonsky/ModemManager
  • vicamo/ModemManager
  • kraj/ModemManager
  • lisablanco777/ModemManager
  • Laymer/ModemManager
  • mahatma/ModemManager
  • ejcaruso/ModemManager
  • scootergrisen/ModemManager
  • fran.dieguez/ModemManager
  • bgalvani/ModemManager
  • dnlplm/ModemManager
  • c.lobrano/ModemManager
  • lingzangwuhen/ModemManager
  • aleksm/ModemManager
  • alexanderyashin/ModemManager
  • welaq/ModemManager
  • wi24rd/ModemManager
  • sadiq/ModemManager
  • Linaro/ModemManager
  • HED-mstarr/ModemManager
  • kdrobinski/ModemManager
  • emintufan/ModemManager
  • yurchor/ModemManager
  • rafaelff/ModemManager
  • remuswu1019/ModemManager
  • Fanice.luo/ModemManager
  • roman.stratiienko/ModemManager
  • mno294163/ModemManager
  • mozzwald/ModemManager
  • andika/ModemManager
  • fiziyr/ModemManager
  • teijo.kinnunen/ModemManager
  • dakhouya/ModemManager
  • gci/ModemManager
  • alexcani109/ModemManager
  • ArenM/ModemManager
  • bpeterlytx/ModemManager
  • Hagstrom/ModemManager
  • aplazas/ModemManager
  • justinsg/ModemManager
  • carlyin/ModemManager
  • dle0/ModemManager
  • jessy.diamondman/ModemManager
  • steven831926/ModemManager
  • nytowl/ModemManager
  • prescott66/ModemManager
  • ajonsson/ModemManager
  • mkm/ModemManager
  • pholla/ModemManager
  • wicadmin/ModemManager
  • deng.yi/ModemManager
  • madhavadas/ModemManager
  • fmartinsons/ModemManager
  • Nananas/ModemManager
  • loicpoulain/ModemManager
  • rafal.mszal/ModemManager
  • dylanvanassche/ModemManager
  • myrkr/ModemManager
  • mobilekiller742/ModemManager
  • maorui/ModemManager
  • timo.jyrinki/ModemManager
  • luksen/ModemManager
  • stoehraj/ModemManager
  • vpalatin/ModemManager
  • yaron/ModemManager
  • yegorslists/ModemManager
  • NorwayFun/ModemManager
  • takc923/ModemManager
  • zrshuo.zhang/ModemManager
  • zhuzhaoyu_fibocom/ModemManager
  • bentiss/ModemManager
  • Som_SP/ModemManager
  • KingSun/ModemManager
  • eliasr/ModemManager
  • craftyguy/ModemManager
  • 3378897661/ModemManager
  • ShivakuS/ModemManager
  • bmork/ModemManager
  • ivan.mikhanchuk/ModemManager
  • SimonGuan/ModemManager
  • ziyou/ModemManager
  • juliandehm/ModemManager
  • Jarrah/ModemManager
  • a-wai/ModemManager
  • ZhangMingjie/ModemManager
  • felipeborges/ModemManager
  • dirksu/ModemManager
  • conklinjames175/ModemManager
  • agupta/ModemManager
  • Jarvis-Jiang-G/ModemManager
  • qyliss/ModemManager
  • UnitacSW/ModemManager
  • joelselvaraj/ModemManager
  • mips171/ModemManager
  • LeSpocky/ModemManager
  • Benoit_Monin/ModemManager
  • inigomartinez/ModemManager
  • humb3rtoguti3rr3z/ModemManager
  • tpikonen/ModemManager
  • awaittrot/ModemManager
  • prakash_p/ModemManager
  • florencewchan/ModemManager
  • shawnguo/ModemManager
  • seabass/ModemManager
  • laeyraud/ModemManager
  • maciejsszmigiero/ModemManager
  • dansebcar/ModemManager
  • Shiva/ModemManager
  • stranche/ModemManager
  • anugrah/ModemManager
  • matthewvia/ModemManager
  • umohr/ModemManager
  • matlinuxer2/ModemManager
  • feckert/ModemManager
  • jakko/ModemManager
  • elguap0x0/ModemManager
  • Project0/ModemManager
  • Pac-Man/ModemManager
  • raphj/ModemManager
  • DadiBit/modem-manager
  • pholla1/ModemManager
  • jinjian.song/ModemManager
  • nathangoulding/ModemManager
  • sc0w/ModemManager
  • guisil/ModemManager
  • arbruijn/ModemManager
  • alor/ModemManager
  • andrewlassalle/ModemManager
  • sdeziel/ModemManager
  • troth/ModemManager
  • Stephan/ModemManager
  • nfollens/ModemManager
  • kzapalowicz/ModemManager
  • alad/ModemManager
  • quic_akasagga/ModemManager
  • rmao/ModemManager
  • chrta/ModemManager
  • nicholas123/ModemManager
  • aa13q/ModemManager
  • chandupokuru/ModemManager
  • dskorykh/ModemManager
  • skv/ModemManager
  • shawn.xiao/ModemManager
  • pineapplefurly/ModemManager
  • tsabsch/ModemManager
  • hthiery/ModemManager
  • devrtz/ModemManager
  • haata/ModemManager
  • okaestne/ModemManager
  • nmarupaka/ModemManager
  • simdeveloper/ModemManager
  • ShaneParslow/ModemManager
  • dukexinaw/ModemManager
  • james.fu/ModemManager
  • dchard/ModemManager
  • lvoegl/ModemManager
  • zaripov-kamil/ModemManager
  • kbuksha/ModemManager
  • dos/ModemManager
  • airsoup/ModemManager
  • mafolk/ModemManager
  • tomwimmenhove/ModemManager
  • Lauszus/ModemManager
  • ujjwalpande/ModemManager
  • jordimas/ModemManager
  • diekleinekuh/ModemManager
  • agx/ModemManager
  • PeterK/ModemManager
  • QuectelDuke/ModemManager
  • Lupuliang/ModemManager
  • sinaro/ModemManager
  • Jerry.Meng/ModemManager
  • dahopem/ModemManager
  • robimarko/ModemManager
  • ausil/ModemManager
  • Ming-Pei/ModemManager
  • mkrle/ModemManager
  • PaulosV/ModemManager
  • fabio.porcedda/ModemManager
  • kop316/ModemManager
  • rgenoud/ModemManager
  • janro/ModemManager
  • RICCIARDI-Adrien/ModemManager
  • Garfield/ModemManager
  • nt8r/ModemManager
  • floris.sm/ModemManager
  • quic_krelangi/ModemManager
  • bolan/ModemManager
  • svalery/ModemManager
  • kevlhop/ModemManager
  • evetsso/ModemManager
  • i-tek/ModemManager
  • vanillan/ModemManager
  • jean/ModemManager
  • tuxor1337/ModemManager
  • lupuliang5/ModemManager
  • fuzzy7k/ModemManager
  • piotrdrag/ModemManager
  • asusmith/ModemManager
  • Mank.Wang/ModemManager
  • mank/ModemManager
  • sleirsgoevy/ModemManager
  • quic_asusmith/ModemManager
  • cs99/ModemManager
  • barracuda156/ModemManager
  • anaghg/ModemManager
  • stigma/ModemManager
  • biemster/ModemManager
  • zolfa/ModemManager
  • Mank-Netprisma/ModemManager
  • andrew-sayers/ModemManager
  • jmkim/ModemManager
  • maldiran/ModemManager
229 results
Show changes
Commits on Source (28)
Showing
with 2014 additions and 599 deletions
......@@ -145,6 +145,12 @@ mmcli_call_shutdown (void)
static void
print_call_info (MMCall *call)
{
const gchar *audio_port;
MMCallAudioFormat *audio_format;
audio_port = mm_call_get_audio_port (call);
audio_format = mm_call_peek_audio_format (call);
/* Not the best thing to do, as we may be doing _get() calls twice, but
* easiest to maintain */
#undef VALIDATE
......@@ -161,6 +167,23 @@ print_call_info (MMCall *call)
if (mm_call_get_state_reason(call) != MM_CALL_STATE_REASON_UNKNOWN)
g_print (" | state reason: '%s'\n",
mm_call_state_reason_get_string(mm_call_get_state_reason (call)));
if (audio_port)
g_print (" | audio port: '%s'\n", VALIDATE (audio_port));
if (audio_format) {
guint rate = mm_call_audio_format_get_rate (audio_format);
gchar *rate_str = rate ? g_strdup_printf ("%u", rate) : NULL;
g_print (" -------------------------\n"
" Audio Format | encoding: '%s'\n"
" | resolution: '%s'\n"
" | rate: '%s'\n",
VALIDATE (mm_call_audio_format_get_encoding (audio_format)),
VALIDATE (mm_call_audio_format_get_resolution (audio_format)),
VALIDATE (rate_str));
g_free (rate_str);
}
}
static void
......
......@@ -1257,6 +1257,10 @@ mm_call_dup_number
mm_call_get_direction
mm_call_get_state
mm_call_get_state_reason
mm_call_get_audio_port
mm_call_dup_audio_port
mm_call_get_audio_format
mm_call_peek_audio_format
<SUBSECTION Methods>
mm_call_start
mm_call_start_finish
......@@ -1341,6 +1345,34 @@ MM_TYPE_PCO
mm_pco_get_type
</SECTION>
<SECTION>
<FILE>mm-call-audio-format</FILE>
<TITLE>MMCallAudioFormat</TITLE>
MMCallAudioFormat
<SUBSECTION Getters>
mm_call_audio_format_get_encoding
mm_call_audio_format_get_resolution
mm_call_audio_format_get_rate
<SUBSECTION Private>
mm_call_audio_format_get_dictionary
mm_call_audio_format_new
mm_call_audio_format_new_from_dictionary
mm_call_audio_format_set_encoding
mm_call_audio_format_set_resolution
mm_call_audio_format_set_rate
mm_call_audio_format_dup
<SUBSECTION Standard>
MMCallAudioFormatClass
MMCallAudioFormatPrivate
MM_CALL_AUDIO_FORMAT
MM_CALL_AUDIO_FORMAT_CLASS
MM_CALL_AUDIO_FORMAT_GET_CLASS
MM_IS_CALL_AUDIO_FORMAT
MM_IS_CALL_AUDIO_FORMAT_CLASS
MM_TYPE_CALL_AUDIO_FORMAT
mm_call_audio_format_get_type
</SECTION>
<SECTION>
<FILE>mm-enums-types</FILE>
<TITLE>Flags and Enumerations</TITLE>
......
......@@ -1354,8 +1354,9 @@ typedef enum { /*< underscore_name=mm_call_state >*/
* @MM_CALL_STATE_REASON_INCOMING_NEW: Received a new incoming call.
* @MM_CALL_STATE_REASON_ACCEPTED: Dialing or Ringing call is accepted.
* @MM_CALL_STATE_REASON_TERMINATED: Call is correctly terminated.
* @MM_CALL_STATE_REASON_REFUSED_OR_BUSY: Remote peer is busy or refused call
* @MM_CALL_STATE_REASON_REFUSED_OR_BUSY: Remote peer is busy or refused call.
* @MM_CALL_STATE_REASON_ERROR: Wrong number or generic network error.
* @MM_CALL_STATE_REASON_AUDIO_SETUP_FAILED: Error setting up audio channel.
*/
typedef enum { /*< underscore_name=mm_call_state_reason >*/
MM_CALL_STATE_REASON_UNKNOWN = 0,
......@@ -1364,7 +1365,8 @@ typedef enum { /*< underscore_name=mm_call_state_reason >*/
MM_CALL_STATE_REASON_ACCEPTED = 3,
MM_CALL_STATE_REASON_TERMINATED = 4,
MM_CALL_STATE_REASON_REFUSED_OR_BUSY = 5,
MM_CALL_STATE_REASON_ERROR = 6
MM_CALL_STATE_REASON_ERROR = 6,
MM_CALL_STATE_REASON_AUDIO_SETUP_FAILED = 7
} MMCallStateReason;
/**
......
......@@ -111,5 +111,44 @@
The remote phone number.
-->
<property name="Number" type="s" access="read" />
<!--
AudioPort:
If call audio is routed via the host, the name of the kernel device that
provides the audio. For example, with certain Huawei USB modems, this
property might be "ttyUSB2" indicating audio is available via ttyUSB2 in
the format described by the AudioFormat property.
-->
<property name="AudioPort" type="s" access="read" />
<!--
AudioFormat:
If call audio is routed via the host, a description of the audio format
supported by the audio port.
This property may include the following items:
<variablelist>
<varlistentry><term><literal>"encoding"</literal></term>
<listitem>
The audio encoding format. For example, "pcm" for PCM audio.
</listitem>
</varlistentry>
<varlistentry><term><literal>"resolution"</literal></term>
<listitem>
The sampling precision and its encoding format. For example,
"s16le" for signed 16-bit little-endian samples.
</listitem>
</varlistentry>
<varlistentry><term><literal>"rate"</literal></term>
<listitem>
The sampling rate as an unsigned integer. For example, 8000 for
8000hz.
</listitem>
</varlistentry>
</variablelist>
-->
<property name="AudioFormat" type="a{sv}" access="read" />
</interface>
</node>
......@@ -85,6 +85,8 @@ libmm_glib_la_SOURCES = \
mm-kernel-event-properties.c \
mm-pco.h \
mm-pco.c \
mm-call-audio-format.h \
mm-call-audio-format.c \
$(NULL)
libmm_glib_la_CPPFLAGS = \
......@@ -155,6 +157,7 @@ include_HEADERS = \
mm-signal.h \
mm-kernel-event-properties.h \
mm-pco.h \
mm-call-audio-format.h \
$(NULL)
CLEANFILES =
......
/* -*- 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) 2017 Red Hat, Inc.
*/
#include <string.h>
#include "mm-errors-types.h"
#include "mm-call-audio-format.h"
/**
* SECTION: mm-call-audio-format
* @title: MMCallAudioFormat
* @short_description: Helper object to handle voice call audio formats.
*
* The #MMCallAudioFormat is an object handling the voice call audio format
* which describes how to send/receive voice call audio from the host.
*
* This object is retrieved with either mm_call_get_audio_format() or
* mm_call_peek_audio_format().
*/
G_DEFINE_TYPE (MMCallAudioFormat, mm_call_audio_format, G_TYPE_OBJECT)
#define PROPERTY_ENCODING "encoding"
#define PROPERTY_RESOLUTION "resolution"
#define PROPERTY_RATE "rate"
struct _MMCallAudioFormatPrivate {
gchar *encoding;
gchar *resolution;
guint rate;
};
/*****************************************************************************/
/**
* mm_call_audio_format_get_encoding:
* @self: a #MMCallAudioFormat.
*
* Gets the encoding of the audio format. For example, "pcm" for PCM-encoded
* audio.
*
* Returns: a string with the encoding, or #NULL if unknown. Do not free the returned value, it is owned by @self.
*/
const gchar *
mm_call_audio_format_get_encoding (MMCallAudioFormat *self)
{
g_return_val_if_fail (MM_IS_CALL_AUDIO_FORMAT (self), NULL);
return self->priv->encoding;
}
void
mm_call_audio_format_set_encoding (MMCallAudioFormat *self,
const gchar *encoding)
{
g_return_if_fail (MM_IS_CALL_AUDIO_FORMAT (self));
g_free (self->priv->encoding);
self->priv->encoding = g_strdup (encoding);
}
/*****************************************************************************/
/**
* mm_call_audio_format_get_resolution:
* @self: a #MMCallAudioFormat.
*
* Gets the resolution of the audio format. For example, "s16le" for signed
* 16-bit little-endian audio sampling resolution.
*
* Returns: a string with the resolution, or #NULL if unknown. Do not free the returned value, it is owned by @self.
*/
const gchar *
mm_call_audio_format_get_resolution (MMCallAudioFormat *self)
{
g_return_val_if_fail (MM_IS_CALL_AUDIO_FORMAT (self), NULL);
return self->priv->resolution;
}
void
mm_call_audio_format_set_resolution (MMCallAudioFormat *self,
const gchar *resolution)
{
g_return_if_fail (MM_IS_CALL_AUDIO_FORMAT (self));
g_free (self->priv->resolution);
self->priv->resolution = g_strdup (resolution);
}
/*****************************************************************************/
/**
* mm_call_audio_format_get_rate:
* @self: a #MMCallAudioFormat.
*
* Gets the sampling rate of the audio format. For example, 8000 for an 8000hz
* sampling rate.
*
* Returns: the sampling rate, or 0 if unknown.
*/
guint
mm_call_audio_format_get_rate (MMCallAudioFormat *self)
{
g_return_val_if_fail (MM_IS_CALL_AUDIO_FORMAT (self), 0);
return self->priv->rate;
}
void
mm_call_audio_format_set_rate (MMCallAudioFormat *self,
guint rate)
{
g_return_if_fail (MM_IS_CALL_AUDIO_FORMAT (self));
self->priv->rate = rate;
}
/*****************************************************************************/
GVariant *
mm_call_audio_format_get_dictionary (MMCallAudioFormat *self)
{
GVariantBuilder builder;
/* We do allow NULL */
if (!self)
return NULL;
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
if (self) {
if (self->priv->encoding)
g_variant_builder_add (&builder,
"{sv}",
PROPERTY_ENCODING,
g_variant_new_string (self->priv->encoding));
if (self->priv->resolution)
g_variant_builder_add (&builder,
"{sv}",
PROPERTY_RESOLUTION,
g_variant_new_string (self->priv->resolution));
if (self->priv->rate)
g_variant_builder_add (&builder,
"{sv}",
PROPERTY_RATE,
g_variant_new_uint32 (self->priv->rate));
}
return g_variant_builder_end (&builder);
}
/*****************************************************************************/
MMCallAudioFormat *
mm_call_audio_format_new_from_dictionary (GVariant *dictionary,
GError **error)
{
GVariantIter iter;
gchar *key;
GVariant *value;
MMCallAudioFormat *self;
self = mm_call_audio_format_new ();
if (!dictionary)
return self;
if (!g_variant_is_of_type (dictionary, G_VARIANT_TYPE ("a{sv}"))) {
g_set_error (error,
MM_CORE_ERROR,
MM_CORE_ERROR_INVALID_ARGS,
"Cannot create call audio format from dictionary: "
"invalid variant type received");
g_object_unref (self);
return NULL;
}
g_variant_iter_init (&iter, dictionary);
while (g_variant_iter_next (&iter, "{sv}", &key, &value)) {
if (g_str_equal (key, PROPERTY_ENCODING))
mm_call_audio_format_set_encoding (
self,
g_variant_get_string (value, NULL));
else if (g_str_equal (key, PROPERTY_RESOLUTION))
mm_call_audio_format_set_resolution (
self,
g_variant_get_string (value, NULL));
else if (g_str_equal (key, PROPERTY_RATE))
mm_call_audio_format_set_rate (
self,
g_variant_get_uint32 (value));
g_free (key);
g_variant_unref (value);
}
return self;
}
/*****************************************************************************/
/**
* mm_call_audio_format_dup:
* @orig: a #MMCallAudioFormat
*
* Creates a copy of @orig.
*
* Returns: (transfer full): a newly created #MMCallAudioFormat
*/
MMCallAudioFormat *
mm_call_audio_format_dup (MMCallAudioFormat *orig)
{
GVariant *dict;
MMCallAudioFormat *copy;
GError *error = NULL;
g_return_val_if_fail (MM_IS_CALL_AUDIO_FORMAT (orig), NULL);
dict = mm_call_audio_format_get_dictionary (orig);
copy = mm_call_audio_format_new_from_dictionary (dict, &error);
g_assert_no_error (error);
g_variant_unref (dict);
return copy;
}
/*****************************************************************************/
MMCallAudioFormat *
mm_call_audio_format_new (void)
{
return (MM_CALL_AUDIO_FORMAT (
g_object_new (MM_TYPE_CALL_AUDIO_FORMAT, NULL)));
}
static void
mm_call_audio_format_init (MMCallAudioFormat *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self),
MM_TYPE_CALL_AUDIO_FORMAT,
MMCallAudioFormatPrivate);
}
static void
finalize (GObject *object)
{
MMCallAudioFormat *self = MM_CALL_AUDIO_FORMAT (object);
g_free (self->priv->encoding);
g_free (self->priv->resolution);
G_OBJECT_CLASS (mm_call_audio_format_parent_class)->finalize (object);
}
static void
mm_call_audio_format_class_init (MMCallAudioFormatClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
g_type_class_add_private (object_class, sizeof (MMCallAudioFormatPrivate));
object_class->finalize = finalize;
}
/* -*- 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) 2017 Red Hat, Inc.
*/
#ifndef MM_CALL_AUDIO_FORMAT_H
#define MM_CALL_AUDIO_FORMAT_H
#if !defined (__LIBMM_GLIB_H_INSIDE__) && !defined (LIBMM_GLIB_COMPILATION)
#error "Only <libmm-glib.h> can be included directly."
#endif
#include <ModemManager.h>
#include <glib-object.h>
G_BEGIN_DECLS
#define MM_TYPE_CALL_AUDIO_FORMAT (mm_call_audio_format_get_type ())
#define MM_CALL_AUDIO_FORMAT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_CALL_AUDIO_FORMAT, MMCallAudioFormat))
#define MM_CALL_AUDIO_FORMAT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_CALL_AUDIO_FORMAT, MMCallAudioFormatClass))
#define MM_IS_CALL_AUDIO_FORMAT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_CALL_AUDIO_FORMAT))
#define MM_IS_CALL_AUDIO_FORMAT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_CALL_AUDIO_FORMAT))
#define MM_CALL_AUDIO_FORMAT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_CALL_AUDIO_FORMAT, MMCallAudioFormatClass))
typedef struct _MMCallAudioFormat MMCallAudioFormat;
typedef struct _MMCallAudioFormatClass MMCallAudioFormatClass;
typedef struct _MMCallAudioFormatPrivate MMCallAudioFormatPrivate;
/**
* MMCallAudioFormat:
*
* The #MMCallAudioFormat structure contains private data and should
* only be accessed using the provided API.
*/
struct _MMCallAudioFormat {
/*< private >*/
GObject parent;
MMCallAudioFormatPrivate *priv;
};
struct _MMCallAudioFormatClass {
/*< private >*/
GObjectClass parent;
};
GType mm_call_audio_format_get_type (void);
const gchar *mm_call_audio_format_get_encoding (MMCallAudioFormat *self);
const gchar *mm_call_audio_format_get_resolution (MMCallAudioFormat *self);
guint mm_call_audio_format_get_rate (MMCallAudioFormat *self);
/*****************************************************************************/
/* ModemManager/libmm-glib/mmcli specific methods */
#if defined (_LIBMM_INSIDE_MM) || \
defined (_LIBMM_INSIDE_MMCLI) || \
defined (LIBMM_GLIB_COMPILATION)
MMCallAudioFormat *mm_call_audio_format_new (void);
MMCallAudioFormat *mm_call_audio_format_new_from_dictionary (GVariant *dictionary,
GError **error);
MMCallAudioFormat *mm_call_audio_format_dup (MMCallAudioFormat *orig);
void mm_call_audio_format_set_encoding (MMCallAudioFormat *self,
const gchar *encoding);
void mm_call_audio_format_set_resolution (MMCallAudioFormat *self,
const gchar *resolution);
void mm_call_audio_format_set_rate (MMCallAudioFormat *self,
guint rate);
GVariant *mm_call_audio_format_get_dictionary (MMCallAudioFormat *self);
#endif
G_END_DECLS
#endif /* MM_CALL_AUDIO_FORMAT_H */
......@@ -36,6 +36,13 @@
G_DEFINE_TYPE (MMCall, mm_call, MM_GDBUS_TYPE_CALL_PROXY)
struct _MMCallPrivate {
/* Audio Format */
GMutex audio_format_mutex;
guint audio_format_id;
MMCallAudioFormat *audio_format;
};
/*****************************************************************************/
/**
......@@ -176,6 +183,156 @@ mm_call_get_state_reason (MMCall *self)
/*****************************************************************************/
/**
* mm_call_get_audio_port:
* @self: A #MMCall.
*
* Gets the kernel device used for audio (if any).
*
* Returns: (transfer none): The audio port, or %NULL if call audio is not
* routed via the host or couldn't be retrieved.
*/
const gchar *
mm_call_get_audio_port (MMCall *self)
{
g_return_val_if_fail (MM_IS_CALL (self), NULL);
RETURN_NON_EMPTY_CONSTANT_STRING (
mm_gdbus_call_get_audio_port (MM_GDBUS_CALL (self)));
}
/**
* mm_call_dup_audio_port:
* @self: A #MMCall.
*
* Gets the kernel device used for audio (if any).
*
* Returns: (transfer full): The audio port, or %NULL if call audio is not
* routed via the host or couldn't be retrieved.
*/
gchar *
mm_call_dup_audio_port (MMCall *self)
{
g_return_val_if_fail (MM_IS_CALL (self), NULL);
RETURN_NON_EMPTY_STRING (
mm_gdbus_call_dup_audio_port (MM_GDBUS_CALL (self)));
}
/*****************************************************************************/
static void
audio_format_updated (MMCall *self,
GParamSpec *pspec)
{
g_mutex_lock (&self->priv->audio_format_mutex);
{
GVariant *dictionary;
g_clear_object (&self->priv->audio_format);
/* TODO: update existing object instead of re-creating? */
dictionary = mm_gdbus_call_get_audio_format (MM_GDBUS_CALL (self));
if (dictionary) {
GError *error = NULL;
self->priv->audio_format = mm_call_audio_format_new_from_dictionary (dictionary, &error);
if (error) {
g_warning ("Invalid audio format update received: %s", error->message);
g_error_free (error);
}
}
}
g_mutex_unlock (&self->priv->audio_format_mutex);
}
static void
ensure_internal_audio_format (MMCall *self,
MMCallAudioFormat **dup)
{
g_mutex_lock (&self->priv->audio_format_mutex);
{
/* If this is the first time ever asking for the object, setup the
* update listener and the initial object, if any. */
if (!self->priv->audio_format_id) {
GVariant *dictionary;
dictionary = mm_gdbus_call_dup_audio_format (MM_GDBUS_CALL (self));
if (dictionary) {
GError *error = NULL;
self->priv->audio_format = mm_call_audio_format_new_from_dictionary (dictionary, &error);
if (error) {
g_warning ("Invalid initial audio format: %s", error->message);
g_error_free (error);
}
g_variant_unref (dictionary);
}
/* No need to clear this signal connection when freeing self */
self->priv->audio_format_id =
g_signal_connect (self,
"notify::audio-format",
G_CALLBACK (audio_format_updated),
NULL);
}
if (dup && self->priv->audio_format)
*dup = g_object_ref (self->priv->audio_format);
}
g_mutex_unlock (&self->priv->audio_format_mutex);
}
/**
* mm_call_get_audio_format:
* @self: A #MMCall.
*
* Gets a #MMCallAudioFormat object specifying the audio format used by the
* audio port if call audio is routed via the host.
*
* <warning>The values reported by @self are not updated when the values in the
* interface change. Instead, the client is expected to call
* mm_call_get_audio_format() again to get a new #MMCallAudioFormat with the
* new values.</warning>
*
* Returns: (transfer full): A #MMCallAudioFormat that must be freed with g_object_unref() or %NULL if unknown.
*/
MMCallAudioFormat *
mm_call_get_audio_format (MMCall *self)
{
MMCallAudioFormat *format = NULL;
g_return_val_if_fail (MM_IS_CALL (self), NULL);
ensure_internal_audio_format (self, &format);
return format;
}
/**
* mm_call_peek_audio_format:
* @self: A #MMCall.
*
* Gets a #MMCallAudioFormat object specifying the audio format used by the
* audio port if call audio is routed via the host.
*
* <warning>The returned value is only valid until the property changes so
* it is only safe to use this function on the thread where
* @self was constructed. Use mm_call_get_audio_format() if on another
* thread.</warning>
*
* Returns: (transfer none): A #MMCallAudioFormat. Do not free the returned value, it belongs to @self.
*/
MMCallAudioFormat *
mm_call_peek_audio_format (MMCall *self)
{
g_return_val_if_fail (MM_IS_CALL (self), NULL);
ensure_internal_audio_format (self, NULL);
return self->priv->audio_format;
}
/*****************************************************************************/
/**
* mm_call_start_finish:
* @self: A #MMCall.
......@@ -500,9 +657,40 @@ mm_call_send_dtmf_sync (MMCall *self,
static void
mm_call_init (MMCall *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
MM_TYPE_CALL,
MMCallPrivate);
g_mutex_init (&self->priv->audio_format_mutex);
}
static void
finalize (GObject *object)
{
MMCall *self = MM_CALL (object);
g_mutex_clear (&self->priv->audio_format_mutex);
G_OBJECT_CLASS (mm_call_parent_class)->finalize (object);
}
static void
dispose (GObject *object)
{
MMCall *self = MM_CALL (object);
g_clear_object (&self->priv->audio_format);
G_OBJECT_CLASS (mm_call_parent_class)->dispose (object);
}
static void
mm_call_class_init (MMCallClass *call_class)
{
GObjectClass *object_class = G_OBJECT_CLASS (call_class);
g_type_class_add_private (object_class, sizeof (MMCallPrivate));
/* Virtual methods */
object_class->dispose = dispose;
object_class->finalize = finalize;
}
......@@ -26,7 +26,7 @@
#include <ModemManager.h>
#include "mm-gdbus-call.h"
#include "mm-call-properties.h"
#include "mm-call-audio-format.h"
G_BEGIN_DECLS
......@@ -39,6 +39,7 @@ G_BEGIN_DECLS
typedef struct _MMCall MMCall;
typedef struct _MMCallClass MMCallClass;
typedef struct _MMCallPrivate MMCallPrivate;
/**
* MMCall:
......@@ -49,7 +50,7 @@ typedef struct _MMCallClass MMCallClass;
struct _MMCall {
/*< private >*/
MmGdbusCallProxy parent;
gpointer unused;
MMCallPrivate *priv;
};
struct _MMCallClass {
......@@ -71,6 +72,11 @@ MMCallStateReason mm_call_get_state_reason (MMCall *self);
MMCallDirection mm_call_get_direction (MMCall *self);
const gchar *mm_call_get_audio_port (MMCall *self);
gchar *mm_call_dup_audio_port (MMCall *self);
MMCallAudioFormat *mm_call_get_audio_format (MMCall *self);
MMCallAudioFormat *mm_call_peek_audio_format(MMCall *self);
void mm_call_start (MMCall *self,
......
......@@ -1016,6 +1016,8 @@ pkglib_LTLIBRARIES += libmm-plugin-ublox.la
libmm_plugin_ublox_la_SOURCES = \
ublox/mm-plugin-ublox.c \
ublox/mm-plugin-ublox.h \
ublox/mm-call-ublox.c \
ublox/mm-call-ublox.h \
ublox/mm-broadband-bearer-ublox.h \
ublox/mm-broadband-bearer-ublox.c \
ublox/mm-broadband-modem-ublox.h \
......
......@@ -103,15 +103,6 @@ struct _MMBroadbandModemHuaweiPrivate {
GRegex *dsflowrpt_regex;
GRegex *ndisstat_regex;
/* Regex for voice call related notifications */
GRegex *orig_regex;
GRegex *conf_regex;
GRegex *conn_regex;
GRegex *cend_regex;
GRegex *ddtmf_regex;
GRegex *cschannelinfo_regex;
GRegex *eons_regex;
/* Regex to ignore */
GRegex *boot_regex;
GRegex *connect_regex;
......@@ -130,6 +121,9 @@ struct _MMBroadbandModemHuaweiPrivate {
GRegex *posend_regex;
GRegex *ecclist_regex;
GRegex *ltersrp_regex;
GRegex *cschannelinfo_regex;
GRegex *eons_regex;
GRegex *orig_regex;
FeatureSupport ndisdup_support;
FeatureSupport rfswitch_support;
......@@ -139,6 +133,7 @@ struct _MMBroadbandModemHuaweiPrivate {
FeatureSupport prefmode_support;
FeatureSupport time_support;
FeatureSupport nwtime_support;
FeatureSupport cvoice_support;
MMModemLocationSource enabled_sources;
......@@ -147,12 +142,16 @@ struct _MMBroadbandModemHuaweiPrivate {
GArray *prefmode_supported_modes;
DetailedSignal detailed_signal;
/* Voice call audio related properties */
guint audio_hz;
guint audio_bits;
};
/*****************************************************************************/
static GList *
get_at_port_list (MMBroadbandModemHuawei *self)
GList *
mm_broadband_modem_huawei_get_at_port_list (MMBroadbandModemHuawei *self)
{
GList *out = NULL;
MMPortSerialAt *port;
......@@ -1853,7 +1852,7 @@ set_3gpp_unsolicited_events_handlers (MMBroadbandModemHuawei *self,
{
GList *ports, *l;
ports = get_at_port_list (self);
ports = mm_broadband_modem_huawei_get_at_port_list (self);
/* Enable/disable unsolicited events in given port */
for (l = ports; l; l = g_list_next (l)) {
......@@ -2517,7 +2516,7 @@ set_cdma_unsolicited_events_handlers (MMBroadbandModemHuawei *self,
{
GList *ports, *l;
ports = get_at_port_list (self);
ports = mm_broadband_modem_huawei_get_at_port_list (self);
/* Enable/disable unsolicited events in given port */
for (l = ports; l; l = g_list_next (l)) {
......@@ -2863,235 +2862,75 @@ get_detailed_registration_state (MMIfaceModemCdma *self,
}
/*****************************************************************************/
/* Setup/Cleanup unsolicited events (Voice interface) */
static void
huawei_voice_origination (MMPortSerialAt *port,
GMatchInfo *match_info,
MMBroadbandModemHuawei *self)
{
guint call_x = 0;
guint call_type = 0;
if (!mm_get_uint_from_match_info (match_info, 1, &call_x))
return;
if (!mm_get_uint_from_match_info (match_info, 2, &call_type))
return;
mm_dbg ("[^ORIG] Origination call id '%u' of type '%u'", call_x, call_type);
/* TODO: Handle multiple calls
* mm_iface_modem_voice_set_call_id (MM_IFACE_MODEM_VOICE (self)); */
}
static void
huawei_voice_ringback_tone (MMPortSerialAt *port,
GMatchInfo *match_info,
MMBroadbandModemHuawei *self)
{
guint call_x = 0;
if (!mm_get_uint_from_match_info (match_info, 1, &call_x))
return;
mm_dbg ("[^CONF] Ringback tone from call id '%u'", call_x);
mm_iface_modem_voice_call_dialing_to_ringing (MM_IFACE_MODEM_VOICE (self));
}
static void
huawei_voice_call_connection (MMPortSerialAt *port,
GMatchInfo *match_info,
MMBroadbandModemHuawei *self)
{
guint call_x = 0;
guint call_type = 0;
if (!mm_get_uint_from_match_info (match_info, 1, &call_x))
return;
if (!mm_get_uint_from_match_info (match_info, 2, &call_type))
return;
mm_dbg ("[^CONN] Call id '%u' of type '%u' connected", call_x, call_type);
mm_iface_modem_voice_call_ringing_to_active (MM_IFACE_MODEM_VOICE (self));
}
static void
huawei_voice_call_end (MMPortSerialAt *port,
GMatchInfo *match_info,
MMBroadbandModemHuawei *self)
{
guint call_x = 0;
guint duration = 0;
guint cc_cause = 0;
guint end_status = 0;
if (!mm_get_uint_from_match_info (match_info, 1, &call_x))
return;
if (!mm_get_uint_from_match_info (match_info, 2, &duration))
return;
if (!mm_get_uint_from_match_info (match_info, 3, &end_status))
return;
//This is optional
mm_get_uint_from_match_info (match_info, 4, &cc_cause);
mm_dbg ("[^CEND] Call '%u' terminated with status '%u' and cause '%u'. Duration of call '%d'", call_x, end_status, cc_cause, duration);
mm_iface_modem_voice_network_hangup (MM_IFACE_MODEM_VOICE (self));
}
static void
huawei_voice_received_dtmf (MMPortSerialAt *port,
GMatchInfo *match_info,
MMBroadbandModemHuawei *self)
{
gchar *key;
key = g_match_info_fetch (match_info, 1);
if (key) {
mm_dbg ("[^DDTMF] Received DTMF '%s'", key);
mm_iface_modem_voice_received_dtmf (MM_IFACE_MODEM_VOICE (self), key);
}
}
static void
set_voice_unsolicited_events_handlers (MMBroadbandModemHuawei *self,
gboolean enable)
{
GList *ports, *l;
ports = get_at_port_list (self);
/* Enable/disable unsolicited events in given port */
for (l = ports; l; l = g_list_next (l)) {
MMPortSerialAt *port = MM_PORT_SERIAL_AT (l->data);
mm_port_serial_at_add_unsolicited_msg_handler (
port,
self->priv->orig_regex,
enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_voice_origination : NULL,
enable ? self : NULL,
NULL);
mm_port_serial_at_add_unsolicited_msg_handler (
port,
self->priv->conf_regex,
enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_voice_ringback_tone : NULL,
enable ? self : NULL,
NULL);
mm_port_serial_at_add_unsolicited_msg_handler (
port,
self->priv->conn_regex,
enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_voice_call_connection : NULL,
enable ? self : NULL,
NULL);
mm_port_serial_at_add_unsolicited_msg_handler (
port,
self->priv->cend_regex,
enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_voice_call_end : NULL,
enable ? self : NULL,
NULL);
mm_port_serial_at_add_unsolicited_msg_handler (
port,
self->priv->ddtmf_regex,
enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_voice_received_dtmf: NULL,
enable ? self : NULL,
NULL);
/* Ignore this message (Huawei ME909s-120 firmware. 23.613.61.00.00) */
mm_port_serial_at_add_unsolicited_msg_handler (
port,
self->priv->cschannelinfo_regex,
NULL, NULL, NULL);
/* Ignore this message (Huawei ME909s-120 firmware. 23.613.61.00.00) */
mm_port_serial_at_add_unsolicited_msg_handler (
port,
self->priv->eons_regex,
NULL, NULL, NULL);
}
g_list_free_full (ports, g_object_unref);
}
/* Check if Voice supported (Voice interface) */
static gboolean
modem_voice_setup_cleanup_unsolicited_events_finish (MMIfaceModemVoice *self,
GAsyncResult *res,
GError **error)
modem_voice_check_support_finish (MMIfaceModemVoice *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
parent_voice_setup_unsolicited_events_ready (MMIfaceModemVoice *self,
GAsyncResult *res,
GTask *task)
voice_parent_check_support_ready (MMIfaceModemVoice *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
gboolean parent_support;
if (!iface_modem_voice_parent->setup_unsolicited_events_finish (self, res, &error))
g_task_return_error (task, error);
else {
/* Our own setup now */
set_voice_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (self), TRUE);
g_task_return_boolean (task, TRUE);
}
parent_support = iface_modem_voice_parent->check_support_finish (self, res, NULL);
g_task_return_boolean (task, parent_support);
g_object_unref (task);
}
static void
modem_voice_setup_unsolicited_events (MMIfaceModemVoice *self,
GAsyncReadyCallback callback,
gpointer user_data)
cvoice_check_ready (MMBaseModem *_self,
GAsyncResult *res,
GTask *task)
{
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
/* Chain up parent's setup */
iface_modem_voice_parent->setup_unsolicited_events (
self,
(GAsyncReadyCallback)parent_voice_setup_unsolicited_events_ready,
task);
}
MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
GError *error = NULL;
const gchar *response;
static void
parent_voice_cleanup_unsolicited_events_ready (MMIfaceModemVoice *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
response = mm_base_modem_at_command_finish (_self, res, &error);
if (!response ||
!mm_huawei_parse_cvoice_response (response,
&self->priv->audio_hz,
&self->priv->audio_bits,
&error)) {
self->priv->cvoice_support = FEATURE_NOT_SUPPORTED;
mm_dbg ("Huawei-specific CVOICE is unsupported: %s", error->message);
g_clear_error (&error);
/* Now check generic support */
iface_modem_voice_parent->check_support (MM_IFACE_MODEM_VOICE (self),
(GAsyncReadyCallback)voice_parent_check_support_ready,
task);
return;
}
if (!iface_modem_voice_parent->cleanup_unsolicited_events_finish (self, res, &error))
g_task_return_error (task, error);
else
g_task_return_boolean (task, TRUE);
mm_dbg ("Huawei-specific CVOICE is supported");
self->priv->cvoice_support = FEATURE_SUPPORTED;
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
modem_voice_cleanup_unsolicited_events (MMIfaceModemVoice *self,
GAsyncReadyCallback callback,
gpointer user_data)
modem_voice_check_support (MMIfaceModemVoice *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
/* Check for Huawei-specific ^CVOICE support */
task = g_task_new (self, NULL, callback, user_data);
/* Our own cleanup first */
set_voice_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (self), FALSE);
/* And now chain up parent's cleanup */
iface_modem_voice_parent->cleanup_unsolicited_events (
self,
(GAsyncReadyCallback)parent_voice_cleanup_unsolicited_events_ready,
task);
mm_base_modem_at_command (MM_BASE_MODEM (self),
"^CVOICE?",
3,
TRUE,
(GAsyncReadyCallback)cvoice_check_ready,
task);
}
/*****************************************************************************/
......@@ -3225,10 +3064,21 @@ modem_voice_disable_unsolicited_events (MMIfaceModemVoice *self,
/* Create call (Voice interface) */
static MMBaseCall *
create_call (MMIfaceModemVoice *self)
create_call (MMIfaceModemVoice *_self,
MMCallDirection direction,
const gchar *number)
{
/* New Huawei Call */
return mm_call_huawei_new (MM_BASE_MODEM (self));
MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
/* If CVOICE is supported we must have audio settings */
g_assert (self->priv->cvoice_support == FEATURE_NOT_SUPPORTED ||
(self->priv->cvoice_support == FEATURE_SUPPORTED && self->priv->audio_hz && self->priv->audio_bits));
return mm_call_huawei_new (MM_BASE_MODEM (self),
direction,
number,
self->priv->audio_hz,
self->priv->audio_bits);
}
/*****************************************************************************/
......@@ -3313,7 +3163,7 @@ enable_disable_unsolicited_rfswitch_event_handler (MMBroadbandModemHuawei *self,
{
GList *ports, *l;
ports = get_at_port_list (self);
ports = mm_broadband_modem_huawei_get_at_port_list (self);
mm_dbg ("%s ^RFSWITCH unsolicited event handler",
enable ? "Enable" : "Disable");
......@@ -4091,7 +3941,7 @@ set_ignored_unsolicited_events_handlers (MMBroadbandModemHuawei *self)
{
GList *ports, *l;
ports = get_at_port_list (self);
ports = mm_broadband_modem_huawei_get_at_port_list (self);
/* Enable/disable unsolicited events in given port */
for (l = ports; l; l = g_list_next (l)) {
......@@ -4161,6 +4011,18 @@ set_ignored_unsolicited_events_handlers (MMBroadbandModemHuawei *self)
port,
self->priv->ltersrp_regex,
NULL, NULL, NULL);
mm_port_serial_at_add_unsolicited_msg_handler (
port,
self->priv->cschannelinfo_regex,
NULL, NULL, NULL);
mm_port_serial_at_add_unsolicited_msg_handler (
port,
self->priv->eons_regex,
NULL, NULL, NULL);
mm_port_serial_at_add_unsolicited_msg_handler (
port,
self->priv->orig_regex,
NULL, NULL, NULL);
}
g_list_free_full (ports, g_object_unref);
......@@ -4188,7 +4050,6 @@ setup_ports (MMBroadbandModem *self)
/* Now reset the unsolicited messages we'll handle when enabled */
set_3gpp_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (self), FALSE);
set_cdma_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (self), FALSE);
set_voice_unsolicited_events_handlers(MM_BROADBAND_MODEM_HUAWEI (self), FALSE);
/* NMEA GPS monitoring */
gps_data_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self));
......@@ -4281,41 +4142,12 @@ mm_broadband_modem_huawei_init (MMBroadbandModemHuawei *self)
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
self->priv->ltersrp_regex = g_regex_new ("\\r\\n\\^LTERSRP:.+\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
/* Voice related regex
* <CR><LF>^ORIG: <call_x>,<call_type><CR><LF>
* <CR><LF>^CONF: <call_x><CR><LF>
* <CR><LF>^CONN: <call_x>,<call_type><CR><LF>
* <CR><LF>^CEND: <call_x>,<duration>,<end_status>[,<cc_cause>]<CR><LF>
*/
self->priv->orig_regex = g_regex_new ("\\r\\n\\^ORIG:\\s*(\\d+),(\\d+)\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
self->priv->conf_regex = g_regex_new ("\\r\\n\\^CONF:\\s*(\\d+)\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
self->priv->conn_regex = g_regex_new ("\\r\\n\\^CONN:\\s*(\\d+),(\\d+)\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
self->priv->cend_regex = g_regex_new ("\\r\\n\\^CEND:\\s*(\\d+),\\s*(\\d+),\\s*(\\d+),?\\s*(\\d*)\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
/* Voice: receive DTMF regex
* <CR><LF>^DDTMF: <key><CR><LF>
* Key should be 0-9, A-D, *, #
*/
self->priv->ddtmf_regex = g_regex_new ("\\r\\n\\^DDTMF:\\s*([0-9A-D\\*\\#])\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
/* Voice: Unknown message that's broke ATA command
* <CR><LF>^CSCHANNELINFO: <number>,<number><CR><LF>
* Key should be 0-9, A-D, *, #
*/
self->priv->cschannelinfo_regex = g_regex_new ("\\r\\n\\^CSCHANNELINFO:\\s*(\\d+),(\\d+)\\r\\n",
self->priv->cschannelinfo_regex = g_regex_new ("\\r\\n\\^CSCHANNELINFO:.+\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
/* Voice: Unknown message that's broke ATA command
* <CR><LF>^EONS:<type><CR><LF>
*/
self->priv->eons_regex = g_regex_new ("\\r\\n\\^EONS:\\s*(\\d+)\\r\\n",
self->priv->eons_regex = g_regex_new ("\\r\\n\\^EONS:.+\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
self->priv->orig_regex = g_regex_new ("\\r\\n\\^ORIG:.+\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
self->priv->ndisdup_support = FEATURE_SUPPORT_UNKNOWN;
self->priv->rfswitch_support = FEATURE_SUPPORT_UNKNOWN;
......@@ -4325,6 +4157,7 @@ mm_broadband_modem_huawei_init (MMBroadbandModemHuawei *self)
self->priv->prefmode_support = FEATURE_SUPPORT_UNKNOWN;
self->priv->nwtime_support = FEATURE_SUPPORT_UNKNOWN;
self->priv->time_support = FEATURE_SUPPORT_UNKNOWN;
self->priv->cvoice_support = FEATURE_SUPPORT_UNKNOWN;
}
static void
......@@ -4365,13 +4198,9 @@ finalize (GObject *object)
g_regex_unref (self->priv->posend_regex);
g_regex_unref (self->priv->ecclist_regex);
g_regex_unref (self->priv->ltersrp_regex);
g_regex_unref (self->priv->orig_regex);
g_regex_unref (self->priv->conf_regex);
g_regex_unref (self->priv->conn_regex);
g_regex_unref (self->priv->cend_regex);
g_regex_unref (self->priv->ddtmf_regex);
g_regex_unref (self->priv->cschannelinfo_regex);
g_regex_unref (self->priv->eons_regex);
g_regex_unref (self->priv->orig_regex);
if (self->priv->syscfg_supported_modes)
g_array_unref (self->priv->syscfg_supported_modes);
......@@ -4486,10 +4315,8 @@ iface_modem_voice_init (MMIfaceModemVoice *iface)
{
iface_modem_voice_parent = g_type_interface_peek_parent (iface);
iface->setup_unsolicited_events = modem_voice_setup_unsolicited_events;
iface->setup_unsolicited_events_finish = modem_voice_setup_cleanup_unsolicited_events_finish;
iface->cleanup_unsolicited_events = modem_voice_cleanup_unsolicited_events;
iface->cleanup_unsolicited_events_finish = modem_voice_setup_cleanup_unsolicited_events_finish;
iface->check_support = modem_voice_check_support;
iface->check_support_finish = modem_voice_check_support_finish;
iface->enable_unsolicited_events = modem_voice_enable_unsolicited_events;
iface->enable_unsolicited_events_finish = modem_voice_enable_unsolicited_events_finish;
iface->disable_unsolicited_events = modem_voice_disable_unsolicited_events;
......
......@@ -50,5 +50,6 @@ MMBroadbandModemHuawei *mm_broadband_modem_huawei_new (const gchar *device,
MMPortSerialAt *mm_broadband_modem_huawei_peek_port_at_for_data (MMBroadbandModemHuawei *self,
MMPort *port);
GList *mm_broadband_modem_huawei_get_at_port_list (MMBroadbandModemHuawei *self);
#endif /* MM_BROADBAND_MODEM_HUAWEI_H */
......@@ -26,116 +26,420 @@
#include "mm-log.h"
#include "mm-base-modem-at.h"
#include "mm-broadband-modem-huawei.h"
#include "mm-call-huawei.h"
G_DEFINE_TYPE (MMCallHuawei, mm_call_huawei, MM_TYPE_BASE_CALL)
enum {
PROP_0,
PROP_AUDIO_HZ,
PROP_AUDIO_BITS,
PROP_LAST
};
static GParamSpec *properties[PROP_LAST];
struct _MMCallHuaweiPrivate {
GRegex *conf_regex;
GRegex *conn_regex;
GRegex *cend_regex;
GRegex *ddtmf_regex;
guint audio_hz;
guint audio_bits;
};
/*****************************************************************************/
/* Start the CALL */
/* Audio channel setup */
typedef struct {
MMBaseModem *modem;
MMPort *audio_port;
MMCallAudioFormat *audio_format;
} SetupAudioChannelContext;
static void
call_start_ready (MMBaseModem *modem,
GAsyncResult *res,
GTask *task)
setup_audio_channel_context_free (SetupAudioChannelContext *ctx)
{
MMBaseCall *self;
GError *error = NULL;
const gchar *response = NULL;
g_clear_object (&ctx->audio_port);
g_clear_object (&ctx->audio_format);
g_clear_object (&ctx->modem);
g_slice_free (SetupAudioChannelContext, ctx);
}
self = g_task_get_source_object (task);
static gboolean
setup_audio_channel_finish (MMBaseCall *self,
GAsyncResult *res,
MMPort **audio_port,
MMCallAudioFormat **audio_format,
GError **error)
{
SetupAudioChannelContext *ctx;
if (!g_task_propagate_boolean (G_TASK (res), error))
return FALSE;
ctx = g_task_get_task_data (G_TASK (res));
if (audio_port && ctx->audio_port)
*audio_port = g_object_ref (ctx->audio_port);
if (audio_format && ctx->audio_format)
*audio_format = g_object_ref (ctx->audio_format);
return TRUE;
}
static void
ddsetex_ready (MMBaseModem *modem,
GAsyncResult *res,
GTask *task)
{
MMCallHuawei *self;
SetupAudioChannelContext *ctx;
GError *error = NULL;
const gchar *response = NULL;
gchar *resolution_str;
response = mm_base_modem_at_command_finish (modem, res, &error);
if (error) {
if (g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_RESPONSE_TIMEOUT)) {
/* something is wrong, serial timeout could never occurs */
}
if (g_error_matches (error, MM_CONNECTION_ERROR, MM_CONNECTION_ERROR_NO_DIALTONE)) {
/* Update state */
mm_base_call_change_state (self, MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_ERROR);
}
if (g_error_matches (error, MM_CONNECTION_ERROR, MM_CONNECTION_ERROR_BUSY) ||
g_error_matches (error, MM_CONNECTION_ERROR, MM_CONNECTION_ERROR_NO_ANSWER) ||
g_error_matches (error, MM_CONNECTION_ERROR, MM_CONNECTION_ERROR_NO_CARRIER)) {
/* Update state */
mm_base_call_change_state (self, MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_REFUSED_OR_BUSY);
}
mm_dbg ("Couldn't start call : '%s'", error->message);
if (!response) {
mm_dbg ("Error enabling audio streaming: '%s'", error->message);
g_task_return_error (task, error);
g_object_unref (task);
return;
}
/* check response for error */
if (response && strlen (response) > 0 ) {
error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Couldn't start the call: "
"Modem response '%s'", response);
/* Update state */
mm_base_call_change_state (self, MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_REFUSED_OR_BUSY);
} else {
/* Update state */
mm_base_call_change_state (self, MM_CALL_STATE_DIALING, MM_CALL_STATE_REASON_OUTGOING_STARTED);
}
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
if (error) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
/* Setup audio format */
g_assert (self->priv->audio_hz && self->priv->audio_bits);
resolution_str = g_strdup_printf ("s%ule", self->priv->audio_bits);
ctx->audio_format = mm_call_audio_format_new ();
mm_call_audio_format_set_encoding (ctx->audio_format, "pcm");
mm_call_audio_format_set_resolution (ctx->audio_format, resolution_str);
mm_call_audio_format_set_rate (ctx->audio_format, self->priv->audio_hz);
/* The QCDM port, if present, switches from QCDM to voice while
* a voice call is active. */
ctx->audio_port = MM_PORT (mm_base_modem_get_port_qcdm (modem));
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
call_start (MMBaseCall *self,
GAsyncReadyCallback callback,
gpointer user_data)
setup_audio_channel (MMBaseCall *_self,
GAsyncReadyCallback callback,
gpointer user_data)
{
MMBaseModem *modem;
GTask *task;
gchar *cmd;
SetupAudioChannelContext *ctx;
MMCallHuawei *self;
GTask *task;
MMBaseModem *modem = NULL;
g_object_get (self,
MM_BASE_CALL_MODEM, &modem,
NULL);
self = MM_CALL_HUAWEI (_self);
task = g_task_new (self, NULL, callback, user_data);
g_task_set_task_data (task, modem, g_object_unref);
cmd = g_strdup_printf ("ATD%s;", mm_gdbus_call_get_number (MM_GDBUS_CALL (self)));
/* If there is no CVOICE support, no custom audio setup required
* (i.e. audio path is externally managed) */
if (!self->priv->audio_hz && !self->priv->audio_bits) {
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return;
}
ctx = g_slice_new0 (SetupAudioChannelContext);
g_object_get (self,
MM_BASE_CALL_MODEM, &ctx->modem,
NULL);
g_task_set_task_data (task, ctx, (GDestroyNotify) setup_audio_channel_context_free);
/* Enable audio streaming on the audio port */
mm_base_modem_at_command (modem,
cmd,
3,
"AT^DDSETEX=2",
5,
FALSE,
(GAsyncReadyCallback)call_start_ready,
(GAsyncReadyCallback)ddsetex_ready,
task);
g_free (cmd);
}
/*****************************************************************************/
/* In-call unsolicited events */
static void
huawei_voice_ringback_tone (MMPortSerialAt *port,
GMatchInfo *match_info,
MMCallHuawei *self)
{
guint call_x = 0;
if (!mm_get_uint_from_match_info (match_info, 1, &call_x))
return;
mm_dbg ("Ringback tone from call id '%u'", call_x);
if (mm_gdbus_call_get_state (MM_GDBUS_CALL (self)) == MM_CALL_STATE_DIALING)
mm_base_call_change_state (MM_BASE_CALL (self), MM_CALL_STATE_RINGING_OUT, MM_CALL_STATE_REASON_OUTGOING_STARTED);
}
static void
huawei_voice_call_connection (MMPortSerialAt *port,
GMatchInfo *match_info,
MMCallHuawei *self)
{
guint call_x = 0;
guint call_type = 0;
if (!mm_get_uint_from_match_info (match_info, 1, &call_x))
return;
if (!mm_get_uint_from_match_info (match_info, 2, &call_type))
return;
mm_dbg ("Call id '%u' of type '%u' connected", call_x, call_type);
if (mm_gdbus_call_get_state (MM_GDBUS_CALL (self)) == MM_CALL_STATE_RINGING_OUT)
mm_base_call_change_state (MM_BASE_CALL (self), MM_CALL_STATE_ACTIVE, MM_CALL_STATE_REASON_ACCEPTED);
}
static void
huawei_voice_call_end (MMPortSerialAt *port,
GMatchInfo *match_info,
MMCallHuawei *self)
{
guint call_x = 0;
guint duration = 0;
guint cc_cause = 0;
guint end_status = 0;
if (!mm_get_uint_from_match_info (match_info, 1, &call_x))
return;
if (!mm_get_uint_from_match_info (match_info, 2, &duration))
return;
if (!mm_get_uint_from_match_info (match_info, 3, &end_status))
return;
/* This is optional */
mm_get_uint_from_match_info (match_info, 4, &cc_cause);
mm_dbg ("Call id '%u' terminated with status '%u' and cause '%u'. Duration of call '%d'",
call_x, end_status, cc_cause, duration);
mm_base_call_change_state (MM_BASE_CALL (self), MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_TERMINATED);
}
static void
huawei_voice_received_dtmf (MMPortSerialAt *port,
GMatchInfo *match_info,
MMCallHuawei *self)
{
gchar *key;
key = g_match_info_fetch (match_info, 1);
if (!key)
return;
mm_dbg ("Received DTMF '%s'", key);
mm_base_call_received_dtmf (MM_BASE_CALL (self), key);
g_free (key);
}
static gboolean
common_setup_cleanup_unsolicited_events (MMCallHuawei *self,
gboolean enable,
GError **error)
{
MMBaseModem *modem = NULL;
GList *ports, *l;
if (G_UNLIKELY (!self->priv->conf_regex))
self->priv->conf_regex = g_regex_new ("\\r\\n\\^CONF:\\s*(\\d+)\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
if (G_UNLIKELY (!self->priv->conn_regex))
self->priv->conn_regex = g_regex_new ("\\r\\n\\^CONN:\\s*(\\d+),(\\d+)\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
if (G_UNLIKELY (!self->priv->cend_regex))
self->priv->cend_regex = g_regex_new ("\\r\\n\\^CEND:\\s*(\\d+),\\s*(\\d+),\\s*(\\d+),?\\s*(\\d*)\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
if (G_UNLIKELY (!self->priv->ddtmf_regex))
self->priv->ddtmf_regex = g_regex_new ("\\r\\n\\^DDTMF:\\s*([0-9A-D\\*\\#])\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
g_object_get (self,
MM_BASE_CALL_MODEM, &modem,
NULL);
ports = mm_broadband_modem_huawei_get_at_port_list (MM_BROADBAND_MODEM_HUAWEI (modem));
for (l = ports; l; l = g_list_next (l)) {
MMPortSerialAt *port;
port = MM_PORT_SERIAL_AT (l->data);
mm_port_serial_at_add_unsolicited_msg_handler (
port,
self->priv->conf_regex,
enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_voice_ringback_tone : NULL,
enable ? self : NULL,
NULL);
mm_port_serial_at_add_unsolicited_msg_handler (
port,
self->priv->conn_regex,
enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_voice_call_connection : NULL,
enable ? self : NULL,
NULL);
mm_port_serial_at_add_unsolicited_msg_handler (
port,
self->priv->cend_regex,
enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_voice_call_end : NULL,
enable ? self : NULL,
NULL);
mm_port_serial_at_add_unsolicited_msg_handler (
port,
self->priv->ddtmf_regex,
enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_voice_received_dtmf: NULL,
enable ? self : NULL,
NULL);
}
g_list_free_full (ports, g_object_unref);
g_object_unref (modem);
return TRUE;
}
static gboolean
setup_unsolicited_events (MMBaseCall *self,
GError **error)
{
return common_setup_cleanup_unsolicited_events (MM_CALL_HUAWEI (self), TRUE, error);
}
static gboolean
cleanup_unsolicited_events (MMBaseCall *self,
GError **error)
{
return common_setup_cleanup_unsolicited_events (MM_CALL_HUAWEI (self), FALSE, error);
}
/*****************************************************************************/
MMBaseCall *
mm_call_huawei_new (MMBaseModem *modem)
mm_call_huawei_new (MMBaseModem *modem,
MMCallDirection direction,
const gchar *number,
guint audio_hz,
guint audio_bits)
{
return MM_BASE_CALL (g_object_new (MM_TYPE_CALL_HUAWEI,
MM_BASE_CALL_MODEM, modem,
MM_BASE_CALL_MODEM, modem,
"direction", direction,
"number", number,
MM_CALL_HUAWEI_AUDIO_HZ, audio_hz,
MM_CALL_HUAWEI_AUDIO_BITS, audio_bits,
MM_BASE_CALL_SUPPORTS_DIALING_TO_RINGING, TRUE,
MM_BASE_CALL_SUPPORTS_RINGING_TO_ACTIVE, TRUE,
NULL));
}
static void
mm_call_huawei_init (MMCallHuawei *self)
{
/* Initialize private data */
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_CALL_HUAWEI, MMCallHuaweiPrivate);
}
static void
set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
MMCallHuawei *self = MM_CALL_HUAWEI (object);
switch (prop_id) {
case PROP_AUDIO_HZ:
self->priv->audio_hz = g_value_get_uint (value);
break;
case PROP_AUDIO_BITS:
self->priv->audio_bits = g_value_get_uint (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
MMCallHuawei *self = MM_CALL_HUAWEI (object);
switch (prop_id) {
case PROP_AUDIO_HZ:
g_value_set_uint (value, self->priv->audio_hz);
break;
case PROP_AUDIO_BITS:
g_value_set_uint (value, self->priv->audio_bits);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
finalize (GObject *object)
{
MMCallHuawei *self = MM_CALL_HUAWEI (object);
if (self->priv->conf_regex)
g_regex_unref (self->priv->conf_regex);
if (self->priv->conn_regex)
g_regex_unref (self->priv->conn_regex);
if (self->priv->cend_regex)
g_regex_unref (self->priv->cend_regex);
if (self->priv->ddtmf_regex)
g_regex_unref (self->priv->ddtmf_regex);
G_OBJECT_CLASS (mm_call_huawei_parent_class)->finalize (object);
}
static void
mm_call_huawei_class_init (MMCallHuaweiClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
MMBaseCallClass *base_call_class = MM_BASE_CALL_CLASS (klass);
base_call_class->start = call_start;
g_type_class_add_private (object_class, sizeof (MMCallHuaweiPrivate));
object_class->get_property = get_property;
object_class->set_property = set_property;
object_class->finalize = finalize;
base_call_class->setup_unsolicited_events = setup_unsolicited_events;
base_call_class->cleanup_unsolicited_events = cleanup_unsolicited_events;
base_call_class->setup_audio_channel = setup_audio_channel;
base_call_class->setup_audio_channel_finish = setup_audio_channel_finish;
properties[PROP_AUDIO_HZ] =
g_param_spec_uint (MM_CALL_HUAWEI_AUDIO_HZ,
"Audio Hz",
"Voice call audio hz if call audio is routed via the host",
0, 24000, 0,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
g_object_class_install_property (object_class, PROP_AUDIO_HZ, properties[PROP_AUDIO_HZ]);
properties[PROP_AUDIO_BITS] =
g_param_spec_uint (MM_CALL_HUAWEI_AUDIO_BITS,
"Audio Bits",
"Voice call audio bits if call audio is routed via the host",
0, 24, 0,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
g_object_class_install_property (object_class, PROP_AUDIO_BITS, properties[PROP_AUDIO_BITS]);
}
......@@ -28,11 +28,16 @@
#define MM_IS_CALL_HUAWEI_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_CALL_HUAWEI))
#define MM_CALL_HUAWEI_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_CALL_HUAWEI, MMCallHuaweiClass))
#define MM_CALL_HUAWEI_AUDIO_HZ "call-huawei-audio-hz"
#define MM_CALL_HUAWEI_AUDIO_BITS "call-huawei-audio-bits"
typedef struct _MMCallHuawei MMCallHuawei;
typedef struct _MMCallHuaweiClass MMCallHuaweiClass;
typedef struct _MMCallHuaweiPrivate MMCallHuaweiPrivate;
struct _MMCallHuawei {
MMBaseCall parent;
MMCallHuaweiPrivate *priv;
};
struct _MMCallHuaweiClass {
......@@ -41,6 +46,10 @@ struct _MMCallHuaweiClass {
GType mm_call_huawei_get_type (void);
MMBaseCall *mm_call_huawei_new (MMBaseModem *modem);
MMBaseCall *mm_call_huawei_new (MMBaseModem *modem,
MMCallDirection direction,
const gchar *number,
guint audio_hz,
guint audio_bits);
#endif /* MM_CALL_HUAWEI_H */
......@@ -1406,3 +1406,66 @@ done:
return ret;
}
/*****************************************************************************/
/* ^CVOICE response parser */
gboolean
mm_huawei_parse_cvoice_response (const gchar *response,
guint *out_hz,
guint *out_bits,
GError **error)
{
GRegex *r;
GMatchInfo *match_info = NULL;
GError *match_error = NULL;
guint supported = 0, hz = 0, bits = 0;
gboolean ret = FALSE;
/* ^CVOICE: <0=supported,1=unsupported>,<hz>,<bits>,<unknown> */
r = g_regex_new ("\\^CVOICE:\\s*(\\d)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)$", 0, 0, NULL);
g_assert (r != NULL);
if (!g_regex_match_full (r, response, -1, 0, 0, &match_info, &match_error)) {
if (match_error) {
g_propagate_error (error, match_error);
g_prefix_error (error, "Could not parse ^CVOICE results: ");
} else {
g_set_error_literal (error,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Couldn't match ^CVOICE reply");
}
} else {
/* Remember that g_match_info_get_match_count() includes match #0 */
g_assert (g_match_info_get_match_count (match_info) >= 5);
if (mm_get_uint_from_match_info (match_info, 1, &supported) &&
mm_get_uint_from_match_info (match_info, 2, &hz) &&
mm_get_uint_from_match_info (match_info, 3, &bits)) {
if (supported == 0) {
if (out_hz)
*out_hz = hz;
if (out_bits)
*out_bits = bits;
ret = TRUE;
} else {
g_set_error_literal (error,
MM_CORE_ERROR,
MM_CORE_ERROR_UNSUPPORTED,
"^CVOICE not supported by this device");
}
} else {
g_set_error_literal (error,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Failed to parse ^CVOICE reply");
}
}
if (match_info)
g_match_info_free (match_info);
g_regex_unref (r);
return ret;
}
......@@ -151,4 +151,12 @@ gboolean mm_huawei_parse_hcsq_response (const gchar *response,
guint *out_value5,
GError **error);
/*****************************************************************************/
/* ^CVOICE response parser */
gboolean mm_huawei_parse_cvoice_response (const gchar *response,
guint *hz,
guint *bits,
GError **error);
#endif /* MM_MODEM_HELPERS_HUAWEI_H */
......@@ -10,7 +10,7 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details:
*
* Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es>
* Copyright (C) 2016-2018 Aleksander Morgado <aleksander@aleksander.es>
*/
#include <config.h>
......@@ -25,6 +25,7 @@
#include "mm-log.h"
#include "mm-iface-modem.h"
#include "mm-iface-modem-3gpp.h"
#include "mm-iface-modem-voice.h"
#include "mm-base-modem-at.h"
#include "mm-broadband-bearer.h"
#include "mm-broadband-modem-ublox.h"
......@@ -32,11 +33,16 @@
#include "mm-sim-ublox.h"
#include "mm-modem-helpers-ublox.h"
#include "mm-ublox-enums-types.h"
#include "mm-call-ublox.h"
static void iface_modem_init (MMIfaceModem *iface);
static void iface_modem_voice_init (MMIfaceModemVoice *iface);
static MMIfaceModemVoice *iface_modem_voice_parent;
G_DEFINE_TYPE_EXTENDED (MMBroadbandModemUblox, mm_broadband_modem_ublox, MM_TYPE_BROADBAND_MODEM, 0,
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init))
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_VOICE, iface_modem_voice_init))
typedef enum {
FEATURE_SUPPORT_UNKNOWN,
......@@ -744,6 +750,135 @@ load_unlock_retries (MMIfaceModem *self,
user_data);
}
/*****************************************************************************/
/* Enabling unsolicited events (Voice interface) */
static gboolean
modem_voice_enable_unsolicited_events_finish (MMIfaceModemVoice *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
own_voice_enable_unsolicited_events_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
mm_base_modem_at_command_full_finish (self, res, &error);
if (error)
g_task_return_error (task, error);
else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
parent_voice_enable_unsolicited_events_ready (MMIfaceModemVoice *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
if (!iface_modem_voice_parent->enable_unsolicited_events_finish (self, res, &error)) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
/* Our own enable now */
mm_base_modem_at_command_full (
MM_BASE_MODEM (self),
mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
"+UCALLSTAT=1",
5,
FALSE, /* allow_cached */
FALSE, /* raw */
NULL, /* cancellable */
(GAsyncReadyCallback)own_voice_enable_unsolicited_events_ready,
task);
}
static void
modem_voice_enable_unsolicited_events (MMIfaceModemVoice *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
/* Chain up parent's enable */
iface_modem_voice_parent->enable_unsolicited_events (
self,
(GAsyncReadyCallback)parent_voice_enable_unsolicited_events_ready,
task);
}
/*****************************************************************************/
/* Disabling unsolicited events (Voice interface) */
static gboolean
modem_voice_disable_unsolicited_events_finish (MMIfaceModemVoice *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
own_voice_disable_unsolicited_events_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
mm_base_modem_at_command_full_finish (self, res, &error);
if (error)
g_task_return_error (task, error);
else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
modem_voice_disable_unsolicited_events (MMIfaceModemVoice *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
/* Note: no parent disable method */
mm_base_modem_at_command_full (
MM_BASE_MODEM (self),
mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
"+UCALLSTAT=0",
5,
FALSE, /* allow_cached */
FALSE, /* raw */
NULL, /* cancellable */
(GAsyncReadyCallback)own_voice_disable_unsolicited_events_ready,
task);
}
/*****************************************************************************/
/* Create call (Voice interface) */
static MMBaseCall *
create_call (MMIfaceModemVoice *self,
MMCallDirection direction,
const gchar *number)
{
/* New u-blox Call */
return mm_call_ublox_new (MM_BASE_MODEM (self), direction, number);
}
/*****************************************************************************/
/* Create Bearer (Modem interface) */
......@@ -1121,6 +1256,19 @@ iface_modem_init (MMIfaceModem *iface)
iface->set_current_bands_finish = common_set_current_modes_bands_finish;
}
static void
iface_modem_voice_init (MMIfaceModemVoice *iface)
{
iface_modem_voice_parent = g_type_interface_peek_parent (iface);
iface->enable_unsolicited_events = modem_voice_enable_unsolicited_events;
iface->enable_unsolicited_events_finish = modem_voice_enable_unsolicited_events_finish;
iface->disable_unsolicited_events = modem_voice_disable_unsolicited_events;
iface->disable_unsolicited_events_finish = modem_voice_disable_unsolicited_events_finish;
iface->create_call = create_call;
}
static void
finalize (GObject *object)
{
......
/* -*- 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) 2018 Aleksander Morgado <aleksander@aleksander.es>
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <ModemManager.h>
#define _LIBMM_INSIDE_MM
#include <libmm-glib.h>
#include "mm-log.h"
#include "mm-base-modem-at.h"
#include "mm-broadband-modem-ublox.h"
#include "mm-call-ublox.h"
G_DEFINE_TYPE (MMCallUblox, mm_call_ublox, MM_TYPE_BASE_CALL)
struct _MMCallUbloxPrivate {
GRegex *ucallstat_regex;
};
/*****************************************************************************/
/* In-call unsolicited events */
static void
ublox_ucallstat_received (MMPortSerialAt *port,
GMatchInfo *match_info,
MMCallUblox *self)
{
static const gchar *call_stat_names[] = {
[0] = "active",
[1] = "hold",
[2] = "dialling (MO)",
[3] = "alerting (MO)",
[4] = "ringing (MT)",
[5] = "waiting (MT)",
[6] = "disconnected",
[7] = "connected",
};
guint id;
guint stat;
if (!mm_get_uint_from_match_info (match_info, 1, &id))
return;
if (!mm_get_uint_from_match_info (match_info, 2, &stat))
return;
if (stat < G_N_ELEMENTS (call_stat_names))
mm_dbg ("u-blox call id '%u' state: '%s'", id, call_stat_names[stat]);
else
mm_dbg ("u-blox call id '%u' state unknown: '%u'", id, stat);
switch (stat) {
case 0:
/* ringing to active */
if (mm_gdbus_call_get_state (MM_GDBUS_CALL (self)) == MM_CALL_STATE_RINGING_OUT)
mm_base_call_change_state (MM_BASE_CALL (self), MM_CALL_STATE_ACTIVE, MM_CALL_STATE_REASON_ACCEPTED);
break;
case 1:
/* ignore hold state, we don't support this yet. */
break;
case 2:
/* ignore dialing state, we already handle this in the parent */
break;
case 3:
/* dialing to ringing */
if (mm_gdbus_call_get_state (MM_GDBUS_CALL (self)) == MM_CALL_STATE_DIALING)
mm_base_call_change_state (MM_BASE_CALL (self), MM_CALL_STATE_RINGING_OUT, MM_CALL_STATE_REASON_OUTGOING_STARTED);
break;
case 4:
/* ignore MT ringing state, we already handle this in the parent */
break;
case 5:
/* ignore MT waiting state */
break;
case 6:
mm_base_call_change_state (MM_BASE_CALL (self), MM_CALL_STATE_TERMINATED, MM_CALL_STATE_REASON_TERMINATED);
break;
case 7:
/* ringing to active */
if (mm_gdbus_call_get_state (MM_GDBUS_CALL (self)) == MM_CALL_STATE_RINGING_OUT)
mm_base_call_change_state (MM_BASE_CALL (self), MM_CALL_STATE_ACTIVE, MM_CALL_STATE_REASON_ACCEPTED);
break;
}
}
static gboolean
common_setup_cleanup_unsolicited_events (MMCallUblox *self,
gboolean enable,
GError **error)
{
MMBaseModem *modem = NULL;
MMPortSerialAt *port;
if (G_UNLIKELY (!self->priv->ucallstat_regex))
self->priv->ucallstat_regex = g_regex_new ("\\r\\n\\+UCALLSTAT:\\s*(\\d+),(\\d+)\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
g_object_get (self,
MM_BASE_CALL_MODEM, &modem,
NULL);
port = mm_base_modem_peek_port_primary (MM_BASE_MODEM (modem));
if (port) {
mm_dbg ("%s +UCALLSTAT URC handler", enable ? "adding" : "removing");
mm_port_serial_at_add_unsolicited_msg_handler (
port,
self->priv->ucallstat_regex,
enable ? (MMPortSerialAtUnsolicitedMsgFn)ublox_ucallstat_received : NULL,
enable ? self : NULL,
NULL);
}
g_object_unref (modem);
return TRUE;
}
static gboolean
setup_unsolicited_events (MMBaseCall *self,
GError **error)
{
return common_setup_cleanup_unsolicited_events (MM_CALL_UBLOX (self), TRUE, error);
}
static gboolean
cleanup_unsolicited_events (MMBaseCall *self,
GError **error)
{
return common_setup_cleanup_unsolicited_events (MM_CALL_UBLOX (self), FALSE, error);
}
/*****************************************************************************/
MMBaseCall *
mm_call_ublox_new (MMBaseModem *modem,
MMCallDirection direction,
const gchar *number)
{
return MM_BASE_CALL (g_object_new (MM_TYPE_CALL_UBLOX,
MM_BASE_CALL_MODEM, modem,
"direction", direction,
"number", number,
MM_BASE_CALL_SUPPORTS_DIALING_TO_RINGING, TRUE,
MM_BASE_CALL_SUPPORTS_RINGING_TO_ACTIVE, TRUE,
NULL));
}
static void
mm_call_ublox_init (MMCallUblox *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_CALL_UBLOX, MMCallUbloxPrivate);
}
static void
finalize (GObject *object)
{
MMCallUblox *self = MM_CALL_UBLOX (object);
if (self->priv->ucallstat_regex)
g_regex_unref (self->priv->ucallstat_regex);
G_OBJECT_CLASS (mm_call_ublox_parent_class)->finalize (object);
}
static void
mm_call_ublox_class_init (MMCallUbloxClass *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 (MMCallUbloxPrivate));
object_class->finalize = finalize;
base_call_class->setup_unsolicited_events = setup_unsolicited_events;
base_call_class->cleanup_unsolicited_events = cleanup_unsolicited_events;
}
/* -*- 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) 2018 Aleksander Morgado <aleksander@aleksander.es>
*/
#ifndef MM_CALL_UBLOX_H
#define MM_CALL_UBLOX_H
#include <glib.h>
#include <glib-object.h>
#include "mm-base-call.h"
#define MM_TYPE_CALL_UBLOX (mm_call_ublox_get_type ())
#define MM_CALL_UBLOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_CALL_UBLOX, MMCallUblox))
#define MM_CALL_UBLOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_CALL_UBLOX, MMCallUbloxClass))
#define MM_IS_CALL_UBLOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_CALL_UBLOX))
#define MM_IS_CALL_UBLOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_CALL_UBLOX))
#define MM_CALL_UBLOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_CALL_UBLOX, MMCallUbloxClass))
typedef struct _MMCallUblox MMCallUblox;
typedef struct _MMCallUbloxClass MMCallUbloxClass;
typedef struct _MMCallUbloxPrivate MMCallUbloxPrivate;
struct _MMCallUblox {
MMBaseCall parent;
MMCallUbloxPrivate *priv;
};
struct _MMCallUbloxClass {
MMBaseCallClass parent;
};
GType mm_call_ublox_get_type (void);
MMBaseCall *mm_call_ublox_new (MMBaseModem *modem,
MMCallDirection direction,
const gchar *number);
#endif /* MM_CALL_UBLOX_H */
This diff is collapsed.