From c203f470fecde28af255720477c85cceaaf7fdeb Mon Sep 17 00:00:00 2001 From: Bob Ham Date: Fri, 19 Oct 2018 10:19:41 +0000 Subject: [PATCH] Fix modem addition/removal and deal with ModemManager appearing/vanishing 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 --- src/calls-application.c | 15 +-- src/calls-dummy-provider.c | 27 ++++ src/calls-mm-provider.c | 266 ++++++++++++++++++++++++++++--------- src/calls-mm-provider.h | 2 +- src/calls-new-call-box.c | 26 ++++ src/calls-provider.c | 34 +++++ src/calls-provider.h | 1 + src/ui/new-call-box.ui | 12 ++ 8 files changed, 304 insertions(+), 79 deletions(-) diff --git a/src/calls-application.c b/src/calls-application.c index fcc5332..da9df6e 100644 --- a/src/calls-application.c +++ b/src/calls-application.c @@ -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; diff --git a/src/calls-dummy-provider.c b/src/calls-dummy-provider.c index 55b2d32..890d468 100644 --- a/src/calls-dummy-provider.c +++ b/src/calls-dummy-provider.c @@ -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"); } diff --git a/src/calls-mm-provider.c b/src/calls-mm-provider.c index 5b6a43c..39623be 100644 --- a/src/calls-mm-provider.c +++ b/src/calls-mm-provider.c @@ -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,18 +285,69 @@ 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 +mm_appeared_cb (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + CallsMMProvider *self) +{ + g_debug ("ModemManager appeared on D-Bus"); + + 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); } @@ -242,25 +357,53 @@ constructed (GObject *object) GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT); CallsMMProvider *self = CALLS_MM_PROVIDER (object); - mm_manager_new (self->connection, - G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, - NULL, - (GAsyncReadyCallback) mm_manager_new_cb, - self); + 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); } diff --git a/src/calls-mm-provider.h b/src/calls-mm-provider.h index d8f5c40..538fcc3 100644 --- a/src/calls-mm-provider.h +++ b/src/calls-mm-provider.h @@ -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 diff --git a/src/calls-new-call-box.c b/src/calls-new-call-box.c index 98c9bfb..9395ca9 100644 --- a/src/calls-new-call-box.c +++ b/src/calls-new-call-box.c @@ -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); diff --git a/src/calls-provider.c b/src/calls-provider.c index 6125c8d..ceb39a0 100644 --- a/src/calls-provider.c +++ b/src/calls-provider.c @@ -27,6 +27,8 @@ #include "calls-message-source.h" #include "util.h" +#include + /** * 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 diff --git a/src/calls-provider.h b/src/calls-provider.h index 564d8dd..bb5af38 100644 --- a/src/calls-provider.h +++ b/src/calls-provider.h @@ -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 diff --git a/src/ui/new-call-box.ui b/src/ui/new-call-box.ui index c4a71a9..e036e9b 100644 --- a/src/ui/new-call-box.ui +++ b/src/ui/new-call-box.ui @@ -109,5 +109,17 @@ True + + + True + False + True + + + False + True + 16 + +