...
 
Commits (2)
  • Bob Ham's avatar
    Fix modem addition/removal and deal with ModemManager appearing/vanishing · c203f470
    Bob Ham authored
    Modems being added or removed were not working.  To fix this, we pay
    attention to the "object-removed" event and not just
    "interface-removed".
    
    Also, to deal with ModemManager appearing and vanishing, we add a
    GDBus watch on ModemManager's D-Bus object.
    
    Finally, we provide appropriate UI feedback when it's not possible to
    make a call.
    
    Closes #15
    Closes #16
    c203f470
  • Bob Ham's avatar
    Merge branch 'monitor-modemmanager' into 'master' · 07cc15ce
    Bob Ham authored
    Fix modem addition/removal and deal with ModemManager appearing/vanishing
    
    Closes #15 and #16
    
    See merge request Librem5/calls!32
    07cc15ce
......@@ -52,7 +52,6 @@ struct _CallsApplication
{
GtkApplication parent_instance;
GDBusConnection *connection;
CallsProvider *provider;
};
......@@ -60,12 +59,11 @@ G_DEFINE_TYPE (CallsApplication, calls_application, GTK_TYPE_APPLICATION)
static void
finalize (GObject *object)
dispose (GObject *object)
{
CallsApplication *self = (CallsApplication *)object;
g_clear_object (&self->provider);
g_clear_object (&self->connection);
G_OBJECT_CLASS (calls_application_parent_class)->finalize (object);
}
......@@ -78,14 +76,7 @@ startup (GApplication *application)
G_APPLICATION_CLASS (calls_application_parent_class)->startup (application);
self->connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
if (!self->connection)
{
g_error ("Error creating D-Bus connection: %s", error->message);
}
self->provider = CALLS_PROVIDER (calls_mm_provider_new (self->connection));
self->provider = CALLS_PROVIDER (calls_mm_provider_new ());
g_assert (self->provider != NULL);
g_set_prgname (APP_ID);
......@@ -123,7 +114,7 @@ calls_application_class_init (CallsApplicationClass *klass)
GApplicationClass *application_class = G_APPLICATION_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = finalize;
object_class->dispose = dispose;
application_class->startup = startup;
application_class->activate = activate;
......
......@@ -44,6 +44,12 @@ G_DEFINE_TYPE_WITH_CODE (CallsDummyProvider, calls_dummy_provider, G_TYPE_OBJECT
calls_dummy_provider_provider_interface_init))
enum {
PROP_0,
PROP_STATUS,
PROP_LAST_PROP,
};
static const gchar *
get_name (CallsProvider *iface)
{
......@@ -66,6 +72,24 @@ calls_dummy_provider_new ()
}
static void
get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_STATUS:
g_value_set_string (value, "Normal");
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
dispose (GObject *object)
{
......@@ -85,6 +109,9 @@ calls_dummy_provider_class_init (CallsDummyProviderClass *klass)
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = dispose;
object_class->get_property = get_property;
g_object_class_override_property (object_class, PROP_STATUS, "status");
}
......
......@@ -35,8 +35,10 @@ struct _CallsMMProvider
{
GObject parent_instance;
/** D-Bus connection */
GDBusConnection *connection;
/* The status property */
gchar *status;
/** ID for the D-Bus watch */
guint watch_id;
/** ModemManager object proxy */
MMManager *mm;
/** Map of D-Bus object paths to origins */
......@@ -52,13 +54,11 @@ G_DEFINE_TYPE_WITH_CODE (CallsMMProvider, calls_mm_provider, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (CALLS_TYPE_PROVIDER,
calls_mm_provider_provider_interface_init))
enum {
PROP_0,
PROP_CONNECTION,
PROP_STATUS,
PROP_LAST_PROP,
};
static GParamSpec *props[PROP_LAST_PROP];
static const gchar *
......@@ -76,15 +76,61 @@ get_origins (CallsProvider *iface)
}
static void
set_status (CallsMMProvider *self,
const gchar *new_status)
{
if (strcmp (self->status, new_status) == 0)
{
return;
}
g_free (self->status);
self->status = g_strdup (new_status);
g_object_notify (G_OBJECT (self), "status");
}
static void
update_status (CallsMMProvider *self)
{
const gchar *s;
if (!self->mm)
{
s = _("ModemManager unavailable");
}
else if (g_hash_table_size (self->origins) == 0)
{
s = _("No voice-capable modem available");
}
else
{
s = _("Normal");
}
set_status (self, s);
}
static void
add_origin (CallsMMProvider *self,
GDBusObject *object)
{
MMObject *mm_obj;
CallsMMOrigin *origin;
const gchar *path;
g_debug ("Adding new voice-cable modem `%s'",
g_dbus_object_get_object_path (object));
path = g_dbus_object_get_object_path (object);
if (g_hash_table_contains (self->origins, path))
{
g_warning ("New voice interface on existing"
" origin with path `%s'", path);
return;
}
g_debug ("Adding new voice-capable modem `%s'",
path);
g_assert (MM_IS_OBJECT (object));
mm_obj = MM_OBJECT (object);
......@@ -97,6 +143,7 @@ add_origin (CallsMMProvider *self,
g_signal_emit_by_name (CALLS_PROVIDER (self),
"origin-added", origin);
update_status (self);
}
......@@ -109,6 +156,10 @@ interface_added_cb (CallsMMProvider *self,
info = g_dbus_interface_get_info (interface);
g_debug ("ModemManager interface `%s' found on object `%s'",
info->name,
g_dbus_object_get_object_path (object));
if (g_strcmp0 (info->name,
"org.freedesktop.ModemManager1.Modem.Voice") == 0)
{
......@@ -118,21 +169,26 @@ interface_added_cb (CallsMMProvider *self,
static void
remove_origin (CallsMMProvider *self,
GDBusObject *object)
remove_modem_object (CallsMMProvider *self,
const gchar *path,
GDBusObject *object)
{
const gchar *path;
gpointer *origin;
path = g_dbus_object_get_object_path (object);
origin = g_hash_table_lookup (self->origins, path);
g_assert (origin != NULL && CALLS_IS_ORIGIN (origin));
if (!origin)
{
return;
}
g_assert (CALLS_IS_ORIGIN (origin));
g_signal_emit_by_name (CALLS_PROVIDER (self),
"origin-removed", CALLS_ORIGIN (origin));
g_hash_table_remove (self->origins, path);
update_status (self);
}
......@@ -141,39 +197,23 @@ interface_removed_cb (CallsMMProvider *self,
GDBusObject *object,
GDBusInterface *interface)
{
const gchar *path;
GDBusInterfaceInfo *info;
path = g_dbus_object_get_object_path (object);
info = g_dbus_interface_get_info (interface);
g_debug ("ModemManager interface `%s' removed on object `%s'",
info->name, path);
if (g_strcmp0 (info->name,
"org.freedesktop.ModemManager1.Modem.Voice") != 0)
{
remove_origin (self, object);
remove_modem_object (self, path, object);
}
}
static void
set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
CallsMMProvider *self = CALLS_MM_PROVIDER (object);
switch (property_id) {
case PROP_CONNECTION:
g_set_object (&self->connection,
g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
add_mm_object (CallsMMProvider *self, GDBusObject *object)
{
......@@ -205,6 +245,30 @@ add_mm_objects (CallsMMProvider *self)
}
void
object_added_cb (CallsMMProvider *self,
GDBusObject *object)
{
g_debug ("ModemManager object `%s' added",
g_dbus_object_get_object_path (object));
add_mm_object (self, object);
}
void
object_removed_cb (CallsMMProvider *self,
GDBusObject *object)
{
const gchar *path;
path = g_dbus_object_get_object_path (object);
g_debug ("ModemManager object `%s' removed", path);
remove_modem_object (self, path, object);
}
static void
mm_manager_new_cb (GDBusConnection *connection,
GAsyncResult *res,
......@@ -221,46 +285,125 @@ mm_manager_new_cb (GDBusConnection *connection,
}
g_signal_connect_swapped (self->mm, "interface-added",
g_signal_connect_swapped (G_DBUS_OBJECT_MANAGER (self->mm),
"interface-added",
G_CALLBACK (interface_added_cb), self);
g_signal_connect_swapped (self->mm, "interface-removed",
g_signal_connect_swapped (G_DBUS_OBJECT_MANAGER (self->mm),
"interface-removed",
G_CALLBACK (interface_removed_cb), self);
g_signal_connect_swapped (G_DBUS_OBJECT_MANAGER (self->mm),
"object-added",
G_CALLBACK (object_added_cb), self);
g_signal_connect_swapped (G_DBUS_OBJECT_MANAGER (self->mm),
"object-removed",
G_CALLBACK (object_removed_cb), self);
update_status (self);
add_mm_objects (self);
if (g_hash_table_size (self->origins) == 0)
{
g_warning ("No modem with voice capability available");
CALLS_EMIT_MESSAGE (self, "No modems available",
GTK_MESSAGE_WARNING);
}
}
static void
constructed (GObject *object)
mm_appeared_cb (GDBusConnection *connection,
const gchar *name,
const gchar *name_owner,
CallsMMProvider *self)
{
GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT);
CallsMMProvider *self = CALLS_MM_PROVIDER (object);
g_debug ("ModemManager appeared on D-Bus");
mm_manager_new (self->connection,
mm_manager_new (connection,
G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
NULL,
(GAsyncReadyCallback) mm_manager_new_cb,
self);
}
static gboolean
remove_origins_cb (const gchar *path,
CallsMMOrigin *origin,
CallsMMProvider *self)
{
g_signal_emit_by_name (CALLS_PROVIDER (self),
"origin-removed", CALLS_ORIGIN (origin));
return TRUE;
}
static void
clear_dbus (CallsMMProvider *self)
{
g_hash_table_foreach_remove (self->origins,
(GHRFunc)remove_origins_cb,
self);
g_clear_object (&self->mm);
}
void
mm_vanished_cb (GDBusConnection *connection,
const gchar *name,
CallsMMProvider *self)
{
g_debug ("ModemManager vanished from D-Bus");
clear_dbus (self);
update_status (self);
}
static void
constructed (GObject *object)
{
GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT);
CallsMMProvider *self = CALLS_MM_PROVIDER (object);
self->watch_id =
g_bus_watch_name (G_BUS_TYPE_SYSTEM,
MM_DBUS_SERVICE,
G_BUS_NAME_WATCHER_FLAGS_AUTO_START,
(GBusNameAppearedCallback)mm_appeared_cb,
(GBusNameVanishedCallback)mm_vanished_cb,
self, NULL);
g_debug ("Watching for ModemManager");
parent_class->constructed (object);
}
static void
get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
CallsMMProvider *self = CALLS_MM_PROVIDER (object);
switch (property_id) {
case PROP_STATUS:
g_value_set_string (value, self->status);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
dispose (GObject *object)
{
GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT);
CallsMMProvider *self = CALLS_MM_PROVIDER (object);
g_hash_table_remove_all (self->origins);
g_clear_object (&self->mm);
g_clear_object (&self->connection);
if (self->watch_id)
{
g_bus_unwatch_name (self->watch_id);
self->watch_id = 0;
}
clear_dbus (self);
parent_class->dispose (object);
}
......@@ -273,6 +416,7 @@ finalize (GObject *object)
CallsMMProvider *self = CALLS_MM_PROVIDER (object);
g_hash_table_unref (self->origins);
g_free (self->status);
parent_class->finalize (object);
}
......@@ -283,19 +427,12 @@ calls_mm_provider_class_init (CallsMMProviderClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->set_property = set_property;
object_class->constructed = constructed;
object_class->get_property = get_property;
object_class->dispose = dispose;
object_class->finalize = finalize;
props[PROP_CONNECTION] =
g_param_spec_object ("connection",
_("Connection"),
_("The D-Bus connection to use for communication with ModemManager"),
G_TYPE_DBUS_CONNECTION,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY);
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
g_object_class_override_property (object_class, PROP_STATUS, "status");
}
......@@ -316,17 +453,14 @@ calls_mm_provider_provider_interface_init (CallsProviderInterface *iface)
static void
calls_mm_provider_init (CallsMMProvider *self)
{
self->status = g_strdup (_("Initialised"));
self->origins = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, g_object_unref);
}
CallsMMProvider *
calls_mm_provider_new (GDBusConnection *connection)
calls_mm_provider_new ()
{
g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
return g_object_new (CALLS_TYPE_MM_PROVIDER,
"connection", connection,
NULL);
return g_object_new (CALLS_TYPE_MM_PROVIDER, NULL);
}
......@@ -34,7 +34,7 @@ G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE (CallsMMProvider, calls_mm_provider, CALLS, MM_PROVIDER, GObject);
CallsMMProvider *calls_mm_provider_new (GDBusConnection *connection);
CallsMMProvider *calls_mm_provider_new ();
G_END_DECLS
......
......@@ -38,6 +38,8 @@ struct _CallsNewCallBox
GtkListStore *origin_store;
GtkComboBox *origin_box;
GtkSearchEntry *number_entry;
GtkButton *dial;
GtkLabel *status;
};
G_DEFINE_TYPE (CallsNewCallBox, calls_new_call_box, GTK_TYPE_BOX);
......@@ -108,6 +110,21 @@ dial_clicked_cb (CallsNewCallBox *self,
}
void
notify_status_cb (CallsNewCallBox *self,
GParamSpec *pspec,
CallsProvider *provider)
{
gchar *status;
g_assert (CALLS_IS_PROVIDER (provider));
status = calls_provider_get_status (provider);
gtk_label_set_text (self->status, status);
g_free (status);
}
void
update_origin_box (CallsNewCallBox *self)
{
......@@ -117,11 +134,16 @@ update_origin_box (CallsNewCallBox *self)
if (!gtk_tree_model_get_iter_first (origin_store, &iter))
{
gtk_widget_hide (GTK_WIDGET (self->origin_box));
gtk_widget_set_sensitive (GTK_WIDGET (self->dial), FALSE);
gtk_widget_set_visible (GTK_WIDGET (self->status), TRUE);
return;
}
/* We know there is at least one origin. */
gtk_widget_set_sensitive (GTK_WIDGET (self->dial), TRUE);
gtk_widget_set_visible (GTK_WIDGET (self->status), FALSE);
if (!gtk_tree_model_iter_next (origin_store, &iter))
{
gtk_combo_box_set_active (self->origin_box, 0);
......@@ -205,6 +227,8 @@ add_provider_origins (CallsNewCallBox *self, CallsProvider *provider)
static void
set_provider (CallsNewCallBox *self, CallsProvider *provider)
{
g_signal_connect_swapped (provider, "notify::status",
G_CALLBACK (notify_status_cb), self);
g_signal_connect_swapped (provider, "origin-added",
G_CALLBACK (add_origin), self);
g_signal_connect_swapped (provider, "origin-removed",
......@@ -282,6 +306,8 @@ calls_new_call_box_class_init (CallsNewCallBoxClass *klass)
gtk_widget_class_bind_template_child (widget_class, CallsNewCallBox, origin_store);
gtk_widget_class_bind_template_child (widget_class, CallsNewCallBox, origin_box);
gtk_widget_class_bind_template_child (widget_class, CallsNewCallBox, number_entry);
gtk_widget_class_bind_template_child (widget_class, CallsNewCallBox, dial);
gtk_widget_class_bind_template_child (widget_class, CallsNewCallBox, status);
gtk_widget_class_bind_template_callback (widget_class, dial_clicked_cb);
gtk_widget_class_bind_template_callback (widget_class, dial_pad_deleted_cb);
gtk_widget_class_bind_template_callback (widget_class, dial_pad_symbol_clicked_cb);
......
......@@ -27,6 +27,8 @@
#include "calls-message-source.h"
#include "util.h"
#include <glib/gi18n.h>
/**
* SECTION:calls-provider
* @short_description: An abstraction of call providers, such as
......@@ -43,6 +45,14 @@
G_DEFINE_INTERFACE (CallsProvider, calls_provider, CALLS_TYPE_MESSAGE_SOURCE);
enum {
PROP_0,
PROP_STATUS,
PROP_LAST_PROP,
};
static GParamSpec *props[PROP_LAST_PROP];
enum {
SIGNAL_ORIGIN_ADDED,
SIGNAL_ORIGIN_REMOVED,
......@@ -55,6 +65,15 @@ calls_provider_default_init (CallsProviderInterface *iface)
{
GType arg_types = CALLS_TYPE_ORIGIN;
props[PROP_STATUS] =
g_param_spec_string ("status",
_("Status"),
_("A text string describing the status for display to the user"),
"",
G_PARAM_READABLE);
g_object_interface_install_property (iface, props[PROP_STATUS]);
signals[SIGNAL_ORIGIN_ADDED] =
g_signal_newv ("origin-added",
G_TYPE_FROM_INTERFACE (iface),
......@@ -86,6 +105,21 @@ calls_provider_default_init (CallsProviderInterface *iface)
*/
DEFINE_PROVIDER_FUNC(get_name, const gchar *, NULL);
gchar *
calls_provider_get_status (CallsProvider *self)
{
gchar *status;
g_return_val_if_fail (CALLS_IS_PROVIDER (self), NULL);
g_object_get (G_OBJECT (self),
"status", &status,
NULL);
return status;
}
/**
* calls_provider_get_origins:
* @self: a #CallsProvider
......
......@@ -45,6 +45,7 @@ struct _CallsProviderInterface
const gchar * calls_provider_get_name (CallsProvider *self);
gchar * calls_provider_get_status (CallsProvider *self);
GList * calls_provider_get_origins (CallsProvider *self);
G_END_DECLS
......
......@@ -109,5 +109,17 @@
<property name="fill">True</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="status">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="wrap">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">16</property>
</packing>
</child>
</template>
</interface>